#include <stdio.h>
#include <fcntl.h>
#include <sys/time.h>
#include <arp1577/arp1577.h>
#include <host/ip_service.h>

static int arp1577_call_retry();
static int key_gen();
static list arp_clients=0;
static void print_arp1577_message();

/* MARS related function that allows messages to also be directed
 * to the MARS server */
extern void handle_dual_service_message();

static FILE *fp=stderr;
static arp_log=0;
static int ptime=0;
static struct timeval tv;
static struct timezone tz;

typedef struct arp1577_retry {
  layer l;
  pbuffer b;
  int f;
  int count;
  address until_address;
  int to_addr_family;
  int timer;
  arp1577_connection a;
} *arp1577_retry;

static int pending_arp1577_compare(arp1577_retry r, address a)
{
  return address_compare(r->until_address, a);
}

static void arp1577_failure(call c,arp1577_connection a)
{
  register_timer_wait(10000,arp1577_call_retry,a);
}

static int arp1577_call_retry(arp1577_connection a)
{
  create_signalled_atm(a->sw,a->a,a->sa,a->sw->a,0,0,STACK_AAL_5,a->llc,
		       arp1577_failure,a);
  return(0);
}

static int arp1577_to_vince(int a)
{
  switch(a){
  case ETHERTYPE_IP:    return (IP_FAMILY);
  case ETHERTYPE_SPANS: return (SPANS_FAMILY);
  case ETHERTYPE_NSAP:  return (NSAP_FAMILY);
  case TYPE_E164_NSAP:  return (E164_NSAP_FAMILY);
  }
}

static int vince_to_arp(int a)
{
  switch(a){
  case IP_FAMILY:    return (ETHERTYPE_IP);
  case SPANS_FAMILY: return (ETHERTYPE_SPANS);
  case NSAP_FAMILY:  return (ETHERTYPE_NSAP);
  case E164_NSAP_FAMILY: return (TYPE_E164_NSAP);
  }
}

static void kill_retry(arp1577_connection c, address a)
{
  arp1577_retry r;
  if (a){
    debug("arp1577t","Called kill_retry with addr: %s\n", sprint_address(a));
    if (r=table_find(c->pending_arps,a)){
      table_remove(c->pending_arps,a);
      debug("arp1577","Killed Arp Retry: %s\n", sprint_address(a));
      remove_timer(r->timer);
      dereference_buffer(r->b);
      deallocate_memory(r);
    }
    else debug("arp1577","KILL ARP RETRY: no addr\n");
  }
}


static int retry_arp1577_message(arp1577_retry a)
{
  pbuffer b;
  int len;
  if (a->until_address)
    if (!table_find(a->a->pending_arps,a->until_address)) {
      if (locate_translation(a->until_address, a->to_addr_family)) {
	debug("arp1577","found\n");
      }
      remove_timer(a->timer);
      dereference_buffer(a->b);
      deallocate_memory(a);
      /* printf("KILLED RETRY\n");*/
      return 0;
    }
  if ((--a->count >= 0) && (a->a->connected)) {
    /*if (a->until_address)
      debug("arp1577","RETRYING: #%d for trans addr %s\n", 
      a->count, sprint_address(a->until_address));*/
    len=buffer_length(a->b);
    b=allocate_downwards_buffer(a->l,len);
    buffer_copy(b,0,a->b,0,len);
    write_down(a->l,b,a->f);
    dereference_buffer(b);
    if (!a->until_address)
      return(0);
    return(1);
  } else {
    kill_retry(a->a,a->until_address);
    return(0);
  }
}


static void send_arp1577_message(arp1577_connection c,
				 address to_hardware, address to_hw_sub,
				 address to_protocol, address from_hardware, 
				 address from_hw_sub, address from_protocol,
				 int code, int retries)
{
  register s;
  arp1577_retry a=(arp1577_retry)allocate_memory(sizeof(struct arp1577_retry));
  struct arp1577_message k;
  int shlen, sslen, splen, thlen, tslen, tplen;
  int pf;
  pbuffer b;

  s = sizeof(k);
  if (from_hardware) {
    k.shtl=(from_hardware->family==NSAP_FAMILY)?NSAP_TL:E164_TL;
    shlen=address_length(from_hardware);
  } else {
    shlen=0;
    k.shtl=0x40;
  }
  if ((k.shtl==E164_TL) && from_hw_sub) {
    sslen=20;
    k.sstl=NSAP_TL;
  } else {
    sslen=0;
    k.sstl=0x40;
  }
  if (from_protocol) {
    k.spln=splen=address_length(from_protocol);
    pf=vince_to_arp(from_protocol->family);
  } else {
    k.spln=0;
    splen=0;
  }

  if (to_hardware) {
    k.thtl=(to_hardware->family == NSAP_FAMILY)?NSAP_TL:E164_TL;
    thlen=address_length(to_hardware);
  } else {
    k.thtl=0x40;
    thlen=0;
  }
  if ((k.thtl==E164_TL) && to_hw_sub){
    tslen=20;
    k.tstl=NSAP_TL;
  } else {
    tslen=0;
    k.tstl=0x40;
  }
  if (to_protocol) {
    k.tpln=tplen=address_length(to_protocol);
    pf=vince_to_arp(to_protocol->family);
  } else {
    k.tpln=0;
    pf=ETHERTYPE_IP;
    tplen=0;
  }
  
  b=a->b=allocate_downwards_buffer(c->l,s+shlen+sslen+splen+
                                               thlen+tslen+tplen);
  k.hrd=ATMHW;
  k.pro=pf;
  k.op=code;
  buffer_write(b,0,&k,s);

  if (from_hardware)
    buffer_write(b,s,address_contents(from_hardware),shlen);
  if (from_hw_sub)
    buffer_write(b,s+shlen,address_contents(from_hw_sub),sslen);
  if (from_protocol)
    buffer_write(b,s+shlen+sslen,address_contents(from_protocol),
    splen);

  if (to_hardware)
    buffer_write(b,s+shlen+sslen+splen,
                 address_contents(to_hardware),thlen);
  if (to_hw_sub)
    buffer_write(b,s+shlen+sslen+splen+thlen,
                 address_contents(to_hardware),tslen);

  if (to_protocol)
    buffer_write(b,s+shlen+sslen+splen+thlen+tslen,
		 address_contents(to_protocol),tplen);

  a->l=c->l;
  a->count=retries;
  a->until_address=0;
  a->to_addr_family=0;
  if (code==inarp_request) {
    a->until_address=to_hardware;
    a->to_addr_family=IP_FAMILY;
  } else  if (code==arp_request) {
    a->until_address=to_protocol;
    a->to_addr_family=NSAP_FAMILY;
  }
  /*if (debug("arp1577","Snd> ") && (!arp_log)) {
    print_arp1577_message(b);
  }*/
  a->f=0;
  a->a=c;
  if (a->until_address) table_insert(c->pending_arps,a,a->until_address);
  a->timer=register_timer(5000,retry_arp1577_message,a);
}


void handle_arp1577_message(layer l, pbuffer b, int f)
{
  register int ss=0;
  arp1577_connection a=(arp1577_connection)(l->state);
  address to_hardware=0, to_hw_sub=0, to_protocol=0;
  address from_hardware=0, from_hw_sub=0, from_protocol=0, t;
  struct arp1577_message k;
  char *protocol_temp,*hardware_temp, *hw_sub_temp;
  int h_family, hs_family, p_family;
  int hlen,hslen,plen;


  if (f & PROTOCOL_CONNECTED) { 
    a->connected=1;
    printf("Sending InArp to %s\n",sprint_address(a->remote_hardware_address));
    if (a->s) send_arp1577_message(a,
			       a->remote_hardware_address,0,0,  /*no subaddrs*/
			       a->s->hardware_address,0,a->s->protocol_address,
			       inarp_request,2);
  }
  if (b){
    buffer_read(b,0,&k,ss=sizeof(struct arp1577_message));
    hlen=(int)(k.shtl & 0x3f);
    hslen=(int)(k.sstl & 0x3f);
    plen=(int)k.spln;
    
    hardware_temp=(char *)allocate_memory(hlen);
    hw_sub_temp=(char *)allocate_memory(hslen);
    protocol_temp=(char *)allocate_memory(plen);
    
    if (k.shtl & 0x40)
      h_family=E164_NSAP_FAMILY;
    else
      h_family=arp1577_to_vince(k.hrd);
    if (hslen)
      hs_family=NSAP_FAMILY;
    p_family=arp1577_to_vince(k.pro);

    if (hlen) {
      buffer_read(b,ss,hardware_temp,hlen);
      from_hardware=create_address(h_family,hlen,hardware_temp);
    }
    if (hslen) {
      buffer_read(b,ss+hlen,hw_sub_temp,hslen);
      from_hw_sub=create_address(hs_family,hslen,hw_sub_temp);
    }
    if (plen) {
      buffer_read(b,ss=(ss+hlen+hslen),protocol_temp,plen);
      from_protocol=create_address(p_family,plen,protocol_temp);
    }
    ss+=plen;

    if (hlen=(int)(k.thtl & 0x3f)) {
      buffer_read(b,ss,hardware_temp,hlen);
      to_hardware=create_address(h_family,hlen,hardware_temp);
    }
    if (hslen=(int)(k.tstl & 0x3f)) {
      buffer_read(b,ss+hlen,hw_sub_temp,hslen);
      to_hw_sub=create_address(hs_family,hslen,hw_sub_temp);
    }
    if (plen=(int)k.tpln) {
      buffer_read(b,ss+hlen+hslen,protocol_temp,plen);
      to_protocol=create_address(p_family,plen,protocol_temp);
    }
    if (arp_log) {
      gettimeofday(&tv, &tz);
      fprintf(fp, "[%9.1f] Rcvd> ", (float)((tv.tv_sec-ptime)/60));
      print_arp1577_message(b);
    } else if (debug("arp1577","Rcvd> ")) {
      print_arp1577_message(b);
    }
    switch(k.op){
    case arp_request:
      if (!(t=find_translation(to_protocol,h_family)))
	t=find_translation(find_translation(to_protocol,SWITCH_NAME_FAMILY),
			   h_family);
      if (t) {
	send_arp1577_message(a,from_hardware,from_hw_sub,from_protocol,
			     t,to_hw_sub,to_protocol,arp_reply,5);
      } else {
	debug ("arp1577","Received Inarp_Request: no NSAP address for %s\n",
	       sprint_address(to_protocol));
	send_arp1577_message(a,to_hardware,to_hw_sub,to_protocol,
			     from_hardware,from_hw_sub,from_protocol,
			     arp_nak,1);
      }
      break;
    case arp_reply:
      add_translation(from_protocol,from_hardware);
      add_translation(from_hardware,from_protocol);
      kill_retry(a,from_protocol);
      break;
    case inarp_request:
      /* Make use of the address information in the InArp message */
      if (from_protocol) {
	  add_translation(from_protocol,from_hardware);
	  add_translation(from_hardware,from_protocol);
	  kill_retry(a,from_protocol);
      }
      if (!(t=find_translation
	    (find_translation(to_hardware,SWITCH_NAME_FAMILY),p_family)))
	t=find_translation(to_hardware,p_family);
      if (t){
	send_arp1577_message(a,from_hardware,from_hw_sub,from_protocol,
			     to_hardware,to_hw_sub,t,inarp_reply,5);
      } else {
	debug("arp1577","Received Inarp_Request: no IP address for %s\n",
	      sprint_address(to_hardware));
      	send_arp1577_message(a,to_hardware,to_hw_sub,to_protocol,
			     from_hardware,from_hw_sub,from_protocol,
			     arp_nak,1);
      }
      break;
    case inarp_reply:
      add_translation(from_protocol,from_hardware);
      add_translation(from_hardware,from_protocol);
      kill_retry(a,from_hardware);
      break;
    case arp_nak:
      if (from_protocol) kill_retry(a,from_protocol);  
      if (from_hardware) kill_retry(a,from_hardware);  
      break;
    }
    deallocate_memory(protocol_temp);
    deallocate_memory(hardware_temp);
  }
}


address arp1577_translation(address from,int to)
{
  arp1577_connection c;
  list i;
  dolist(i,arp_clients){
    c=(arp1577_connection)i->first;
    if (find_translation(c->sw->a,from->family))
      switch(from->family){
      case IP_FAMILY:
	send_arp1577_message(c,0,0,from,
			     find_translation(c->sw->a,to),0,
			     find_translation(c->sw->a,from->family),
			     arp_request,5);
	break;
      case NSAP_FAMILY:
	send_arp1577_message(c,from,0,0,
			     find_translation(c->sw->a,from->family),0,
			     find_translation(c->sw->a,to),
			     inarp_request,5);
	break;
      }
  }
  return(0);
}

static layer new_arp1577_connection(call_tail t, arp1577_server s)
{
  arp1577_connection c=
    (arp1577_connection)allocate_memory(sizeof(struct arp1577_connection));
  c->remote_hardware_address=t->c->source;
  c->s=s;
  c->sw=t->c->s;
  c->pending_arps=create_table(pending_arp1577_compare,key_from_address);
  assemble_stack(2,c->l=create_layer(c,0,handle_arp1577_message,0),
		 add_llc_snap(c->llc=create_llc(),0,0x806));
  return(c->llc);
}

int key_gen(address a)
{
  int s=0, i;
  /*   for(i=0; i < a->length; i++) s+=(int)a->contents[i];*/
  return s;
}

void create_arp1577_server(virtual_switch sw,address sa,address ha,address pa)
{
  arp1577_server s=
    (arp1577_server)allocate_memory(sizeof(struct arp1577_server));
  s->hardware_address=ha;
  s->protocol_address=pa;
  s->sw=sw;
  bind_service(sw,sa,STACK_AAL_5,new_arp1577_connection,s);
}

void create_arp1577_client(virtual_switch sw, address a, address sap)
{
  arp1577_connection c=
    (arp1577_connection)allocate_memory(sizeof(struct arp1577_connection));
  add_translation_function(NSAP_FAMILY,IP_FAMILY,arp1577_translation);
  add_translation_function(IP_FAMILY,NSAP_FAMILY,arp1577_translation);
  arp_clients=insert(c,arp_clients);
  c->sw=sw;
  c->a=a;
  c->s=0;
  c->sa=sap;
  c->connected=0;
  assemble_stack(2,c->l=create_layer(c,0,handle_arp1577_message,0),
		 add_llc_snap(c->llc=create_llc(),0,0x806));
  c->pending_arps=create_table(pending_arp1577_compare,key_from_address);
  create_signalled_atm(c->sw,c->a,c->sa,c->sw->a,0,0,
                       STACK_AAL_5|STACK_AGGRESSIVE_SETUP,c->llc,
                       arp1577_failure,c);
}


static void print_arp1577_message(pbuffer b)
{
  register int s,ss=0;
  address to_hardware, to_hw_sub, to_protocol;
  address from_hardware,from_hw_sub, from_protocol;
  struct arp1577_message k;
  char *protocol_temp=0,*hardware_temp=0, *hw_sub_temp=0;
  int h_family, hs_family, p_family;
  int hlen,hslen,plen;

  s = sizeof(k);
  buffer_read(b,0,&k,sizeof(struct arp1577_message));
  hlen=(int)(k.shtl & 0x3f);
  hslen=(int)(k.sstl & 0x3f);
  plen=(int)k.spln;
  
  switch(k.op){
  case arp_request: fprintf(fp,"Arp_Rqst\n"); break;
  case arp_reply: fprintf(fp,"Arp_Repl\n"); break;
  case inarp_request: fprintf(fp,"InArp_Rqst\n"); break;
  case inarp_reply: fprintf(fp,"InArp_Repl\n"); break;
  case arp_nak: fprintf(fp,"Arp_Nak\n"); break;
  }
  
  if (k.shtl & 0x40)
    h_family=E164_NSAP_FAMILY;
  else
    h_family=arp1577_to_vince(k.hrd);
  if (hslen)
    hs_family=NSAP_FAMILY;
  p_family=arp1577_to_vince(k.pro);
  
  if (hlen) {
    hardware_temp=(char *)allocate_memory(hlen);
    buffer_read(b,s,hardware_temp,hlen);
    from_hardware=create_address(h_family,hlen,hardware_temp);
    fprintf(fp," frm hw: %s\n",sprint_address(from_hardware));
  }
  if (hslen) {
    hw_sub_temp=(char *)allocate_memory(hslen);
    buffer_read(b,s+hlen,hw_sub_temp,hslen);
    from_hw_sub=create_address(hs_family,hslen,hw_sub_temp);
    fprintf(fp," frm hw_s: %s\n",sprint_address(from_hw_sub));
  }
  if (plen) {
    protocol_temp=(char *)allocate_memory(plen);
    buffer_read(b,ss=(s+hlen+hslen),protocol_temp,plen);
    from_protocol=create_address(p_family,plen,protocol_temp);
    fprintf(fp," frm pro: %s\n",sprint_address(from_protocol));
  }    
  ss+=plen;
  hlen=(int)(k.thtl & 0x3f);
  hslen=(int)(k.tstl & 0x3f);
  plen=(int)k.tpln;
  
  if (hlen) {
    if (!hardware_temp) hardware_temp=(char *)allocate_memory(hlen);
    buffer_read(b,ss,hardware_temp,hlen);
     to_hardware=create_address(h_family,hlen,hardware_temp);
    fprintf(fp," to hw: %s\n",sprint_address(to_hardware));
  }
  if (hslen) {
    if (!hw_sub_temp) hw_sub_temp=(char *)allocate_memory(hslen);
    buffer_read(b,ss+hlen,hw_sub_temp,hslen);
    to_hw_sub=create_address(hs_family,hslen,hw_sub_temp);
    fprintf(fp," to hw_s: %s\n",sprint_address(to_hw_sub));
  }
  if (plen) {
    if (!protocol_temp) protocol_temp=(char *)allocate_memory(plen);
    buffer_read(b,ss+hlen+hslen,protocol_temp,plen);
    to_protocol=create_address(p_family,plen,protocol_temp);
    fprintf(fp," to pro: %s\n",sprint_address(to_protocol));
  }
  fflush(fp);
  if (protocol_temp) deallocate_memory(protocol_temp);
  if (hw_sub_temp) deallocate_memory(hw_sub_temp);
  if (hardware_temp) deallocate_memory(hardware_temp);
}

arp1577_server allocate_arp1577_server(ip_service i,address ha,address pa)
{
  arp1577_server s=
      (arp1577_server)allocate_memory(sizeof(struct arp1577_server));
  s->hardware_address=ha;
  s->protocol_address=pa;
  s->sw=i->s;
  return s;
}

log_arp_experiment(char *fname)
{
  if ((fp = fopen(fname, "w")) == 0) {
     printf("arp_1577: error in opening output file %s\n", fname);
     fp = stderr;
  }
  gettimeofday(&tv, &tz);
  fprintf(fp,"Begin time: %d\n", ptime=tv.tv_sec);
  fflush(fp);
  arp_log++;
}

#ifdef HOST
static layer new_llc_1577_client_connection(address a, ip_service i)
{
  arp1577_connection new=
    (arp1577_connection)allocate_memory(sizeof(struct arp1577_connection));
  add_translation_function(NSAP_FAMILY,IP_FAMILY,arp1577_translation);
  add_translation_function(IP_FAMILY,NSAP_FAMILY,arp1577_translation);
  new->sw=i->s;
  new->a=a;
  new->remote_hardware_address=a;
  new->s=0;
  /* not setting service address...not applicable
     might want to consider having client, server, and connection*/
  new->connected=0;
  new->pending_arps=create_table(pending_arp1577_compare,key_from_address);
  return(new->l=create_layer(new,0,handle_arp1577_message,0));
}

layer new_llc_1577_server_connection(address a,arp1577_server s)
{
  arp1577_connection new=
    (arp1577_connection)allocate_memory(sizeof(struct arp1577_connection));
  if (arp_log) {
      fprintf(fp, "SETUP: %s\n", sprint_address(a));
  }
  add_translation_function(NSAP_FAMILY,IP_FAMILY,arp1577_translation);
  add_translation_function(IP_FAMILY,NSAP_FAMILY,arp1577_translation);
  new->sw=s->sw;
  new->a=a;
  new->remote_hardware_address=a;
  new->s=s;
  /* not setting service address...not applicable
     might want to consider having client, server, and connection*/
  new->connected=0;
  new->pending_arps=create_table(pending_arp1577_compare,key_from_address);
  if (s->mars_server) {
      return(new->l=create_layer(new,0,handle_dual_service_message,0));
  } else {
      return(new->l=create_layer(new,0,handle_arp1577_message,0));
  }
}

void register_llc_arp1577_client(ip_service i, address a)
{
  host_add_llc_protocol(i,0x0806,new_llc_1577_client_connection,i,0);
  /* layer(last argument) here is a little difficult*/
  ip_service_add_ip_vc(i,a,i->s->a,0);
}

void register_llc_arp1577_server(ip_service i,address ha,address pa)
{
  arp1577_server s=
      (arp1577_server)allocate_memory(sizeof(struct arp1577_server));
  s->hardware_address=ha;
  s->protocol_address=pa;
  s->sw=i->s;
  s->mars_server = 0;
  host_add_llc_protocol(i,0x0806,new_llc_1577_server_connection,s,0);
}

#endif



