/*********************************************************************
 *                
 * Filename:      irlmp_frame.c
 * Version:       0.1
 * Description:   IrLMP frame implementation
 * Status:        Experimental.
 * Author:        Dag Brattli <dagb@cs.uit.no>
 * Created at:    Tue Aug 19 02:09:59 1997
 * Modified at:   Wed Dec  9 01:25:47 1998
 * Modified by:   Dag Brattli <dagb@cs.uit.no>
 * 
 *     Copyright (c) 1997 Dag Brattli <dagb@cs.uit.no>, All Rights Reserved.
 *     
 *     This program is free software; you can redistribute it and/or 
 *     modify it under the terms of the GNU General Public License as 
 *     published by the Free Software Foundation; either version 2 of 
 *     the License, or (at your option) any later version.
 *
 *     Neither Dag Brattli nor University of Tromsų admit liability nor
 *     provide warranty for any of this software. This material is 
 *     provided "AS-IS" and at no charge.
 *
 ********************************************************************/

#include <linux/config.h>
#include <linux/skbuff.h>
#include <linux/kernel.h>

#include <net/irda/irda.h>
#include <net/irda/irlap.h>
#include <net/irda/irlmp.h>
#include <net/irda/irlmp_frame.h>

static struct lsap_cb *irlmp_find_lsap( struct lap_cb *self, __u8 dlsap, 
					__u8 slsap, int status);

inline void irlmp_send_data_pdu( struct lap_cb *self, __u8 dlsap, __u8 slsap,
				 int expedited, struct sk_buff *skb)
{
	__u8 *frame;

	DEBUG( 4, __FUNCTION__ "()\n");

	ASSERT( self != NULL, return;);
	ASSERT( self->magic == LMP_LAP_MAGIC, return;);
	ASSERT( skb != NULL, return;);

	frame = skb->data;

	frame[0] = dlsap;
	frame[1] = slsap;

	if ( expedited) {
		DEBUG( 4, __FUNCTION__ "(), sending expedited data\n");
		irlap_data_request( self->irlap, skb, FALSE);
	} else {
		DEBUG( 4, __FUNCTION__ "(), sending reliable data\n");
		irlap_data_request( self->irlap, skb, TRUE);	
	}
}

/*
 * Function irlmp_send_lcf_pdu (dlsap, slsap, opcode,skb)
 *
 *    Send Link Control Frame to IrLAP
 */
void irlmp_send_lcf_pdu( struct lap_cb *self, __u8 dlsap, __u8 slsap,
			 __u8 opcode, struct sk_buff *skb) 
{
	__u8 *frame;
	
	DEBUG( 4, __FUNCTION__ "()\n");
	
	ASSERT( self != NULL, return;);
	ASSERT( self->magic == LMP_LAP_MAGIC, return;);
	ASSERT( skb != NULL, return;);
	
	frame = skb->data;
	
	frame[0] = dlsap | CONTROL_BIT;
	frame[1] = slsap;

	frame[2] = opcode;

	if (opcode == DISCONNECT)
		frame[3] = 0x01; /* Service user request */
	else
		frame[3] = 0x00; /* rsvd */

	ASSERT( self->irlap != NULL, return;);
	irlap_data_request( self->irlap, skb, TRUE);
}

/*
 * Function irlmp_input (skb)
 *
 *    Used by IrLAP to pass received data frames to IrLMP layer
 *
 */
void irlmp_link_data_indication( struct lap_cb *self, int reliable, 
				 struct sk_buff *skb)
{
	__u8 *fp;
	__u8  slsap_sel;   /* Source (this) LSAP address */
	__u8  dlsap_sel;   /* Destination LSAP address */
	struct lsap_cb *lsap;

	
	DEBUG( 4, "irlmp_link_data_indication()\n");
	
	ASSERT( self != NULL, return;);
	ASSERT( self->magic == LMP_LAP_MAGIC, return;);
	ASSERT( skb != NULL, return;);
	ASSERT( skb->len > 2, return;);

	fp = skb->data;

	/*
	 *  The next statements may be confusing, but we do this so that 
	 *  destination LSAP of received frame is source LSAP in our view
	 */
	slsap_sel = fp[0] & ~CONTROL_BIT; 
	dlsap_sel = fp[1];
	
	DEBUG( 4, "slsap_sel = %02x, dlsap_sel = %02x\n", slsap_sel, 
	       dlsap_sel);
	
	/*
	 *  Check if this is an incoming connection, since we must deal with
	 *  it in a different way than other established connections.
	 */
	if (( fp[0] & CONTROL_BIT) && ( fp[2] == CONNECT_CMD)) {
		DEBUG( 4,"Incoming connection, source LSAP=%d, dest LSAP=%d\n",
		       slsap_sel, dlsap_sel);
		lsap = irlmp_find_lsap( self, dlsap_sel, slsap_sel, 
					CONNECT_CMD);
	} else
		lsap = irlmp_find_lsap( self, dlsap_sel, slsap_sel, 0);

	if ( lsap == NULL) {
		DEBUG( 0, "IrLMP, Sorry, no LSAP for received frame!\n");
		DEBUG( 0, __FUNCTION__ 
		       "(), slsap_sel = %02x, dlsap_sel = %02x\n", slsap_sel, 
		       dlsap_sel);
		if ( fp[0] & CONTROL_BIT) {
			DEBUG( 0, __FUNCTION__ 
			       "(), received control frame %02x\n", fp[2]);
		} else {
			DEBUG( 0, __FUNCTION__ 
			       "(), received data frame\n");
		}
		dev_kfree_skb( skb);
		return;
	}

	/* 
	 *  Check if we received a control frame? 
	 */
	if ( fp[0] & CONTROL_BIT) {
		/* DEBUG( 0, "irlmp_input: Got control frame\n"); */
		switch( fp[2]) {
		case CONNECT_CMD:
			DEBUG( 4, "irlmp_input: CONNECT_CMD\n");
			lsap->lap = self;
			irlmp_do_lsap_event( lsap, LM_CONNECT_INDICATION, skb);
			break;
		case CONNECT_CNF:
			irlmp_do_lsap_event( lsap, LM_CONNECT_CONFIRM, skb);
			break;
		case DISCONNECT:
			DEBUG( 4, "irlmp_input: Disconnect indication!\n");
			irlmp_do_lsap_event( lsap, LM_DISCONNECT_INDICATION, 
					     skb);
			break;
		case ACCESSMODE_CMD:
			DEBUG( 0, "Access mode cmd not implemented!\n");
			break;
		case ACCESSMODE_CNF:
			DEBUG( 0, "Access mode cnf not implemented!\n");
			break;
		default:
			DEBUG( 0, "irlmp_input: Unknown control frame %02x\n",
			       fp[2]);
			break;
		}
	} else if ( reliable == LAP_RELIABLE) {
		/* Must be pure data */
		irlmp_do_lsap_event( lsap, LM_DATA_INDICATION, skb);
	} else if ( reliable == LAP_UNRELIABLE) {
		irlmp_do_lsap_event( lsap, LM_UDATA_INDICATION, skb);
	}
}

/*
 * Function irlmp_link_disconnect_indication (reason, userdata)
 *
 *    IrLAP has disconnected 
 *
 */
void irlmp_link_disconnect_indication( struct lap_cb *lap, 
				       struct irlap_cb *irlap, 
				       LAP_REASON reason, 
				       struct sk_buff *userdata)
{
	DEBUG( 4, "irlmp_link_disconnect_indication()\n");

	ASSERT( lap != NULL, return;);
	ASSERT( lap->magic == LMP_LAP_MAGIC, return;);

	lap->reason = reason;

        /* FIXME: must do something with the userdata if any */

	/*
	 *  Inform station state machine
	 */
	irlmp_do_lap_event( lap, LM_LAP_DISCONNECT_INDICATION, NULL);
}

/*
 * Function irlmp_link_connect_indication (qos)
 *
 *    Incoming LAP connection!
 *
 */
void irlmp_link_connect_indication( struct lap_cb *self, struct qos_info *qos,
				    struct sk_buff *skb) 
{
	DEBUG( 4, "irlmp_link_connect_indication()\n");

	/* Copy QoS settings for this session */
	self->qos = qos;

	irlmp_do_lap_event( self, LM_LAP_CONNECT_INDICATION, skb);
}

/*
 * Function irlmp_link_connect_confirm (qos)
 *
 *    LAP connection confirmed!
 *
 */
void irlmp_link_connect_confirm( struct lap_cb *self, struct qos_info *qos, 
				 struct sk_buff *userdata)
{
	DEBUG( 4, "irlmp_link_connect_confirm()\n");

	ASSERT( self != NULL, return;);
	ASSERT( self->magic == LMP_LAP_MAGIC, return;);
	ASSERT( qos != NULL, return;);

	/* Copy QoS settings for this session */
	self->qos = qos;

	irlmp_do_lap_event( self, LM_LAP_CONNECT_CONFIRM, NULL);
}

/*
 * Function irlmp_link_discovery_confirm (self, log)
 *
 *    Called by IrLAP with a list of discoveries after the discovery
 *    request has been carried out. A NULL log is received if IrLAP
 *    was unable to carry out the discovery request
 *
 */
void irlmp_link_discovery_confirm( struct lap_cb *self, hashbin_t *log)
{
	DEBUG( 4, "irlmp_link_connect_confirm()\n");

	ASSERT( self != NULL, return;);
	ASSERT( self->magic == LMP_LAP_MAGIC, return;);

	if ( self->cachelog)
		hashbin_delete( self->cachelog, (FREE_FUNC) kfree);

	self->cachelog = log;
      
	irlmp_do_lap_event( self, LM_LAP_DISCOVERY_CONFIRM, NULL);
}

#ifdef CONFIG_IRDA_CACHE_LAST_LSAP
void irlmp_update_cache( struct lsap_cb *self)
{
	DEBUG( 4, __FUNCTION__ "()\n");

	/* Update cache entry */
	irlmp->cache.dlsap_sel = self->dlsap_sel;
	irlmp->cache.slsap_sel = self->slsap_sel;
	irlmp->cache.lsap = self;
	irlmp->cache.valid = TRUE;
}
#endif

/*
 * Function irlmp_find_handle (dlsap, slsap)
 *
 *    Find handle assosiated with destination and source LSAP
 *
 */
static struct lsap_cb *irlmp_find_lsap( struct lap_cb *self, __u8 dlsap_sel,
					__u8 slsap_sel, int status) 
{
	struct lsap_cb *lsap;
	hashbin_t *queue;
	
	DEBUG( 4, "irlmp_find_lsap: dlsap_sel=0x%02x, slsap_sel=0x%02x\n", 
	       dlsap_sel, slsap_sel);
	
	ASSERT( self != NULL, return NULL;);
	ASSERT( self->magic == LMP_LAP_MAGIC, return NULL;);

	if ( status)
		queue = irlmp->unconnected_lsaps;
	else
		queue = self->lsaps;

	/* 
	 *  Optimize for the common case. We assume that the last frame
	 *  received is in the same connection as the last one, so check in
	 *  cache first to avoid the linear search
	 */
#ifdef CONFIG_IRDA_CACHE_LAST_LSAP
	ASSERT( irlmp != NULL, return NULL;);

	if (( irlmp->cache.valid) && 
	    ( irlmp->cache.slsap_sel == slsap_sel) && 
	    ( irlmp->cache.dlsap_sel == dlsap_sel)) 
	{
		DEBUG( 4, __FUNCTION__ "(), Using cached LSAP\n");
		return ( irlmp->cache.lsap);
	}	
#endif
	lsap = ( struct lsap_cb *) hashbin_get_first( queue);
	while ( lsap != NULL) {
		/* 
		 *  Check if source LSAP (in our view!) match, and if
		 *  dest LSAP is equal to LM_ANY, which is the case
		 *  for incomming connections 
		 */
		DEBUG( 4, "irlmp_find_lsap: "
		       "LSAP: lsap->dlsap=%d, lsap->slsap=%d\n", 
		       lsap->dlsap_sel, lsap->slsap_sel);
		
		if (( status == CONNECT_CMD) && 
		    ( lsap->slsap_sel == slsap_sel) &&      
		    ( lsap->dlsap_sel == LSAP_ANY)) 
		{
			DEBUG( 4,"Incoming connection: Setting dlsap_sel=%d\n",
			       dlsap_sel);
			lsap->dlsap_sel = dlsap_sel;
			
#ifdef CONFIG_IRDA_CACHE_LAST_LSAP
			irlmp_update_cache( lsap);
#endif
			return lsap;
		}
		/*
		 *  Check if source LSAP and dest LSAP selectors match.
		 */
		if (( lsap->slsap_sel == slsap_sel) && 
		    ( lsap->dlsap_sel == dlsap_sel)) 
		{
#ifdef CONFIG_IRDA_CACHE_LAST_LSAP
			irlmp_update_cache( lsap);
#endif
			return lsap;
		}
		lsap = ( struct lsap_cb *) hashbin_get_next( queue);
	}

	/* Sorry not found! */
	return NULL;
}
