/*
 * COPYRIGHT:   See COPYING in the top level directory
 * PROJECT:     ReactOS TCP/IP protocol driver
 * FILE:        network/receive.c
 * PURPOSE:     Internet Protocol receive routines
 * PROGRAMMERS: Casper S. Hornstrup (chorns@users.sourceforge.net)
 * NOTES:       The IP datagram reassembly algorithm is taken from
 *              from RFC 815
 * REVISIONS:
 *   CSH 01/08-2000 Created
 */
#include <tcpip.h>
#include <receive.h>
#include <routines.h>
#include <checksum.h>
#include <transmit.h>
#include <address.h>
#include <pool.h>
#include <route.h>


LIST_ENTRY ReassemblyListHead;
KSPIN_LOCK ReassemblyListLock;
NPAGED_LOOKASIDE_LIST IPDRList;
NPAGED_LOOKASIDE_LIST IPFragmentList;
NPAGED_LOOKASIDE_LIST IPHoleList;


PIPDATAGRAM_HOLE CreateHoleDescriptor(
  ULONG First,
  ULONG Last)
/*
 * FUNCTION: Returns a pointer to a IP datagram hole descriptor
 * ARGUMENTS:
 *     First = Offset of first octet of the hole
 *     Last  = Offset of last octet of the hole
 * RETURNS:
 *     Pointer to descriptor, NULL if there was not enough free
 *     resources
 */
{
	PIPDATAGRAM_HOLE Hole;

	TI_DbgPrint(DEBUG_IP, ("Called. First (%d)  Last (%d).\n", First, Last));

	Hole = ExAllocateFromNPagedLookasideList(&IPHoleList);
	if (!Hole) {
	    TI_DbgPrint(MIN_TRACE, ("Insufficient resources.\n"));
	    return NULL;
	}

	Hole->First = First;
	Hole->Last  = Last;

	TI_DbgPrint(DEBUG_IP, ("Returning hole descriptor at (0x%X).\n", Hole));

	return Hole;
}


VOID FreeIPDR(
  PIPDATAGRAM_REASSEMBLY IPDR)
/*
 * FUNCTION: Frees an IP datagram reassembly structure
 * ARGUMENTS:
 *     IPDR = Pointer to IP datagram reassembly structure
 */
{
  PLIST_ENTRY CurrentEntry;
  PLIST_ENTRY NextEntry;
  PIPDATAGRAM_HOLE CurrentH;
  PIP_FRAGMENT CurrentF;

  TI_DbgPrint(DEBUG_IP, ("Freeing IP datagram reassembly descriptor (0x%X).\n", IPDR));

  /* Free all descriptors */
  CurrentEntry = IPDR->HoleListHead.Flink;
  while (CurrentEntry != &IPDR->HoleListHead) {
    NextEntry = CurrentEntry->Flink;
	  CurrentH = CONTAINING_RECORD(CurrentEntry, IPDATAGRAM_HOLE, ListEntry);
    /* Unlink it from the list */
    RemoveEntryList(CurrentEntry);

    TI_DbgPrint(DEBUG_IP, ("Freeing hole descriptor at (0x%X).\n", CurrentH));

    /* And free the hole descriptor */
    ExFreeToNPagedLookasideList(&IPHoleList, CurrentH);

    CurrentEntry = NextEntry;
  }

  /* Free all fragments */
  CurrentEntry = IPDR->FragmentListHead.Flink;
  while (CurrentEntry != &IPDR->FragmentListHead) {
    NextEntry = CurrentEntry->Flink;
	  CurrentF = CONTAINING_RECORD(CurrentEntry, IP_FRAGMENT, ListEntry);
    /* Unlink it from the list */
    RemoveEntryList(CurrentEntry);

    TI_DbgPrint(DEBUG_IP, ("Freeing fragment data at (0x%X).\n", CurrentF->Data));

    /* Free the fragment data buffer */
    ExFreePool(CurrentF->Data);

    TI_DbgPrint(DEBUG_IP, ("Freeing fragment at (0x%X).\n", CurrentF));

    /* And free the fragment descriptor */
    ExFreeToNPagedLookasideList(&IPFragmentList, CurrentF);
    CurrentEntry = NextEntry;
  }

  /* Free resources for the header, if it exists */
  if (IPDR->IPv4Header) {
    TI_DbgPrint(DEBUG_IP, ("Freeing IPv4 header data at (0x%X).\n", IPDR->IPv4Header));
    ExFreePool(IPDR->IPv4Header);
  }

  TI_DbgPrint(DEBUG_IP, ("Freeing IPDR data at (0x%X).\n", IPDR));

  ExFreeToNPagedLookasideList(&IPDRList, IPDR);
}


VOID RemoveIPDR(
  PIPDATAGRAM_REASSEMBLY IPDR)
/*
 * FUNCTION: Removes an IP datagram reassembly structure from the global list
 * ARGUMENTS:
 *     IPDR = Pointer to IP datagram reassembly structure
 */
{
  KIRQL OldIrql;

  TI_DbgPrint(DEBUG_IP, ("Removing IPDR at (0x%X).\n", IPDR));

  KeAcquireSpinLock(&ReassemblyListLock, &OldIrql);
  RemoveEntryList(&IPDR->ListEntry);
  KeReleaseSpinLock(&ReassemblyListLock, OldIrql);
}


PIPDATAGRAM_REASSEMBLY GetReassemblyInfo(
  PIP_PACKET IPPacket)
/*
 * FUNCTION: Returns a pointer to an IP datagram reassembly structure
 * ARGUMENTS:
 *     IPPacket = Pointer to IP packet
 * NOTES:
 *     A datagram is identified by four paramters, which are
 *     Source and destination address, protocol number and
 *     identification number
 */
{
  KIRQL OldIrql;
  PLIST_ENTRY CurrentEntry;
  PIPDATAGRAM_REASSEMBLY Current;
  PIPv4_HEADER Header = (PIPv4_HEADER)IPPacket->Header;

  TI_DbgPrint(DEBUG_IP, ("Searching for IPDR for IP packet at (0x%X).\n", IPPacket));

  KeAcquireSpinLock(&ReassemblyListLock, &OldIrql);

  /* FIXME: Assume IPv4 */

  CurrentEntry = ReassemblyListHead.Flink;
  while (CurrentEntry != &ReassemblyListHead) {
	  Current = CONTAINING_RECORD(CurrentEntry, IPDATAGRAM_REASSEMBLY, ListEntry);
    if (AddrIsEqual(&IPPacket->SrcAddr, &Current->SrcAddr) &&
      (Header->Id == Current->Id) &&
      (Header->Protocol == Current->Protocol) &&
      (AddrIsEqual(&IPPacket->DstAddr, &Current->DstAddr))) {
      KeReleaseSpinLock(&ReassemblyListLock, OldIrql);

      return Current;
    }
    CurrentEntry = CurrentEntry->Flink;
  }

  KeReleaseSpinLock(&ReassemblyListLock, OldIrql);

  return NULL;
}


PIP_PACKET ReassembleDatagram(
  PIPDATAGRAM_REASSEMBLY IPDR)
/*
 * FUNCTION: Reassembles an IP datagram
 * ARGUMENTS:
 *     IPDR = Pointer to IP datagram reassembly structure
 * NOTES:
 *     This routine concatenates fragments into a complete IP datagram.
 *     The lock is held when this routine is called
 * RETURNS:
 *     Pointer to IP packet, NULL if there was not enough free resources
 */
{
  PIP_PACKET IPPacket;
  PLIST_ENTRY CurrentEntry;
  PIP_FRAGMENT Current;
  PVOID Data;

  TI_DbgPrint(DEBUG_IP, ("Reassembling datagram from IPDR at (0x%X).\n", IPDR));

  /* FIXME: Assume IPv4 */
  IPPacket = IPCreatePacket(IP_ADDRESS_V4);
  if (!IPPacket)
    return NULL;

  IPPacket->TotalSize  = IPDR->HeaderSize + IPDR->DataSize;
  IPPacket->ContigSize = IPPacket->TotalSize;
  IPPacket->HeaderSize = IPDR->HeaderSize;
  IPPacket->Position   = IPDR->HeaderSize;

  RtlCopyMemory(&IPPacket->SrcAddr, &IPDR->SrcAddr, sizeof(IP_ADDRESS));
  RtlCopyMemory(&IPPacket->DstAddr, &IPDR->DstAddr, sizeof(IP_ADDRESS));

  /* Allocate space for full IP datagram */
  IPPacket->Header = ExAllocatePool(NonPagedPool, IPPacket->TotalSize);
  if (!IPPacket->Header) {
    TI_DbgPrint(MIN_TRACE, ("Insufficient resources.\n"));
    (*IPPacket->Free)(IPPacket);
    return NULL;
  }

  /* Copy the header into the buffer */
  RtlCopyMemory(IPPacket->Header, IPDR->IPv4Header, IPDR->HeaderSize);  
  
  Data = (PVOID)((ULONG_PTR)IPPacket->Header + IPDR->HeaderSize);
  IPPacket->Data = Data;

  /* Copy data from all fragments into buffer */
  CurrentEntry = IPDR->FragmentListHead.Flink;
  while (CurrentEntry != &IPDR->FragmentListHead) {
    Current = CONTAINING_RECORD(CurrentEntry, IP_FRAGMENT, ListEntry);

    TI_DbgPrint(DEBUG_IP, ("Copying (%d) bytes of fragment data from (0x%X) to offset (%d).\n",
      Current->Size, Data, Current->Offset));
    /* Copy fragment data to the destination buffer at the correct offset */
    RtlCopyMemory(
		  (PVOID)((ULONG_PTR)Data + Current->Offset),
      Current->Data,
      Current->Size);

    CurrentEntry = CurrentEntry->Flink;
  }

  return IPPacket;
}


__inline VOID Cleanup(
  PKSPIN_LOCK Lock,
  KIRQL OldIrql,
  PIPDATAGRAM_REASSEMBLY IPDR,
  PVOID Buffer OPTIONAL)
/*
 * FUNCTION: Performs cleaning operations on errors
 * ARGUMENTS:
 *     Lock     = Pointer to spin lock to be released
 *     OldIrql  = Value of IRQL when spin lock was acquired
 *     IPDR     = Pointer to IP datagram reassembly structure to free
 *     Buffer   = Optional pointer to a buffer to free
 */
{
  TI_DbgPrint(MIN_TRACE, ("Insufficient resources.\n"));

  KeReleaseSpinLock(Lock, OldIrql);
  RemoveIPDR(IPDR);
  FreeIPDR(IPDR);
  if (Buffer)
    ExFreePool(Buffer);
}


VOID ProcessFragment(
  PIP_INTERFACE IF,
  PIP_PACKET IPPacket,
  PNET_TABLE_ENTRY NTE)
/*
 * FUNCTION: Processes an IP datagram or fragment
 * ARGUMENTS:
 *     IF       = Pointer to IP interface packet was receive on
 *     IPPacket = Pointer to IP packet
 *     NTE      = Pointer to NTE packet was received on
 * NOTES:
 *     This routine reassembles fragments and, if a whole datagram can
 *     be assembled, passes the datagram on to the IP protocol dispatcher
 */
{
  KIRQL OldIrql;
  PIPDATAGRAM_REASSEMBLY IPDR;
  PLIST_ENTRY CurrentEntry;
  PIPDATAGRAM_HOLE Hole, NewHole;
  USHORT FragFirst;
  USHORT FragLast;
  BOOLEAN MoreFragments;
  PIPv4_HEADER IPv4Header;
  PIP_PACKET Datagram;
  PIP_FRAGMENT Fragment;

  /* FIXME: Assume IPv4 */

  IPv4Header = (PIPv4_HEADER)IPPacket->Header;

  /* Check if we already have an reassembly structure for this datagram */
  IPDR = GetReassemblyInfo(IPPacket);
  if (IPDR) {
    TI_DbgPrint(DEBUG_IP, ("Continueing assembly.\n"));
    /* We have a reassembly structure */
    KeAcquireSpinLock(&IPDR->Lock, &OldIrql);
    CurrentEntry = IPDR->HoleListHead.Flink;
    Hole = CONTAINING_RECORD(CurrentEntry, IPDATAGRAM_HOLE, ListEntry);
  } else {
    TI_DbgPrint(DEBUG_IP, ("Starting new assembly.\n"));

    /* We don't have a reassembly structure, create one */
    IPDR = ExAllocateFromNPagedLookasideList(&IPDRList);
    if (!IPDR)
      /* We don't have the resources to process this packet, discard it */
      return;

    /* Create a descriptor spanning from zero to infinity.
       Actually, we use a value slightly greater than the
       maximum number of octets an IP datagram can contain */
    Hole = CreateHoleDescriptor(0, 65536);
    if (!Hole) {
      /* We don't have the resources to process this packet, discard it */
      ExFreeToNPagedLookasideList(&IPDRList, IPDR);
      return;
    }
    AddrInitIPv4(&IPDR->SrcAddr, IPv4Header->SrcAddr);
    AddrInitIPv4(&IPDR->DstAddr, IPv4Header->DstAddr);
    IPDR->Id         = IPv4Header->Id;
    IPDR->Protocol   = IPv4Header->Protocol;
    IPDR->IPv4Header = NULL;
    InitializeListHead(&IPDR->FragmentListHead);
    InitializeListHead(&IPDR->HoleListHead);
    InsertTailList(&IPDR->HoleListHead, &Hole->ListEntry);
    CurrentEntry = IPDR->HoleListHead.Flink;

    KeInitializeSpinLock(&IPDR->Lock);

    KeAcquireSpinLock(&IPDR->Lock, &OldIrql);

    /* Update the reassembly list */
    ExInterlockedInsertTailList(
		  &ReassemblyListHead,
      &IPDR->ListEntry,
      &ReassemblyListLock);
  }

  FragFirst     = (WN2H(IPv4Header->FlagsFragOfs) & IPv4_FRAGOFS_MASK) << 3;
  FragLast      = FragFirst + WN2H(IPv4Header->TotalLength);
  MoreFragments = (WN2H(IPv4Header->FlagsFragOfs) & IPv4_MF_MASK) > 0;

  for (;;) {
    if (CurrentEntry == &IPDR->HoleListHead)
      /* No more entries */
      break;

    TI_DbgPrint(DEBUG_IP, ("Comparing Fragment (%d,%d) to Hole (%d,%d).\n",
      FragFirst, FragLast, Hole->First, Hole->Last));

    if ((FragFirst > Hole->Last) || (FragLast < Hole->First)) {
      TI_DbgPrint(MID_TRACE, ("No overlap.\n"));
      /* The fragment does not overlap with the hole, try next
         descriptor in the list */

      CurrentEntry = CurrentEntry->Flink;
      if (CurrentEntry != &IPDR->HoleListHead)
          Hole = CONTAINING_RECORD(CurrentEntry, IPDATAGRAM_HOLE, ListEntry);
      continue;
    }

    /* The fragment overlap with the hole, unlink the descriptor */
    RemoveEntryList(CurrentEntry);

    if (FragFirst > Hole->First) {
      NewHole = CreateHoleDescriptor(Hole->First, FragLast - 1);
      if (!NewHole) {
        /* We don't have the resources to process this packet, discard it */
        Cleanup(&IPDR->Lock, OldIrql, IPDR, Hole);
        return;
      }

      /* Put the new descriptor in the list */
      InsertTailList(&IPDR->HoleListHead, &NewHole->ListEntry);
    }

    if ((FragLast < Hole->Last) && (MoreFragments)) {
      /* We can reuse the descriptor for the new hole */
		  Hole->First = FragLast + 1;

		  /* Put the new hole descriptor in the list */
      InsertTailList(&IPDR->HoleListHead, &Hole->ListEntry);
    } else
      ExFreeToNPagedLookasideList(&IPHoleList, Hole);

    /* If this is the first fragment, save the IP header */
    if (FragFirst == 0) {
      IPDR->IPv4Header = ExAllocatePool(NonPagedPool, IPPacket->HeaderSize);
      if (!IPDR->IPv4Header) {
        /* We don't have the resources to process this packet, discard it */
        Cleanup(&IPDR->Lock, OldIrql, IPDR, NULL);
        return;
      }

      TI_DbgPrint(DEBUG_IP, ("First fragment found. Header buffer is at (0x%X). "
        "Header size is (%d).\n", IPDR->IPv4Header, IPPacket->HeaderSize));

      RtlCopyMemory(IPDR->IPv4Header, IPPacket->Header, IPPacket->HeaderSize);
      IPDR->HeaderSize = IPPacket->HeaderSize;
    }

    /* Create a buffer, copy the data into it and put it
       in the fragment list */

    Fragment = ExAllocateFromNPagedLookasideList(&IPFragmentList);
    if (!Fragment) {
      /* We don't have the resources to process this packet, discard it */
      Cleanup(&IPDR->Lock, OldIrql, IPDR, NULL);
      return;
    }

    TI_DbgPrint(DEBUG_IP, ("Fragment descriptor allocated at (0x%X).\n", Fragment));

    Fragment->Size = IPPacket->TotalSize - IPPacket->HeaderSize;
    Fragment->Data = ExAllocatePool(NonPagedPool, Fragment->Size);
    if (!Fragment->Data) {
      /* We don't have the resources to process this packet, discard it */
      Cleanup(&IPDR->Lock, OldIrql, IPDR, Fragment);
      return;
    }

    TI_DbgPrint(DEBUG_IP, ("Fragment data buffer allocated at (0x%X)  Size (%d).\n",
      Fragment->Data, Fragment->Size));

    /* Copy datagram data into fragment buffer */
    CopyPacketToBuffer(
		  Fragment->Data,
      IPPacket->NdisPacket,
      IPPacket->Position,
      Fragment->Size);
      Fragment->Offset = FragFirst;

    /* If this is the last fragment, compute and save the datagram data size */
    if (!MoreFragments)
      IPDR->DataSize = FragFirst + Fragment->Size;

    /* Put the fragment in the list */
    InsertTailList(&IPDR->FragmentListHead, &Fragment->ListEntry);
	  break;
  }

  TI_DbgPrint(DEBUG_IP, ("Done searching for hole descriptor.\n"));

  if (IsListEmpty(&IPDR->HoleListHead)) {
    /* Hole list is empty which means a complete datagram can be assembled.
       Assemble the datagram and pass it to an upper layer protocol */

    TI_DbgPrint(DEBUG_IP, ("Complete datagram received.\n"));

    Datagram = ReassembleDatagram(IPDR);

	  KeReleaseSpinLock(&IPDR->Lock, OldIrql);

    RemoveIPDR(IPDR);
    FreeIPDR(IPDR);

    if (!Datagram)
      /* Not enough free resources, discard the packet */
      return;

    DISPLAY_IP_PACKET(Datagram);

    /* Give the packet to the protocol dispatcher */
    IPDispatchProtocol(NTE, Datagram);

    /* We're done with this datagram */
    ExFreePool(Datagram->Header);
    TI_DbgPrint(MAX_TRACE, ("Freeing datagram at (0x%X).\n", Datagram));
    (*Datagram->Free)(Datagram);
  } else
    KeReleaseSpinLock(&IPDR->Lock, OldIrql);
}


VOID IPFreeReassemblyList(
  VOID)
/*
 * FUNCTION: Frees all IP datagram reassembly structures in the list
 */
{
  KIRQL OldIrql;
  PLIST_ENTRY CurrentEntry;
  PIPDATAGRAM_REASSEMBLY Current;

  KeAcquireSpinLock(&ReassemblyListLock, &OldIrql);

  CurrentEntry = ReassemblyListHead.Flink;
  while (CurrentEntry != &ReassemblyListHead) {
	  Current = CONTAINING_RECORD(CurrentEntry, IPDATAGRAM_REASSEMBLY, ListEntry);
    /* Unlink it from the list */
    RemoveEntryList(CurrentEntry);

    /* And free the descriptor */
    FreeIPDR(Current);

    CurrentEntry = CurrentEntry->Flink;
  }

  KeReleaseSpinLock(&ReassemblyListLock, OldIrql);
}


VOID IPDatagramReassemblyTimeout(
  VOID)
/*
 * FUNCTION: IP datagram reassembly timeout handler
 * NOTES:
 *     This routine is called by IPTimeout to free any resources used
 *     to hold IP fragments that have taken too long to reassemble
 */
{
}


VOID IPv4Receive(
  PVOID Context,
  PIP_PACKET IPPacket)
/*
 * FUNCTION: Receives an IPv4 datagram (or fragment)
 * ARGUMENTS:
 *     Context  = Pointer to context information (IP_INTERFACE)
 *     IPPacket = Pointer to IP packet
 */
{
  PNEIGHBOR_CACHE_ENTRY NCE;
  PNET_TABLE_ENTRY NTE;
  UINT AddressType;

  TI_DbgPrint(DEBUG_IP, ("Received IPv4 datagram.\n"));

  IPPacket->HeaderSize = (((PIPv4_HEADER)IPPacket->Header)->VerIHL & 0x0F) << 2;

  if (IPPacket->HeaderSize > IPv4_MAX_HEADER_SIZE) {
    TI_DbgPrint(MIN_TRACE, ("Datagram received with incorrect header size (%d).\n",
      IPPacket->HeaderSize));
    /* Discard packet */
    return;
  }

  /* Checksum IPv4 header */
  if (!CorrectChecksum(IPPacket->Header, IPPacket->HeaderSize)) {
    TI_DbgPrint(MIN_TRACE, ("Datagram received with bad checksum. Checksum field (0x%X)\n",
      WN2H(((PIPv4_HEADER)IPPacket->Header)->Checksum)));
    /* Discard packet */
    return;
  }

//    TI_DbgPrint(DEBUG_IP, ("TotalSize (datalink) is (%d).\n", IPPacket->TotalSize));

  IPPacket->TotalSize = WN2H(((PIPv4_HEADER)IPPacket->Header)->TotalLength);

//    TI_DbgPrint(DEBUG_IP, ("TotalSize (IPv4) is (%d).\n", IPPacket->TotalSize));

	AddrInitIPv4(&IPPacket->SrcAddr, ((PIPv4_HEADER)IPPacket->Header)->SrcAddr);
	AddrInitIPv4(&IPPacket->DstAddr, ((PIPv4_HEADER)IPPacket->Header)->DstAddr);

  IPPacket->Position = IPPacket->HeaderSize;
  IPPacket->Data     = (PVOID)((ULONG_PTR)IPPacket->Header + IPPacket->HeaderSize);

  /* FIXME: Possibly forward packets with multicast addresses */

  /* FIXME: Should we allow packets to be received on the wrong interface? */
#if 0
  NTE = IPLocateNTE(&IPPacket->DstAddr, &AddressType);
#else
  NTE = IPLocateNTEOnInterface((PIP_INTERFACE)Context, &IPPacket->DstAddr, &AddressType);
#endif
  if (NTE) {
    /* This packet is destined for us */
    ProcessFragment((PIP_INTERFACE)Context, IPPacket, NTE);

    /* Done with this NTE */
    DereferenceObject(NTE);
  } else {
    /* This packet is not destined for us. If we are a router,
       try to find a route and forward the packet */

    /* FIXME: Check if acting as a router */
#if 1
    //NCE = RouteFindRouter(&IPPacket->DstAddr, NULL);
    NCE = NULL;
    if (NCE) {
      /* FIXME: Possibly fragment datagram */
      /* Forward the packet */
      IPSendFragment(IPPacket, NCE);
    } else {
      TI_DbgPrint(MIN_TRACE, ("No route to destination (0x%X).\n",
        IPPacket->DstAddr.Address.IPv4Address));

      /* FIXME: Send ICMP error code */
    }
#endif
  }
}


VOID IPReceive(
  PVOID Context,
  PIP_PACKET IPPacket)
/*
 * FUNCTION: Receives an IP datagram (or fragment)
 * ARGUMENTS:
 *     Context  = Pointer to context information (IP_INTERFACE)
 *     IPPacket = Pointer to IP packet
 */
{
  UINT Version;

  /* Check that IP header has a supported version */
  Version = (((PIPv4_HEADER)IPPacket->Header)->VerIHL >> 4);

  switch (Version) {
  case 4:
    IPPacket->Type = IP_ADDRESS_V4;
    IPv4Receive(Context, IPPacket);
    break;
  case 6:
    IPPacket->Type = IP_ADDRESS_V6;
    TI_DbgPrint(MAX_TRACE, ("Datagram of type IPv6 discarded.\n"));
    return;
  default:
	  TI_DbgPrint(MIN_TRACE, ("Datagram has an unsupported IP version %d.\n", Version));
    return;
  }
}

/* EOF */
