#include <stdio.h>
#include <ctype.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/param.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include "inet-udp.h"
#include "Connect.h"

#ifndef NULL
#define NULL 0
#endif

extern char *malloc();

Trap udp_init(), udp_open(), udp_close(), udp_ntoa(), udp_aton(),
  udp_free(), udp_send(), udp_receive(), udp_sendfake(),udp_sendsim(),
  udp_receivefake(),udp_receivesim();

#ifdef SIMCL
ConnectDomain inetudp =
{
  "inet-udp",
  udp_init,
  udp_ntoa,
  udp_aton,
  udp_free,
  udp_open,
  udp_close,
  udp_sendfake,
  udp_receivefake
};
#else
#ifdef SIM
ConnectDomain inetudp =
{
  "inet-udp",
  udp_init,
  udp_ntoa,
  udp_aton,
  udp_free,
  udp_open,
  udp_close,
  udp_sendsim,
  udp_receivesim
};
#else
#ifdef
ConnectDomain inetudp =
{
  "inet-udp",
  udp_init,
  udp_ntoa,
  udp_aton,
  udp_free,
  udp_open,
  udp_close,
  udp_send,
  udp_receive
};
#endif 
#endif 
#endif 

udpaddr *addrhash[INETTABLESIZE], *inethash[INETTABLESIZE];
int udpdomain;
Addr wild,fake;

/* REGISTER: Connect_RegisterDomain(&inetudp); */

#define hash_addr(a) ((a)%INETTABLESIZE)
#define hash_inet(h) (((h)->sin_port + (h)->sin_addr.s_addr)%INETTABLESIZE)
#define inet_eq(a, b) (((a)->sin_port == (b)->sin_port) && \
		       ((a)->sin_addr.s_addr == (b)->sin_addr.s_addr))

Trap udp_init(d)
     int d;
{
  int i;

  udpdomain = d;
  for (i = 0; i < INETTABLESIZE; i++)
    {
      addrhash[i] = NULL;
      inethash[i] = NULL;
    }

  wild = Connect_AnyAddress(d);
#ifdef SIMCL
  Connect_NameToAddress(FAKE_ADDR,&fake);
#endif
#ifdef SIM
  Connect_NameToAddress(FAKE_ADDR,&fake);
#endif
  
}

udpaddr *addrlookup(addr)
     Addr addr;
{
  udpaddr *a;

  a = addrhash[hash_addr(addr)];

  while (a != NULL)
    {
      if (addr == a->address)
	break;
      a = a->nextaddr;
    }

  return a;
}

udpaddr *inetlookup(inetaddr)
     struct sockaddr_in *inetaddr;
{
  udpaddr *a;

  a = inethash[hash_inet(inetaddr)];

  while (a != NULL)
    {
      if (inet_eq(inetaddr, &(a->netaddr)))
	break;
      a = a->nextinet;
    }

  return a;
}

/*
 * ERRORS:
 *
 * MEMORY_ALLOC: FATAL
 * Failure to allocate memory
 */
Trap newaddress(new, addr)
     udpaddr **new, *addr;
{
  udpaddr *newaddr;
  int ahash, ihash;
  Addr a;

  newaddr = (udpaddr *)malloc(sizeof(udpaddr));
  if (newaddr == NULL)
    Return(MEMORY_ALLOC, sizeof(udpaddr));
  bcopy(addr, newaddr, sizeof(udpaddr));

  if (Connect_NewAddress(udpdomain, &a))
    {
      free(newaddr);
      return CHECK;
    }

  ahash = hash_addr(a);
  ihash = hash_inet(&(addr->netaddr));

  newaddr->address = a;
  newaddr->nextaddr = addrhash[ahash];
  newaddr->nextinet = inethash[ihash];
  addrhash[ahash] = newaddr;
  inethash[ihash] = newaddr;

  *new = newaddr;
  return OK;
}

void freeaddr(freeaddr)
     udpaddr *freeaddr;
{
  int ahash, ihash;
  udpaddr *find;

  ahash = hash_addr(freeaddr->address);
  ihash = hash_inet(&(freeaddr->netaddr));

  /*
   * Remove from address bucket...
   */
  if (addrhash[ahash] == freeaddr)
    addrhash[ahash] = freeaddr->nextaddr;
  else
    {
      find = addrhash[ahash];
      while (find != NULL && find->nextaddr != freeaddr)
	find = find->nextaddr;
      if (find->nextaddr == freeaddr)
	find->nextaddr = freeaddr->nextaddr;
    }

  /*
   * Remove from inet bucket...
   */
  if (inethash[ihash] == freeaddr)
    inethash[ihash] = freeaddr->nextinet;
  else
    {
      find = inethash[ihash];
      while (find != NULL && find->nextinet != freeaddr)
	find = find->nextinet;
      if (find->nextinet == freeaddr)
	find->nextinet = freeaddr->nextinet;
    }

  if (freeaddr->hostname != NULL)
    free(freeaddr->hostname);
  free(freeaddr);
}

/*
 * ERRORS:
 *
 * CONNECT_BADADDRESSFORMAT: FATAL
 * The address string was ill-formed.
 *
 * CONNECT_UNKNOWNNAME: FATAL
 * The hostname given was not resolvable.
 *
 * CONNECT_DOMAINMISMATCH: FATAL
 * The hostname given did not match the domain it was paired with.
 *
 * CONNECT_NEWADDRESS: INFO
 * Operation generated a new address.
 *
 * CONNECT_UNKNOWNSERVICE: FATAL
 * Service name requested for address was unknown.
 */
Trap udp_ntoa(domain, name, address)
     int domain;
     char *name;
     Addr *address;
{
  struct hostent *hp;
  struct servent *sp;
  unsigned long a;
  char *n, *p;
  udpaddr addr, *s;

  if (name[0] == '*' && name[1] == '\0')
    {
      *address = wild;
      return OK;
    }

  n = malloc(strlen(name) + 1);
  if (n == NULL)
    Return(MEMORY_ALLOC, strlen(name) + 1);
  strcpy(n, name);

  bzero(&addr, sizeof(addr));
  addr.netaddr.sin_family = AF_INET;

  p = index(n, '/');
  if (p == NULL)
    addr.netaddr.sin_port = 0;
  else
    {
      *p = '\0';
      p++;
      if (isdigit(*p))
	addr.netaddr.sin_port = htons(atoi(p));
      else
	{
	  sp = getservbyname(p, "udp");
	  if (sp == NULL)
	    Return(CONNECT_UNKNOWNSERVICE, NULL);
	  addr.netaddr.sin_port = sp->s_port;
	  /* Note: network routines seem to return values in net byte order */
	}
    }

  if (isdigit(*n))
    {
      addr.netaddr.sin_addr.s_addr = inet_addr(n);
      free(n);
      if (addr.netaddr.sin_addr.s_addr == -1)
	Return(CONNECT_BADADDRESSFORMAT, name);

      /*
       * If they passed the address in dot notation, it's
       * unlikely they'll care to get the name later. If
       * they do, it'll be handled there.
       */
      addr.hostname = NULL;
    }
  else
    {
      char hnam[MAXHOSTNAMELEN];

      if (*n == '.')
	{
	  gethostname(hnam, MAXHOSTNAMELEN);
	  hp = gethostbyname(hnam);
	}
      else
	hp = gethostbyname(n);

      free(n);
      if (hp == NULL)
	Return(CONNECT_UNKNOWNNAME, name);

      if (hp->h_addrtype != AF_INET)
	Return(CONNECT_DOMAINMISMATCH, name);

      addr.hostname = malloc(strlen(hp->h_name) + 1);
      if (addr.hostname != NULL)
	strcpy(addr.hostname, hp->h_name);

      bcopy(hp->h_addr_list[0], &addr.netaddr.sin_addr,
	    sizeof(addr.netaddr.sin_addr));
    }

  /*
   * At this point, we have udpaddr filled up.
   * Now we look for an existing address which matches it.
   */
  s = inetlookup(&addr.netaddr);
  if (s != NULL)
    {
      if (addr.hostname != NULL)
	free(addr.hostname);
      *address = s->address;
      return OK;
    }

  if (!newaddress(&s, &addr))
    {
      *address = s->address;
      Return(CONNECT_NEWADDRESS, name);
    }

  if (addr.hostname != NULL)
    free(addr.hostname);
  return CHECK;
}

Trap udp_aton(address, name, length)
     Addr address;
     char *name;
     int length;
{
  udpaddr *a;
  struct hostent *hp;
  char *dot;

  if (address == wild)
    {
      strncat(name, "*", length);
      return OK;
    }

  a = addrlookup(address);
  if (a == NULL)
    Return(CONNECT_BADADDRESS, address);

  if (a->hostname == NULL)
    {
      hp = gethostbyaddr((char *) &(a->netaddr.sin_addr),
			 sizeof(a->netaddr.sin_addr), AF_INET);
      if (hp != NULL)
	{
	  a->hostname = malloc(strlen(hp->h_name) + 1);
	  if (a->hostname != NULL)
	    strcpy(a->hostname, hp->h_name);
	}
      else
	{
	  dot = inet_ntoa(a->netaddr.sin_addr);
	  a->hostname = malloc(strlen(dot) + 1);
	  if (a->hostname != NULL)
	    strcpy(a->hostname, dot);
	}
    }

  strncat(name, a->hostname, length); /* XXX port # */
  return OK;
}

/*
 * Since we are connectionless, the "to" address is irrelevant.
 *
 * ERRORS:
 *
 * CONNECT_ADDRESSOPENED: WARNING
 * The connection requested to be opened already was.
 *
 * UDP_SOCKET: FATAL
 * Socket creation failed.
 *
 * UDP_BIND: FATAL
 * The address could not be bound to a socket.
 */
Trap udp_open(from, to)
     Addr from, to;
{
  int s;
  udpaddr *a;

  a = addrlookup(from);
  if (a == NULL)
    Return(CONNECT_BADADDRESS, from);
  if (a->socket != 0)
    Return(CONNECT_ADDRESSOPENED, from);

  s = socket(AF_INET, SOCK_DGRAM, 0);
  if (s < 0)
    Return(UDP_SOCKET, errno);

  if (bind(s, &(a->netaddr), sizeof(a->netaddr)))
    {
      close(s);
      Return(UDP_BIND, errno); /* close won't disturb errno */
    }

  if (Connect_RegisterDescriptor(from, s))
    {
      close(s);
      return CHECK;
    }

  Connect_SelectInterest(s, Set, Clear, Clear);

  a->socket = s;
      
  return 0;
}

Trap udp_free(address)
     Addr address;
{
  return udp_close(address, wild);
}

Trap udp_close(from, to)
     Addr from, to;
{
  udpaddr *a;
  Addr which;

  if (to == wild)
    which = from;
  else
    which = to;

  a = addrlookup(which);
  if (a == NULL)
    Return(CONNECT_BADADDRESS, which);

  if (a->socket != 0)
    {
      Connect_FreeDescriptor(a->socket);
      close(a->socket);
      a->socket = 0;
    }

  freeaddr(a);
  Connect_KillAddress(which);
  return OK;
}

/*
 * ERRORS:
 *
 * CONNECT_ADDRESSNOTOPEN: FATAL
 * The operation on the address failed because it was not open.
 *
 * CONNECT_BADDEST: FATAL
 * The destination address was insufficiently specified.
 *
 * UDP_SENDTO: FATAL
 * Sendto returned an unexpected error.
 *
 * UDP_PARTIALSEND: WARNING
 * Sendto only send part of the packet.
 *
 * UDP_NOBROADCAST: FATAL
 * Broadcast is not supported.
 */
Trap udp_send(p)
     Packet *p;
{
  int cc;
  udpaddr *from, *to;

  if (p->Destination == wild)
    Return(UDP_NOBROADCAST, NULL);

  from = addrlookup(p->Source);
  if (from == NULL)
    Return(CONNECT_BADADDRESS, p->Source);
  if (from->socket == 0)
    Return(CONNECT_ADDRESSNOTOPEN, p->Source);

  to = addrlookup(p->Destination);
  if (to == NULL)
    Return(CONNECT_BADADDRESS, p->Destination);
  if (to->netaddr.sin_port == 0)
    Return(CONNECT_BADDEST, p->Destination);

  cc = sendto(from->socket, p->packet, p->length, 0,
	      &(to->netaddr), sizeof(to->netaddr));
  if (cc == -1)
    Return(UDP_SENDTO, errno);

  if (cc != p->length)
    Return(UDP_PARTIALSEND, cc);

  return OK;
}
Trap udp_sendfake(p)  /* For use building test clients & servers
		       send to fake place while stashing the real dest in 
		       packet for use on originating process*/
     Packet *p;
{
  int cc;
  udpaddr *from, *to,*fakeaddr;
  char newpacket[BUFSIZ],addrstr[100];

  if (p->Destination == wild)
    Return(UDP_NOBROADCAST, NULL);

  from = addrlookup(p->Source);
  if (from == NULL)
    Return(CONNECT_BADADDRESS, p->Source);
  if (from->socket == 0)
    Return(CONNECT_ADDRESSNOTOPEN, p->Source);

  to = addrlookup(p->Destination);
  if (to == NULL)
    Return(CONNECT_BADADDRESS, p->Destination);
  if (to->netaddr.sin_port == 0)
    Return(CONNECT_BADDEST, p->Destination);
  fakeaddr = addrlookup(fake);
/* stash address */
sprintf(addrstr,"%s/%d",inet_ntoa(to->netaddr.sin_addr),ntohs(to->netaddr.sin_port));
strcpy(newpacket,addrstr);
bcopy(p->packet,newpacket + strlen(addrstr) + 1,p->length);  
p->length = p->length + strlen(addrstr) + 1;
/*
printf("sending packet <%s>\n",p->packet);
printf("stashing addr <%s> in packet\n",newpacket); */

  cc = sendto(from->socket, newpacket, p->length, 0,
	      &(fakeaddr->netaddr), sizeof(fakeaddr->netaddr));
  if (cc == -1)
    Return(UDP_SENDTO, errno);

  if (cc != p->length)
    Return(UDP_PARTIALSEND, cc);

  return OK;
}
Trap udp_sendsim(p)  /* For use in the simulator process!!!
			send  while stashing the real source in 
			packet for use on originating process*/
     Packet *p;
{
  int cc;
  udpaddr *from, *two,*fakeaddr;  /* dbx gets confused with "to" */
  char newpacket[BUFSIZ],addrstr[100];

  if (p->Destination == wild)
    Return(UDP_NOBROADCAST, NULL);

  from = addrlookup(p->Source);
  if (from == NULL)
    Return(CONNECT_BADADDRESS, p->Source);

  two = addrlookup(p->Destination);
  if (two == NULL)
    Return(CONNECT_BADADDRESS, p->Destination);
  if (two->netaddr.sin_port == 0)
    Return(CONNECT_BADDEST, p->Destination);

/* stash address */
sprintf(addrstr,"%s/%d",inet_ntoa(from->netaddr.sin_addr),ntohs(from->netaddr.sin_port));
/*
printf("sending <%s> %d bytes\n",p->packet,p->length); */
strcpy(newpacket,addrstr);
bcopy(p->packet,newpacket + strlen(addrstr) +1,p->length);  
p->length = p->length + strlen(addrstr) +1;

/*
printf("trying to sending as <%s>\n",addrstr);
printf("trying to send to <%s>\n",addrstr); */
  fakeaddr = addrlookup(fake);

  cc = sendto(fakeaddr->socket, newpacket, p->length, 0,
	      &(two->netaddr), sizeof(two->netaddr));

  if (cc == -1) {
    perror("sendto");
    Return(UDP_SENDTO, errno);
  }

  if (cc != p->length)
    Return(UDP_PARTIALSEND, cc);

  return OK;
}

Trap Udp_IP(addr, ip)
     Addr addr;
     u_long *ip;
{
  udpaddr *a;

  a = addrlookup(addr);
  if (a == NULL)
    Return(CONNECT_BADADDRESS, addr);

  *ip = a->netaddr.sin_addr.s_addr;
  return OK;
}

/*
 * ERRORS:
 *
 * UDP_NREAD: FATAL
 * FIONREAD returned an unexpected error.
 *
 * UDP_RECVFROM: FATAL
 * recvfrom returned an unexpected error.
 *
 * UDP_PARTIALREAD: WARNING
 * The whole packet as expected from FIONREAD was not read.
 *
 * UDP_DEADSOCKET: FATAL
 * The socket was expected to have data on it but did not.
 */
Trap udp_receive(s, a, p)
     int s;
     Addr a;
     Packet *p;
{
  int length, fromlen, cc;
  struct sockaddr_in from;
  char *buffer;
  struct hostent *hp;
  udpaddr *source;
  udpaddr new;
  Trap trap = OK;

  if (ioctl(s, FIONREAD, &length))
    Return(UDP_NREAD, errno);

  if (length != 0)
    {
      buffer = malloc(length);
      if (buffer == NULL)
	Return(MEMORY_ALLOC, length);

      fromlen = sizeof(from);
      cc = recvfrom(s, buffer, length, 0, &from, &fromlen);
      if (cc == -1)
	{
	  free(buffer);
	  Return(UDP_RECVFROM, errno);
	}

      source = inetlookup(&from);
      if (source == NULL)
	{
	  bzero(&new, sizeof(new));
	  bcopy(&from, &new.netaddr, sizeof(new.netaddr));
	  
	  if (newaddress(&source, &new))
	    {
	      /*
	       * XXX We could recover more gracefully here by having
	       * a reserved static address to which error replies
	       * could be directed, set to the address of the last
	       * packet dropped.
	       */
	      free(buffer);
	      return CHECK;
	    }

	  Error_Push(CONNECT_NEWADDRESS, source->address);
	  trap = CHECK;
	}

      p->Source = source->address;
      p->Destination = a;
      p->length = cc;
      p->packet = buffer;
/*    if (cc != length) XXX - Not sure what's going on.
	Return(UDP_PARTIALREAD, length); */
      return trap;
    }

  /*
   * This shouldn't happen on an unconnected UDP socket.
   * So I don't really know what should be done here,
   * though maybe the socket should be closed.
   */
  Return(UDP_DEADSOCKET, a);
}
Trap udp_receivefake(s, a, p) /* for use in testing */
     int s;
     Addr a;
     Packet *p;
{
  int length, fromlen, cc;
  struct sockaddr_in from;
  char *buffer,*cp;
  struct hostent *hp;
  udpaddr *source;
  udpaddr new;
  Trap trap = OK;
  char newpacket[BUFSIZ],addrstr[100];
  if (ioctl(s, FIONREAD, &length))
    Return(UDP_NREAD, errno);

  if (length != 0)
    {
      buffer = malloc(length);
      if (buffer == NULL)
	Return(MEMORY_ALLOC, length);

      fromlen = sizeof(from);
      cc = recvfrom(s, buffer, length, 0, &from, &fromlen);
      if (cc == -1)
	{
	  free(buffer);
	  Return(UDP_RECVFROM, errno);
	}

      source = inetlookup(&from);
      if (source == NULL)
	{
	  bzero(&new, sizeof(new));
	  bcopy(&from, &new.netaddr, sizeof(new.netaddr));
	  
	  if (newaddress(&source, &new))
	    {
	      /*
	       * XXX We could recover more gracefully here by having
	       * a reserved static address to which error replies
	       * could be directed, set to the address of the last
	       * packet dropped.
	       */
	      free(buffer);
	      return CHECK;
	    }

	  Error_Push(CONNECT_NEWADDRESS, source->address);
	  trap = CHECK;
	}
/* Here's what we changed  **/
      p->length = cc;
      strcpy(addrstr,buffer);
      cp = index(buffer,'\0');
      if (cp)
	cp++;
      p->length = p->length - strlen(buffer) - 1;
      p->packet = cp;
      printf("recv buffer <%s> ln = %d\n",p->packet,p->length); 
      udp_ntoa(udpdomain,addrstr,&p->Source);

      p->Destination = a;
/*    if (cc != length) XXX - Not sure what's going on.
	Return(UDP_PARTIALREAD, length); */
      return trap;
    }

  /*
   * This shouldn't happen on an unconnected UDP socket.
   * So I don't really know what should be done here,
   * though maybe the socket should be closed.
   */
  Return(UDP_DEADSOCKET, a);
}
Trap udp_receivesim(s, a, p) /* for use with the simulator */
     int s;
     Addr a;
     Packet *p;
{
  int length, fromlen, cc;
  struct sockaddr_in from;
  char *buffer,*cp;
  struct hostent *hp;
  udpaddr *source;
  udpaddr new;
  Trap trap = OK;
  char newpacket[BUFSIZ],addrstr[100];

  if (ioctl(s, FIONREAD, &length))
    Return(UDP_NREAD, errno);

  if (length != 0)
    {
      buffer = malloc(length);
      if (buffer == NULL)
	Return(MEMORY_ALLOC, length);

      fromlen = sizeof(from);
      cc = recvfrom(s, buffer, length, 0, &from, &fromlen);
      if (cc == -1)
	{
	  free(buffer);
	  Return(UDP_RECVFROM, errno);
	}

      source = inetlookup(&from);
      if (source == NULL)
	{
	  bzero(&new, sizeof(new));
	  bcopy(&from, &new.netaddr, sizeof(new.netaddr));
	  
	  if (newaddress(&source, &new))
	    {
	      /*
	       * XXX We could recover more gracefully here by having
	       * a reserved static address to which error replies
	       * could be directed, set to the address of the last
	       * packet dropped.
	       */
	      free(buffer);
	      return CHECK;
	    }

	  Error_Push(CONNECT_NEWADDRESS, source->address);
	  trap = CHECK;
	}
/* Here's what we changed  **/
      strcpy(addrstr,buffer);
 /* printf("recv packet <%s>\n",addrstr); */
      cp = index(buffer,'\0');
      if (cp)
	cp++;
      p->length = cc;
      p->length = p->length - strlen(buffer) - 1;  
      p->packet = cp;
    printf("packet = <%s>\n",p->packet);
      udp_ntoa(udpdomain,addrstr,&p->Destination);
      p->Source = source->address;

/*    if (cc != length) XXX - Not sure what's going on.
	Return(UDP_PARTIALREAD, length); */
      return trap;
    }

  /*
   * This shouldn't happen on an unconnected UDP socket.
   * So I don't really know what should be done here,
   * though maybe the socket should be closed.
   */
  Return(UDP_DEADSOCKET, a);
}
