/*
 * 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.
 */
/* mux.c
 *
 * Limitations:
 *  - LM exclusive mode not supported
 */

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

#include <string.h>

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

static const char id_mux_lsap[]="mux lsap";
static const char id_mux_connection[]="mux connection";
static const char id_mux_sarbuffer[]="mux sar buffer";
static const char id_mux_sendbuf[]="mux sendbuf";

#define LM_CONTROL               0x80

#define LM_CONNECT               0x01
#define LM_CONNECT_CONFIRM       0x81
#define LM_DISCONNECT            0x02
#define LM_ACCESSMODE            0x03
#define LM_ACCESSMODE_CONFIRM    0x83

#define LM_UNSUPPORTED           0xff

#define REASON_USER              0x01
#define REASON_LINK_MANAGEMENT   0x05
#define REASON_DISCONNECTED      0x06
#define REASON_NO_PEER           0x08
#define REASON_UNSPECIFIED       0xff

#define LSAP_UNCONNECTED         0x70

#define TTP_P                    0x80
#define TTP_M                    0x80

#define PI_MAXSDUSIZE            1

#define INITIAL_CREDITS          1       /* Is this the level we want? */

/**********************************************************************
 * Internal functions
 **********************************************************************/

static SendBuf* makeSendBuf(int length)
{
  SendBuf* s=allocMem(id_mux_sendbuf,sizeof(SendBuf)+length-1);
  s->length=length;
  return s;
}

static void sendDisconnect(LAPPrivate* lapp, int dlsap, int slsap, int reason)
{
  SendBuf* sb=makeSendBuf(6);

  sb->buf[2]=dlsap|LM_CONTROL;
  sb->buf[3]=slsap;
  sb->buf[4]=LM_DISCONNECT;
  sb->buf[5]=reason;
  /* User data is not supported */
  lapAppendSendBuffer(lapp,sb);
}

static void sendConnect(LAPPrivate* lapp, int dlsap, int slsap,
			bool ttp, int credits, const u_char* buf, int len)
{
  SendBuf* sb;

  sb=makeSendBuf((ttp ? 7 : 6)+len);
  sb->buf[2]=dlsap|LM_CONTROL;
  sb->buf[3]=slsap;
  sb->buf[4]=LM_CONNECT;
  sb->buf[5]=0;
  if(ttp) sb->buf[6]=credits;
  memcpy(sb->buf+(ttp ? 7 : 6),buf,len);
  lapAppendSendBuffer(lapp,sb);
}

static void sendConnectConfirm(LAPPrivate* lapp, int dlsap, int slsap,
			       bool ttp, int credits)
{
  SendBuf* sb;

  sb=makeSendBuf(ttp ? 7 : 6);
  sb->buf[2]=dlsap|LM_CONTROL;
  sb->buf[3]=slsap;
  sb->buf[4]=LM_CONNECT_CONFIRM;
  sb->buf[5]=0;
  if(ttp) sb->buf[6]=credits;
  /* User data not supported */
  lapAppendSendBuffer(lapp,sb);
}

static void sendAccessModeDeny(LAPPrivate* lapp, int dlsap, int slsap)
{
  SendBuf* sb=makeSendBuf(6);

  sb->buf[2]=dlsap|LM_CONTROL;
  sb->buf[3]=slsap;
  sb->buf[4]=LM_ACCESSMODE_CONFIRM;
  sb->buf[5]=LM_UNSUPPORTED;
  lapAppendSendBuffer(lapp,sb);
}

static void disableConnection(ConnectionPrivate* conp)
{
  if((conp->flags&(CST_OPEN|CST_OPENING)) &&
     !(conp->flags&(CST_NOTIFIED))) {
    conp->flags|=CST_NOTIFIED;
    sendDisconnect(conp->lap,conp->remoteSel,conp->localSel,REASON_USER);
  }

  if(conp->sarBuffer) {
    freeMem(conp->sarBuffer);
    conp->sarBuffer=0;
  }

  while(conp->sendHead) {
    SendBuf* sb=conp->sendHead;
    conp->sendHead=sb->next;
    freeMem(sb);
  }

  if(conp->flags&(CST_OPENING|CST_OPEN)) {
    conp->flags&=~(CST_OPENING|CST_OPEN);
    if(conp->con.status) conp->con.status(&conp->con,CONN_CLOSED,0,0);
  }
}

static void freeConnection(ConnectionPrivate* conp)
{
  LAPPrivate* lapp=conp->lap;
  ConnectionPrivate** ch=&lapp->connections;
  ConnectionPrivate* c;

  while((c=*ch)) {
    if(c==conp) {
      *ch=c->next;
      break;
    } else {
      ch=&c->next;
    }
  }

  if(lapp->nextConn==conp) lapp->nextConn=0;
  freeMem(conp);
}

static int parseTTPConnect(ConnectionPrivate* c, const u_char* buf, int len)
{
  int len1;

  if(len<1) return -1;
  c->sendCredits=buf[0]&~TTP_P;
  if(buf[0]&TTP_P) {
    if(len<2) return -1;
    len1=buf[1]+2;
    if(len1>len) return -1;
    c->sendSduSize=0x7fffffff&getBEParameter(PI_MAXSDUSIZE,0,buf+2,len1-2);
    return len1;
  } else {
    return 1;
  }
}

static void lsapControl(LAPPrivate* lapp, int dlsap, int slsap, int op,
			ConnectionPrivate* con, u_char* buf, int len)
{
  int i;

  switch(op) {
  case LM_CONNECT:
    if(con) {
      /* Reply to this? */
      birda_log("Attempted connect to existing connection\n");
    } else {
      LSAPPrivate* lsapp=lapp->lsaps;
      while(lsapp && lsapp->lsapSel!=dlsap) lsapp=lsapp->next;
      if(lsapp) {
	con=allocMem(id_mux_connection,sizeof(ConnectionPrivate));
	con->con.handle=0;
	con->con.status=0;
	con->con.data=0;
	con->lap=lapp;
	con->localSel=dlsap;
	con->remoteSel=slsap;
	con->flags=(lsapp->flags&CST_USER_FLAGS)|CST_OPEN;
	con->sendSduSize=0;
	con->recvSduSize=0;
	con->sarLength=0;
	con->sarBuffer=0;
	con->sendHead=0;

	con->sendCredits=0;
	con->recvCredits=0;
	con->wantedRecvCredits=0;
	con->sendDataSize=lapp->sendParams.dataSize-2; /* LM header bytes */
	con->recvDataSize=lapp->recvDataSize-2;        /* LM header bytes */

	if(con->flags&LM_TINY_TP) {
	  con->recvDataSize--;
	  con->sendDataSize--;
	  con->recvCredits=1;
	  con->wantedRecvCredits=INITIAL_CREDITS;
	}

	i=lsapp->flags&LM_TINY_TP ? parseTTPConnect(con,buf,len) : 0;
	if(i>=0 &&
	   lsapp->lsap.accept &&
	   lsapp->lsap.accept(&lsapp->lsap,&con->con,buf+i,len-i)) {
	  if(con->recvSduSize) con->sarBuffer=allocMem(id_mux_sarbuffer,con->recvSduSize);
	  con->next=lapp->connections;
	  lapp->connections=con;

	  /* SAR not enabled in the return direction */
	  sendConnectConfirm(lapp,slsap,dlsap,con->flags&LM_TINY_TP,con->recvCredits);
	} else {
	  freeMem(con);
	  sendDisconnect(lapp,slsap,dlsap,REASON_UNSPECIFIED);
	}
      } else {
	birda_log("No such LSAP: %d\n",dlsap);
	sendDisconnect(lapp,slsap,dlsap,REASON_NO_PEER);
      }
    }
    break;
  case LM_CONNECT_CONFIRM:
    if(con->flags&CST_OPENING) {
      i=(con->flags&LM_TINY_TP) ? parseTTPConnect(con,buf,len) : 0;
      if(i>=0) {
	con->flags=(con->flags&~CST_OPENING)|CST_OPEN;
	if(con->recvSduSize) con->sarBuffer=allocMem(id_mux_sarbuffer,con->recvSduSize);
	if(con->con.status) con->con.status(&con->con,CONN_OPENED,buf+i,len-i);
      } else {
	birda_log("failed to exchanged ttp settings\n");
	con->flags&=CST_OPENING;
	/* Do anything more? */
      }
    }
    break;
  case LM_DISCONNECT:
    if(con) {
      con->flags|=CST_NOTIFIED;
      disableConnection(con);
    }
    break;
  }
}

static void unconnData(LAPPrivate* lapp, u_char* buf, int len)
{
  birda_log("Unconnected LSAP data:\n");
  showBytes(buf,len);
}

/**********************************************************************
 * Called by lap.c
 **********************************************************************/

void lmDisableConnections(LAPPrivate* lapp)
{
  ConnectionPrivate* c;

  for(c=lapp->connections;c;c=c->next) disableConnection(c);
}

void lmFreeConnections(LAPPrivate* lapp)
{
  while(lapp->connections) freeConnection(lapp->connections);
}

void lmMoveConnectionBuffers(LAPPrivate* lapp, int wanted)
{
  if(lapp->connections) {
    int flag;
    ConnectionPrivate* c;

    if(!lapp->nextConn) lapp->nextConn=lapp->connections;
    for(flag=0,c=lapp->nextConn;lapp->sendCount<wanted;) {
      if(c->flags&CST_OPEN) {
	int delta=c->wantedRecvCredits-c->recvCredits;
	SendBuf* s=c->sendHead;
	if(s) {
	  c->sendHead=s->next;
	} else if(delta>0) {
	  s=makeSendBuf(5);
	  s->buf[2]=c->remoteSel;
	  s->buf[3]=c->localSel;
	  s->buf[4]=0;
	}
	if(delta>0) {
	  if(delta>127) delta=127;
	  s->buf[4]=(s->buf[4]&TTP_M)|delta;
	  c->recvCredits+=delta;
	}
	if(s) {
	  lapAppendSendBuffer(lapp,s);
	  flag=1;
	}
      }
      c=c->next ? c->next : lapp->connections;
      if(c==lapp->nextConn) {
	if(!flag) return;
	flag=0;
      }
    }
  }
}

void lmData(LAPPrivate* lapp, u_char* buf, int len, int expedited)
{
  int cmd,dlsap,slsap,opcode;
  ConnectionPrivate* conp;

  if(len<2) return;
  cmd=LM_CONTROL&buf[0];
  dlsap=~LM_CONTROL&buf[0];
  slsap=~LM_CONTROL&buf[1];
  
  if(dlsap>=LSAP_UNCONNECTED || slsap>=LSAP_UNCONNECTED) {
    if(!cmd && dlsap==LSAP_UNCONNECTED && slsap==LSAP_UNCONNECTED)
      unconnData(lapp,buf+2,len-2);
    return;
  }

  for(conp=lapp->connections;conp;conp=conp->next)
    if((conp->flags&(CST_OPENING|CST_OPEN)) &&
       conp->localSel==dlsap &&
       conp->remoteSel==slsap) break;

  /* NB: Should we also discard expedited data? */

  if(cmd) {
    if(len<3 || expedited) return;
    opcode=buf[2];

    switch(opcode) {
    case LM_CONNECT_CONFIRM:
    case LM_CONNECT:
    case LM_DISCONNECT:
      lsapControl(lapp,dlsap,slsap,opcode,conp,buf+4,len-4);
      break;
    case LM_ACCESSMODE:
      sendAccessModeDeny(lapp,slsap,dlsap);
      break;
    case LM_ACCESSMODE_CONFIRM:
      break;
    }
  } else if(!conp) {
    sendDisconnect(lapp,slsap,dlsap,REASON_DISCONNECTED);
  } else if(len>2) {
    if(!(conp->flags&LM_TINY_TP)) {
      if(conp->con.data) conp->con.data(&conp->con,buf+2,len-2);
    } else {
      if(conp->flags&CST_OPEN) {
	conp->sendCredits+=buf[2]&~TTP_M;
	if(len>3) {
	  if(conp->recvCredits>0) conp->recvCredits--;
	  if(!conp->recvSduSize) {
	    if(conp->con.data) conp->con.data(&conp->con,buf+3,len-3);
	  } else if(conp->sarLength+len-3>conp->recvSduSize) {
	    sendDisconnect(conp->lap,conp->remoteSel,conp->localSel,REASON_LINK_MANAGEMENT);
	    disableConnection(conp);
	    birda_log("SDU is too long, closing the connection\n");
	  } else if(buf[2]&TTP_M) {
	    memcpy(conp->sarBuffer+conp->sarLength,buf+3,len-3);
	    conp->sarLength+=len-3;
	  } else if(conp->sarLength) {
	    memcpy(conp->sarBuffer+conp->sarLength,buf+3,len-3);
	    conp->sarLength+=len-3;
	    if(conp->con.data) conp->con.data(&conp->con,conp->sarBuffer,conp->sarLength);
	    conp->sarLength=0;
	  } else {
	    if(conp->con.data) conp->con.data(&conp->con,buf+3,len-3);
	  }
	}
      }
    }
  }
}

void lmUnitData(LAPPrivate* lapp, u_char* buf, int len)
{
  if(len>=2 && buf[0]==LSAP_UNCONNECTED && (~LM_CONTROL&buf[1])==LSAP_UNCONNECTED)
    unconnData(lapp,buf+2,len-2);
}

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


LSAP* lapNewLSAP(LAP* lap, int flags)
{
  LAPPrivate* lapp=(LAPPrivate*)lap;
  LSAPPrivate* lsapp;
  ConnectionPrivate* c;
  u_char sel=0;

  for(sel=0;sel<LSAP_UNCONNECTED;sel++) {
    for(lsapp=lapp->lsaps; lsapp && lsapp->lsapSel!=sel; lsapp=lsapp->next);
    if(lsapp) continue;
    for(c=lapp->connections;
	c && !(c->flags&(CST_OPENING|CST_OPEN)) && c->localSel!=sel;
	c=c->next);
    if(!c) break;
  }
  if(sel==LSAP_UNCONNECTED) return 0;

  lsapp=allocMem(id_mux_lsap,sizeof(LSAPPrivate));
  lsapp->lsap.handle=0;
  lsapp->lsap.accept=0;
  lsapp->lsapSel=sel;
  lsapp->flags=flags;
  lsapp->lap=lapp;

  lsapp->next=lapp->lsaps;
  lapp->lsaps=lsapp;

  return &lsapp->lsap;
}

u_char lsapGetSelector(LSAP* lsap)
{
  return ((LSAPPrivate*)lsap)->lsapSel;
}

void lsapClose(LSAP* lsap)
{
  LSAPPrivate* lsapp=(LSAPPrivate*)lsap;
  LSAPPrivate* l;
  LSAPPrivate** lh=&lsapp->lap->lsaps;

  while((l=*lh)) {
    if(l==lsapp) {
      *lh=l->next;
      freeMem(l);
      return;
    } else {
      lh=&l->next;
    }
  }
}

Connection* lapNewConnection(LAP* lap, int rlsap, int flags, const void* buf0, int len)
{
  const u_char* buf=(u_char*)buf0;
  LAPPrivate* lapp=(LAPPrivate*)lap;
  LSAPPrivate* l;
  ConnectionPrivate* c;
  u_char llsap=0;

  if(!(lapp->state&STATE_CONNECTED)) return 0;

  for(llsap=0;llsap<LSAP_UNCONNECTED;llsap++) {
    for(l=lapp->lsaps; l && l->lsapSel!=llsap; l=l->next);
    if(l) continue;
    for(c=lapp->connections;
	c && (!(c->flags&(CST_OPENING|CST_OPEN)) ||
	      c->localSel!=llsap || c->remoteSel!=rlsap);
	c=c->next);
    if(!c) break;
  }
  if(llsap==LSAP_UNCONNECTED) return 0;

  c=allocMem(id_mux_connection,sizeof(ConnectionPrivate));
  c->con.handle=0;
  c->con.status=0;
  c->con.data=0;
  c->lap=lapp;
  c->localSel=llsap;
  c->remoteSel=rlsap;
  c->flags=(flags&CST_USER_FLAGS)|CST_OPENING;
  c->sendSduSize=0;
  c->recvSduSize=0;
  c->sarLength=0;
  c->sarBuffer=0;
  c->sendHead=0;

  c->sendCredits=0;
  c->recvCredits=0;
  c->wantedRecvCredits=0;
  c->sendDataSize=lapp->sendParams.dataSize-2; /* LM header bytes */
  c->recvDataSize=lapp->recvDataSize-2;        /* LM header bytes */

  if(c->flags&LM_TINY_TP) {
    c->sendDataSize--;
    c->recvDataSize--;
    c->recvCredits=1;
    c->wantedRecvCredits=INITIAL_CREDITS;
  }

  c->next=lapp->connections;
  lapp->connections=c;

  sendConnect(lapp,rlsap,llsap,c->flags&LM_TINY_TP,c->recvCredits,buf,len);

  return &c->con;
}

void connClose(Connection* con)
{
  ConnectionPrivate* conp=(ConnectionPrivate*)con;

  conp->con.status=0;
  disableConnection(conp);
  freeConnection(conp);
}

int connGetSendDataSize(Connection* con)
{
  ConnectionPrivate* conp=(ConnectionPrivate*)con;
  return conp->sendSduSize ? conp->sendSduSize : conp->sendDataSize;
}    

int connGetRecvDataSize(Connection* con)
{
  ConnectionPrivate* conp=(ConnectionPrivate*)con;
  return conp->recvSduSize ? conp->recvSduSize : conp->recvDataSize;
}    

bool connWrite(Connection* con, const void* buf0, int len)
{
  u_char* buf=(u_char*)buf0;
  ConnectionPrivate* conp=(ConnectionPrivate*)con;
  int ttp=conp->flags&LM_TINY_TP ? 1 : 0;

  if(!(conp->flags&(CST_OPENING|CST_OPEN))) return FALSE;

  if(len>(conp->sendSduSize ? conp->sendSduSize : conp->sendDataSize)) {
    birda_log("Packet is too large, discarding\n");
    return FALSE;
  }

  while(len>0) {
    int len1=len<conp->sendDataSize ? len : conp->sendDataSize;
    SendBuf* sb=makeSendBuf(len1+4+ttp);
    sb->buf[2]=conp->remoteSel;
    sb->buf[3]=conp->localSel;
    if(ttp) sb->buf[4]=len1<len ? TTP_M : 0;
    memcpy(sb->buf+4+ttp,buf,len1);
    if(conp->sendHead) conp->sendTail->next=sb;
    else               conp->sendHead=sb;
    conp->sendTail=sb;
    sb->next=0;
    buf+=len1;
    len-=len1;
  }

  return TRUE;
}

bool connWrite2(Connection* con, const void* buf10, int len1, const void* buf20, int len2)
{
  u_char* buf1=(u_char*)buf10;
  u_char* buf2=(u_char*)buf20;
  ConnectionPrivate* conp=(ConnectionPrivate*)con;
  int ttp=conp->flags&LM_TINY_TP ? 1 : 0;

  if(!(conp->flags&(CST_OPENING|CST_OPEN))) return FALSE;

  if(len1+len2>(conp->sendSduSize ? conp->sendSduSize : conp->sendDataSize)) {
    birda_log("Packet is too large, discarding\n");
    return FALSE;
  }

  while(len1+len2>0) {
    int len1x=len1<conp->sendDataSize ? len1 : conp->sendDataSize;
    int len2x=len2<conp->sendDataSize-len1x ? len2 : conp->sendDataSize-len1x;
    SendBuf* sb=makeSendBuf(len1x+len2x+4+ttp);
    sb->buf[2]=conp->remoteSel;
    sb->buf[3]=conp->localSel;
    if(ttp) sb->buf[4]=len2x<len2 ? TTP_M : 0;
    memcpy(sb->buf+4+ttp,buf1,len1x);
    memcpy(sb->buf+4+ttp+len1x,buf2,len2x);
    if(conp->sendHead) conp->sendTail->next=sb;
    else               conp->sendHead=sb;
    conp->sendTail=sb;
    sb->next=0;
    buf1+=len1x;
    len1-=len1x;
    buf2+=len2x;
    len2-=len2x;
  }

  return TRUE;
}
