/*
    Copyright 2000 (c) by Marc Wegmueller,
    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/ICMP6/NetICMP6ModuleUp.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 "Syscall.h"
#include "Support.h"
#include "IO.h"

#include "NetModulesNew.h"
#include "NetAttr.h"
#include "LWThreads.h"
#include "NetBitFields.h"
#include "NetByteorder.h"
#include "NetDebug.h"
#include "NetDevice.h"
#include "NetIP6Module.h"
#include "NetICMP6Checksum.h"
#include "NetICMP6Module.h"
#include "NetARP.h"
#include "NetMemoryManager.h"

static void neticmpEchoUp(LWPool lw, NetBuf data, NetAttrs meta, NetModuleId id);
static void neticmpReplyUp(LWPool lw, NetBuf data, NetAttrs meta, NetModuleId id);
static void neticmpNeighborSolicitation(LWPool lw, NetBuf data, NetAttrs meta, NetModuleId id, ThreadArg arg, u8* pa, u8* ha );
//static void neticmpNeighborSolicitation(LWPool lw, NetBuf data, NetAttrs meta, NetModuleId id, ThreadArg arg );
static void neticmpNeighborAdvertisement(LWPool lw, NetBuf data, NetAttrs meta, NetModuleId id, ThreadArg arg);

void netICMP6ModuleUpInit (NetModule this)
{
  // XXX what should I do?
  return;
}


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


void netICMP6ModuleUpMain (LWPool lw, ThreadArg arg)
{
  NetModule		modDesc = (NetModule) arg;
  NetICMP6Hdr		*hdr;
  unsigned short	tLen, checksum, calculated;
  NetBuf			data;
  NetAttrs		meta;

  u8  pa[17];
  u8  ha[7];

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

    netdbgPrintf(NETDEBUG_ICMP6, "netICMP6up: netICMP6ModuleUp: received\n");

    hdr = (NetICMP6Hdr*) NETBUF_DATA(data);

    /* verify minimum lenght */
    tLen = netbufLen(data);
    if (tLen < sizeof(NetICMP6Hdr) )
    {
      PACKERROR(modDesc);
      netmodRIP (lw, data, meta, "netICMP6: Packet to small\n");
      continue;
    }

    /* verify checksum */
    checksum = ntohs(hdr->checksum);
    hdr->checksum = 0;
    calculated = netICMP6Checksum (lw, data, tLen, meta, arg);
    if ( calculated != checksum )
    {
      PACKERROR(modDesc);
      netdbgPrintf(NETDEBUG_ICMP6, "Checksum should be:%x, but is:%x\n", checksum, calculated);
      netmodRIP (lw, data, meta, "netICMP6: Checksum failed\n");
      continue;
    }
    else
    {
      netdbgPrintf(NETDEBUG_ICMP6, "Entering checksum passed\n" );
    }
    /* set all attributes */
    if ( !(	netattrSet(meta, NETATTR_ICMP6_TYPE, hdr->type ) &&
            netattrSet(meta, NETATTR_ICMP6_CODE, hdr->code )  &&
            netattrSet(meta, NETATTR_ICMP6_CHECKSUM, checksum ) &&
            netattrSet(meta, NETATTR_ICMP6_IDENTIFIER, ntohs(hdr->bitfield0) ) &&
            netattrSet(meta, NETATTR_ICMP6_SEQUENCENUMBER, ntohs(hdr->bitfield1) ) ))
    {
      PACKERROR(modDesc);
      netmodRIP (lw, data, meta, "netICMP6: failed to set attributes\n");
      continue;
    }
    /* Strip header (4 Bytes) */
    data->start += sizeof(NetICMP6Hdr);

    netdbgPrintf(NETDEBUG_ICMP6, "netICMP6up: NetICMP6ModuleUp: NETATTR_ICMP6_TYPE %d \n", hdr->type );
    netdbgPrintf(NETDEBUG_ICMP6, "netICMP6up: NetICMP6ModuleUp: NETATTR_ICMP6_CODE %d \n", hdr->code );
    netdbgPrintf(NETDEBUG_ICMP6, "netICMP6up: NetICMP6ModuleUp: NETATTR_ICMP6_CHECKSUM %x \n", checksum );

    /*
       		netdbgDumpBuf(NETDEBUG_ICMP6, data);
    netdbgDisplay(NETDEBUG_ICMP6, "*************************\n");
    netdbgDumpAttr(NETDEBUG_ICMP6, meta);
    netdbgDisplay(NETDEBUG_ICMP6, "*************************\n");
    */

    /* check type */
    switch (hdr->type)
    {
    case NETICMP6_TYPE_ECHO_REQUEST:
      netdbgPrintf(NETDEBUG_ICMP6, "NetICMP6ModuleUp: NETICMP6_TYPE_ECHO_REQUEST \n" );
      PACKDONE(modDesc);
      neticmpEchoUp(lw, data, meta, CHAINNEXT(modDesc));
      break;
    case NETICMP6_TYPE_ECHO_REPLY:
      netdbgPrintf(NETDEBUG_ICMP6, "NetICMP6ModuleUp: NETICMP6_TYPE_ECHO_REPLY \n" );
      PACKDONE(modDesc);
      neticmpReplyUp(lw, data, meta, CHAINNEXT(modDesc));
      break;
    case NETICMP6_TYPE_NeighborSolicitation:
      netdbgPrintf(NETDEBUG_ICMP6, "NetICMP6ModuleUp: NETICMP6_TYPE_NeighborSolicitation \n" );
      PACKDONE(modDesc);
      neticmpNeighborSolicitation(lw, data, meta, CHAINNEXT(modDesc), arg, &pa[0], &ha[0]);
      break;
    case NETICMP6_TYPE_NeighborAdvertisement:
      netdbgPrintf(NETDEBUG_ICMP6, "NetICMP6ModuleUp: NETICMP6_TYPE_NeighborAdvertisementn \n" );
      PACKDONE(modDesc);
      neticmpNeighborAdvertisement(lw, data, meta, CHAINNEXT(modDesc), arg);
      break;
    default:
      PACKERROR(modDesc);
      netmodRIP (lw, data, meta, "netICMP6: Unknown type\n");
      break;
    }
  }
}

static void neticmpEchoUp(LWPool lw, NetBuf data, NetAttrs meta, NetModuleId id)
{
  unsigned short	val1;
  unsigned long checksum = 0;


  /* We resend exactly the same data to the sender
   * Only 'type' is changed from ECHO to ECHOREPLY */
  /* Re-calculate checksum (see RFC 1141)
   * hdr->checksum=htons(netICMP6Checksum(buf,netbufLen(buf)));
   * ~C' = ~(C + (-m) + m') = ~C + (m - m') = ~C + m + ~m' 
   */

  netdbgPrintf(NETDEBUG_ICMP6, "netICMP6up: netICMP6EchoUp: received\n");

  if ( netattrGet(meta, NETATTR_ICMP6_CHECKSUM, &val1) )
  {
    checksum = val1 +
               (long)(unsigned short)(NETICMP6_TYPE_ECHO_REQUEST << 8)+
               (long)(unsigned short)~(NETICMP6_TYPE_ECHO_REPLY << 8);
    checksum = (checksum & 0xFFFF) + (checksum >> 16);  /* add carry */
  }
  else
  {
    checksum = 0;
  }

  if (!(	/* XXX this is a hack to get ping working */
        netattrGet(meta, NETATTR_IF_FROM, &val1) &&
        netattrSet(meta, NETATTR_IF_TO, val1) &&
        netattrGet(meta, NETATTR_IN_TYPE, &val1) &&
        netattrSet(meta, NETATTR_OSI_2_TYPE, val1) &&
        /* reset the new calculated checksum */
        netattrSet(meta, NETATTR_ICMP6_CHECKSUM, checksum) &&
        /* and finaly set the ICMP type to ECHOREPLY */
        netattrSet(meta, NETATTR_ICMP6_TYPE, NETICMP6_TYPE_ECHO_REPLY) ))
  {
    /* one of the netattr funtctions failed */
    netmodRIP (lw, data, meta, "netICMP6: Failed to set new attributes!\n");
    return;
  }

  /* now unset those IP attributes that need to be reset */
  /* XXX this is mainly the TTL field and checksum */
  netattrGet(meta, NETATTR_OSI_3_TYPE, &val1);

  switch ( val1 )
  {
  case NETATTR_TYPE_IPv6:
    netattrUnset(meta, NETATTR_IP6_FLOWLABEL_0);
    netattrUnset(meta, NETATTR_IP6_FLOWLABEL_1);
    break;
  default:
    netdbgPrintf(0, "netICMP6: unknown OSI 3 protocol (%i)\n", val1);
    return;
  }
  /* normaly we would leave the stack so set NETATTR_SENDTO_LOOP */
  netattrSet(meta, NETATTR_SENDTO_LOOP, NETATTR_TYPE_ICMP6);

  /* Finished: send the packet */
  netmodSend (lw, id, data, meta);

  return;
}

static void neticmpReplyUp(LWPool lw, NetBuf data, NetAttrs meta, NetModuleId id)
{
  unsigned short	val1;
  unsigned long checksum = 0;


  /* We resend exactly the same data to the sender
   * Only 'type' is changed from ECHO to ECHOREPLY */
  /* Re-calculate checksum (see RFC 1141)
   * hdr->checksum=htons(netICMP6Checksum(buf,netbufLen(buf)));
   * ~C' = ~(C + (-m) + m') = ~C + (m - m') = ~C + m + ~m' 
   */

  netdbgPrintf(NETDEBUG_ICMP6, "netICMP6up: netICMP6EchoUp: received\n");

  if ( netattrGet(meta, NETATTR_ICMP6_CHECKSUM, &val1) )
  {
    checksum = val1 +
               (long)(unsigned short)(NETICMP6_TYPE_ECHO_REQUEST << 8)+
               (long)(unsigned short)~(NETICMP6_TYPE_ECHO_REPLY << 8);
    checksum = (checksum & 0xFFFF) + (checksum >> 16);  /* add carry */
  }
  else
  {
    checksum = 0;
  }

  if (!(	/* XXX this is a hack to get ping working */
        netattrGet(meta, NETATTR_IF_FROM, &val1) &&
        netattrSet(meta, NETATTR_IF_TO, val1) &&
        netattrGet(meta, NETATTR_IN_TYPE, &val1) &&
        netattrSet(meta, NETATTR_OSI_2_TYPE, val1) &&
        /* reset the new calculated checksum */
        netattrSet(meta, NETATTR_ICMP6_CHECKSUM, checksum) &&
        /* and finaly set the ICMP type to ECHOREPLY */
        netattrSet(meta, NETATTR_ICMP6_TYPE, NETICMP6_TYPE_ECHO_REPLY) ))
  {
    /* one of the netattr funtctions failed */
    netmodRIP (lw, data, meta, "netICMP6: Failed to set new attributes!\n");
    return;
  }

  /* now unset those IP attributes that need to be reset */
  /* XXX this is mainly the TTL field and checksum */
  netattrGet(meta, NETATTR_OSI_3_TYPE, &val1);

  switch ( val1 )
  {
  case NETATTR_TYPE_IPv6:
    netattrUnset(meta, NETATTR_IP6_FLOWLABEL_0);
    netattrUnset(meta, NETATTR_IP6_FLOWLABEL_1);
    break;
  default:
    netdbgPrintf(0, "netICMP6: unknown OSI 3 protocol (%i)\n", val1);
    return;
  }
  /* normaly we would leave the stack so set NETATTR_SENDTO_LOOP */
  netattrSet(meta, NETATTR_SENDTO_LOOP, NETATTR_TYPE_ICMP6);

  /* Finished: send the packet */
  netmodSend (lw, id, data, meta);

  return;
}

static void neticmpNeighborSolicitation(LWPool lw, NetBuf data, NetAttrs meta, NetModuleId id, ThreadArg arg, u8* pa, u8* ha )
{
  unsigned short	        val1;

  NetDevice		d;
  unsigned short	        val[8], deviceNr;

  NetModule		modDesc = (NetModule) arg;
  NetICMP6Hdr_ta		*hdr_ta;
  NetICMP6Hdr_opt		*hdr_opt;
  unsigned short	        tLen;

  Message                 msg;

  u16                     i;

  netdbgPrintf(NETDEBUG_ICMP6, "netICMP6up: netICMP6NeighborSolicitation : received\n");
  netdbgPrintf(NETDEBUG_ICMP6, "NetICMP6ModuleUp: NETICMP6_TYPE_NeighborSolicitation %x %x\n", &pa[0], &ha[0]);
  netdbgPrintf(NETDEBUG_ICMP6, "NetICMP6ModuleUp: NETICMP6_TYPE_NeighborSolicitation %x %x\n", pa[0], ha[0] );


  hdr_ta = (NetICMP6Hdr_ta*) NETBUF_DATA(data);

  /*verify minimum lenght */
  tLen = netbufLen(data);
  if (tLen < sizeof(NetICMP6Hdr) )
  {
    PACKERROR(modDesc);
    netmodRIP (lw, data, meta, "netICMP6: neticmpNeighborSolicitation: Packet to small\n");
    return;
  }

  if ( !( netattrSet(meta, NETATTR_ICMP6_TARGETADDR0, ntohs( hdr_ta->targetaddr[0] )) &&
          netattrSet(meta, NETATTR_ICMP6_TARGETADDR1, ntohs( hdr_ta->targetaddr[1] )) &&
          netattrSet(meta, NETATTR_ICMP6_TARGETADDR2, ntohs( hdr_ta->targetaddr[2] )) &&
          netattrSet(meta, NETATTR_ICMP6_TARGETADDR3, ntohs( hdr_ta->targetaddr[3] )) &&
          netattrSet(meta, NETATTR_ICMP6_TARGETADDR4, ntohs( hdr_ta->targetaddr[4] )) &&
          netattrSet(meta, NETATTR_ICMP6_TARGETADDR5, ntohs( hdr_ta->targetaddr[5] )) &&
          netattrSet(meta, NETATTR_ICMP6_TARGETADDR6, ntohs( hdr_ta->targetaddr[6] )) &&
          netattrSet(meta, NETATTR_ICMP6_TARGETADDR7, ntohs( hdr_ta->targetaddr[7] )) ))
  {
    PACKERROR(modDesc);
    netmodRIP (lw, data, meta, "netICMP6: neticmpNeighborSolicitation: failed to set attributes\n");
    return;
  }

  netdbgPrintf(NETDEBUG_ICMP6, "netICMP6up: netICMP6NeighborSolicitation: targetaddr %x %x %x %x %x %x %x %x\n",
               ntohs(hdr_ta->targetaddr[0]),  ntohs(hdr_ta->targetaddr[1]),  ntohs(hdr_ta->targetaddr[2]),  ntohs(hdr_ta->targetaddr[3]),
               ntohs(hdr_ta->targetaddr[4]),  ntohs(hdr_ta->targetaddr[5]),  ntohs(hdr_ta->targetaddr[6]),  ntohs(hdr_ta->targetaddr[7]) );

  /* Strip header (8 Bytes) */
  data->start += sizeof(NetICMP6Hdr_ta);

  hdr_opt = (NetICMP6Hdr_opt*) NETBUF_DATA(data);

  /* verify minimum lenght */
  tLen = netbufLen(data);
  if (tLen < sizeof(NetICMP6Hdr_opt) )
  {
    PACKERROR(modDesc);
    netmodRIP (lw, data, meta, "netICMP6up: Packet to small\n");
    return;
  }

  /* set optiontype */
  if ( hdr_opt->optiontype == NETICMP6_SOURCE_LINK_LAYER_ADDRESS )
  {
    hdr_opt->optiontype =  NETICMP6_TARGET_LINK_LAYER_ADDRESS ;
  }
  else
  {
    PACKERROR(modDesc);
    netmodRIP (lw, data, meta, "netICMP6up: wrong NeighborSolicitation Optiontype\n");
    return;
  }

  if ( !(	netattrSet(meta, NETATTR_ICMP6_OPTIONTYPE, hdr_opt->optiontype ) &&
          netattrSet(meta, NETATTR_ICMP6_OPTIONLENGTH, hdr_opt->optionlength ) &&
          netattrSet(meta, NETATTR_ICMP6_LINKLAYERADDR0, hdr_opt->linklayer[0]  ) &&
          netattrSet(meta, NETATTR_ICMP6_LINKLAYERADDR1, hdr_opt->linklayer[1] ) &&
          netattrSet(meta, NETATTR_ICMP6_LINKLAYERADDR2, hdr_opt->linklayer[2] ) &&
          netattrSet(meta, NETATTR_ICMP6_LINKLAYERADDR3, hdr_opt->linklayer[3] ) &&
          netattrSet(meta, NETATTR_ICMP6_LINKLAYERADDR4, hdr_opt->linklayer[4] ) &&
          netattrSet(meta, NETATTR_ICMP6_LINKLAYERADDR5, hdr_opt->linklayer[5] ) ))
  {
    PACKERROR(modDesc);
    netmodRIP (lw, data, meta, "netICMP6up: neticmpNeighborSolicitation: failed to set attributes\n");
    return;
  }

  netdbgPrintf(NETDEBUG_ICMP6, "netICMP6up: netICMP6NeighborSolicitation: options %x %x \n",
               ntohs(hdr_opt->optiontype), ntohs(hdr_opt->optionlength) );
  netdbgPrintf(NETDEBUG_ICMP6, "netICMP6up: netICMP6NeighborSolicitation: LinkLayerAddr %x %x %x %x %x %x \n",
               ntohs(hdr_opt->linklayer[0]), ntohs(hdr_opt->linklayer[1]), ntohs(hdr_opt->linklayer[2]),
               ntohs(hdr_opt->linklayer[3]), ntohs(hdr_opt->linklayer[4]), ntohs(hdr_opt->linklayer[5]) );


  //going to insert host from which this solicitation came into arp-daemon
  netdbgPrintf(NETDEBUG_ICMP6,  "netICMP6NeighborSolicitation : going to make arp-entry\n");

  pa[0] = 16;    //length of ipv6-address

  netdbgPrintf(NETDEBUG_ICMP6, "NetICMP6ModuleUp: NETICMP6_TYPE_NeighborSolicitation %x %x\n", &pa[0], &ha[0]);
  netdbgPrintf(NETDEBUG_ICMP6, "NetICMP6ModuleUp: NETICMP6_TYPE_NeighborSolicitation %x %x\n", pa[0], ha[0] );

  if(!(netattrGet(meta, NETATTR_IP6_DESTINATIONADDRESS_0, &val[0])
       && netattrGet(meta, NETATTR_IP6_DESTINATIONADDRESS_1, &val[1])
       && netattrGet(meta, NETATTR_IP6_DESTINATIONADDRESS_2, &val[2])
       && netattrGet(meta, NETATTR_IP6_DESTINATIONADDRESS_3, &val[3])
       && netattrGet(meta, NETATTR_IP6_DESTINATIONADDRESS_4, &val[4])
       && netattrGet(meta, NETATTR_IP6_DESTINATIONADDRESS_5, &val[5])
       && netattrGet(meta, NETATTR_IP6_DESTINATIONADDRESS_6, &val[6])
       && netattrGet(meta, NETATTR_IP6_DESTINATIONADDRESS_7, &val[7])
       && netattrGet(meta, NETATTR_IF_FROM, &deviceNr)))
  {
    netdbgPrintf(NETDEBUG_ICMP6, "netICMP6NeighborSolicitation : something went wrong with the arp entry!!!\n");
  }

  pa[1]  = val[0] >> 8;
  pa[2]  = val[0] & 0xff;
  pa[3]  = val[1] >> 8;
  pa[4]  = val[1] & 0xff;
  pa[5]  = val[2] >> 8;
  pa[6]  = val[2] & 0xff;
  pa[7]  = val[3] >> 8;
  pa[8]  = val[3] & 0xff;
  pa[9]  = val[4] >> 8;
  pa[10] = val[4] & 0xff;
  pa[11] = val[5] >> 8;
  pa[12] = val[5] & 0xff;
  pa[13] = val[6] >> 8;
  pa[14] = val[6] & 0xff;
  pa[15] = val[7] >> 8;
  pa[16] = val[7] & 0xff ;

  ha[0] = 6;   //length of ethernet-address

  netdbgPrintf(NETDEBUG_ICMP6, "netICMP6NeighborSolicitation: allocated memory done\n");
  for ( i = 0; i < 6; i++ )
    ha[i+1] = hdr_opt->linklayer[i];
  msg.msg.netARPAdd.proto = ARP_TYPE_IPv6;
  msg.msg.netARPAdd.device = deviceNr;
  msg.id = NET_ARP_ADD;
  msg.msg.netARPAdd.pa = &pa[0];
  msg.msg.netARPAdd.ha = &ha[0];

  netdbgPrintf(NETDEBUG_ICMP6, "netICMP6NeighborSolicitation: sending  NET_ARP_ADD\n");
  netdbgPrintf(NETDEBUG_ICMP6, "netICMP6NeighborSolicitation: netARPAdd.proto: %x\n", msg.msg.netARPAdd.proto);
  netdbgPrintf(NETDEBUG_ICMP6, "netICMP6NeighborSolicitation: netARPAdd.device %d\n", msg.msg.netARPAdd.device);
  netdbgPrintf(NETDEBUG_ICMP6, "netICMP6NeighborSolicitation: pa_length: %d \n", msg.msg.netARPAdd.pa[0]);
  netdbgPrintf(NETDEBUG_ICMP6, "netICMP6NeighborSolicitation: ha-length: %d \n", msg.msg.netARPAdd.ha[0]);
  netdbgPrintf(NETDEBUG_ICMP6, "netICMP6NeighborSolicitation: pa: %x%x.%x%x.%x%x.%x%x.%x%x.%x%x.%x%x.%x%x\n",
               msg.msg.netARPAdd.pa[1], msg.msg.netARPAdd.pa[2], msg.msg.netARPAdd.pa[3], msg.msg.netARPAdd.pa[4],
               msg.msg.netARPAdd.pa[5], msg.msg.netARPAdd.pa[6], msg.msg.netARPAdd.pa[7], msg.msg.netARPAdd.pa[8],
               msg.msg.netARPAdd.pa[9], msg.msg.netARPAdd.pa[10], msg.msg.netARPAdd.pa[11], msg.msg.netARPAdd.pa[12],
               msg.msg.netARPAdd.pa[13], msg.msg.netARPAdd.pa[14], msg.msg.netARPAdd.pa[15], msg.msg.netARPAdd.pa[16]);
  netdbgPrintf(NETDEBUG_ICMP6, "netICMP6NeighborSolicitation: ha: %x %x %x %x %x %x\n",
               msg.msg.netARPAdd.ha[1], msg.msg.netARPAdd.ha[2], msg.msg.netARPAdd.ha[3],
               msg.msg.netARPAdd.ha[4], msg.msg.netARPAdd.ha[5], msg.msg.netARPAdd.ha[6]);

  /* send message to arp_main_thread */
  netdbgPrintf(NETDEBUG_ICMP6, "netICMP6NeighborSolicitation: allocated memory done\n");
  if ( tmMsgSend(arp_main_thread, &msg) != TM_MSGSENDOK )
  {
    netdbgPrintf(NETDEBUG_ICMP6,  "netICMP6NeighborSolicitation : tmMsgSend failed\n");
  }

  netdbgPrintf(NETDEBUG_ICMP6,  "netICMP6NeighborSolicitation :  arp-entry finished\n");

  /* Strip header (8 Bytes) */
  data->start += sizeof(NetICMP6Hdr_opt);

  netdbgPrintf(NETDEBUG_ICMP6, "netICMP6up: netICMP6NeighborSolicitation : Set LinkLayer Addr\n");
  netattrUnset(meta, NETATTR_IP6_FLOWLABEL_0);

  if (!(	/* XXX this is a hack to get ping working */
        netattrGet(meta, NETATTR_IF_FROM, &val1) &&
        netattrSet(meta, NETATTR_IF_TO, val1) &&
        netattrGet(meta, NETATTR_IN_TYPE, &val1) &&
        netattrSet(meta, NETATTR_OSI_2_TYPE, val1) &&
        /* reset the new calculated checksum */
        //netattrSet(meta, NETATTR_ICMP6_CHECKSUM, checksum) &&
        /* and finaly set the ICMP type to ECHOREPLY */
        netattrSet(meta, NETATTR_ICMP6_TYPE, NETICMP6_TYPE_NeighborAdvertisement) ))
  {
    /* one of the netattr funtctions failed */
    netmodRIP (lw, data, meta, "netICMP6: Failed to set new attributes!\n");
    return;
  }

  if (!(	netattrGet(meta, NETATTR_IF_TO, &val1) ) )
  {
    /* one of the netattr funtctions failed */
    netmodRIP (lw, data, meta, "netICMP6: Failed to set new attributes!\n");
    return;
  }


  /* now unset those IP attributes that need to be reset */
  /* XXX this is mainly the TTL field and checksum */
  netattrGet(meta, NETATTR_ICMP6_CHECKSUM, &val1);
  netdbgPrintf(NETDEBUG_ICMP6, "netICMP6up: netICMP6NeighborSolicitation: CheckSum : %x\n", val1 );


  netattrGet(meta, NETATTR_OSI_3_TYPE, &val1);

  switch ( val1 )
  {
  case NETATTR_TYPE_IPv6:
    /* get the device descriptor */

    /*  		        if (!(	netattrGet(meta, NETATTR_IF_TO, &deviceNr) ) ) { */
    /*  			  // one of the netattr funtctions failed */
    /*  			  netmodRIP (lw, data, meta, "netICMP6: Failed to set new attributes!\n"); */
    /*  			  return; */
    /*  			} */

    // THIS IS ANOTHER HACK!
    // we maintain only one arp-database with all addresses, not seperate databases for each interface
    deviceNr = 0;

    netdbgPrintf(NETDEBUG_ICMP6, "netICMP6up: netICMP6NeighborSolicitation: 5.IF_TO: %x\n ", deviceNr );

    if (! netdevGet(deviceNr, &d) )
    {
      PACKERROR(modDesc);
      netmodRIP (lw, data, meta, "netICMP6: no Ethernet device\n");
      return;
    }

    netattrUnset(meta, NETATTR_IP6_FLOWLABEL_0);
    netattrUnset(meta, NETATTR_IP6_FLOWLABEL_1);
    netattrUnset(meta,  NETATTR_IP6_HOPLIMIT);

    netattrSet(meta, NETATTR_IP6_HOPLIMIT, 255);

    /*  			netattrUnset(meta, NETATTR_IP6_SOURCEADDRESS_0); */
    /*  			netattrUnset(meta, NETATTR_IP6_SOURCEADDRESS_1); */
    /*  			netattrUnset(meta, NETATTR_IP6_SOURCEADDRESS_2); */
    /*  			netattrUnset(meta, NETATTR_IP6_SOURCEADDRESS_3); */
    /*  			netattrUnset(meta, NETATTR_IP6_SOURCEADDRESS_4); */
    /*  			netattrUnset(meta, NETATTR_IP6_SOURCEADDRESS_5); */
    /*  			netattrUnset(meta, NETATTR_IP6_SOURCEADDRESS_6); */
    /*  			netattrUnset(meta, NETATTR_IP6_SOURCEADDRESS_7);	 */

    if (!(	netattrGet(d->addrList->addr,  NETATTR_IP6_SOURCEADDRESS_0, &val[0]) &&
           netattrGet(d->addrList->addr,  NETATTR_IP6_SOURCEADDRESS_1, &val[1]) &&
           netattrGet(d->addrList->addr,  NETATTR_IP6_SOURCEADDRESS_2, &val[2]) &&
           netattrGet(d->addrList->addr,  NETATTR_IP6_SOURCEADDRESS_3, &val[3]) &&
           netattrGet(d->addrList->addr,  NETATTR_IP6_SOURCEADDRESS_4, &val[4]) &&
           netattrGet(d->addrList->addr,  NETATTR_IP6_SOURCEADDRESS_5, &val[5]) &&
           netattrGet(d->addrList->addr,  NETATTR_IP6_SOURCEADDRESS_6, &val[6]) &&
           netattrGet(d->addrList->addr,  NETATTR_IP6_SOURCEADDRESS_7, &val[7]) ))
    {

      /* failed what now: Normaly we would dump the packet
       * How can it be that we do not know our IF_ADDR this will be
       * set when the device is allocated */

      netmodRIP (lw, data, meta, "netICMP6: no ethernet device found\n");
      return;
    }

    netdbgPrintf(NETDEBUG_ICMP6, "netICMP6NeighborSolicitation: Verify Adress for device %d: %x.%x.%x.%x.%x.%x.%x.%x\n",
                 deviceNr, val[0], val[1], val[2], val[3], val[4], val[5], val[6], val[7]);

    netattrSet (meta, NETATTR_IP6_SOURCEADDRESS_0, val[0] );
    netattrSet (meta, NETATTR_IP6_SOURCEADDRESS_1, val[1] );
    netattrSet (meta, NETATTR_IP6_SOURCEADDRESS_2, val[2] );
    netattrSet (meta, NETATTR_IP6_SOURCEADDRESS_3, val[3] );
    netattrSet (meta, NETATTR_IP6_SOURCEADDRESS_4, val[4] );
    netattrSet (meta, NETATTR_IP6_SOURCEADDRESS_5, val[5] );
    netattrSet (meta, NETATTR_IP6_SOURCEADDRESS_6, val[6] );
    netattrSet (meta, NETATTR_IP6_SOURCEADDRESS_7, val[7] );

    break;

  default:
    netdbgPrintf(0, "netICMP6: unknown OSI 3 protocol (%i)\n", val1);
    return;
  }

  /* normaly we would leave the stack so set NETATTR_SENDTO_LOOP */
  netattrSet(meta, NETATTR_SENDTO_LOOP, NETATTR_TYPE_ICMP6);

  netdbgPrintf(NETDEBUG_ICMP6, "netICMP6NeighborSolicitation: done\n");

  /*
  netdbgDumpBuf(NETDEBUG_ICMP6, data);
  netdbgDisplay(NETDEBUG_ICMP6, "*************************\n");
  netdbgDumpAttr(NETDEBUG_ICMP6, meta);
  netdbgDisplay(NETDEBUG_ICMP6, "*************************\n");
  */

  /* Finished: send the packet */
  netmodSend (lw, id, data, meta);
  return;
}

static void neticmpNeighborAdvertisement(LWPool lw, NetBuf data, NetAttrs meta, NetModuleId id, ThreadArg arg)
{
  unsigned short	        val1;

  ThreadId		from;

  NetModule		modDesc = (NetModule) arg;
  NetICMP6Hdr_ta		*hdr_ta;
  NetICMP6Hdr_opt		*hdr_opt;
  unsigned short	        tLen, i;

  u8  pa[17];
  u8  ha[7];

  NetDevice		d;
  unsigned short	        val[8];
  unsigned short          deviceNr;
  Message			msg;

  netdbgPrintf(NETDEBUG_ICMP6, "netICMP6up: neticmpNeighborAdvertisement: received\n");

  hdr_ta = (NetICMP6Hdr_ta*) NETBUF_DATA(data);

  /*verify minimum lenght */
  tLen = netbufLen(data);
  if (tLen < sizeof(NetICMP6Hdr) )
  {
    PACKERROR(modDesc);
    netmodRIP (lw, data, meta, "netICMP6: Packet to small\n");
    return;
  }

  if ( !( netattrSet(meta, NETATTR_ICMP6_TARGETADDR0, ntohs( hdr_ta->targetaddr[0] )) &&
          netattrSet(meta, NETATTR_ICMP6_TARGETADDR1, ntohs( hdr_ta->targetaddr[1] )) &&
          netattrSet(meta, NETATTR_ICMP6_TARGETADDR2, ntohs( hdr_ta->targetaddr[2] )) &&
          netattrSet(meta, NETATTR_ICMP6_TARGETADDR3, ntohs( hdr_ta->targetaddr[3] )) &&
          netattrSet(meta, NETATTR_ICMP6_TARGETADDR4, ntohs( hdr_ta->targetaddr[4] )) &&
          netattrSet(meta, NETATTR_ICMP6_TARGETADDR5, ntohs( hdr_ta->targetaddr[5] )) &&
          netattrSet(meta, NETATTR_ICMP6_TARGETADDR6, ntohs( hdr_ta->targetaddr[6] )) &&
          netattrSet(meta, NETATTR_ICMP6_TARGETADDR7, ntohs( hdr_ta->targetaddr[7] )) ))
  {
    PACKERROR(modDesc);
    netmodRIP (lw, data, meta, "netICMP6: failed to set attributes\n");
    return;
  }

  netdbgPrintf(NETDEBUG_ICMP6, "netICMP6up: neticmpNeighborAdvertisement: targetaddr %x %x %x %x %x %x %x %x  \n",
               hdr_ta->targetaddr[0],hdr_ta->targetaddr[1],hdr_ta->targetaddr[2],hdr_ta->targetaddr[3],
               hdr_ta->targetaddr[4],hdr_ta->targetaddr[5],hdr_ta->targetaddr[6],hdr_ta->targetaddr[7]);

  /* Strip header (8 Bytes) */
  data->start += sizeof(NetICMP6Hdr_ta);

  hdr_opt = (NetICMP6Hdr_opt*) NETBUF_DATA(data);

  /* verify minimum lenght */
  tLen = netbufLen(data);
  if (tLen < sizeof(NetICMP6Hdr_opt) )
  {
    PACKERROR(modDesc);
    netmodRIP (lw, data, meta, "netICMP6: Packet to small\n");
    return;
  }

  /* set optiontype */
  if (!(hdr_opt->optiontype == NETICMP6_TARGET_LINK_LAYER_ADDRESS))
  {
    PACKERROR(modDesc);
    netmodRIP (lw, data, meta, "netICMP6: wrong neticmpNeighborAdvertisement Optiontype \n");
  }

  if ( !(	netattrSet(meta, NETATTR_ICMP6_OPTIONTYPE, hdr_opt->optiontype ) &&
          netattrSet(meta, NETATTR_ICMP6_OPTIONLENGTH, hdr_opt->optionlength ) &&
          netattrSet(meta, NETATTR_ICMP6_LINKLAYERADDR0, hdr_opt->linklayer[0] ) &&
          netattrSet(meta, NETATTR_ICMP6_LINKLAYERADDR1, hdr_opt->linklayer[1] ) &&
          netattrSet(meta, NETATTR_ICMP6_LINKLAYERADDR2, hdr_opt->linklayer[2] ) &&
          netattrSet(meta, NETATTR_ICMP6_LINKLAYERADDR3, hdr_opt->linklayer[3] ) &&
          netattrSet(meta, NETATTR_ICMP6_LINKLAYERADDR4, hdr_opt->linklayer[4] ) &&
          netattrSet(meta, NETATTR_ICMP6_LINKLAYERADDR5, hdr_opt->linklayer[5] ) ))
  {
    PACKERROR(modDesc);
    netmodRIP (lw, data, meta, "netICMP6: failed to set attributes\n");
    return;
  }

  netdbgPrintf(NETDEBUG_ICMP6, "netICMP6up: neticmpNeighborAdvertisement: options %x %x \n",
               ntohs(hdr_opt->optiontype),  ntohs(hdr_opt->optionlength) );
  netdbgPrintf(NETDEBUG_ICMP6, "netICMP6up: neticmpNeighborAdvertisement: LinkLayerAddr %x %x %x %x %x %x \n",
               ntohs(hdr_opt->linklayer[0]),  ntohs(hdr_opt->linklayer[1]),  ntohs(hdr_opt->linklayer[2]),
               ntohs(hdr_opt->linklayer[3]),  ntohs(hdr_opt->linklayer[4]),  ntohs(hdr_opt->linklayer[5]) );


  netdbgPrintf(NETDEBUG_ICMP6, "netICMP6up: neticmpNeighborAdvertisement : Set LinkLayer Addr\n");

  /* now unset those IP attributes that need to be reset */
  /* XXX this is mainly the TTL field and checksum */
  netattrGet(meta, NETATTR_ICMP6_CHECKSUM, &val1);
  netdbgPrintf(NETDEBUG_ICMP6, "netICMP6up: neticmpNeighborAdvertisement: CheckSum : %x\n", val1 );

  /* get the device descriptor */
  if (! netattrGet(meta, NETATTR_IF_FROM, &deviceNr) )
  {
    PACKERROR(modDesc);
    netmodRIP (lw, data, meta, "netICMP6up: neticmpNeighborAdvertisement: no device specified\n");
    return;
  }
  if (! netdevGet(deviceNr, &d) )
  {
    PACKERROR(modDesc);
    netmodRIP (lw, data, meta, "netICMP6up: neticmpNeighborAdvertisement: no Ethernet device\n");
    return;
  }

  /* going to insert host from which this avertisement came into arp-daemon */

  pa[0] = 16;    //length of ipv6-address
  if(!(netattrGet(meta, NETATTR_IP6_DESTINATIONADDRESS_0, &val[0])
       && netattrGet(meta, NETATTR_IP6_DESTINATIONADDRESS_1, &val[1])
       && netattrGet(meta, NETATTR_IP6_DESTINATIONADDRESS_2, &val[2])
       && netattrGet(meta, NETATTR_IP6_DESTINATIONADDRESS_3, &val[3])
       && netattrGet(meta, NETATTR_IP6_DESTINATIONADDRESS_4, &val[4])
       && netattrGet(meta, NETATTR_IP6_DESTINATIONADDRESS_5, &val[5])
       && netattrGet(meta, NETATTR_IP6_DESTINATIONADDRESS_6, &val[6])
       && netattrGet(meta, NETATTR_IP6_DESTINATIONADDRESS_7, &val[7])
       && netattrGet(meta, NETATTR_IF_FROM, &deviceNr)))
  {
    netdbgPrintf(NETDEBUG_ICMP6, "neticmpNeighborAdvertisement:
                 something went wrong with the arp entry!!!");
  }

  pa[1]  = val[0] >> 8;
  pa[2]  = val[0] & 0xff;
  pa[3]  = val[1] >> 8 ;
  pa[4]  = val[1] & 0xff;
  pa[5]  = val[2] >> 8;
  pa[6]  = val[2] & 0xff;
  pa[7]  = val[3] >> 8;
  pa[8]  = val[3] & 0xff;
  pa[9]  = val[4] >> 8;
  pa[10] = val[4] & 0xff;
  pa[11] = val[5] >> 8;
  pa[12] = val[5] & 0xff;
  pa[13] = val[6] >> 8;
  pa[14] = val[6] & 0xff;
  pa[15] = val[7] >> 8 ;
  pa[16] = val[7] & 0xff ;

  ha[0] = 6;   //length of ethernet-address
  for ( i = 0; i < 6; i++ )
    ha[i+1] = hdr_opt->linklayer[i];

  msg.msg.netARPAdd.proto = ARP_TYPE_IPv6;
  msg.msg.netARPAdd.device = deviceNr;
  msg.id = NET_ARP_ADD;
  msg.msg.netARPAdd.pa = &pa[0];
  msg.msg.netARPAdd.ha = &ha[0];
  netdbgPrintf(NETDEBUG_ICMP6, "neticmpNeighborAdvertisement: sending  NET_ARP_ADD\n");
  netdbgPrintf(NETDEBUG_ICMP6, "neticmpNeighborAdvertisement: netARPAdd.proto: %x\n", msg.msg.netARPAdd.proto);
  netdbgPrintf(NETDEBUG_ICMP6, "neticmpNeighborAdvertisement: netARPAdd.device %d\n", msg.msg.netARPAdd.device);
  netdbgPrintf(NETDEBUG_ICMP6, "neticmpNeighborAdvertisement: pa_length: %d \n", msg.msg.netARPAdd.pa[0]);
  netdbgPrintf(NETDEBUG_ICMP6, "neticmpNeighborAdvertisement: ha-length: %d \n", msg.msg.netARPAdd.ha[0]);
  netdbgPrintf(NETDEBUG_ICMP6, "neticmpNeighborAdvertisement: pa[0]: %x\n", msg.msg.netARPAdd.pa[1]);
  netdbgPrintf(NETDEBUG_ICMP6, "neticmpNeighborAdvertisement: pa[1]: %x\n", msg.msg.netARPAdd.pa[2]);
  netdbgPrintf(NETDEBUG_ICMP6, "neticmpNeighborAdvertisement: ha[0]: %x\n", msg.msg.netARPAdd.ha[1]);
  netdbgPrintf(NETDEBUG_ICMP6, "neticmpNeighborAdvertisement: ha[1]: %x\n", msg.msg.netARPAdd.ha[2]);

  /* send message to arp_main_thread */

  if ( tmMsgSend(arp_main_thread, &msg) != TM_MSGSENDOK )
  {
    netdbgPrintf(NETDEBUG_ICMP6,  "netICMP6NeighborSolicitation : tmMsgSend failed\n");
  }

  from = arp_main_thread;
  if (tmMsgRecv (&from, ANYMSGTYPE , &msg, INFINITY) != TM_MSGRECVOK)
  {
    printf ("netICMP6NeighborSolicitation: failed to received arp_main_thread!\n");
  }

  netdbgPrintf(NETDEBUG_ICMP6,  "netICMP6NeighborSolicitation :  arp-entry finished\n");

  /* Strip header (8 Bytes) */
  data->start += sizeof(NetICMP6Hdr_opt);

  netmodRIP (lw, data, meta, "neticmpNeighborAdvertisement: Send netICMP6NeighborAdvertisment to ARP Main \n");
  return;
}


