/*
    Copyright 2000 (c) by David Schweikert,
    Swiss Federal Institute of Technology,
    Computer Engineering and Networks Laboratory.
 
    TOPSY -- A Teachable Operating System.
             Implementation of a tiny and simple
             micro kernel for teaching purposes.
 
    For further information, please visit http://www.tik.ee.ethz.ch/~topsy
 
    This software is provided under the terms of the GNU General Public 
    Licence. A full copy of the GNU GPL is provided in the file COPYING 
    found in the development root of Topsy.
*/
/*
    File:                  $Source: /home/ruf/cvs_Topsy/cvs/Topsy/Net/old/UDP/NetUDP.c,v $
    Author(s):             
    Affiliation:           ETH Zuerich, TIK
    Version:               $Revision: 2936 $
    Creation Date:         
    Last Date of Change:   $Date: 2007-03-21 15:23:42 +0100 (Mit, 21 Mär 2007) $      by: $Author: baderma $
 
 
    $Log: NetUDP.c,v $
    Revision 1.2  2000/11/15 10:12:15  ruf
    Repository Reorganisation
 
    Revision 1.1  2000/03/31 17:50:40  gfa
    Merged with /Net from several term projects
 
*/
#include <Topsy.h>
#include <Messages.h>
#include <Syscall.h>

#include <NetDebug.h>
#include <NetBuf.h>
#include <NetAttr.h>
#include <NetConfig.h>
#include <NetModules.h>
#include <UDP/NetUDP.h>

#include <IP/NetIP.h>         /* needed for NETIP_PROTOCOL_UDP */
#include <IP/NetIPchecksum.h>

void netudpRead(ThreadId id, unsigned long int length);
static void netudpMain(ThreadArg arg);
static void netudpUp(NetBuf buf, NetAttrs attrs);
static void netudpDown(NetBuf buf, NetAttrs attrs);
static void netudpListen(ThreadId thread, NetAttrs attrs, long value);
static void netudpClose(NetAttrs attrs, long value);


ThreadId netudp_ports[NETUDP_MAXPORTNR+1]; /* Should be done with a hash-table... */

void netudpInit()
{
  ThreadId netudpId;
  int i = 0;

  netudp_ports[0]=NETUDP_FIRST_USER_PORT;
  for(i=1; i<NETUDP_MAXPORTNR+1; i++)
    netudp_ports[i]=0;

  /* Start main thread */
  if (tmStart(&netudpId, netudpMain, (ThreadArg)0, "netudp") != TM_STARTOK)
  {
    netdbgDisplay(NETDEBUG_GENERIC, "Start: couldn't start UDP thread\n");
    return;
  }
}

static void netudpMain(ThreadArg arg)
{
  ThreadId myThreadId, parentThreadId;
  NetModMsg msg;

  tmGetInfo(SELF, &myThreadId, &parentThreadId);

  // Add to list of modules
  netmodAdd(NETMODULE_UDP, myThreadId);

  netdbgPrintf(NETDEBUG_MODULES,
               "UDP module started (moduleId=%d, threadId=%d).\n",
               NETMODULE_UDP, myThreadId);

  while(1)
  {
    if(!netmodMsgRecv(&msg))
      return;

    switch (msg.id)
    {
    case NETMOD_SENDUP:
      netudpUp(msg.buf, msg.attrs);
      break;
    case NETMOD_SENDDOWN:
      netudpDown(msg.buf, msg.attrs);
      break;
    case NETMOD_LISTEN:
      netudpListen(msg.from, msg.attrs, msg.value);
      break;
    case NETMOD_CLOSE:
      netudpClose(msg.attrs, msg.value);
      break;
    default:
      netdbgPrintf(NETDEBUG_GENERIC,
                   "*** ERROR: UDP: Unknown message (%d)!\n",
                   msg.id);
      break;
    }
  }
}

typedef struct netudp_pseudo_header_t
{
  NetBufHdr netbuf;

  u8 saddr0;
  u8 saddr1;
  u8 saddr2;
  u8 saddr3;

  u8 daddr0;
  u8 daddr1;
  u8 daddr2;
  u8 daddr3;

  u8 zero;
  u8 protocol;
  u16 len;
}
netudp_pseudo_header;

static int netudpChecksum(NetBuf buf, NetAttrs attrs, int len)
{
  netudp_pseudo_header phdr;
  unsigned short tmp;

  /* Construct the Pseudo-header by adding a false-NetBuf. */
  phdr.netbuf.start= NETBUF_HEADERSIZE;
  phdr.netbuf.end  = sizeof(netudp_pseudo_header);
  phdr.netbuf.next = buf;

  /* source address */
  if(!netattrGet(attrs, NETATTR_IP_FROM_0, &tmp))
    return -1;
  phdr.saddr0 = tmp;
  if(!netattrGet(attrs, NETATTR_IP_FROM_1, &tmp))
    return -1;
  phdr.saddr1 = tmp;
  if(!netattrGet(attrs, NETATTR_IP_FROM_2, &tmp))
    return -1;
  phdr.saddr2 = tmp;
  if(!netattrGet(attrs, NETATTR_IP_FROM_3, &tmp))
    return -1;
  phdr.saddr3 = tmp;

  /* dest address */
  if(!netattrGet(attrs, NETATTR_IP_TO_0, &tmp))
    return -1;
  phdr.daddr0 = tmp;
  if(!netattrGet(attrs, NETATTR_IP_TO_1, &tmp))
    return -1;
  phdr.daddr1 = tmp;
  if(!netattrGet(attrs, NETATTR_IP_TO_2, &tmp))
    return -1;
  phdr.daddr2 = tmp;
  if(!netattrGet(attrs, NETATTR_IP_TO_3, &tmp))
    return -1;
  phdr.daddr3 = tmp;

  phdr.zero=0;
  phdr.protocol=NETIP_PROTOCOL_UDP;
  phdr.len =htons(len);

  /* calculate checksum */
  return netipChecksum(&phdr.netbuf,len+sizeof(netudp_pseudo_header));
}

static inline ThreadId netudpLookup(unsigned short port)
{
  return netudp_ports[port];
}

static void netudpUp(NetBuf buf, NetAttrs attrs)
{
  NetUDPHdr *hdr;
  ThreadId thread;
  int len = 0;
  unsigned short sport, dport;

  hdr = (NetUDPHdr *) NETBUF_DATA(buf);
  len = netbufLen(buf);

  netdbgDisplay(NETDEBUG_UDP, "netudp: Received upcoming packet\n");

  if(len<sizeof(NetUDPHdr))
    goto error;
  if(len<ntohs(hdr->len))
    goto error;

  /* checksum (facultative) */
  if(hdr->checksum)
  {
    if(netudpChecksum(buf,attrs,len))
    {
      netdbgDisplay(NETDEBUG_UDP, "netudp: Checksum error.\n");
      goto error;
    }
  }

  sport = ntohs(hdr->sport);
  dport = ntohs(hdr->dport);

  /* source and destination port */
  netattrSet(attrs, NETATTR_UDP_FROM, sport);
  netattrSet(attrs, NETATTR_UDP_TO,   dport);

  if(!(thread = netudpLookup(dport)))
  {
    netdbgPrintf(NETDEBUG_UDP, "netudp: Nobody listens to port %d.\n",
                 dport);
    goto error;
  }

  /* remove header */
  buf->start+=sizeof(NetUDPHdr);

  netmodSendUpThread(thread,buf,attrs);
  return;

error:
  netdbgPrintf(NETDEBUG_UDP, "netudp: Discarding upcoming packet.\n");
  netbufFree(buf);
  NETATTR_FREE(attrs);
}

static void netudpDown(NetBuf buf, NetAttrs attrs)
{
  NetUDPHdr *hdr;
  unsigned short tmp, len;

  netdbgDisplay(NETDEBUG_UDP, "netudp: Received downgoing packet\n");

  if(!netbufAddHead(&buf,sizeof(NetUDPHdr)))
    goto error;

  hdr = (NetUDPHdr *) NETBUF_DATA(buf);

  /* source port, dest port */
  if(!netattrGet(attrs,NETATTR_UDP_FROM,&tmp))
    goto error;
  hdr->sport = htons(tmp);
  if(!netattrGet(attrs,NETATTR_UDP_TO,&tmp))
    goto error;
  hdr->dport = htons(tmp);

  /* length */
  len = netbufLen(buf);
  hdr->len = htons(len);

  /* UDP BRAINDAMAGE: I DON'T KNOW THE SOURCE IP ADDRESS YET, SO
   * I CAN'T CALCULATE THE CHECKSUM!!!!!!!!!!!
   */
#if 0

  if(!netattrGet(attrs,NETATTR_UDP_NOCHECKSUM,&tmp))
  {
    if((tmp=netudpChecksum(buf,attrs,len))==0)
      tmp=0xFFFF;
  }
  else
  {
    tmp=0;
  }
#endif
  tmp=0;
  hdr->checksum = tmp;

  netattrSet(attrs, NETATTR_IP_PROTOCOL, NETIP_PROTOCOL_UDP);

  /* Send down packet */
  netcfgSendNextDown(NETMODULE_UDP, buf, attrs);
  return;

error:
  netbufFree(buf);
  NETATTR_FREE(attrs);
}

static void netudpListen(ThreadId thread, NetAttrs attrs, long port)
{
  /* We don't need attributes... */
  NETATTR_FREE(attrs);

  if(port<0 || port>NETUDP_MAXPORTNR)
  {
    netdbgDisplay(NETDEBUG_UDP, "netudp: Invalid port number.\n");
    return;
  }

  /* port nr. 0 is special: allocate a free port yourself */
  /* netudp_ports[pos] (should) contains the first free port */

  if(port==0)
  {
    port = netudp_ports[0];
    if(netudp_ports[port])
    {
      while(port<NETUDP_MAXPORTNR+1 && netudp_ports[port])
        port++;
      netudp_ports[0]=port;
    }
  }
  else if(netudp_ports[port])
  {
    netdbgDisplay(NETDEBUG_UDP, "netudp: Port already listened!\n");
    netmodListenReplyThread(thread,0,-1);
    return;
  }

  netdbgPrintf(NETDEBUG_UDP,
               "netudp: Thread %d successfully registered for port %d\n",
               thread, port);
  netudp_ports[port]=thread;

  netmodListenReplyThread(thread,0,port);
}

static void netudpClose(NetAttrs attrs, long port)
{
  /* We don't need attributes... */
  NETATTR_FREE(attrs);

  if(port<0 || port>NETUDP_MAXPORTNR)
  {
    netdbgDisplay(NETDEBUG_UDP, "netudp: Invalid port number.\n");
    return;
  }

  if(port >= NETUDP_FIRST_USER_PORT && netudp_ports[0]>port)
    netudp_ports[0]=port;

  netudp_ports[port]=0;
}
