/*
    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/NetARPRecv.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 "NetARP.h"
#include "NetDevice.h"
#include "NetDebug.h"
#include "NetARPModules.h"
#include "NetByteorder.h"
#include "Syscall.h"

void netARPRecvInit(NetModule this)
{
  return;
}

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

static int netARPIsLocal(NetDevice device, u16 type, u8* pa, u8 pln)
{
  NetAttr attrVec[4]; /* need to be enough big to hold all needed attrs */

  switch ( type )
  {
  case NETATTR_TYPE_IPv4:
    attrVec[0].key = NETATTR_IP_LOCAL_0;
    attrVec[1].key = NETATTR_IP_LOCAL_1;
    attrVec[2].key = NETATTR_IP_LOCAL_2;
    attrVec[3].key = NETATTR_IP_LOCAL_3;
    attrVec[0].data = pa[0];
    attrVec[1].data = pa[1];
    attrVec[2].data = pa[2];
    attrVec[3].data = pa[3];
    return netdevIsLocal(device, attrVec, 4);
  case NETATTR_TYPE_IPv6:
  default:
    return 0;
  }
}

void netARPRecvMain(LWPool lw, ThreadArg arg)
{
  NetModule		modDesc = (NetModule) arg;
  NetBuf			data;
  NetAttrs		meta;
  NetDevice device;
  u16 dev_Nr, i, val[3], proto_type;
  u8 *d, *t, *s, c, hln, pln;
  Message msg;
  struct arphdr *head;


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

    head = (struct arphdr *) NETBUF_DATA(data);
    d = (u8 *)head + sizeof(struct arphdr);

    /*		netdbgPrintf(NETDEBUG_ARP,
    				"netARPRecv: recieved data: (buf %p Attr %p)\n", 
    				data, meta);
    		netdbgDumpBuf(NETDEBUG_ARP, data);
    		netdbgPrintf(NETDEBUG_ARP, "**********************\n");
    		netdbgDumpAttr(NETDEBUG_ARP, meta);
    */		/* check if valid packet */
    if ( NETBUF_LEN(data) < sizeof(struct arphdr) )
    {
      /* normaly a ARP packet is in one NetBuf if not
         we have definitifly other problems */
      PACKERROR(modDesc);
      netmodRIP(lw, data, meta, "netARPRecv: bad packet size\n");
      continue;
    }
    if ( NETBUF_LEN(data) < sizeof(struct arphdr) +
         2*(head->ar_pln + head->ar_hln) )
    {
      netdbgDumpBuf (NETDEBUG_ARP, data);
      PACKERROR(modDesc);
      netmodRIP(lw, data, meta, "netARPRecv: bad packet size\n");
      continue;
    }

    /* get the device */
    if (! netattrGet(meta, NETATTR_IF_FROM, &dev_Nr) )
    {
      PACKERROR(modDesc);
      netmodRIP( lw, data, meta,
                 "netARPRecv: failed to get NetAttrs\n");
      continue;
    }

    /* is address length corresponding to the interface length? */
    if (! netdevGet( dev_Nr, &device ) )
    {
      PACKERROR(modDesc);
      netmodRIP(lw, data, meta,
                "netARPRecv: unknown device\n");
      continue;
    }
    switch ( ntohs(head->ar_hrd) )
    {
    case ARP_TYPE_ETHERNET:
      if ( (hln = head->ar_hln) != 6 )
      {
        PACKERROR(modDesc);
        netmodRIP(lw, data, meta, "netARPRecv: bad packet\n");
        continue;
      }
      if ( netdevGetType(device->type) != NETATTR_TYPE_ETHERNET )
      {
        PACKERROR(modDesc);
        netmodRIP(lw, data, meta, "netARPRecv: bad packet\n");
        continue;
      }
      break;
    default:
      PACKERROR(modDesc);
      netmodRIP(lw, data, meta,
                "netARPRecv: unknown hardware protocol\n");
      continue;

    }
    switch ( ntohs( head->ar_pro ) )
    {
    case ARP_TYPE_IPv4:
      msg.msg.netARPAdd.proto = ARP_TYPE_IPv4;
      proto_type = NETATTR_TYPE_IPv4;
      if ( (pln = head->ar_pln) != 4 )
      {
        PACKERROR(modDesc);
        netmodRIP(lw, data, meta, "netARPRecv: bad packet\n");
        continue;
      }
      break;
    case ARP_TYPE_IPv6:
    default:
      PACKERROR(modDesc);
      netmodRIP(lw, data, meta, "netARPRecv: unknown protocol\n");
      continue;
    }

    /* what do we do ... */
    switch ( ntohs(head->ar_op) )
    {
    case ARP_REPLY_OP:
      //				netdbgPrintf (NETDEBUG_ARP, "netARPRecv: ARP_REPLY_OP\n");
      msg.msg.netARPAdd.device = dev_Nr;
      msg.id = NET_ARP_ADD;
      /* get the buffer and the two sizes and add them to the
         message. It's ugly but we rape the arp packet a bit, 
         we do not need the target (our) address fields so we 
         reuse them as temporary space */
      /* +------+----+------+----+    +----+-+----+---+-+------+
         | sha  |spa | tha  | tpa| -> |    |s|spa |   |s| sha  |
         +------+----+------+----+    +----+-+----+---+-+------+
      */
      d[hln + 2*pln -1] = hln;
      for ( i = 0; i < hln; i++ )
        d[hln + 2*pln + i] = d[i];
      d[hln - 1] = pln;
      msg.msg.netARPAdd.pa = &d[hln - 1];
      msg.msg.netARPAdd.ha = &d[hln + 2*pln - 1];

      //				netdbgPrintf (NETDEBUG_ARP,
      //						"netARPRecv: NET_ARP_ADD msg:\n\tpa: ");
      //				for ( i = 0; i<msg.msg.netARPAdd.pa[0]; i++)
      //					netdbgPrintf (NETDEBUG_ARP,"%02x",msg.msg.netARPAdd.pa[i]);
      //				netdbgPrintf (NETDEBUG_ARP, "\n\tha: ");
      //				for ( i = 0; i<msg.msg.netARPAdd.ha[0]; i++)
      //					netdbgPrintf (NETDEBUG_ARP,"%02x",msg.msg.netARPAdd.ha[i]);
      //				netdbgPrintf (NETDEBUG_ARP, "\nproto: %i device %i\n",
      //						msg.msg.netARPAdd.proto, msg.msg.netARPAdd.device);

      /* send message to arp_main_thread */
      if ( tmMsgSend(arp_main_thread, &msg) != TM_MSGSENDOK )
      {
        PACKERROR(modDesc);
        netmodRIP( lw, data, meta,
                   "netARPRecv: tmMsgSend failed\n");
        continue;
      }
      /* no response needed */
      PACKDONE(modDesc);
      netmodRIP( lw, data, meta, NULL);
      break;
    case ARP_REQUEST_OP:
      //				netdbgPrintf (NETDEBUG_ARP, "netARPRecv: ARP_REQUEST_OP\n");
      /* first check if the target pa is one of mine */
      if (netARPIsLocal(device, proto_type, &d[2*hln + pln], pln) )
      {
        //					netdbgPrintf (NETDEBUG_ARP,
        //							"netARPRecv: local address send reply\n");
        /* local, so respond to req with a reply */
        /* first switch spa/sha and tpa/tha */
        t = d + hln + pln;
        s = d;
        for ( i = 0; i < hln + pln; i++ )
        {
          c = *s;
          *s++ = *t;
          *t++ = c;
        }
        /* now set the sha to our address */
        switch ( netdevGetType(device->type) )
        {
        case NETATTR_TYPE_ETHERNET:
          if ( !(	netattrGet(device->addrList->addr,
                             NETATTR_ETH_LOCAL_0, &val[0]) &&
                  netattrGet(device->addrList->addr,
                             NETATTR_ETH_LOCAL_1, &val[1]) &&
                  netattrGet(device->addrList->addr,
                             NETATTR_ETH_LOCAL_2, &val[2]) ))
          {
            PACKERROR(modDesc);
            netmodRIP(lw, data, meta,
                      "netARPRecv: can't set device hw address\n");
            continue;
          }
          d[0] = val[0] >> 8;
          d[1] = val[0] & 0xFF;
          d[2] = val[1] >> 8;
          d[3] = val[1] & 0xFF;
          d[4] = val[2] >> 8;
          d[5] = val[2] & 0xFF;

          /* set the outgoing interface to the incomming
             and swap the ethernet addresses */
          if ( !(	netattrSet(meta, NETATTR_IF_TO, dev_Nr) &&
                  netattrSet(meta, NETATTR_OSI_3_TYPE,
                             NETATTR_TYPE_ARP) &&
                  netattrSet(meta, NETATTR_OSI_2_TYPE,
                             NETATTR_TYPE_ETHERNET)
                ))
          {
            PACKERROR(modDesc);
            netmodRIP( lw, data, meta,
                       "netARPRecv: failed to set NetAttrs\n");
            continue;
          }
          break;
        default: /* this is allready checked, just for gcc */
          break;
        }
        head->ar_op = htons(ARP_REPLY_OP);
        PACKDONE(modDesc);
        netmodSend (lw, CHAINNEXT(modDesc), data, meta);
        continue;
      }
      else
      {
        netdbgPrintf (NETDEBUG_ARP, "netARPRecv: not local address\n");
        for ( i = 0; i < pln; i++ )
          if ( d[i + hln] != d[i + 2*hln + pln] )
            break;
        if ( d[i + hln] == d[i + 2*hln + pln] )
        {
          /* gratuitous ARP, add it to the table */
          *(d - 1) = hln; /* over write op (rape packet again) */
          d[2*hln + pln - 1] = pln;
          msg.msg.netARPAdd.device = dev_Nr;
          msg.id = NET_ARP_ADD;
          msg.msg.netARPAdd.pa = d - 1;
          msg.msg.netARPAdd.ha = &d[2*hln + pln - 1];

          /* send message to arp_main_thread */
          if (tmMsgSend(arp_main_thread, &msg) != TM_MSGSENDOK)
          {
            PACKERROR(modDesc);
            netmodRIP( lw, data, meta,
                       "netARPRecv: tmMsgSend failed\n");
            continue;
          }
          /* no response needed */
          PACKDONE(modDesc);
          netmodRIP( lw, data, meta, NULL );
          continue;
        }
        /* unknown IP so we silently dump the packet */
        PACKERROR(modDesc);
        netmodRIP(lw, data, meta, "netARPRecv: not my IP\n");
        continue;
      }
      break;
    default:
      PACKERROR(modDesc);
      netmodRIP(lw, data, meta,
                "netARPRecv: bad arp operation\n");
      break;
    }
    continue;
  }
}

