/*
    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/NetARP.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 "Topsy.h"
#include "IODevice.h"
#include "NetARP.h"
#include "NetDevice.h"
#include "NetDebug.h"
#include "NetBuf.h"
#include "NetAttr.h"
#include "Syscall.h"
#include "Support.h"
#include "TMIPC.h"
#include "TMTime.h"
#include "TMTimerNG.h"
#include "NetInQueueMngr.h"
#include "NetMemoryManager.h"
#include "NetICMP6Module.h"

/* thread id */
ThreadId arp_main_thread;

/* timeouts */
#ifdef ia32
int arpt_keep = 1200; 	/* # seconds ARP entry valid once resolved */
int arpt_inval = 180;		/* # seconds ARP entry is invalid */
int arpt_down = 20; 		/* # seconds ARP entry is blocked */
int arpt_cleanup = 300;	/* # seconds ARP tables are cleand up */
#define ARPT_SHORT 1000/TIMESLICE  /* 1000 ms */
#else
#warning ____________MIPS-DELAYS____________
int arpt_keep = 120; 	/* # seconds ARP entry valid once resolved */
int arpt_inval = 18;		/* # seconds ARP entry is invalid */
int arpt_down = 2; 		/* # seconds ARP entry is blocked */
int arpt_cleanup = 30;	/* # seconds ARP tables are cleand up */
#define ARPT_SHORT 1    /* ca 1000ms on the very slow MIPSSim */
#endif

/* macros for to compare two timestamp */
#define TIMESTAMP_LT(a,b)	((int) ((a)-(b)) < 0)
#define TIMESTAMP_LEQ(a,b)	((int) ((a)-(b)) <= 0)
#define TIMESTAMP_GT(a,b)	((int) ((a)-(b)) > 0)
#define TIMESTAMP_GEQ(a,b)	((int) ((a)-(b)) >= 0)

static ArpCacheTable arp_cache_table[ARP_PROTO_MAX];

static int arp_pending_max;
static struct arp_pending_entry *arp_pending_table;

/* this is a null entry, for fast erasing */
u8 arp_null[ARP_MAX_HA_SIZE];

static ArpCacheTable arp_get_cache(int proto)
{
  switch ( proto )
  {
  case ARP_TYPE_IPv4:
    return (ArpCacheTable) arp_cache_table[ARP_PROTO_IPv4];
  case ARP_TYPE_IPv6:
    return (ArpCacheTable) arp_cache_table[ARP_PROTO_IPv6];
  default:
    return NULL;
  }
}

static int arp_get_pa_len(int place)
{
  switch ( place )
  {
  case ARP_PROTO_IPv4:
    return 4;
  case ARP_PROTO_IPv6:
    return 16;
  default:
    return 0;
  }
}

static int arp_init_cache(ArpCacheTable arp_proto_cache, u8 pa_len)
{
  NetDevice d;
  ArpCacheTable c = arp_proto_cache;
  NetBuf buf;
  u16 i, j;
  struct arp_cache_header *header;
  ArpCacheTableEntry entry;

  for ( i = 0, j = 0 ; netdevGet(i, &d); i++, j++ )
  {
    if ( j == 8 )
    {
      entry = c[7];
      if ( netMMAlloc((Address *) &(c[7]),
                      8 * sizeof(ArpCacheTableEntry) ) )
      {
        netdbgPrintf(NETDEBUG_ARP,
                     "arp_init_cache: can't alloc memory\n");
        tmExit();
      }
      c = (ArpCacheTable) c[7];
      c[0] = (ArpCacheTableEntry) ARP_SUBDEVICETABLE;
      c[1] = entry;
      j = 2;
    }
    switch ( d->type )
    {
    case IFT_ETHER:
      if (! netbufAlloc( &buf, ARP_CACHESIZE+ARP_CACHEHEADERSIZE))
      {
        netdbgPrintf(NETDEBUG_ARP,
                     "ARP init: can not alloc new cache\n");
        return ARP_FAILED;
      }
      NETBUF_END(buf) += NETBUF_SIZE(buf);
      if ( NETBUF_LEN(buf) < ARP_CACHESIZE+ARP_CACHEHEADERSIZE )
      {
        netdbgPrintf(NETDEBUG_ARP,
                     "arp_init_cache: PANIC NETBUF TOO SMALL\n");
        tmExit();
      }
      c[j] = (ArpCacheTableEntry) NETBUF_DATA(buf);
      header = c[j];
      header->magic = ARP_CHACHETABLE;
      header->pa_len = pa_len;
      header->ha_len = ARP_ETHER_HA_LEN;
      /* zeroOut the cache, just to be sure */
      zeroOut((u8 *)header + ARP_CACHEHEADERSIZE, ARP_CACHESIZE);
      break;
    default:
      c[j] = NULL;
      break;
    }
  }
  while ( ++j < 8 )
    c[j] = NULL;
  return ARP_OK;
}

static int arp_guess_position(u8* a, int alen, int max)
{
  int t = 0, i = 0;

  /* return a value between 0 and max (including 0 and max) */
  for ( i = 0; i < alen; i++ )
    t = *a++;
  /* XXX need better algorithm */
  return t % (max+1);
}

static int arp_set(ArpCacheTable arp_proto_cache, int device, u16 flags,
                   u8* pa, int pa_len,
                   u8* ha, int ha_len,
                   Address *hint)
{
  /* pointers for work on data */
  struct arp_cache_header *h;
  struct arp_entry *e;
  u8 *target, *entry;

  int size = 0;
  int max = 0;
  int i = 0, j = 0, match = 0;
  u32 otime;

  while ( device > 6 && arp_proto_cache[7] != NULL &&
          *((int *)arp_proto_cache[7]) == ARP_SUBDEVICETABLE )
  {
    arp_proto_cache = (ArpCacheTable) arp_proto_cache[7];
    device -= 6;
  }
  if ( device > 7 )
    return ARP_FAILED;

  h = arp_proto_cache[device];
  if ( h == NULL )
    return ARP_FAILED;
  if ( h->magic != ARP_CHACHETABLE )
  {
    netdbgPrintf(NETDEBUG_ARP, "arp_set:  proto cache is not a arp cache table!\n");
    return ARP_FAILED;
  }
  if ( h->pa_len != pa_len )
  {
    netdbgPrintf(NETDEBUG_ARP, "arp_set: pa_len(%d) does not match proto cache(%d)!\n", pa_len, h->pa_len);
    return ARP_FAILED;
  }
  if ( ha_len != 0 && h->ha_len != ha_len )
  {
    netdbgPrintf(NETDEBUG_ARP, "arp_set: ha_len does not match!\n");
    return ARP_FAILED;
  }

  if ( ha_len == 0 )
    ha_len = h->ha_len; /* ha_len = 0 for unknown addrs */

  size = (ARP_FLAGSLEN + pa_len + ha_len + 3) & ~3; /* get entry size */
  /* size needs to be a multiple of 4 (word aligned ) */
  max = ARP_CACHESIZE/size - 1; /* higest array number */
  entry = (u8 *)h + ARP_CACHEHEADERSIZE;
  /* entry points now to first real cache entry */
  j = arp_guess_position(pa, pa_len, max);

  netdbgPrintf(NETDEBUG_ARP,
               "arp_set: starting search @ %p[%i], %i of %i size entries\n",
               entry, j, max, size);

  /* set entry */
  otime = secs(k_now()); /* get the current time */
  for( i = j--; i != j; i = i==max?0:i+1 )
  {
    e = (struct arp_entry *)((u8 *)entry + i*size); /* just to be sure */
    if ( e->flags == ARP_EMPTY )
    {
      match = i;
      break;
    }
    if (TIMESTAMP_LT(e->timestamp,otime) && !IS_ARP_STATIC(e->flags))
    {
      /* if the table is full remove the
      	 oldest entry (suboptimal but efficent) */
      match = i;
    }
  }

  switch ( pa_len )
  {
  case 4: /* IPv4 */
    if ( ha )
      netdbgPrintf(NETDEBUG_ARP,
                   "arp_set: [%i] %i.%i.%i.%i -> %02x:%02x:%02x:%02x:%02x:%02x\n",
                   i, pa[0], pa[1], pa[2], pa[3],
                   ha[0], ha[1], ha[2], ha[3], ha[4], ha[5]);
    else
      netdbgPrintf(NETDEBUG_ARP,
                   "arp_set: [%i] %i.%i.%i.%i -> 00:00:00:00:00:00\n",
                   i, pa[0], pa[1], pa[2], pa[3]);
    break;
  case 16:
    if ( /* XXX CJ e->flags != ARP_EMPTY */ ha )
      netdbgPrintf(NETDEBUG_ARP6,
                   "arp_set: [%i] %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x ->  %02x:%02x:%02x:%02x:%02x:%02x\n",
                   i, pa[0], pa[1], pa[2], pa[3], pa[4], pa[5], pa[6], pa[7], pa[8],
                   pa[9], pa[10], pa[11], pa[12], pa[13], pa[14], pa[15],
                   ha[0], ha[1], ha[2], ha[3], ha[4], ha[5]);
    else
      netdbgPrintf(NETDEBUG_ARP6,
                   "arp_set: [%i] %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x -> 00:00:00:00:00:00\n",
                   i, pa[0], pa[1], pa[2], pa[3], pa[4], pa[5], pa[6], pa[7], pa[8],
                   pa[9], pa[10], pa[11], pa[12], pa[13], pa[14], pa[15]);
    break;
  default:
    netdbgPrintf(NETDEBUG_ARP, "arp_set: unknown type\n");
    break;
  }

  /* set the entry at position i. i points to empty or oldest slot */
  e = (struct arp_entry *)((u8 *)entry + i*size); /* just to be ... */
  /* first mark empty */
  e->flags = ARP_EMPTY;

  e->pa_len = (u8) pa_len;
  e->ha_len = (u8) ha_len;

  /* copy the data first the protocol address */
  target = &(e->data[0]);
  while ( pa_len-- > 0 )
    *target++ = *pa++;

  /* then the ethernet address */
  target = &(e->data[0]) + e->pa_len;
  if ( ha == 0 )
  {
    ha = arp_null;
  }
  while ( ha_len-- > 0 )
    *target++ = *ha++;

  /* init header as last updated so no lock is needed */
  e->flags = flags;
  e->timestamp = secs(k_now()) +
                 IS_ARP_VALID(flags)?arpt_keep:
                 IS_ARP_INVALID(flags)?arpt_inval:arpt_keep;

  if ( hint )
    *hint = (Address) e;
  /* done */
  return ARP_OK;
}

static int arp_get_entry(ArpCacheTable arp_proto_cache, int device,
                         struct arp_entry **e,
                         u8 *pa, int pa_len)
{
  struct arp_cache_header *h;
  u8* entry;
  long *x, *y;
  int size;
  int max;
  int i, j, k;
  u16 flags;

  while ( device > 6 && arp_proto_cache[7] != NULL &&
          *((int *)arp_proto_cache[7]) == ARP_SUBDEVICETABLE )
  {
    arp_proto_cache = (ArpCacheTable) arp_proto_cache[7];
    device -= 6;
  }
  if ( device > 7 )
    return ARP_FAILED;

  h = arp_proto_cache[device];
  if ( h == NULL )
  {
    netdbgPrintf(NETDEBUG_ARP, "arp_get_entry: could not get arp_proto_cache!\n");
    return ARP_FAILED;
  }
  if ( h->magic != ARP_CHACHETABLE )
  {
    netdbgPrintf(NETDEBUG_ARP, "arp_get_entry:  proto cache is not a arp cache table!\n");
    return ARP_FAILED;
  }
  if ( h->pa_len != pa_len )
  {
    netdbgPrintf(NETDEBUG_ARP, "arp_get_entry: pa_len(%d) does not match proto cache(%d)!\n", pa_len, h->pa_len);
    return ARP_FAILED;
  }

  size = (ARP_FLAGSLEN + pa_len + h->ha_len + 3) & ~3; /* get entry size */
  /* size needs to be a multiple of 4 (word aligned ) */
  max = ARP_CACHESIZE/size - 1; /* higest array number */
  entry = (u8 *)h + ARP_CACHEHEADERSIZE;
  /* entry points now to first real cache entry */
  i = arp_guess_position(pa, pa_len, max);

  netdbgPrintf(NETDEBUG_ARP,
               "arp_get_entry: starting search @ %p[%i], %i of %i size entries\n",
               entry, i, max, size);

  for ( j = i--; j != i; j = j==max?0:j+1 )
  {
    *e = (struct arp_entry *)((u8 *)entry + j*size); /* just to ... */
    if ( (*e)->flags == ARP_EMPTY )
      continue;
    flags = (*e)->flags;
    x = (long *)((u8 *)*e + ARP_FLAGSLEN); /* just ... */
    y = (long *) pa;
    k = pa_len;

    /* here we can use direct long addressing because both
       data structures are in the same byteorder (network) 
       Only when writing or reading from/to the cache u8 should be used */
    /* compare entries */
    /* XXX I removed a lot of ugly IPv6 code form here, I do not know if this
    	 breaks the IPv6 Tree but I think it shouldn't.
    	 CJ 2001 11 27 */
    while ( (k-=4) >= 0 )
    {
      if ( *x++ != *y++ )
      {
        break;
      }
    }
    if ( k >= 0 )
      continue; /* breaked */
    k+=4;
    while ( (k-=1) >= 0 )
    {
      if ( *((char *)x)++ != *((char *)y)++ )
      {
        break;
      }
    }
    if ( k >= 0 )
      continue; /* breaked */

    /* while we compared the entries, this entry was removed or is
       updated, so we can not return it correctly */
    /* XXX this is works because when if an update or set happens
       the flags change (else the entry shouldn't be updated) */
    if ( (*e)->flags != flags )
    {
      netdbgPrintf(NETDEBUG_ARP,"arp_get_entry: race condition\n");
      *e = 0;
      return ARP_RACE;
    }

    netdbgPrintf(NETDEBUG_ARP,"arp_get_entry: match found\n");
    /* if we got here we got a match, e points already to the entry */
    return ARP_OK;
  }
  /* done nothing found */
  netdbgPrintf(NETDEBUG_ARP,"arp_get_entry: nothing found\n");
  *e = 0;
  return ARP_NOTFOUND;
}

static int arp_delete_entry(struct arp_entry *e)
{
  /* XXX if an invalid entry timeouts we should notifiy the routing table,
  	 because then the host entry can be set unknown/removed again */
  /* simple function, just set the flags to ARP_EMPTY */
  e->flags = ARP_EMPTY;
  return ARP_OK;
}

static int arp_update_entry(struct arp_entry *e, u16 flags,
                            u8* pa, int pa_len,
                            u8* ha, int ha_len)
{
  u8 *target;

  if ( e->ha_len != ha_len || e->pa_len != pa_len )
    return ARP_FAILED;

  if ( ha )
    netdbgPrintf(NETDEBUG_ARP,
                 "arp_update: %i.%i.%i.%i -> %02x:%02x:%02x:%02x:%02x:%02x\n",
                 pa[0], pa[1], pa[2], pa[3],
                 ha[0], ha[1], ha[2], ha[3], ha[4], ha[5]);
  else
    netdbgPrintf(NETDEBUG_ARP,
                 "arp_update: %i.%i.%i.%i -> 00:00:00:00:00:00\n",
                 pa[0], pa[1], pa[2], pa[3]);

  /* first mark empty */
  e->flags = ARP_EMPTY;
  /* protocoll address */
  target = (u8 *) &(e->data[0]); /* just ... */
  while ( pa_len-- > 0 )
    *target++ = *pa++;

  /* hw address */
  target = (u8 *)&(e->data[0]) + e->pa_len; /* ... */
  if ( ha == NULL )
    ha = arp_null;
  while ( ha_len-- > 0 )
    *target++ = *ha++;

  /* header as last so we do not need a lock */
  e->flags = flags;
  e->timestamp = secs(k_now()) +
                 IS_ARP_VALID(flags)?arpt_keep:
                 IS_ARP_INVALID(flags)?arpt_inval:arpt_keep;

  return ARP_OK;
}

static void arp_reawake(struct arp_entry *arp_entry)
{
  int i;
  Message msg;

  msg.id = NET_ARP_REPLY;
  msg.from = arp_main_thread;
  for ( i = 0; i < arp_pending_max; i++)
  {
    if ( arp_pending_table[i].arp_entry == arp_entry )
    {
      msg.msg.netARPReply.ha =   (u8*)arp_entry +
                                 ARP_FLAGSLEN + arp_entry->pa_len;
      msg.msg.netARPReply.ha_len = arp_entry->ha_len;
      msg.msg.netARPReply.status = IS_ARP_VALID(arp_entry->flags)?
                                   ARP_FOUND:ARP_NOTFOUND;
      netdbgPrintf(NETDEBUG_ARP,
                   "arp_reawake: reaweaking %i ARP entry %s %#x\n",
                   arp_pending_table[i].requestor,
                   IS_ARP_VALID(arp_entry->flags)?"found":"not found",
                   msg.msg.netARPReply.status);
      if ( tmMsgSend(arp_pending_table[i].requestor, &msg) != TM_MSGSENDOK )
      {
        /* XXX kSend */
        netdbgPrintf(NETDEBUG_ARP, "arp_reawake: kSend failed\n");
      }
      arp_pending_table[i].requestor = 0;
      arp_pending_table[i].timerId = 0;
      arp_pending_table[i].arp_entry = NULL;
      arp_pending_table[i].flags = 0;
      arp_pending_table[i].proto = 0;
      arp_pending_table[i].device = 0;
    }
  }
  /* force a new scheduling decision */
  tmYield();
}

static int arp_add_pending(ThreadId f, struct arp_entry *a, u16 p, u16 d)
{
  int i;
  unsigned long timerId = 0;

  netdbgPrintf(NETDEBUG_ARP, "arp_add_pending: set thread %i into list\n", f);

  /* first find pending requests */
  for ( i = 0; i < arp_pending_max; i++)
  {
    if ( arp_pending_table[i].arp_entry == a )
    {
      timerId = arp_pending_table[i].timerId;
      /* wait for this timer to expire only then
      	 we can send a new request */
    }
  }

  netdbgPrintf(NETDEBUG_ARP, "arp_add_pending: timerId = %i\n", timerId);
  for ( i = 0; i < arp_pending_max; i++)
  {
    if ( arp_pending_table[i].arp_entry == 0 )
    {
      if ( !timerId )
      {
        if ( tmSetTimer(ARPT_SHORT /* ca 1000 ms */, &timerId )
             != TM_TIMERSETOK )
        {
          timerId = 0;
          return ARP_FAILED;
        }
      }
      arp_pending_table[i].arp_entry = a;
      arp_pending_table[i].requestor = f;
      arp_pending_table[i].timerId = timerId;
      arp_pending_table[i].flags = FIRST_SENT;
      arp_pending_table[i].proto = p;
      arp_pending_table[i].device = d;
      return ARP_OK;
    }
  }
  netdbgPrintf(NETDEBUG_ARP, "arp_add_pending: list is full\n");
  return ARP_REJECT;
}

int arp_send_lookup(int proto, int device, u8 *pa, int pa_len)
{
  NetDevice d;
  NetAttrs meta;
  NetBuf data;
  u8 *t;
  Message msg;

  netdbgPrintf(NETDEBUG_ARP, "arp_send_lookup: proceeding\n");
  if (! netdevGet(device, &d) )
    return ARP_FAILED;
  if (! netbufAlloc(&data, pa_len) ) /* get the smallest NetBuf */
    return ARP_FAILED;
  if (! netattrNew(&meta) )
    goto fail1;

  switch (proto)
  {
  case ARP_TYPE_IPv4:
    netdbgPrintf(NETDEBUG_ARP, "arp_send_lookup: going to make an arp-lookup\n");
    if (!(netattrSet(meta, NETATTR_ARP_HW_TYPE, netdevGetType(d->type) ) &&
          netattrSet(meta, NETATTR_ARP_PR_TYPE, proto ) &&
          netattrSet(meta, NETATTR_ARP_PR_LEN, pa_len) &&
          netattrSet(meta, NETATTR_IF_TO, device) &&
          netattrSet(meta, NETATTR_IN_TYPE, NETATTR_TYPE_ARP) ) )
    {
      NETATTR_FREE(meta);
      goto fail;
    }

    /* set the target protocol address at the end of the NetBuf */
    NETBUF_START(data) += ( NETBUF_SIZE(data) - pa_len );
    NETBUF_END(data) = NETBUF_START(data) + pa_len;

    if ( NETBUF_END(data) > NETBUF_SIZE(data) + NETBUF_HEADERSIZE )
      /* pa_len > 108 ??? */
      goto fail;

    t = (u8*)NETBUF_DATA(data);
    netdbgPrintf(NETDEBUG_ARP, //-CJ 2001-02-08
                 "arp_send_lookup: filled in target with %i.%i.%i.%i\n",
                 pa[0], pa[1], pa[2], pa[3]);
    while ( pa_len-- > 0 )
      *t++ = *pa++;
    netdbgDumpBuf(NETDEBUG_ARP, data);
    netdbgPrintf(NETDEBUG_ARP, "******************\n");
    break;

  case ARP_TYPE_IPv6:
    netdbgPrintf(NETDEBUG_ARP, "arp_send_lookup: going to send an ICMPv6 neighbor-solicitation\n");
    if (!(netattrSet(meta, NETATTR_ICMP6_TYPE, NETICMP6_TYPE_NeighborSolicitation ) &&
          netattrSet(meta, NETATTR_ICMP6_TARGETADDR0, pa[0]<<8 | pa[1] ) &&
          netattrSet(meta, NETATTR_ICMP6_TARGETADDR1, pa[2]<<8 | pa[3] ) &&
          netattrSet(meta, NETATTR_ICMP6_TARGETADDR2, pa[4]<<8 | pa[5] ) &&
          netattrSet(meta, NETATTR_ICMP6_TARGETADDR3, pa[6]<<8 | pa[7] ) &&
          netattrSet(meta, NETATTR_ICMP6_TARGETADDR4, pa[8]<<8 | pa[9] ) &&
          netattrSet(meta, NETATTR_ICMP6_TARGETADDR5, pa[10]<<8 | pa[11] ) &&
          netattrSet(meta, NETATTR_ICMP6_TARGETADDR6, pa[12]<<8 | pa[13] ) &&
          netattrSet(meta, NETATTR_ICMP6_TARGETADDR7, pa[14]<<8 | pa[15] ) &&
          netattrSet(meta, NETATTR_IF_TO, device) &&
          netattrSet(meta, NETATTR_IN_TYPE, NETATTR_TYPE_ICMP6) ) )
    {
      NETATTR_FREE(meta);
      goto fail;
    }
    break;
  default:
    netdbgPrintf(NETDEBUG_ARP, "arp_send_lookup: unknown protocol type.\n");
    return ARP_FAILED;
  }

  msg.from = arp_main_thread;
  msg.id = NET_QUEUE_DATA;
  msg.msg.netqMsg.netData = data;
  msg.msg.netqMsg.netMeta = meta;

  /* use kSend because we need to do another tmMsgSend
  	 afterwards for the timer, so reducing the overhead */
  /* XXX kSend */
  if ( tmMsgSend(netInqueueThread, &msg) != TM_MSGSENDOK )
    goto fail;

  return ARP_OK;
fail:
  NETATTR_FREE(meta);
fail1:
  netbufFree(data);
  return ARP_FAILED;
}

static int arp_generate_req(int proto, int device, ThreadId from,
                            u8 *pa, int pa_len)
{
  ArpCacheTable arp_cache;
  struct arp_entry *e;
  int err = 0;

  arp_cache = arp_get_cache(proto);
  if ( arp_cache == NULL )
    return ARP_FAILED;

  switch (proto)
  {
  case ARP_TYPE_IPv4:
    netdbgPrintf(NETDEBUG_ARP,
                 "arp_generate_req: lookup target %i.%i.%i.%i\n",
                 pa[0], pa[1], pa[2], pa[3]);
    break;
  case ARP_TYPE_IPv6:
    netdbgPrintf(NETDEBUG_ARP,
                 "arp_generate_req: lookup target %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x\n",
                 pa[0], pa[1], pa[2], pa[3], pa[4],pa[5],pa[6],pa[7],pa[8],
                 pa[9], pa[10], pa[11], pa[12], pa[13], pa[14],pa[15]);
    break;
  default:
    netdbgPrintf(NETDEBUG_ARP, "arp_generate_req: unknown protocol\n");
    break;
  }

  err = arp_get_entry(arp_cache, device, &e, pa, pa_len);
  if ( err == ARP_OK )
  {
    if (! IS_ARP_PENDING(e->flags) )
      return IS_ARP_VALID(e->flags)?ARP_FOUND:ARP_NOTFOUND;
    return arp_add_pending(from, e, (u16) proto, (u16) device);
  }
  else if ( err == ARP_NOTFOUND )
  {
    if ( arp_set(arp_cache, device, ARP_PENDING1, pa, pa_len,
                 NULL, 0, (Address *) &e) != ARP_OK)
      return ARP_FAILED;

    if ( arp_send_lookup(proto, device, pa, pa_len) == ARP_FAILED )
      return ARP_FAILED;
    return arp_add_pending(from, e, (u16) proto, (u16) device);
  }

  return err;
}

static int arp_response(int proto, int device,
                        u8 *pa, int pa_len,
                        u8 *ha, int ha_len)
{
  ArpCacheTable arp_cache;
  struct arp_entry *e;
  int err = 0;

  arp_cache = arp_get_cache(proto);
  if ( arp_cache == NULL )
    return ARP_FAILED;

  switch (proto)
  {
  case ARP_TYPE_IPv4:
    netdbgPrintf(NETDEBUG_ARP,
                 "arp_response: adding %i.%i.%i.%i @ %02x:%02x:%02x:%02x:%02x:%02x\n",
                 pa[0], pa[1], pa[2], pa[3],
                 ha[0], ha[1], ha[2], ha[3], ha[4], ha[5]);
    break;
  case ARP_TYPE_IPv6:
    netdbgPrintf(NETDEBUG_ARP,
                 "arp_response: adding %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x @ %02x:%02x:%02x:%02x:%02x:%02x\n",
                 pa[0], pa[1], pa[2], pa[3], pa[4],pa[5],pa[6],pa[7],pa[8],
                 pa[9], pa[10], pa[11], pa[12], pa[13], pa[14],pa[15],
                 ha[0], ha[1], ha[2], ha[3], ha[4], ha[5]);
    break;
  default:
    netdbgPrintf(NETDEBUG_ARP, "arp_response: unkown protocol\n");
    break;
  }
  err = arp_get_entry(arp_cache, device, &e, pa, pa_len);
  if ( err == ARP_OK )
  {
    if ( e->ha_len != ha_len )
      return ARP_FAILED;
    if ( arp_update_entry(e, ARP_VALID, pa, pa_len, ha, ha_len) != ARP_OK )
      return ARP_FAILED;
    /* reawake pending packets */
    arp_reawake(e);
  }
  else
  {
    if ( arp_set(arp_cache, device, ARP_VALID, pa, pa_len,
                 ha, ha_len, (Address *)&e) != ARP_OK )
      return ARP_FAILED;
    /* reawake pending packets */
    arp_reawake(e);
  }
  return ARP_OK;
}

int arp_lookup(int proto, int device,
               u8 *pa, u8 pa_len,
               u8 **ha, u8 *ha_len)
{
  ArpCacheTable arp_cache;
  struct arp_entry *e;
  int err = 0;

  arp_cache = arp_get_cache(proto);
  if ( arp_cache == NULL )
    return ARP_FAILED;

  switch (proto)
  {
  case ARP_TYPE_IPv4:
    netdbgPrintf(NETDEBUG_ARP,
                 "arp_lookup: lookup target %i.%i.%i.%i\n",
                 pa[0], pa[1], pa[2], pa[3]);
    break;
  case ARP_TYPE_IPv6:
    netdbgPrintf(NETDEBUG_ARP6,
                 "arp_lookup: lookup target %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x\n",
                 pa[0], pa[1], pa[2], pa[3], pa[4],pa[5],pa[6],pa[7],pa[8],
                 pa[9], pa[10], pa[11], pa[12], pa[13], pa[14],pa[15]);
    break;
  default:
    netdbgPrintf(NETDEBUG_ARP, "arp_lookup: unkown protocol\n");
    break;
  }

  err = arp_get_entry(arp_cache, device, &e, pa, pa_len);

  if ( err == ARP_OK )
  {
    if ( IS_ARP_VALID(e->flags) )
    {
      *ha = (u8 *)e + ARP_FLAGSLEN + e->pa_len;
      *ha_len = e->ha_len;
      netdbgPrintf(NETDEBUG_ARP,
                   "arp_lookup: lookup found %02x:%02x:%02x:%02x:%02x:%02x\n",
                   (*ha)[0], (*ha)[1], (*ha)[2], (*ha)[3], (*ha)[4], (*ha)[5]);
      return ARP_FOUND;
    }
    else
    {
      netdbgPrintf(NETDEBUG_ARP, "arp_lookup: invalid entry\n");
      return ARP_NOTFOUND;
    }
  }
  netdbgPrintf(NETDEBUG_ARP, "arp_lookup: found no valid entry\n");
  return err;
}

int arp_traverse_tree(ArpCacheTable tree,
                      int (*action)(	struct arp_entry *e,
                                     struct arp_cache_header *h) )
{
  int i = 0, j = 0, err = 0, size = 0, num_of_e = 0;
  struct arp_cache_header *h;
  struct arp_entry *e;
  NetDevice d;

  err = ARP_OK;
  for ( i = 0; netdevGet(i, &d); i++ )
  {
    if ( tree[i] == NULL )
      continue;
    h = tree[i];
    if ( h->magic == ARP_SUBDEVICETABLE )
    {
      tree = (ArpCacheTable) tree[i];
      continue;
    }

    if ( h->magic == ARP_CHACHETABLE )
    {

      e = (struct arp_entry *)((u8 *)h + ARP_CACHEHEADERSIZE);
      size = (ARP_FLAGSLEN + h->pa_len + h->ha_len + 3) & ~3;
      num_of_e = ARP_CACHESIZE/size;
      netdbgPrintf(NETDEBUG_ARP,
                   "arp_traverse_tree: cache @ %p, %i size of %i sized entries\n", e, num_of_e, size);
      for ( j = 0; j < num_of_e; j++ )
      {
        if ( e->flags == ARP_EMPTY )
          continue;
        if ( ( err = action(e, h) ) != ARP_OK )
        {
          netdbgPrintf(NETDEBUG_ARP,
                       "arp_traverse_tree: action failed (%i)\n", err);
          return err;
        }
        e = (struct arp_entry *)((u8 *)e + size);
      }
      continue;
    }
    netdbgPrintf(NETDEBUG_ARP,
                 "arp_traverse_tree: what the HELL... magic %#x unknown\n",
                 *tree[i]);
  }
  return err;
}

void arp_main(ThreadArg max_request)
{
  int i = 0, pending_size = 0, err = 0;
  Message msg, reply;
  ThreadId cleanup, from;
  unsigned long timeout = 0, new_timerId = 0, arp_entry_updated = 0, sent = 0;

  /* XXX init */
  pending_size = (int) max_request * sizeof(struct arp_pending_entry);

  if (netMMAlloc((Address *) &arp_pending_table, pending_size ) != NMM_OK)
  {
    netdbgPrintf(NETDEBUG_ARP, "arp_main: can not alloc memory\n");
    tmExit();
  }
  zeroOut(arp_pending_table, pending_size );

  for ( i = 0; i < ARP_PROTO_MAX; i++)
  {
    if ( netMMAlloc((Address *)&(arp_cache_table[i]),
                    8 * sizeof(ArpCacheTableEntry) ) != NMM_OK )
    {
      netdbgPrintf(NETDEBUG_ARP, "arp_main: can not alloc memory\n");
      tmExit();
    }
    arp_init_cache(arp_cache_table[i], arp_get_pa_len(i));
  }

  zeroOut(arp_null, sizeof(arp_null) );

  arp_pending_max = (int) max_request;
  /* finaly start the cleanup thread */
  if ( tmStart(&cleanup, arp_cleanup, (ThreadArg) NULL, "ARP Cleanup") !=
       TM_STARTOK )
  {
    netdbgPrintf(NETDEBUG_ARP, "arp_main: can not alloc memory\n");
    tmExit();
  }
  while(1)
  {
    from = ANY;
    if ( tmMsgRecv(&from, ANYMSGTYPE, &msg, INFINITY) != TM_MSGRECVOK )
    {
      netdbgPrintf(NETDEBUG_ARP, "arp_main: tmMsgRecv failed\n");
      continue;
    }

    switch (msg.id)
    {
    case NET_ARP_REQ:
      /* get address and do a generate req */
      err = arp_generate_req(
              msg.msg.netARPReq.proto,
              msg.msg.netARPReq.device,
              msg.from,
              msg.msg.netARPReq.pa,
              msg.msg.netARPReq.pa_len);
      if ( err != ARP_OK )
      {
        netdbgPrintf(NETDEBUG_ARP,
                     "arp_main: arp_generate_req failed (err = %i)!\n", err);
        msg.id = NET_ARP_REPLY;
        msg.msg.netARPReply.status = err;
        tmMsgSend(msg.from, &msg);
      }
      break;
    case NET_ARP_ADD:
      /* get address and do a arp_response */
      if ( arp_response(
             msg.msg.netARPAdd.proto,
             msg.msg.netARPAdd.device,
             msg.msg.netARPAdd.pa+1,
             msg.msg.netARPAdd.pa[0],
             msg.msg.netARPAdd.ha+1,
             msg.msg.netARPAdd.ha[0]) != ARP_OK )
      {
        netdbgPrintf(NETDEBUG_ARP,
                     "arp_main: failed to add arp entry\n");
      }
      break;
    case TM_TIMEREXPIRED:
      /* update pending table */
      timeout = ARPT_SHORT; /* ca 1000ms */
      arp_entry_updated = sent = 0;
      for ( i = 0; i < arp_pending_max; i++ )
      {
        if ( arp_pending_table[i].timerId == msg.msg.tmTimerExpired.timerId )
        {
          /* timer expired for this message */
          /* check entry:
          	 1. shift pending state to the next state
          	 2. if PENDING5 set timeout to arpt_down
          	 3. if SECOND_SENT is set send ARP_NOTFOUND back
          	 remove also the entry and set ARP_INVALID 
          	 else set SECOND_SENT and send a second packet
          	 4. set new timer
           */
          if (! arp_entry_updated )
          {
            arp_entry_updated = 1;
            if ( IS_ARP_PENDING5( arp_pending_table[i].arp_entry->flags) )
            {
              timeout = arpt_down * ARPT_SHORT;
              arp_pending_table[i].arp_entry->flags = ARP_PENDING1;
            }
            else
            {
              arp_pending_table[i].arp_entry->flags <<= 1;
            }
          }
          if ( arp_pending_table[i].flags == SECOND_SENT )
          {
            arp_pending_table[i].flags = SENT_FAILED;
          }
          else if ( arp_pending_table[i].flags == FIRST_SENT )
          {
            arp_pending_table[i].flags = SECOND_SENT;
          }
        }
      }
      /* set counter */
      if ( tmSetTimer(timeout, &new_timerId ) != TM_TIMERSETOK )
      {
        goto clear_table;
      }
      for ( i = 0; i < arp_pending_max; i++ )
      {
        if ( arp_pending_table[i].timerId == msg.msg.tmTimerExpired.timerId )
        {
          arp_pending_table[i].timerId = new_timerId;
          if ( sent == 0 && ( arp_pending_table[i].flags == SECOND_SENT || arp_pending_table[i].flags == FIRST_SENT ) )
          {
            sent = 1;
            if ( arp_send_lookup(
                   arp_pending_table[i].proto,
                   arp_pending_table[i].device,
                   arp_pending_table[i].arp_entry->data,
                   arp_pending_table[i].arp_entry->pa_len) == ARP_FAILED )
              goto clear_table;
          }
          if ( arp_pending_table[i].flags == SENT_FAILED )
            arp_reawake(arp_pending_table[i].arp_entry);
        }
      }
      break;
clear_table:
      for ( i = 0; i < arp_pending_max; i++ )
      {
        if ( arp_pending_table[i].timerId == msg.msg.tmTimerExpired.timerId )
        {
          arp_reawake(arp_pending_table[i].arp_entry);
          arp_delete_entry(arp_pending_table[i].arp_entry);
          arp_pending_table[i].arp_entry = NULL;
          arp_pending_table[i].requestor = 0;
          arp_pending_table[i].timerId = 0;
          arp_pending_table[i].proto = 0;
          arp_pending_table[i].device = 0;
          break;
        }
      }
      /* no message needs to be sent back */
      break;
    default:
      netdbgPrintf(NETDEBUG_ARP, "arp_main: unknown msg type\n");
      reply.id = UNKNOWN_SYSCALL;
      tmMsgSend(msg.from, &msg);
      break;
    }
  }
}

static u32 arp_cleanup_now;

static int arp_cleanup_entry(struct arp_entry *e, struct arp_cache_header *h)
{
  /* an other way to remove the entry is to resend an arprequest and set
  	 it corresponding to the reply */
  if ( TIMESTAMP_GEQ(arp_cleanup_now,e->timestamp) )
    arp_delete_entry(e);
  return ARP_OK;
}

void arp_cleanup(ThreadArg nothing)
{
  ArpCacheTable arp_cache;
  Message msg;
  ThreadId from;
  unsigned long timerId;
  int i = 0;

  while(1)
  {
    /* XXX tmSetTimer */
    if ( tmSetTimer(arpt_cleanup * ARPT_SHORT, &timerId) != TM_TIMERSETOK )
    {
      tmYield(); /* give other threads a change */
      continue;
    }
    from = ANY;
    tmMsgRecv(&from, TM_TIMEREXPIRED, &msg, INFINITY);
    arp_cleanup_now = secs(k_now());
    for ( i = 0; i < ARP_PROTO_MAX; i++ )
    {
      arp_cache = arp_cache_table[i];
      arp_traverse_tree(arp_cache, arp_cleanup_entry);
    }
  }
}

