/*
 * Cisco router simulation platform.
 * Copyright (c) 2005,2006 Christophe Fillot (cf@utc.fr)
 *
 * ATM utility functions and Virtual ATM switch.
 *
 * HEC and AAL5 CRC computation functions are from Charles Michael Heard
 * and can be found at (no licence specified, this is to check!):
 *
 *    http://cell-relay.indiana.edu/cell-relay/publications/software/CRC/
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>

#include "utils.h"
#include "registry.h"
#include "atm.h"
#include "net_io.h"

/* RFC1483 bridged mode header */
m_uint8_t atm_rfc1483b_header[ATM_RFC1483B_HLEN] = { 
   0xaa, 0xaa, 0x03, 0x00, 0x80, 0xc2, 0x00, 0x07, 0x00, 0x00,
};

/********************************************************************/
#define HEC_GENERATOR   0x107               /* x^8 + x^2 +  x  + 1  */
#define COSET_LEADER    0x055               /* x^6 + x^4 + x^2 + 1  */

m_uint8_t hec_syndrome_table[256];

/* Generate a table of CRC-8 syndromes for all possible input bytes */
static void gen_syndrome_table(void)
{
   int i,j,syndrome;

   for(i=0;i<256;i++) {
      syndrome = i;
      
      for(j=0;j<8;j++) {
         if (syndrome & 0x80)
            syndrome = (syndrome << 1) ^ HEC_GENERATOR;
         else
            syndrome = (syndrome << 1);
      }
      hec_syndrome_table[i] = (unsigned char)syndrome;
   }
}

/* Compute HEC field for ATM header */
m_uint8_t atm_compute_hec(m_uint8_t *cell_header)
{
   register m_uint8_t hec_accum = 0;
   register int i;

   /* 
    * calculate CRC-8 remainder over first four bytes of cell header.
    * exclusive-or with coset leader & insert into fifth header byte.
    */
   for(i=0;i<4;i++)
      hec_accum = hec_syndrome_table[hec_accum ^ cell_header[i]];
   
   return(hec_accum ^ COSET_LEADER);
}

/* Insert HEC field into an ATM header */
void atm_insert_hec(m_uint8_t *cell_header)
{
   cell_header[4] = atm_compute_hec(cell_header);
}

/* Initialize ATM code (for HEC checksums) */
void atm_init(void)
{
   gen_syndrome_table();
}

/* VPC hash function */
static inline u_int atmsw_vpc_hash(u_int vpi)
{
   return((vpi ^ (vpi >> 8)) & (ATMSW_VP_HASH_SIZE-1));
}

/* VCC hash function */
static inline u_int atmsw_vcc_hash(u_int vpi,u_int vci)
{
   return((vpi ^ vci) & (ATMSW_VC_HASH_SIZE-1));
}

/* VP lookup */
atmsw_vp_conn_t *atmsw_vp_lookup(atmsw_table_t *t,netio_desc_t *input,
                                 u_int vpi)
{
   atmsw_vp_conn_t *swc;
   
   for(swc=t->vp_table[atmsw_vpc_hash(vpi)];swc;swc=swc->next)
      if ((swc->input == input) && (swc->vpi_in == vpi))
         return swc;

   return NULL;
}

/* VC lookup */
atmsw_vc_conn_t *atmsw_vc_lookup(atmsw_table_t *t,netio_desc_t *input,
                                 u_int vpi,u_int vci)
{
   atmsw_vc_conn_t *swc;

   for(swc=t->vc_table[atmsw_vcc_hash(vpi,vci)];swc;swc=swc->next)
      if ((swc->input == input) && (swc->vpi_in == vpi) && 
          (swc->vci_in == vci))
         return swc;

   return NULL;
}

/* VP switching */
void atmsw_vp_switch(atmsw_vp_conn_t *vpc,m_uint8_t *cell)
{
   m_uint32_t atm_hdr;

   /* rewrite the atm header with new vpi */
   atm_hdr =  m_ntoh32(cell);
   atm_hdr &= ~ATM_HDR_VPI_MASK;
   atm_hdr |= vpc->vpi_out << ATM_HDR_VPI_SHIFT;
   m_hton32(cell,atm_hdr);

   /* recompute HEC field */
   atm_insert_hec(cell);

   /* update the statistics counter */
   vpc->cell_cnt++;
}

/* VC switching */
void atmsw_vc_switch(atmsw_vc_conn_t *vcc,m_uint8_t *cell)
{
   m_uint32_t atm_hdr;

   /* rewrite the atm header with new vpi/vci */
   atm_hdr = m_ntoh32(cell);

   atm_hdr &= ~(ATM_HDR_VPI_MASK|ATM_HDR_VCI_MASK);
   atm_hdr |= vcc->vpi_out << ATM_HDR_VPI_SHIFT;
   atm_hdr |= vcc->vci_out << ATM_HDR_VCI_SHIFT;
   m_hton32(cell,atm_hdr);

   /* recompute HEC field */
   atm_insert_hec(cell);

   /* update the statistics counter */
   vcc->cell_cnt++;
}

/* Handle an ATM cell */
ssize_t atmsw_handle_cell(atmsw_table_t *t,netio_desc_t *input,
                          m_uint8_t *cell)
{
   m_uint32_t atm_hdr,vpi,vci;
   netio_desc_t *output = NULL;
   atmsw_vp_conn_t *vpc;
   atmsw_vc_conn_t *vcc;
   ssize_t len;

   /* Extract VPI/VCI information */
   atm_hdr = m_ntoh32(cell);

   vpi = (atm_hdr & ATM_HDR_VPI_MASK) >> ATM_HDR_VPI_SHIFT;
   vci = (atm_hdr & ATM_HDR_VCI_MASK) >> ATM_HDR_VCI_SHIFT;

   /* VP switching */
   if ((vpc = atmsw_vp_lookup(t,input,vpi)) != NULL) {
      atmsw_vp_switch(vpc,cell);
      output = vpc->output;
   } else {  
      /* VC switching */
      if ((vcc = atmsw_vc_lookup(t,input,vpi,vci)) != NULL) {
         atmsw_vc_switch(vcc,cell);
         output = vcc->output;
      }
   }

   len = netio_send(output,cell,ATM_CELL_SIZE);
   
   if (len != ATM_CELL_SIZE) {
      t->cell_drop++;
      return(-1);
   }

   return(0);
}

/* Receive an ATM cell */
static int atmsw_recv_cell(netio_desc_t *nio,u_char *atm_cell,ssize_t cell_len,
                           atmsw_table_t *t)
{
   int res;

   if (cell_len != ATM_CELL_SIZE)
      return(-1);

   ATMSW_LOCK(t);
   res = atmsw_handle_cell(t,nio,atm_cell);
   ATMSW_UNLOCK(t);
   return(res);
}

/* Acquire a reference to an ATM switch (increment reference count) */
atmsw_table_t *atmsw_acquire(char *name)
{
   return(registry_find(name,OBJ_TYPE_ATMSW));
}

/* Release an ATM switch (decrement reference count) */
int atmsw_release(char *name)
{
   return(registry_unref(name,OBJ_TYPE_ATMSW));
}

/* Create a virtual switch table */
atmsw_table_t *atmsw_create_table(char *name)
{
   atmsw_table_t *t;

   /* Allocate a new switch structure */
   if (!(t = malloc(sizeof(*t))))
      return NULL;

   memset(t,0,sizeof(*t));
   pthread_mutex_init(&t->lock,NULL);
   mp_create_fixed_pool(&t->mp,"ATM Switch");

   if (!(t->name = mp_strdup(&t->mp,name)))
      goto err_name;

   /* Record this object in registry */
   if (registry_add(t->name,OBJ_TYPE_ATMSW,t) == -1) {
      fprintf(stderr,"atmsw_create_table: unable to create switch '%s'\n",
              name);
      goto err_reg;
   }

   return t;

 err_reg:
 err_name:
   mp_free_pool(&t->mp);
   free(t);
   return NULL;
}

/* Free resources used by a VPC */
static void atmsw_release_vpc(atmsw_vp_conn_t *swc)
{
   if (swc) {
      /* release input NIO */
      if (swc->input) {
         netio_rxl_remove(swc->input);
         netio_release(swc->input->name);
      }

      /* release output NIO */
      if (swc->output) 
         netio_release(swc->output->name);
   }
}

/* Free resources used by a VCC */
static void atmsw_release_vcc(atmsw_vc_conn_t *swc)
{
   if (swc) {
      /* release input NIO */
      if (swc->input) {
         netio_rxl_remove(swc->input);
         netio_release(swc->input->name);
      }

      /* release output NIO */
      if (swc->output) 
         netio_release(swc->output->name);
   }
}

/* Create a VP switch connection */
int atmsw_create_vpc(atmsw_table_t *t,char *nio_input,u_int vpi_in,
                     char *nio_output,u_int vpi_out)
{
   atmsw_vp_conn_t *swc;
   u_int hbucket;

   ATMSW_LOCK(t);

   /* Allocate a new switch connection */
   if (!(swc = mp_alloc(&t->mp,sizeof(*swc)))) {
      ATMSW_UNLOCK(t);
      return(-1);
   }

   swc->input   = netio_acquire(nio_input);
   swc->output  = netio_acquire(nio_output);
   swc->vpi_in  = vpi_in;
   swc->vpi_out = vpi_out;

   /* Check these NIOs are valid and the input VPI does not exists */
   if (!swc->input || !swc->output || atmsw_vp_lookup(t,swc->input,vpi_in))
      goto error;

   /* Add as a RX listener */
   if (netio_rxl_add(swc->input,(netio_rx_handler_t)atmsw_recv_cell,
                     t,NULL) == -1)
      goto error;

   hbucket = atmsw_vpc_hash(vpi_in);
   swc->next = t->vp_table[hbucket];
   t->vp_table[hbucket] = swc;
   ATMSW_UNLOCK(t);
   return(0);

 error:
   ATMSW_UNLOCK(t);
   atmsw_release_vpc(swc);
   mp_free(swc);
   return(-1);
}

/* Delete a VP switch connection */
int atmsw_delete_vpc(atmsw_table_t *t,char *nio_input,u_int vpi_in,
                     char *nio_output,u_int vpi_out)
{   
   netio_desc_t *input,*output;
   atmsw_vp_conn_t **swc,*p;
   u_int hbucket;

   ATMSW_LOCK(t);

   input = registry_exists(nio_input,OBJ_TYPE_NIO);
   output = registry_exists(nio_output,OBJ_TYPE_NIO);

   if (!input || !output) {
      ATMSW_UNLOCK(t);
      return(-1);
   }

   hbucket = atmsw_vpc_hash(vpi_in);
   for(swc=&t->vp_table[hbucket];*swc;swc=&(*swc)->next) 
   {
      p = *swc;

      if ((p->input == input) && (p->output == output) &&
          (p->vpi_in == vpi_in) && (p->vpi_out == vpi_out))
      {
         /* found a matching VP, remove it */
         *swc = (*swc)->next;
         ATMSW_UNLOCK(t);

         atmsw_release_vpc(p);
         mp_free(p);
         return(0);
      }
   }

   ATMSW_UNLOCK(t);
   return(-1);
}

/* Create a VC switch connection */
int atmsw_create_vcc(atmsw_table_t *t,
                     char *input,u_int vpi_in,u_int vci_in,
                     char *output,u_int vpi_out,u_int vci_out)
{
   atmsw_vc_conn_t *swc;
   u_int hbucket;

   ATMSW_LOCK(t);

   /* Allocate a new switch connection */
   if (!(swc = mp_alloc(&t->mp,sizeof(*swc)))) {
      ATMSW_UNLOCK(t);
      return(-1);
   }

   swc->input   = netio_acquire(input);
   swc->output  = netio_acquire(output);
   swc->vpi_in  = vpi_in;
   swc->vci_in  = vci_in;
   swc->vpi_out = vpi_out;
   swc->vci_out = vci_out;

   /* Ensure that there is not already VP switching */
   if (atmsw_vp_lookup(t,swc->input,vpi_in) != NULL) {
      fprintf(stderr,"atmsw_create_vcc: VP switching already exists for "
              "VPI=%u\n",vpi_in);
      goto error;
   }

   /* Check these NIOs are valid and the input VPI does not exists */
   if (!swc->input || !swc->output || 
       atmsw_vc_lookup(t,swc->input,vpi_in,vci_in)) 
      goto error;

   /* Add as a RX listener */
   if (netio_rxl_add(swc->input,(netio_rx_handler_t)atmsw_recv_cell,
                     t,NULL) == -1)
      goto error;

   hbucket = atmsw_vcc_hash(vpi_in,vci_in);
   swc->next = t->vc_table[hbucket];
   t->vc_table[hbucket] = swc;
   ATMSW_UNLOCK(t);
   return(0);

 error:
   ATMSW_UNLOCK(t);
   atmsw_release_vcc(swc);
   mp_free(swc);
   return(-1);
}

/* Delete a VC switch connection */
int atmsw_delete_vcc(atmsw_table_t *t,
                     char *nio_input,u_int vpi_in,u_int vci_in,
                     char *nio_output,u_int vpi_out,u_int vci_out)
{  
   netio_desc_t *input,*output;
   atmsw_vc_conn_t **swc,*p;
   u_int hbucket;

   ATMSW_LOCK(t);

   input = registry_exists(nio_input,OBJ_TYPE_NIO);
   output = registry_exists(nio_output,OBJ_TYPE_NIO);

   hbucket = atmsw_vcc_hash(vpi_in,vci_in);
   for(swc=&t->vc_table[hbucket];*swc;swc=&(*swc)->next) 
   {
      p = *swc;

      if ((p->input == input) && (p->output == output) &&
          (p->vpi_in == vpi_in) && (p->vci_in == vci_in) &&
          (p->vpi_out == vpi_out) && (p->vci_out == vci_out))
      {
         /* found a matching VP, remove it */
         *swc = (*swc)->next;
         ATMSW_UNLOCK(t);

         atmsw_release_vcc(p);
         mp_free(p);
         return(0);
      }
   }

   ATMSW_UNLOCK(t);
   return(-1);
}

/* Free resources used by an ATM switch */
static int atmsw_free(void *data,void *arg)
{
   atmsw_table_t *t = data;
   atmsw_vp_conn_t *vp;
   atmsw_vc_conn_t *vc;
   int i;

   /* Remove all VPs */
   for(i=0;i<ATMSW_VP_HASH_SIZE;i++)
      for(vp=t->vp_table[i];vp;vp=vp->next)
         atmsw_release_vpc(vp);

   /* Remove all VCs */
   for(i=0;i<ATMSW_VC_HASH_SIZE;i++)
      for(vc=t->vc_table[i];vc;vc=vc->next)
         atmsw_release_vcc(vc);

   mp_free_pool(&t->mp);
   free(t);
   return(TRUE);
}

/* Delete an ATM switch */
int atmsw_delete(char *name)
{
   return(registry_delete_if_unused(name,OBJ_TYPE_ATMSW,atmsw_free,NULL));
}

/* Delete all ATM switches */
int atmsw_delete_all(void)
{
   return(registry_delete_type(OBJ_TYPE_ATMSW,atmsw_free,NULL));
}

/* Save the configuration of an ATM switch */
void atmsw_save_config(atmsw_table_t *t,FILE *fd)
{
   atmsw_vp_conn_t *vp;
   atmsw_vc_conn_t *vc;
   int i;

   fprintf(fd,"atmsw create %s\n",t->name);

   ATMSW_LOCK(t);

   for(i=0;i<ATMSW_VP_HASH_SIZE;i++) {
      for(vp=t->vp_table[i];vp;vp=vp->next) {
         fprintf(fd,"atmsw create_vpc %s %s %u %s %u\n",
                 t->name,vp->input->name,vp->vpi_in,
                 vp->output->name,vp->vpi_out);
      }
   }

   for(i=0;i<ATMSW_VC_HASH_SIZE;i++) {
      for(vc=t->vc_table[i];vc;vc=vc->next) {
         fprintf(fd,"atmsw create_vcc %s %s %u %u %s %u %u\n",
                 t->name,vc->input->name,vc->vpi_in,vc->vci_in,
                 vc->output->name,vc->vpi_out,vc->vci_out);
      }
   }
   
   ATMSW_UNLOCK(t);

   fprintf(fd,"\n");
}

/* Save configurations of all ATM switches */
static void atmsw_reg_save_config(registry_entry_t *entry,void *opt,int *err)
{
   atmsw_save_config((atmsw_table_t *)entry->data,(FILE *)opt);
}

void atmsw_save_config_all(FILE *fd)
{
   registry_foreach_type(OBJ_TYPE_ATMSW,atmsw_reg_save_config,fd,NULL);
}

/* Create a new interface */
int atmsw_cfg_create_if(atmsw_table_t *t,char **tokens,int count)
{
   netio_desc_t *nio = NULL;
   int nio_type;

   /* at least: IF, interface name, NetIO type */
   if (count < 3) {
      fprintf(stderr,"atmsw_cfg_create_if: invalid interface description\n");
      return(-1);
   }
   
   nio_type = netio_get_type(tokens[2]);
   switch(nio_type) {
      case NETIO_TYPE_UNIX:
         if (count != 5) {
            fprintf(stderr,"ATMSW: invalid number of arguments "
                    "for UNIX NIO '%s'\n",tokens[1]);
            break;
         }

         nio = netio_desc_create_unix(tokens[1],tokens[3],tokens[4]);
         break;

      case NETIO_TYPE_UDP:
         if (count != 6) {
            fprintf(stderr,"ATMSW: invalid number of arguments "
                    "for UDP NIO '%s'\n",tokens[1]);
            break;
         }

         nio = netio_desc_create_udp(tokens[1],atoi(tokens[3]),
                                     tokens[4],atoi(tokens[5]));
         break;

      case NETIO_TYPE_TCP_CLI:
         if (count != 5) {
            fprintf(stderr,"ATMSW: invalid number of arguments "
                    "for TCP CLI NIO '%s'\n",tokens[1]);
            break;
         }

         nio = netio_desc_create_tcp_cli(tokens[1],tokens[3],tokens[4]);
         break;

      case NETIO_TYPE_TCP_SER:
         if (count != 4) {
            fprintf(stderr,"ATMSW: invalid number of arguments "
                    "for TCP SER NIO '%s'\n",tokens[1]);
            break;
         }

         nio = netio_desc_create_tcp_ser(tokens[1],tokens[3]);
         break;

      default:
         fprintf(stderr,"ATMSW: unknown/invalid NETIO type '%s'\n",
                 tokens[2]);
   }

   if (!nio) {
      fprintf(stderr,"ATMSW: unable to create NETIO descriptor of "
              "interface %s\n",tokens[1]);
      return(-1);
   }

   netio_release(nio->name);
   return(0);
}

/* Create a new Virtual Path Connection */
int atmsw_cfg_create_vpc(atmsw_table_t *t,char **tokens,int count)
{
   /* 5 parameters: "VP", InputIF, InVPI, OutputIF, OutVPI */
   if (count != 5) {
      fprintf(stderr,"ATMSW: invalid VPC descriptor.\n");
      return(-1);
   }

   return(atmsw_create_vpc(t,tokens[1],atoi(tokens[2]),
                           tokens[3],atoi(tokens[4])));
}

/* Create a new Virtual Channel Connection */
int atmsw_cfg_create_vcc(atmsw_table_t *t,char **tokens,int count)
{
   /* 7 parameters: "VP", InputIF, InVPI/VCI, OutputIF, OutVPI/VCI */
   if (count != 7) {
      fprintf(stderr,"ATMSW: invalid VCC descriptor.\n");
      return(-1);
   }

   return(atmsw_create_vcc(t,tokens[1],atoi(tokens[2]),atoi(tokens[3]),
                           tokens[4],atoi(tokens[5]),atoi(tokens[6])));
}

#define ATMSW_MAX_TOKENS  16

/* Handle an ATMSW configuration line */
int atmsw_handle_cfg_line(atmsw_table_t *t,char *str)
{  
   char *tokens[ATMSW_MAX_TOKENS];
   int count;

   if ((count = m_strsplit(str,':',tokens,ATMSW_MAX_TOKENS)) <= 1)
      return(-1);

   if (!strcmp(tokens[0],"IF"))
      return(atmsw_cfg_create_if(t,tokens,count));
   else if (!strcmp(tokens[0],"VP"))
      return(atmsw_cfg_create_vpc(t,tokens,count));
   else if (!strcmp(tokens[0],"VC"))
      return(atmsw_cfg_create_vcc(t,tokens,count));

   fprintf(stderr,"ATMSW: Unknown statement \"%s\" (allowed: IF,VP,VC)\n",
           tokens[0]);
   return(-1);
}

/* Read an ATMSW configuration file */
int atmsw_read_cfg_file(atmsw_table_t *t,char *filename)
{
   char buffer[1024],*ptr;
   FILE *fd;

   if (!(fd = fopen(filename,"r"))) {
      perror("fopen");
      return(-1);
   }
   
   while(!feof(fd)) {
      if (!fgets(buffer,sizeof(buffer),fd))
         break;
      
      /* skip comments and end of line */
      if ((ptr = strpbrk(buffer,"#\r\n")) != NULL)
         *ptr = 0;

      /* analyze non-empty lines */
      if (strchr(buffer,':'))
         atmsw_handle_cfg_line(t,buffer);
   }
   
   fclose(fd);
   return(0);
}

/* Start a virtual ATM switch */
int atmsw_start(char *filename)
{
   atmsw_table_t *t;

   if (!(t = atmsw_create_table("default"))) {
      fprintf(stderr,"ATMSW: unable to create virtual fabric table.\n");
      return(-1);
   }

   if (atmsw_read_cfg_file(t,filename) == -1) {
      fprintf(stderr,"ATMSW: unable to parse configuration file.\n");
      return(-1);
   }

   atmsw_release("default");
   return(0);
}
