/*
 * session_serve.c - Contains functions that implement NetBIOS Session Service
 *
 * Important Note:
 *	- Consider the following design issues when reading session service sources
 *		1- Session Establishmenti, Session Termination and Session Data
 *		   Output Stream are joined to from the Session States
 *		2- Session Data Input Stream does is not implemented in State
 *		   Machine model, it is simply a buffering and acknowledgement 
 *		   mechanism.
 *
 * Notes:
 *	- VRP in comments is the acronym of "Value Result Parameter"
 *	- Session Service is the most complicated service in NetBIOS. Numerous 
 *	  states and events used for session establishment, session data transfer
 *	  and session termination need to much effort to overcome. Please read
 *	  documentations before changing even a bit (!) in source codes. 
 *
 * 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>


extern int llc_in_progress;

/* These functions are used for locking sessions to avoid race conditions */
static inline void nbss_lock_session(session_t *);
static inline int  nbss_release_session(session_t *);


static void nbss_wait_timer_function( unsigned long);
static void nbss_resource_timer_function( unsigned long);
static int  nbss_abort_send_in_all(session_t *);

/* These functions are Session Service State Transition handlers */
static int  nbss_call_in_initial( session_t *);
static int  nbss_listen_in_initial( session_t *);

static int  nbss_confirm_in_callwait( session_t *);
static int  nbss_reject_in_callwait( session_t *);
static int  nbss_abort_in_callwait(session_t *);

static int  nbss_connect_in_confwait( session_t *);
static int  nbss_timeout_in_confwait( session_t *);
static int  nbss_abort_in_confwait(session_t *);

static int  nbss_confirm_in_listenwait( session_t *);
static int  nbss_reject_in_listenwait( session_t *);

static int  nbss_connect_in_initwait( session_t *);
static int  nbss_timeout_in_initwait( session_t *);
static int  nbss_reject_in_initwait( session_t *);
static int  nbss_abort_in_initwait(session_t *);

static int  nbss_hangup_in_connected( session_t *);
static int  nbss_first_middle_cont_in_connected(session_t *);
static int  nbss_first_middle_in_connected(session_t *);
static int  nbss_only_last_ack_in_connected(session_t *);
static int  nbss_only_last_in_connected(session_t *);
static int  nbss_resource_in_connected(session_t *);
static int  nbss_end_in_connected( session_t *);
static int  nbss_abort_in_connected( session_t *);

static int  nbss_hangup_in_discwait( session_t *);

static int  nbss_continue_in_contwait(session_t *);
static int  nbss_restart_in_contwait(session_t *);
static int  nbss_pause_in_contwait(session_t *);
static int  nbss_nonblock_in_contwait(session_t *);

static int  nbss_restart_in_standwait(session_t *);
static int  nbss_pause2_in_standwait(session_t *);

static int  nbss_data_acked_in_ackwait(session_t *);
static int  nbss_restart_in_ackwait(session_t *);
static int  nbss_pause_in_ackwait(session_t *);
static int  nbss_nonblock_in_ackwait(session_t *);

static int  nbss_norm_retry_in_rsrcwait(session_t *);
static int  nbss_conn_retry_in_rsrcwait(session_t *);

static int  nbss_first_middle_in_normal(session_t *);
static int  nbss_only_last_ack_in_normal(session_t *);
static int  nbss_only_last_in_normal(session_t *);
static int  nbss_pause_in_normal(session_t *);
static int  nbss_restart_in_normal(session_t *);
static int  nbss_resource_in_normal(session_t *);
static int  nbss_nonblock_in_normal(session_t *);


static unsigned short int nbss_correlator= 0; 
#define nbss_next_correlator() (++nbss_correlator)


/* This is a list for pending session listened for incomming session establishment
   requests */
static session_t *session_list= NULL;



/*
 * Session service state machine definition
 */
typedef int (* session_event_handler_t)(session_t *);

struct event_struct {
	session_state_t  next_state;
	session_event_handler_t event_handler;
};


static struct event_struct 
session_state_table[12][23]= {
			/* NB_SESS_INITIAL */
{
{NB_SESS_CALLWAIT, nbss_call_in_initial},		/* NB_SESS_CALL */
{NB_SESS_LISTENWAIT, nbss_listen_in_initial},		/* NB_SESS_LISTEN */  
{-1, NULL},						/* NB_SESS_CONFIRM */ 
{-1, NULL},						/* NB_SESS_REJECT */
{-1, NULL},						/* NB_SESS_CONNECT */ 
{-1, NULL},						/* NB_SESS_TIMEOUT */
{-1, NULL},						/* NB_SESS_ABORT */
{-1, NULL},						/* NB_SESS_HANGUP */ 
{-1, NULL},						/* NB_SESS_END */ 
{-1, NULL},						/* NB_SESS_FIRST_MIDDLE_CONT */
{-1, NULL},						/* NB_SESS_FIRST_MIDDLE */
{-1, NULL}, 						/* NB_SESS_CONTINUE */
{-1, NULL}, 						/* NB_SESS_NONBLOCK */
{-1, NULL}, 						/* NB_SESS_PAUSE */
{-1, NULL}, 						/* NB_SESS_PAUSE2 */
{-1, NULL}, 						/* NB_SESS_RESTART */
{-1, NULL}, 						/* NB_SESS_ONLY_LAST_ACK */
{-1, NULL}, 						/* NB_SESS_ONLY_LAST */
{-1, NULL}, 						/* NB_SESS_DATA_ACKED */
{-1, NULL}, 						/* NB_SESS_RESOURCE */
{-1, NULL}, 						/* NB_SESS_CONN_RETRY */
{-1, NULL}, 						/* NB_SESS_NORM_RETRY */
{-1, NULL}						/* NB_SESS_ABORT_SEND */
},			
			/* NB_SESS_CALLWAIT */
{
{-1, NULL},						/* NB_SESS_CALL */
{-1, NULL},						/* NB_SESS_LISTEN */  
{NB_SESS_CONFWAIT, nbss_confirm_in_callwait},		/* NB_SESS_CONFIRM */ 
{NB_SESS_INITIAL, nbss_reject_in_callwait},		/* NB_SESS_REJECT */
{-1, NULL},						/* NB_SESS_CONNECT */ 
{-1, NULL},						/* NB_SESS_TIMEOUT */
{NB_SESS_INITIAL, nbss_abort_in_callwait},		/* NB_SESS_ABORT */
{-1, NULL},						/* NB_SESS_HANGUP */ 
{-1, NULL},						/* NB_SESS_END */ 
{-1, NULL},						/* NB_SESS_FIRST_MIDDLE_CONT */
{-1, NULL},						/* NB_SESS_FIRST_MIDDLE */
{-1, NULL}, 						/* NB_SESS_CONTINUE */
{-1, NULL}, 						/* NB_SESS_NONBLOCK */
{-1, NULL}, 						/* NB_SESS_PAUSE */
{-1, NULL}, 						/* NB_SESS_PAUSE2 */
{-1, NULL}, 						/* NB_SESS_RESTART */
{-1, NULL}, 						/* NB_SESS_ONLY_LAST_ACK */
{-1, NULL}, 						/* NB_SESS_ONLY_LAST */
{-1, NULL}, 						/* NB_SESS_DATA_ACKED */
{-1, NULL}, 						/* NB_SESS_RESOURCE */
{-1, NULL}, 						/* NB_SESS_CONN_RETRY */
{-1, NULL}, 						/* NB_SESS_NORM_RETRY */
{-1, NULL}						/* NB_SESS_ABORT_SEND */
},	
			/* NB_SESS_CONFWAIT */
{
{-1, NULL},						/* NB_SESS_CALL */
{-1, NULL},						/* NB_SESS_LISTEN */  
{-1, NULL},						/* NB_SESS_CONFIRM */ 
{-1, NULL},						/* NB_SESS_REJECT */
{NB_SESS_CONNECTED, nbss_connect_in_confwait},		/* NB_SESS_CONNECT */ 
{NB_SESS_INITIAL, nbss_timeout_in_confwait},		/* NB_SESS_TIMEOUT */
{NB_SESS_INITIAL, nbss_abort_in_confwait},		/* NB_SESS_ABORT */
{-1, NULL},						/* NB_SESS_HANGUP */ 
{-1, NULL},						/* NB_SESS_END */ 
{-1, NULL},						/* NB_SESS_FIRST_MIDDLE_CONT */
{-1, NULL},						/* NB_SESS_FIRST_MIDDLE */
{-1, NULL}, 						/* NB_SESS_CONTINUE */
{-1, NULL}, 						/* NB_SESS_NONBLOCK */
{-1, NULL}, 						/* NB_SESS_PAUSE */
{-1, NULL}, 						/* NB_SESS_PAUSE2 */
{-1, NULL}, 						/* NB_SESS_RESTART */
{-1, NULL}, 						/* NB_SESS_ONLY_LAST_ACK */
{-1, NULL}, 						/* NB_SESS_ONLY_LAST */
{-1, NULL}, 						/* NB_SESS_DATA_ACKED */
{-1, NULL}, 						/* NB_SESS_RESOURCE */
{-1, NULL}, 						/* NB_SESS_CONN_RETRY */
{-1, NULL}, 						/* NB_SESS_NORM_RETRY */
{-1, NULL}						/* NB_SESS_ABORT_SEND */
},	
			/* NB_SESS_LISTENWAIT */
{
{-1, NULL},						/* NB_SESS_CALL */
{-1, NULL},						/* NB_SESS_LISTEN */  
{NB_SESS_INITWAIT, nbss_confirm_in_listenwait},		/* NB_SESS_CONFIRM */ 
{NB_SESS_INITIAL, nbss_reject_in_listenwait},		/* NB_SESS_REJECT */
{-1, NULL},						/* NB_SESS_CONNECT */ 
{-1, NULL},						/* NB_SESS_TIMEOUT */
{-1, NULL},						/* NB_SESS_ABORT */
{-1, NULL},						/* NB_SESS_HANGUP */ 
{-1, NULL},						/* NB_SESS_END */ 
{-1, NULL},						/* NB_SESS_FIRST_MIDDLE_CONT */
{-1, NULL},						/* NB_SESS_FIRST_MIDDLE */
{-1, NULL}, 						/* NB_SESS_CONTINUE */
{-1, NULL}, 						/* NB_SESS_NONBLOCK */
{-1, NULL}, 						/* NB_SESS_PAUSE */
{-1, NULL}, 						/* NB_SESS_PAUSE2 */
{-1, NULL}, 						/* NB_SESS_RESTART */
{-1, NULL}, 						/* NB_SESS_ONLY_LAST_ACK */
{-1, NULL}, 						/* NB_SESS_ONLY_LAST */
{-1, NULL}, 						/* NB_SESS_DATA_ACKED */
{-1, NULL}, 						/* NB_SESS_RESOURCE */
{-1, NULL}, 						/* NB_SESS_CONN_RETRY */
{-1, NULL}, 						/* NB_SESS_NORM_RETRY */
{-1, NULL}						/* NB_SESS_ABORT_SEND */
},	
			/* NB_SESS_INITWAIT */
{
{-1, NULL},						/* NB_SESS_CALL */
{-1, NULL},						/* NB_SESS_LISTEN */  
{-1, NULL},						/* NB_SESS_CONFIRM */ 
{NB_SESS_INITIAL, nbss_reject_in_initwait},		/* NB_SESS_REJECT */ 
{NB_SESS_CONNECTED, nbss_connect_in_initwait},		/* NB_SESS_CONNECT */ 
{NB_SESS_LISTENWAIT, nbss_timeout_in_initwait},		/* NB_SESS_TIMEOUT */
{NB_SESS_LISTENWAIT, nbss_abort_in_initwait},		/* NB_SESS_ABORT */
{-1, NULL},						/* NB_SESS_HANGUP */
{-1, NULL},						/* NB_SESS_END */ 
{-1, NULL},						/* NB_SESS_FIRST_MIDDLE_CONT */
{-1, NULL},						/* NB_SESS_FIRST_MIDDLE */
{-1, NULL}, 						/* NB_SESS_CONTINUE */
{-1, NULL}, 						/* NB_SESS_NONBLOCK */
{-1, NULL}, 						/* NB_SESS_PAUSE */
{-1, NULL}, 						/* NB_SESS_PAUSE2 */
{-1, NULL}, 						/* NB_SESS_RESTART */
{-1, NULL}, 						/* NB_SESS_ONLY_LAST_ACK */
{-1, NULL}, 						/* NB_SESS_ONLY_LAST */
{-1, NULL}, 						/* NB_SESS_DATA_ACKED */
{-1, NULL}, 						/* NB_SESS_RESOURCE */
{-1, NULL}, 						/* NB_SESS_CONN_RETRY */
{-1, NULL}, 						/* NB_SESS_NORM_RETRY */
{-1, NULL}						/* NB_SESS_ABORT_SEND */
},	
			/* NB_SESS_CONNECTED */
{
{-1, NULL},						/* NB_SESS_CALL */
{-1, NULL},						/* NB_SESS_LISTEN */  
{-1, NULL},						/* NB_SESS_CONFIRM */ 
{-1, NULL},						/* NB_SESS_REJECT */
{-1, NULL},						/* NB_SESS_CONNECT */ 
{-1, NULL},						/* NB_SESS_TIMEOUT */
{NB_SESS_DISCWAIT, nbss_abort_in_connected},		/* NB_SESS_ABORT */
{NB_SESS_INITIAL, nbss_hangup_in_connected},		/* NB_SESS_HANGUP */ 
{NB_SESS_DISCWAIT, nbss_end_in_connected},		/* NB_SESS_END */ 
{NB_SESS_CONTWAIT, nbss_first_middle_cont_in_connected},/* NB_SESS_FIRST_MIDDLE_CONT */
{NB_SESS_NORMAL, nbss_first_middle_in_connected},	/* NB_SESS_FIRST_MIDDLE */
{-1, NULL}, 						/* NB_SESS_CONTINUE */
{-1, NULL}, 						/* NB_SESS_NONBLOCK */
{-1, NULL}, 						/* NB_SESS_PAUSE */
{-1, NULL}, 						/* NB_SESS_PAUSE2 */
{-1, NULL}, 						/* NB_SESS_RESTART */
{NB_SESS_ACKWAIT, nbss_only_last_ack_in_connected},	/* NB_SESS_ONLY_LAST_ACK */
{NB_SESS_CONNECTED, nbss_only_last_in_connected}, 	/* NB_SESS_ONLY_LAST */
{-1, NULL}, 						/* NB_SESS_DATA_ACKED */
{NB_SESS_RSRCWAIT, nbss_resource_in_connected}, 	/* NB_SESS_RESOURCE */
{-1, NULL}, 						/* NB_SESS_CONN_RETRY */
{-1, NULL}, 						/* NB_SESS_NORM_RETRY */
{-1, NULL}						/* NB_SESS_ABORT_SEND */
},
			/* NB_SESS_DISCWAIT */
{
{-1, NULL},						/* NB_SESS_CALL */
{-1, NULL},						/* NB_SESS_LISTEN */  
{-1, NULL},						/* NB_SESS_CONFIRM */ 
{-1, NULL},						/* NB_SESS_REJECT */
{-1, NULL},						/* NB_SESS_CONNECT */ 
{-1, NULL},						/* NB_SESS_TIMEOUT */
{-1, NULL},						/* NB_SESS_ABORT */
{NB_SESS_INITIAL, nbss_hangup_in_discwait},		/* NB_SESS_HANGUP */ 
{-1, NULL},						/* NB_SESS_END */ 
{-1, NULL},						/* NB_SESS_FIRST_MIDDLE_CONT */
{-1, NULL},						/* NB_SESS_FIRST_MIDDLE */
{-1, NULL}, 						/* NB_SESS_CONTINUE */
{-1, NULL}, 						/* NB_SESS_NONBLOCK */
{-1, NULL}, 						/* NB_SESS_PAUSE */
{-1, NULL}, 						/* NB_SESS_PAUSE2 */
{-1, NULL}, 						/* NB_SESS_RESTART */
{-1, NULL}, 						/* NB_SESS_ONLY_LAST_ACK */
{-1, NULL}, 						/* NB_SESS_ONLY_LAST */
{-1, NULL}, 						/* NB_SESS_DATA_ACKED */
{-1, NULL}, 						/* NB_SESS_RESOURCE */
{-1, NULL}, 						/* NB_SESS_CONN_RETRY */
{-1, NULL}, 						/* NB_SESS_NORM_RETRY */
{-1, NULL}						/* NB_SESS_ABORT_SEND */
},
	   	       /* NB_SESS_CONTWAIT */
{
{-1, NULL},						/* NB_SESS_CALL */
{-1, NULL},						/* NB_SESS_LISTEN */  
{-1, NULL},						/* NB_SESS_CONFIRM */ 
{-1, NULL},						/* NB_SESS_REJECT */
{-1, NULL},						/* NB_SESS_CONNECT */ 
{-1, NULL},						/* NB_SESS_TIMEOUT */
{-1, NULL},						/* NB_SESS_ABORT */
{-1, NULL},						/* NB_SESS_HANGUP */ 
{-1, NULL},						/* NB_SESS_END */ 
{-1, NULL},						/* NB_SESS_FIRST_MIDDLE_CONT */
{-1, NULL},						/* NB_SESS_FIRST_MIDDLE */
{NB_SESS_NORMAL, nbss_continue_in_contwait},		/* NB_SESS_CONTINUE */
{NB_SESS_CONNECTED, nbss_nonblock_in_contwait}, 	/* NB_SESS_NONBLOCK */
{NB_SESS_STANDWAIT, nbss_pause_in_contwait}, 		/* NB_SESS_PAUSE */
{-1, NULL}, 						/* NB_SESS_PAUSE2 */
{NB_SESS_NORMAL, nbss_restart_in_contwait}, 		/* NB_SESS_RESTART */
{-1, NULL}, 						/* NB_SESS_ONLY_LAST_ACK */
{-1, NULL}, 						/* NB_SESS_ONLY_LAST */
{-1, NULL}, 						/* NB_SESS_DATA_ACKED */
{-1, NULL}, 						/* NB_SESS_RESOURCE */
{-1, NULL}, 						/* NB_SESS_CONN_RETRY */
{-1, NULL}, 						/* NB_SESS_NORM_RETRY */
{NB_SESS_CONNECTED, nbss_abort_send_in_all}		/* NB_SESS_ABORT_SEND */
},   	       
		       /* NB_SESS_STANDWAIT */
{
{-1, NULL},						/* NB_SESS_CALL */
{-1, NULL},						/* NB_SESS_LISTEN */  
{-1, NULL},						/* NB_SESS_CONFIRM */ 
{-1, NULL},						/* NB_SESS_REJECT */
{-1, NULL},						/* NB_SESS_CONNECT */ 
{-1, NULL},						/* NB_SESS_TIMEOUT */
{-1, NULL},						/* NB_SESS_ABORT */
{-1, NULL},						/* NB_SESS_HANGUP */ 
{-1, NULL},						/* NB_SESS_END */ 
{-1, NULL},						/* NB_SESS_FIRST_MIDDLE_CONT */
{-1, NULL},						/* NB_SESS_FIRST_MIDDLE */
{-1, NULL}, 						/* NB_SESS_CONTINUE */
{-1, NULL}, 						/* NB_SESS_NONBLOCK */
{-1, NULL}, 						/* NB_SESS_PAUSE */
{NB_SESS_STANDWAIT, nbss_pause2_in_standwait}, 		/* NB_SESS_PAUSE2 */
{NB_SESS_NORMAL, nbss_restart_in_standwait}, 		/* NB_SESS_RESTART */
{-1, NULL}, 						/* NB_SESS_ONLY_LAST_ACK */
{-1, NULL}, 						/* NB_SESS_ONLY_LAST */
{-1, NULL}, 						/* NB_SESS_DATA_ACKED */
{-1, NULL}, 						/* NB_SESS_RESOURCE */
{-1, NULL}, 						/* NB_SESS_CONN_RETRY */
{-1, NULL}, 						/* NB_SESS_NORM_RETRY */
{NB_SESS_CONNECTED, nbss_abort_send_in_all}		/* NB_SESS_ABORT_SEND */
},   	       
		       /* NB_SESS_ACKWAIT */
{
{-1, NULL},						/* NB_SESS_CALL */
{-1, NULL},						/* NB_SESS_LISTEN */  
{-1, NULL},						/* NB_SESS_CONFIRM */ 
{-1, NULL},						/* NB_SESS_REJECT */
{-1, NULL},						/* NB_SESS_CONNECT */ 
{-1, NULL},						/* NB_SESS_TIMEOUT */
{-1, NULL},						/* NB_SESS_ABORT */
{-1, NULL},						/* NB_SESS_HANGUP */ 
{-1, NULL},						/* NB_SESS_END */ 
{-1, NULL},						/* NB_SESS_FIRST_MIDDLE_CONT */
{-1, NULL},						/* NB_SESS_FIRST_MIDDLE */
{-1, NULL}, 						/* NB_SESS_CONTINUE */
{NB_SESS_CONNECTED, nbss_nonblock_in_ackwait}, 		/* NB_SESS_NONBLOCK */
{NB_SESS_STANDWAIT, nbss_pause_in_ackwait},		/* NB_SESS_PAUSE */
{-1, NULL}, 						/* NB_SESS_PAUSE2 */
{NB_SESS_NORMAL, nbss_restart_in_ackwait}, 		/* NB_SESS_RESTART */
{-1, NULL}, 						/* NB_SESS_ONLY_LAST_ACK */
{-1, NULL}, 						/* NB_SESS_ONLY_LAST */
{NB_SESS_CONNECTED, nbss_data_acked_in_ackwait}, 	/* NB_SESS_DATA_ACKED */
{-1, NULL}, 						/* NB_SESS_RESOURCE */
{-1, NULL}, 						/* NB_SESS_CONN_RETRY */
{-1, NULL}, 						/* NB_SESS_NORM_RETRY */
{NB_SESS_CONNECTED, nbss_abort_send_in_all}		/* NB_SESS_ABORT_SEND */
},   	       
		       /* NB_SESS_RSRCAIT */
{
{-1, NULL},						/* NB_SESS_CALL */
{-1, NULL},						/* NB_SESS_LISTEN */  
{-1, NULL},						/* NB_SESS_CONFIRM */ 
{-1, NULL},						/* NB_SESS_REJECT */
{-1, NULL},						/* NB_SESS_CONNECT */ 
{-1, NULL},						/* NB_SESS_TIMEOUT */
{-1, NULL},						/* NB_SESS_ABORT */
{-1, NULL},						/* NB_SESS_HANGUP */ 
{-1, NULL},						/* NB_SESS_END */ 
{-1, NULL},						/* NB_SESS_FIRST_MIDDLE_CONT */
{-1, NULL},						/* NB_SESS_FIRST_MIDDLE */
{-1, NULL}, 						/* NB_SESS_CONTINUE */
{-1, NULL}, 						/* NB_SESS_NONBLOCK */
{-1, NULL}, 						/* NB_SESS_PAUSE */
{-1, NULL}, 						/* NB_SESS_PAUSE2 */
{-1, NULL}, 						/* NB_SESS_RESTART */
{-1, NULL}, 						/* NB_SESS_ONLY_LAST_ACK */
{-1, NULL}, 						/* NB_SESS_ONLY_LAST */
{-1, NULL}, 						/* NB_SESS_DATA_ACKED */
{-1, NULL}, 						/* NB_SESS_RESOURCE */
{NB_SESS_CONNECTED, nbss_conn_retry_in_rsrcwait}, 	/* NB_SESS_CONN_RETRY */
{NB_SESS_NORMAL, nbss_norm_retry_in_rsrcwait}, 		/* NB_SESS_NORM_RETRY */
{NB_SESS_CONNECTED, nbss_abort_send_in_all}		/* NB_SESS_ABORT_SEND */
},   	       
		       /* NB_SESS_NORMAL */
{
{-1, NULL},						/* NB_SESS_CALL */
{-1, NULL},						/* NB_SESS_LISTEN */  
{-1, NULL},						/* NB_SESS_CONFIRM */ 
{-1, NULL},						/* NB_SESS_REJECT */
{-1, NULL},						/* NB_SESS_CONNECT */ 
{-1, NULL},						/* NB_SESS_TIMEOUT */
{-1, NULL},						/* NB_SESS_ABORT */
{-1, NULL},						/* NB_SESS_HANGUP */ 
{-1, NULL},						/* NB_SESS_END */ 
{-1, NULL},						/* NB_SESS_FIRST_MIDDLE_CONT */
{NB_SESS_NORMAL, nbss_first_middle_in_normal},		/* NB_SESS_FIRST_MIDDLE */
{-1, NULL}, 						/* NB_SESS_CONTINUE */
{NB_SESS_CONNECTED, nbss_nonblock_in_normal},		/* NB_SESS_NONBLOCK */
{NB_SESS_STANDWAIT, nbss_pause_in_normal}, 		/* NB_SESS_PAUSE */
{-1, NULL}, 						/* NB_SESS_PAUSE2 */
{NB_SESS_NORMAL, nbss_restart_in_normal}, 		/* NB_SESS_RESTART */
{NB_SESS_ACKWAIT, nbss_only_last_ack_in_normal}, 	/* NB_SESS_ONLY_LAST_ACK */
{NB_SESS_CONNECTED, nbss_only_last_in_normal}, 		/* NB_SESS_ONLY_LAST */
{-1, NULL}, 						/* NB_SESS_DATA_ACKED */
{NB_SESS_RSRCWAIT, nbss_resource_in_normal}, 		/* NB_SESS_RESOURCE */
{-1, NULL}, 						/* NB_SESS_CONN_RETRY */
{-1, NULL}, 						/* NB_SESS_NORM_RETRY */
{NB_SESS_CONNECTED, nbss_abort_send_in_all}		/* NB_SESS_ABORT_SEND */
}   	       
};




/*
 * Session service state machine functions
 * Implementing General functions
 */

/*
 * Function: CALC_SESS_MTU
 *	Calculates MAXimum length of user data that can send through a
 *	session, and sets the 'mtu' field of it.
 *
 * Parameters:
 *	sn : pointer to session that calculation is for it.
 *
 * Returns: none
 */

static inline void
CALC_SESS_MTU (session_t *sn)
{
	sn->mtu = (sn->remote_dev->type == ARPHRD_LOOPBACK) ? 1500 : sn->remote_dev->mtu ;
	if (sn->remote_dev->type == ARPHRD_IEEE802) { /* Token Ring */
		__u16   dolfb;

		switch (sn->tr_frame_lf) {
			case 0x00 :
				dolfb = 516;
				break;

			case 0x01 :
				dolfb = 1470;
				break;

			case 0x02 :
				dolfb = 2052;
				break;

			case 0x03 :
				dolfb = 4399;
				break;

			case 0x04 :
				dolfb = 8130;
				break;

			case 0x05 :
				dolfb = 11407;
				break;

			case 0x06 :
				dolfb = 17749;
				break;

			case 0x07 :
			default:
				dolfb = 0xFFFF;
				break;

		}
		if (sn->mtu > dolfb)
			sn->mtu = dolfb;
	}
	sn->mtu = sn->mtu - sn->llcmac_ihl - NETB_ILEN;

	return;
}


/*
 * Function: nbss_alloc_session
 *	Allocates a session_t structure and does completely initialize all fields
 *
 * Parameters: none
 *
 * Returns: 
 *	NULL	: if can not allocate memory for session_t
 *	non-NULL: pointer to session_t 
 *
 * Notes:
 *	- Session 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 session_t *
nbss_alloc_session(void)
{
	session_t *session= kmalloc(sizeof(session_t), GFP_KERNEL);

	if (!session)
		return NULL;

	/* Implicitly initialize all fields */
	memset(session, 0, sizeof(session_t));
	init_timer(&session->timer);
	session->timer.data= (unsigned long)session;
	session->timer.function= nbss_wait_timer_function;

	session->version= NB_VERSION_2xx;
	session->nack_indicator= NB_NACK_ABLE;
	
	skb_queue_head_init(&session->back_log);
	skb_queue_head_init(&session->i_skbq);
	
	session->o_receive_continue= 1;
	session->i_rcvbuf= 0xFFFF;

	return session;
}



/*
 * Function: nbss_free_session
 *	Deallocates memory used for session_t and does a complete housekeeping
 *
 * Parameters:
 *	session : pointer to session_t memory to be freed
 *
 * Returns: none
 */
static inline void
nbss_free_session( session_t *session)
{
	struct sk_buff *skb;

	while ((skb= __skb_dequeue(&session->back_log)) != NULL)
		kfree_skb(skb, 0);
 
	while ((skb= __skb_dequeue(&session->i_skbq)) != NULL)
		kfree_skb(skb, 0);   

	kfree(session);
}


/*
 * Function: nbss_ack_bytes
 *	Acknowledges reception of some bytes from input stream, by correctly
 *	setting control variables of session. 
 *
 * Parameters:
 *	session : pointer to session_t whose bytes are to be acknowledged.
 *
 * Returns: none
 *
 * Notes:
 *	- This function is used in transition handlers.
 *	- It does not actually acknowledge byte, it simply justifies 
 *	  acknowledgement control variables.
 */
static inline void
nbss_ack_bytes(session_t *session)
{
	unsigned short r_nacked= session->o_txed - session->r_acked;

	session->o_size += r_nacked;
	session->o_buff -= r_nacked;
	session->o_acked += session->r_acked;
	session->o_txed = session->r_acked = 0;
}


/*
 * Function: nbss_sleep_on
 *	This is a process control routine and customized version of Linux
 *	kernel sleep_on for session operations. It works in conjunction
 *	with session locking facilities to provide a secure mechanism for 
 *	process control. 
 *
 * Parameters: 
 *	session : pointer to session_t whose process is to be controlled
 *	state 	: Linux state parameter that describes how to sleep. Usally
 *		  TASK_INTERRUPTIBLE or TASK_UNINTERRUPTIBLE are values passed
 *		  for this argument. 
 *
 * Returns: none
 *
 * Notes:
 *	- The only difference between nbss_sleep_on and sleep_on is in the
 *	  decision made before schedule() which in nbss_sleep_on is determined
 *	  by nbss_release_session(). The reason is in abrupt termination of a
 *	  session while the sleeping procedure runs. This routines integrates 
 *	  conditions so that race conditions are avoided. 
 */
static inline void 
nbss_sleep_on(session_t *session, int state)
{
	unsigned long flags;
	struct wait_queue wait= { current, NULL };

	current->state= state;
	add_wait_queue(&session->waitq, &wait);
	save_flags(flags);
	sti();
	
	if (nbss_release_session(session) == 0)
		schedule();
	else
		current->state= TASK_RUNNING;

	remove_wait_queue(&session->waitq, &wait);
	restore_flags(flags);
}


/*
 * Function: nbss_add_session_to_list
 *	Inserts a previously allocated/initialized session_t into session
 *	pending list. 
 *
 * Parameters:
 *	session : pointer to session_t to add to pending session list
 *
 * Returns: none
 */
static inline void
nbss_add_session_to_list( session_t *session)
{
	session->next= session_list;
	session_list= session;
}


/*
 * Function: nbss_remove_session_from_list
 *	Removes a session from pending session list
 *
 * Parameters:
 *	session : pointer to session_t to remove from pending session list
 *
 * Returns: none
 */
static inline void
nbss_remove_session_from_list( session_t *session)
{
	session_t *entry= session_list;
	session_t *prev_entry= NULL;

	while (entry)
	{
	if (entry == session)
		{
		if (prev_entry)
			prev_entry->next= entry->next;
		else
			session_list= entry->next;
		}
	prev_entry= entry;
	entry= entry->next;
	}
	return;
}


/*
 * Function: nbss_find_listen
 *	Finds a session_t in session pending list who listens to a specific
 *	name.
 *
 * Parameters:
 *	name	: pointer to NetBIOS name the session listens to
 *
 * Returns: 
 *	NULL	: if no pending session found listening to name
 *	non-NULL: pointer to a pending session in list who listens to name
 */
static session_t *
nbss_find_listen( unsigned char *name)
{
	session_t *session= session_list;

	while (session)
		{
		if ((session->state == NB_SESS_LISTENWAIT) &&
		    (memcmp(session->local_name->name, name, NB_NAME_LEN) == 0))
			return session;
		session= session->next;
		}
	return NULL;
}


/*
 * Function: nbss_unicast_name_recognized
 *	Prepares a NetBIOS NAME RECOGNIZED frame and nbll_uisends it to network
 *
 * Parameters:
 *	session : pointer to session_t structure the frame should be built from
 *		  this session usually is selected from pending session list.
 *
 * Returns: 
 *	0	: if frame is successfully transmitted to network.
 *	non-zero: if frame transmission encountered an error (usually at NDI layer)
 *
 * Notes:
 *	- NetBIOS NAME RECOGNIZED frame is the response to a NetBIOS NAME QUERY
 *	  frame received from network. The interface which accepts NAME QUERY
 *	  frame passes the sk_buff via session->skb to reuse it for NAME RECOGNIZED
 *	  frame, thus reducing memory consumption.
 */
static int
nbss_unicast_name_recognized( session_t * session)
{
	/* It is supposed that name query skb is put in session->skb for reuse */
	dgram_t *hdr= (dgram_t *)session->skb->data;

	hdr->length= nb_command_header_len[ NAME_RECOGNIZED ];
	hdr->command= NAME_RECOGNIZED;
	hdr->data1= 0;
	hdr->data2= CALL_DATA2(session->local_name->type, session->lsn);
	hdr->xmit_correlator= hdr->resp_correlator;
	hdr->resp_correlator= session->resp_correlator= nbss_next_correlator();
	memcpy(hdr->dest_name, hdr->source_name, NB_NAME_LEN);
	memcpy(hdr->source_name, session->local_name->name, NB_NAME_LEN);

	return nbll_uisend(session->remote_mac, session->skb);
}


/*
 * Function: nbss_alloc_session_skb
 *	Allocates and prepares a skb for session service purposes
 *
 * Parameters:
 *	sn	: pointer to session that sk_buff is allocated for it.
 *	len	: length of data in skb
 *	priority: a Linux kernel style memory allocation policy flag from 
 *		  GFP_* (GFP_KERNEL, GF_ATOMIC, GFP_USER, ...) family. 
 *
 * Returns:
 *	NULL	: if can not allocate memory for sk_buff
 *	non-NULL: pointer to sk_buff
 */

static struct sk_buff *
nbss_alloc_session_skb(session_t *sn, int len, int priority)
{
	struct sk_buff *skb= alloc_skb((len + sn->llcmac_ihl), priority); 

#ifdef _NB_TR_DBG_
printk("nbss_alloc_session_skb 1>>> sn->llcmac_ihl=%d\n", sn->llcmac_ihl);
#endif
	if (!skb)
		return NULL;

	skb_reserve(skb, sn->llcmac_ihl);
	skb->free= 1;

	return skb;
}


/*
 * Function: nbss_ack_with_data
 *	Acknowledges incoming bytes with an outgoing frame by justifying
 *	data control bytes. 
 *
 * Parameters:
 *	session	: pointer to session_t, the acknowledgement is generated for
 *	hdr	: pointer to packet_t header of outgoing frame.
 *
 * Returns: none
 */
static inline void
nbss_ack_with_data(session_t * session, packet_t *hdr)
{
	barrier();
	if (session->o_ack_correlator)
		{
		del_timer(&session->timer);
		INCLUDE_ACK_WITH_DATA(hdr);
		hdr->xmit_correlator= (unsigned short)session->o_ack_correlator;
		session->o_ack_correlator= 0;
		}
}


/*
 * Function: nbss_isend_session_initialize
 *	Prepares a NetBIOS SESSION INITIALIZE frame and nbll_isends it toward
 *	remote session. 
 *
 * Parameters:
 *	session : pointer to session_t structure the frame should be built for
 *
 * Returns: 
 *	0	: if frame is successfully transmitted to network.
 *	-ENOMEM : if memory allocation for sk_buff failed.
 *	other   : if frame transmission encountered an error 
 *
 * Notes:
 *	- NetBIOS NAME RECOGNIZED frame is the response to a NetBIOS NAME QUERY
 *	  frame received from network. The interface which accepts NAME QUERY
 *	  frame passes the sk_buff via session->skb to reuse it for NAME RECOGNIZED
 *	  frame, thus reducing memory consumption.
 */
static int
nbss_isend_session_initialize( session_t * session)
{
	int session_packet_len= nb_command_header_len[ SESSION_INITIALIZE ];
	struct sk_buff *skb= nbss_alloc_session_skb(session, session_packet_len,
								GFP_ATOMIC);
	packet_t *hdr;

#ifdef _NB_TR_DBG_
printk("nbss_isend_session_initialize 1>>> STARTED!\n");
#endif
	if (!skb)
		return -ENOMEM;
	
#ifdef _NB_TR_DBG_
printk("nbss_isend_session_initialize 2>>> session_packet_len=%d\n", session_packet_len);
#endif
	hdr= (packet_t *)skb_put(skb, session_packet_len);
#ifdef _NB_TR_DBG_
printk("nbss_isend_session_initialize 3>>> OK!\n");
#endif
	hdr->length= session_packet_len;
	hdr->delimiter= NB_DELIMITER;
	hdr->command= SESSION_INITIALIZE;
	hdr->data1= 0x81; /* NetBIOS 2.2 with ability to handle nack */

//	hdr->data2= CALC_MTU(session->remote_dev);
	/* Token Ring support */
	CALC_SESS_MTU(session);

#ifdef _NB_TR_DBG_
printk("nbss_isend_session_initialize 4>>> session->mtu=%d\n", session->mtu);
#endif
	hdr->data1 |= ((session->tr_frame_lf << 1) & 0x0E);
	hdr->data2 = session->mtu;
#ifdef _NB_TR_DBG_
printk("nbss_isend_session_initialize 5>>> hdr->data1=%d\n", hdr->data1);
printk("nbss_isend_session_initialize 6>>> hdr->data2=%d\n", hdr->data2);
#endif

	hdr->xmit_correlator= session->xmit_correlator;
	hdr->resp_correlator= session->resp_correlator= nbss_next_correlator();
	hdr->dest_num= session->rsn;
	hdr->source_num= session->lsn;

	return nbll_isend(session->link_no, skb);
}


/*
 * Function: nbss_isend_session_confirm
 *	Prepares a NetBIOS SESSION CONFIRM frame and nbll_isends it toward
 *	remote session. 
 *
 * Parameters:
 *	session : pointer to session_t structure the frame should be built for
 *
 * Returns:
 *	0	: if frame is successfully transmitted to remote session.
 *	non-zero: if frame transmission encountered an error
 *
 * Notes:
 *	- NetBIOS SESSION INITIALIZE frame is the response to a NetBIOS SESSION
 *	  CONFIRM frame received from remote session. The interface which accepts 
 *	  SESSION INITIALIZE frame passes the sk_buff via session->skb to reuse 
 *	  it for SESSION CONFIRM frame, thus reducing memory consumption.
 */
static int 
nbss_isend_session_confirm( session_t * session)
{
	/* I suppose get_session_initialize has set session->skb for reuse */
	packet_t *hdr= (packet_t *)session->skb->data;

	hdr->length= nb_command_header_len[ SESSION_CONFIRM ];
	hdr->command= SESSION_CONFIRM;   
	hdr->data1= 0x81; /* NetBIOS 2.2 with ability to handle nack */
//	hdr->data2= CALC_MTU(session->remote_dev);
	hdr->data2= session->mtu;
	session->resp_correlator= hdr->resp_correlator;
	hdr->resp_correlator= session->xmit_correlator;
	hdr->xmit_correlator= session->resp_correlator;
	hdr->dest_num= session->rsn;
	hdr->source_num= session->lsn;
	
	return nbll_isend(session->link_no, session->skb);
}


/*
 * Function: nbss_isend_data_first_middle
 *	Prepares a NetBIOS DATA FIRST MIDDLE frame and nbll_isends it toward
 *	remote session. 
 *
 * Parameters:
 *	session : pointer to session_t structure the frame should be built for
 *	cflow	: flag that indicates requesting RECEIVE CONTINUE from remote session
 *		  zero means do not request RECEIVE CONTINUE
 *		  non-zero means request RECEIVE CONTINUE
 *
 * Returns: 
 *	0	: if frame is successfully transmitted to remote session.
 *	-ENOMEM : if memory allocation for sk_buff failed.
 *	other   : if frame transmission encountered an error 
 */
static int 
nbss_isend_data_first_middle( session_t *session, int cflow)
{
	int session_packet_len= nb_command_header_len[ DATA_FIRST_MIDDLE ];
	struct sk_buff *skb= nbss_alloc_session_skb(session,
					session_packet_len + session->mtu,
					GFP_KERNEL);
	packet_t *hdr;

	if (!skb)
		return -ENOMEM;

#ifdef _NB_TR_DBG_
printk("nbss_isend_data_first_middle 1>>> session_packet_len=%d\n", session_packet_len);
#endif
	hdr= (packet_t *)skb_put(skb, session_packet_len);
#ifdef _NB_TR_DBG_
printk("nbss_isend_data_first_middle 2>>> OK!\n");
#endif
	hdr->length= session_packet_len;
	hdr->delimiter= NB_DELIMITER;
	hdr->command= DATA_FIRST_MIDDLE;
	hdr->data1= 0;
	hdr->data2= session->o_receive_outstanding;
	session->o_receive_outstanding= 0;
	hdr->xmit_correlator= 0;
	hdr->resp_correlator= 0;
	hdr->dest_num= session->rsn;
	hdr->source_num= session->lsn;
	
	if (cflow)
		{
		REQUEST_RECEIVE_CONTINUE(hdr);
		session->o_receive_continue= 0;
		hdr->resp_correlator= session->resp_correlator= nbss_next_correlator();
		}
 
	if (session->o_noack)
		INDICATE_NACK(hdr);

#ifdef _NB_TR_DBG_
printk("nbss_isend_data_first_middle 3>>> session->mtu=%d\n", session->mtu);
#endif
	memcpy_fromfs(skb_put(skb, session->mtu), session->o_buff, session->mtu);
#ifdef _NB_TR_DBG_
printk("nbss_isend_data_first_middle 4>>> OK!\n");
#endif
	
	nbss_ack_with_data(session, hdr);

	return nbll_isend(session->link_no, skb);
}


/*
 * Function: nbss_isend_data_only_last
 *	Prepares a NetBIOS DATA ONLY LAST frame and nbll_isends it toward
 *	remote session. 
 *
 * Parameters:
 *	session : pointer to session_t structure the frame should be built for
 *
 * Returns: 
 *	0	: if frame is successfully transmitted to remote session.
 *	-ENOMEM : if memory allocation for sk_buff failed.
 *	other   : if frame transmission encountered an error 
 */
static int 
nbss_isend_data_only_last( session_t *session)
{
	int session_packet_len= nb_command_header_len[ DATA_ONLY_LAST ];
	struct sk_buff *skb= nbss_alloc_session_skb(session,
					session_packet_len + session->o_size,
					GFP_KERNEL);
	packet_t *hdr;

	if (!skb)
		return -ENOMEM;

#ifdef _NB_TR_DBG_
printk("nbss_isend_data_only_last 1>>> session_packet_len=%d\n",session_packet_len);
#endif
	hdr= (packet_t *)skb_put(skb, session_packet_len);
#ifdef _NB_TR_DBG_
printk("nbss_isend_data_only_last 2>>> OK!\n");
#endif
	hdr->length= session_packet_len;
	hdr->delimiter= NB_DELIMITER;
	hdr->command= DATA_ONLY_LAST;
	hdr->data1= 0;
	hdr->data2= session->o_receive_outstanding;
	session->o_receive_outstanding= 0;
	hdr->xmit_correlator= 0;
	hdr->resp_correlator= 0;
	hdr->dest_num= session->rsn;
	hdr->source_num= session->lsn;

	if (session->o_noack)
		INDICATE_NACK(hdr);
	else
		{
		hdr->resp_correlator= session->resp_correlator= nbss_next_correlator(); 
		ALLOW_ACK_WITH_DATA(hdr);
		}

#ifdef _NB_TR_DBG_
printk("nbss_isend_data_only_last 3>>> session->o_size=%d\n",session->o_size);
#endif
	memcpy_fromfs(skb_put(skb, session->o_size), session->o_buff, session->o_size);
#ifdef _NB_TR_DBG_
printk("nbss_isend_data_only_last 4>>> OK!\n");
#endif

	nbss_ack_with_data(session, hdr);

	return nbll_isend(session->link_no, skb);
}


/*
 * Function: nbss_isend_receive_continue
 *	Prepares a NetBIOS RECEIVE CONTINUE frame and nbll_isends it toward
 *	remote session. 
 *
 * Parameters:
 *	session : pointer to session_t structure the frame should be built for
 *	hdr	: pointer to packet_t header of the incoimg frame which the
 *		  RECEIVE CONTINUE frame is built for.
 *
 * Returns: 
 *	0	: if frame is successfully transmitted to remote session.
 *	-ENOMEM : if memory allocation for sk_buff failed.
 *	other   : if frame transmission encountered an error 
 */
static int
nbss_isend_receive_continue(packet_t *source_hdr, session_t *session)
{
	int session_packet_len= nb_command_header_len[ RECEIVE_CONTINUE ];
	struct sk_buff *skb= nbss_alloc_session_skb(session, session_packet_len,
								GFP_ATOMIC);
	packet_t *hdr;

	if (!skb)
		return -ENOMEM;

#ifdef _NB_TR_DBG_
printk("nbss_isend_receive_continue 1>> session_packet_len=%d\n", session_packet_len);
#endif
	hdr= (packet_t *)skb_put(skb, session_packet_len);
#ifdef _NB_TR_DBG_
printk("nbss_isend_receive_continue 2>> OK!\n");
#endif
	hdr->length= session_packet_len;
	hdr->delimiter= NB_DELIMITER;
	hdr->command= RECEIVE_CONTINUE;
	hdr->data1= 0;
	hdr->data2= 0;
	hdr->xmit_correlator= source_hdr->resp_correlator;
	hdr->resp_correlator= 0;
	hdr->dest_num= session->rsn;
	hdr->source_num= session->lsn;

	return nbll_isend(session->link_no, skb);
}


/*
 * Function: nbss_isend_no_receive
 *	Prepares a NetBIOS RECEIVE CONTINUE frame and nbll_isends it toward
 *	remote session. 
 *
 * Parameters:
 *	session : pointer to session_t structure the frame should be built for
 *	skb	: pointer to sk_buff to use for building frame. This sk_buff 
 *		  is the one that contained incoming frame and since its data
 *		  is not accepted from input stream, it is passed for reuse.
 *
 * Returns: 
 *	0	: if frame is successfully transmitted to remote session.
 *	-ENOMEM : if memory allocation for sk_buff failed.
 *	other   : if frame transmission encountered an error 
 */
static int
nbss_isend_no_receive( struct sk_buff *skb, session_t *session)
{
	int session_packet_len= nb_command_header_len[ NO_RECEIVE ];
	packet_t *hdr;
	unsigned char data1= NACK_INDICATOR(((packet_t *)skb->data));
	int retval;

	skb_trim(skb, 0);
#ifdef _NB_TR_DBG_
printk("nbss_isend_no_receive 1>> session_packet_len=%d\n", session_packet_len);
#endif
	hdr= (packet_t *)skb_put(skb, session_packet_len);
#ifdef _NB_TR_DBG_
printk("nbss_isend_no_receive 2>> OK!\n");
#endif
	hdr->length= session_packet_len;
	hdr->delimiter= NB_DELIMITER;
	hdr->command= NO_RECEIVE;
	hdr->data1= data1;
	hdr->data2= session->i_notacked;
	hdr->xmit_correlator= 0;
	hdr->resp_correlator= 0;
	hdr->dest_num= session->rsn;
	hdr->source_num= session->lsn;

	if ((retval= nbll_isend(session->link_no, skb)) != 0)
		return retval;

	session->i_state= NB_RECV_NO_RECEIVE;
	session->i_notacked= 0;
	return 0;
}


/*
 * Function: nbss_isend_receive_outstanding
 *	Prepares a NetBIOS RECEIVE OUTSTANDING frame and nbll_isends it toward
 *	remote session. 
 *
 * Parameters:
 *	session : pointer to session_t structure the frame should be built for
 *
 * Returns:
 *	0	: if frame is successfully transmitted to remote session.
 *	-ENOMEM : if memory allocation for sk_buff failed.
 *	other   : if frame transmission encountered an error 
 *
 * Notes:
 *	- Since the frame is transmitted within a user process context,
 *	  and and works with input state variable, it locks a session during
 *	  transmission period.
 */
static int
nbss_isend_receive_outstanding(session_t *session)
{
	int session_packet_len= nb_command_header_len[ RECEIVE_OUTSTANDING ];
	struct sk_buff *skb= nbss_alloc_session_skb(session, session_packet_len,
								GFP_KERNEL);
	packet_t *hdr;
	int retval;

	if (!skb)
		return -ENOMEM;

#ifdef _NB_TR_DBG_
printk("nbss_isend_receive_outstanding 1>> session_packet_len=%d\n", session_packet_len);
#endif
	hdr= (packet_t *)skb_put(skb, session_packet_len);
#ifdef _NB_TR_DBG_
printk("nbss_isend_receive_outstanding 2>> OK!\n");
#endif
	hdr->length= session_packet_len;
	hdr->delimiter= NB_DELIMITER;
	hdr->command= RECEIVE_OUTSTANDING;
	hdr->data1= 0;
	hdr->data2= 0;
	hdr->xmit_correlator= 0;
	hdr->resp_correlator= 0;
	hdr->dest_num= session->rsn;
	hdr->source_num= session->lsn;
	
	nbss_lock_session(session);

	if ((retval= nbll_isend(session->link_no, skb)) == 0)
		session->i_state= NB_RECV_RECEIVE_OUTSTANDING;

	nbss_release_session(session);
	
	return retval;
}


/*
 * Function: nbss_isend_data_ack
 *	Prepares a NetBIOS DATA ACK frame and nbll_isends it toward remote session. 
 *
 * Parameters:
 *	session : pointer to session_t structure the frame should be built for
 *	ack_correlator: this is the xmit correlator found in the incoming frame
 *		        passed to be put in response coreelator of the DATA ACK
 *
 * Returns:
 *	0	: if frame is successfully transmitted to remote session.
 *	-ENOMEM : if memory allocation for sk_buff failed.
 *	other   : if frame transmission encountered an error 
 */
static int 
nbss_isend_data_ack(session_t *session, unsigned short ack_correlator)
{
	int session_packet_len= nb_command_header_len[ DATA_ACK ];
	struct sk_buff *skb= nbss_alloc_session_skb(session, session_packet_len,
								GFP_ATOMIC);
	packet_t *hdr;

	if (!skb)
		return -ENOMEM;

#ifdef _NB_TR_DBG_
printk("nbss_isend_data_ack 1>> session_packet_len=%d\n", session_packet_len);
#endif
	hdr= (packet_t *)skb_put(skb, session_packet_len);
#ifdef _NB_TR_DBG_
printk("nbss_isend_data_ack 2>> OK!\n");
#endif
	hdr->length= session_packet_len;
	hdr->delimiter= NB_DELIMITER;
	hdr->command= DATA_ACK;
	hdr->data1= 0;
	hdr->data2= 0;
	hdr->xmit_correlator= ack_correlator;
	hdr->resp_correlator= 0;
	hdr->dest_num= session->rsn;
	hdr->source_num= session->lsn;
	
	return nbll_isend(session->link_no, skb);
}


/*
 * Function: nbss_ack_session_data
 *	Actually acknowledges incoming session data depending on current
 *	condition either by sending DATA ACK frame or by postponing and
 *	sending ACK WITH DATA. 
 *
 * Parameters:
 *	session : pointer to session_t structure the frame should be built for
 *	hdr	: pointer to packet_t header of the incoimg frame which the 
 *		  acknowledgement is built for.
 *
 * Returns:
 *	0	: if frame is successfully transmitted to remote session.
 *	-ENOMEM : if memory allocation for sk_buff failed.
 *	other   : if frame transmission encountered an error 
 */
static inline int 
nbss_ack_session_data(packet_t *hdr, session_t *session)
{
	if (ACK_WITH_DATA_ALLOWED(hdr) && 
	   (! session->o_ack_correlator) &&
	   (! session->urgent_ack))
		{
		session->o_ack_correlator= hdr->resp_correlator | ACK_FLAG;
		session->timer.expires= jiffies + NB_DATA_ACK_TIMEOUT;
		add_timer(&session->timer);
		return 0;
		}

	return nbss_isend_data_ack(session, hdr->resp_correlator);
}


/*
 * Function: nbss_ack_timer_function
 *	Handles delayed acknowledgement for a session when its timer expires
 *	by sending a DATA ACK frame.
 *
 * Parameters:
 *	input	: pointer to session_t structure whose timer is expired.
 *
 * Returns: none
 *
 * Notes:
 *	- We never re-enter LLC from its upper layer interface since it is
 *	  not re-entrant. However ack timers expire and thus cause NetBEUI 
 *	  to send DATA ACK which re-enters LLC. 
 */
static void 
nbss_ack_timer_function( unsigned long input)
{
	session_t *session= (session_t *)input;

	if ((llc_in_progress) ||
	    (nbss_isend_data_ack(session, session->o_ack_correlator) != 0))
		{
		session->timer.expires= jiffies + NB_DATA_ACK_TIMEOUT;
		add_timer(&session->timer);
		}
	else
		session->o_ack_correlator= 0;
}


/*
 * Function: nbss_isend_session_end
 *	Prepares a NetBIOS SESSION END frame and nbll_isends it toward remote session. 
 *
 * Parameters:
 *	session : pointer to session_t structure the frame should be built for
 *
 * Returns: 
 *	0	: if frame is successfully transmitted to remote session.
 *	-ENOMEM : if memory allocation for sk_buff failed.
 *	other   : if frame transmission encountered an error 
 */
static int
nbss_isend_session_end( session_t * session)
{
	int session_packet_len= nb_command_header_len[ SESSION_END ];
	struct sk_buff *skb= nbss_alloc_session_skb(session, session_packet_len,
								GFP_KERNEL);
	packet_t *hdr;

	if (!skb)
		return -ENOMEM;

#ifdef _NB_TR_DBG_
printk("nbss_isend_session_end 1>> session_packet_len=%d\n", session_packet_len);
#endif
	hdr= (packet_t *)skb_put(skb, session_packet_len);
#ifdef _NB_TR_DBG_
printk("nbss_isend_session_end 2>> OK!\n");
#endif
	hdr->length= session_packet_len;
	hdr->delimiter= NB_DELIMITER;
	hdr->command= SESSION_END;
	hdr->data1= 0;
	hdr->data2= 0;
	hdr->xmit_correlator= 0;
	hdr->resp_correlator= 0;
	hdr->dest_num= session->rsn;
	hdr->source_num= session->lsn;
	
	return nbll_isend(session->link_no, skb);
}



/*
 * Function: nbns_handle_event
 *	This is the heart of Session Service State Machine, which performs a 
 *	transition from current state of session element to new state based
 *	on event occured and session state table contents.
 *
 * Parameters:
 *	event	: An integer of NB_SESS_* 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 NBSS_DEBUG in Makefile causes the module to generate the code 
 *         that provide usefull information about transitions on every session element. 
 */
static void
nbss_handle_event( session_event_t event, session_t *session)
{
	struct event_struct *ev= &session_state_table[session->state][event];
	unsigned char old_state;

#ifdef NBSS_DEBUG
	printk("NBSS event handler (LINK=%d,LSN=%u,RSN=%u): EVENT=%u on STATE=%u\n", session->link_no, session->lsn, session->rsn, event, session->state);
#endif NBSS_DEBUG

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

#ifdef NBSS_DEBUG
	printk("NBSS event handler (LINK=%d,LSN=%u,RSN=%u): NEW STATE=%u\n", session->link_no, session->lsn, session->rsn, session->state);
#endif NBSS_DEBUG

	return;
}


/*
 * Function: nbss_wait_timer_function
 *	This is the callback function trigerred upon expiration of session
 *	wait timer used first during session establishment and then for
 *	delaying acknowledgements.
 *
 * Parameters:
 *	input	: pointer to session_t structure whose timer is expired.
 *
 * Returns: none
 *
 * Notes:
 *	- This timer function, set in session allocation time is used during
 *	  session establishment.
 */
static void 
nbss_wait_timer_function( unsigned long input)
{
	session_t *session= (session_t *)input;

	session->status= -ETIMEDOUT;
	nbss_handle_event(NB_SESS_TIMEOUT, session);
	
	return;
}


/*
 * Function: 
 *	This is the callback function trigerred upon expiration of session
 *	resource timer used when resources (usually memory) are ended up. 
 *
 * Parameters:
 *	input	: pointer to session_t structure whose timer is expired.
 *
 * Returns: none
 */
static void 
nbss_resource_timer_function( unsigned long input)
{
	session_t *session= (session_t *)input;

	switch (session->o_rsrc_origin)
		{
		case NB_ORIGIN_CONNECTED:
			nbss_handle_event(NB_SESS_CONN_RETRY, session);
			break;
		case NB_ORIGIN_NORMAL:
			nbss_handle_event(NB_SESS_NORM_RETRY, session);
			break;
		default:
			printk("NetBEUI serious error: Resource timer expired from %u\n", 
			session->o_rsrc_origin);
			break;
		}
}




/*
 * Session service state machine functions
 * Implementing transition actions
 */

static int  
nbss_abort_send_in_all(session_t *session)
{
	session->o_aborted= 1;
	
	wake_up(&session->waitq);
	
	return 0;
}


static int  
nbss_call_in_initial( session_t * session)
{
	unsigned long flags;

	save_flags(flags);
	cli();

	session->status= nbll_attach_session(session, session->remote_dev, session->remote_mac);

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

	nbss_add_session_to_list(session);

	restore_flags(flags);

	return 0;
}


static int  
nbss_listen_in_initial( session_t * session)
{
	unsigned long flags;
	
	save_flags(flags);
	cli();

	nbss_add_session_to_list(session);
	
	restore_flags(flags);

	return 0;
}


static int  
nbss_confirm_in_callwait( session_t * session)
{
	unsigned long flags;

	session->status= nbll_link_session(session->link_no, session->lsn);

	if (session->status != 0)
		return session->status;

	session->status= nbss_isend_session_initialize(session);

	if (session->status != 0)
		return -ENOMEM;

	save_flags(flags);
	cli();

	nbss_remove_session_from_list(session);

	session->timer.expires= jiffies+ (NB_TRANSMIT_TIMEOUT * NB_TRANSMIT_COUNT);
	add_timer(&session->timer);

	sleep_on(&session->waitq);

	restore_flags(flags);

	return 0;
}


static int  
nbss_reject_in_callwait( session_t * session)
{
	unsigned long flags;

	save_flags(flags);
	cli();

	nbss_remove_session_from_list(session);

	nbll_detach_session(session->link_no, session->lsn);

	restore_flags(flags);

	session->status= -ECONNABORTED;

	return 0;
}


static int  
nbss_abort_in_callwait( session_t * session)
{
	nbss_remove_session_from_list(session);

	session->status= -ECONNRESET;

	return 0;
}


static int  
nbss_connect_in_confwait( session_t * session)
{
	del_timer(&session->timer);

	wake_up(&session->waitq);

	session->status= 0;

	return 0;
}


static int  
nbss_timeout_in_confwait( session_t * session)
{
	nbll_detach_session(session->link_no, session->lsn);

	wake_up(&session->waitq);

	session->status= -ECONNREFUSED;

	return 0;
}


static int  
nbss_abort_in_confwait( session_t * session)
{    
	del_timer(&session->timer);

	wake_up(&session->waitq);

	session->status= -ECONNRESET;

	return 0;
}


static int  
nbss_confirm_in_listenwait( session_t * session)
{
	session->status= nbll_attach_session(session, session->remote_dev, 
		                             session->remote_mac);

	if (session->status != 0)
		return -1;

	session->status= nbss_unicast_name_recognized(session);
	if (session->status != 0)
		{
		nbll_detach_session(session->link_no, session->lsn);
		return -1;
		}

	session->timer.expires= jiffies+ (NB_TRANSMIT_TIMEOUT * NB_TRANSMIT_COUNT);
	add_timer(&session->timer);

	return 0;
}


static int  
nbss_reject_in_listenwait( session_t * session)
{
	unsigned long flags;

	save_flags(flags);
	cli();
	
	nbss_remove_session_from_list(session);

	restore_flags(flags);

	session->status= -ECONNABORTED;

	return 0;
}


static int  
nbss_connect_in_initwait( session_t * session)
{
	session->status= nbss_isend_session_confirm(session);

	if (session->status != 0)
	return -1;

	del_timer(&session->timer);        

	nbss_remove_session_from_list(session);

	session->session_ready_callback(session->owner, session);

	session->status= 0;

	return 0;
}


static int  
nbss_timeout_in_initwait( session_t * session)
{
	nbll_detach_session(session->link_no, session->lsn);

	session->status= -ECONNREFUSED;

	return 0;
}


static int  
nbss_abort_in_initwait( session_t * session)
{
	del_timer(&session->timer);

	session->status= -ECONNRESET;

	return 0;
}


static int  
nbss_reject_in_initwait( session_t * session)
{
	del_timer(&session->timer);

	nbll_detach_session(session->link_no, session->lsn);

	nbss_remove_session_from_list(session);

	session->status= -ECONNABORTED;

	return 0;
}


static int  
nbss_abort_in_connected( session_t * session)
{
	if (session->owner)
		session->abort_owner_callback(session->owner, session);

	session->status= -ECONNRESET;

	return 0;
}


static int  
nbss_hangup_in_connected( session_t * session)
{
	unsigned long flags;

	nbss_isend_session_end(session);

	save_flags(flags);
	cli();

	nbll_detach_session(session->link_no, session->lsn);

	restore_flags(flags);

	session->status= -ECONNABORTED;

	return 0;
}


static int  
nbss_end_in_connected( session_t * session)
{
	if (session->owner)
		session->abort_owner_callback(session->owner, session);

	nbll_detach_session(session->link_no, session->lsn);

	session->status= -ECONNRESET;

	return 0;
}


static int
nbss_hangup_in_discwait(session_t *session)
{
	return 0;
}


static int  
nbss_first_middle_cont_in_connected(session_t *session)
{
	session->status= nbss_isend_data_first_middle(session, 1);
	if (session->status != 0)
		return -1;

	session->o_buff += session->mtu;
	session->o_size -= session->mtu;
	session->o_txed += session->mtu;

	nbss_sleep_on(session, TASK_UNINTERRUPTIBLE);

	return 0;
}


static int  
nbss_first_middle_in_connected(session_t *session)
{
	session->status= nbss_isend_data_first_middle(session, 0);
	if (session->status != 0)
		return -1;

	session->o_buff += session->mtu;
	session->o_size -= session->mtu;
	session->o_txed += session->mtu;

	return 0;
}


static int  
nbss_only_last_ack_in_connected(session_t *session)
{
	session->status= nbss_isend_data_only_last(session);
	if (session->status != 0)
		return -1;

	session->o_buff += session->o_size;
	session->o_txed += session->o_size;
	session->o_size -= session->o_size;

	nbss_sleep_on(session, TASK_UNINTERRUPTIBLE);

	return 0;
}


static int  
nbss_only_last_in_connected(session_t *session)
{
	session->status= nbss_isend_data_only_last(session);
	if (session->status != 0)
		return -1;

	session->o_buff += session->o_size;
	session->o_txed += session->o_size;
	session->o_size -= session->o_size;

	return 0;
}


static int  
nbss_resource_in_connected(session_t *session)
{
	session->o_rsrc_origin= NB_ORIGIN_CONNECTED;

	session->timer.expires= jiffies+NB_RESOURCE_TIMEOUT;
	session->timer.function= nbss_resource_timer_function;
	add_timer(&session->timer);

	sleep_on(&session->waitq);

	return 0;
}


static int  
nbss_continue_in_contwait(session_t *session)
{
	session->o_acked += session->o_txed;
	session->o_txed= 0;

	wake_up(&session->waitq);

	return 0;
}


static int  
nbss_restart_in_contwait(session_t *session)
{
	nbss_ack_bytes(session);

	wake_up(&session->waitq);

	return 0;
}


static int  
nbss_pause_in_contwait(session_t *session)
{    
	nbss_ack_bytes(session);

	session->o_no_receive= 1;

	wake_up(&session->waitq);

	return 0;
}


static int  
nbss_nonblock_in_contwait(session_t *session)
{
	nbss_ack_bytes(session);

	session->o_no_receive= 1;

	wake_up(&session->waitq);

	return 0;
}


static int  
nbss_restart_in_standwait(session_t *session)
{
	session->o_no_receive= 0;

	wake_up(&session->waitq);
	
	return 0;
}


static int  
nbss_pause2_in_standwait(session_t *session)
{
	nbss_sleep_on(session, TASK_INTERRUPTIBLE);

	return 0;
}


static int  
nbss_data_acked_in_ackwait(session_t *session)
{
	session->o_acked += session->o_txed;
	session->o_txed= 0;
	
	wake_up(&session->waitq);

	return 0;
}


static int  
nbss_restart_in_ackwait(session_t *session)
{
	nbss_ack_bytes(session);

	wake_up(&session->waitq);

	return 0;
}


static int  
nbss_pause_in_ackwait(session_t *session)
{
	nbss_ack_bytes(session);

	session->o_no_receive= 1;

	wake_up(&session->waitq);

	return 0;
}


static int  
nbss_nonblock_in_ackwait(session_t *session)
{
	nbss_ack_bytes(session);

	session->o_no_receive= 1;

	wake_up(&session->waitq);

	return 0;
}


static int  
nbss_norm_retry_in_rsrcwait(session_t *session)
{
	wake_up(&session->waitq);

	return 0;
}


static int  
nbss_conn_retry_in_rsrcwait(session_t *session)
{
	wake_up(&session->waitq);

	return 0;
}


static int  
nbss_first_middle_in_normal(session_t *session)
{
	session->status= nbss_isend_data_first_middle(session, 0);
	if (session->status != 0)
		return -1;

	session->o_buff += session->mtu;
	session->o_size -= session->mtu;
	session->o_txed += session->mtu;

	return 0;
}


static int  
nbss_restart_in_normal(session_t *session)
{
	nbss_ack_bytes(session);

	return 0;
}


static int  
nbss_only_last_ack_in_normal(session_t *session)
{
	session->status= nbss_isend_data_only_last(session);
	if (session->status != 0)
		return -1;

	session->o_buff += session->o_size;
	session->o_txed += session->o_size;
	session->o_size -= session->o_size;

	nbss_sleep_on(session, TASK_UNINTERRUPTIBLE);

	return 0;
}


static int  
nbss_only_last_in_normal(session_t *session)
{
	session->status= nbss_isend_data_only_last(session);
	if (session->status != 0)
		return -1;

	session->o_buff += session->o_size;
	session->o_txed += session->o_size;
	session->o_size -= session->o_size;

	return 0;
}


static int  
nbss_pause_in_normal(session_t *session)
{
	nbss_ack_bytes(session);

	session->o_no_receive= 1;

	return 0;
}


static int  
nbss_nonblock_in_normal(session_t *session)
{
	nbss_ack_bytes(session);

	session->o_no_receive= 1;

	return 0;
}


static int  
nbss_resource_in_normal(session_t *session)
{
	session->o_rsrc_origin= NB_ORIGIN_NORMAL;

	session->timer.expires= jiffies+NB_RESOURCE_TIMEOUT;
	session->timer.function= nbss_resource_timer_function;
	add_timer(&session->timer);
	
	sleep_on(&session->waitq);

	return 0;
}




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

/*
 * Function: nbss_call
 *	This is a sophisticated interface routine for establishing a session
 *	with a remote hosts. Finding host that owns name, establishing session
 *	building LLC connection and configuring session are the phases a session
 *	element passes through them to prepare session for transferring data.
 *
 * Parameters:
 *	calling_name   : pointer to name_t structure that the session is built
 *			 either registered by client or NAME_NUMBER_1 auto bound
 *	called_name    : pointer to NetBIOS name of remote node 
 *	owner	       : pointer to a data structure of the owner of session 
 *			 used for calling its call backs
 *	itf_abort_owner: pointer to a callback routine for alerting owner, of
 *			 session abortion 
 *	session_ptr    : (VRP) pointer to session_tstructure if return vlaue
 *			 indicates a valid value which is a pointer to session_t
 *			 The owner later uses this value for getting services 
 *			 from Session Service module
 *
 * Returns: 
 *	0		: if a session successfully established to remote node
 * 	-ENOMEM 	: if failed to allocate memory for session_t structure.	
 *	-EHOSTUNREACH	: if can not find a node on network who owns the name.
 *	-ETOONAMYREFS	: if more than one node responded to session establishment 
 *			  request. This means that it can not eastablish session
 *			  with group names.
 *	-ECONNREFUSED	: if node responded to request but refused to establish 
 *			  a session anyway.
 *	-ECONNRESET	: if connection reset by LLC before session establishment
 *			  ends
 *	other		: any other error reported by LLC.
 */

int 
nbss_call( name_t * calling_name, char * called_name, void *owner,
       abort_owner_cbt itf_abort_owner, session_t **session_ptr)
{
	unsigned long flags;
	int status;
#define MAX_FIND 3
	char macs [MAX_FIND] [6];
	struct device *devs [MAX_FIND];
	session_t *session= nbss_alloc_session();

#ifdef _NB_TR_DBG_
printk("nbss_call 1>>> STARTED!\n");
#endif
	if (!session)
		return -ENOMEM;

	*session_ptr= NULL;

	session->state= NB_SESS_INITIAL;
	session->owner= owner;
	session->local_name= calling_name;
	memcpy(session->remote_name, called_name, NB_NAME_LEN);
	session->abort_owner_callback= itf_abort_owner;

	status= nbqs_find_name(called_name, (char *)macs, devs, MAX_FIND);
	if (status < 0)
		{
		nbss_free_session(session);
		return status;
		}
	else if (status == 0)
		{
		nbss_free_session(session);
		return -EHOSTUNREACH;
		}
	else if (status != 1) 
		{
		nbss_free_session(session);
		return -ETOOMANYREFS;
		}

	session->remote_dev= devs[0];
	memcpy(session->remote_mac, macs, session->remote_dev->addr_len);

	do {
		nbss_handle_event(NB_SESS_CALL, session);
		if (session->state == NB_SESS_INITIAL)
			{
			status= session->status;
			nbss_free_session(session);
			return status;
			}

		session->status= nbqs_query_name(called_name, calling_name,
						session->lsn, &session->rsn,
						&session->tr_frame_lf,
						&session->xmit_correlator);

		save_flags(flags);
		cli();

		/* We may have got a session abort here */
		if (session->state == NB_SESS_INITIAL) 
			{
			restore_flags(flags);
			continue;
			}
		else
			break;
	} while (1);

	/* We may also could not query server */ 
	if ((session->status != 0) ||
	    (session->rsn == 0) ||
	    (session->rsn == 0xFF))
		{
		restore_flags(flags); 
		nbss_handle_event(NB_SESS_REJECT, session);
		nbss_free_session(session);
		return -ECONNREFUSED;
		}


	sti();

	/* Token Ring support */
	session->llcmac_ihl = LLCMAC_I_HEADLEN(session->remote_dev);
#ifdef _NB_TR_DBG_
printk("nbss_call 2>>> session->llcmac_ihl=%d\n", session->llcmac_ihl);
#endif

	nbss_handle_event(NB_SESS_CONFIRM, session);

	cli();

	if ((session->state != NB_SESS_CONNECTED) || (session->status != 0))
		{
		restore_flags(flags);
		status= session->status;
		nbss_free_session(session);
		nbqs_delete_rnc(called_name);
		return ((status==0) ? -ECONNRESET : status);
		}

	*session_ptr= session;

	restore_flags(flags);
	return 0;
}


/*
 * Function: nbss_listen
 *	Prepares backlog session_ts for a server who is going to listen on
 *	a name, kept in pending session list. 
 *
 * Parameters:
 *	nb_name	: pointer to name_t the sessions listen to connection requests on it
 *	backlog	: count of concurrent session establishments the session service
 *		  should process at once.
 *	owner	: pointer to a data structure of the owner of session 
 *		  used for calling its call backs
 *	itf_abort_owner: pointer to a callback routine for alerting owner, of
 *	     	  session abortion 
 * 	itf_session_ready: 	pointer to callback routine alerting owner, of
 *		  existence of an established session.
 *
 * Returns: 
 *	>= 0	: count of backlog pending session in list who listen to name
 *		  for connection establishment requests.
 *
 * Notes:
 *	- The session does not guarantee to have as many pending sessions
 *	  as requested by backlog parameter due to memory allocation fails.
 *	  This should be considered by upper layer routines. 
 */
int
nbss_listen( name_t *nb_name, int backlog, void *owner,
         abort_owner_cbt itf_abort_owner, session_ready_cbt itf_session_ready)
{    
	unsigned long flags;
	int count, exist= 0;
	session_t *session;

	save_flags(flags);
	cli();

	session= session_list;
	while (session)
	{
	if (session->local_name== nb_name)
		exist++;
	session= session->next;
	}
    
	restore_flags(flags);
	
	for (count= exist; count<backlog; count++)
		{ 
		session= nbss_alloc_session();
		if (session)
			{
			session->state= NB_SESS_INITIAL;
			session->owner= owner;
			session->local_name= nb_name;
			session->abort_owner_callback= itf_abort_owner;
			session->session_ready_callback= itf_session_ready; 
		
			nbss_handle_event(NB_SESS_LISTEN, session);

			exist++;
			}
		else
			break;
		}

	return exist;
}


/*
 * Function: nbss_end_listen
 *	Removes session_ts on pending session list who listen to session
 *	establishment requests on a specific name
 *
 * Parameters:
 *	nb_name	: pointer to name which sessions listening to it should be
 *		  removed from pending session list.
 *
 * Returns: none
 */
void
nbss_end_listen( name_t *nb_name)
{
	unsigned long flags;
	session_t *next_session;
	session_t *session;

	save_flags(flags);
	cli();

	session= session_list;
	while (session)
		{
		next_session= session->next;
		if (session->local_name == nb_name)
			{
			nbss_handle_event(NB_SESS_REJECT, session);
			nbss_free_session(session);
			}
		session= next_session;
		}	

	restore_flags(flags);
}


/*
 * Function: nbss_send
 *	This is the heart of output data stream.
 *	Sends a block of user data to remote session by fragmenting and 
 *	controlling flow of data. In addition to simplicity it operates
 *	a considerable part of Session Service State Machine by generating
 *	different events carefully
 *
 * Parameters:
 *	session	: pointer to session_t to send data on
 *	buf	: pointer to user buffer that contains data
 *	size	: size of data in user buffer
 *	nonblock: flag that indicates to block user process or not block it (0/1)
 *	noack	: flag that indicates to receive data receive acknowledgement
 *		  or not (0/1)
 *
 * Returns:
 *	>= 0		: count of data bytes successfully sent to remote session
 *	-ECONNABORTED	: if session aborted.
 *	-EINPROGRESS	: if another process currently sends data in session
 *			  this may happen if two process share a socket.
 *	-ETIME		: if send timedout. this happens if interface layer
 *			  calls nbss_send_abort when its timers expire.
 */
int
nbss_send(session_t *session, 
      unsigned char *buf, unsigned short size, unsigned char nonblock, 
      unsigned char noack)
{
	int rc= 0;

	if (session->zapped)
		return -ECONNABORTED;

	if (session->state != NB_SESS_CONNECTED)
		return -EINPROGRESS;

	session->o_nonblock= nonblock;
	session->o_noack= (session->version == NB_VERSION_2xx) ? noack : 0;
	session->o_no_receive= 0;
	session->o_rsrc_origin= 0;
	session->o_buff= buf;
	session->o_buffsize= size;
	session->o_size= size;
	session->o_txed= 0;
	session->o_acked= 0;
	session->r_acked= 0;
	session->o_aborted= 0;

	while (session->o_acked < session->o_buffsize)
		{
		nbss_lock_session(session);
		
		session->status= 0;

		if ((session->o_size > session->mtu) &&
 		    (session->o_size == session->o_buffsize) &&
		    (session->o_receive_continue))
			nbss_handle_event(NB_SESS_FIRST_MIDDLE_CONT, session);
         
		else if (session->o_size > session->mtu)
			nbss_handle_event(NB_SESS_FIRST_MIDDLE, session);
         
		else if ((noack == 0) && (session->o_size > 0))
			nbss_handle_event(NB_SESS_ONLY_LAST_ACK, session);
         
		else if (session->o_size > 0) 
			nbss_handle_event(NB_SESS_ONLY_LAST, session);
 
		if (nbss_release_session(session) != 0)
			return session->status;

		if (session->o_aborted)
			return -ETIME;
     
		if ((session->o_no_receive) && (session->o_nonblock))
			{ 
			rc= session->o_acked;  
			break;
			}
			
     
		if (session->o_no_receive)
			{
			nbss_lock_session(session);
			nbss_handle_event(NB_SESS_PAUSE2, session);
			nbss_release_session(session);
			if (current->signal & ~current->blocked)
				{
				rc= (session->o_acked > 0) ? session->o_acked : -ERESTART;
				break;
				}
			}

		if (session->status ==  -ENOMEM)
			{
			nbss_lock_session(session);
			nbss_handle_event(NB_SESS_RESOURCE, session);
			nbss_release_session(session);
			}

		rc= session->o_acked;
		}

	session->state= NB_SESS_CONNECTED;

	if (rc > size)  
		rc= size;

	if (rc > 0) 
		session->o_total += rc;

	return rc;
}


/*
 * Function: nbss_send_zero
 *	Sends a zero byte DATA ONLY LAST frame to remote node. 
 *
 * Parameters:
 *	session	: pointer to session_t to send frame on
 *	buf	: pointer to a user buffer that is referenced in transition
 *		  handlers of state machine
 *
 * Returns: 
 *	>= 0		: if size of data bytes successfully sent to remote session
 *	-ECONNABORTED	: if session aborted.
 *	-EINPROGRESS	: if another process currently sends data in session
 *			  this may happen if two process share a socket.
 *
 * Notes:
 *	- This feature is activated via ioctl interface and has special 
 *	  meaning to SAMBA that operates over NetBEUI 
 */
int
nbss_send_zero(session_t *session, char *buf)
{
	if (session->zapped)
		return -ECONNABORTED;

	if (session->state != NB_SESS_CONNECTED)
		return -EINPROGRESS;

	session->o_nonblock= 1;
	session->o_noack= 1;
	session->o_no_receive= 0;
	session->o_rsrc_origin= 0;
	session->o_buff= buf;
	session->o_buffsize= 0;
	session->o_size= 0;
	session->o_txed= 0;
	session->o_acked= 0;
	session->r_acked= 0;
	session->o_aborted= 0;

	session->status= 0;
	nbss_handle_event(NB_SESS_ONLY_LAST, session);
	return session->status;
}


/*
 * Function: nbss_abort_send
 *	The interface which aborts a send operation, usually called from
 *	upper interface when send timers expire.
 *
 * Parameters:
 *	session	: pointer to session_t whose send operation is to be aborted.
 *
 * Returns: none
 */
void
nbss_abort_send(session_t *session)
{
	session->o_aborted= 1;
	nbss_handle_event(NB_SESS_ABORT_SEND, session);
}


/*
 * Function: nbss_send_ready
 *	Determines if session is ready to send data or not
 *
 * Parameters:
 *	session	: pointer to session_t.
 *
 * Returns: 
 *	0	: if session is ready to send data
 *	other	: if session is not ready to send data
 */
int
nbss_send_ready(session_t *session)
{
	if ((!session->zapped) &&
	    (session->state == NB_SESS_CONNECTED)) 
		return 0;

	return -1;
}


/*
 * Function: nbss_receive
 *	Receives data from input stream buffer and copies it into user 
 *	buffer. 
 *
 * Parameters:
 *	session	: pointer to session_t to receive data from
 *	buf	: pointer to user space buffer to put data into
 *	size	: maximum size of data to put in buf
 *	nonblock: flag that indicates wether to block user process if
 *	          data was not available
 *
 * Returns: 
 *	>= 0		: count of data bytes fetched from input stream queue
 *	-ECONNABORTED	: if session aborted.
 *	-ETIME		: if receive timedout. this happens if interface layer
 *			  calls nbss_receive_abort when its timers expire.
 *	-ERESTART	: if process caught a signal
 *	-EAGAIN		: if zero data bytes received.
 */
int 
nbss_receive(session_t *session, unsigned char *buf, unsigned short size,
         unsigned char nonblock)
{
	unsigned short i_size= size;
	struct sk_buff *skb;

	
	if ((session->zapped) &&
	    (skb_queue_len(&session->i_skbq) == 0))
		return -ECONNABORTED;

	session->i_aborted= 0;

try_read:
	nbss_lock_session(session);

	while ((i_size > 0) && ((skb=skb_peek(&session->i_skbq)) != NULL))
		{
		nbss_release_session(session);
		if (i_size >= skb->len)
			{
			memcpy_tofs(buf, skb->data, skb->len);
			i_size -= skb->len;
			buf += skb->len;
			nbss_lock_session(session);
			__skb_unlink(skb, &session->i_skbq);
			session->i_size -= skb->len;
			kfree_skb(skb, 0);
			} 
		else
			{
			memcpy_tofs(buf, skb->data, i_size);
			skb_pull(skb, i_size);
			buf += i_size;
			nbss_lock_session(session);
			session->i_size -= i_size;
			i_size= 0;
			skb->used= 1;
			}
		}

	while ((i_size == size) && (nonblock == 0))
		{
		if ((session->i_state == NB_RECV_NO_RECEIVE)  &&
		    (nbss_isend_receive_outstanding(session) != 0))
			return 0;
    
		nbss_sleep_on(session, TASK_INTERRUPTIBLE);

		if (current->signal & ~current->blocked)
			return -ERESTART;

		if (session->i_size)
			goto try_read;

		if (session->zapped)
			return -ECONNABORTED;

		if (session->i_aborted)
			return -ETIME;
		} 

	nbss_release_session(session);

	if (i_size == size)
		return -EAGAIN;

	return (size - i_size);
}


/*
 * Function: nbss_abort_receive
 *	The interface which aborts a receive operation, usually called from
 *	upper interface when receive timers expire.
 *
 * Parameters:
 *	session	: pointer to session_t whose send operation is to be aborted.
 *
 * Returns: none
 */
void
nbss_abort_receive(session_t *session)
{
	session->i_aborted= 1;

	session->status= -ETIME;

	wake_up_interruptible(&session->waitq);
}


/*
 * Function: nbss_receive_ready
 *	Determines if session has data in input stream queue or not
 *
 * Parameters:
 *	session	: pointer to session_t.
 *
 * Returns: 
 *	0	: if session is ready to send data
 *	other	: if session is not ready to send data
 */
int
nbss_receive_ready(session_t *session)
{
	if (session->i_size)
		return 0;
	
	return -1;
}


/*
 * Function: nbss_trim_data
 *	Removes some bytes from a DATA ONLY LAST frame which is fetched 
 *	before.
 *
 * Parameters:
 *	session	: pointer to session_t.
 *
 * Returns:
 *	0	: always returns zero
 *
 * Notes:
 *	- This feature is activated via ioctl interface and has special 
 *	  meaning to SAMBA that operates over NetBEUI 
 *	- skb->acked flags the frame as a  DATA ONLY LAST frame
 *	- skb->used  indicates that some of frame data is fetched
 *
 * Important Note:
 *	- Windows API implements NetBEUI socket interface of type SOCK_SEQPACKEt
 *	  and sends garbage at end of SMB messages. We have implemented SOCK_STREAM
 *	  which accepts those garbages. The users process activates an IOCTL
 *	  to remove unused data bytes from the stream of bytes.
 *
 *	- Another solution is modifying socket read mechanism so that a porcess
 *	  can read the whole message. This may significantly improve SAMBA reads
 *	  but reduces NetBEUI extensibility. 
 */
int
nbss_trim_data(session_t *session)
{
	struct sk_buff *skb= skb_peek(&session->i_skbq);

	if (skb && (skb->acked) && (skb->used))
		{
		nbss_lock_session(session);
		__skb_unlink(skb, &session->i_skbq);
		session->i_size -= skb->len;
		nbss_release_session(session);
		kfree_skb(skb, 0);
		}

	return 0;
}


/*
 * Function: nbss_hangup
 *	The upper layer interface which terminates a session by generating a 
 *	proper event.
 *
 * Parameters:
 *	session	: pointer to session_t which is to be terminated
 *
 * Returns: none
 *
 * Notes:
 *	- During session termination there may be some unacknowledged data
 *	  in input stream queue whose acknowledgement is not a an action
 *	  of session transition handlers, since session data input stream
 *	  is not part of Session State Machine.
 */
void
nbss_hangup( session_t *session)
{
	del_timer(&session->timer);
	if (session->o_ack_correlator)
		nbss_isend_data_ack(session, (unsigned short)session->o_ack_correlator);

	nbss_handle_event(NB_SESS_HANGUP, session);
	
	nbss_free_session(session);
}


/*
 * Function: nbss_abort_session
 *	This upper layer interface abnormally terminates a session.
 *
 * Parameters:
 *	session	: pointer to session_t which is to be aborted
 *
 * Returns: none
 */
void
nbss_abort_session( session_t *session)
{
	session->zapped= 1;
	del_timer(&session->timer);
	nbss_abort_receive(session);
	nbss_handle_event(NB_SESS_ABORT_SEND, session);
	nbss_handle_event(NB_SESS_ABORT, session);
}


/*
 * Function: nbss_get_name_query
 *	Accepts a NAME QUERY frame and depending on FIND/QUERY characteristic 
 *	of it either responds NAME FIND or generates an event to start
 *	session establishment.
 *
 * Parameters:
 *	skb	  : pointer to sk_buff that holds the frame
 *	remote_mac: pointer to MAC address of remote node who sent the frame
 *
 * Returns: none
 *
 * Notes:
 *	- NAME FIND and NAME QUERY use the same frame format with different 
 *	  flags and field values.
 *	- NAME FIND processing is simply responding with a NAME RECOGNIZED
 *	- NAME QUERY processing is doing the first step of session establishment
 *	- Since the skb is reused for generating response frame it is passed
 *	  to element in session->skb
 */

void 
nbss_get_name_query(struct sk_buff *skb, unsigned char *remote_mac)
{
	dgram_t *hdr= (dgram_t *)skb->data;
	name_t *nb_name= nbns_find_name(hdr->dest_name);
	session_t *session= nbss_find_listen(hdr->dest_name);
	session_t tmp_session;

#ifdef _NB_TR_DBG_
printk("nbss_get_name_query 1>>> STARTED!\n");
#endif
	if (!nb_name)
		{
		kfree_skb(skb, 0);
		return;
		}
	
	if (!session)
		{
		session= &tmp_session;
		session->local_name= nb_name;
		session->rsn= 0;
		}
	else
		session->rsn= CALL_SS(hdr->data2);

	session->skb= skb;
	session->remote_dev= skb->dev;
	memcpy(session->remote_mac, remote_mac, skb->dev->addr_len);
	memcpy(session->remote_name, hdr->source_name, NB_NAME_LEN);
	session->lsn= 0;

	if ((session->rsn == 0) ||
	    (session->rsn == 0xFF))
		nbss_unicast_name_recognized(session);
	else {
		/* Token Ring support */
		session->llcmac_ihl = LLCMAC_I_HEADLEN(session->remote_dev);
#ifdef _NB_TR_DBG_
printk("nbss_get_name_query 2>>> session->llcmac_ihl=%d\n", session->llcmac_ihl);
#endif

		nbss_handle_event(NB_SESS_CONFIRM, session);
	}

	return;
}


/*
 * Function: nbss_get_session_initialize
 *	Accepts a SESSION INITIALIZE frame and generates proper event.
 *
 * Parameters:
 *	skb	: pointer to sk_buff that holds the frame
 *	session : pointer to session_t structre the frame is received from
 *
 * Returns: none
 *
 * Notes:
 *	- Due to implementation of events in NetBEUI whose data is put
 *	  element temorary variable and just event identifier is passed
 *	  to state machine, the routine first checks if the session is 
 *	  in a proper state to accept event or not.
 *	- Since the skb is reused for generating response frame it is passed
 *	  to element in session->skb
 *	- At this point session_t element timer callback function changes to
 *	  the callback that processes delayed acknowledgements of input data
 */

static inline void 
nbss_get_session_initialize(struct sk_buff *skb, session_t *session)
{
	packet_t *hdr= (packet_t *)skb->data;
	unsigned short data2= hdr->data2;

#ifdef _NB_TR_DBG_
printk("nbss_get_session_initialize 1>>> STARTED!\n");
#endif
	if ((session->state != NB_SESS_INITWAIT) ||
	    (session->resp_correlator != hdr->xmit_correlator))
		{
		kfree_skb(skb, 0);
		return;
		}

	if (IS_ABLE_TO_HANDLE_NACK(hdr) == 0)
		session->nack_indicator= NB_NACK_NONE;
	
	if (NETBIOS_VERSION(hdr) == 0)
		session->version= NB_VERSION_1xx;

	session->tr_frame_lf= TR_FRAME_LF(hdr);

//	session->mtu= CALC_MTU(session->remote_dev);
	/* Token Ring support */
	CALC_SESS_MTU(session);
#ifdef _NB_TR_DBG_
printk("nbss_get_session_initialize 2>>> session->mtu=%d\n", session->mtu);
#endif
	if (session->mtu > data2)
		session->mtu = data2;
#ifdef _NB_TR_DBG_
printk("nbss_get_session_initialize 3>>> session->mtu=%d\n", session->mtu);
#endif
	
	session->skb= skb;

	nbss_handle_event(NB_SESS_CONNECT, session);

	/* From now on, the session timer is used for ACK piggy-backing */
	session->timer.function= nbss_ack_timer_function;

	/* skb is reused, thus we don't need to free it */
	return;
}


/*
 * Function: nbss_get_session_confirm
 *	Accepts a SESSION confirm frame and generates proper event.
 *
 * Parameters:
 *	hdr	: pointer to packet_t which is frame header in sk_buff 
 *	session : pointer to session_t structre the frame is received from
 *
 * Returns: none
 *
 * Notes:
 *	- Due to implementation of events in NetBEUI whose data is put
 *	  element temorary variable and just event identifier is passed
 *	  to state machine, the routine first checks if the session is 
 *	  in a proper state to accept event or not.
 *	- At this point session_t element timer callback function changes to
 *	  the callback that processes delayed acknowledgements of input data
 */

static inline void 
nbss_get_session_confirm(packet_t *hdr, session_t *session)
{    
	unsigned short data2= hdr->data2;


#ifdef _NB_TR_DBG_
printk("nbss_get_session_confirm 1>>> STARTED!\n");
#endif
	if ((session->state != NB_SESS_CONFWAIT) ||
	    (session->resp_correlator != hdr->xmit_correlator))
		return;

	if (IS_ABLE_TO_HANDLE_NACK(hdr) == 0)
		session->nack_indicator= NB_NACK_NONE;

	if (NETBIOS_VERSION(hdr) == 0)
		session->version= NB_VERSION_1xx;
	
//	session->mtu= CALC_MTU(session->remote_dev); 
#ifdef _NB_TR_DBG_
printk("nbss_get_session_confirm 2>>> session->mtu=%d\n", session->mtu);
#endif
	if (session->mtu > data2)
		session->mtu = data2;
#ifdef _NB_TR_DBG_
printk("nbss_get_session_confirm 3>>> session->mtu=%d\n", session->mtu);
#endif

	nbss_handle_event(NB_SESS_CONNECT, session);

	/* From now on, the session timer is used for ACK piggy-backing */
	session->timer.function= nbss_ack_timer_function;

	return;
}


/*
 * Function: nbss_get_data_ack
 *	Accepts a DATA ACK frame and generates proper event.
 *
 * Parameters:
 *	hdr	: pointer to packet_t which is frame header in sk_buff 
 *	session : pointer to session_t structre the frame is received from
 *
 * Returns: none
 */
static inline void
nbss_get_data_ack(packet_t *hdr, session_t *session)
{
	if (session->resp_correlator == hdr->xmit_correlator)
		nbss_handle_event(NB_SESS_DATA_ACKED, session);
}


/*
 * Function: nbss_get_session_data
 *	Accepts DATA FIRST MIDDLE and DATA ONLY LAST frames and tries to
 *	process frame by checking input state, data acknoewledgement, controlling
 *	flow of data and queueing frame in input stream queue. 
 *	(it does more than you may think at first glance)
 *
 * Parameters:
 *	skb	: pointer to sk_buff which holds the frame
 *	session : pointer to session_t structre the frame is received from
 *
 * Returns: none
 *
 * Notes:
 *	- The frame pointers are adjusted to user data when frame is queued.
 *	- skb->acked is set for DATA ONLY LAST frames and is used in trimming
 *	  user data in nbss_trim_data(). It is since we adjust pointers to 
 *	  user data not NetBIOS header of frame. 
 */
static inline void
nbss_get_session_data(struct sk_buff *skb, session_t *session)
{
	packet_t *hdr= (packet_t *)skb->data;

	if (ACK_WITH_DATA_INCLUDED(hdr))
		nbss_get_data_ack(hdr, session);

	if ((session->i_state == NB_RECV_NO_RECEIVE) ||
	    ((session->i_state == NB_RECV_RECEIVE_OUTSTANDING) && (RESYNCH_INDICATOR(hdr) != 0x0001)) ||
	    ((session->i_state == NB_RECV_NORMAL) && (RESYNCH_INDICATOR(hdr) != 0x0000)))
		{
		kfree_skb(skb, 0);
		return;
		}
   
	session->i_state= NB_RECV_NORMAL; 


	if ((session->i_size > session->i_rcvbuf) 
	    ||
	    ((hdr->command == DATA_FIRST_MIDDLE) && 
	     (RECEIVE_CONTINUE_REQUESTED(hdr)) && 
	     (nbss_isend_receive_continue(hdr, session) != 0)) 
	    ||
	    ((hdr->command == DATA_ONLY_LAST) && 
	     (NACK_INDICATOR(hdr) == 0) && 
	     (nbss_ack_session_data(hdr, session)!=0)))
		{
		nbss_isend_no_receive(skb, session);
		return;
		}

	skb_pull(skb, hdr->length);
	__skb_queue_tail(&session->i_skbq, skb);

	session->i_size += skb->len;

	skb->used= 0;
	if (hdr->command == DATA_FIRST_MIDDLE) 
		{
		if (RECEIVE_CONTINUE_REQUESTED(hdr) == 0) 
			session->i_notacked += skb->len;
		else
			session->i_notacked= 0;
	 	skb->acked= 0;
		}
	else  /* DATA ONLY LAST */
		{
		session->i_notacked= 0;
	 	skb->acked= 1;
		}

	wake_up_interruptible(&session->waitq);

	session->i_total += skb->len;
	return;
}


/*
 * Function: nbss_get_receive_continue
 *	Accepts a RECEIVE CONTINUE frame and generates a proper event.
 *
 * Parameters:
 *	hdr	: pointer to packet_t which is frame header in sk_buff 
 *	session : pointer to session_t structre the frame is received from
 *
 * Returns: none
 */
static inline void
nbss_get_receive_continue(packet_t *hdr, session_t *session)
{
	if (session->resp_correlator == hdr->xmit_correlator)
		nbss_handle_event(NB_SESS_CONTINUE, session);
}


/*
 * Function: nbss_get_no_receive
 *	Accepts a NO RECEIVE frame and generates proper events. The session
 *	state digram purpose different events for blocking and non-blocking 
 *	sends
 *
 * Parameters:
 *	hdr	: pointer to packet_t which is frame header in sk_buff 
 *	session : pointer to session_t structre the frame is received from
 *
 * Returns: none
 */
static inline void
nbss_get_no_receive(packet_t *hdr, session_t *session)
{
	session->o_receive_continue= 1;
	if (session->o_nonblock == 0)
		nbss_handle_event(NB_SESS_PAUSE, session);
	else
		nbss_handle_event(NB_SESS_NONBLOCK, session);
}


/*
 * Function: nbss_get_receive_outstanding
 *	Accepts a RECEIVE OUTSTANDING frame and generates a proper event. 
 *
 * Parameters:
 *	hdr	: pointer to packet_t which is frame header in sk_buff 
 *	session : pointer to session_t structre the frame is received from
 *
 * Returns: none
 */
static inline void
nbss_get_receive_outstanding(packet_t *hdr, session_t *session)
{
	if (session->version == NB_VERSION_1xx)
		hdr->data2= 0;

	session->o_receive_outstanding= 1;
	session->r_acked= hdr->data2;

	nbss_handle_event(NB_SESS_RESTART, session);
}


/*
 * Function: nbss_get_session_end
 *	Accepts a SESSION END frame and generates a proper event. 
 *
 * Parameters:
 *	session : pointer to session_t structre the frame is received from
 *
 * Returns: none
 */
static inline void
nbss_get_session_end(session_t *session)
{
	session->zapped= 1;
	del_timer(&session->timer);
	nbss_abort_receive(session);
	nbss_handle_event(NB_SESS_ABORT_SEND, session);
	nbss_handle_event(NB_SESS_END, session);
}



/*
 * Function: nbss_switch_frame
 *	This is the I-frame dispatcher. 
 *
 * Parameters:
 *	skb	: pointer to sk_buff which holds the frame
 *	session : pointer to session_t structre the frame is received from
 *
 * Returns: none
 */
static inline void
nbss_switch_frame(session_t *session, struct sk_buff *skb)
{
	packet_t *hdr= (packet_t *)skb->data;

	switch(hdr->command)
		{
		case DATA_FIRST_MIDDLE:
		case DATA_ONLY_LAST:
			nbss_get_session_data(skb, session);
			return;
	
		case DATA_ACK:
			nbss_get_data_ack(hdr, session);
			break;

		case RECEIVE_CONTINUE:
			nbss_get_receive_continue(hdr, session);
			break;
        
		case NO_RECEIVE:
			nbss_get_no_receive(hdr, session);
			break;

		case RECEIVE_OUTSTANDING:
			nbss_get_receive_outstanding(hdr, session);
			break;
        
		case SESSION_INITIALIZE:
			nbss_get_session_initialize(skb, session);
			return;  /* SKB is resued */

		case SESSION_CONFIRM:
			nbss_get_session_confirm(hdr, session);
			break;

		case SESSION_END:
			nbss_get_session_end(session);
			break;
		}

	kfree_skb(skb, 0);
}


/*
 * Function: nbss_deliver_frame
 *	Accepts an I-Frame frame and depending on session locking state
 *	either queues the frame for later processing (when session unlocked)
 *	or calls frame dispatcher for direct processing.
 *
 * Parameters:
 *	session : pointer to session_t structre the frame is received from
 *	skb	: pointer to sk_buff which holds the frame
 *
 * Returns: none
 */
void
nbss_deliver_frame(session_t *session, struct sk_buff *skb)
{
	if (session->users)
		{
		__skb_queue_tail(&session->back_log, skb);
		return;
		}

	nbss_switch_frame(session, skb);
}


/*
 * Function: nbss_drop_session
 *	Drops a specified session on a specified link. Before calling this
 *	function must call 'start_bh_atomic()' and after it must call
 *	'end_bh_atomic()' to make assurance of system's safety.
 *
 * Parameters:
 *	link_no    : an integer that indicates the link number.
 *	session_no : an integer that indicates the session number.
 *
 * Returns: int
 *	zero     : if the session dropped successfully.
 *	negative : if operation fails.
 *	           (-EINVAL) : at least one of the arguments is invalid.
 */

int
nbss_drop_session (int link_no, int session_no)
{
	session_t   *sn;
	dextab_t   *sn_tbl;

	sn_tbl = nbll_get_session_table(link_no);
	if (!sn_tbl) /* Invalid link number */
		return (-EINVAL);

	if (session_no > sn_tbl->size) /* Invalid session number */
		return (-EINVAL);

	sn = sn_tbl->addr[session_no];
	if (!sn) /* no such session */
		return (-EINVAL);

	/* Announce the link manager */
	nbll_detach_session(link_no, session_no);

	/* Announce the session state machine */
	nbss_abort_session(sn);

	return 0;
}



/*
 * Session service state machine functions
 * Implementing locking mechanism
 */

/*
 * Function: nbss_lock_session
 *	Locks a session while processing a critical region.
 *
 * Parameters:
 *	session : pointer to session_t structre which is to be locked 
 *
 * Returns: none
 *
 * Notes:
 *	- Locking is a low cost mechanism for avoiding races when a 
 *	  ciritical region apears in code.
 */
static inline void
nbss_lock_session(session_t *session)
{
	session->users++;
	barrier();
}


/*
 * Function: nbss_process_backlog
 *	Processes frames entered on a session while the session was locked
 *	it simply calls dispatcher for every queued frame.
 *
 * Parameters:
 *	session : pointer to session_t structre who may have some frames in its
 *		  backlog.
 *
 * Returns: none
 */
static inline void 
nbss_process_backlog(session_t *session)
{
	struct sk_buff *skb;
	
	start_bh_atomic();

	while ((skb= __skb_dequeue(&session->back_log)) != NULL)
		nbss_switch_frame(session, skb);

	mark_bh(NET_BH);
	end_bh_atomic();
}


/*
 * Function: nbss_release_session
 *	Unlocks a previously locked session.
 *
 * Parameters:
 *	session : pointer to session_t structre which is to be unlocked 
 *
 * Returns:
 *	0	     : if session is successfully unlocked.
 *	-ECONNABORTED: if session is aborted
 *
 * Notes:
 *	- The return value is important to nbss_sleep_on() who decides whether
 *	  to re-schedule system or contine execution of current process.
 */
static inline int
nbss_release_session(session_t *session)
{
	barrier();

	if (session->users == 0)
		return 0;

	if ((session->users=session->users-1) == 0)
		nbss_process_backlog(session);

	if (session->zapped)
		return session->status= -ECONNABORTED;

	return 0;
}
