/*
    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/NetDevice.c,v $
    Affiliation:           ETH Zuerich, TIK
    Version:               $Revision: 2922 $
    Last Date of Change:   $Date: 2007-03-19 17:48:52 +0100 (Mon, 19 Mär 2007) $  by: $Author: baderma $
 
*/

#include "NetDevice.h"
#include "NetDebug.h"
#include "NetSyscall.h"
#include "Support.h"
#include "HashList.h"
#include "List.h"
#include "NetARP.h"
#include "NetDevQueue.h"
#include "NetMemoryManager.h"



static u16 netdevCount;
List netdevList;
HashList netdevHash;

short netdevGetType(u8 type)
/* function to map if types to NETATTRs */
{
  switch (type)
  {
  case IFT_ETHER:
    return NETATTR_TYPE_ETHERNET;
  case IFT_UNKNOWN:
  default:
    return NETATTR_TYPE_UNKOWN;
  }
}

static u32 getBaudrate(u8 type)
{
  return 0; /* XXX not yet implemented */
}

int netdevGet(u16 devNr, NetDevice* this)
{
  if ( hashListGet(netdevHash, (void **) this, devNr) != HASHOK )
    return 0;
  else
    return 1;
}

int netdevAdd(ThreadId device, u16 major, u16 minor, u8 type, char* name)
{
  NetDevice this;
  unsigned char hwAddr[12];

  if ( netMMAlloc((Address *) &this, sizeof(NetDeviceDesc) ) != NMM_OK )
  {
    netdbgPrintf(NETDEBUG_DEVICES,
                 "netdevAdd: can not alloc new Device %i (%i %i)\n",
                 (int) device, major, minor);
    return 0;
  }

  if ( listAddAtEnd(netdevList, this, NULL) != LIST_OK )
  {
    netdbgPrintf(NETDEBUG_DEVICES,
                 "netdevAdd: can not add new Device %i (%i %i) to list\n",
                 (int) device, major, minor);
    netMMFree(this);
    return 0;
  }
  this->index = netdevCount++;
  if ( hashListAdd(netdevHash, this, this->index) != HASHOK )
  {
    netdbgPrintf(NETDEBUG_DEVICES,
                 "netdevAdd: can not add new Device %i (%i %i) to list\n",
                 (int) device, major, minor);
    netMMFree(this);
    return 0;
  }

  this->device = device;
  this->queue = 0;
  snprintf(this->name, IF_NAME_SIZE, "%s%hi", name, minor-1);
  this->type = type;
  this->linkstate = IFS_UNKNOWN;
  this->mtu = getMaxMTU(this->type);
  this->baudrate = getBaudrate(this->type);
  if ( netMMAlloc((Address *) &(this->addrList),
                  sizeof(NetAddressDesc) ) != NMM_OK )
  {
    netdbgPrintf(NETDEBUG_DEVICES,
                 "netdevAdd: can not alloc new Address %i (%i %i)\n",
                 (int) device, major, minor);
    netMMFree(this);
    return 0;
  }
  this->addrList->next = NULL;
  this->addrList->prev = NULL;
  this->addrList->device = this;
  this->addrList->index = 0;
#ifdef NET_STATISTICS

  this->inpackets = 0;
  this->inerrors = 0;
  this->outpackets = 0;
  this->outerrors = 0;
  this->collisions = 0;
  this->inbytes = 0;
  this->outbytes = 0;
  this->inmcasts = 0;
  this->outmcasts = 0;
  this->inqdrops = 0;
  this->noproto = 0;
  this->lastchange = 0;
#endif

  if ( netattrNew(&(this->addrList->addr)) )
  {
    switch (this->type)
    {
    case IFT_ETHER:
      ioGetAddr (this->device, hwAddr, 6);
      netdbgPrintf(NETDEBUG_DEVICES,
                   "netdevAdd: %s %02x:%02x:%02x:%02x:%02x:%02x\n",
                   this->name, hwAddr[0], hwAddr[1], hwAddr[2],
                   hwAddr[3], hwAddr[4], hwAddr[5]);
      netattrSet (this->addrList->addr,
                  NETATTR_ETH_LOCAL_0, hwAddr[0] << 8 | hwAddr[1]);
      netattrSet (this->addrList->addr,
                  NETATTR_ETH_LOCAL_1, hwAddr[2] << 8 | hwAddr[3]);
      netattrSet (this->addrList->addr,
                  NETATTR_ETH_LOCAL_2, hwAddr[4] << 8 | hwAddr[5]);
      /* set 0.0.0.0 as address so it is possible to send bootp
         request without knowing our address and the modules do 
         not need to change their behavour */
      netattrSet (this->addrList->addr, NETATTR_IP_LOCAL_0, 0);
      netattrSet (this->addrList->addr, NETATTR_IP_LOCAL_1, 0);
      netattrSet (this->addrList->addr, NETATTR_IP_LOCAL_2, 0);
      netattrSet (this->addrList->addr, NETATTR_IP_LOCAL_3, 0);
      break;
    default:
      netdbgPrintf(NETDEBUG_DEVICES,
                   "netdevAdd: unknown device %i type!\n", this->type);
      break;
    }
    return 1;
  }
  else
  {
    netMMFree(this);
    return 0;
  }
}

u32 getMaxMTU(u8 type)
{
  return 1500; /* XXX to be implemented */
}

void netdevStartAll(void)
{
  NetDevice t;
  char buf[16];

  if ( listGetFirst(netdevList,(void **) &t) != LIST_OK )
  {
    netdbgPrintf(NETDEBUG_DEVICES,
                 "netdevStartAll: DAMN, no DeviceQ to start\n");
    return;
  }
  do
  {
    /* start new async device queue thread */
    if (t && t->device && t->queue == 0 )
    {
      snprintf(buf, 16, "NetDeviceQ(%i)", t->device);
      if ( tmStart(&(t->queue), netDevQueue, t, buf) != TM_STARTOK )
      {
        netdbgPrintf(NETDEBUG_DEVICES,
                     "netdevStartAll: DAMN, can not start the DeviceQ\n");
      }
      netdbgPrintf(NETDEBUG_DEVICES,
                   "netdevStartAll: DeviceQ %s started\n", t->name);
    }
  }
  while ( listGetNext(netdevList, (void **) &t) == LIST_OK && t != NULL );
}

int netdevInit(void)
{
  if ( (netdevList = listNew() ) == NULL )
    return 0;
  if ( (netdevHash = hashListNew() ) == NULL )
  {
    listFree(netdevList);
    return 0;
  }
  netdevCount = 0;

  return 1;
}

int netdevGetHWAddr (int deviceNr, NetAttr* avec, unsigned long *len)
{
  NetDevice		this;

  if (! netdevGet ( (u16) deviceNr, &this) )
  {
    netdbgPrintf(NETDEBUG_DEVICES,
                 "netdevGetHWAddr: failed to get device(%d)!\n", deviceNr );
    return 0;
  }
  switch ( this->type )
  {
  case IFT_ETHER:
    if ( *len < 3 )
      return 0;
    *len = 3;
    netattrGet(this->addrList->addr, NETATTR_ETH_LOCAL_0,
               &(avec[0].data));
    netattrGet(this->addrList->addr, NETATTR_ETH_LOCAL_1,
               &(avec[1].data));
    netattrGet(this->addrList->addr, NETATTR_ETH_LOCAL_2,
               &(avec[2].data));
    avec[0].key = NETATTR_ETH_LOCAL_0;
    avec[1].key = NETATTR_ETH_LOCAL_1;
    avec[2].key = NETATTR_ETH_LOCAL_2;
    return 1;
  default:
    return 0;
  }
}

int netdevSetAddr (int deviceNr, NetAttr* avec, unsigned long len,
                   unsigned long index)
{
  NetAddress		thisAddr;
  NetDevice		this;
  Message			msg,
  reply;
  u8				pa[ARP_MAX_PA_SIZE];
  u8                              pa6[ARP_MAX_PA_SIZE];

  unsigned short	i,
  val,
  ip0,
  ip1,
  ip2,
  ip3;

  u16                            ip6[8];

  ThreadId				from;

  if (! netdevGet ( (u16) deviceNr, &this) )
  {
    netdbgPrintf(NETDEBUG_DEVICES,
                 "netdevSetAddr: failed to get device!(%d)\n", deviceNr );
    return 0;
  }
  thisAddr = this->addrList;
  while (thisAddr->next && thisAddr->index != index)
  {
    thisAddr = thisAddr->next;
  }

  if (thisAddr->index == index)
  {
    for (i = 0; i < len; i++)
    {
      if (! netattrSet (thisAddr->addr, avec[i].key, avec[i].data) )
        return 0;
    }
    netdbgPrintf (NETDEBUG_DEVICES,
                  "netdevSetAddr: dumping Address\n");
    netdbgDumpAttr (NETDEBUG_DEVICES, thisAddr->addr);
  }
  else
  {
    if ( netMMAlloc((Address *) &(thisAddr->next),
                    sizeof(NetAddressDesc) ) != NMM_OK )
    {
      netdbgPrintf(NETDEBUG_DEVICES,
                   "netdevSetAddr: can not alloc new Address %i\n",
                   (int) this->device);
      return 0;
    }
    (thisAddr->next)->prev = thisAddr;
    thisAddr = thisAddr->next;
    thisAddr->next = NULL;
    thisAddr->device = this;
    thisAddr->index = index;
    if ( ! netattrNew(&(thisAddr->addr)) )
    {
      netMMFree(thisAddr);
      return 0;
    }
    for (i = 0; i < len; i++)
    {
      if (! netattrSet (thisAddr->addr, avec[i].key, avec[i].data) )
        return 0;
    }
    netdbgPrintf (NETDEBUG_DEVICES,
                  "netdevSetAddr: dumping Address 2\n");
    netdbgDumpAttr (NETDEBUG_DEVICES, thisAddr->addr);
  }

  // do gratious ARP
  msg.id = NET_ARP_REQ;
  reply.id = ANYMSGTYPE;
  msg.msg.netARPReq.device = deviceNr;



  if ( netattrGet(thisAddr->addr, NETATTR_TYPE_IPv6, &val) )
  {
    msg.msg.netARPReq.proto = ARP_TYPE_IPv6;
    if ( ! ( netattrGet(thisAddr->addr, NETATTR_IP6_SOURCEADDRESS_0, &ip6[0]) &&
             netattrGet(thisAddr->addr, NETATTR_IP6_SOURCEADDRESS_1, &ip6[1]) &&
             netattrGet(thisAddr->addr, NETATTR_IP6_SOURCEADDRESS_2, &ip6[2]) &&
             netattrGet(thisAddr->addr, NETATTR_IP6_SOURCEADDRESS_3, &ip6[3]) &&
             netattrGet(thisAddr->addr, NETATTR_IP6_SOURCEADDRESS_4, &ip6[4]) &&
             netattrGet(thisAddr->addr, NETATTR_IP6_SOURCEADDRESS_5, &ip6[5]) &&
             netattrGet(thisAddr->addr, NETATTR_IP6_SOURCEADDRESS_6, &ip6[6]) &&
             netattrGet(thisAddr->addr, NETATTR_IP6_SOURCEADDRESS_7, &ip6[7]) ) )
    {
      netdbgPrintf(NETDEBUG_DEVICES, "NetDevice: can not get address\n");
      return 0;
    }
    netdbgPrintf(NETDEBUG_DEVICES, "NetDevice: %x %x %x %x %x %x %x %x\n", ip6[0], ip6[1], ip6[2], ip6[3], ip6[4], ip6[5] ,ip6[6], ip6[7]);
    pa6[0] = ip6[0] >> 8;
    pa6[1] = ip6[0] & 0xff;
    pa6[2] = ip6[1] >> 8;
    pa6[3] = ip6[1] & 0xff;
    pa6[4] = ip6[2] >> 8;
    pa6[5] = ip6[2] & 0xff;
    pa6[6] = ip6[3] >> 8;
    pa6[7] = ip6[3] & 0xff;
    pa6[8] = ip6[4] >> 8;
    pa6[9] = ip6[4] & 0xff;
    pa6[10] = ip6[5] >> 8;
    pa6[11] = ip6[5] & 0xff;
    pa6[12] = ip6[6] >> 8;
    pa6[13] = ip6[6] & 0xff;
    pa6[14] = ip6[7] >> 8;
    pa6[15] = ip6[7] & 0xff;
    netdbgPrintf(NETDEBUG_DEVICES, "NetDevice: 1:%x %x %x %x %x %x %x %x\n",
                 pa6[0], pa6[1], pa6[2], pa6[3], pa6[4], pa6[5] ,pa6[6], pa6[7]);
    netdbgPrintf(NETDEBUG_DEVICES, "NetDevice: 2:%x %x %x %x %x %x %x %x\n",
                 pa6[8], pa6[9], pa6[10], pa6[11], pa6[12], pa6[13] ,pa6[14], pa6[15]);

    msg.msg.netARPReq.pa = pa6;
    msg.msg.netARPReq.pa_len = 16;

    //return 0;	// not implemented


  }
  else if ( netattrGet(thisAddr->addr, NETATTR_TYPE_IPv4, &val) )
  {
    msg.msg.netARPReq.proto = ARP_TYPE_IPv4;
    if ( ! ( netattrGet(thisAddr->addr, NETATTR_IP_LOCAL_0, &ip0) &&
             netattrGet(thisAddr->addr, NETATTR_IP_LOCAL_1, &ip1) &&
             netattrGet(thisAddr->addr, NETATTR_IP_LOCAL_2, &ip2) &&
             netattrGet(thisAddr->addr, NETATTR_IP_LOCAL_3, &ip3) ) )
    {
      netdbgPrintf(NETDEBUG_DEVICES, "NetDevice: can not get address\n");
      return 0;
    }
    pa[0] = ip0;
    pa[1] = ip1;
    pa[2] = ip2;
    pa[3] = ip3;
    msg.msg.netARPReq.pa = pa;
    msg.msg.netARPReq.pa_len = 4;

    netdbgPrintf(NETDEBUG_ARP, "DEVICE SENDING TO ARP THREAD\n");
    if (genericSyscall (arp_main_thread, &msg, &reply) != TM_MSGRECVOK )
    {
      printf ("NetDevice: failed to send ARP-Request!\n");
      return 0;
    }

    if ( reply.msg.netARPReply.status != ARP_NOTFOUND )
    {
      printf ("NetDevice: address already in use!\n");
      return 0;
    }

  }
  else
  {
    return 0;
  }
  /*	netdbgPrintf(NETDEBUG_ARP, "DEVICE SENDING TO ARP THREAD\n");
  	if (genericSyscall (arp_main_thread, &msg, &reply) != TM_MSGRECVOK ) {
  		printf ("NetDevice: failed to send ARP-Request!\n");
  		return 0;
  	}  
  	
  	if ( reply.msg.netARPReply.status != ARP_NOTFOUND ) {
  		printf ("NetDevice: address already in use!\n");
  		return 0;
  	}
  */
  return 1;
}

int netdevIsLocal(NetDevice this, NetAttr *avec, unsigned long len)
{
  NetAddress a = this->addrList;
  unsigned long i;
  u16 value;
  unsigned int isUnspecified  = 1;

  //netdbgPrintf(NETDEBUG_IP6, "NetDevice: netdevIsLocal called\n");

  while ( a )
  {
    for ( i = 0; i < len && netattrGet(a->addr, avec[i].key, &value); i++)
    {
      //printf ("NetDevice: comparing(%d): local: %x, received: %x\n", i, value, avec[i].data );
      if ( value != avec[i].data )
        break;
      if(value != 0x0)
        isUnspecified = 0; //(jb) it is not the unspecified address '::'
    }
    if ( i >= len && isUnspecified == 0)
      return 1;
    a = a->next;
  }
  return 0;
}
