/* socket.c:  Berkeley sockets style interface to Pilot SLP/PADP
 *
 * (c) 1996, D. Jeff Dionne.
 * This is free software, licensed under the GNU Public License V2.
 * See the file COPYING for details.
 */

#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <signal.h>
#include <fcntl.h>
#include "pi-source.h"
#include "pi-socket.h"
#include "pi-serial.h"
#include "pi-padp.h"
#include "pi-cmp.h"
#include "pi-dlp.h"
#include "pi-syspkt.h"

static struct pi_socket *psl = (struct pi_socket *)0;

void installexit(void);

extern int dlp_trace;

/* Automated tickling interval */
int interval=0;
int busy=0;

/* Create a local connection endpoint */

int pi_socket(int domain, int type, int protocol)
{
  struct pi_socket *p;
  struct pi_socket *ps;

  if ((domain != PI_AF_SLP) ||
      ((type  != PI_SOCK_STREAM) &&
      (type   != PI_SOCK_RAW)) ||
      ((protocol != PI_PF_PADP) &&
       (protocol != PI_PF_SLP))) {  /* FIXME:  Need to support more */
    errno = EINVAL;
    return -1;
  }

  ps = malloc(sizeof(struct pi_socket));
  memset(ps,0,sizeof(struct pi_socket));

#ifdef OS2
  if((ps->sd = open("NUL", O_RDWR))==-1) {
#else
  if((ps->sd = open("/dev/null", O_RDWR))==-1) {
#endif
    int err = errno; /* Save errno of open */
    free(ps);
    errno = err;
    return -1;
  }
  ps->mac = calloc(1, sizeof(struct pi_mac));
  ps->type = type;
  ps->protocol = protocol;
  ps->connected = 0;
  ps->mac->fd = 0;
  ps->mac->ref = 1;
  ps->xid = 0;
  ps->initiator = 0;
  ps->minorversion = 0;
  ps->majorversion = 0;
  ps->version = 0;
  ps->dlprecord = 0;
  ps->busy = 0;
  
#ifdef OS2
  ps->os2_read_timeout=60;
  ps->os2_write_timeout=60;
#endif

#ifndef NO_SERIAL_TRACE
  ps->debuglog = 0;
  ps->debugfd = 0;
  
  if (getenv("PILOTLOG")) {
    if ((ps->debuglog = getenv("PILOTLOGFILE"))==0)
      ps->debuglog = "PiDebug.log";
  }
#endif

#ifndef NO_DLP_TRACE
  if (getenv("PILOTDLP")) {
    dlp_trace=1;
  }
#endif

  if(type == PI_SOCK_STREAM) {
    ps->establishrate = 9600; /* Default PADP connection rate */
    if (getenv("PILOTRATE"))
    	ps->establishrate = atoi(getenv("PILOTRATE"));
    ps->rate = 9600; /* Mandatory CMP conncetion rate */
  } else if(type == PI_SOCK_RAW) {
    ps->establishrate = ps->rate = 57600; /* Mandatory SysPkt connection rate */
  }
  
  installexit();

  if (!psl) psl = ps;
  else {
    for (p = psl; p->next; p=p->next);

    p->next = ps;
  }
  
  return ps->sd;
}

/* Connect to a remote server */

int pi_connect(int pi_sd, struct pi_sockaddr *addr, int addrlen)
{
  struct pi_socket *ps;
  struct cmp c;

  if (!(ps = find_pi_socket(pi_sd))) {
    errno = ESRCH;
    return -1;
  }

  if (pi_device_open(addr->pi_device, ps) == -1) {
    return -1;     /* errno already set */
  }

  ps->raddr = *addr;
  ps->laddr = *addr;
   
  if(ps->type == PI_SOCK_STREAM) {

    if(cmp_wakeup(ps, 38400)<0) /* Assume this box can't go over 38400 */
      return -1;

    if(cmp_rx(ps, &c) < 0)
      return -1; /* failed to read, errno already set */

    if(c.type == 2) {
      /* CMP init packet */

      if(c.flags & 0x80) {
        /* Change baud rate */
        ps->rate = c.baudrate;
        pi_device_changebaud(ps);
      }
      return 0;

    } else if(c.type == 3) {
      /* CMP abort packet -- the other side didn't like us */
      pi_device_close(ps);

#ifdef DEBUG
      fprintf(stderr,"Received CMP abort from client\n");
#endif
      errno = -EIO;
      return -1;
    }
  }
  ps->connected = 1;
  
  ps->initiator = 1; /* We initiated the link */
  
  return 0;
}

/* Bind address to a local socket */

int pi_bind(int pi_sd, struct pi_sockaddr *addr, int addrlen)
{
  struct pi_socket *ps;

  if (!(ps = find_pi_socket(pi_sd))) {
    errno = ESRCH;
    return -1;
  }
  
  if (pi_device_open(addr->pi_device, ps) == -1) {
    return -1;     /* errno already set */
  }
  
  ps->laddr = *addr;
  ps->raddr = *addr;

  return 0;
}

/* Wait for an incoming connection */

int pi_listen(int pi_sd, int backlog)
{
  struct pi_socket *ps;

  if (!(ps = find_pi_socket(pi_sd))) {
    errno = ESRCH;
    return -1;
  }
  return 0;
}

/* Accept an incoming connection */

int pi_accept(int pi_sd, struct pi_sockaddr *addr, int *addrlen)
{
  struct pi_socket *ps, *accept;
  struct cmp c;

  if (!(ps = find_pi_socket(pi_sd))) {
    errno = ESRCH;
    return -1;
  }
  
  accept = malloc(sizeof(struct pi_socket));
  memcpy(accept, ps, sizeof(struct pi_socket));

  if(accept->type == PI_SOCK_STREAM) {
    pi_socket_read(accept, 200);
    if(cmp_rx(accept, &c) < 0)
      goto fail; /* Failed to establish connection, errno already set */
    
    if ((c.version & 0xFF00) == 0x0100) {
      if((unsigned long) accept->establishrate > c.baudrate) {
#ifdef DEBUG
        fprintf(stderr,"Rate %d too high, dropping to %ld\n",ps->establishrate,c.baudrate);
#endif
        accept->establishrate = c.baudrate;
      }
      accept->rate = accept->establishrate;
      accept->version = c.version;
      if(cmp_init(accept, accept->rate)<0)
        goto fail;
      pi_socket_flush(accept);
      if(accept->rate != 9600) {
        pi_device_changebaud(accept);
      }
      accept->connected = 1;
      accept->dlprecord = 0;
    }else {
      cmp_abort(ps, 0x80); /* 0x80 means the comm version wasn't compatible*/

      fprintf(stderr, "pi_socket connection failed due to comm version mismatch\n");
      fprintf(stderr, " (expected version 01xx, got %4.4X)\n", c.version);

      errno = ECONNREFUSED;
      goto fail;
    }
  } else {
    accept->connected = 1;
  }
  
  accept->sd = dup(ps->sd);

  if (!psl) psl = accept;
  else {
    struct pi_socket *p;
    for (p = psl; p->next; p=p->next);
      p->next = accept;
  }
  
  accept->mac->ref++; /* Keep mac around even if the bound socket is closed */
  accept->initiator = 0; /* We accepted the link, we did not initiate it */
  
  return accept->sd;
fail:
  free(accept);
  return -1;
}

/* Send msg on a connected socket */

int pi_send(int pi_sd, void *msg, int len, unsigned int flags)
{
  struct pi_socket *ps;

  if (!(ps = find_pi_socket(pi_sd))) {
    errno = ESRCH;
    return -1;
  }
  
  if (interval)
    alarm(interval);

  if(ps->type == PI_SOCK_STREAM) {
    return padp_tx(ps,msg,len,padData);
  } else {
    return syspkt_tx(ps, msg, len);
  }
}

/* Recv msg on a connected socket */

int pi_recv(int pi_sd, void *msg, int len, unsigned int flags)
{
  struct pi_socket *ps;

  if (!(ps = find_pi_socket(pi_sd))) {
    errno = ESRCH;
    return -1;
  }

  if(ps->type == PI_SOCK_STREAM) {
    return padp_rx(ps,msg,len);
  } else {
    return syspkt_rx(ps,msg,len);
  }
}

/* Wrapper for recv */

int pi_read(int pi_sd, void *msg, int len)
{
  return pi_recv(pi_sd, msg, len, 0);
}

/* Wrapper for send */

int pi_write(int pi_sd, void *msg, int len)
{
  return pi_send(pi_sd, msg, len, 0);
}

/* Tickle a stream connection */

int pi_tickle(int pi_sd)
{
  struct pi_socket *ps;

  if (!(ps = find_pi_socket(pi_sd))) {
    errno = ESRCH;
    return -1;
  }
  
  if(ps->type == PI_SOCK_STREAM) {
    struct padp pd;
    int ret;
    if (ps->busy || !ps->connected)
      return -1;
    pd.type = padTickle;
    pd.flags = 0x00;
    pd.size = 0x00;
    ret = padp_tx(ps, (void *)&pd, 0, padTickle);
    pi_socket_flush(ps);
    return ret;
  }
  else {
    errno = EOPNOTSUPP;
    return -1;
  }
}

RETSIGTYPE pi_onalarm(int);

int pi_watchdog(int pi_sd, int newinterval)
{
  struct pi_socket *ps;

  if (!(ps = find_pi_socket(pi_sd))) {
    errno = ESRCH;
    return -1;
  }
  
  if(ps->type == PI_SOCK_STREAM) {
    ps->tickle = 1;
    signal(SIGALRM, pi_onalarm);
    interval = newinterval;
    alarm(interval);
    return 0;
  } else {
    errno = EOPNOTSUPP;
    return -1;
  }
}

/* Close a connection, destroy the socket */

int pi_close(int pi_sd)
{
  struct pi_socket *ps;
  struct pi_socket *p;
  

  if (!(ps = find_pi_socket(pi_sd))) {
    errno = ESRCH;
    return -1;
  }
  
  busy++;

  if (ps->type == PI_SOCK_STREAM) {
    if (ps->connected & 1) /* If socket is connected */
      if (!(ps->connected & 2)) /* And it wasn't end-of-synced */
        dlp_EndOfSync(pi_sd, 0);  /* then end sync, with clean status */
  }
  
  if(ps->sd && (ps->sd != ps->mac->fd)) /* If device still has a /dev/null handle */
    close(ps->sd); /* Close /dev/null handle */
    
  if(ps->mac->fd) { /* If device was opened */
    if (ps->connected) {
      pi_socket_flush(ps); /* Flush the device, and set baud rate back to the initial setting */
      ps->rate = 9600; 
      pi_device_changebaud(ps);
    }
    if (--(ps->mac->ref) == 0) { /* If no-one is using the device, close it */
      pi_device_close(ps);
      free(ps->mac);
    }
  }

  if (ps == psl) {
    psl = psl->next;
  } else {
    for (p=psl; p; p=p->next) {
      if (ps == p->next) {
        p->next = p->next->next;
	break;
      }
    }
  }
  
  busy--;

  free(ps);
  return 0;
}

/* Install an atexit handler that closes open sockets */

void pi_onexit(void)
{
  struct pi_socket *p, *n;
  
  for (p=psl; p; p=n ) {
    n = p->next;
    pi_close(p->sd);
  }
  
}

RETSIGTYPE pi_onalarm(int signo)
{
  struct pi_socket *p, *n;
  
  signal(SIGALRM,pi_onalarm);
  
  if (busy) {
#ifdef DEBUG
    fprintf(stderr, "world is busy. Rescheduling.\n");
#endif
    alarm(1);
  } else
    for (p=psl; p; p=n ) {
      n = p->next;
      if (p->connected) {
#ifdef DEBUG
        fprintf(stderr, "Tickling socket %d\n", p->sd);
#endif
        if (pi_tickle(p->sd)==-1) {
#ifdef DEBUG
          fprintf(stderr, " but socket is busy. Rescheduling.\n");
#endif
          alarm(1);
        } else
          alarm(interval);
      }
    }
}

void installexit(void)
{
  static installedexit = 0;
  
  if (!installedexit)
    atexit(pi_onexit);
    
  installedexit = 1;
}

/* Get the local address for a socket */

int pi_getsockname(int pi_sd, struct pi_sockaddr * addr, int * namelen)
{
  struct pi_socket *ps;

  if (!(ps = find_pi_socket(pi_sd))) {
    errno = ESRCH;
    return -1;
  }
  
  if(addr)
    *addr = ps->laddr;
  if(namelen)
    *namelen = sizeof(struct pi_sockaddr);
    
  return 0;
}

/* Get the remote address for a socket */

int pi_getsockpeer(int pi_sd, struct pi_sockaddr * addr, int * namelen)
{
  struct pi_socket *ps;

  if (!(ps = find_pi_socket(pi_sd))) {
    errno = ESRCH;
    return -1;
  }
  
  if(addr)
    *addr = ps->raddr;
  if(namelen)
    *namelen = sizeof(struct pi_sockaddr);
    
  return 0;
}

unsigned int pi_version(int pi_sd)
{
  struct pi_socket *ps;

  if (!(ps = find_pi_socket(pi_sd))) {
    errno = ESRCH;
    return -1;
  }
  
  return ps->version;
}

struct pi_socket *find_pi_socket(int sd)
{
  struct pi_socket *p;

  for (p=psl; p; p=p->next) {
    if (p->sd == sd) return p;
  }

  return 0;
}
