/*
 * udp.c:
 *
 * Copyright (c) 2000 James McKenzie <james@fishsoup.dhs.org>.
 * All rights reserved.
 *
 * $Log: udp.c,v $
 * Revision 1.22  2001/10/12 13:51:11  root
 * #
 *
 * Revision 1.21  2001/01/30 06:16:17  root
 * #
 *
 * Revision 1.20  2000/12/01 04:42:09  root
 * #
 *
 * Revision 1.19  2000/12/01 04:13:41  root
 * #
 *
 * Revision 1.18  2000/11/23 23:45:16  root
 * #
 *
 * Revision 1.17  2000/10/07 18:30:02  root
 * #
 *
 * Revision 1.16  2000/10/06 19:52:17  root
 * #
 *
 * Revision 1.15  2000/09/27 08:26:28  root
 * #
 *
 * Revision 1.14  2000/09/24 01:57:55  root
 * #
 *
 * Revision 1.13  2000/09/24 01:57:53  root
 * #
 *
 * Revision 1.12  2000/09/24 01:57:40  root
 * #
 *
 * Revision 1.11  2000/09/24 01:48:12  root
 * #
 *
 * Revision 1.10  2000/09/24 01:46:04  root
 * #
 *
 * Revision 1.9  2000/09/24 01:12:46  root
 * #
 *
 * Revision 1.8  2000/09/24 01:10:19  root
 * #
 *
 * Revision 1.7  2000/02/21 04:13:33  root
 * #
 *
 * Revision 1.6  2000/02/21 04:11:26  root
 * #
 *
 * Revision 1.5  2000/02/21 04:01:01  root
 * #
 *
 * Revision 1.4  2000/02/21 02:53:46  root
 * #
 *
 * Revision 1.3  2000/02/21 00:49:34  root
 * *** empty log message ***
 *
 * Revision 1.2  2000/02/20 18:22:59  root
 * #
 *
 * Revision 1.1  2000/02/20 14:07:12  root
 * #
 *
 *
 */

#define UDPPORT 4567

static char rcsid[] = "$Id: udp.c,v 1.22 2001/10/12 13:51:11 root Exp root $";


#include "hifimsg.h"
#include "hifimsg_private.h"
#include <errno.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>

#define HIFI_UDP_TIMEOUT 10


typedef struct client_struct
{
  struct sockaddr_in addr;
  time_t timestamp;
  struct client_struct *next;
}
 *Client;

typedef struct
{
  int fd;
  struct sockaddr_in dest;
  struct sockaddr_in me;
  struct sockaddr_in sendme;
  struct sockaddr_in bound;
  Client clients;
}
Udp;


#ifdef DEBUG
#ifndef SEMIDEBUG
#define SEMIDEBUG
#endif
#endif

#ifdef SEMIDEBUG
char *
ntoa (struct sockaddr_in *s)
{
  static char ret[1024];

  int p = ntohs (s->sin_port);
  unsigned long a = ntohl (s->sin_addr.s_addr);


  sprintf (ret, "%d.%d.%d.%d:%d",
           (int) ((a >> 24L) & 0xffL),
           (int) ((a >> 16L) & 0xffL),
           (int) ((a >> 8L) & 0xffL), (int) ((a) & 0xffL), p);

  return ret;
}
#endif

static int
sockaddr_in_cmp (struct sockaddr_in *a, struct sockaddr_in *b)
{
  unsigned long al = ntohl (a->sin_addr.s_addr);
  unsigned long bl = ntohl (b->sin_addr.s_addr);
  unsigned short as = ntohs (a->sin_port);
  unsigned short bs = ntohs (b->sin_port);
  if (al > bl)
    return 1;
  if (al < bl)
    return -1;
  if (as > bs)
    return 1;
  if (as < bs)
    return -1;
  return 0;
}


static int
getudpport (void)
{
  static int port;

  if (port)
    return port;

  port = hifi_getint ("port", UDPPORT);

  return port;
}


static int
is_server (struct sockaddr_in *sin)
{
//FIXME: better test? actually this one isn't that bad
  if (ntohs (sin->sin_port) == getudpport ())
    return 1;
  return 0;
}

static void
testforhub (void)
{
  int fd;
  struct sockaddr_in sin;

  fd = socket (PF_INET, SOCK_DGRAM, 0);

  sin.sin_family = AF_INET;
  sin.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
  sin.sin_port = htons (getudpport ());

  if (!bind (fd, (struct sockaddr *) &sin, sizeof (sin)))
    {
      close (fd);
      printf ("NB:::::: No hub spawning one...\n");
      hifimsg_spawn_hub ();
      usleep (100000);
      return;
    }
  close (fd);
  printf ("Hub located...\n");

}


static void *
do_udp_open (char *name, struct sockaddr_in *sa, struct sockaddr_in *bd)
{
  Udp *udp = (Udp *) malloc (sizeof (Udp));
  int i;


   /*OCTOPUS*/
#ifdef DEBUG
    if (name)
    printf ("udp socket open on deivce %s\n", name);
#endif
  if (!udp)
    return NULL;


  bzero (udp, sizeof (Udp));

  if (!sa)
    testforhub ();

  udp->fd = socket (PF_INET, SOCK_DGRAM, 0);

  if (udp->fd < 0)
    {
      free (udp);
      return NULL;
    }

  udp->me.sin_family = AF_INET;

  if (sa)
    {

      udp->me.sin_addr.s_addr = sa->sin_addr.s_addr;
      udp->me.sin_port = htons (getudpport ());
    }
  else
    {
      udp->me.sin_addr.s_addr = htonl (INADDR_ANY);
      udp->me.sin_port = htons (0);
    }

  if (bd)
    {
      udp->bound = *bd;
      udp->bound.sin_port = htons (getudpport ());
    }
  else
    {
      udp->bound = udp->me;
    }


  if (bind (udp->fd, (struct sockaddr *) &udp->bound, sizeof (udp->bound)))
    {
      close (udp->fd);
      free (udp);
      return NULL;
    }


  if (sa)
    {

      if (bd)
        {
          i = 1;
          if (setsockopt (udp->fd, SOL_SOCKET, SO_BROADCAST, &i, sizeof (i)))
            {
              close (udp->fd);
              free (udp);
              return NULL;
            }
          udp->dest.sin_addr.s_addr = bd->sin_addr.s_addr;
        }
      else
        {
          udp->dest.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
        }
      udp->dest.sin_family = AF_INET;
      udp->dest.sin_port = htons (getudpport ());

    }
  else
    {
      udp->dest.sin_family = AF_INET;
      udp->dest.sin_port = htons (getudpport ());
      udp->dest.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
    }




  udp->clients = NULL;
  return (void *) udp;
}



static int
udp_fd (void *_udp)
{
  Udp *u = (Udp *) _udp;
  return u->fd;
}


static int
udp_recv (void *_udp, void *buf, int len)
{
  Udp *u = (Udp *) _udp;
  struct sockaddr_in from, fz;
  int fromlen;
  int i = 0;

  do
    {
      fromlen = sizeof (struct sockaddr_in);
      bzero (&from, fromlen);
      bzero (&fz, fromlen);


      len =
        recvfrom (u->fd, buf, len, 0, (struct sockaddr *) &from, &fromlen);
      i++;

    }
  while ((i < 10) && (len == -1));

  if (len <= 0)
    return len;

  if (fromlen != sizeof (struct sockaddr_in))
    return 0;

  if (!bcmp (&from, &fz, sizeof (struct sockaddr_in)))
    return 0;

#ifdef DEBUG
  printf ("recv on fd %d(%s) from", u->fd, ntoa (&u->me));
  printf (" %s\n", ntoa (&from));
#endif


  if (!sockaddr_in_cmp (&from, &u->me))
    {
#ifdef DEBUG
      printf ("loopback: Rejected\n");
#endif
      return 0;
    }




  return len;
}



static int
udp_server_recv (void *_udp, void *buf, int len)
{
  Udp *u = (Udp *) _udp;
  struct sockaddr_in from, fz;
  int fromlen;
  int matched;
  Client *c, *n;
  time_t now;
  int i, j;


  i = 0;

  {
    fromlen = sizeof (struct sockaddr_in);
    bzero (&from, fromlen);
    bzero (&fz, fromlen);

    len = recvfrom (u->fd, buf, len, 0, (struct sockaddr *) &from, &fromlen);

    i++;
  }

#ifdef DEBUG
  printf ("Srecv on fd %d(%s) from", u->fd, ntoa (&u->me));
  printf (" %s/%d returns %d\n", ntoa (&from), fromlen, len);
#endif


  if (len < 0)
    {
      //printf("Redoing socket\n");

      close (u->fd);
      i = socket (PF_INET, SOCK_DGRAM, 0);
      if (i != u->fd)
        {
          dup2 (i, u->fd);
          close (i);
        }


      if (u->fd < 0)
        return len;


      if (bind (u->fd, (struct sockaddr *) &u->bound, sizeof (u->bound)))
        {
          close (u->fd);
          u->fd = -1;
          return len;
        }



      j = 1;

      setsockopt (u->fd, SOL_SOCKET, SO_BROADCAST, &j, sizeof (j));


      return len;
    }

  if (fromlen != sizeof (struct sockaddr_in))
    return 0;

  if (!bcmp (&from, &fz, sizeof (struct sockaddr_in)))
    {
      return 0;
    }

  //Two possibilities 2. broadcast reception or 3. client reception
  matched = 0;

  now = hifimsg_timestamp ();

  //DANG BEGIN ATOMIC
  c = &u->clients;
  while (*c)
    {
      n = &((*c)->next);

#if DEBUG
      printf ("Client: %p (%s)\n", *c, ntoa (&((*c)->addr)));
#endif

      if (!sockaddr_in_cmp (&from, &(*c)->addr))
        {
          //This guy is on our books take note of the
          //fact he isn't dead
          matched++;
          (*c)->timestamp = hifimsg_timestamp ();
        }
      else
        {
          int i;
          //Didn't come from this one so ship out
#ifdef DEBUG
          printf ("Srecvsend on fd %d(%s) to", u->fd, ntoa (&u->me));
          printf (" %s\n", ntoa (&((*c)->addr)));
#endif
          errno = 0;


          i = 0;

          while ((i < 10) && (sendto (u->fd,
                                      buf, len, 0,
                                      (struct sockaddr *) &((*c)->addr),
                                      sizeof (struct sockaddr_in)) != len))
            i++;


          if ((difftime (now, (*c)->timestamp) > HIFI_UDP_TIMEOUT))
            {
              Client dead = *c;

#ifdef  DEBUG
              printf ("Killing %p %s\n", dead, ntoa (&dead->addr));
#endif

              //hmm bury them
              *c = dead->next;
              free (dead);
            }
        }

      c = n;
    }
  //DANG END ATOMIC

  if (is_server (&from))
    {
      //Broadcast from another server
      //all done
      return len;
    }

  if (!matched)
    //message from a new client stick on head
    {
      Client n = (Client) malloc (sizeof (struct client_struct));

      n->timestamp = now;
      bcopy (&from, &n->addr, sizeof (struct sockaddr_in));

#ifdef SEMIDEBUG
      printf ("New Client %p %s\n", n, ntoa (&n->addr));
#endif
      //DANG BEGIN ATOMIC

      n->next = u->clients;
      u->clients = n;

      //DANG END ATOMIC

    }
  //Need to forward to other servers 
  //no this is now the job of the hub

#ifdef DEBUG
  {

    printf ("clients:");
    c = &u->clients;
    while (*c)
      {
        printf (" %p(%s)", *c, ntoa (&((*c)->addr)));
        if (*c)
          c = &((*c)->next);
      }

    printf ("\n");
  }
#endif

  //All done
  return len;

}

static int
udp_send (void *_udp, void *buf, int len)
{
  Udp *udp = (Udp *) _udp;

#ifdef DEBUG
  printf ("send on fd %d(%s) to", udp->fd, ntoa (&udp->me));
  printf (" %s\n", ntoa (&udp->dest));
#endif

  return sendto (udp->fd, buf, len, 0, (struct sockaddr *) &udp->dest,
                 sizeof (udp->dest));
}

static int
udp_null (void *_udp, void *buf, int len)
{
  return 0;
}

static int
udp_server_send (void *_udp, void *buf, int len)
{
  Udp *udp = (Udp *) _udp;
  Client *c, *n;
  int ret;

// send to localweenies only not localhost broadcast rqd

//  ret = sendto (udp->fd, buf, len, 0, (struct sockaddr *) &udp->dest, sizeof (udp->dest));

  //smart send to all people on our books.

  //DANG BEGIN ATOMIC
  c = &(udp->clients);
  while (*c)
    {
      int i;
      n = &((*c)->next);
      //ship out
#ifdef DEBUG
      printf ("Ssend on fd %d(%s) to", udp->fd, ntoa (&udp->me));
      printf (" %s\n", ntoa (&(*c)->addr));
#endif
      {
        errno = 0;
      }



      i = 0;
      while ((i < 10)
             &&
             (sendto
              (udp->fd, buf, len, 0, (struct sockaddr *) &(*c)->addr,
               sizeof (struct sockaddr_in)) != len))
        i++;

      c = n;


    }
  //DANG END ATOMIC

#ifdef DEBUG
  {
    printf ("clients:");
    c = &udp->clients;
    while (*c)
      {
        printf (" %p", *c);

        if (*c)
          c = &((*c)->next);
      }

    printf ("\n");
  }
#endif

  return len;
}

static void
udp_close (void *_udp)
{
  Udp *u = (Udp *) _udp;

  if (u->clients)
    {
      Client c = u->clients, d;

      while (c)
        {
          d = c->next;
          free (c);
          c = d;
        }
    }

  close (u->fd);
  free (u);
}


Connexion hifimsg_udp_open (void)
{
  Connexion c = (Connexion) malloc (sizeof (struct connexion_struct));
  c->h = do_udp_open (NULL, NULL, 0);
  if (!c->h)
    {
      free (c);
      return NULL;
    }
  c->fd = udp_fd (c->h);
#ifdef I_AM_A_BROKEN_UDP_WEENIE1
  c->fd2 = udp_fd2 (c->h);
#else
  c->fd2 = 0;
#endif
  c->recv = udp_recv;
  c->send = udp_send;
  c->close = udp_close;
  return c;
}


Connexion hifimsg_udp_server_open (Connexion c)
{
  void *irda;
  char buf[1024];
  struct ifconf ifc;
  int fd;
  int i;
  int ifs;

  ifc.ifc_buf = buf;
  ifc.ifc_len = sizeof (buf);

  fd = socket (PF_INET, SOCK_DGRAM, 0);
  if (fd < 0)
    return c;

  if (ioctl (fd, SIOCGIFCONF, &ifc))
    {
      close (fd);
      return c;
    }

  ifs = ifc.ifc_len / sizeof (struct ifreq);

  for (i = 0; i < ifs; ++i)
    {
      struct ifreq *ifr = &ifc.ifc_req[i];
      if (ifc.ifc_req[i].ifr_addr.sa_family == PF_INET)
        {
          struct sockaddr_in ifaddr = *(struct sockaddr_in *) &ifr->ifr_addr;
          struct sockaddr_in *bdaddr =
            (struct sockaddr_in *) &ifr->ifr_broadaddr;
          void *udp;


          ioctl (fd, SIOCGIFBRDADDR, ifr);


          if (ifaddr.sin_addr.s_addr == htonl (INADDR_LOOPBACK))
            {
              bdaddr = 0;
            }

          udp = do_udp_open (ifc.ifc_req[i].ifr_name, &ifaddr, bdaddr);


          if (udp)
            {
              Connexion n =
                (Connexion) malloc (sizeof (struct connexion_struct));
              n->h = udp;
              n->fd = udp_fd (n->h);
#ifdef I_AM_A_BROKEN_UDP_WEENIE1
              n->fd2 = udp_fd2 (n->h);
#else
              n->fd2 = 0;
#endif
              n->close = udp_close;
              n->next = c;
              n->forward = 1;   //Forward messages on these directed interfaces

              if (!bdaddr)
                {
                  n->recv = udp_server_recv;
                  n->send = udp_server_send;
                  n->local = 1;
                }
              else
                {
                  n->local = 0;
                  n->recv = udp_recv;
                  n->send = udp_send;
                }
              c = n;
            }
        }
    }

  close (fd);

//Should forward from a local=0 interface to all local=1 interfaces
//Should forward from a local=1 interface to all interfaces
//Should forward from a local=0 interface to a local=0 interface iff
//first if has forward=1

//And the global broadcast address
//Must not forward from this interface
//Must not transmit
//No use for this...
  if (0)
    {
      struct sockaddr_in sin;
      void *udp;
      sin.sin_addr.s_addr = htonl (INADDR_BROADCAST);
      udp = do_udp_open (ifc.ifc_req[i].ifr_name, &sin, 0);

      if (udp)
        {
          Connexion n = (Connexion) malloc (sizeof (struct connexion_struct));
          n->h = udp;
          n->fd = udp_fd (n->h);
#ifdef I_AM_A_BROKEN_UDP_WEENIE1
          n->fd2 = udp_fd2 (n->h);
#else
          n->fd2 = 0;
#endif
          n->recv = udp_recv;
          n->send = udp_null;
          n->close = udp_close;
          n->next = c;
          n->local = 0;
          n->forward = 0;
          c = n;
        }


    }


  return c;

}
