/* BlueDataDistributor - data distribution module from ethernet to 32 serial ports
 * version 0.1.1 date 2006-10-07
 * Copyright (C) 2006 Stefan Schuermans <stefan@blinkenarea.org>
 * a BlinkenArea project - http://www.blinkenarea.org/
 */

#include "arp.h"
#include "config.h"
#include "ethernet.h"
#include "ip.h"
#include "macros.h"
#include "nethelp.h"

// timing parameters
#define ArpTicksMax 150 // maximum age of ARP table entries (in 200ms steps)
#define ArpNoMacTicksMax 50 // maximum age of ARP table entries without MAC (in 200ms steps)
#define ArpRetryTicks 8 // time after which to retry ARP query (must be power of 2, in 200ms steps)

// ARP table
#define ArpTabFlagInUse 0x01
#define ArpTabFlagMacOk 0x02
struct ArpTable
{
  unsigned char Flags; // flags - see constants
  unsigned char Ticks; // age of entry in 200ms steps
  unsigned char Mac[6];
  unsigned char Ip[4];
} ArpTab[12];

// initialize
void ArpInit( void ) // (extern)
{
  unsigned char i;

  // empty ARP tabale
  for( i = 0; i < count( ArpTab ); i++ )
    ArpTab[i].Flags = 0;
}

// send an ARP request
static void ArpSendRequest( unsigned char * pIp )
{
  struct ArpPacket ArpRequest;

  // build ARP request
  ArpRequest.ArpHdr.HwType = htons( 0x0001 ); // ethernet
  ArpRequest.ArpHdr.ProtoType = htons( 0x0800 ); // IP
  ArpRequest.ArpHdr.HwLen = 0x06; // length of a MAC address
  ArpRequest.ArpHdr.ProtoLen = 0x04; // length of an IP address
  ArpRequest.ArpHdr.Op = htons( 0x0001 ); // ARP request
  mac_cpy( ArpRequest.ArpHdr.SrcMac, ConfigMac ); // own MAC
  ip_cpy( ArpRequest.ArpHdr.SrcIp, ConfigIp ); // own IP
  mac_cpy( ArpRequest.ArpHdr.DestMac, "\xFF\xFF\xFF\xFF\xFF\xFF" ); // broadcast MAC
  ip_cpy( ArpRequest.ArpHdr.DestIp, pIp ); // requested IP

  // sent ARP request
  mac_cpy( ArpRequest.EthHdr.Dest, ArpRequest.ArpHdr.DestMac ); // ethernet destination address
  ArpRequest.EthHdr.Type = htons( 0x0806 ); // ethernet packet type: ARP
  EthernetSend( (unsigned char *)&ArpRequest, sizeof( ArpRequest ) );
}

// tick procedure - call every 200ms
void ArpTick200( void ) // (extern)
{
  unsigned char i;

  // increase age of ARP table entires and remove timed out ones
  for( i = 0; i < count( ArpTab ); i++ )
  {
    if( ArpTab[i].Flags & ArpTabFlagInUse ) // entry in use
    {
      ArpTab[i].Ticks++; // increase age
      if( ArpTab[i].Flags & ArpTabFlagMacOk ) // entry has got a MAC
      {
        if( ArpTab[i].Ticks > ArpTicksMax ) // too old
          ArpTab[i].Flags = 0; // remove entry
      }
      else // entry has not got a MAC
      {
        if( ArpTab[i].Ticks > ArpNoMacTicksMax ) // too old
          ArpTab[i].Flags = 0; // remove entry
        else if( (ArpTab[i].Ticks & (ArpRetryTicks - 1)) == 0 ) // retry ARP request
          ArpSendRequest( ArpTab[i].Ip );
      }
    }
  }
}

// process a received ARP packet
void ArpRecv( unsigned char * pData, unsigned short Length ) // (extern)
{
  struct ArpPacket * pArpPack;

  // packet too short
  if( Length < sizeof( struct ArpPacket ) )
    return;

  // convert pointer to ARP packet
  // (this saves us from always casting pData)
  pArpPack = (struct ArpPacket *)pData;

  // not IP over ethernet
  if( pArpPack->ArpHdr.HwType != htons( 0x0001 ) || // ethernet
      pArpPack->ArpHdr.ProtoType != htons( 0x0800 ) || // IP
      pArpPack->ArpHdr.HwLen != 0x06 || // length of a MAC address
      pArpPack->ArpHdr.ProtoLen != 0x04 ) // length of an IP address
    // we do not support other protocols than IP over ethernet
    return;

  // source MAC is broadcast MAC
  if( mac_eq( pArpPack->ArpHdr.SrcMac, "\xFF\xFF\xFF\xFF\xFF\xFF" ) )
    // broadcast MAC cannot be source, this is some kind of attack, get lost!
    return;

  // ARP request for own IP address
  if( pArpPack->ArpHdr.Op == htons( 0x0001 ) && // ARP request
      ip_eq( pArpPack->ArpHdr.DestIp, ConfigIp ) ) // own IP address
  {
    struct ArpPacket ArpReply;
    // build ARP reply
    ArpReply.ArpHdr.HwType = htons( 0x0001 ); // ethernet
    ArpReply.ArpHdr.ProtoType = htons( 0x0800 ); // IP
    ArpReply.ArpHdr.HwLen = 0x06; // length of a MAC address
    ArpReply.ArpHdr.ProtoLen = 0x04; // length of an IP address
    ArpReply.ArpHdr.Op = htons( 0x0002 ); // ARP reply
    mac_cpy( ArpReply.ArpHdr.SrcMac, ConfigMac ); // own MAC
    ip_cpy( ArpReply.ArpHdr.SrcIp, ConfigIp ); // own IP
    mac_cpy( ArpReply.ArpHdr.DestMac, pArpPack->ArpHdr.SrcMac ); // requestor's MAC
    ip_cpy( ArpReply.ArpHdr.DestIp, pArpPack->ArpHdr.SrcIp ); // requestor's IP

    // sent ARP reply
    mac_cpy( ArpReply.EthHdr.Dest, ArpReply.ArpHdr.DestMac ); // ethernet destination address
    ArpReply.EthHdr.Type = htons( 0x0806 ); // ethernet packet type: ARP
    EthernetSend( (unsigned char *)&ArpReply, sizeof( ArpReply ) );
    return;
  }

  // ARP reply to own MAC address and own IP address
  if( pArpPack->ArpHdr.Op == htons( 0x0002 ) && // ARP reply
      mac_eq( pArpPack->ArpHdr.DestMac, ConfigMac ) && // own MAC address
      ip_eq( pArpPack->ArpHdr.DestIp, ConfigIp ) ) // own IP address
  {
    unsigned char i;

    // search IP in ARP tabale
    for( i = 0; i < count( ArpTab ); i++ )
      if( (ArpTab[i].Flags & ArpTabFlagInUse) &&
          ip_eq( pArpPack->ArpHdr.SrcIp, ArpTab[i].Ip ) )
        break;
    // if found in ARP table
    // (we do not want to put an entry in the ARP table
    //  if we have not asked for the MAC of this IP)
    if( i < count( ArpTab ) )
    {
      // update ARP table entry
      ArpTab[i].Flags = ArpTabFlagInUse | ArpTabFlagMacOk;
      ArpTab[i].Ticks = 0;
      mac_cpy( ArpTab[i].Mac, pArpPack->ArpHdr.SrcMac );
      // notify IP
      //  - IP might be waiting for the MAC to transmit a packet
      IpGotMac( ArpTab[i].Ip, ArpTab[i].Mac );
    }
    return;
  }

}

// lookup the MAC for an IP address
// returns 0x00 in case of success, 0x01 if the MAC address is unknown
unsigned char ArpLookup( unsigned char * pIp, unsigned char * pMac ) // (extern)
{
  unsigned char i, j;

  // own IP
  if( ip_eq( pIp, ConfigIp ) )
    // own IP may not be looked up via ARP
    return 0x01;

  // search IP in ARP tabale
  for( i = 0; i < count( ArpTab ); i++ )
    if( (ArpTab[i].Flags & ArpTabFlagInUse) &&
        ip_eq( pIp, ArpTab[i].Ip ) )
      break;

  // not found
  if( i >= count( ArpTab ) )
  {
    // find a free entry
    for( i = 0; i < count( ArpTab ); i++ )
      if( ! (ArpTab[i].Flags & ArpTabFlagInUse) )
        break;

    // no free entry
    if( i >= count( ArpTab ) )
    {
      // find oldest entry
      i = 0;
      for( j = 1; j < count( ArpTab ); j++ )
        if( ArpTab[j].Ticks > ArpTab[i].Ticks )
          i = j;
    }

    // set up this entry
    ArpTab[i].Flags = ArpTabFlagInUse;
    ArpTab[i].Ticks = 0;
    ip_cpy( ArpTab[i].Ip, pIp );
  }

  // MAC available
  if( ArpTab[i].Flags & ArpTabFlagMacOk )
  {
    // return MAC and success
    mac_cpy( pMac, ArpTab[i].Mac );
    return 0x00;
  }

  // send ARP request
  ArpSendRequest( pIp );

  // return no success for now
  return 0x01;
}

