/*
 * dgram_serve.c - Contains functions that supply DATAGRAM service for NetBEUI
 *                 protocol stack, and also some utility functions.
 *
 * Notes:
 *	- VRP in comments is the acronym of "Value Result Parameter"
 *
 * 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/string.h>
#include <asm/system.h>
#include <linux/malloc.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/netbeui.h>


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

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

static struct {
	volatile unsigned char	lock;	/* 0:UnLock , 1:Lock */
	name_dgrms_t		*obc;	/* One Behind Cache */
	name_dgrms_t		*normal_list;
	name_dgrms_t		*star_list;
} nbdg_names = {0,NULL,NULL,NULL};



/*
 * Internal NBDG functions 
 */

/* 
 * Function: nbdg_find_name
 *	Finds sightly name_dgrms structure address depend on its name in the
 *	normal_list.
 *
 * Parameters:
 *	name : pointer to name that must find its structure address.
 *
 * Returns: name_dgrms_t *
 *	non NULL : address of related name_dgrms structure.
 *	NULL     : means requested name not exist in normal_list.
 */

static inline name_dgrms_t *
nbdg_find_name (char *name)
{
	name_dgrms_t   *curr;

	for (curr = nbdg_names.normal_list ; curr ; curr = curr->next)
		if (memcmp(curr->name, name, NB_NAME_LEN) == 0)
			return curr;

	return NULL;
}


/* 
 * Function: nbdg_find_such_frame
 *	Finds another frame in 'frameq' that has same "frame type" and
 *	"source name" with 'skb'.
 *
 * Parameters:
 *	skb : pointer to sk_buff that contains sightly frame.
 *
 * Returns: struct sk_buff *
 *	non NULL : address of appropriate sk_buff.
 *	NULL     : if no such frame exist in frameq.
 */

static struct sk_buff *
nbdg_find_such_frame (struct sk_buff *skb)
{
	dgram_t   *hdrp;
	int   frame_type;
	struct sk_buff_head   *frameq;
	char   source_name[NB_NAME_LEN];
	
	hdrp = (dgram_t *) skb->data;
	frame_type = hdrp->command;
	frameq = skb->list;
	memcpy(source_name, hdrp->source_name, NB_NAME_LEN);
	
	skb = skb->next;
	while (skb != (struct sk_buff *) frameq) {
		hdrp = (dgram_t *) skb->data;

		if (hdrp->command == frame_type)
			if (
			    memcmp(hdrp->source_name,
			           source_name,
			           NB_NAME_LEN) == 0
			   )
				return skb;

		skb = skb->next;
	}

	return NULL;
}


/* 
 * Function: nbdg_enqueue_name
 *	Enqueues a name_dgrms structure at begin of 'list' queue. it guards
 *	code from race conditions by Lock/UnLock 'nbdg_names' structure.
 *
 * Parameters:
 *	namep : pointer to name_dgrms structure that must be inserted into
 *	        the list.
 *	list  : pointer to a pointer that points to first member of list.
 *
 * Returns: none
 */

static void
nbdg_enqueue_name (name_dgrms_t *namep, name_dgrms_t **list)
{
	namep->prev = NULL;
	namep->next = *list;

	nbdg_names.lock = 1;
	barrier();

	*list = namep;
	if (namep->next)
		namep->next->prev = namep;

	barrier();
	nbdg_names.lock = 0;

	namep->list = list;

	return;
}


/* 
 * Function: nbdg_dequeue_name
 *	Dequeues a name_dgrms structure from 'list' queue. it guards code
 *	from race conditions by Lock/UnLock 'nbdg_names' structure.
 *
 * Parameters:
 *	namep : pointer to name_dgrms structure that must be removed from
 *	        its list.
 *
 * Returns: none
 */

static void
nbdg_dequeue_name (name_dgrms_t *namep)
{
	nbdg_names.lock = 1;
	barrier();

	if (namep->next)
		namep->next->prev = namep->prev;

	if (namep->prev)
		namep->prev->next = namep->next;
	else
		*namep->list = namep->next;

	barrier();
	nbdg_names.lock = 0;

	return;
}


/*
 * Exported  NBDG  functions 
 */

/* 
 * Function: nbdg_set_dgbc_mtu
 *	Tunes the 'dgbc_mtu' global variable up to correct value. this function
 *	is called at begin of protocol stack installation in kernel space, and
 *	also must be called each time 'adapters[]' array altered.
 *
 * Parameters: none
 *
 * Returns: none
 */

void
nbdg_set_dgbc_mtu (void)
{
	int   i,
	      tmp = adapters[0]->mtu;

	for (i=1 ; (i < NB_MAX_ADAPTERS) && (adapters[i]) ; i++)
		tmp = MIN(tmp, adapters[i]->mtu);

	dgbc_mtu = tmp - LLCMAC_UIB_HEADLEN() - NETB_UILEN;

	return;
}


/* 
 * Function: nbdg_remove_unwanted_dgf
 *	Removes frames in frame queue that contain up to 'len' bytes of data.
 *	user program can uses this function by call ioctl() with SIOCRUWDGF
 *	command. because we have not any mechanism in NetBEUI to recognize
 *	message boundaries, this is user (program) that recognizes them and
 *	announce us that Maximum 'len' bytes of remainder data is not
 *	beneficial and can be removed. 'len' == 0 means that we must remove all
 *	of such frames from queue.
 *
 * Parameters:
 *	namep : pointer to name_dgrms structure that operation perform on its
 *	        frameq.
 *	len   : maximum length which upper layer knows that is not usable.
 *
 * Returns: int
 *	0 : this function always succeed.
 */

int
nbdg_remove_unwanted_dgf (name_dgrms_t *namep, int len)
{
	struct sk_buff   *skb,
	                 *such_skb;

	skb = namep->curr_skb;
	if (!skb)
		return 0;  /* We are at begin of a message boundary now */

	namep->curr_skb = NULL;

	if (len) {
		len -= skb->len;
		while (len > 0) {
			such_skb = nbdg_find_such_frame(skb);

			skb_unlink(skb);
			kfree_skb(skb, 0);

			if (!such_skb)
				return 0;

			skb = such_skb;
			len -= skb->len;
		}
	}
	else /* Remove all of such frames */
		while ((such_skb = nbdg_find_such_frame(skb))) {
			skb_unlink(skb);
			kfree_skb(skb, 0);
			skb = such_skb;
		}

	skb_unlink(skb);
	kfree_skb(skb, 0);

	return 0;
}


/* 
 * Function: nbdg_register_peername
 *	Registers a remote name for a local name. it causes that received
 *	frames only from specified remote name be acceptable. broadcasted frames
 *	will be accepted only if 'remote_name[0]' is equal to '*'.
 *
 * Parameters:
 *	namep       : pointer to name_dgrms structure that remote name registers
 *	              for it.
 *	remote_name : pointer to remote name that must be registered.
 *
 * Returns: none
 */

void
nbdg_register_peername (name_dgrms_t *namep, char *remote_name)
{
	if (remote_name[0] == '*') {
		if (namep->list != &nbdg_names.star_list) {
			nbdg_dequeue_name(namep);
			nbdg_enqueue_name(namep, &nbdg_names.star_list);
		}
	}
	else {
		if (namep->list != &nbdg_names.normal_list) {
			nbdg_dequeue_name(namep);
			nbdg_enqueue_name(namep, &nbdg_names.normal_list);
		}
	}

	namep->conn_name = remote_name;
	namep->connected = 1;

	return;
}


/* 
 * Function: nbdg_deregister_peername
 *	Deregisters a registered name for a local name. it causes that received
 *	frames which are broadcasted or have our name in dest_name field of
 *	their NetBIOS header, be acceptable.
 *
 * Parameters:
 *	namep : pointer to name_dgrms structure that must deregidter its
 *	        attached name.
 *
 * Returns: none
 */

void
nbdg_deregister_peername (name_dgrms_t *namep)
{
	if (namep->conn_name[0] == '*') {
		nbdg_dequeue_name(namep);
		nbdg_enqueue_name(namep, &nbdg_names.normal_list);
	}

	namep->connected = 0;

	return;
}


/* 
 * Function: nbdg_add_name
 *	Creates a name_dgrms structure and adds it to normal_list.
 *
 * Parameters:
 *	local_name : pointer to name that must add its structure to normal_list.
 *	wq         : pointer to a pointer that points to dependent wait_queue.
 *	namep      : (VRP) pointer to a pointer buffer that in successful return
 *	             contains address of related name_dgrms structure in list.
 *
 * Returns: int
 *	0        : if operation is performed successfully.
 *	negative : if a fault occurs.
 *                  (-ENOMEM) : Out of memory condition.
 */

int
nbdg_add_name (char *local_name, struct wait_queue **wq, name_dgrms_t **namep)
{
	name_dgrms_t   *new;

	new = (name_dgrms_t *) kmalloc(sizeof(name_dgrms_t), GFP_KERNEL);
	if (!new)
		return (-ENOMEM);

	memset(new, 0, sizeof(name_dgrms_t));

	skb_queue_head_init(&new->frameq);
	memcpy(new->name, local_name, NB_NAME_LEN);
	new->waitq = wq;
	*new->waitq = NULL;

	nbdg_enqueue_name(new, &nbdg_names.normal_list);

	*namep = new;

	return 0;
}


/* 
 * Function: nbdg_del_name
 *	Removes a name_dgrms structure from its list.
 *
 * Parameters:
 *	namep : pointer to name_dgrms structure that must deleted.
 *
 * Returns: none
 */

void
nbdg_del_name (name_dgrms_t *namep)
{
	struct sk_buff   *skb;

	nbdg_names.lock = 1;
	barrier();
	if (nbdg_names.obc == namep)
		nbdg_names.obc = NULL;
	barrier();
	nbdg_names.lock = 0;

	nbdg_dequeue_name(namep);

	while ((skb = skb_dequeue(&namep->frameq)))
		kfree_skb(skb, 0);

	kfree(namep);

	return;
}


/*
 * Function: nbdg_receive_ready
 *	Answers to the question "Do in transport layer exist any queued
 *	received data for this name ?"
 *
 * Parameters:
 *	namep : pointer to name_dgrms structure that question is about it.
 *
 * Returns: int
 *	0  : means "OK, exist some data".
 *	-1 : means "NO, not exist any queued received data as yet".
 */

int
nbdg_receive_ready (name_dgrms_t *namep)
{
	return ((skb_queue_empty(&namep->frameq) == 1) ? -1 : 0 /* OK! */);
}


/*
 * Function: nbdg_send
 *	Sends datagrams to the sightly destination(s).
 *
 * Parameters:
 *	local_name : pointer to name that must copied to source_name field of
 *	             NetBIOS header of all sent frames.
 *	dest_name  : pointer to name that must copied to dest_name field of
 *	             NetBIOS header of all sent frames (target name).
 *	dest_type  : type of destination of frames.
 *	buff       : pointer to buffer that contains data that must be sent.
 *	bufflen    : length of data buffer.
 *
 * Returns: int
 *	positive : number of bytes that was sent.
 *	negative : if a fault occurs.
 *	           (-ENOMEM)       : Out of memory condition.
 */

int
nbdg_send (char *local_name, char *dest_name, name_type_t dest_type, char *buff,
								int bufflen)
{
	int   rc,
	      fskbl,
	      bytes_put = 0;
	dgram_t   hdr;
	struct sk_buff   *skb;
	unsigned char   *datap;

	if (dest_name[0] == '*')
		hdr.command = DATAGRAM_BROADCAST;
	else {
		hdr.command = DATAGRAM;
		memcpy(hdr.dest_name, dest_name, NB_NAME_LEN);
	}
		
	hdr.length = NETB_UILEN;
	hdr.delimiter = NB_DELIMITER;
	memcpy(hdr.source_name, local_name, NB_NAME_LEN);

	fskbl = CALC_DG_SKBLEN(MAC_B_HEADLEN, dgbc_mtu + NETB_UILEN);
	skb = alloc_skb(fskbl, GFP_KERNEL);
	if (!skb)
		return (-ENOMEM);
	
	skb->free = 1;
	skb_reserve(skb, LLCMAC_UIB_HEADLEN());

#ifdef _NB_TR_DBG_
printk("nbdg_send 1>>> sizeof(hdr)=%d\n", sizeof(hdr));
#endif
	memcpy(skb_put(skb, sizeof(hdr)), &hdr, sizeof(hdr));
#ifdef _NB_TR_DBG_
printk("nbdg_send 2>>> OK!\n");
#endif

	if (bufflen > dgbc_mtu) {
		datap = skb_put(skb, dgbc_mtu);
		do {
			memcpy_fromfs(datap, buff, dgbc_mtu);

			buff += dgbc_mtu;
			bytes_put += dgbc_mtu;
			bufflen -= dgbc_mtu;

			rc = nbll_uisend(NULL, skb);
			if (rc) {
				kfree_skb(skb, 0);
				return rc;
			}
		} while (bufflen > dgbc_mtu);
	}

	if (bufflen) {
		skb_trim(skb, sizeof(hdr));
#ifdef _NB_TR_DBG_
printk("nbdg_send 3>>> bufflen=%d\n", bufflen);
#endif
		memcpy_fromfs(skb_put(skb, bufflen), buff, bufflen);
#ifdef _NB_TR_DBG_
printk("nbdg_send 4>>> OK!\n");
#endif

		bytes_put += bufflen;

		rc = nbll_uisend(NULL, skb);
		if (rc) {
			kfree_skb(skb, 0);
			return rc;
		}
	}

	kfree_skb(skb, 0);
	return bytes_put;
}


/*
 * Function: nbdg_receive
 *	Copies data that received and queued into user data buffer.
 *
 * Parameters:
 *	namep       : pointer to name_dgrms that we want receive data that
 *	              queued for it.
 *	source_name : (VRP) pointer to a name buffer. if this parameter is not
 *	              NULL at return the name buffer will be filled with message
 *	              sender name.
 *	dest_name   : (VRP) pointer to a name buffer. if this parameter is not
 *	              NULL at return the name buffer will be filled with message
 *	              destination name.
 *	buff        : (VRP) pointer to data buffer. at return this buffer
 *	              contains data that received.
 *	bufflen     : indicates Maximum no of bytes that user wants to receive.
 *	nonblock    : an integer that if be set to non-zero value means that
 *	              no waiting (sleeping, blocking & ...) acceptable during
 *	              operation.
 *
 * Returns: int
 *	positive : no of bytes that was received.
 *	negative : if a fault occurs.
 *	           (-EWOULDBLOCK) : user requests non-blocking operation, but
 *	                            operation would block.
 *	           (-ERESTARTSYS) : interrupted system call.
 */

int
nbdg_receive (name_dgrms_t *namep, char *source_name, char *dest_name,
					char *buff, int bufflen, int nonblock)
{
	int   bytes_gotten = 0;
	char   *datap;
	dgram_t   *hdrp;
	struct sk_buff   *skb;

	while (skb_queue_empty(&namep->frameq) == 1)
		if (nonblock)
			return (-EWOULDBLOCK);
		else {
			interruptible_sleep_on(namep->waitq);
			if (current->signal & ~current->blocked)
				return (-ERESTARTSYS);
		}

	skb = namep->curr_skb;
	if (!skb) {
		skb = namep->curr_skb = skb_peek(&namep->frameq);
		skb->len -= sizeof(dgram_t);
	}

	hdrp = (dgram_t *) skb->data;
	datap = (char *) (skb->tail - skb->len);

	if (source_name)
		memcpy(source_name, hdrp->source_name, NB_NAME_LEN);
	if (dest_name)
		if (hdrp->command == DATAGRAM)
			memcpy_tofs(dest_name, hdrp->dest_name, NB_NAME_LEN);
		else
			put_user('*', dest_name);

	while (bufflen) {
		int   len;

		len = MIN(bufflen, skb->len);
		
		memcpy_tofs(buff, datap, len);
		
		buff += len;
		bufflen -= len;

		datap += len;
		skb->len -= len;

		bytes_gotten += len;

		if (!skb->len) {  /* sk_buff copied completely */
			struct sk_buff   *such_skb;

			such_skb = nbdg_find_such_frame(skb);

			skb_unlink(skb);
			kfree_skb(skb, 0);

			namep->curr_skb = such_skb;

			if (!such_skb)  /* No any such frame */
				return bytes_gotten;

			skb = such_skb;
			hdrp = (dgram_t *) skb->data;
			skb->len -= sizeof(dgram_t);
			datap = (char *) (hdrp + sizeof(dgram_t));
		}
	}

	return bytes_gotten;
}


/*
 * Function: nbdg_get_datagram
 *	Takes a DATAGRAM frame from 'llc supplementary' and place it on
 *	appropriate frame queues.
 *
 * Parameters:
 *	skb        : pointer to sk_buff that contains DATAGRAM frame.
 *
 * Returns: none
 */

void
nbdg_get_datagram (struct sk_buff *skb)
{
	dgram_t   *hdrp;
	name_dgrms_t   *namep;
	struct sk_buff   *new_skb;

	barrier();
	if (nbdg_names.lock) {  /* Is the structure lock ?! */
		kfree_skb(skb, 0);
		return;
	}

	/* Place a copy of the skb in frame queue of all star_list's members */
	for (namep = nbdg_names.star_list ; namep ; namep = namep->next) {
		new_skb = skb_clone(skb, GFP_ATOMIC);
		if (!new_skb)
			continue;

		skb_queue_tail(&namep->frameq, new_skb);
		wake_up_interruptible(namep->waitq);
	}

	hdrp = (dgram_t *) skb->data;

	if ((nbdg_names.obc) /* If One Behind Cache is not empty and ... */ &&
	    (memcmp(nbdg_names.obc->name, hdrp->dest_name, NB_NAME_LEN) == 0))
		namep = nbdg_names.obc;
	else {
		namep = nbdg_find_name(hdrp->dest_name);
		if (!namep) {  /* No registered name for this sk_buff */
			kfree_skb(skb, 0);
			return;
		}
		nbdg_names.obc = namep;
	}

	if (namep->connected)
		/* To force short circuiting by compiler */
		if (memcmp(namep->conn_name, hdrp->source_name, NB_NAME_LEN) !=
		    0) { /* Not connected to this source_name */
			kfree_skb(skb, 0);
			return;
		}

	/* Now we find the apposite name */
	skb_queue_tail(&namep->frameq, skb);
	wake_up_interruptible(namep->waitq);

	return;
}


/*
 * Function: nbdg_get_datagram_broadcast
 *	Takes a DATAGRAM_BROADCAST frame from 'llc supplementary' and place
 *	it on appropriate frame queues.
 *
 * Parameters:
 *	skb        : pointer to sk_buff that contains DATAGRAM_BROADCAST frame.
 *
 * Returns: none
 */

void
nbdg_get_datagram_broadcast (struct sk_buff *skb)
{
	name_dgrms_t   *namep;
	struct sk_buff   *new_skb;

	barrier();
	if (nbdg_names.lock) {  /* Is the structure lock ?! */
		kfree_skb(skb, 0);
		return;
	}

	/* Place a copy of skb in frame queue of all star_list's members */
	for (namep = nbdg_names.star_list ; namep ; namep = namep->next) {
		new_skb = skb_clone(skb, GFP_ATOMIC);
		if (!new_skb)
			continue;

		skb_queue_tail(&namep->frameq, new_skb);
		wake_up_interruptible(namep->waitq);
	}

	for (namep = nbdg_names.normal_list ; namep ; namep = namep->next) {
		if (namep->connected)
			/* To force short circuiting by compiler */
			if (memcmp(
			           namep->conn_name,
			           ((dgram_t *) skb->data)->source_name,
			           NB_NAME_LEN
			          ) != 0)
				/* Not connected to this source_name */
				continue;

		new_skb = skb_clone(skb, GFP_ATOMIC);
		if (!new_skb)
			continue;

		skb_queue_tail(&namep->frameq, new_skb);
		wake_up_interruptible(namep->waitq);
	}

	kfree_skb(skb, 0);
	return;
}
