#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#ifdef SOLARIS
#include <sys/filio.h>
#endif
#include <sys/param.h>
#include <netdb.h>
#include <errno.h>
#include "inet-udp.h"
#include <arpa/inet.h> /* hmmm... */
#include "Connect.h"

#ifndef NULL
#define NULL 0
#endif

Trap udp_init(), udp_open(), udp_close(), udp_ntoa(),
  udp_free(), udp_send(), udp_receive();

ConnectDomain inetudp =
{
  "inet-udp",
  udp_init,
  udp_ntoa,
  ip_aton,
  udp_free,
  udp_open,
  udp_close,
  udp_send,
  udp_receive
};

int udpdomain;
Addr wild;

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

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

  udpdomain = d;
  wild = Connect_AnyAddress(d);

  return ip_init();
}

Trap udp_ntoa(domain, name, address)
     int domain;
     char *name;
     Addr *address;
{
  return ip_ntoa(domain, "udp", name, address);
}

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

  a = ip_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, (struct sockaddr *)&(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;
{
  inetaddr *a;
  Addr which;

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

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

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

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

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

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

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

  to = ip_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,
	      (struct sockaddr *)&(to->netaddr), sizeof(to->netaddr));
  if (cc == -1)
    Return(UDP_SENDTO, errno);

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

  return OK;
}

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

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

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

Trap Udp_Port(addr, port)
     Addr addr;
     u_short *port;
{
  inetaddr *a;

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

  *port = ntohs(a->netaddr.sin_port);
  return OK;
}

/*
 * ERRORS:
 *
 * UDP_NREAD: FATAL
 * FIONREAD returned an unexpected error, %d.
 *
 * UDP_RECVFROM: FATAL
 * recvfrom returned an unexpected error, %d.
 *
 * 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;
  inetaddr *source;
  inetaddr new;
  Trap trap = OK;

  /*
   * FIONREAD appears to return the number of bytes available to
   * be read. However, it may reflect the sum of bytes in multiple
   * packets. Read will only return one packet at a time, so
   * if there are multiple packets available all of FIONREAD bytes
   * will not be read. I don't know where this info is documented.
   * This is the behavior I have observed.
   */
  if (ioctl(s, FIONREAD, &length))
    Return(UDP_NREAD, errno);

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

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

      source = ip_inetlookup(&from);
      if (source == NULL)
	/* We haven't seen this address before. Make a record of
	   it. */
	{
	  memset(&new, 0, sizeof(new));
	  memcpy(&new.netaddr, &from, sizeof(new.netaddr));

	  if (ip_newaddress(udpdomain, &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;

      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);
}
