/*
 * COPYRIGHT:   See COPYING in the top level directory
 * PROJECT:     ReactOS TCP/IP protocol driver
 * FILE:        network/icmp.c
 * PURPOSE:     Internet Control Message Protocol routines
 * PROGRAMMERS: Casper S. Hornstrup (chorns@users.sourceforge.net)
 * REVISIONS:
 *   CSH 01/08-2000 Created
 */
#include <tcpip.h>
#include <icmp.h>
#include <rawip.h>
#include <checksum.h>
#include <routines.h>
#include <transmit.h>
#include <pool.h>


VOID SendICMPComplete(
    PVOID Context,
    PNDIS_PACKET Packet,
    NDIS_STATUS NdisStatus)
/*
 * FUNCTION: ICMP datagram transmit completion handler
 * ARGUMENTS:
 *     Context    = Pointer to context infomation (IP_PACKET)
 *     Packet     = Pointer to NDIS packet
 *     NdisStatus = Status of transmit operation
 * NOTES:
 *     This routine is called by IP when a ICMP send completes
 */
{
    PIP_PACKET IPPacket = (PIP_PACKET)Context;

    TI_DbgPrint(DEBUG_ICMP, ("Freeing NDIS packet (%X).\n", Packet));

    /* Free packet */
    FreeNdisPacket(Packet);

    TI_DbgPrint(DEBUG_ICMP, ("Freeing IP packet at %X.\n", IPPacket));

    (*IPPacket->Free)(IPPacket);
}


PIP_PACKET PrepareICMPPacket(
    PNET_TABLE_ENTRY NTE,
    PIP_ADDRESS Destination,
    UINT DataSize)
/*
 * FUNCTION: Prepares an ICMP packet
 * ARGUMENTS:
 *     NTE         = Pointer to net table entry to use
 *     Destination = Pointer to destination address
 *     DataSize    = Size of dataarea
 * RETURNS:
 *     Pointer to IP packet, NULL if there is not enough free resources
 */
{
    PIP_PACKET IPPacket;
    PNDIS_PACKET NdisPacket;
    PNDIS_BUFFER NdisBuffer;
    NDIS_STATUS NdisStatus;
    PIPv4_HEADER IPHeader;
    PVOID DataBuffer;
    ULONG Size;

    TI_DbgPrint(DEBUG_ICMP, ("Called. DataSize (%d).\n", DataSize));

    /* Prepare ICMP packet */

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

    /* No special flags */
    IPPacket->Flags = 0;

    Size = MaxLLHeaderSize + sizeof(IPv4_HEADER) +
        sizeof(ICMP_HEADER) + DataSize;
    DataBuffer = ExAllocatePool(NonPagedPool, Size);
    if (!DataBuffer) {
        (*IPPacket->Free)(IPPacket);
        return NULL;
    }

    TI_DbgPrint(DEBUG_ICMP, ("Size (%d). Data at (0x%X).\n", Size, DataBuffer));

    /* Allocate NDIS packet */
    NdisAllocatePacket(&NdisStatus, &NdisPacket, GlobalPacketPool);
    if (NdisStatus != NDIS_STATUS_SUCCESS) {
        (*IPPacket->Free)(IPPacket);
        ExFreePool(DataBuffer);
        return NULL;
    }

    TI_DbgPrint(MAX_TRACE, ("NdisPacket at (0x%X).\n", NdisPacket));

    /* Allocate NDIS buffer for maximum link level header and ICMP packet */
    NdisAllocateBuffer(&NdisStatus, &NdisBuffer, GlobalBufferPool,
        DataBuffer, Size);
    if (NdisStatus != NDIS_STATUS_SUCCESS) {
        (*IPPacket->Free)(IPPacket);
        NdisFreePacket(NdisPacket);
        ExFreePool(DataBuffer);
        return NULL;
    }

    TI_DbgPrint(MAX_TRACE, ("NdisBuffer at (0x%X).\n", NdisBuffer));

    /* Link NDIS buffer into packet */
    NdisChainBufferAtFront(NdisPacket, NdisBuffer);
    IPPacket->NdisPacket = NdisPacket;
    IPPacket->Header     = (PVOID)((ULONG_PTR)DataBuffer + MaxLLHeaderSize);
    IPPacket->Data       = (PVOID)((ULONG_PTR)DataBuffer + MaxLLHeaderSize + sizeof(IPv4_HEADER));

    IPPacket->HeaderSize = sizeof(IPv4_HEADER);
    IPPacket->TotalSize  = Size - MaxLLHeaderSize;
    RtlCopyMemory(&IPPacket->DstAddr, Destination, sizeof(IP_ADDRESS));

    /* Build IPv4 header. FIXME: IPv4 only */

    IPHeader = (PIPv4_HEADER)IPPacket->Header;

    /* Version = 4, Length = 5 DWORDs */
    IPHeader->VerIHL = 0x45;
    /* Normal Type-of-Service */
    IPHeader->Tos = 0;
    /* Length of data and header */
    IPHeader->TotalLength = WH2N((USHORT)DataSize +
        sizeof(IPv4_HEADER) + sizeof(ICMP_HEADER));
    /* Identification */
    IPHeader->Id = (USHORT)Random();
    /* One fragment at offset 0 */
    IPHeader->FlagsFragOfs = 0;
    /* Time-to-Live is 128 */
    IPHeader->Ttl = 128;
    /* Internet Control Message Protocol */
    IPHeader->Protocol = IPPROTO_ICMP;
    /* Checksum is 0 (for later calculation of this) */
    IPHeader->Checksum = 0;
    /* Source address */
    IPHeader->SrcAddr = NTE->Address->Address.IPv4Address;
    /* Destination address */
    IPHeader->DstAddr = Destination->Address.IPv4Address;

    /* Completion handler */
    PC(NdisPacket)->Complete = SendICMPComplete;
    PC(NdisPacket)->Context  = IPPacket;

    return IPPacket;
}


VOID ICMPReceive(
    PNET_TABLE_ENTRY NTE,
    PIP_PACKET IPPacket)
/*
 * FUNCTION: Receives an ICMP packet
 * ARGUMENTS:
 *     NTE      = Pointer to net table entry which the packet was received on
 *     IPPacket = Pointer to an IP packet that was received
 */
{
    PICMP_HEADER ICMPHeader;
    PIP_PACKET NewPacket;
    UINT DataSize;

    TI_DbgPrint(DEBUG_ICMP, ("Called.\n"));

    ICMPHeader = (PICMP_HEADER)IPPacket->Data;

    TI_DbgPrint(DEBUG_ICMP, ("Size (%d).\n", IPPacket->TotalSize));

    TI_DbgPrint(DEBUG_ICMP, ("HeaderSize (%d).\n", IPPacket->HeaderSize));

    TI_DbgPrint(DEBUG_ICMP, ("Type (%d).\n", ICMPHeader->Type));

    TI_DbgPrint(DEBUG_ICMP, ("Code (%d).\n", ICMPHeader->Code));

    TI_DbgPrint(DEBUG_ICMP, ("Checksum (0x%X).\n", ICMPHeader->Checksum));

    /* Checksum ICMP header and data */
    if (!CorrectChecksum(IPPacket->Data, IPPacket->TotalSize - IPPacket->HeaderSize)) {
        TI_DbgPrint(DEBUG_ICMP, ("Bad ICMP checksum.\n"));
        /* Discard packet */
        return;
    }

    switch (ICMPHeader->Type) {
    case ICMP_TYPE_ECHO_REQUEST:
        /* Reply with an ICMP echo reply message */
        DataSize  = IPPacket->TotalSize - IPPacket->HeaderSize - sizeof(ICMP_HEADER);
        NewPacket = PrepareICMPPacket(NTE, &IPPacket->SrcAddr, DataSize);
        if (!NewPacket)
            return;

        /* Copy ICMP header and data into new packet */
        RtlCopyMemory(NewPacket->Data, IPPacket->Data, DataSize + sizeof(ICMP_HEADER));
        ((PICMP_HEADER)NewPacket->Data)->Type     = ICMP_TYPE_ECHO_REPLY;
        ((PICMP_HEADER)NewPacket->Data)->Code     = 0;
        ((PICMP_HEADER)NewPacket->Data)->Checksum = 0;

        DisplayIPPacket(IPPacket);

        DisplayIPPacket(NewPacket);

        ICMPTransmit(NTE, NewPacket);

        TI_DbgPrint(DEBUG_ICMP, ("Echo reply sent.\n"));
        return;

    case ICMP_TYPE_ECHO_REPLY:
        break;

    default:
        TI_DbgPrint(DEBUG_ICMP, ("Discarded ICMP datagram of unknown type %d.\n",
            ICMPHeader->Type));
        /* Discard packet */
        break;
    }

    /* Send datagram up the protocol stack */
    RawIPReceive(NTE, IPPacket);
}


VOID ICMPTransmit(
    PNET_TABLE_ENTRY NTE,
    PIP_PACKET IPPacket)
/*
 * FUNCTION: Transmits an ICMP packet
 * ARGUMENTS:
 *     NTE      = Pointer to net table entry to use (NULL if don't care)
 *     IPPacket = Pointer to IP packet to transmit
 */
{
    PROUTE_CACHE_NODE RCN;

    TI_DbgPrint(DEBUG_ICMP, ("Called.\n"));

    /* Calculate checksum of ICMP header and data */
    ((PICMP_HEADER)IPPacket->Data)->Checksum = (USHORT)
        IPv4Checksum(IPPacket->Data, IPPacket->TotalSize - IPPacket->HeaderSize, 0);

    /* Get a route to the destination address */
    if (RouteGetRouteToDestination(&IPPacket->DstAddr, NTE, &RCN) == IP_SUCCESS) {
        /* Send the packet */
        if (IPSendDatagram(IPPacket, RCN) != STATUS_SUCCESS) {
            FreeNdisPacket(IPPacket->NdisPacket);
            (*IPPacket->Free)(IPPacket);
        }
        /* We're done with the RCN */
        DereferenceObject(RCN);
    } else {
        TI_DbgPrint(MIN_TRACE, ("RCN at (0x%X).\n", RCN));

        /* No route to destination (or no free resources) */
        TI_DbgPrint(DEBUG_ICMP, ("No route to destination address 0x%X.\n",
            IPPacket->DstAddr.Address.IPv4Address));
        /* Discard packet */
        FreeNdisPacket(IPPacket->NdisPacket);
        (*IPPacket->Free)(IPPacket);
    }
}


VOID ICMPReply(
    PNET_TABLE_ENTRY NTE,
    PIP_PACKET IPPacket,
	  UCHAR Type,
 	  UCHAR Code)
/*
 * FUNCTION: Transmits an ICMP packet in response to an incoming packet
 * ARGUMENTS:
 *     NTE      = Pointer to net table entry to use
 *     IPPacket = Pointer to IP packet that was received
 *     Type     = ICMP message type
 *     Code     = ICMP message code
 * NOTES:
 *     We have received a packet from someone and is unable to
 *     process it due to error(s) in the packet or we have run out
 *     of resources. We transmit an ICMP message to the host to
 *     notify him of the problem
 */
{
    UINT DataSize;
    PIP_PACKET NewPacket;

    TI_DbgPrint(DEBUG_ICMP, ("Called. Type (%d)  Code (%d).\n", Type, Code));

    DataSize = IPPacket->TotalSize;
    if ((DataSize) > (576 - sizeof(IPv4_HEADER) - sizeof(ICMP_HEADER)))
        DataSize = 576;

    NewPacket = PrepareICMPPacket(NTE, &IPPacket->SrcAddr, DataSize);
    if (!NewPacket)
        return;

    RtlCopyMemory((PVOID)((ULONG_PTR)NewPacket->Data + sizeof(ICMP_HEADER)),
        IPPacket->Header, DataSize);
    ((PICMP_HEADER)NewPacket->Data)->Type     = Type;
    ((PICMP_HEADER)NewPacket->Data)->Code     = Code;
    ((PICMP_HEADER)NewPacket->Data)->Checksum = 0;

    ICMPTransmit(NTE, NewPacket);
}

/* EOF */
