/*
 * 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.
 */
/* obexsrv.c
 *
 * 2do:
 *  - GET command?
 *  - HINT_FILE_SERVER?
 */

#include <irda.h>
#include <obex.h>

#include <string.h>

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

static const char id_server[]="obex server";
static const char id_connection[]="obex server connection";
static const char id_inbuf[]="obex server inbuf";
static const char id_collbuf[]="obex server collbuf";
static const char id_outbuf[]="obex server outbuf";

/**********************************************************************
 * Data structures
 **********************************************************************/

typedef struct OBEXServerPrivate {
  OBEXServer obex;
  Object* obexObject;
  Object* xferObject;
  LSAP* lsap;

  struct OBEXConnection* connections;
} OBEXServerPrivate;

typedef struct OBEXConnection {
  struct OBEXConnection* next;
  OBEXServerPrivate* obex;
  int maxSendSize;

  int op;

  int chunkLength;
  int chunkCode;
  int outOfs;
  int outLength;
  int outMax;
  u_char* outBuf;

  int inLength;
  int inMax;
  u_char* inBuf;

  int collPos;
  int collLen;
  int collOn;
  u_char* collBuf;
} OBEXConnection;

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

static void sendConnectResponse(Connection* con)
{
  u_char hdr[7];

  hdr[0]=RES_SUCCESS|OBEX_LAST;
  putBEShort(hdr+1,7);
  hdr[3]=OBEX_VERSION;
  hdr[4]=0;

  /* NB: Stick to max frame size for now. We may want to increase
   *     it later if it works for other devices. Max OBEX size is 65535
   */
  putBEShort(hdr+5,connGetRecvDataSize(con));

  connWrite(con,hdr,7);
}

static void sendSuccess(Connection* con)
{
  u_char hdr[3];

  hdr[0]=RES_SUCCESS|OBEX_LAST;
  putBEShort(hdr+1,3);
  connWrite(con,hdr,3);
}

static void sendContinue(Connection* con)
{
  u_char hdr[3];

  hdr[0]=RES_CONTINUE|OBEX_LAST;
  putBEShort(hdr+1,3);
  connWrite(con,hdr,3);
}

static void sendBadRequest(Connection* con)
{
  u_char hdr[3];

  hdr[0]=RES_BAD_REQUEST|OBEX_LAST;
  putBEShort(hdr+1,3);
  connWrite(con,hdr,3);
}

static void sendNotImplemented(Connection* con)
{
  u_char hdr[3];

  hdr[0]=RES_NOT_IMPLEMENTED|OBEX_LAST;
  putBEShort(hdr+1,3);
  connWrite(con,hdr,3);
}

static int headerLength(OBEXConnection* oc, int ofs)
{
  int len=oc->outLength-ofs;

  switch(oc->outBuf[ofs]&TYPE_MASK) {
  case TYPE_UNICODE:
    if(len>2) {
      int l1=getBEShort(oc->outBuf+ofs+1);
      if(l1<5) l1=5;
      if(len>l1) len=l1;
    }
    break;
  case TYPE_BYTES:
    if(len>2) {
      int l1=getBEShort(oc->outBuf+ofs+1);
      if(l1<3) l1=3;
      if(len>l1) len=l1;
    }
    break;
  case TYPE_INT1:
    if(len>2) len=2;
    break;
  case TYPE_INT4:
    if(len>5) len=5;
    break;
  }
  return len;
}

static void sendChunk(Connection* con, OBEXConnection* oc)
{
  u_char hdr[6];

  if(oc->chunkLength>0) {
    int l=oc->outOfs+oc->chunkLength;
    while(oc->outLength-l>=3) {
      int l1=l+headerLength(oc,l);
      if(l1-oc->outOfs+6>oc->maxSendSize) break;
      l=l1;
    }
    if(l-oc->outOfs+6>oc->maxSendSize) {
      hdr[0]=OP_PUT;
      putBEShort(hdr+1,oc->maxSendSize);
      hdr[3]=oc->chunkCode==(HI_END_OF_BODY|TYPE_BYTES) ? (HI_BODY|TYPE_BYTES) : oc->chunkCode;
      putBEShort(hdr+4,oc->maxSendSize-3);
      connWrite2(con,hdr,6,oc->outBuf+oc->outOfs,oc->maxSendSize-6);

      oc->chunkLength-=oc->maxSendSize-6;
      oc->outOfs+=oc->maxSendSize-6;
    } else {
      hdr[0]=OP_PUT;
      if(l==oc->outLength) hdr[0]|=OBEX_LAST;
      putBEShort(hdr+1,l-oc->outOfs+6);
      hdr[3]=oc->chunkCode;
      putBEShort(hdr+4,oc->chunkLength+3);
      connWrite2(con,hdr,6,oc->outBuf+oc->outOfs,l-oc->outOfs);

      oc->chunkLength=0;
      oc->outOfs=l;
    }
  } else {
    int l=oc->outOfs;
    while(oc->outLength-l>=3) {
      int l1=l+headerLength(oc,l);
      if(l1-oc->outOfs+3>oc->maxSendSize) break;
      l=l1;
    }
    if(l>oc->outOfs) {
      hdr[0]=OP_PUT;
      if(l==oc->outLength) hdr[0]|=OBEX_LAST;
      putBEShort(hdr+1,l-oc->outOfs+3);
      connWrite2(con,hdr,3,oc->outBuf+oc->outOfs,l-oc->outOfs);

      oc->outOfs=l;
    } else if(oc->outLength-oc->outOfs>=3) {
      oc->chunkCode=oc->outBuf[oc->outOfs];
      oc->chunkLength=headerLength(oc,oc->outOfs)-3;
      oc->outOfs+=3;

      hdr[0]=OP_PUT;
      putBEShort(hdr+1,oc->maxSendSize);
      hdr[3]=oc->chunkCode==(HI_END_OF_BODY|TYPE_BYTES) ? (HI_BODY|TYPE_BYTES) : oc->chunkCode;
      putBEShort(hdr+4,oc->maxSendSize-3);
      connWrite2(con,hdr,6,oc->outBuf+oc->outOfs,oc->maxSendSize-6);

      oc->chunkLength-=oc->maxSendSize-6;
      oc->outOfs+=oc->maxSendSize-6;
    } else {
      hdr[0]=OP_PUT|OBEX_LAST;
      putBEShort(hdr+1,oc->outLength-oc->outOfs+3);
      connWrite2(con,hdr,3,oc->outBuf+oc->outOfs,oc->outLength-oc->outOfs);

      oc->outOfs=oc->outLength;
    }
  }
}

static void status(Connection* con, int event, void* buf, int len)
{
  OBEXConnection* oc=(OBEXConnection*)con->handle;

  if(event==CONN_CLOSED) {
    if(oc->obex) {
      OBEXConnection** oh=&oc->obex->connections;
      OBEXConnection* o;

      while((o=*oh)) {
	if(o==oc) {
	  *oh=o->next;
	  break;
	} else {
	  oh=&o->next;
	}
      }
    }

    if(oc->outBuf) freeMem(oc->outBuf);
    if(oc->inBuf) freeMem(oc->inBuf);
    freeMem(oc);
    if(oc->obex->obex.debug&OBEX_DEBUG_INFO) birda_log("obex connection closed\n");

    connClose(con);
  }
}

static void data(Connection* con, void* buf0, int len)
{
  u_char* buf=(u_char*)buf0;
  OBEXConnection* oc=(OBEXConnection*)con->handle;
  int len1;
  u_char op;

  if(oc->collOn) {
    birda_log("add to collBuf %d+%d (%d)\n",oc->collPos,len,oc->collLen);
    len1=oc->collPos+len;
    if(len1>oc->collLen) {
      birda_log("coll buffer overflow\n");
      sendBadRequest(con);
      oc->chunkLength=0;
      oc->outLength=0;
      oc->inLength=0;
      oc->collOn=0;
      return;
    }
    memcpy(oc->collBuf+oc->collPos,buf,len);
    if(len1<oc->collLen) {
      birda_log("continue %d\n", len1,oc->collLen);
      oc->collPos=len1;
      sendContinue(con);
      return;
      }
    buf=oc->collBuf;
    len=oc->collLen;
    oc->collOn=0;
  }
  if(len<3) {
    birda_log("short obex packet\n");
    sendBadRequest(con);
    oc->chunkLength=0;
    oc->outLength=0;
    oc->inLength=0;
    return;
  }
  len1=getBEShort(buf+1);
  if(len1>len) {
    birda_log("bad length in OBEX packet %d>%d\n",len1,len);
    if(oc->collBuf) freeMem(oc->collBuf);
    oc->collBuf=allocMem(id_collbuf,len1);
    memcpy(oc->collBuf,buf,len);
    oc->collOn=1;
    oc->collPos=len;
    oc->collLen=len1;
    sendContinue(con);
    return;
  }
  op=buf[0]&~OBEX_LAST;
  if(op!=oc->op) {
    oc->op=op;
    oc->chunkLength=0;
    oc->outLength=0;
    oc->inLength=0;
  }

  if(op==RES_CONTINUE) {
    if(oc->outOfs<oc->outLength) sendChunk(con,oc);
  } else {
    oc->chunkLength=0;
    oc->outOfs=0;
    oc->outLength=0;

    while(oc->inMax<oc->inLength+len-1) {
      oc->inMax*=2;
      oc->inBuf=growMem(oc->inBuf,oc->inMax);
    }      
    if(len1>3) {
      memcpy(oc->inBuf+oc->inLength,buf+3,len1-3);
      oc->inLength+=len1-3;
    }
    if(buf[0]&OBEX_LAST) {
      switch(op) {
      case OP_CONNECT:
	if(len>=7) {
	  int version=buf[3];
	  int flags=buf[4];
	  int maxlen=getBEShort(buf+5);

	  if(oc->obex->obex.debug&OBEX_DEBUG_INFO) {
	    birda_log("obex connect version=%d.%d flags=%d maxlen=%d\n",
		version>>4,version&15,flags,maxlen);
	    if(len>7) {
	      birda_log("optional data: ");
	      showBytes(buf+7,len-7);
	    }
	  }

	  oc->maxSendSize=connGetSendDataSize(con);
	  if(oc->maxSendSize>maxlen) oc->maxSendSize=maxlen;
	  sendConnectResponse(con);
	} else {
	  birda_log("short obex connect packet\n");
	  sendBadRequest(con);
	}
	break;
      case OP_DISCONNECT:
	if(oc->obex->obex.debug&OBEX_DEBUG_INFO) birda_log("obex disconnect\n");
	sendSuccess(con);
	break;
      case OP_PUT:
	if(oc->obex->obex.debug&OBEX_DEBUG_INFO) birda_log("obex put\n");
	sendSuccess(con);
	if(oc->obex->obex.object)
	  oc->obex->obex.object(&oc->obex->obex,oc->inBuf,oc->inLength);
	break;
      case OP_GET:
	birda_log("obex get is not supported\n");
	sendNotImplemented(con);
	break;
      default:
	birda_log("obex op code %x not supported\n",op);
	sendNotImplemented(con);
	break;
      }
      oc->inLength=0;
    } else {
      sendContinue(con);
    }
  }
}

static bool accept(LSAP* lsap, Connection* con, void* buf, int len)
{
  OBEXConnection* oc=allocMem(id_connection,sizeof(OBEXConnection));
  OBEXServerPrivate* obex=(OBEXServerPrivate*)lsap->handle;

  oc->obex=obex;
  oc->inLength=0;
  oc->inMax=256;
  oc->inBuf=allocMem(id_inbuf,oc->inMax);
  oc->chunkLength=0;
  oc->outOfs=0;
  oc->outLength=0;
  oc->outMax=256;
  oc->outBuf=allocMem(id_outbuf,oc->outMax);

  oc->maxSendSize=connGetSendDataSize(con);
  if(oc->maxSendSize>255) oc->maxSendSize=255;

  oc->next=obex->connections;
  obex->connections=oc;

  con->handle=oc;

  con->status=status;
  con->data=data;

  return TRUE;
}

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

void obexSrvClose(OBEXServer* obex)
{
  OBEXServerPrivate* op=(OBEXServerPrivate*)obex;
  OBEXConnection* oc;

  iasObjDelete(op->obexObject);
  iasObjDelete(op->xferObject);

  lsapClose(op->lsap);
  for(oc=op->connections;oc;oc=oc->next) oc->obex=0;

  if(oc->inBuf) freeMem(oc->inBuf);
  if(oc->outBuf) freeMem(oc->outBuf);
  if(oc->collBuf) freeMem(oc->collBuf);

  freeMem(op);
}

OBEXServer* createOBEXServer(LAP* lap, IASServer* ias)
{
  OBEXServerPrivate* obexp=allocMem(id_server,sizeof(OBEXServerPrivate));

  obexp->obex.object=0;
  obexp->lsap=lapNewLSAP(lap,LM_TINY_TP);
  obexp->lsap->handle=obexp;
  obexp->lsap->accept=accept;

  obexp->obexObject=iasSrvNewObject(ias,"OBEX");
  iasObjNewInteger(obexp->obexObject,"IrDA:TinyTP:LsapSel",lsapGetSelector(obexp->lsap));

  obexp->xferObject=iasSrvNewObject(ias,"OBEX:IrXfer");
  iasObjNewInteger(obexp->xferObject,"IrDA:TinyTP:LsapSel",lsapGetSelector(obexp->lsap));

  lap->flags|=HINT_IROBEX;

  return &obexp->obex;
}
