/*
 * status_serve.c - Contains functions that supply STATUS service for NetBEUI
 *                  protocol stack, and also some utility functions.
 *
 * Notes:
 *	- VRP in comments is the acronym of "Value Result Parameter"
 *	- EHF in comments is the acronym of "Event Handling Function".
 *
 * Copyright (c) 1997 by Procom Technology,Inc.
 *
 * This program can be redistributed or modified under the terms of the 
 * GNU General Public License as published by the Free Software Foundation.
 * This program is distributed without any warranty or implied warranty
 * of merchantability or fitness for a particular purpose.
 *
 * See the GNU General Public License for more details.
 *
 */
 


#include <asm/types.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/if_arp.h>
#include <linux/errno.h>
#include <linux/timer.h>
#include <linux/skbuff.h>
#include <linux/netbeui.h>



static void
nbst_timer_function (unsigned long);


/* These functions are STATUS State Transition handlers */
static int
nbst_status_query_in_initial (status_t *);

static int
nbst_retry_timeout_in_all (status_t *);

static int
nbst_response_timeout_in_all (status_t *);

static int
nbst_status_response_in_respwait (status_t *);

static int
nbst_incomp_response_in_respwait (status_t *);


#define NBST_LENGTH_MASK	0x3FFF
#define NBST_OVERFLOW_MASK	0x4000
#define NBST_FRAGMENT_MASK	0x8000

/* List of adapters bound to */
extern struct device   *adapters[];

/* DataGram BroadCast Maximum Transfer Unit */
extern unsigned int   dgbc_mtu;

/* InterRoutines flags buffer */
static unsigned long   FLAGS;

static unsigned long   jiffies_at_reset;

static unsigned short int   nbst_correlator = 0;
#define nbst_next_correlator()   (++nbst_correlator)

static status_t   *status_request_list = NULL;

typedef int (* status_event_handler_t)(status_t *);

struct event_struct {
	status_state_t   next_state;
	status_event_handler_t   event_handler;
};

static struct event_struct 
status_state_table[2][5] = {
			/* NBS_STAT_INITIAL */
   {
    {NBS_STAT_RESPWAIT, nbst_status_query_in_initial},	/* NBE_STAT_STATUS_QUERY     */  
    {-1, NULL},				/* NBE_STAT_RETRY_TIMEOUT    */
    {-1, NULL},				/* NBE_STAT_RESPONSE_TIMEOUT */ 
    {-1, NULL},				/* NBE_STAT_STATUS_RESPONSE  */
    {-1, NULL}				/* NBE_STAT_INCOMP_RESPONSE  */
   },
			/* NBS_STAT_RESPWAIT */
   {
    {-1, NULL},				/* NBE_STAT_STATUS_QUERY     */  
    {NBS_STAT_RESPWAIT, nbst_retry_timeout_in_all},	  /* NBE_STAT_RETRY_TIMEOUT    */
    {NBS_STAT_INITIAL, nbst_response_timeout_in_all},	  /* NBE_STAT_RESPONSE_TIMEOUT */ 
    {NBS_STAT_INITIAL, nbst_status_response_in_respwait}, /* NBE_STAT_STATUS_RESPONSE  */
    {NBS_STAT_RESPWAIT, nbst_incomp_response_in_respwait} /* NBE_STAT_INCOMP_RESPONSE  */
   }
};

static nb_status_buffer_t   nb_status_record;


/*
 * Internal NBST functions 
 */

/*
 * Function: nbst_alloc_status
 *	Allocates a 'nb_status' structure and initializes its fields.
 *
 * Parameters: none
 *
 * Returns: status_t *
 *       non NULL : address of allocated nb_status.
 *       NULL     : if can not allocate nb_status, or initialize its fields
 *	           properly.
 */

static inline status_t *
nbst_alloc_status (void)
{
	status_t   *nb_status;
	int   status_dgram_len;

	nb_status = kmalloc(sizeof(status_t), GFP_KERNEL);
	if (!nb_status)
		return NULL;

	/* Implicitly initialize all fields */
	memset(nb_status, 0, sizeof(status_t));

	/* Allocate status skb */
	status_dgram_len = nb_command_header_len[STATUS_QUERY];  
	nb_status->tx_skb = alloc_skb(CALC_DG_SKBLEN(MAC_B_HEADLEN, status_dgram_len),
								GFP_KERNEL);
	if (!nb_status->tx_skb) {
		kfree(nb_status);
		return NULL;
	}

	skb_reserve(nb_status->tx_skb, LLCMAC_UIB_HEADLEN());
	skb_put(nb_status->tx_skb, status_dgram_len);

	nb_status->tx_skb->free = 0; /* assurance of keeping our sk_buff */
	nb_status->tx_skb->dev = NULL;

	init_timer(&nb_status->timer);
	nb_status->timer.data = (unsigned long) nb_status;
	nb_status->timer.function = nbst_timer_function;

	return nb_status;
}


/*
 * Function: nbst_free_status
 *	Frees a nb_status structure and its sk_buff.
 *
 * Parameters:
 *	nb_status : pointer to nb_status structure that must be freed.
 *
 * Returns: none
 */

static inline void
nbst_free_status (status_t *nb_status)
{
	kfree_skb(nb_status->tx_skb, 0);
	kfree(nb_status);

	return;
}


/*
 * Function: nbst_add_status_to_list
 *	Adds a nb_status structure to the 'status_request_list'.
 *
 * Parameters:
 *	nb_status : pointer to nb_status structure that must be added to the
 *	            list.
 *
 * Returns: none
 */

static inline void
nbst_add_status_to_list (status_t *nb_status)
{
	nb_status->next = status_request_list;
	status_request_list = nb_status;

	return;
}


/*
 * Function: nbst_remove_status_from_list
 *	Removes a nb_status structure from the 'status_request_list'.
 *
 * Parameters:
 *       nb_status : pointer to nb_status structure that must be removed from
 *	            the list.
 *
 * Returns: none
 */

static inline void
nbst_remove_status_from_list (status_t *nb_status)
{
	status_t   *entry,
	           *prev_entry = NULL;

	for (entry = status_request_list ; entry ; entry = entry->next) {
		if (entry == nb_status) {
			if (prev_entry)
				prev_entry->next = entry->next;
			else
				status_request_list = entry->next;

			break;
		}

		prev_entry = entry;
	}

	return;
}


/*
 * Function: nbst_find_correlator
 *	Finds a nb_status in the 'status_request_list' that have a specific
 *	response correlator.
 *
 * Parameters:
 *	correlator : the response correlator that we search for it.
 *
 * Returns: status_t *
 *       non NULL : address of sightly nb_status in the list.
 *       NULL     : if no match be found.
 */

static status_t *
nbst_find_correlator (__u16 correlator)
{
	status_t   *nb_status;
	
	for (nb_status = status_request_list ; nb_status ; nb_status = nb_status->next)
		if (nb_status->resp_correlator == correlator)
			return nb_status;

	return NULL;
}


/*
 * Function: nbst_handle_event
 *	Handles an entrant event to STATUS state machine.
 *
 * Parameters:
 *	event     : the entrant event.
 *	nb_status : (Semi VRP!) pointer to nb_status structure (entity) that
 *	            event is for it.
 *
 * Returns: none
 */

static void
nbst_handle_event (status_event_t event, status_t *nb_status)
{
	struct event_struct   *ev;

	ev = &status_state_table[nb_status->state][event];
	if ((ev) && (ev->event_handler))
		if (ev->event_handler(nb_status) == 0)
			nb_status->state = ev->next_state;

	return;
}


/*
 * Function: nbst_timer_function (Call back function)
 *	Produces an appropriate event related to number of retries
 *	so far, due to fire of a timer.
 *
 * Parameters:
 *	input : pointer to nb_status structure that timer of it has been fired.
 *
 * Returns: none
 */

static void 
nbst_timer_function (unsigned long input)
{
	status_t   *nb_status = (status_t *) input;

	if (nb_status->retries < NB_TRANSMIT_COUNT)
		nbst_handle_event(NBE_STAT_RETRY_TIMEOUT, nb_status);
	else
		nbst_handle_event(NBE_STAT_RESPONSE_TIMEOUT, nb_status);
		
	return;
}


/*
 * Function: nbst_status_query_in_initial
 *	(EHF) Handles NBE_STAT_STATUS_QUERY event in NBS_STAT_INITIAL state.
 *
 * Parameters:
 *	nb_status : pointer to nb_status structure that event happens on it.
 *
 * Returns: int
 *	zero     : if event be handled successfully.
 *	non zero : if an error occured during event handling.
 */

static int
nbst_status_query_in_initial (status_t *nb_status)
{
	int   rc;
	dgram_t   *hdrp;

	/* Implicitly was set to zero in nbst_alloc_status() */
//	nb_status->retries = 0;
//	nb_status->len_rx_info = 0;

	nb_status->resp_status = NO_RESPONSE;
	
	hdrp = (dgram_t *) nb_status->tx_skb->data;
	hdrp->length = nb_command_header_len[STATUS_QUERY];
	hdrp->delimiter = NB_DELIMITER;
	hdrp->command = STATUS_QUERY;

	/* We implement NetBIOS 3.0 */
	hdrp->data1 = 1;
	hdrp->data2 = nb_status->sbuff_len;

	hdrp->resp_correlator = nb_status->resp_correlator = nbst_next_correlator();

	memcpy_fromfs(hdrp->dest_name, nb_status->called_name, NB_NAME_LEN);
	memcpy(hdrp->source_name, (nbns_name_number_1())->name, NB_NAME_LEN);

	/* BroadCast STATUS_QUERY */
	rc = nbll_uisend(NULL, nb_status->tx_skb);
	if (rc)
		return rc;

	nb_status->retries++;

	nb_status->timer.expires = jiffies + NB_TRANSMIT_TIMEOUT;
	add_timer(&nb_status->timer);

	save_flags(FLAGS); /* Restore in 'nbst_wait_for_resp()' */
	cli();

	nbst_add_status_to_list(nb_status);

	return 0;
}


/*
 * Function: nbst_retry_timeout_in_all
 *	(EHF) Handles NBE_STAT_RETRY_TIMEOUT event in all of states.
 *
 * Parameters:
 *	nb_status : pointer to nb_status structure that event happens on it.
 *
 * Returns: int
 *	zero     : if event be handled successfully.
 *	non zero : if an error occured during event handling.
 */

static int
nbst_retry_timeout_in_all (status_t *nb_status)
{
	int   rc;

	nb_status->timer.expires = jiffies+ NB_TRANSMIT_TIMEOUT;
	add_timer(&nb_status->timer);

	if (nb_status->unicast) {
		/* UniCast STATUS_QUERY */
		rc = nbll_uisend(nb_status->remote_mac, nb_status->tx_skb);
		if (rc)
			return rc;

		skb_pull(nb_status->tx_skb,
		         LLCMAC_UI_HEADLEN(MAC_HEADLEN(nb_status->tx_skb->dev)));
	}
	else {
		/* BroadCast STATUS_QUERY */
		rc = nbll_uisend(NULL, nb_status->tx_skb);
		if (rc)
			return rc;
	}
		
	nb_status->retries++; 
   
	return 0;
}


/*
 * Function: nbst_response_timeout_in_all
 *	(EHF) Handles NBE_STAT_RESPONSE_TIMEOUT event in all of states.
 *
 * Parameters:
 *	nb_status : pointer to nb_status structure that event happens on it.
 *
 * Returns: int
 *	zero     : this function always succeed.
 */

static int
nbst_response_timeout_in_all (status_t *nb_status)
{
	nbst_remove_status_from_list(nb_status);

	wake_up(&nb_status->waitq);

	return 0;
}


/*
 * Function: nbst_status_response_in_respwait
 *	(EHF) Handles NBE_STAT_STATUS_RESPONSE event in NBS_STAT_RESPWAIT state.
 *
 * Parameters:
 *	nb_status : pointer to nb_status structure that event happens on it.
 *
 * Returns: int
 *	zero     : this function always succeed.
 */

static int 
nbst_status_response_in_respwait (status_t *nb_status)
{
	del_timer(&nb_status->timer);

	nbst_remove_status_from_list(nb_status);

	if (nb_status->overflowed)
		nb_status->resp_status = USER_BUFFER_OVERFLOW;
	else
		nb_status->resp_status = COMPLETED_RESPONSE;

	return 0;
}


/*
 * Function: nbst_incomp_response_in_respwait
 *	(EHF) Handles NBE_STAT_INCOMP_RESPONSE event in NBS_STAT_RESPWAIT state.
 *
 * Parameters:
 *	nb_status : pointer to nb_status structure that event happens on it.
 *
 * Returns: int
 *	zero     : if event be handled successfully.
 *	non zero : if an error occured during event handling.
 */

static int
nbst_incomp_response_in_respwait (status_t *nb_status)
{
	int   rc;

	del_timer(&nb_status->timer);

	nb_status->unicast = 1;
	nb_status->retries = 0;
	nb_status->resp_status = INCOMPLETE_RESPONSE;
	((dgram_t *) (nb_status->tx_skb->data))->data1 = nb_status->no_rx_names;
	((dgram_t *) (nb_status->tx_skb->data))->data2 = nb_status->sbuff_len -
	                                                 nb_status->len_rx_info;

	nb_status->timer.expires = jiffies + NB_TRANSMIT_TIMEOUT;
	add_timer(&nb_status->timer);

	rc = nbll_uisend(nb_status->remote_mac, nb_status->tx_skb);
	if (rc)
		return rc;

	skb_pull(nb_status->tx_skb,
		LLCMAC_UI_HEADLEN(MAC_HEADLEN(nb_status->tx_skb->dev)));
	nb_status->retries++;

	return 0;
}


/*
 * Function: nbst_gather_status_info
 *	Gathers NetBIOS status information in local machine.
 *
 * Parameters:
 *	dev : pointer to struct device that its related status information
 *	      has been requested.
 *
 * Returns: int
 *	positive : length of gathered status information. this function always
 *	           succeed.
 */

static int 
nbst_gather_status_info (struct device *dev)
{
	int   i;
	__u8   tmp8;
	__u16   non;
	name_t   *ntp;
	unsigned long   tmpl;

	if (!dev)
		dev = adapters[0];

	if (dev) {
		memcpy(nb_status_record.adptr_addr, dev->dev_addr, 6);

		switch (dev->type) {
			case ARPHRD_IEEE802:
				nb_status_record.adptr_type_AND_sftwr_level\
				                       .adptr_type = TOKEN_RING;
				break;
			
			case ARPHRD_ETHER:
			case ARPHRD_EETHER:
				nb_status_record.adptr_type_AND_sftwr_level\
				                       .adptr_type = ETHERNET;
		}
	}
	else {
		memset(nb_status_record.adptr_addr, 0, 6);
		nb_status_record.adptr_type_AND_sftwr_level.adptr_type = 0;
	}

	/* We support new parameters , NetBIOS 3.0 */
	nb_status_record.adptr_type_AND_sftwr_level.sftwr_level = 0x20;

	nb_status_record.sftwr_release_no = 3; /* We implement NetBIOS 3.0 */

	tmpl = (jiffies - jiffies_at_reset)/(HZ*60);
	nb_status_record.duration = (tmpl > 0xFFFF) ? 0xFFFF : tmpl;

	nb_status_record.max_dgram_packet_size = dgbc_mtu;

	nb_status_record.max_no_pend_sess = 0xFFFF; /* No limit! */

	nb_status_record.max_size_sess_data_packet = dev ?
	                              (dev->mtu - LLCMAC_I_HEADLEN(dev) - NETB_ILEN) : 0 ;

	non = nbns_count_names();
	nb_status_record.no_names_in_local_name_tbl = non;

	ntp = nbns_get_name_list();
	for (i=0 ; i < non ; i++ , ntp = ntp->next) {
		memcpy(nb_status_record.local_names[i].name, ntp->name, NB_NAME_LEN);
		nb_status_record.local_names[i].name_number = ntp->name_number;

		tmp8 = (ntp->type == NB_NAME_GROUP) ? 0x80 : 0x00;
		switch (ntp->state) {
			case NB_NAME_ADDWAIT:  /* Try to register a name */
//				tmp8 |= 0x00;
				break;

			case NB_NAME_ACQUIRED: /* A registered name */
				tmp8 |= 0x04;
				break;

			case NB_NAME_INITIAL:  /* A deregistered name */
				tmp8 |= 0x05;

			default:	/* Only for silence of the gcc! */
		}
		if (ntp->conflicted)           /* A detected duplicate name */
			tmp8 |= 0x06;

		nb_status_record.local_names[i].name_status = tmp8;
	}

	return (NB_MIN_STATUS_BUFF_LEN + (NB_NAME_LEN + 2) * non);
}


/*
 * Function: nbst_local_status
 *	Retrieves local NetBIOS status information.
 *
 * Parameters:
 *	status_buff : pointer to user buffer that must be filled with local
 *	              status information.
 *	buff_len    : (VRP) pointer to an integer that indicates length of
 *	              user buffer at start and length of retrieved information
 *	              at end.
 *
 * Returns: int
 *	zero     : if information retrieved successfully.
 *	negative : if something is bad.
 *	           (-EOVERFLOW) : user buffer length is not large enough to
 *	                          keep all of information, so some of it was
 *	                          copied to user buffer.
 */

static inline int 
nbst_local_status (char *status_buff, int *buff_len)
{
	int   len,
	      info_len;

	len = get_user(buff_len);
	info_len = nbst_gather_status_info(NULL);
	len = MIN(len, info_len);
	memcpy_tofs(status_buff, &nb_status_record, len);
	put_user(len, buff_len);

	return ((len < info_len) ? (-EOVERFLOW) : 0);
}


/*
 * Function: nbst_wait_for_resp
 *	Waits for end of STATUS state machine operation, and supervise
 *	received status response frames.
 *
 * Parameters:
 *	nb_status : pointer to nb_status structure that operations perform
 *	            on it.
 *
 * Returns: none
 */

static inline void
nbst_wait_for_resp (status_t *nb_status)
{
	unsigned long   flags;

	sleep_on(&nb_status->waitq);

	nb_status->locked = 1;
	barrier();

	restore_flags(FLAGS);

	while (nb_status->rx_skb) {
		int   sdl,
		      tmp;
		char   *sdp;
		dgram_t   *hdrp = (dgram_t *) nb_status->rx_skb->data;

		nb_status->overflowed = hdrp->data2 & NBST_OVERFLOW_MASK;

		/* Pointer to the status data in the received frame */
		sdp = skb_pull(nb_status->rx_skb, NETB_UILEN);

		sdl = hdrp->data2 & NBST_LENGTH_MASK;

		tmp = (nb_status->len_rx_info) ? (sdl) : (sdl - NB_MIN_STATUS_BUFF_LEN);
		nb_status->no_rx_names += (tmp / (NB_NAME_LEN + 2));

		nb_status->len_rx_info += sdl;

		if (nb_status->len_rx_info < nb_status->sbuff_len) {
			memcpy_tofs(nb_status->user_sbuff, (char *) sdp, sdl);

			if ((hdrp->data2 & NBST_FRAGMENT_MASK) && hdrp->data1)
				nbst_handle_event(NBE_STAT_INCOMP_RESPONSE, nb_status);
			else
				nbst_handle_event(NBE_STAT_STATUS_RESPONSE, nb_status);
		}
		else {
			sdl -= (nb_status->len_rx_info - nb_status->sbuff_len);
			memcpy_tofs(nb_status->user_sbuff, sdp, sdl);

			nbst_handle_event(NBE_STAT_STATUS_RESPONSE, nb_status);
		}

		nb_status->user_sbuff += sdl;

		kfree_skb(nb_status->rx_skb, 0);
		nb_status->rx_skb = NULL;

		if (nb_status->state == NBS_STAT_RESPWAIT) {
			save_flags(flags);
			cli();

			barrier();
			nb_status->locked = 0;

			sleep_on(&nb_status->waitq);

			restore_flags(flags);
		}
	}

	return;
}


/*
 * Function: nbst_remote_status
 *	Retrieves NetBIOS status information of a remote host.
 *
 * Parameters:
 *	remote_name : pointer to a NetBIOS name that must retrieve status
 *	              information about it.
 *	status_buff : (VRP) pointer to user buffer that must be filled with
 *	              status information of remote host.
 *	buff_len    : (VRP) pointer to an integer that indicates length of
 *	              user buffer at start and length of retrieved information
 *	              at end.
 *
 * Returns: int
 *	zero     : if information retrieved successfully.
 *	negative : if something is bad.
 *	           (-ENONET)       : no network device found.
 *	           (-ENOMEM)       : out of memory condition.
 *	           (-EHOSTUNREACH) : no response from remote host.
 *	           (-ETIMEDOUT)    : system timed out before retrieving all of
 *	                             information, so some of it was copied to
 *	                             user buffer.
 *	           (-EOVERFLOW)    : user buffer length is not large enough to
 *	                             keep all of information, so some of it was
 *	                             copied to user buffer.
 */

static inline int 
nbst_remote_status (char *remote_name, char *status_buff, int *buff_len)
{
	int   rc;
	status_t   *nb_status;

	if (!adapters[0])
		return (-ENONET);

	nb_status = nbst_alloc_status();
	if (!nb_status)
		return (-ENOMEM);

	nb_status->called_name = remote_name;
	nb_status->user_sbuff = status_buff;
	nb_status->sbuff_len = get_user(buff_len);

	nb_status->state = NBS_STAT_INITIAL;

	nbst_handle_event(NBE_STAT_STATUS_QUERY, nb_status);

	if (nb_status->state != NBS_STAT_INITIAL)
		nbst_wait_for_resp(nb_status);

	put_user(MIN(nb_status->sbuff_len, nb_status->len_rx_info), buff_len);

	switch (nb_status->resp_status) {
		case NO_RESPONSE:
			rc = -EHOSTUNREACH;
			break;
		case INCOMPLETE_RESPONSE:
			rc = -ETIMEDOUT;
			break;
		case USER_BUFFER_OVERFLOW:
			rc = -EOVERFLOW;

		default: /* COMPLETED_RESPONSE */
			rc = 0;
	}

	nbst_free_status(nb_status);

	return rc;
}


/******************************************************************************/
/************************ Exported  N B S T  Functions ************************/
/******************************************************************************/

/*
 * Function: nbst_init_status
 *	Performs some initializes for STATUS service functions during
 *	installation of NetBEUI in memory.
 *
 * Parameters: none
 *
 * Returns: none
 */

void
nbst_init_status (void)
{
	memset(&nb_status_record, 0, sizeof(nb_status_record));
	jiffies_at_reset = jiffies;

	return;
}


/*
 * Function: nbst_obtain_status
 *	Interface function for NetBEUI STATUS service.
 *
 * Parameters:
 *	remote_name : pointer to a NetBIOS name that must retrieve status
 *	              information about it.
 *	status_buff : (VRP) pointer to user buffer that must be filled with
 *	              status information.
 *	buff_len    : (VRP) pointer to an integer that indicates length of
 *	              user buffer at start and length of retrieved information
 *	              at end.
 *
 * Returns: int
 *	zero     : if information retrieved successfully.
 *	negative : if something is bad.
 */

int 
nbst_obtain_status (char *called_name, char *status_buff, int *buff_len)
{
	return ((get_user(called_name) == '*') ?
	        nbst_local_status(status_buff, buff_len) :
		nbst_remote_status(called_name, status_buff, buff_len));
}


/*
 * Function: nbst_get_status_query
 *	Takes a STATUS_QUERY frame from 'llc supplementary' and response
 *	to it.
 *
 * Parameters:
 *	skb        : pointer to sk_buff that contains received frame.
 *	remote_mac : pointer to buffer that contains MAC address of sender
 *	             of the frame.
 *
 * Returns: none
 */

void  
nbst_get_status_query (struct sk_buff *skb, unsigned char *remote_mac)
{
	int   tmp,
	      losd,
	      nonts,
	      info_len;
	__u16   loits;
	name_t   *namep;
	struct sk_buff   *resp_skb;
	dgram_t   *resp_hdrp,
	          *hdrp = (dgram_t *) skb->data;

	/* Test for illegal requests */
	namep = nbns_find_name(hdrp->dest_name);
	if (!namep) {
		kfree_skb(skb, 0);
		return;
	}

	/* Status query for a group name has no meaning */
	if (namep->type == NB_NAME_GROUP) {
		kfree_skb(skb, 0);
		return;
	}

	info_len = nbst_gather_status_info(skb->dev);
	if ((hdrp->data1 > 1) &&
	    (hdrp->data1 >= nb_status_record.no_names_in_local_name_tbl)) {
                kfree_skb(skb, 0);
                return;
        }

	/* Make a sk_buff for response */
	resp_skb = alloc_skb(skb->dev->mtu, GFP_ATOMIC);
	if (!resp_skb) {
		kfree_skb(skb, 0);
		return;
	}

	tmp = LLCMAC_UI_HEADLEN(MAC_HEADLEN(skb->dev));
	skb_reserve(resp_skb, tmp);

	resp_hdrp = (dgram_t *) skb_put(resp_skb, nb_command_header_len[STATUS_RESPONSE]);
	resp_hdrp->length = nb_command_header_len[STATUS_RESPONSE];
	resp_hdrp->delimiter = NB_DELIMITER;
	resp_hdrp->command = STATUS_RESPONSE;
	resp_hdrp->xmit_correlator = hdrp->resp_correlator;

	/* Find length of data that can be sent */
	tmp = skb->dev->mtu - tmp;
	losd = tmp - nb_command_header_len[STATUS_RESPONSE];
	losd = MIN(losd, hdrp->data2);

	if (hdrp->data1 <= 1) { /* An initial request */
		nonts = (losd - NB_MIN_STATUS_BUFF_LEN) / (NB_NAME_LEN + 2);
		nonts = MIN(nonts, nb_status_record.no_names_in_local_name_tbl);
		loits = NB_MIN_STATUS_BUFF_LEN + nonts * (NB_NAME_LEN + 2);

		memcpy(skb_put(resp_skb, loits), &nb_status_record, loits);

		resp_hdrp->data1 = nonts ? nonts : 1;
	}
	else {
		nonts = losd / (NB_NAME_LEN + 2);
		tmp = nb_status_record.no_names_in_local_name_tbl - hdrp->data1;
		nonts = MIN(nonts, tmp);
		info_len = tmp * (NB_NAME_LEN + 2);
		loits = nonts * (NB_NAME_LEN + 2);

		memcpy(skb_put(resp_skb, loits),
		       &nb_status_record.local_names[hdrp->data1], loits);

		resp_hdrp->data1 = nonts + hdrp->data1;
	}

	loits &= NBST_LENGTH_MASK;
	if (info_len > losd)
		loits |= NBST_FRAGMENT_MASK;
	if (info_len > hdrp->data2)
		loits |= NBST_OVERFLOW_MASK;
	resp_hdrp->data2 = loits;

	memcpy(resp_hdrp->dest_name, hdrp->source_name, NB_NAME_LEN);
	memcpy(resp_hdrp->source_name, hdrp->dest_name, NB_NAME_LEN);

	resp_skb->free = 1;
	resp_skb->dev = skb->dev;

	kfree_skb(skb, 0);

	nbll_uisend(remote_mac, resp_skb);

	return;
}


/*
 * Function: nbst_get_status_response
 *	Takes a STATUS_RESPONSE frame from 'llc supplementary' and place it
 *	on appropriate nb_status structure.
 *
 * Parameters:
 *	skb        : pointer to sk_buff that contains received frame.
 *	remote_mac : pointer to buffer that contains MAC address of sender
 *	             of the frame.
 *
 * Returns: none
 */

void  
nbst_get_status_response (struct sk_buff *skb, unsigned char *remote_mac)
{
	status_t   *nb_status;

	nb_status = nbst_find_correlator(((dgram_t *) skb->data)->xmit_correlator);

	/* If it does not match a waited status query */
	if (!nb_status) {
		kfree_skb(skb, 0);
		return;
	}

	barrier();
	if (nb_status->locked) {
		kfree_skb(skb, 0);
		return;
	}

	nb_status->rx_skb = skb;
	nb_status->tx_skb->dev = skb->dev;
	memcpy(nb_status->remote_mac, remote_mac, MAC_ADDR_LEN);

	wake_up(&nb_status->waitq);

	return;
}
