/* Communication routines for application program interface (server side)

	This module is a layer below apiserve which isolates
	communication details.						*/


#include <stdio.h>
#include <memory.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/un.h>
#include "apicom.h"
#define BUFSIZE 8192


/* Internal states */
static enum states
{
  st_init, st_synch, st_len1, st_len2, st_recv
};

typedef void (*callback_func)();
typedef char Boolean;


/* Global variables */
static callback_func callback;	/* Routine to call when data is received */
static int sock;		/* Socket descriptor for accept */
static int msgsock;		/* Socket descriptor while connected */
static int cursock;		/* Socket descriptor to wait on */
static struct sockaddr_un s_un;	/* Socket address */
static enum states state;	/* Internal state */
static char *inbuff;		/* Input buffer */
static char *inp;		/* Input buffer pointer */
static int incount;		/* Number of characters in input buffer */
extern Boolean debug;		/* Debugging flag */
extern Boolean trace;		/* Tracing flag */
extern int errno;		/* Last error number */

static void sendit();


/* Prepare to receive connections.  Return address of file descriptor
   to use for select() calls.  */

int *coms_init(sessid, cb)
     char sessid;
     callback_func cb;
{
  static int on = 1;

  /* Save address of callback routine */
  callback = cb;
  
  /* Create a socket */
  if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
    errorm("API socket creation failed");
  cursock = sock;

  /* Make a socket address */
  s_un.sun_family = AF_UNIX;
  sprintf(s_un.sun_path, "%s%c", SOCKET_NAME, sessid);

  /* If a socket already exists with this name, erase it */
  (void) unlink(s_un.sun_path);

  /* Assign name to this socket */
  if (bind(sock, &s_un, sizeof s_un) < 0)
    errorm("API socket bind failed");
  
  /* Start accepting connections */
  if (listen(sock, 5) < 0)
    errorm("API listen failed");

  /* Set for non-blocking I/O */
  if (ioctl(sock, FIONBIO, &on) < 0)
    errorm("API ioctl failed");

  /* Allocate space for input buffer */
  if (!(inbuff = (char *) malloc(BUFSIZE)))
    error("Not enough memory");
  inp = inbuff;
  incount = 0;

  state = st_init;
  return &cursock;
}


/* Process any pending events.  Return 1 if something was found.  */
int coms_check()
{     
  int retval, len;
  struct sockaddr_un fromaddr;
  static int datalen;
  static char *databuff, *dataptr;
  static int on = 1;

  retval = 0;
  while (1)
    {
      /* If we're connected and input buffer is empty, read more data */
      if (state != st_init && !incount)
	{
	  int j;
	  if ((j = recv(msgsock, (char *) inbuff, BUFSIZE, 0)) < 0)
	    {
	      if (errno == EWOULDBLOCK) return retval;
	      errorm("API receive data failed");
	    }
	  if (!j) 
	    {
	      if (trace) printf("API connection broken\n");
	      if (state == st_recv) free(databuff);
	      coms_disc();
	      return 0;
	    }
	  retval = 1;
	  inp = inbuff;
	  incount = j;
	  if (trace) 
	    {
	      printf("API received %d bytes:\n", incount);
	      dumpabuff(inbuff, incount);
	      putchar('\n');
	    }
	}

      switch (state) 
	{
	case st_init:
	  /* +------------------------+ */
	  /* | Waiting for connection | */
	  /* +------------------------+ */
	  len = sizeof fromaddr;
	  if ((msgsock = accept(sock, &fromaddr, &len)) < 0)
	    {
	      if (errno == EWOULDBLOCK) return 0;
	      errorm("API accept failed");
	    }
	  if (ioctl(msgsock, FIONBIO, &on) < 0)
	    errorm("API ioctl failed");
	  cursock = msgsock;
	  state = st_synch;
	  break;

	case st_synch:
	  /* +------------------------+ */
	  /* | Waiting for synch byte | */
	  /* +------------------------+ */
	  if (*inp != API_SYNCH && debug)
	    printf("API protocol error\n");
	  for (; incount > 0 && *inp != API_SYNCH; inp++, incount--);
	  if (!incount) return 1;
	  inp++; incount--;
	  state = st_len1;
	  break;

	case st_len1:
	  /* +------------------------------+ */
	  /* | Reading first byte of length | */
	  /* +------------------------------+ */
	  datalen = *inp++ << 8;
	  incount--;
	  state = st_len2;
	  break;

	case st_len2:
	  /* +-------------------------------+ */
	  /* | Reading second byte of length | */
	  /* +-------------------------------+ */
	  datalen += *inp++;
	  incount--;
	  if (!datalen && debug)
	    {
	      /* We should always get at least one byte of data */
	      printf("API protocol error\n");
	      state = st_synch;
	    }
	  if (!(databuff = (char *) malloc(datalen)))
	    error("Not enough memory");
	  dataptr = databuff;
	  state = st_recv;
	  break;

	case st_recv:
	  /* +----------------+ */
	  /* | Receiving data | */
	  /* +----------------+ */

	  /* Compute amount remaining to be filled */
	  len = datalen - (dataptr - databuff);

	  /* If we didn't get enough, then copy what we got and return */
	  if (incount < len)
	    {
	      memcpy(dataptr, inp, incount);
	      dataptr += incount;
	      incount = 0;
	      return 1;
	    }

	  /* We got all the data, so call the callback routine */
	  memcpy(dataptr, inp, len);
	  (*callback)(databuff, datalen);

	  /* We're done with this data */
	  free(databuff);
	  inp += len;
	  incount -= len;

	  /* If callback routine didn't disconnect then wait for more data */
	  if (state == st_recv) state = st_synch;
	  break;
	}
    }
}


/* Send data to client */
coms_put(buff, length)
     char *buff;
     int length;
{
  static char hdr[] = { API_SYNCH, 0, 0 };

  if (state == st_init) return 0;
  if (length > 65535) error("API data buffer is too big");
  hdr[1] = length >> 8;
  hdr[2] = length & 255;
  sendit(hdr, sizeof hdr);
  sendit(buff, length);
  if (trace) 
    {
      printf("API sending %d bytes:\n", length);
      dumpabuff(buff, length);
      putchar('\n');
    }
  return 0;
}

  
/* Disconnect */
coms_disc()
{
  if (state == st_init) return 0;
  (void) close(msgsock);
  cursock = sock;
  state = st_init;
  return 0;
}


/* Shut down */
coms_close()
{
  (void) close(sock);
  (void) unlink(s_un.sun_path);
  return 0;
}



/*** Private routines ***/


/* Send data */
static void sendit(buff, length)
     register char *buff;
     register int length;
{
  static int off = 0, on = 1;
  while (length > 0)
    {
      int n = send(msgsock, buff, length, 0);
      if (n < 0)
	{
	  /* Normally, I/O is non-blocking.  In the rare event that no
	     space is available in the sending socket, we switch
	     temporarily to blocking I/O to do the send.  */

	  if (errno != EWOULDBLOCK) errorm("API send data failed");
	  if (ioctl(msgsock, FIONBIO, &off) < 0) errorm("API ioctl failed");
	  n = send(msgsock, buff, length, 0);
	  if (n < 0) errorm("API send data failed");
	  if (ioctl(msgsock, FIONBIO, &on) < 0) errorm("API ioctl failed");
	}
      if (n == 0) error("API send data failed:  No data sent");
      buff += n;
      length -= n;
    }
}


/* Print out an ASCII data buffer */
dumpabuff(buff, len)
     char *buff;
     int len;
{
  int i, j, numrows;
  char c;

  numrows = (len + 15) / 16;
  for (i = 0; i < numrows; i++) 
    {
      printf("    ");
      for (j = 0; j < 16; j++) 
	{
	  if (j + i * 16 >= len) printf("  ");
	  else printf("%02X", buff[j + i * 16]);
	  if (j % 4 == 3) putchar(' ');
	}
      printf(" |");
      for (j = 0; j < 16; j++)
	{
	  if (j + i * 16 >= len) putchar(' ');
	  else 
	    {
	      c = buff[j + i * 16];
	      if (c >= 32 && c < 127) putchar(c);
	      else putchar('.');
	    }
	}
      printf("|\n");
    }
}
