/* 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 "config.h"
#include "checksum.h"
#include "ethernet.h"
#include "ip.h"
#include "macros.h"
#include "nethelp.h"
#include "serial.h"
#include "udp.h"

// some kind of "token bucket" for UDP echo
#define UdpEchoTicks 10 // allowed rate of UDP echo replies (in 200ms steps)
unsigned char UdpEchoTickCnt = 0; // tick counter
#define UdpEchoReliesMax 3 // maximum value for UdpEchoReplies
unsigned char UdpEchoReplies = 0; // number of UDP echo replies that may be sent at the moment

// tick procedure - call every 200ms
void UdpTick200( void ) // (extern)
{
  // count ticks
  UdpEchoTickCnt++;
  // time to allow one reply more
  if( UdpEchoTickCnt >= UdpEchoTicks )
  {
    UdpEchoTickCnt = 0;

    // increase reply count if not at maximum
    if( UdpEchoReplies < UdpEchoReliesMax )
      UdpEchoReplies++;
  }
}

// process a received UDP echo packet
static void UdpEchoRecv( unsigned char * pData, unsigned short Length )
{
  struct UdpPacket * pUdpPack;

  // convert pointer to UDP packet
  // (this saves us from always casting pData)
  pUdpPack = (struct UdpPacket *)pData;

  // source port is UDP echo port
  if( pUdpPack->UdpHdr.SrcPort == htons( 7 ) )
    // ignore this packet
    //  - UDP echo answer to another UDP echo port will result in endless echoing
    return;

  // only reply with allowed packet rate
  if( UdpEchoReplies == 0 )
    return;
  UdpEchoReplies--;

  // send an UDP echo
  //  - use same buffer to send reply
  //  - this saves us from allocating a new buffer
  //  - this saves us from copying the data
  pUdpPack->UdpHdr.DestPort = pUdpPack->UdpHdr.SrcPort; // back to originationg port
  pUdpPack->UdpHdr.SrcPort = htons( 7 ); // UDP echo port
  ip_cpy( pUdpPack->IpHdr.Dest, pUdpPack->IpHdr.Src ); // back to originating IP
  UdpSend( pData, Length );
}

// process a received serial output packet
static void UdpSerOutRecv( unsigned char * pData, unsigned short Length )
{
  // short packet
  if( Length < sizeof( struct UdpPacket ) )
	  return;

  // get pointer to data and length of data
  unsigned char * ptr = pData + sizeof( struct UdpPacket );
  unsigned short len = Length - sizeof( struct UdpPacket );

  // output serial data
  SerialOutput( ptr, len );
}

// process a received UDP packet
void UdpRecv( unsigned char * pData, unsigned short Length ) // (extern)
{
  struct UdpPacket * pUdpPack;
  unsigned int len;

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

  // convert pointer to UDP packet
  // (this saves us from always casting pData)
  pUdpPack = (struct UdpPacket *)pData;

  // ignore packets sent from or to port 0
  //  - this might be some attack
  if( pUdpPack->UdpHdr.SrcPort == htons( 0 ) ||
      pUdpPack->UdpHdr.DestPort == htons( 0 ) )
    return;

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

  // test checksum
  if( Checksum( (unsigned char*)&pUdpPack->IpHdr.Src,
                Length - sizeof( struct EthernetHeader ) - sizeof( struct IpHeader ) + 8,
                0x0011,
                ntohs( pUdpPack->UdpHdr.Length ) ) != 0 )
    return;

  // branch according to destination port
  switch( ntohs( pUdpPack->UdpHdr.DestPort ) )
  {
    // UDP echo
    case 7:
      UdpEchoRecv( pData, Length );
      break;
    // serial output
    case 2342:
      UdpSerOutRecv( pData, Length );
      break;
  }
}

// send an UDP packet
// pData must point to a struct UdpPacket with UdpHdr.SrcPort, UdpHdr.DestPort and IpHdr.Dest already initialized
void UdpSend( unsigned char * pData, unsigned short Length ) // (extern)
{
  struct UdpPacket * pUdpPack;
  unsigned int chk;

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

  // convert pointer to UDP packet
  // (this saves us from always casting pData)
  pUdpPack = (struct UdpPacket *)pData;

  // fill in header values
  pUdpPack->UdpHdr.Length = htons( Length - sizeof( struct EthernetHeader ) - sizeof( struct IpHeader ) );
  pUdpPack->UdpHdr.Chk = 0x0000;
  ip_cpy( pUdpPack->IpHdr.Src, ConfigIp ); // put IP already here into IP header
                                           // because it is needed for calculation of UDP checksum

  // generate checksum
  chk = Checksum( (unsigned char *)&pUdpPack->IpHdr.Src,
                  Length - sizeof( struct EthernetHeader ) - sizeof( struct IpHeader ) + 8,
                  0x0011,
                  ntohs( pUdpPack->UdpHdr.Length ) );
  pUdpPack->UdpHdr.Chk = htons( chk );

  // send UDP packet
  pUdpPack->IpHdr.Proto = 0x11; // UDP
  IpSend( pData, Length );
}


