/*
    Copyright 2000 (c) by Boris Lutz,
    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/ICMP/NetICMPModuleUp.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 "NetModulesNew.h"
#include "NetAttr.h"
#include "LWThreads.h"
#include "NetBitFields.h"
#include "NetByteorder.h"
#include "NetDebug.h"
#include "NetIPModule.h"
#include "NetIPChecksum.h"
#include "NetICMPModule.h"

static void neticmpEchoUp(LWPool lw, NetBuf data, NetAttrs meta, NetModuleId id);

void netICMPModuleUpInit (NetModule this)
{
  // XXX what should I do?
  return;
}


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


void netICMPModuleUpMain (LWPool lw, ThreadArg arg)
{
  NetModule		modDesc = (NetModule) arg;
  NetICMPHdr		*hdr;
  unsigned short	tLen;
  NetBuf			data;
  NetAttrs		meta;


  while (1)
  {
    netmodRecv (lw, &data, &meta);
    PACKSTART(modDesc);
    hdr = (NetICMPHdr*) NETBUF_DATA(data);

    /* verify minimum lenght */
    tLen = netbufLen(data);
    if (tLen < sizeof(NetICMPHdr) )
    {
      PACKERROR(modDesc);
      netmodRIP (lw, data, meta, "netICMP: Packet to small\n");
      continue;
    }
    /* verify checksum */
    if ( netipChecksum (data, tLen) )
    {
      PACKERROR(modDesc);
      netmodRIP (lw, data, meta, "netICMP: Checksum failed\n");
      continue;
    }

    /* set all attributes */
    if ( !(	netattrSet(meta, NETATTR_ICMP_TYPE, hdr->type ) &&
            netattrSet(meta, NETATTR_ICMP_CHECKSUM, ntohs(hdr->checksum)) &&
            netattrSet(meta, NETATTR_ICMP_CODE, hdr->code ) ))
    {
      PACKERROR(modDesc);
      netmodRIP (lw, data, meta, "netICMP: failed to set attributes\n");
      continue;
    }

    /* Strip header (4 Bytes) */
    data->start += sizeof(NetICMPHdr);

    /*-CJ		netdbgDisplay(NETDEBUG_ICMP, "netICMP: got ICMP packet:\n");
    		netdbgDumpBuf(NETDEBUG_ICMP, data);
    		netdbgDisplay(NETDEBUG_ICMP, "*************************\n");
    		netdbgDumpAttr(NETDEBUG_ICMP, meta);
    		netdbgDisplay(NETDEBUG_ICMP, "*************************\n");
    */
    /* check type */
    switch (hdr->type)
    {
    case NETICMP_TYPE_ECHO:
      PACKDONE(modDesc);
      neticmpEchoUp(lw, data, meta, CHAINNEXT(modDesc));
      break;
    default:
      PACKERROR(modDesc);
      netmodRIP (lw, data, meta, "neticmp: Unknown type\n");
      break;
    }
  }
}


static void neticmpEchoUp(LWPool lw, NetBuf data, NetAttrs meta, NetModuleId id)
{
  unsigned short	val1;
  unsigned long checksum = 0;


  /* We resend exactly the same data to the sender
   * Only 'type' is changed from ECHO to ECHOREPLY */
  /* Re-calculate checksum (see RFC 1141)
   * hdr->checksum=htons(netipChecksum(buf,netbufLen(buf)));
   * ~C' = ~(C + (-m) + m') = ~C + (m - m') = ~C + m + ~m' 
   */
  if ( netattrGet(meta, NETATTR_ICMP_CHECKSUM, &val1) )
  {
    checksum = val1 +
               (long)(unsigned short)(NETICMP_TYPE_ECHO << 8)+
               (long)(unsigned short)~(NETICMP_TYPE_ECHOREPLY << 8);
    checksum = (checksum & 0xFFFF) + (checksum >> 16); /* add carry */
  }
  else
  {
    checksum = 0;
  }

  if (!(	/* XXX this is a hack to get ping working */
        netattrGet(meta, NETATTR_IF_FROM, &val1) &&
        netattrSet(meta, NETATTR_IF_TO, val1) &&
        netattrGet(meta, NETATTR_IN_TYPE, &val1) &&
        netattrSet(meta, NETATTR_OSI_2_TYPE, val1) &&
        /* reset the new calculated checksum */
        netattrSet(meta, NETATTR_ICMP_CHECKSUM, checksum) &&
        /* and finaly set the ICMP type to ECHOREPLY */
        netattrSet(meta, NETATTR_ICMP_TYPE, NETICMP_TYPE_ECHOREPLY) ))
  {
    /* one of the netattr funtctions failed */
    netmodRIP (lw, data, meta, "netICMP: Failed to set new attributes!\n");
    return;
  }

  /* now unset those IP attributes that need to be reset */
  /* XXX this is mainly the TTL field and checksum */
  netattrGet(meta, NETATTR_OSI_3_TYPE, &val1);
  switch ( val1 )
  {
  case NETATTR_TYPE_IPv4:
    netattrUnset(meta, NETATTR_IP_TTL);
    netattrUnset(meta, NETATTR_IP_CHECKSUM);
    break;
  default:
    netdbgPrintf(0, "netICMP: unknown OSI 3 protocol (%i)\n", val1);
    return;
  }
  /* normaly we would leave the stack so set NETATTR_SENDTO_LOOP */
  netattrSet(meta, NETATTR_SENDTO_LOOP, NETATTR_TYPE_ICMP);

  /* Finished: send the packet */
  netmodSend (lw, id, data, meta);

  return;
}

