/*
    Copyright 2000 (c) by Claudio Jeker,
    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/NetPCB.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 $
	
*/
#include "NetPCB.h"
#include "HashList.h"
#include "NetSocket.h"
#include "NetSyscall.h"
#include "NetMemoryManager.h"
#include "NetByteorder.h"

static struct NetPCBTable tcpPCBTable, udpPCBTable;
static HashList socket2PCB;

static int netInitPCBHash(NetPCB **table, int elements, unsigned long *hashMask)
{
  long hashsize;
  int i = 0;

  if (elements <= 0 )
    return 0;
  /* round down to next 2^x number */
  for (hashsize = 1; hashsize <= elements; hashsize <<= 1)
    ;
  hashsize >>= 1;
  if ( netMMAlloc((Address *)table,
                  hashsize * sizeof(struct NetPCB *) ) != NMM_OK )
  {
    return 0;
  }
  for (i = 0; i < hashsize; i++)
    (*table)[i] = 0;
  *hashMask = hashsize - 1;
  return 1;
}

int netInitPCB(void)
{
  tcpPCBTable.first = NULL;

  udpPCBTable.first = NULL;

  /* set the last client port */
  tcpPCBTable.lastPort = 0;
  udpPCBTable.lastPort = 0;

  if (! netInitPCBHash( &(udpPCBTable.hashTable),
                        PCB_UDP_HASHSIZE, &udpPCBTable.hashMask ) )
  {
    return 0;
  }
  if (! netInitPCBHash( &(tcpPCBTable.hashTable),
                        PCB_TCP_HASHSIZE, &tcpPCBTable.hashMask ) )
  {
    netMMFree(udpPCBTable.hashTable);
    return 0;
  }
  if ( ( socket2PCB = hashListNew() ) == NULL )
  {
    netMMFree(tcpPCBTable.hashTable);
    netMMFree(udpPCBTable.hashTable);
    return 0;
  }

  return 1;
}

NetPCBTable netGetPCBTable(unsigned short domain, unsigned short protocol)
{
  /* domain is igored nut perhaps somewhen it is needed,
     especially if we have a diffrent pcb table for tcp over IPv4 and
     tcp over IPv6 */
  switch ( protocol )
  {
  case PROTO_TCP:
    return &tcpPCBTable;
  case PROTO_UDP:
    return &udpPCBTable;
  default:
    return 0;
  }
}

int netConfPCB(NetPCB pcb, ThreadId socket, Address privatePCB,
               unsigned short    so_domain,
               unsigned short    so_protocol)
{
  /* general init */
  pcb->next = NULL;
  pcb->prev = NULL;
  pcb->hashNext = NULL;
  pcb->hashPrev = NULL;
  pcb->so_domain = so_domain;
  pcb->so_protocol = so_protocol;

  pcb->privatePCB = privatePCB; /* XXX attach tcpPCB here */
  pcb->socket = socket;
  if (! netattrNew(&(pcb->attributes)) )
    return 0;
  return 1;
}


NetPCB netSocket2PCB(ThreadId socket)
{
  NetPCB t;

  if ( hashListGet(socket2PCB,(void **) &t, socket) == HASHOK )
    return t;
  return 0;
}

/* macro to get the HashTable list where the corresponding PCB lives */
/* USAGE: table is a NetPCBTable, l should be a part of the foreign address
          that changes often (size long), especially in IPv6 a good part of
		  the address should be choosed. fp and lp are foreign and local port
		  of th protocol (normaly shorts but longs would also go).
   RETURN: pointer to the first hash table queue entry (can be NULL) */
#define NET_HASH_PCB(table, l, fp, lp) \
	&(table->hashTable[((long)l + (long) fp + (long) lp) & table->hashMask])

NetPCB netAddress2PCB(NetPCBTable table, long l1, long l2, long l3,
                      int (*compare)(NetAttrs pcb_attr, NetAttrs find), NetAttrs find)
/* l1 - l3 should be often changing parts of the address e.g. part of
   foreign address, foreign port and local port */
{
  NetPCB *head= NET_HASH_PCB(table, l1, l2, l3);
  NetPCB hashEntry = *head;

  for(; hashEntry; hashEntry = hashEntry->hashNext)
  {
    if ( compare(hashEntry->attributes, find) )
    {
      /* move to front so the LFU (least freq used) moves
         to the end of the list (so often used PCBs are looked up
         faster */
      *(hashEntry->hashPrev) = hashEntry->hashNext;
      if ( hashEntry->hashNext )
        hashEntry->hashNext->hashPrev = hashEntry->hashPrev;
      if ( (*head) && (*head)->hashPrev )
        /* we know for sure that *head != NULL but paranoia */
        (*head)->hashPrev = &(hashEntry->hashNext);
      hashEntry->hashNext = *head;
      hashEntry->hashPrev = head;
      *head = hashEntry;
      return hashEntry;
    }
  }
  return NULL;
}
NetPCB netV6Address2PCB(NetPCBTable table, long l1[4], long l2, long l3,
                        int (*compare)(NetAttrs pcb_attr, NetAttrs find), NetAttrs find)
/* l1 - l3 should be often changing parts of the address e.g. part of
   foreign address, foreign port and local port */
{
  NetPCB *head= NET_HASH_PCB(table, l1[3], l2, l3);
  NetPCB hashEntry = *head;

  for(; hashEntry; hashEntry = hashEntry->hashNext)
  {
    if ( compare(hashEntry->attributes, find) )
    {
      /* move to front so the LFU (least freq used) moves
         to the end of the list (so often used PCBs are looked up
         faster */
      *(hashEntry->hashPrev) = hashEntry->hashNext;
      if ( hashEntry->hashNext )
        hashEntry->hashNext->hashPrev = hashEntry->hashPrev;
      if ( (*head) && (*head)->hashPrev )
        /* we know for sure that *head != NULL but paranoia */
        (*head)->hashPrev = &(hashEntry->hashNext);
      hashEntry->hashNext = *head;
      hashEntry->hashPrev = head;
      *head = hashEntry;
      return hashEntry;
    }
  }
  return NULL;
}

void netUnsetOptPCB(NetPCB pcb, NetAttr *attrVec, unsigned int len)
{
  /* we do not check for correctness, this is the duty of the caller */
  for ( len--; len >= 0; len-- )
  {
    netattrUnset(pcb->attributes, attrVec[len].key);
  }
}

int netSetOptPCB(NetPCB pcb, NetAttr *attrVec, unsigned int len)
{
  /* we do not check for correctness, this is the duty of the caller */
  for ( --len; (int) len >= 0; len-- )
  {
    //-CJ		printf("netSetOptPCB: setting [%i] %i -> %i\n", len,
    //-CJ				attrVec[len].key, attrVec[len].data );
    if ( !netattrSet(pcb->attributes, attrVec[len].key, attrVec[len].data) )
      return 0;
  }
  return 1;
}

int netBindPCB(NetPCBTable head, NetPCB pcb, int id,
               NetAttr *attrVec, unsigned int len)
{
  NetPCB *hashHead;
  u16 v;
  long l1[4], l2, l3;

  /* XXX we should verify the data we are getting, if id is
     XXX bind we have a local address and a local port 
     XXX connect we have remote address and a remote prot 
     XXX It should also be checked the protocol and the NetAttr types match*/
  if ( netSetOptPCB(pcb, attrVec, len) )
  {
    switch ( pcb->so_domain )
    {
    case AF_INET:
      netattrGet(pcb->attributes, NETATTR_IP_REMOTE_0, &v);
      l1[0] = v;
      netattrGet(pcb->attributes, NETATTR_IP_REMOTE_1, &v);
      l1[0] = v << 8;
      netattrGet(pcb->attributes, NETATTR_IP_REMOTE_2, &v);
      l1[0] = v << 16;
      netattrGet(pcb->attributes, NETATTR_IP_REMOTE_3, &v);
      l1[0] = v << 24;
      l1[0] = ntohl(l1[0]);
      netattrSet (pcb->attributes, NETATTR_TYPE_IPv4, 0);	// XXX
      break;
    case AF_INET6:
      netattrGet(pcb->attributes,  NETATTR_IP6_DESTINATIONADDRESS_0, &v);
      l1[0] = v;
      netattrGet(pcb->attributes,  NETATTR_IP6_DESTINATIONADDRESS_1, &v);
      l1[0] = v << 16;
      netattrGet(pcb->attributes,  NETATTR_IP6_DESTINATIONADDRESS_2, &v);
      l1[1] = v;
      netattrGet(pcb->attributes,  NETATTR_IP6_DESTINATIONADDRESS_3, &v);
      l1[1] = v << 16;
      netattrGet(pcb->attributes,  NETATTR_IP6_DESTINATIONADDRESS_4, &v);
      l1[2] = v;
      netattrGet(pcb->attributes,  NETATTR_IP6_DESTINATIONADDRESS_5, &v);
      l1[2] = v << 16;
      netattrGet(pcb->attributes,  NETATTR_IP6_DESTINATIONADDRESS_6, &v);
      l1[3] = v;
      netattrGet(pcb->attributes,  NETATTR_IP6_DESTINATIONADDRESS_7, &v);
      l1[3] = v << 16;
      l1[0] = ntohl(l1[0]);
      l1[1] = ntohl(l1[1]);
      l1[2] = ntohl(l1[2]);
      l1[3] = ntohl(l1[3]);
      netattrSet (pcb->attributes, NETATTR_TYPE_IPv6, 0);
    default:
      printf("unknown domain\n");
      return 0;
    }
    switch ( pcb->so_protocol )
    {
    case PROTO_TCP:
      netattrGet(pcb->attributes, NETATTR_TCP_REMOTE, &v);
      l2 = v;
      netattrGet(pcb->attributes, NETATTR_TCP_LOCAL, &v);
      l3 = v;
      break;
    case PROTO_UDP:
      netattrGet(pcb->attributes, NETATTR_UDP_REMOTE, &v);
      l2 = v;
      netattrGet(pcb->attributes, NETATTR_UDP_LOCAL, &v);
      l3 = v;
      break;
    default:
      printf("unknown protocol\n");
      return 0;
    }
    l1[0] = l1[1] = l1[2] = l1[3] = l2 = l3 = 0; /* XXX THIS IS A GIANT HACK */
    /* at it at the front of the corresponding hashTable */
    hashHead = NET_HASH_PCB(head, l1[0], l2, l3);
    if ( pcb->hashPrev )
      *(pcb->hashPrev) = pcb->hashNext;
    if ( pcb->hashNext )
      pcb->hashNext->hashPrev = pcb->hashPrev;
    if ( (*hashHead) && (*hashHead)->hashPrev )
      (*hashHead)->hashPrev = &(pcb->hashNext);
    pcb->hashNext = *hashHead;
    pcb->hashPrev = hashHead;
    *hashHead = pcb;
    return 1;
  }
  return 0;
}

int netAddPCB(NetPCBTable head, NetPCB pcb)
{
  /* hash add */
  if ( hashListAdd(socket2PCB, pcb, pcb->socket) != HASHOK )
    return 0;

  /* queue add */
  pcb->next = head->first;
  pcb->prev = &head->first;

  if ( head->first )
    head->first->prev = &pcb->next;
  head->first = pcb;

  /* init hash table entries */
  pcb->hashPrev = NULL;
  pcb->hashNext = NULL;

  return 1;
}

void netUnlinkPCB(NetPCB pcb)
{
  /* hash remove */
  hashListRemove(socket2PCB, pcb->socket);

  /* queue remove */
  if ( pcb->next )
    pcb->next->prev = pcb->prev;
  /* for first element pcb->prev points to &head->first so we do not
     the list when removing the first or the last remaining pcb */
  *(pcb->prev) = pcb->next;

  /* hash remove */
  if ( pcb->hashNext )
    pcb->hashNext->hashPrev = pcb->hashPrev;
  /* for first element pcb->hashPrev points to the hashTable[%HASH] entry
     so we do not break the list when removing the first or the last 
     remaining pcb hash entry in hashTable[%HASH] 
     The only problem is that the hash list can only be traversed from
     head to tail*/
  if ( pcb->hashPrev ) /* if not, unbinded PCB */
    *(pcb->hashPrev) = pcb->hashNext;

}

NetPCB netAllocPCB(void)
{
  NetPCB t;

  if ( netMMAlloc((Address *)&t, sizeof(struct NetPCB) ) == NMM_OK )
    return t;
  return 0;
}

void netFreePCB(NetPCB pcb)
{
  if (! pcb )
    return;

  /* NOTE: the privatePCB has to be emptied and freed by the
  		 protocol handler */

  NETATTR_FREE(pcb->attributes);
  netMMFree(pcb);
}

