/*
    Copyright 2000 (c) by David Schweikert,
    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/old/IP/NetIPdown.c,v $
    Author(s):             
    Affiliation:           ETH Zuerich, TIK
    Version:               $Revision: 2922 $
    Creation Date:         
    Last Date of Change:   $Date: 2007-03-19 17:48:52 +0100 (Mon, 19 Mär 2007) $      by: $Author: baderma $
 
 
    $Log: NetIPdown.c,v $
    Revision 1.2  2000/11/15 10:12:14  ruf
    Repository Reorganisation
 
    Revision 1.1  2000/03/31 17:50:37  gfa
    Merged with /Net from several term projects
 
 
*/
#include "NetIP.h"
#include "NetIPchecksum.h"

#include <Topsy.h>
#include <Messages.h>
#include <NetDebug.h>
#include <NetBuf.h>
#include <NetAttr.h>
#include <NetConfig.h>
#include <NetModules.h>
#include <NetBitFields.h>
#include <NetIface.h>

static int netipFillHeader(NetBuf buf, NetAttrs attrs);
static void netipDownSend(NetBuf buf, NetAttrs attrs);
static void netipDownSendFragment(NetBuf buf, NetAttrs attrs,
                                  int hlen, unsigned short off, int more);
static void netipDownFragmentPacket(NetBuf buf, NetAttrs attrs, int hlen, int dlen, int mtu);

/* Note: these are a classic example of BAD macros :-) */
#define MAX(a,b) (a>b?a:b)
#define MIN(a,b) (a>b?b:a)

void netipDown(NetBuf buf, NetAttrs attrs)
{
  /* Add and fill header */
  netdbgDisplay(NETDEBUG_IP, "netip : Adding header to downgoing packet.\n");
  netbufAddHead(&buf,sizeof(NetIPHdr));
  if(!netipFillHeader(buf,attrs))
  {
    netbufFree(buf);
    NETATTR_FREE(attrs);
    return;
  }

  /* Send... */
  netipDownSend(buf,attrs);
}

static int netipFillHeader(NetBuf buf, NetAttrs attrs)
{
  NetIPHdr *hdr=(NetIPHdr *) NETBUF_DATA(buf);
  unsigned short val, hlen;

  /* version */
  BNIBSET(hdr->bitfield1,0,4);

  /* header length */
  hlen = sizeof(NetIPHdr);
  BNIBSET(hdr->bitfield1,1,hlen>>2);

  /* id */
  if(netattrGet(attrs,NETATTR_IP_ID, &val))
    hdr->id = htons(val);
  else
    hdr->id = ++netip_last_id;

  /* type of service */
  if(netattrGet(attrs,NETATTR_IP_TOS,&val))
    hdr->tos = val;
  else
    hdr->tos = 0;

  /* total length */
  hdr->tot_len = htons(netbufLen(buf));

  /* TTL */
  if(netattrGet(attrs,NETATTR_IP_TTL, &val))
    hdr->ttl = val;
  else
    hdr->ttl = NETIP_DEFAULT_TTL;

  /* protocol */
  if(!netattrGet(attrs,NETATTR_IP_PROTOCOL, &val))
  {
    netdbgDisplay(NETDEBUG_IP, "netip: NETATTR_IP_PROTOCOL not specified!\n");
    return 0;
  }
  hdr->protocol = val;

  /* source ip address (to be filled in netipDownSend) */
  hdr->saddr0 = 0;

  /* destination ip address */
  if(!netattrGet(attrs,NETATTR_IP_TO_0,&val))
  {
    netdbgDisplay(NETDEBUG_IP, "netip: NETATTR_IP_TO not specified!\n");
    return 0;
  }
  hdr->daddr0 = val;
  netattrGet(attrs,NETATTR_IP_TO_1,&val);
  /* daddr1 = daddr1<<8 | val; */
  hdr->daddr1 = val;
  netattrGet(attrs,NETATTR_IP_TO_2,&val);
  hdr->daddr2 = val;
  netattrGet(attrs,NETATTR_IP_TO_3,&val);
  hdr->daddr3 = val;

  return 1;
}

/* Send down IP packet (header already built) */
static void netipDownSend(NetBuf buf, NetAttrs attrs)
{
  NetIPHdr *hdr=(NetIPHdr *) NETBUF_DATA(buf);
  int iface, len;
  unsigned short hlen,mtu,tmp;

  /* Determine Route */
  iface=netipRoute(attrs);
  if(iface<0)
  {
    /* Dest. unreachable */
    netdbgDisplay(NETDEBUG_GENERIC, "Destination unreachable.\n");
    netbufFree(buf);
    NETATTR_FREE(attrs);
    return;
  }
  netattrSet(attrs,NETATTR_IF_TO, iface);

  /* If source address not set, set it to iface address */
  if(hdr->saddr0 == 0)
  {
    netifGetAttr(iface,NETATTR_IP_FROM_0,&tmp);
    hdr->saddr0 = tmp;
    netifGetAttr(iface,NETATTR_IP_FROM_1,&tmp);
    hdr->saddr1 = tmp;
    netifGetAttr(iface,NETATTR_IP_FROM_2,&tmp);
    hdr->saddr2 = tmp;
    netifGetAttr(iface,NETATTR_IP_FROM_3,&tmp);
    hdr->saddr3 = tmp;
  }

  /* Determine MTU of iface */
  if(!netifGetAttr(iface,NETATTR_IF_MTU,&mtu))
    mtu=NETIP_DEFAULT_MTU;

  /* Determine header length */
  hlen = BNIBGET(hdr->bitfield1,1);
  hlen <<= 2;

  /* Determine Total length */
  len = netbufLen(buf);
  if(len<=mtu)
  {
    /* don't need to fragment */
    netipDownSendFragment(buf,attrs,hlen,0,0);
  }
  else
  {
    /* fragment packet */
    netipDownFragmentPacket(buf,attrs,hlen,len,mtu);
  }
}

static void netipDownSendFragment(NetBuf buf, NetAttrs attrs,
                                  int hlen, unsigned short off, int more)
{
  NetIPHdr *hdr=(NetIPHdr *) NETBUF_DATA(buf);
  unsigned short tmp, df, mf;

  /* DF: Don't Fragment */
  if(netattrGet(attrs,NETATTR_IP_DF,&tmp))
    df=1U<<14;
  else
    df=0;

  /* MF: More Fragments */
  mf=(more?1:0);
  mf<<=15;

  /* Set off, DF and MF */
  hdr->frag_off = htons(off|df|mf);

  /* Checksum */
  hdr->checksum=0;
  hdr->checksum=netipChecksum(buf,hlen);

  /* Send down packet */
  netcfgSendNextDown(NETMODULE_IP, buf, attrs);
}


static void netipDownFragmentPacket(NetBuf buf, NetAttrs attrs, int hlen, int dlen, int mtu)
{
  NetBuf frag_data, frag_hdr;
  NetAttrs frag_attrs;
  int pos, frag_len, frag_nr=0;

  mtu -= hlen;
  pos  = hlen;

  while(hlen>0)
  {
    frag_len=MIN(dlen,mtu);
    frag_len^=7; /* round down to multiple of 8 */

    /* make a copy of the fragment data */
    if(!netbufCopy(&frag_data, buf, pos, frag_len))
      break;

    /* insert at head of the fragment data a copy of the
     * packet header */
    if(!netbufCopy(&frag_hdr, buf, 0, hlen))
    {
      netbufFree(frag_data);
      break;
    }
    frag_hdr->next = frag_data;

    /* make a copy of the attributes */
    if(!netbufClone((NetBuf *) &frag_attrs, (NetBuf) attrs))
    {
      netbufFree(frag_data);
      netbufFree(frag_hdr);
      break;
    }


    dlen -= frag_len;

    /* send it */
    netipDownSendFragment(frag_hdr,frag_attrs,hlen,frag_nr,dlen>0);

    frag_nr++;
    pos+=frag_len;
  }

  /* free original packet */
  netbufFree(buf);
  NETATTR_FREE(attrs);
}

