/*
 * 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.
 */
/* iassrv.c
 *
 * Limitations:
 *  - Only GetValueByClass supported
 */

#include <irda.h>
#include <ias.h>

#include <string.h>

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

static const char id_server[]="ias server";
static const char id_object[]="ias object";
static const char id_attribute[]="ias attribute";
static const char id_connection[]="ias server connection";
static const char id_inbuf[]="ias server inbuf";
static const char id_outbuf[]="ias server outbuf";

#define IrLMP_VERSION            0x01
#define IAS_SUPPORT              0x00
#define LM_MUX_SUPPORT           0x00

static const u_char irLMPSupport[] = { IrLMP_VERSION, IAS_SUPPORT, LM_MUX_SUPPORT };

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

typedef struct String {
  int length;
  char chars[60];  /* Longest class and attribute names according to spec */
} String;

typedef struct AttributePrivate {
  Attribute attr;
  struct AttributePrivate* next;
  struct AttributePrivate* selnext;
  struct ObjectPrivate* obj;
  String name;
  int type;
  union {
    int i;
    struct {
      u_short length;
      u_char charset;
      u_char bytes[1];
    } s;
  } value;
} AttributePrivate;

typedef struct ObjectPrivate {
  Object object;
  struct ObjectPrivate* next;
  struct IASServerPrivate* ias;
  u_short id;
  String class;
  AttributePrivate* attrs;
} ObjectPrivate;

typedef struct IASServerPrivate {
  IASServer ias;
  LSAP* lsap;
  ObjectPrivate* objects;
  struct IASConnection* connections;
} IASServerPrivate;

typedef struct IASConnection {
  struct IASConnection* next;
  IASServerPrivate* ias;

  int op;

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

  int inLength;
  int inMax;
  u_char* inBuf;
} IASConnection;

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

static int cmpString(String* s1, String* s2)
{
  int d=s1->length-s2->length;
  int len=d<0 ? s1->length : s2->length;
  return memcmp(s1->chars,s2->chars,len) || d;
}

static int getString(String* s, u_char* buf, int* ip, int len)
{
  int i=*ip;

  if(i>=len) return 0;
  s->length=buf[i++];
  if(i+s->length>len) return 0;
  if(s->length>sizeof s->chars) return 0;
  memcpy(s->chars,buf+i,len);
  i+=s->length;
  *ip=i;
  return 1;
}

static void sendACK(Connection* con, int op)
{
  u_char hdr[1];

  hdr[0]=op|IAS_ACK|IAS_LAST;
  connWrite(con,hdr,1);
}

static void sendUnsupported(Connection* con, int op)
{
  u_char hdr[2];

  hdr[0]=op|IAS_LAST;
  hdr[1]=IAS_UNSUPPORTED;
  connWrite(con,hdr,2);
}

static u_char* reserveReply(IASConnection* iasc, int n)
{
  u_char* p;

  while(iasc->outLength+n>iasc->outMax) {
    iasc->outMax*=2;
    iasc->outBuf=growMem(iasc->outBuf,iasc->outMax);
  }
  p=iasc->outBuf+iasc->outLength;
  iasc->outLength+=n;
  return p;
}

static void getValueByClass(IASConnection* ic, u_char* buf, int len)
{
  String class;
  String attr;
  int i;
  bool classFound=FALSE;
  int nFound=0;
  AttributePrivate* selected=0;
  ObjectPrivate* op;
  AttributePrivate* ap;

  /* Dissect arguments */
  i=0;
  if(!getString(&class,buf,&i,len)) {
    *reserveReply(ic,1)=IAS_NO_CLASS;
    return;
  }
  if(!getString(&attr,buf,&i,len)) {
    *reserveReply(ic,1)=IAS_NO_ATTR;
    return;
  }

  /* Filter out reply set */
  if(ic->ias) {
    for(op=ic->ias->objects;op;op=op->next) {
      if(cmpString(&class,&op->class)) continue;
      classFound=TRUE;
      for(ap=op->attrs;ap;ap=ap->next) {
	if(cmpString(&attr,&ap->name)) continue;
	ap->selnext=selected;
	selected=ap;
	nFound++;
      }
    }
  }

  if(ic->ias->ias.debug&IAS_DEBUG_INFO)
    birda_log("GetValueByClass %.*s %.*s\n",class.length,class.chars,attr.length,attr.chars);

  /* Format reply */

  if(nFound) {
    *reserveReply(ic,1)=IAS_SUCCESS;
    putBEShort(reserveReply(ic,2),nFound);

    for(ap=selected;ap;ap=ap->selnext) {
      putBEShort(reserveReply(ic,2),ap->obj->id);
      *reserveReply(ic,1)=ap->type;
      if(ic->ias->ias.debug&IAS_DEBUG_INFO) birda_log(" -> %d ",ap->obj->id);
      usleep(50000);		/* XXX LA: this seems to improve ircomm */
      switch(ap->type) {
      case IAS_INTEGER:
	putBELong(reserveReply(ic,4),ap->value.i);
	if(ic->ias->ias.debug&IAS_DEBUG_INFO) birda_log("%d\n",ap->value.i);
	break;
      case IAS_OCTETS:
	i=ap->value.s.length;
	putBEShort(reserveReply(ic,2),i);
	memcpy(reserveReply(ic,i),ap->value.s.bytes,i);
	showBytes(ap->value.s.bytes,i);
	if(ic->ias->ias.debug&IAS_DEBUG_INFO) birda_log("\n");
	break;
      case IAS_STRING:
	i=ap->value.s.length;
	*reserveReply(ic,1)=ap->value.s.charset;
	*reserveReply(ic,1)=i;
	memcpy(reserveReply(ic,i),ap->value.s.bytes,i);
	if(ic->ias->ias.debug&IAS_DEBUG_INFO) birda_log("%.*s\n",i,ap->value.s.bytes);
	break;
      default:
	if(ic->ias->ias.debug&IAS_DEBUG_INFO) birda_log("[missing]\n");
	break;
      }
    }
  } else {
    *reserveReply(ic,1)=classFound ? IAS_NO_ATTR : IAS_NO_CLASS;
  }
}

static void sendChunk(Connection* con, IASConnection* iasc)
{
  int n=iasc->outLength-iasc->outOfs;
  int k=connGetSendDataSize(con)-1;
  u_char hdr[1];

  if(k>n) k=n;
  hdr[0]=iasc->op;
  if(k==n) hdr[0]|=IAS_LAST;
  connWrite2(con,hdr,1,iasc->outBuf,k);
  iasc->outOfs+=k;
}

static AttributePrivate* makeAttribute(Object* obj, const char* name, int extra)
{
  ObjectPrivate* objp=(ObjectPrivate*)obj;
  AttributePrivate* attr;
  int len=strlen(name);

  if(len>sizeof attr->name.chars) return 0;
  for(attr=objp->attrs;attr;attr=attr->next) {
    if(attr->name.length==len &&
       !memcmp(attr->name.chars,name,len)) return 0;
  }

  attr=allocMem(id_attribute,sizeof(AttributePrivate)+extra);
  attr->obj=objp;
  attr->name.length=len;
  memcpy(attr->name.chars,name,len);

  attr->next=objp->attrs;
  objp->attrs=attr;

  return attr;
}

static void status(Connection* con, int event, void* buf, int len)
{
  int flags;
  IASConnection* ic=(IASConnection*)con->handle;

  if(event==CONN_CLOSED) {
    if(ic->ias) {
      IASConnection** ih=&ic->ias->connections;
      IASConnection* i;

      while((i=*ih)) {
	if(i==ic) {
	  *ih=i->next;
	  break;
	} else {
	  ih=&i->next;
	}
      }
    }

    if(ic->outBuf) freeMem(ic->outBuf);
    if(ic->inBuf) freeMem(ic->inBuf);
    flags = ic->ias->ias.debug&IAS_DEBUG_INFO;
    freeMem(ic);
    connClose(con);
    if(flags) birda_log("ias closed\n");
  }
}

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

  /* Just ignore null input, OK? */
  if(len<1) return;

  op=buf[0]&~(IAS_LAST|IAS_ACK);
  if(op!=iasc->op) {
    iasc->op=op;
    iasc->outLength=0;
    iasc->inLength=0;
  }

  if(buf[0]&IAS_ACK) {
    if(iasc->outOfs<iasc->outLength) sendChunk(con,iasc);
  } else {
    iasc->outOfs=0;
    iasc->outLength=0;

    while(iasc->inMax<iasc->inLength+len-1) {
      iasc->inMax*=2;
      iasc->inBuf=growMem(iasc->inBuf,iasc->inMax);
    }      

    memcpy(iasc->inBuf+iasc->inLength,buf+1,len-1);
    iasc->inLength+=len-1;

    if(buf[0]&IAS_LAST) {
      switch(op) {
      case OP_GetValueByClass:
	getValueByClass(iasc,buf+1,len-1);
	sendChunk(con,iasc);
	break;
      default:
	birda_log("opcode %x not supported\n",op);
	sendUnsupported(con,op);
	break;
      }
      iasc->inLength=0;
    } else {
      sendACK(con,op);
    }
  }
}

static bool accept(LSAP* lsap, Connection* con, void* buf0, int len)
{
  IASConnection* iasc=allocMem(id_connection,sizeof(IASConnection));
  IASServerPrivate* iasp=(IASServerPrivate*)lsap->handle;

  iasc->ias=iasp;
  iasc->inLength=0;
  iasc->inMax=256;
  iasc->inBuf=allocMem(id_inbuf,iasc->inMax);
  iasc->outOfs=0;
  iasc->outLength=0;
  iasc->outMax=256;
  iasc->outBuf=allocMem(id_outbuf,iasc->outMax);

  iasc->next=iasp->connections;
  iasp->connections=iasc;

  con->handle=iasc;
  con->status=status;
  con->data=data;

  if(iasc->ias->ias.debug&IAS_DEBUG_INFO) birda_log("ias accept\n");
  return TRUE;
}

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

void iasAttrDelete(Attribute* attr)
{
  AttributePrivate* attrp=(AttributePrivate*)attr;
  AttributePrivate** ah=&attrp->obj->attrs;
  AttributePrivate* a;

  while((a=*ah)) {
    if(a==attrp) {
      *ah=a->next;
      break;
    } else {
      ah=&a->next;
    }
  }

  freeMem(attrp);
}

Attribute* iasObjNewInteger(Object* obj, const char* name, int value)
{
  AttributePrivate* attr=makeAttribute(obj,name,0);

  if(attr) {
    attr->type=IAS_INTEGER;
    attr->value.i=value;
  }
  return &attr->attr;
}

Attribute* iasObjNewString(Object* obj, const char* name, int charset, const char* value, int length)
{
  AttributePrivate* attr;

  if(length<0 || length>255) return 0;
  attr=makeAttribute(obj,name,length);
  if(attr) {
    attr->type=IAS_STRING;
    attr->value.s.charset=charset;
    attr->value.s.length=length;
    memcpy(attr->value.s.bytes,value,length);
  }
  return &attr->attr;
}

Attribute* iasObjNewOctets(Object* obj, const char* name, const void* value0, int length)
{
  u_char* value=(u_char*)value0;
  AttributePrivate* attr;

  if(length<0 || length>1024) return 0;
  attr=makeAttribute(obj,name,length);
  if(attr) {
    attr->type=IAS_OCTETS;
    attr->value.s.length=length;
    memcpy(attr->value.s.bytes,value,length);
  }
  return &attr->attr;
}

void iasObjDelete(Object* obj)
{
  ObjectPrivate* objp=(ObjectPrivate*)obj;
  ObjectPrivate** oh=&objp->ias->objects;
  ObjectPrivate* o;
  AttributePrivate* attr;

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

  for(attr=objp->attrs;attr;) {
    AttributePrivate* a1=attr;
    attr=attr->next;
    freeMem(a1);
  }

  freeMem(objp);
}

Object* iasSrvNewObject(IASServer* ias, const char* class)
{
  IASServerPrivate* iasp=(IASServerPrivate*)ias;
  ObjectPrivate* op;
  int len=strlen(class);
  u_short id;

  if(len>sizeof op->class.chars) return 0;

  for(id=0;;id++) {
    for(op=iasp->objects;op;op=op->next) if(op->id==id) break;
    if(!op) break;
  }

  op=allocMem(id_object,sizeof(ObjectPrivate));
  op->attrs=0;
  op->ias=iasp;
  op->id=id;
  op->class.length=len;
  memcpy(op->class.chars,class,len);

  op->next=iasp->objects;
  iasp->objects=op;

  return &op->object;
}

void iasSrvClose(IASServer* ias)
{
  IASServerPrivate* iasp=(IASServerPrivate*)ias;
  ObjectPrivate* obj;
  IASConnection* iasc;

  lsapClose(iasp->lsap);
  for(iasc=iasp->connections;iasc;iasc=iasc->next) iasc->ias=0;

  for(obj=iasp->objects;obj;) {
    ObjectPrivate* o1=obj;
    AttributePrivate* attr;
    for(attr=obj->attrs;attr;) {
      AttributePrivate* a1=attr;
      attr=attr->next;
      freeMem(a1);
    }
    obj=obj->next;
    freeMem(o1);
  }

  freeMem(iasp);
}

IASServer* createIASServer(LAP* lap, int charset, const char* name, int len)
{
  IASServerPrivate* iasp=allocMem(id_server,sizeof(IASServerPrivate));
  IASServer* ias=&iasp->ias;
  Object* obj;

  iasp->objects=0;
  iasp->connections=0;

  iasp->lsap=lapNewLSAP(lap,0);
  iasp->lsap->handle=iasp;
  iasp->lsap->accept=accept;

  if(lsapGetSelector(iasp->lsap)!=IAS_LSAPSEL) {
    birda_log("IAS wasn't registered first: wrong LSAP selector\n");
  }

  obj=iasSrvNewObject(ias,"Device");

  iasObjNewString(obj, "DeviceName", charset, name, len);
  iasObjNewOctets(obj, "IrLMPSupport", irLMPSupport, sizeof irLMPSupport);

  return ias;
}
