/*
 * name_serve.c - Contains functions that implement NetBIOS Name Service
 *
 * Notes:
 *	- Two intermixed structures hold local name table a dextab_t that is
 *	  used for assigning numbers to names and implicit name rule checking
 *	  and a unidirectional linked list which is used for name search. every
 *	  name entry may be found in both data structures.
 *
 * 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 <linux/sched.h>
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/errno.h>
#include <linux/timer.h>
#include <linux/skbuff.h>
#include <linux/netbeui.h>


static void nbns_timer_function(unsigned long);

/* These functions are Name State Transition handlers */
static int nbns_add_name_in_initial(name_t *);
static int nbns_retry_timeout_in_all(name_t *);
static int nbns_response_timeout_in_addwait(name_t *);
static int nbns_add_name_response1_in_addwait(name_t *);
static int nbns_add_name_query_in_addwait(name_t *);
static int nbns_response_timeout_in_collided(name_t *);
static int nbns_add_name_response2_in_collided(name_t *);
static int nbns_add_name_query_in_acquired(name_t *);
static int nbns_name_conflict_in_acquired(name_t *);
static int nbns_remove_name_in_acquired(name_t *);


static unsigned short int nbns_correlator= 0;
#define nbns_next_correlator() (++nbns_correlator)


/* We need to have two paths to access names from dextab and linked-list */
static name_t *name_list= NULL;
dextab_t name_table= {NULL, 0, 2, NB_MAX_NAMES, 0};

name_t name_number_1;

#define name_table_entry(i)	((name_t *)name_table.addr[i])



/*
 * Name Service State Machine definition
 */

typedef int (* name_event_handler_t)(name_t *);

struct event_struct {
	name_state_t  next_state;
	name_event_handler_t event_handler;
};

static struct event_struct 
name_state_table[4][8]= {
			/* NB_NAME_INITIAL */
{
{NB_NAME_ADDWAIT, nbns_add_name_in_initial},	/* NB_NAME_ADD_NAME */  
{-1, NULL},					/* NB_NAME_RETRY_TIMEOUT */
{-1, NULL},					/* NB_NAME_RESPONSE_TIMEOUT */ 
{-1, NULL},					/* NB_NAME_ADD_NAME_RESPONSE1 */
{-1, NULL},					/* NB_NAME_ADD_NAME_RESPONSE2 */
{-1, NULL},					/* NB_NAME_ADD_NAME_QUERY */
{-1, NULL},					/* NB_NAME_NAME_CONFLICT */ 
{-1, NULL}					/* NB_NAME_REMOVE_NAME */ 
},
			/* NB_NAME_ADDWAIT  */
{
{-1, NULL},					/* NB_NAME_ADD_NAME */  
{NB_NAME_ADDWAIT, nbns_retry_timeout_in_all},	/* NB_NAME_RETRY_TIMEOUT */  
{NB_NAME_ACQUIRED, nbns_response_timeout_in_addwait},	/* NB_NAME_RESPONSE_TIMEOUT */ 
{NB_NAME_COLLIDED, nbns_add_name_response1_in_addwait},	/* NB_NAME_ADD_NAME_RESPONSE1 */
{-1, NULL},					/* NB_NAME_ADD_NAME_RESPONSE2 */
{NB_NAME_ADDWAIT, nbns_add_name_query_in_addwait},	/* NB_NAME_ADD_NAME_QUERY */
{-1, NULL},					/* NB_NAME_NAME_CONFLICT */ 
{-1, NULL}					/* NB_NAME_REMOVE_NAME */ 
},
			/* NB_NAME_COLLIDED */
{
{-1, NULL},					/* NB_NAME_ADD_NAME */  
{NB_NAME_COLLIDED, nbns_retry_timeout_in_all},	/* NB_NAME_RETRY_TIMEOUT */  
{NB_NAME_INITIAL, nbns_response_timeout_in_collided},	/* NB_NAME_RESPONSE_TIMEOUT */ 
{-1, NULL},					/* NB_NAME_ADD_NAME_RESPONSE1 */
{NB_NAME_INITIAL, nbns_add_name_response2_in_collided},	/* NB_NAME_ADD_NAME_RESPONSE2 */
{-1, NULL},					/* NB_NAME_ADD_NAME_QUERY */
{-1, NULL},					/* NB_NAME_NAME_CONFLICT */ 
{-1, NULL}					/* NB_NAME_REMOVE_NAME */ 
},
			/* NB_NAME_ACQUIRED */
{
{-1, NULL},					/* NB_NAME_ADD_NAME */  
{-1, NULL},					/* NB_NAME_RETRY_TIMEOUT */  
{-1, NULL}, 					/* NB_NAME_RESPONSE_TIMEOUT */ 
{-1, NULL},					/* NB_NAME_ADD_NAME_RESPONSE1 */
{-1, NULL},					/* NB_NAME_ADD_NAME_RESPONSE2 */
{NB_NAME_ACQUIRED, nbns_add_name_query_in_acquired}, /* NB_NAME_ADD_NAME_QUERY */
{NB_NAME_ACQUIRED, nbns_name_conflict_in_acquired},  /* NB_NAME_NAME_CONFLICT */ 
{NB_NAME_INITIAL, nbns_remove_name_in_acquired},     /* NB_NAME_REMOVE_NAME */ 
}
};



/*
 * NAME_NUMBER_1 assignment and interface functions
 */

/*
 * Function: DEV_NAME_NUMBER_1
 *	Returns the name number of built by device MAC address
 *
 * Parameters:
 *	dev	: pointer to device structure to build its NAME_NUMBER_1
 *
 * Returns: 
 *	non-NULL: pointer to NAME_NUMBER_1 built by device MAC address
 *
 * Note:
 *	NAME_NUMBER_1 is defined as (16-n) bytes zero concatenated by (n)
 *	bytes of MAC address, where n is size of MAC address in bytes.
 */
unsigned char * 
DEV_NAME_NUMBER_1(struct device *dev)
{
	static char dev_name_number_1[ NB_NAME_LEN ];
	int zero_count= NB_NAME_LEN - dev->addr_len;

	memset(dev_name_number_1, '\0', zero_count);
	memcpy(dev_name_number_1 + zero_count, dev->dev_addr, dev->addr_len);
	return dev_name_number_1;
}


/*
 * Function: VALIDATE_NAME
 *	Checks a NetBIOS name validity according to NetBIOS name rules.
 *
 * Parameters:
 *	name	: pointer to NetBIOS name
 *
 * Returns: 
 *	0	: if name is a valid NetBIOS name
 *	non-zero: if name is not a valid NetBIOS name
 */
int 
VALIDATE_NAME(char *name)
{
	if ((memcmp(name, "IBM", 3) == 0) ||
	    (memchr(name, '\0', NB_NAME_LEN-1) != NULL))
		return -1;

	return 0;
}


/*
 * Function: nbns_init_name_number_1
 *	Finds a correct value for NAME_NUMBER_1
 *
 * Parameters:
 *	adapters: list of adapters the NetBEUI is bound to
 *
 * Returns: none
 *
 * Notes:
 *	- A NetBEUI implementation should provide a unique NAME_NUMBER_1.
 *	  Since our implementations supports both multiple interface and
 *	  loopback device, we need to decide on the device that is used
 *	  for generating NAME_NUMBER_1. 
 */
void
nbns_init_name_number_1(struct device *adapters[])
{
	name_number_1.type= NB_NAME_UNIQUE;
	name_number_1.state= NB_NAME_ACQUIRED;
	name_number_1.name_number= 1;
 
	if ((adapters[0] != NULL) && ((adapters[0]->flags & IFF_LOOPBACK)==0))
		{
		memcpy(name_number_1.name, DEV_NAME_NUMBER_1( adapters[0] ),
		       NB_NAME_LEN);
		return;
		}   

	if (adapters[1] != NULL)
		{
		memcpy(name_number_1.name, DEV_NAME_NUMBER_1( adapters[1] ), 
		       NB_NAME_LEN);
		return;
		}

	if (adapters[0] != NULL)
		{
		memcpy(name_number_1.name, "LOOPBACK DEVICE   ", NB_NAME_LEN);
		return;
		}
    
	name_number_1.name_number= -1;
	return;
}


/*
 * Function: nbns_name_number_1
 *	The Name Service interface access routine for NAME_NUMBER_1
 *
 * Parameters: none
 *
 * Returns: 
 *	non-NULL: pointer to name_t structure that holds NAME_NUMBER_1
 */
name_t *
nbns_name_number_1(void)
{
	return (name_number_1.name_number == 1) ? &name_number_1 : NULL;
}

/*
 * Name service state machine functions
 * Implementing general functions
 */ 

/*
 * Function: nbns_alloc_skb
 *	allocates a sk_buff that suits Name Service framing needs
 *
 * Parameters:
 *	data_len: number of data bytes that the calling routine intends
 *		  to put in skb
 *
 * Returns: 
 *	NULL	: if can not allocate memory for skb
 *	non-NULL: pointer to skb allocated with data_len bytes space for data
 */
static inline struct sk_buff *
nbns_alloc_skb(int mac_hlen, int data_len)
{
	struct sk_buff *skb= alloc_skb(CALC_DG_SKBLEN(mac_hlen, data_len), GFP_ATOMIC);

	if (!skb)
		return NULL;

	skb_reserve(skb, LLCMAC_UI_HEADLEN(mac_hlen));
#ifdef _NB_TR_DBG_
printk("nbns_alloc_skb 1>>> data_len=%d\n", data_len);
#endif
	skb_put(skb, data_len);
#ifdef _NB_TR_DBG_
printk("nbns_alloc_skb 2>>> OK!\n");
#endif
	skb->free= 1;
	skb->dev= NULL;

	return skb;
}


/*
 * Function: nbns_alloc_name
 *	Allocates a name_t structure and does completely initialize all fields
 *
 * Parameters: none
 *
 * Returns: 
 *	NULL	: if can not allocate memory for name_t or its sk_buff
 *	non-NULL: pointer to name_t 
 *
 * Notes:
 *	- An skb is attached to each new name for name registration purposes
 *	- Name timer is initialized but not started.
 *	- The call to memset does implicitly initialize all fields. Those 
 *	  fileds that need explicit non-zero initialization are manipulated
 *	  afterwards.
 */
static inline name_t *
nbns_alloc_name (void)
{
	name_t *nb_name= kmalloc(sizeof(name_t), GFP_KERNEL);

	if (!nb_name)
		return NULL;

	/* Implicitly initialize all fields */
	memset(nb_name, 0, sizeof(name_t));
	nb_name->users= 1;
    
	init_timer(&nb_name->timer);
	nb_name->timer.data= (unsigned long)nb_name;
	nb_name->timer.function= nbns_timer_function;

	/* Allocate name skb */
	nb_name->skb= nbns_alloc_skb(MAC_B_HEADLEN ,nb_command_header_len[ADD_NAME_QUERY]);
	if (!nb_name->skb)
		{
		kfree(nb_name);
		return NULL;
		}

	return nb_name;
}


/*
 * Function: nbns_free_name
 *	Deallocates memory used for name_t and its sk_buff
 *
 * Parameters:
 *	nb_name	: pointer to name_t memory to be freed
 *
 * Returns: none
 */
static inline void
nbns_free_name(name_t *nb_name)
{
	kfree_skb(nb_name->skb, 0);
	kfree(nb_name);
}


/*
 * Function: nbns_add_name_to_table
 *	Inserts a previously allocated/initialized name_t into system local
 *	name table and local name list.
 *
 * Parameters:
 *	nb_name	: pointer to name_t to insert
 *
 * Returns: 
 *	0	: if name is successfully inserted into name table
 *	-ENOSPC : if local name table is full
 */
static inline int
nbns_add_name_to_table( name_t *nb_name)
{
	/* Allocate a name table entry  */
	int name_number= dextab_insert_entry(&name_table, nb_name);
    
	if (name_number <= 1) 
		return -ENOSPC;
	
	nb_name->name_number= name_number;
    
	/* Add name to name list */
	nb_name->next= name_list;
	name_list= nb_name;

	return 0;
}


/*
 * Function: nbns_remove_name_from_table
 *	Removes a name from system local name table.
 *
 * Parameters:
 *	nb_name	: pointer to name_t to remove
 *
 * Returns: none
 */
static inline void
nbns_remove_name_from_table( name_t *nb_name)
{
	name_t *entry= name_list;
	name_t *prev_entry= NULL;

	dextab_delete_entry(&name_table, nb_name);

	while (entry)
		{
		if (entry == nb_name)
			{
			if (prev_entry)
				prev_entry->next= entry->next;
			else
				name_list= entry->next;
			return;
			}
		prev_entry= entry;
		entry= entry->next;
		}
	return;
}


/*
 * Function: nbns_find_correlator
 *	Finds a name in local name list, which has a specific NetBIOS name
 *	and has transmitted frames with a specific correlator
 *
 * Parameters:
 *	correlator : a sixteen bit integer which contains the response 
 *	             correlator of input frame. It should be machted against
 *		     xmit correlator of frames sent by the element.
 *	name	   : pointer to NetBIOS name that the requested element should have
 *
 * Returns: 
 *	NULL	   : if no name_t element found with the requested characteristics
 *	non-NULL   : pointer to matching name_t with requested characteristics
 *
 * Notes:
 *	- This routine is usefull for relating incoming response frames with 
 *	  name elements that have transmitted requets to remote nodes.
 */
static name_t *
nbns_find_correlator(unsigned short correlator, char *name)
{
	name_t *nb_name= name_list;

	while (nb_name)
		{
		if (( nb_name->resp_correlator == correlator) &&
		    ( nb_name->state != NB_NAME_ACQUIRED) && 
		    ( memcmp(nb_name->name, name, NB_NAME_LEN) == 0))
			return nb_name;
		nb_name= nb_name->next;
		}
	return NULL;
}


/*
 * Function: nbns_boradcast_add_name_query
 *	Prepares a NetBIOS ADD NAME QUERY frame and nbll_uisends it to network
 *
 * Parameters:
 *	nb_name	: pointer to name_t structure which the frame should be built for
 *
 * Returns: 
 *	0 	: if frame is successfully broadcasted to network
 *	non-zero: if frame transmission encountered an error (usually at NDI layer)
 *	
 * Notes:
 *	- Since ADD NAME QUERY frames are retransmitted in timed intervals, it 
 *	  is considered to build frame once, but transmit it multiple times.
 *	  having built frames in each retransmission does generate multiple
 *	  correlators and does frustrate processing responses.
 */
static int 
nbns_broadcast_add_name_query(name_t *nb_name)
{
	if (nb_name->retries == 0)
		{
		dgram_t *hdr= (dgram_t *)nb_name->skb->data;

		hdr->length= nb_command_header_len[ADD_NAME_QUERY];
		hdr->delimiter= NB_DELIMITER;
		hdr->command= (nb_name->type == NB_NAME_UNIQUE) ? 
		         ADD_NAME_QUERY : ADD_GROUP_NAME_QUERY;
		hdr->data1= 0;
		hdr->data2= 0;
		hdr->xmit_correlator= 0;
		hdr->resp_correlator= nb_name->resp_correlator= nbns_next_correlator();
		memset(hdr->dest_name, 0, NB_NAME_LEN);
		memcpy(hdr->source_name, nb_name->name, NB_NAME_LEN);
		}

	return nbll_uisend(NULL, nb_name->skb);
}


/*
 * Function: nbns_broadcast_name_in_conflict
 *	Prepares a NetBIOS NAME IN CONFLICT frame and nbll_uisends it to network
 *
 * Parameters:
 *	nb_name	: pointer to name_t structure which the frame should be built for
 *
 * Returns: 
 *	0 	: if frame is successfully broadcasted to network
 *	non-zero: if frame transmission encountered an error (usually at NDI layer)
 */
static int
nbns_broadcast_name_in_conflict(name_t *nb_name)
{
	dgram_t *hdr= (dgram_t *)nb_name->skb->data;

	hdr->length= nb_command_header_len[NAME_IN_CONFLICT];
	hdr->delimiter= NB_DELIMITER;
	hdr->command= NAME_IN_CONFLICT;
	hdr->data1= 0;
	hdr->data2= 0;
	hdr->xmit_correlator= 0;
	hdr->resp_correlator= 0;
	memcpy(hdr->dest_name, nb_name->name, NB_NAME_LEN);
	memcpy(hdr->source_name, nbns_name_number_1()->name, NB_NAME_LEN);

	return nbll_uisend(NULL, nb_name->skb);
}


/*
 * Function: nbns_unicast_add_name_response
 *	Prepares a NetBIOS ADD NAME RESPONSE frame and nbll_uisends it to network
 *
 * Parameters:
 *	nb_name	: pointer to name_t structure which the frame should be built for
 *
 * Returns: 
 *	0 	: if frame is successfully broadcasted to network
 *	non-zero: if frame transmission encountered an error (usually at NDI layer)
 *		  or it can not allocate memory for frame.
 *	
 * Note:
 *	- The lack of memory and unability to send response frame may cause 
 *	  a name to conflict on network. This is simply the case on a heavily
 *	  loaded server, however since the query is retransmitted multiple times
 *	  we hope the server would be able to defend its name at least once. In
 *	  addition if an administrator finds the case he can increase either
 *	  retransmissions or timeouts for all nodes on network.
 */
static int
nbns_unicast_add_name_response(name_t *nb_name)
{
	dgram_t *hdr;
	struct sk_buff *skb= nbns_alloc_skb(MAC_HEADLEN(nb_name->remote_dev),
					nb_command_header_len[ADD_NAME_RESPONSE]);

	if (!skb)
		return -ENOMEM;

	hdr= (dgram_t *)skb->data;
	hdr->length= nb_command_header_len[ADD_NAME_RESPONSE];
	hdr->delimiter= NB_DELIMITER;
	hdr->command= ADD_NAME_RESPONSE;
	hdr->data1= (nb_name->state == NB_NAME_ACQUIRED) ? 0 : 1;
	hdr->data2= nb_name->type;
	hdr->xmit_correlator= nb_name->xmit_correlator;
	hdr->resp_correlator= 0;
	memcpy(hdr->source_name, nb_name->name, NB_NAME_LEN);
	memcpy(hdr->dest_name, nb_name->name, NB_NAME_LEN);
	skb->dev= nb_name->remote_dev;
	return nbll_uisend(nb_name->remote_mac, skb);
}


/*
 * Function: nbns_handle_event
 *	This is the heart of Name Service State Machine, which performs a 
 *	transition from current state of name element to new state based
 *	on event occured and name state table contents.
 *
 * Parameters:
 *	event	: An integer of NB_NAME_* family that implies type of event
 *	nb_name	: pointer to name_t structure which the event occured on
 *
 * Returns: none
 *
 * Notes:
 *	- The state changes before actions be executed. This is due to
 *	  non deterministic behaviour of actions which may sleep the current
 *	  process, thus stopping the function in the mid-way. 
 *
 *	- Setting NBNS_DEBUG in Makefile causes the module to generate the code 
 *         that provide usefull information about transitions on every name element. 
 */
static void
nbns_handle_event( name_event_t event, name_t *nb_name)
{
	struct event_struct *ev= &name_state_table[nb_name->state][event];
	unsigned char old_state;

#ifdef NBNS_DEBUG
	printk("NBNS event handler (%s): EVENT=%u on STATE=%u\n", nb_name->name, event, nb_name->state);
#endif NBNS_DEBUG

	if ((ev) && (ev->event_handler))
		{
		old_state= nb_name->state;
		nb_name->state= ev->next_state;
		if (ev->event_handler(nb_name) != 0)
			nb_name->state= old_state;
		}

#ifdef NBNS_DEBUG
	printk("NBNS event handler (%s): NEW STATE=%u\n", nb_name->name, nb_name->state);
#endif NBNS_DEBUG

	return;
}



/*
 * Function: nbns_timer_function
 *	This is the callback function triggered upon expiration of name 
 *	retransmittion timer. It just injects an event into state machine for
 *	its link.
 *
 * Parameters:
 *	input	: pointer to name_t structure whose timer is expired.
 *
 * Returns: none
 */
static void 
nbns_timer_function(unsigned long input)
{
	name_t *nb_name= (name_t *)input;

	if (nb_name->retries < NB_TRANSMIT_COUNT)
		{
		nbns_handle_event(NB_NAME_RETRY_TIMEOUT, nb_name);
		return;
		}
    
	nbns_handle_event(NB_NAME_RESPONSE_TIMEOUT, nb_name);
}



/*
 * Name service state machine functions
 * Implementing transition functions
 */

/*
 * Function: nbns_xxxx_in_ssss
 *	The section below contains functions that implement actions needed
 *	to  legally transit from one state to another.
 *
 * Parameters:
 *	nb_name	: pointer to name_t structure which the actions are to be 
 *		  applied to
 *
 * Returns: 
 *	0	: if all actions are done successfully
 *	non-zero: if one of actions failed
 *
 * Note:
 *	- For the sake of simplicity, the actions are automatically rollbacked 
 *	  in each function, if an action in transition fails. The design documents
 *	  do not cover these parts of code.
 */


static int
nbns_add_name_in_initial(name_t *nb_name)
{
	unsigned long flags;
	nb_name->retries= 0;

	nb_name->status= nbns_broadcast_add_name_query(nb_name);
	if (nb_name->status != 0) 
		return -1;
    
	save_flags(flags);
	cli();

	nb_name->status= nbns_add_name_to_table(nb_name);
	if (nb_name->status != 0)
		{
		restore_flags(flags);
		return -1;
		}

	nb_name->retries++;

	nb_name->timer.expires= jiffies+ NB_TRANSMIT_TIMEOUT;
	add_timer(&nb_name->timer);
    
	sleep_on(&nb_name->waitq);

	restore_flags(flags);

	return 0;
}


static int
nbns_retry_timeout_in_all(name_t *nb_name)
{
	nb_name->timer.expires= jiffies+ NB_TRANSMIT_TIMEOUT;
	add_timer(&nb_name->timer);

	if (nbns_broadcast_add_name_query(nb_name) != 0)
		return -1;

	nb_name->retries++; 

	return 0;
}


static int
nbns_response_timeout_in_addwait(name_t *nb_name)
{
	wake_up(&nb_name->waitq);
	
	return 0;
}


static int
nbns_add_name_response1_in_addwait(name_t *nb_name)
{
	nb_name->status= -EADDRINUSE;

	return 0;
}


static int
nbns_add_name_query_in_addwait(name_t *nb_name)
{
	if (nbns_unicast_add_name_response(nb_name) != 0)
		return -1;
	return 0;
}


static int
nbns_response_timeout_in_collided(name_t *nb_name)
{
	wake_up(&nb_name->waitq);

	nbns_remove_name_from_table(nb_name);

	return 0;
}


static int
nbns_add_name_response2_in_collided(name_t *nb_name)
{
	nbns_broadcast_name_in_conflict(nb_name);
	    
	del_timer(&nb_name->timer);

	wake_up(&nb_name->waitq);

	nbns_remove_name_from_table(nb_name);

	return 0;
}


static int
nbns_add_name_query_in_acquired(name_t *nb_name)
{
	if (nbns_unicast_add_name_response(nb_name) != 0)
		return -1;
	return 0;
}


static int
nbns_name_conflict_in_acquired(name_t *nb_name)
{
	nb_name->conflicted= 1;
    
	return 0;
}


static int
nbns_remove_name_in_acquired(name_t *nb_name)
{
	unsigned long flags;

	save_flags(flags);
	cli();
    
	nbns_remove_name_from_table(nb_name);

	restore_flags(flags);

	return 0;
}



/*
 * Name service state machine functions
 * Implementing interface functions
 */

/*
 * Function: nbns_add_name
 *	Adds a name to local name table after checking network (being permitted). 
 *
 * Parameters:
 *	name	: pointer to 16 byte NetBIOS name
 *	type	: type of NetBIOS name that is NB_NAME_GROUP, NB_NAME_UNIQUE
 *	out_name: pointer to name_t structure built for that name. Since this
 *		  a result argument, its value depends on function return value
 *		  if return value specifies successful operation then this
 *		  argument contains a valid pointer.
 *
 * Returns: 
 *	0	   : if name successfully registered into local name table
 *	-EINVAL    : if name is not a valid NetBIOS name
 *	-EADDRINUSE: if name is registered either in local name table or
 *		     on another machine in network. 
 *	-ENOMEM	   : if memory allocation for name element name_t fails
 *	others	   : any other error value reported by LLC or system.
 */
int
nbns_add_name(char *name, name_type_t type, name_t **out_name)
{
	name_t *nb_name;
	int status;

	if (VALIDATE_NAME(name) != 0)
		return -EINVAL;

	if (nbns_find_name(name) != NULL)
		return -EADDRINUSE;

	nb_name= nbns_alloc_name();
	if (!nb_name)
		return -ENOMEM;
    
	*out_name= NULL;

	nb_name->state= NB_NAME_INITIAL;
	nb_name->type= type;
	memcpy(nb_name->name, name, NB_NAME_LEN);
    
	nbns_handle_event(NB_NAME_ADD_NAME, nb_name);

	if (nb_name->state != NB_NAME_ACQUIRED)
		{
		status= nb_name->status;
		nbns_free_name(nb_name);
		return ((status == 0) ? -1 : status);
		}

	*out_name= nb_name; 
	return 0;
}


/*
 * Function: nbns_use_name
 *	Increments name use (inheritence) counter.
 *
 * Parameters: 
 *	nb_name	: pointer to name that is to be used (inherited)
 *
 * Returns: 
 *	non-NULL: pointer to original name whose use counter incremented.
 *
 * Note:
 *	IN UNIX concurrent server model, a server receives incoming connections
 *	and forks upon receiving incoming connections. In this model, the forked
 *	server inherits original server TRANSPORT ADDRESS bound to a new socket.
 *	NetBIOS name use counter determines how many running servers are bound
 *	to name_t element. 
 */
name_t *
nbns_use_name(name_t *nb_name)
{
	nb_name->users++;
	return nb_name;
}


/*
 * Function: nbns_find_name
 *	Finds a name with a specific 16 bytes NetBIOS name in local name table.
 *
 * Parameters:
 *	name	: pointer to 16 bytes NetBIOS name to be located in local name table.
 *
 * Returns: 
 *	NULL	: if NetBIOS name not found in local name table
 *	non-NULL: pointer to name_t element found in local name table.
 */
name_t *
nbns_find_name(char *name)
{
	name_t *nb_name= name_list;

	while (nb_name)
		{
		if (memcmp(nb_name->name, name, NB_NAME_LEN) == 0)
			return nb_name;
		nb_name= nb_name->next;
		}
	return NULL;
}


/*
 * Function: nbns_del_name
 *	Removes a name_t element from local name table and local name list,
 *	using its NetBIOS name.
 *
 * Parameters:
 *	nb_name	: pointer to name_t element to remove from local name table
 *
 * Returns: none
 *
 * Notes:
 *	- Removing the name_t is a multi-pass mechanism depending on the value 
 *	  of name use count. A name is actually removed from local name table
 *	  when its use count reaches zero.
 */
void  
nbns_del_name(name_t *nb_name)
{
	if (nb_name->name_number == 1)
		return;

	nb_name->users --;
	if (nb_name->users != 0)
		return;

	nbns_handle_event(NB_NAME_REMOVE_NAME, nb_name);

	nbns_free_name(nb_name);
}


/*
 * Function: nbns_del_identifier
 *	Removes a name_t element from local name table and local name list,
 *	using its identification number.
 *
 * Parameters:
 *	id	: id or number of the name to remove from local name table
 *
 * Returns: none
 *	
 * Notes:
 *	- Refer to notes section of nbns_del_name comment
 *	- The ideintification number is a notion used in sock_name. Every 
 *	  SOCK_NAME type socket assigns a unique identifier to all the name 
 *	  it registers, which lets not to keep another table of registered
 *	  names in socket data structures. The value is simple memory address
 *	  of socket and is assigned directly from SOCK_NAME codes.
 *	- Multiple names may have similar identifier.
 */
void
nbns_del_identifier(unsigned long id)
{
	name_t *nb_name;
	int index;

	for (index=name_table.reserved; index<name_table.size; index++)
		{
		nb_name= name_table_entry(index);
		if (nb_name && (nb_name->identifier == id))
			nbns_del_name(nb_name);
		}
}


/*
 * Function: nbns_get_add_name_query
 *	Accepts an ADD NAME QUERY frame and generates an event for Name 
 *	Service State Machine.
 *
 * Parameters:
 *	skb	  : pointer to sk_buff that holds the frame
 *	remote_mac: pointer to MAC address of remote node, whom sent the frame
 *	type	  : the flag indicates the type of query:
 *			NB_NAME_GROUP indicates ADD GROUP NAME QUERY
 *			NB_NAME_UNIQUE indicates ADD NAME QUERY
 *
 * Returns: none
 */
void  
nbns_get_add_name_query(struct sk_buff *skb, unsigned char *remote_mac, int type)
{
	dgram_t *hdr= (dgram_t *)skb->data;
	name_t *nb_name= nbns_find_name(hdr->source_name);

#ifdef _NB_TR_DBG_
printk("nbns_get_add_name_query 1>>> nb_name=%p\n", nb_name);
#endif

	/* If name not found in name table or both are group names */
	if ((!nb_name) ||
	    ((skb->dev->flags & IFF_LOOPBACK) != 0) ||
            ((nb_name->type== NB_NAME_GROUP) && (type== NB_NAME_GROUP)))
		{
		kfree_skb(skb, 0);
		return;
		}

	memcpy(nb_name->remote_mac, remote_mac, skb->dev->addr_len);
	nb_name->remote_dev= skb->dev;
	nb_name->xmit_correlator= hdr->resp_correlator;

#ifdef _NB_TR_DBG_
printk("nbns_get_add_name_query 2>>> nb_name=%p\n", nb_name);
#endif
	nbns_handle_event(NB_NAME_ADD_NAME_QUERY, nb_name);

	kfree_skb(skb, 0);
	return;
}


/*
 * Function: nbns_get_add_name_response
 *	Accepts an ADD NAME RESPONSE frame and generates an event for Name 
 *	Service State Machine.
 *
 * Parameters:
 *	skb	  : pointer to sk_buff that holds the frame
 *	remote_mac: pointer to MAC address of remote node, whom sent the frame
 *
 * Returns: none
 */
void  
nbns_get_add_name_response(struct sk_buff *skb, unsigned char *remote_mac)
{
	dgram_t *hdr= (dgram_t *)skb->data;
	name_t *nb_name= nbns_find_correlator(hdr->xmit_correlator, hdr->dest_name);

#ifdef _NB_TR_DBG_
printk("nbns_get_add_name_response 1>>> nb_name=%p\n", nb_name);
#endif
	/* If it does not match a query */
	if (!nb_name)
		{
		kfree_skb(skb, 0);
		return;
		}
       
	/* The name registration query got a nagative response */
	/* Test if it is a duplicate and already received */

	if ((nb_name->remote_dev != skb->dev) ||
	    (memcmp(nb_name->remote_mac, remote_mac, skb->dev->addr_len) != 0))
		{
		nb_name->remote_dev= skb->dev;
		memcpy(nb_name->remote_mac, remote_mac, skb->dev->addr_len);
		nb_name->responses++;
	
#ifdef _NB_TR_DBG_
printk("nbns_get_add_name_response 2>>> nb_name->responses=%d\n", nb_name->responses);
#endif
		if (nb_name->responses == 1)
			nbns_handle_event(NB_NAME_ADD_NAME_RESPONSE1, nb_name);
		else
			nbns_handle_event(NB_NAME_ADD_NAME_RESPONSE2, nb_name);    
		}
        
	kfree_skb(skb, 0);
	return;
}


/*
 * Function: nbns_get_name_conflict
 *	Accepts a NAME CONFLICT frame and generates an event for Name 
 *	Service State Machine.
 *
 * Parameters:
 *	skb	  : pointer to sk_buff that holds the frame
 *
 * Returns: none
 *
 * Notes:
 *	- The strategy chosen for manipuating NAME CONFLICT frames is to
 *	  set a flag in name_t structure, which is available via /proc entry
 *	  interface. 
 */
void  
nbns_get_name_conflict(struct sk_buff *skb)
{
	name_t *nb_name= nbns_find_name(((dgram_t *)skb->data)->source_name);

	if (nb_name)
		nbns_handle_event(NB_NAME_NAME_CONFLICT, nb_name);

	kfree_skb(skb, 0);
	return;
}



/*
 * Function: nbns_get_link_table
 *	returns a pointer to NetBEUI name table. The proc support code uses
 *	the name table to map its contents to /proc/sys/netbeui entry.
 *
 * Parameters: none
 *
 * Returns:
 *	non-NULL: pointer to NetBEUI name table
 */
dextab_t *
nbns_get_name_table(void)
{
	return &name_table;
}


/*
 * Function: nbns_get_name_list
 *	returns a pointer to NetBEUI name list. The status module uses
 *	the name list to prepare its status response.
 *
 * Parameters: none
 *
 * Returns:
 *	NULL	: name list has no entry
 *	non-NULL: pointer to NetBEUI name list
 */
name_t *
nbns_get_name_list(void)
{
	return name_list;
}


/*
 * Function: nbns_count_names
 *	returns number of entries in name table. The status module uses
 *	the this number to prepare its status response.
 *
 * Parameters: none
 *
 * Returns:
 *	zero	: name table has no entry
 *	positive: number of entries in name table
 */
int
nbns_count_names(void)
{
	return name_table.count;
}
