/*
 * Copyright (c) 2001 Tommy Bohlin <tommy@gatespace.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
/* lap.c
 *
 * Limitations:
 *  - Exchange primary/secondary roles not supported
 *  - Point to multipoint not supported
 *  - 2400bps connection procedure not supported
 *  - FRMR and RESET not supported
 */

#include <irda.h>
#include <lapmux.h>

#include <string.h>

static void disconnectLAP(LAPPrivate* lapp);

/**********************************************************************
 * Constants
 **********************************************************************/

static const char id_lap[]="lap";

#define BROADCAST_ADDRESS        0xffffffff

/* Address field */
#define COMMAND_MASK             0x01
#define BROADCAST_HANDLE         0xfe

/* Control field */
#define PF_MASK                  0x10
#define U_SNRM                   0x83
#define U_DISC                   0x43
#define U_UI                     0x03
#define U_XID_CMD                0x2f
#define U_TEST                   0xe3
#define U_UA                     0x63
#define U_FRMR                   0x87
#define U_DM                     0x0f
#define U_XID_RSP                0xaf
#define S_MASK                   0x0f
#define S_RR                     0x01
#define S_RNR                    0x05
#define S_REJ                    0x09
#define S_SREJ                   0x0d
#define IS_U(c)                  (((c)&3)==3)
#define IS_S(c)                  (((c)&3)==1)
#define NR_MASK                  0xe0
#define NR_SHIFT                 5
#define NS_MASK                  0x0e
#define NS_SHIFT                 1
#define NR(c)                    ((NR_MASK&(c))>>NR_SHIFT)
#define NS(c)                    ((NS_MASK&(c))>>NS_SHIFT)

/* Discovery */
#define XID_FORMAT               1
#define XID_VERSION              0
#define END_OF_SLOTS             0xff
#define XID_SLOTS_1              0
#define XID_SLOTS_6              1
#define XID_SLOTS_8              2
#define XID_SLOTS_16             3
#define XID_SLOTS_MASK           0x3
#define SLOT_TIME                100
static const int answerSlots[] = { 1, 6, 8, 16 };

/* Negotiation parameters */

#define NEG_BAUD_RATE            0x01
#define ALL_BAUD_RATES           0x3ff
static const int baudRateBits[] = {
  2400, 9600, 19200, 38400, 57600, 115200, 576000, 1152000, 4000000, 16000000
};

#define NEG_MAX_TA_TIME          0x82
#define MAX_TA_500ms             1
#define RECV_MAX_TA              1        /* Try something shorter? */
#define ALL_MAX_TAS              0xf
static const int maxTurnaroundBits[] = { 500, 250, 100, 50 };

#define NEG_DATA_SIZE            0x83
#define DATASIZE_64              1
#define RESET_DATASIZE           384
#define RECV_DATASIZE            512      /* The Palm pilot has a bug that causes it */
#define RECV_DATASIZES           0xf      /* to fail if the dataSize is set larger   */
#define ALL_DATASIZES            0x3f
static const int dataSizeBits[] = { 64, 128, 256, 512, 1024, 2048 };

#define NEG_WINDOW_SIZE          0x84
#define WINDOWSIZE_1             7
#define RECV_WINDOWSIZES         0x7f
#define ALL_WINDOWSIZES          0x7f
static const int windowSizeBits[] = { 1, 2, 3, 4, 5, 6, 7 };

#define NEG_ADD_BOFS             0x85
#define EXTRA_BOFS_0             0x80
#define RECV_EXTRA_BOFS          0xff
#define ALL_EXTRA_BOFS           0xff
static const int extraBOFsBits[] = { 48, 24, 12, 5, 3, 2, 1, 0 };

#define NEG_MIN_TA_TIME          0x86
#define ALL_MIN_TAS              0x7f
#define MIN_TA_10                1
/* Exact values are: 10ms, 5ms, 1ms, 500us, 100us, 50us, 10us, 0 */
static const int minTurnaroundBits[] = { 10, 5, 1, 1, 1, 1, 1, 0 };

#define NEG_DISC_TIME            0x08
#define RECV_DISCONNECTS         0x1f
#define MOST_DISCONNECTS         0x1f
#define DISCONNECT_12            0x02
static const int disconnectBits[] = { 3, 8, 12, 16, 20, 25, 30, 40 };

#define PRIMARY_TIMEOUT          500
#define SECONDARY_TIMEOUT        1500

#define HINT_CONTINUATION        0x80808080

#define DISCONNECT_TIME          2000

#define SETUP_TIME               200
#define SETUP_TRIES              15


/**********************************************************************
 * Frame transmission
 **********************************************************************/

static void sendXIDSlot(LAPPrivate* lapp)
{
  lapp->ctrlBuf[0]=BROADCAST_HANDLE|COMMAND_MASK;
  lapp->ctrlBuf[1]=U_XID_CMD|PF_MASK;
  lapp->ctrlBuf[2]=XID_FORMAT;
  putBELong(lapp->ctrlBuf+3,lapp->address);
  putBELong(lapp->ctrlBuf+7,BROADCAST_ADDRESS);
  lapp->ctrlBuf[11]=lapp->xidFlags;
  lapp->ctrlBuf[12]=lapp->xidSlot;
  lapp->ctrlBuf[13]=XID_VERSION;
  lapp->ctrlLen=14;
  lapp->mod|=MOD_CONTROL;
}

static void sendXIDEnd(LAPPrivate* lapp)
{
  lapp->ctrlBuf[0]=BROADCAST_HANDLE|COMMAND_MASK;
  lapp->ctrlBuf[1]=U_XID_CMD|PF_MASK;
  lapp->ctrlBuf[2]=XID_FORMAT;
  putBELong(lapp->ctrlBuf+3,lapp->address);
  putBELong(lapp->ctrlBuf+7,BROADCAST_ADDRESS);
  lapp->ctrlBuf[11]=lapp->xidFlags;
  lapp->ctrlBuf[12]=END_OF_SLOTS;
  lapp->ctrlBuf[13]=XID_VERSION;
  lapp->ctrlBuf[14]=lapp->lap.flags|HINT_CONTINUATION;
  lapp->ctrlBuf[15]=(lapp->lap.flags>>8)&~HINT_CONTINUATION;
  lapp->ctrlBuf[16]=lapp->info.charSet;
  memcpy(lapp->ctrlBuf+17,lapp->info.bytes,lapp->info.length);
  lapp->ctrlLen=17+lapp->info.length;
  lapp->mod|=MOD_CONTROL;
}

static void sendXIDResponse(LAPPrivate* lapp, int addr, int flags, int slot)
{
  if(lapp->lap.debug&LAP_DEBUG_FRAMES_OUT) {
    birda_log("sendXIDResponse: lap-addr=%x addr=%x\n", lapp->address, addr);
  }
  lapp->ctrlBuf[0]=BROADCAST_HANDLE;
  lapp->ctrlBuf[1]=U_XID_RSP|PF_MASK;
  lapp->ctrlBuf[2]=1;                       /* format */
  putBELong(lapp->ctrlBuf+3,lapp->address); /* src address */
  putBELong(lapp->ctrlBuf+7,addr);          /* dest address */
  lapp->ctrlBuf[11]=flags;
  lapp->ctrlBuf[12]=slot;
  lapp->ctrlBuf[13]=0;                      /* version */
  lapp->ctrlBuf[14]=0x80|lapp->lap.flags;
  lapp->ctrlBuf[15]=0x7f&(lapp->lap.flags>>8);
  lapp->ctrlBuf[16]=lapp->info.charSet;
  memcpy(lapp->ctrlBuf+17,lapp->info.bytes,lapp->info.length);

  lapp->ctrlLen=17+lapp->info.length;
  lapp->mod|=MOD_CONTROL;
}

static void sendSNRMConnect(LAPPrivate* lapp)
{
  int i=0;
  FrameDevice* fd=lapp->fdev;
  int speedMask=fd->getSpeedMask(fd);

  lapp->ctrlBuf[i++]=BROADCAST_HANDLE|COMMAND_MASK;
  lapp->ctrlBuf[i++]=U_SNRM|PF_MASK;
  putBELong(lapp->ctrlBuf+i,lapp->address); i+=4;
  putBELong(lapp->ctrlBuf+i,lapp->setupAddress); i+=4;
  lapp->ctrlBuf[i++]=lapp->handle;

  lapp->ctrlBuf[i++]=NEG_BAUD_RATE;
  if(speedMask>0xff) {
    lapp->ctrlBuf[i++]=2;
    lapp->ctrlBuf[i++]=speedMask;
    lapp->ctrlBuf[i++]=speedMask>>2;
  } else {
    lapp->ctrlBuf[i++]=1;
    lapp->ctrlBuf[i++]=speedMask;
  }

  lapp->ctrlBuf[i++]=NEG_MAX_TA_TIME;
  lapp->ctrlBuf[i++]=1;
  lapp->ctrlBuf[i++]=RECV_MAX_TA;

  lapp->ctrlBuf[i++]=NEG_DATA_SIZE;
  lapp->ctrlBuf[i++]=1;
  lapp->ctrlBuf[i++]=RECV_DATASIZES;

  lapp->ctrlBuf[i++]=NEG_WINDOW_SIZE;
  lapp->ctrlBuf[i++]=1;
  lapp->ctrlBuf[i++]=RECV_WINDOWSIZES;

  lapp->ctrlBuf[i++]=NEG_ADD_BOFS;
  lapp->ctrlBuf[i++]=1;
  lapp->ctrlBuf[i++]=RECV_EXTRA_BOFS;

  lapp->ctrlBuf[i++]=NEG_MIN_TA_TIME;
  lapp->ctrlBuf[i++]=1;
  lapp->ctrlBuf[i++]=fd->getMinTurnaroundMask(fd);

  lapp->ctrlBuf[i++]=NEG_DISC_TIME;
  lapp->ctrlBuf[i++]=1;
  lapp->ctrlBuf[i++]=RECV_DISCONNECTS;

  lapp->ctrlLen=i;
  lapp->mod|=MOD_CONTROL;
}

static void sendUAConnect(LAPPrivate* lapp, int addr, int speedMask, int disconnectMask)
{
  int i=0;
  FrameDevice* fd=lapp->fdev;

  lapp->ctrlBuf[i++]=lapp->handle;
  lapp->ctrlBuf[i++]=U_UA|PF_MASK;
  putBELong(lapp->ctrlBuf+i,lapp->address); i+=4;
  putBELong(lapp->ctrlBuf+i,addr); i+=4;

  lapp->ctrlBuf[i++]=NEG_BAUD_RATE;
  if(speedMask>0xff) {
    lapp->ctrlBuf[i++]=2;
    lapp->ctrlBuf[i++]=speedMask;
    lapp->ctrlBuf[i++]=speedMask>>2;
  } else {
    lapp->ctrlBuf[i++]=1;
    lapp->ctrlBuf[i++]=speedMask;
  }

  lapp->ctrlBuf[i++]=NEG_MAX_TA_TIME;
  lapp->ctrlBuf[i++]=1;
  lapp->ctrlBuf[i++]=RECV_MAX_TA;

  lapp->ctrlBuf[i++]=NEG_DATA_SIZE;
  lapp->ctrlBuf[i++]=1;
  lapp->ctrlBuf[i++]=RECV_DATASIZES;

  lapp->ctrlBuf[i++]=NEG_WINDOW_SIZE;
  lapp->ctrlBuf[i++]=1;
  lapp->ctrlBuf[i++]=RECV_WINDOWSIZES;

  lapp->ctrlBuf[i++]=NEG_ADD_BOFS;
  lapp->ctrlBuf[i++]=1;
  lapp->ctrlBuf[i++]=RECV_EXTRA_BOFS;

  lapp->ctrlBuf[i++]=NEG_MIN_TA_TIME;
  lapp->ctrlBuf[i++]=1;
  lapp->ctrlBuf[i++]=fd->getMinTurnaroundMask(fd);

  lapp->ctrlBuf[i++]=NEG_DISC_TIME;
  lapp->ctrlBuf[i++]=1;
  lapp->ctrlBuf[i++]=disconnectMask;

  lapp->ctrlLen=i;
  lapp->mod|=MOD_CONTROL;
}

static void sendDISC(LAPPrivate* lapp)
{
  lapp->ctrlBuf[0]=lapp->handle;
  if(lapp->state==STATE_PRIMARY) lapp->ctrlBuf[0]|=COMMAND_MASK;
  lapp->ctrlBuf[1]=U_DISC|PF_MASK;
  lapp->ctrlLen=2;
  lapp->mod|=MOD_CONTROL;
}

static void sendUA(LAPPrivate* lapp)
{
  lapp->ctrlBuf[0]=lapp->handle;
  lapp->ctrlBuf[1]=U_UA|PF_MASK;
  lapp->ctrlLen=2;
  lapp->mod|=MOD_CONTROL;
}

static void sendDM(LAPPrivate* lapp, int handle)
{
  lapp->ctrlBuf[0]=handle;
  lapp->ctrlBuf[1]=U_DM|PF_MASK;

  lapp->ctrlLen=2;
  lapp->mod|=MOD_CONTROL;
}

static void sendFirstRR(LAPPrivate* lapp)
{
  lapp->ctrlBuf[0]=lapp->handle|COMMAND_MASK;
  lapp->ctrlBuf[1]=S_RR|PF_MASK;
  lapp->ctrlLen=2;
  lapp->mod|=MOD_CONTROL;
}

static void sendTEST(LAPPrivate* lapp, const u_char* buf, int len)
{
  if(len>sizeof lapp->ctrlBuf) len=sizeof lapp->ctrlBuf;
  memcpy(lapp->ctrlBuf,buf,len);
  lapp->ctrlBuf[0]&=~COMMAND_MASK;
  lapp->ctrlBuf[1]|=PF_MASK;
  if(lapp->ctrlBuf[0]==BROADCAST_HANDLE && len>=10) {
    memcpy(lapp->ctrlBuf+6,lapp->ctrlBuf+2,4);
    putBELong(lapp->ctrlBuf+2,lapp->address);
  }
  lapp->ctrlLen=len;
  lapp->mod|=MOD_CONTROL;
}

static void sReply(LAPPrivate* lapp)
{
  FrameDevice* fd=lapp->fdev;

  lapp->ctrlBuf[0]=lapp->handle;
  if(lapp->state==STATE_PRIMARY) lapp->ctrlBuf[0]|=COMMAND_MASK;
  lapp->ctrlBuf[1]=lapp->nextVr<<NR_SHIFT;
  lapp->ctrlBuf[1]|=PF_MASK;
  lapp->ctrlBuf[1]|=lapp->mod&MOD_BUSY_LOCAL ? S_RNR : S_RR;
  fd->sendFrame(fd,lapp->ctrlBuf,2);
  if(lapp->lap.debug&LAP_DEBUG_FRAMES_OUT) {
    birda_log("sReply out: "); showBytes(lapp->ctrlBuf,2);
  }
}

static void disconnectReply(LAPPrivate* lapp)
{
  FrameDevice* fd=lapp->fdev;

  lapp->ctrlBuf[0]=lapp->handle;
  if(lapp->state==STATE_PRIMARY) lapp->ctrlBuf[0]|=COMMAND_MASK;
  lapp->ctrlBuf[1]=U_DISC|PF_MASK;
  fd->sendFrame(fd,lapp->ctrlBuf,2);
  if(lapp->lap.debug&LAP_DEBUG_FRAMES_OUT) {
    birda_log("disc out: "); showBytes(lapp->ctrlBuf,2);
  }
}

/**********************************************************************
 * Timers
 **********************************************************************/

static void xidReplyTimer(void* lap)
{
  LAPPrivate* lapp=(LAPPrivate*)lap;

  if(lapp->lap.debug&LAP_DEBUG_TIMERS) birda_log("xidReplyTimer\n");
  lapp->state=STATE_NDM;
}

static void speedChangeTimer(void* lap)
{
  LAPPrivate* lapp=(LAPPrivate*)lap;

  if(lapp->lap.debug&LAP_DEBUG_TIMERS) birda_log("speedChangeTimer %d\n",lapp->sendParams.speed);
  lapp->fdev->setParams(lapp->fdev,lapp->sendParams.speed,lapp->sendParams.extraBOFs,RECV_DATASIZE+2);
  if(lapp->lap.debug&LAP_DEBUG_INFO) birda_log("%d baud\n",lapp->sendParams.speed);
}

static void disconnectTimer(void* lap)
{
  LAPPrivate* lapp=(LAPPrivate*)lap;

  disconnectLAP(lapp);
}

static void receiveTimer(void* lap)
{
  LAPPrivate* lapp=(LAPPrivate*)lap;
  int delay=lapp->state==STATE_PRIMARY ? PRIMARY_TIMEOUT : SECONDARY_TIMEOUT;

  if(lapp->lap.debug&LAP_DEBUG_TIMERS) birda_log("receiveTimer\n");
  if(++lapp->nTimeouts*delay>=lapp->sendParams.disconnect*1000) {
    disconnectLAP(lapp);
  } else {
    if(lapp->state==STATE_PRIMARY) {
      if(lapp->mod&MOD_DISCONNECT) disconnectReply(lapp);
      else sReply(lapp);
    }
    evtSetTimer(delay,receiveTimer,lapp);
  }
}

static void turnaroundTimer(void* lap)
{
  LAPPrivate* lapp=(LAPPrivate*)lap;
  FrameDevice* fd;
  int queryEnd=0;

  fd=lapp->fdev;
  if(lapp->lap.debug&LAP_DEBUG_TIMERS) birda_log("turnaroundTimer\n");

  if(lapp->state==STATE_QUERY) {
    if(lapp->xidSlot<answerSlots[lapp->xidFlags&XID_SLOTS_MASK]) {
      sendXIDSlot(lapp);
      lapp->xidSlot++;
      evtSetTimer(SLOT_TIME,turnaroundTimer,lapp);
    } else {
      sendXIDEnd(lapp);
      queryEnd=1;
    }
  } else if(lapp->mod&MOD_DISCONNECT) {
    sendDISC(lapp);
  } else if(lapp->state==STATE_SETUP) {
    if(lapp->setupTries<SETUP_TRIES) {
      lapp->setupTries++;
      sendSNRMConnect(lapp);
    } else {
      lapp->state=STATE_NDM;
      if(lapp->lap.status) lapp->lap.status(&lapp->lap,LAP_DISCONNECTED,0,0,0,0,0);
    }
  }

  if(lapp->mod&MOD_CONTROL) {
    fd->sendFrame(fd,lapp->ctrlBuf,lapp->ctrlLen);
    if(lapp->lap.debug&LAP_DEBUG_FRAMES_OUT) {
      birda_log("turn1 out: "); showBytes(lapp->ctrlBuf,lapp->ctrlLen);
    }
  } else {
    int answered=0;
    if(!(lapp->mod&MOD_BUSY_REMOTE) || lapp->mod&MOD_SEL_REJECT) {
      int nFrames=lapp->mod&MOD_SEL_REJECT ? 1 : lapp->sendParams.windowSize;
      SendBuf* sb;

      lmMoveConnectionBuffers(lapp,nFrames);
      if(nFrames>lapp->sendCount) nFrames=lapp->sendCount;
      if(nFrames>0) answered=1;

      sb=lapp->sendHead;
      while(nFrames-->0) {
	if(!nFrames && !(lapp->mod&MOD_NEED_S)) {
	  sb->buf[1]|=PF_MASK;
	} else {
	  sb->buf[1]&=~PF_MASK;
	}

	sb->buf[1]=(sb->buf[1]&~NR_MASK)|(lapp->nextVr<<NR_SHIFT);

	fd->sendFrame(fd,sb->buf,sb->length);
	if(lapp->lap.debug&LAP_DEBUG_FRAMES_OUT) {
	  birda_log("turn2 out: "); showBytes(sb->buf,sb->length);
	}
	sb=sb->next;
      }
    }

    if(lapp->mod&MOD_NEED_S || !answered) sReply(lapp);
  }

  if(queryEnd) {
    lapp->state=STATE_NDM;
    if(lapp->lap.status) lapp->lap.status(&lapp->lap,LAP_DISCOVERY_END,0,0,0,0,0);
  }

  if(lapp->mod&MOD_CHANGE_SPEED) {
    /* NB: Enough time to complete UA response frame
     *     with all negotiation params at 9600 baud
     */
    evtSetTimer(60,speedChangeTimer,lapp);
  }

  if(lapp->state&STATE_CONNECTED) {
    if(lapp->state==STATE_SETUP) {
      evtSetTimer(SETUP_TIME,turnaroundTimer,lapp);
    } else {
      int delay=lapp->state==STATE_PRIMARY ? PRIMARY_TIMEOUT : SECONDARY_TIMEOUT;
      evtSetTimer(delay,receiveTimer,lapp);
    }
  }

  lapp->mod&=~(MOD_NEED_S|MOD_SEL_REJECT|MOD_CONTROL|MOD_CHANGE_SPEED);
}

/**********************************************************************
 * Frame reception
 **********************************************************************/

static void doXIDCommand(LAPPrivate* lapp, const u_char* buf, int len)
{
  int src,dest,flags,slot;

  if(len<14 || !(buf[1]&COMMAND_MASK) || buf[2]!=XID_FORMAT || buf[13]!=XID_VERSION) return;

  src=getBELong(buf+3);
  if(src==0 || src==BROADCAST_ADDRESS) return;

  dest=getBELong(buf+7);

  flags=buf[11];
  slot=buf[12];

  if(slot==END_OF_SLOTS) {
    if(lapp->state==STATE_REPLY) lapp->state=STATE_NDM;
    evtCancelTimer(xidReplyTimer,lapp);
    if(lapp->lap.status) {
      int i,hints,charset;

      if(len>14+32) len=14+32;  /* It's not supposed to be longer */
      for(i=14;i<len && (HINT_CONTINUATION&buf[i++]););
      hints=getLEVariable(buf+14,i-14)&~HINT_CONTINUATION;

      charset=i<len ? buf[i++] : CHARSET_ASCII;

      lapp->lap.status(&lapp->lap,LAP_DISCOVERED,src,hints,charset,(char*)buf+i,len-i);
    }
  } else if(dest==lapp->address || dest==BROADCAST_ADDRESS) {
    if(lapp->state==STATE_NDM &&
       slot==0 &&
       lapp->lap.status &&
       lapp->lap.status(&lapp->lap,LAP_DISCOVERING,src,0,0,0,0)) {
      int nslots=answerSlots[3&flags];
      if(flags&4) lapp->address=getRandom(BROADCAST_ADDRESS-2)+1;
      lapp->xidSlot=getRandom(nslots-1);
      lapp->state=STATE_REPLY;
      evtSetTimer(100*(nslots-slot),xidReplyTimer,lapp);
    }
    if(lapp->state==STATE_REPLY && slot>=lapp->xidSlot) {
      lapp->state=STATE_NDM;
      evtCancelTimer(xidReplyTimer,lapp);
      sendXIDResponse(lapp,src,flags,slot);
    }
  }
}

static void doXIDResponse(LAPPrivate* lapp, const u_char* buf, int len)
{
  int src;

  if(len<14 || (buf[0]&COMMAND_MASK) || buf[2]!=XID_FORMAT || buf[13]!=XID_VERSION) return;

  src=getBELong(buf+3);
  if(src==0 || src==BROADCAST_ADDRESS) return;

  if(lapp->lap.status) {
    int i,hints,charset;

    if(len>14+32) len=14+32;  /* It's not supposed to be longer */
    for(i=14;i<len && (HINT_CONTINUATION&buf[i++]););
    hints=getLEVariable(buf+14,i-14)&~HINT_CONTINUATION;

    charset=i<len ? buf[i++] : CHARSET_ASCII;

    lapp->lap.status(&lapp->lap,LAP_DISCOVERED,src,hints,charset,(char*)buf+i,len-i);
  }
}

static bool readParams(LAPPrivate* lapp, const u_char* buf, int len, Params* params)
{
  int bits;

  bits=ALL_BAUD_RATES&getLEParameter(NEG_BAUD_RATE,SPEED_9600,buf,len);
  bits&=lapp->fdev->getSpeedMask(lapp->fdev);
  if(!bits) return FALSE;
  params->speedMask=bits;
  params->speed=baudRateBits[highestBit(bits)];

  bits=ALL_MAX_TAS&getLEParameter(NEG_MAX_TA_TIME,MAX_TA_500ms,buf,len);
  if(!bits) return FALSE;
  params->maxTurnaround=maxTurnaroundBits[highestBit(bits)];

  bits=ALL_DATASIZES&getLEParameter(NEG_DATA_SIZE,DATASIZE_64,buf,len);
  if(!bits) return FALSE;
  params->dataSize=dataSizeBits[highestBit(bits)];

  bits=ALL_WINDOWSIZES&getLEParameter(NEG_WINDOW_SIZE,WINDOWSIZE_1,buf,len);
  if(!bits) return FALSE;
  params->windowSize=windowSizeBits[highestBit(bits)];

  bits=ALL_EXTRA_BOFS&getLEParameter(NEG_ADD_BOFS,EXTRA_BOFS_0,buf,len);
  if(!bits) return FALSE;
  params->extraBOFs=extraBOFsBits[highestBit(bits)];

  bits=ALL_MIN_TAS&getLEParameter(NEG_MIN_TA_TIME,MIN_TA_10,buf,len);
  if(!bits) return FALSE;
  params->minTurnaround=minTurnaroundBits[highestBit(bits)];

  bits=MOST_DISCONNECTS&getLEParameter(NEG_DISC_TIME,DISCONNECT_12,buf,len);
  if(!bits) return FALSE;
  params->disconnectMask=bits;
  params->disconnect=disconnectBits[highestBit(bits)];

#if 0
  birda_log("adjust window and data sizes is NYI\n");
  /* NB: Don't truncate the window size, turnaroundTimer should
   *     instead estimate the time needed based on actual frame
   *     sizes, and queue up as many as possible within the
   *     allowed window size
   */
  birda_log("params: speed=%d maxta=%d data=%d window=%d bofs=%d minta=%d disc=%d\n",
	 lapp->speed,lapp->maxTurnaround,lapp->dataSize,lapp->windowSize,
	 lapp->extraBOFs,lapp->minTurnaround,lapp->disconnect);
#endif

  return TRUE;
}

static void initConnection(LAPPrivate* lapp)
{
  lapp->mod=0;
  lapp->nTimeouts=0;
  lapp->nextVs=0;
  lapp->nextVr=0;
}

static bool checkSNRM(LAPPrivate* lapp, const u_char* buf, int len, int* handle, int* src, Params* params)
{
  *handle=BROADCAST_HANDLE;

  if(len<11 || !(buf[0]&COMMAND_MASK)) return FALSE;

  *src=getBELong(buf+2);
  if(*src==0 || *src==BROADCAST_ADDRESS) return FALSE;

  if(getBELong(buf+6)!=lapp->address) return FALSE;

  *handle=buf[10]&~COMMAND_MASK;
  if(*handle==0 || *handle==BROADCAST_HANDLE) return FALSE;

  if(!readParams(lapp,buf+11,len-11,params)) return FALSE;

  return TRUE;
}

static void connectSNRM(LAPPrivate* lapp, int handle, int src, Params* params)
{
  initConnection(lapp);
  lapp->handle=handle;
  memcpy(&lapp->sendParams,params,sizeof(Params));
  lapp->mod|=MOD_CHANGE_SPEED;
  lapp->state=STATE_SECONDARY;

  sendUAConnect(lapp,src,params->speedMask,params->disconnectMask);
}

static bool checkUA(LAPPrivate* lapp, const u_char* buf, int len, Params* params)
{
  if(len<10 || (buf[0]&COMMAND_MASK)) return FALSE;
  if(getBELong(buf+2)!=lapp->setupAddress) return FALSE;
  if(getBELong(buf+6)!=lapp->address) return FALSE;
  if(!readParams(lapp,buf+10,len-10,params)) return FALSE;
  return TRUE;
}

static void connectUA(LAPPrivate* lapp, Params* params)
{
  int delay;

  initConnection(lapp);
  memcpy(&lapp->sendParams,params,sizeof(Params));
  delay=lapp->fdev->setParams(lapp->fdev,lapp->sendParams.speed,
			      lapp->sendParams.extraBOFs,RECV_DATASIZE+2);
  lapp->state=STATE_PRIMARY;
  sendFirstRR(lapp);
  if(lapp->lap.debug&LAP_DEBUG_INFO) birda_log("%d baud\n",lapp->sendParams.speed);

  /* Wait at least 10ms for the speed change to take effect */
  if(delay<10) delay=10;

  evtSetTimer(delay,turnaroundTimer,lapp);
}

#if 0
static void snoopResetSpeed(void* lap)
{
  LAPPrivate* lapp=(LAPPrivate*)lap;

  birda_log("snoopResetSpeed\n");
  lapp->sendParams.speed=9600;
  lapp->fdev->resetParams(lapp->fdev);
  birda_log("reset\n");
}
#endif

static void snoopFollowSpeed(LAPPrivate* lapp, const u_char* buf, int len)
{
  /* XXX why > 10? in my case the size is exactly 10 */
  if(len>10 && buf[1]==(U_UA|PF_MASK)) {
    if(readParams(lapp,buf+10,len-10,&lapp->sendParams)) {
      birda_log("snoopFollowSpeed %d\n",lapp->sendParams.speed);
      lapp->fdev->setParams(lapp->fdev,lapp->sendParams.speed,0,RECV_DATASIZE+2);
      birda_log("%d baud\n",lapp->sendParams.speed);
    }
  }
}

static void frame(FrameDevice* fd, void* buf0, int len)
{
  u_char* buf=(u_char*)buf0;
  LAPPrivate* lapp=(LAPPrivate*)fd->handle;
  u_char handle=buf[0]&~COMMAND_MASK;
  u_char isCommand=(COMMAND_MASK&buf[0])!=0;
  u_char control=buf[1];
  u_char isFinal=(PF_MASK&buf[1])!=0;

  if(lapp->lap.debug&LAP_DEBUG_FRAMES_IN) {
    birda_log("in: "); showBytes(buf,len);
  }
  if(lapp->lap.debug&LAP_SNOOP) {
    snoopFollowSpeed(lapp,buf,len);
    /* evtSetTimer(5000,snoopResetSpeed,lapp); */
    return;
  }

  if(IS_U(control)) {
    switch(lapp->state) {
    case STATE_NDM:
    case STATE_QUERY:
    case STATE_REPLY:
      if(handle!=BROADCAST_HANDLE) return;
      switch(control) {
      case U_XID_CMD:
      case U_XID_CMD|PF_MASK: doXIDCommand(lapp,buf,len);  break;
      case U_XID_RSP:
      case U_XID_RSP|PF_MASK: doXIDResponse(lapp,buf,len); break;
      case U_UI:
      case U_UI|PF_MASK:      if(isCommand) lmUnitData(lapp,buf+2,len-2); break;
      case U_TEST|PF_MASK:    sendTEST(lapp,buf,len);      break;
      case U_SNRM|PF_MASK:
	{
	  Params params;
	  int handle;
	  int src;
	  if(checkSNRM(lapp,buf,len,&handle,&src,&params) &&
	     lapp->lap.status &&
	     lapp->lap.status(&lapp->lap,LAP_CONNECTING,src,0,0,0,0)) {
	    connectSNRM(lapp,handle,src,&params);
	  } else {
	    sendDM(lapp,handle);
	  }
	}
	break;
      }
      break;
    case STATE_SETUP:
      if(handle==lapp->handle) {
	Params params;

	switch(control) {
	case U_DM:
	case U_DM|PF_MASK:   
	  if(!isCommand) {
	    disconnectLAP(lapp); 
	  }
	  break;
	case U_DISC:
	case U_DISC|PF_MASK: 
	  if(isCommand) {
	    disconnectLAP(lapp); 
	  }
	  break;
	case U_UA|PF_MASK:
	  if(checkUA(lapp,buf,len,&params)) {
	    connectUA(lapp,&params);
	    if(lapp->lap.status)
	      lapp->lap.status(&lapp->lap,LAP_CONNECTED,lapp->setupAddress,0,0,0,0);
	  }
	  break;
	}
      } else {
	Params params;
	int handle;
	int src;

	if(handle==BROADCAST_HANDLE &&
	   control==(U_SNRM|PF_MASK) &&
	   checkSNRM(lapp,buf,len,&handle,&src,&params) &&
	   src==lapp->setupAddress &&
	   lapp->address<lapp->setupAddress) {
	  connectSNRM(lapp,handle,src,&params);
	} else {
	  return;
	}
      }
      break;
    case STATE_PRIMARY:
    case STATE_SECONDARY:    
      if(handle!=lapp->handle) return;
      if((lapp->state==STATE_PRIMARY) == isCommand) break;
      switch(control) {
      case U_UI:
      case U_UI|PF_MASK:
	if(!(lapp->mod&(MOD_DISCONNECT|MOD_BUSY_LOCAL))) lmData(lapp,buf+2,len-2,1);
	break;
      case U_DM|PF_MASK:
	if(lapp->mod&MOD_DISCONNECT) {
	  disconnectLAP(lapp);
	}
	break;
      case U_UA|PF_MASK:
	if(lapp->state==STATE_PRIMARY && (lapp->mod&MOD_DISCONNECT)) {
	  disconnectLAP(lapp);
	}
	break;
      case U_TEST|PF_MASK:
	if(lapp->state==STATE_SECONDARY) sendTEST(lapp,buf,len);
	break;
      case U_DISC|PF_MASK:
	if(lapp->state==STATE_SECONDARY) {
	  sendUA(lapp);
	  disconnectLAP(lapp);
	} else if(!(lapp->mod&MOD_DISCONNECT)) {
	  lapp->mod|=MOD_DISCONNECT;
	  evtSetTimer(DISCONNECT_TIME,disconnectTimer,lapp);
	}
	break;
      case U_FRMR|PF_MASK:
      case U_SNRM|PF_MASK:
	if(!(lapp->mod&MOD_DISCONNECT)) {
	  lapp->mod|=MOD_DISCONNECT;
	  evtSetTimer(DISCONNECT_TIME,disconnectTimer,lapp);
	}
	break;
      }
      break;
    }
  } else if(lapp->state==STATE_PRIMARY || lapp->state==STATE_SECONDARY) {
    if(handle!=lapp->handle) return;
    if((lapp->state==STATE_PRIMARY) == isCommand) {
      birda_log("Primary/secondary conflict\n");
      disconnectLAP(lapp);
      return;
    }
    if(!(lapp->mod&MOD_DISCONNECT)) {
      int nr=NR(control);

      while(lapp->sendHead && NS(lapp->sendHead->buf[1])!=nr) {
	SendBuf* s=lapp->sendHead;
	lapp->sendHead=s->next;
	lapp->sendCount--;
	freeMem(s);
      }

      if(!lapp->sendHead && nr!=lapp->nextVs) {
	birda_log("Sequence error (nr)\n");
      }

      if(IS_S(control)) {
	switch(S_MASK&control) {
	case S_RR: lapp->mod&=~MOD_BUSY_REMOTE; break;
	case S_RNR: lapp->mod|=MOD_BUSY_REMOTE; break;
	case S_SREJ: lapp->mod|=MOD_SEL_REJECT; break;
	}
      } else {
	int ns=NS(control);
	if(!(lapp->mod&MOD_BUSY_LOCAL) && lapp->nextVr==ns) {
	  lapp->nextVr=(lapp->nextVr+1)%8;
	  lmData(lapp,buf+2,len-2,0);
	}
      }
    }
  }

  lapp->nTimeouts=0;

  if(isFinal &&
     ((lapp->mod&MOD_CONTROL) || (lapp->state&STATE_CONNECTED) || (lapp->state==STATE_QUERY))) {
    evtSetTimer(lapp->sendParams.minTurnaround,turnaroundTimer,lapp);
  }
}

/**********************************************************************
 * Cleanup
 **********************************************************************/

static void resetState(LAPPrivate* lapp)
{
  FrameDevice* fd=lapp->fdev;

  lapp->state=STATE_NDM;
  lapp->mod=0;
  lapp->sendParams.minTurnaround=0;
  fd->resetParams(fd);
  if(lapp->lap.debug&LAP_DEBUG_INFO) birda_log("reset\n");
}

static void disconnectLAP(LAPPrivate* lapp)
{
  lmDisableConnections(lapp);

  while(lapp->sendHead) {
    SendBuf* s=lapp->sendHead->next;
    freeMem(lapp->sendHead);
    lapp->sendHead=s;
  }
  lapp->sendCount=0;

  evtCancelTimer(xidReplyTimer,lapp);
  evtCancelTimer(speedChangeTimer,lapp);
  evtCancelTimer(receiveTimer,lapp);
  evtCancelTimer(turnaroundTimer,lapp);
  evtCancelTimer(disconnectTimer,lapp);

  resetState(lapp);

  if(lapp->lap.status) lapp->lap.status(&lapp->lap,LAP_DISCONNECTED,0,0,0,0,0);
}

/**********************************************************************
 * Used by mux.c
 **********************************************************************/

void lapAppendSendBuffer(LAPPrivate* lapp, SendBuf* s)
{
  if(lapp->sendHead) {
    lapp->sendTail->next=s;
  } else {
    lapp->sendHead=s;
  }
  lapp->sendTail=s;
  s->next=0;
  lapp->sendCount++;

  s->buf[0]=lapp->handle;
  if(lapp->state==STATE_PRIMARY) s->buf[0]|=COMMAND_MASK;
  s->buf[1]=lapp->nextVs<<NS_SHIFT;
  lapp->nextVs=(lapp->nextVs+1)%8;
}

/**********************************************************************
 * External functions
 **********************************************************************/

void lapClose(LAP* lap)
{
  LAPPrivate* lapp=(LAPPrivate*)lap;

  while(lapp->lsaps) {
    LSAPPrivate* l=lapp->lsaps;
    lapp->lsaps=l->next;
    freeMem(l);
  }

  disconnectLAP(lapp);
  lmFreeConnections(lapp);
  lapp->fdev->handle=0;
  lapp->fdev->frame=0;
  freeMem(lapp);
}

void lapDisconnect(struct LAP* lap)
{
  LAPPrivate* lapp=(LAPPrivate*)lap;

  if(lapp->state&STATE_CONNECTED) {
    lapp->mod|=MOD_DISCONNECT;
    evtSetTimer(DISCONNECT_TIME,disconnectTimer,lapp);
  }
}

bool lapConnect(struct LAP* lap, int address)
{
  LAPPrivate* lapp=(LAPPrivate*)lap;

  /* Media busy ... */
  if(lapp->state==STATE_NDM) {
    lapp->state=STATE_SETUP;
    lapp->handle=(getRandom(BROADCAST_HANDLE-4)+2)&~1;
    lapp->setupTries=0;
    lapp->setupAddress=address;
    turnaroundTimer(lap);
    return TRUE;
  } else {
    return FALSE;
  }
}

bool lapDiscover(struct LAP* lap)
{
  LAPPrivate* lapp=(LAPPrivate*)lap;

  /* Media busy ... */
  if(lapp->state==STATE_NDM) {
    lapp->state=STATE_QUERY;
    lapp->xidSlot=0;
    lapp->xidFlags=XID_SLOTS_6;
    turnaroundTimer(lap);
    return TRUE;
  } else {
    return FALSE;
  }
}

LAP* createLAP(FrameDevice* fdev, int charset, const char* name, int len)
{
  LAPPrivate* lapp=allocMem(id_lap,sizeof(LAPPrivate));

  lapp->lap.flags=0;
  lapp->lap.debug=0;
  lapp->lap.handle=0;
  lapp->lap.status=0;
  lapp->fdev=fdev;
  lapp->address=getRandom(BROADCAST_ADDRESS-2)+1;
  lapp->lsaps=0;
  lapp->sendHead=0;
  lapp->sendCount=0;
  lapp->connections=0;
  lapp->nextConn=0;
  lapp->recvDataSize=RECV_DATASIZE;

  if(len>sizeof(lapp->info.bytes)) len=sizeof(lapp->info.bytes);
  lapp->info.charSet=charset;
  lapp->info.length=len;
  memcpy(lapp->info.bytes,name,len);

  resetState(lapp);
  fdev->handle=lapp;
  fdev->frame=frame;

  return &lapp->lap;
}
