/* 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 <string.h>

#include "arp.h"
#include "checksum.h"
#include "config.h"
#include "ethernet.h"
#include "icmp.h"
#include "ip.h"
#include "macros.h"
#include "nethelp.h"
#include "tcp.h"
#include "udp.h"

// timing parameters
#define IpBufferTicksMax 50 // maximum age of buffered IP packet (in 200ms steps)

// buffers for IP packets to transmit
//  - used if MAC is unknown when packet shall be transmitted
//  - packet is sent when MAC becomes known
unsigned char IpBuffer0[80]; // some buffers with different length (IP packets have different length)
unsigned char IpBuffer1[80];
unsigned char IpBuffer2[160];
unsigned char IpBuffer3[320];
struct IpBufferTable // table with buffers
{
  unsigned char * pBuffer; // pointer to buffer for packet
  unsigned short BufferLength; // length of buffer
  unsigned short PacketLength; // length of packet in buffer, 0 if no packet in this buffer
  unsigned char Ticks; // age of entry in 200ms steps
} IpBufferTab[] =
{ // put smaller buffers infront of larger buffers
  //  - then short packets will use smaller buffers more often
  { IpBuffer0, sizeof( IpBuffer0 ), 0, 0 },
  { IpBuffer1, sizeof( IpBuffer1 ), 0, 0 },
  { IpBuffer2, sizeof( IpBuffer2 ), 0, 0 },
  { IpBuffer3, sizeof( IpBuffer3 ), 0, 0 },
};

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

  // increase age of buffered IP packets and remove timed out ones
  for( i = 0; i < count( IpBufferTab ); i++ )
  {
    if( IpBufferTab[i].PacketLength > 0 ) // buffer in use
    {
      IpBufferTab[i].Ticks++; // increase age
      if( IpBufferTab[i].Ticks > IpBufferTicksMax ) // too old
        IpBufferTab[i].PacketLength = 0; // discard packet
    }
  }
}

// process a received IP packet
void IpRecv( unsigned char * pData, unsigned short Length ) // (extern)
{
  struct IpPacket * pIpPack;
  unsigned int len;

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

  // convert pointer to IP packet
  // (this saves us from always casting pData)
  pIpPack = (struct IpPacket *)pData;

  // not IPv4 or not to own IP
  if( pIpPack->IpHdr.Ver_HdrLen != 0x45 || // IPv4 with no options present
      ! ip_eq( pIpPack->IpHdr.Dest, ConfigIp ) ) // own IP
    return;

  // ignore packets sent from invalid source adresses
  //  - this might be some attack or some router fault
  if( pIpPack->IpHdr.Src[0] >= 0xE0 || // broadcast, reserved or multicast addresses
      pIpPack->IpHdr.Src[0] == 0x7F || // loopback network
      ip_eq( pIpPack->IpHdr.Src, "\x00\x00\x00\x00" ) ) // IP 0.0.0.0
    return;
  // ignore packets sent from local network or broadcast address
  if( (pIpPack->IpHdr.Src[0] & ConfigMask[0]) == (ConfigIp[0] & ConfigMask[0]) && // source IP is in own subnet
      (pIpPack->IpHdr.Src[1] & ConfigMask[1]) == (ConfigIp[1] & ConfigMask[1]) &&
      (pIpPack->IpHdr.Src[2] & ConfigMask[2]) == (ConfigIp[2] & ConfigMask[2]) &&
      (pIpPack->IpHdr.Src[3] & ConfigMask[3]) == (ConfigIp[3] & ConfigMask[3]) )
  {
    if( (pIpPack->IpHdr.Src[0] & ~ConfigMask[0]) == 0x00 && // local network address
        (pIpPack->IpHdr.Src[1] & ~ConfigMask[1]) == 0x00 &&
        (pIpPack->IpHdr.Src[2] & ~ConfigMask[2]) == 0x00 &&
        (pIpPack->IpHdr.Src[3] & ~ConfigMask[3]) == 0x00 )
      return;
    if( (pIpPack->IpHdr.Src[0] & ~ConfigMask[0]) == 0xFF && // local broadcast address
        (pIpPack->IpHdr.Src[1] & ~ConfigMask[1]) == 0xFF &&
        (pIpPack->IpHdr.Src[2] & ~ConfigMask[2]) == 0xFF &&
        (pIpPack->IpHdr.Src[3] & ~ConfigMask[3]) == 0xFF )
      return;
  }
  // ignore packets sent from own IP address
  if( ip_eq( pIpPack->IpHdr.Src, ConfigIp ) )
    return;

  // ignore fragmented packets
  // BUG: fragmentation must be supported according to RFC781
  //      but there is no way of assembling packets with up to 64kB on a processor with 4kB of RAM
  if( (ntohs( pIpPack->IpHdr.FragOfs ) & 0xBFFF) != 0x0000 ) // fragment offset 0, MoreFrags=0, DontFrag=x, reservedFlag=0
    return;

  // check total length
  len = sizeof( struct EthernetHeader ) + ntohs( pIpPack->IpHdr.TotalLen ); // length according to IP header
  if( Length < len ) // packet is truncated
    return;
  Length = len; // remove ethernet padding from packet (maybe Length > len)

  // test header checksum
  if( Checksum( (unsigned char*)&pIpPack->IpHdr, sizeof( struct IpHeader ), 0x0000, 0x0000 ) != 0 )
    return;

  // branch according to protocol
  switch( pIpPack->IpHdr.Proto )
  {
    // ICMP
    case 0x01:
      IcmpRecv( pData, Length );
      break;
    // TCP
    case 0x06:
      TcpRecv( pData, Length );
      break;
    // UDP
    case 0x11:
      UdpRecv( pData, Length );
      break;
  }
}

// send an IP packet
// pData must point to a struct IpPacket with IpHdr.Proto and IpHdr.Dest already initialized
void IpSend( unsigned char * pData, unsigned short Length ) // (extern)
{
  struct IpPacket * pIpPack;
  unsigned int chk;
  unsigned char i;

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

  // convert pointer to IP packet
  // (this saves us from always casting pData)
  pIpPack = (struct IpPacket *)pData;

  // fill in header values
  pIpPack->IpHdr.Ver_HdrLen = 0x45;
  pIpPack->IpHdr.Tos = 0x00;
  pIpPack->IpHdr.TotalLen = htons( Length - sizeof( struct EthernetHeader ) );
  pIpPack->IpHdr.Id = 0x0000;
  pIpPack->IpHdr.FragOfs = 0x0000;
  pIpPack->IpHdr.Ttl = 0x40;
  pIpPack->IpHdr.HdrChk = 0x0000;
  ip_cpy( pIpPack->IpHdr.Src, ConfigIp );

  // generate header checksum
  chk = Checksum( (unsigned char *)&pIpPack->IpHdr, sizeof( struct IpHeader ), 0x0000, 0x0000 );
  pIpPack->IpHdr.HdrChk = htons( chk );

  // destination is in own subnet
  if( (pIpPack->IpHdr.Dest[0] & ConfigMask[0]) == (ConfigIp[0] & ConfigMask[0]) &&
      (pIpPack->IpHdr.Dest[1] & ConfigMask[1]) == (ConfigIp[1] & ConfigMask[1]) &&
      (pIpPack->IpHdr.Dest[2] & ConfigMask[2]) == (ConfigIp[2] & ConfigMask[2]) &&
      (pIpPack->IpHdr.Dest[3] & ConfigMask[3]) == (ConfigIp[3] & ConfigMask[3]) )
    // lookup MAC address of destination
    i = ArpLookup( pIpPack->IpHdr.Dest, pIpPack->EthHdr.Dest );
  // destination is not in own subnet
  else
    // lookup MAC address of default gateway
    i = ArpLookup( ConfigGw, pIpPack->EthHdr.Dest );

  // MAC available
  if( i == 0x00 )
  {
    // sent IP packet
    pIpPack->EthHdr.Type = htons( 0x0800 ); // ethernet packet type: IP
    EthernetSend( pData, Length );
    return;
  }

  // find a buffer to store the packet in
  for( i = 0; i < count( IpBufferTab ); i++ )
  {
    if( IpBufferTab[i].PacketLength == 0 && // buffer not in use
        Length < IpBufferTab[i].BufferLength ) // buffer long enough
    {
      // put packet into buffer
      memcpy( IpBufferTab[i].pBuffer, pData, Length );
      IpBufferTab[i].PacketLength = Length;
      IpBufferTab[i].Ticks = 0;
      break;
    }
  }
  // if no buffer was found, we cannnot do anything about it and must discard the packet (i.e. do nothing here)
}

// a MAC address was discovered
// called by ARP to notify IP
void IpGotMac( unsigned char Ip[4], unsigned char Mac[6] ) // (extern)
{
  unsigned char i;
  struct IpPacket * pIpPack;

  // search for buffered packets that can be sent now
  for( i = 0; i < count( IpBufferTab ); i++ )
  {
    if( IpBufferTab[i].PacketLength > 0 ) // buffer in use
    {
      // convert pointer to IP packet
      pIpPack = (struct IpPacket *)IpBufferTab[i].pBuffer;

      // destination is in own subnet
      if( (pIpPack->IpHdr.Dest[0] & ConfigMask[0]) == (ConfigIp[0] & ConfigMask[0]) &&
          (pIpPack->IpHdr.Dest[1] & ConfigMask[1]) == (ConfigIp[1] & ConfigMask[1]) &&
          (pIpPack->IpHdr.Dest[2] & ConfigMask[2]) == (ConfigIp[2] & ConfigMask[2]) &&
          (pIpPack->IpHdr.Dest[3] & ConfigMask[3]) == (ConfigIp[3] & ConfigMask[3]) )
      {
        // packet can be sent to destination
        if( ip_eq( pIpPack->IpHdr.Dest, Ip ) )
        {
          // send IP packet
          pIpPack->EthHdr.Type = htons( 0x0800 ); // ethernet packet type: IP
          mac_cpy( pIpPack->EthHdr.Dest, Mac );
          EthernetSend( IpBufferTab[i].pBuffer, IpBufferTab[i].PacketLength );
          // buffer is now free
          IpBufferTab[i].PacketLength = 0;
        }
      }
      // destination is not in own subnet
      else
      {
        // packet can be sent to gateway
        if( ip_eq( ConfigGw, Ip ) )
        {
          // send IP packet
          pIpPack->EthHdr.Type = htons( 0x0800 ); // ethernet packet type: IP
          mac_cpy( pIpPack->EthHdr.Dest, Mac );
          EthernetSend( IpBufferTab[i].pBuffer, IpBufferTab[i].PacketLength );
          // buffer is now free
          IpBufferTab[i].PacketLength = 0;
        }
      }
    }
  } // for( i ...
}

