/*
    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/Modules/ARP/NetARPLookup.c,v $
    Affiliation:           ETH Zuerich, TIK
    Version:               $Revision: 2936 $
    Last Date of Change:   $Date: 2007-03-21 15:23:42 +0100 (Mit, 21 Mär 2007) $  by: $Author: baderma $
 
*/
#include "NetARP.h"
#include "NetARPModules.h"
#include "NetDebug.h"
#include "Syscall.h"
//#include "NetIPv6NeighborDiscovery.h"
#include "NetBitFields.h"

void netARPLookupInit(NetModule this)
{
  return;
}

int netARPLookupConfig(NetModule this, Message *msg)
{
  return 0;
}

int IPv6NeighborDetermination(NetAttrs meta, u16 proto, u16 device, u8* pa, u8 pa_len, u8* ha, u8* ha_len)
{
  int err = 0;
  u16 v;



  netdbgPrintf(NETDEBUG_ARP, "IPv6NeighborDetermination: starting\n");


  // Check if this is a forwarded packet
  if(!(netattrGet(meta, NETATTR_OSI_4_TYPE, &v) && v == NETATTR_TYPE_IPv4))
  {
    //this is not a forwarded packet


    if (! netattrGet(meta, NETATTR_IP6_DESTINATIONADDRESS_0, &v) )
      return ARP_NOTFOUND;

    netdbgPrintf(NETDEBUG_ARP, "IPv6NeighborDetermination:DESTINATIONADDRESS_0: %x\n",v);
    pa[0] = WBYTGET(v,8);
    pa[1] = WBYTGET(v,0);

    if (! netattrGet(meta, NETATTR_IP6_DESTINATIONADDRESS_1, &v) )
      return ARP_NOTFOUND;
    netdbgPrintf(NETDEBUG_ARP, "IPv6NeighborDetermination:DESTINATIONADDRESS_1: %x\n",v);
    pa[2] = WBYTGET(v,8);
    pa[3] = WBYTGET(v,0);

    if (! netattrGet(meta, NETATTR_IP6_DESTINATIONADDRESS_2, &v) )
      return ARP_NOTFOUND;
    netdbgPrintf(NETDEBUG_ARP, "IPv6NeighborDetermination:DESTINATIONADDRESS_2: %x\n",v);
    pa[4] = WBYTGET(v,8);
    pa[5] = WBYTGET(v,0);

    if (! netattrGet(meta, NETATTR_IP6_DESTINATIONADDRESS_3, &v) )
      return ARP_NOTFOUND;
    netdbgPrintf(NETDEBUG_ARP, "IPv6NeighborDetermination:DESTINATIONADDRESS_3: %x\n",v);
    pa[6] = WBYTGET(v,8);
    pa[7] = WBYTGET(v,0);

    if (! netattrGet(meta, NETATTR_IP6_DESTINATIONADDRESS_4, &v) )
      return ARP_NOTFOUND;
    netdbgPrintf(NETDEBUG_ARP, "IPv6NeighborDetermination:DESTINATIONADDRESS_4: %x\n",v);
    pa[8] = WBYTGET(v,8);
    pa[9] = WBYTGET(v,0);

    if (! netattrGet(meta, NETATTR_IP6_DESTINATIONADDRESS_5, &v) )
      return ARP_NOTFOUND;
    netdbgPrintf(NETDEBUG_ARP, "IPv6NeighborDetermination:DESTINATIONADDRESS_5: %x\n",v);
    pa[10] = WBYTGET(v,8);
    pa[11] = WBYTGET(v,0);

    if (! netattrGet(meta, NETATTR_IP6_DESTINATIONADDRESS_6, &v) )
      return ARP_NOTFOUND;
    netdbgPrintf(NETDEBUG_ARP, "IPv6NeighborDetermination:DESTINATIONADDRESS_6: %x\n",v);
    pa[12] = WBYTGET(v,8);
    pa[13] = WBYTGET(v,0);

    if (! netattrGet(meta, NETATTR_IP6_DESTINATIONADDRESS_7, &v) )
      return ARP_NOTFOUND;
    netdbgPrintf(NETDEBUG_ARP, "IPv6NeighborDetermination:DESTINATIONADDRESS_7: %x\n",v);
    pa[14] = WBYTGET(v,8);
    pa[15] = WBYTGET(v,0);
  }
  else
  {
    // this is a forwarded packet

    if (! netattrGet(meta, NETATTR_IP6_SOURCEADDRESS_0, &v) )
      return ARP_NOTFOUND;

    netdbgPrintf(NETDEBUG_ARP, "IPv6NeighborDetermination:DESTINATIONADDRESS_0: %x\n",v);
    pa[0] = WBYTGET(v,8);
    pa[1] = WBYTGET(v,0);

    if (! netattrGet(meta, NETATTR_IP6_SOURCEADDRESS_1, &v) )
      return ARP_NOTFOUND;
    netdbgPrintf(NETDEBUG_ARP, "IPv6NeighborDetermination:DESTINATIONADDRESS_1: %x\n",v);
    pa[2] = WBYTGET(v,8);
    pa[3] = WBYTGET(v,0);

    if (! netattrGet(meta, NETATTR_IP6_SOURCEADDRESS_2, &v) )
      return ARP_NOTFOUND;
    netdbgPrintf(NETDEBUG_ARP, "IPv6NeighborDetermination:DESTINATIONADDRESS_2: %x\n",v);
    pa[4] = WBYTGET(v,8);
    pa[5] = WBYTGET(v,0);

    if (! netattrGet(meta, NETATTR_IP6_SOURCEADDRESS_3, &v) )
      return ARP_NOTFOUND;
    netdbgPrintf(NETDEBUG_ARP, "IPv6NeighborDetermination:DESTINATIONADDRESS_3: %x\n",v);
    pa[6] = WBYTGET(v,8);
    pa[7] = WBYTGET(v,0);

    if (! netattrGet(meta, NETATTR_IP6_SOURCEADDRESS_4, &v) )
      return ARP_NOTFOUND;
    netdbgPrintf(NETDEBUG_ARP, "IPv6NeighborDetermination:DESTINATIONADDRESS_4: %x\n",v);
    pa[8] = WBYTGET(v,8);
    pa[9] = WBYTGET(v,0);

    if (! netattrGet(meta, NETATTR_IP6_SOURCEADDRESS_5, &v) )
      return ARP_NOTFOUND;
    netdbgPrintf(NETDEBUG_ARP, "IPv6NeighborDetermination:DESTINATIONADDRESS_5: %x\n",v);
    pa[10] = WBYTGET(v,8);
    pa[11] = WBYTGET(v,0);

    if (! netattrGet(meta, NETATTR_IP6_SOURCEADDRESS_6, &v) )
      return ARP_NOTFOUND;
    netdbgPrintf(NETDEBUG_ARP, "IPv6NeighborDetermination:DESTINATIONADDRESS_6: %x\n",v);
    pa[12] = WBYTGET(v,8);
    pa[13] = WBYTGET(v,0);

    if (! netattrGet(meta, NETATTR_IP6_SOURCEADDRESS_7, &v) )
      return ARP_NOTFOUND;
    netdbgPrintf(NETDEBUG_ARP, "IPv6NeighborDetermination:DESTINATIONADDRESS_7: %x\n",v);
    pa[14] = WBYTGET(v,8);
    pa[15] = WBYTGET(v,0);
  }

  if(pa[0] == 0xff && pa[1] == 0x02 &&
      pa[2] == 0x0 && pa[3] == 0x0 &&
      pa[4] == 0x0 && pa[5] == 0x0 &&
      pa[6] == 0x0 && pa[7] == 0x0 &&
      pa[8] == 0x0 && pa[9] == 0x0 &&
      pa[10] == 0x0 && pa[11] == 0x1 )
  {
    // (jb)this is a solicited-multicast-address
    // the corresponding ethernet-multicast-address is
    // contained in the solicited-multicast-address
    // sending the packet without arp-lookup
    ha_len = 6;
    ha[0] = 0x33;
    ha[1] = 0x33;
    ha[2] = pa[12];
    ha[3] = pa[13];
    ha[4] = pa[14];
    ha[5] = pa[15];
    err = ARP_FOUND;
  }
  else
  {

    //pa_len = 16;

    /*      netdbgPrintf(NETDEBUG_TEST, "IPv6NeighborDetermination: making a lookup: device: %d, addr: %x %x %x %x\n", */
    /*                   device, pa[pa_len-4], pa[pa_len-3], pa[pa_len-2], pa[pa_len-1] ); */

    //netdbgPrintf(NETDEBUG_ARP, "IPv6NeighborDetermination: making a lookup\n");
    err = arp_lookup(proto, device, pa, pa_len, ha, ha_len);
  }

  return err;
}


void netARPLookupMain(LWPool lw, ThreadArg arg)
{
  NetModule		modDesc = (NetModule) arg;
  NetBuf			data;
  NetAttrs		meta;
  Message msg;
  int err;
  ThreadId t;
  u16 proto, device, v, val[4];
  u8 *ha;
  u8 pa[ARP_MAX_PA_SIZE];
  u8 pa_len, ha_len;

  while (1)
  {
    netmodRecv(lw, &data, &meta);
    PACKSTART(modDesc);

    if ( netattrGet(meta, NETATTR_BROADCAST, &v ) )
    {
      /* broadcast so the ethernet address is automaticaly
      FF:FF:FF:FF:FF:FF */
      if (! netattrSet(meta, NETATTR_ETH_REMOTE_0, 0xFFFF) )
        goto fail;
      if (! netattrSet(meta, NETATTR_ETH_REMOTE_1, 0xFFFF) )
        goto fail;
      if (! netattrSet(meta, NETATTR_ETH_REMOTE_2, 0xFFFF) )
        goto fail;
      netdbgPrintf(NETDEBUG_ARP, "netARPLookup: broadcast\n");
      PACKDONE(modDesc);
      netmodSend(lw, CHAINNEXT(modDesc), data, meta);
      continue;
    }

    if ( !(	netattrGet(meta, NETATTR_OSI_3_TYPE, &v) &&
            netattrGet(meta, NETATTR_IF_TO, &device) ) )
    {
      goto fail;
    }
    switch ( v )
    {
    case NETATTR_TYPE_IPv4:
      proto = ARP_TYPE_IPv4;
      if (! netattrGet(meta, NETATTR_IP_REMOTE_0, &v) )
        goto fail;
      pa[0] = v;
      if (! netattrGet(meta, NETATTR_IP_REMOTE_1, &v) )
        goto fail;
      pa[1] = v;
      if (! netattrGet(meta, NETATTR_IP_REMOTE_2, &v) )
        goto fail;
      pa[2] = v;
      if (! netattrGet(meta, NETATTR_IP_REMOTE_3, &v) )
        goto fail;
      pa[3] = v;
      pa_len = 4;
      err = arp_lookup(proto, device, pa, pa_len, &ha, &ha_len);
      break;
    case NETATTR_TYPE_ARP:
      netdbgPrintf(NETDEBUG_ARP, "netARPLookup: ARP packet\n");
      PACKDONE(modDesc);
      netmodSend(lw, CHAINNEXT(modDesc), data, meta);
      continue;
    case NETATTR_TYPE_IPv6:
      netdbgPrintf(NETDEBUG_ARP, "netARPLookup: IPv6 packet received\n");

      if(netattrGet(meta, NETATTR_ETH_MULTICAST, &val[0]) &&
          netattrGet(meta, NETATTR_ETH_LOCAL_0, &val[1]) &&
          netattrGet(meta, NETATTR_ETH_LOCAL_1, &val[2]) &&
          netattrGet(meta, NETATTR_ETH_LOCAL_2, &val[3]))
      {
        //(jb)setting the correct destination multicast-link-layer-address
        if(!(netattrSet(meta, NETATTR_ETH_REMOTE_0, val[1]) &&
             netattrSet(meta, NETATTR_ETH_REMOTE_1, val[2]) &&
             netattrSet(meta, NETATTR_ETH_REMOTE_2, val[3])))
        {
          PACKERROR(modDesc);
          netmodRIP( lw, data, meta, "netARPLookup: failed to set multicast link layer address\n");
          continue;
        }

        //(jb) sending the packet without arp-lookup
        netdbgPrintf(NETDEBUG_TEST, "netARPLookup: sending an IPv6 multicast packet\n");
        netmodSend(lw, CHAINNEXT(modDesc), data, meta);
        continue;
      }

      //(jb)  performing an arp-lookup to get destination link-layer-address:
      proto = ARP_TYPE_IPv6;
      pa_len = 16;

      //consulting the oracle and setting the HW-address
      err = IPv6NeighborDetermination(meta, proto, device, pa, pa_len, &ha, &ha_len);
      break;

    default:
      PACKERROR(modDesc);
      netmodRIP( lw, data, meta, "netARPLookup: unknown protocol\n");
      continue;
    }

    switch ( err )
    {
    case ARP_FOUND:
      netdbgPrintf(NETDEBUG_ARP, "netARPLookup: lookup: ARP found!\n");
      /* set HW ADDRESS in NetAttrs and fire */
      if ( ha_len != 6 )
      {
        PACKERROR(modDesc);
        netmodRIP( lw, data, meta,
                   "netARPLookup: arp cache corrupted\n");
        continue;
      }
      v = ha[1] | (ha[0]<<8);
      if (! netattrSet(meta, NETATTR_ETH_REMOTE_0, v) )
        goto fail;
      v = ha[3] | (ha[2]<<8);
      if (! netattrSet(meta, NETATTR_ETH_REMOTE_1, v) )
        goto fail;
      v = ha[5] | (ha[4]<<8);
      if (! netattrSet(meta, NETATTR_ETH_REMOTE_2, v) )
        goto fail;
      break;
    case ARP_RACE:
      netdbgPrintf(NETDEBUG_ARP, "netARPLookup: lookup: ARP RACE ");
    case ARP_NOTFOUND:
      netdbgPrintf(NETDEBUG_ARP, "netARPLookup: lookup: ARP NOT found?\n");
      /* send request */
      msg.id = NET_ARP_REQ;
      msg.msg.netARPReq.proto = proto;
      msg.msg.netARPReq.device = device;
      msg.msg.netARPReq.pa = pa;
      msg.msg.netARPReq.pa_len = pa_len;
      netdbgPrintf(NETDEBUG_ARP, "netARPLookup: sending a message to ARP-Daemon\n");
      if ( tmMsgSend(arp_main_thread, &msg) != TM_MSGSENDOK )
      {
        PACKERROR(modDesc);
        netmodRIP( lw, data, meta,
                   "netARPLookup: tmMsgSend failed\n");
        continue;
      }
      t = arp_main_thread;
      /* wait for a reply of this lookup */
      /* this is how we implement a wait queue, but it is important
      	 that we get a reply or this will cause a deadlock */
      netdbgPrintf(NETDEBUG_ARP, "netARPLookup: waiting for a reply from ARP-Daemon\n");
      if ( tmMsgRecv(&t, NET_ARP_REPLY, &msg,
                     INFINITY) != TM_MSGRECVOK )
      {
        PACKERROR(modDesc);
        netmodRIP( lw, data, meta,
                   "netARPLookup: tmMsgRecv failed\n");
        continue;
      }
      if ( msg.msg.netARPReply.status != ARP_FOUND &&
           msg.msg.netARPReply.status != ARP_NOTFOUND )
      {
        PACKERROR(modDesc);
        netmodRIP( lw, data, meta,
                   "netARPLookup: arp_generate_req failed\n");
        continue;
      }
      /* verify response */
      if ( msg.msg.netARPReply.status != ARP_FOUND )
      {
        PACKERROR(modDesc);
        netmodRIP( lw, data, meta,
                   "netARPLookup: no such address\n");
        continue;
      }
      if ( msg.msg.netARPReply.ha_len != 6 )
      {
        PACKERROR(modDesc);
        netmodRIP( lw, data, meta,
                   "netARPLookup: arp cache corrupted\n");
        continue;
      }
      ha = msg.msg.netARPReply.ha;
      netdbgPrintf(NETDEBUG_ARP,
                   "arp_lookup: -> %02x:%02x:%02x:%02x:%02x:%02x\n",
                   ha[0], ha[1], ha[2], ha[3], ha[4], ha[5]);
      v = ha[1] | (ha[0]<<8);
      if (! netattrSet(meta, NETATTR_ETH_REMOTE_0, v) )
        goto fail;
      v = ha[3] | (ha[2]<<8);
      if (! netattrSet(meta, NETATTR_ETH_REMOTE_1, v) )
        goto fail;
      v = ha[5] | (ha[4]<<8);
      if (! netattrSet(meta, NETATTR_ETH_REMOTE_2, v) )
        goto fail;
      break;
    default:
      /* dump packet */
      PACKERROR(modDesc);
      netmodRIP( lw, data, meta, "netARPLookup: arp_lookup failed\n");
      continue;
    }
    PACKDONE(modDesc);
    netmodSend(lw, CHAINNEXT(modDesc), data, meta);
    continue;
fail:
    PACKERROR(modDesc);
    netmodRIP( lw, data, meta,
               "netARPLookup: failed to get/set NetAttrs\n");
    continue;
  }
}

