/*
 * COPYRIGHT:   See COPYING in the top level directory
 * PROJECT:     ReactOS TCP/IP protocol driver
 * FILE:        network/transmit.c
 * PURPOSE:     Internet Protocol transmit routines
 * PROGRAMMERS: Casper S. Hornstrup (chorns@users.sourceforge.net)
 * REVISIONS:
 *   CSH 01/08-2000 Created
 */

#include "precomp.h"

BOOLEAN PrepareNextFragment(PIPFRAGMENT_CONTEXT IFC);
NTSTATUS IPSendFragment(PNDIS_PACKET NdisPacket,
			PNEIGHBOR_CACHE_ENTRY NCE,
			PIPFRAGMENT_CONTEXT IFC);

VOID IPSendComplete
(PVOID Context, PNDIS_PACKET NdisPacket, NDIS_STATUS NdisStatus)
/*
 * FUNCTION: IP datagram fragment send completion handler
 * ARGUMENTS:
 *     Context    = Pointer to context information (IP_INTERFACE)
 *     Packet     = Pointer to NDIS packet that was sent
 *     NdisStatus = NDIS status of operation
 * NOTES:
 *    This routine is called when an IP datagram fragment has been sent
 */
{
    PIPFRAGMENT_CONTEXT IFC = (PIPFRAGMENT_CONTEXT)Context;

    TI_DbgPrint
	(MAX_TRACE,
	 ("Called. Context (0x%X)  NdisPacket (0x%X)  NdisStatus (0x%X)\n",
	  Context, NdisPacket, NdisStatus));

    /* FIXME: Stop sending fragments and cleanup datagram buffers if
       there was an error */

    if (PrepareNextFragment(IFC)) {
	/* A fragment was prepared for transmission, so send it */
	IPSendFragment(IFC->NdisPacket, IFC->NCE, IFC);
    } else {
	TI_DbgPrint(MAX_TRACE, ("Calling completion handler.\n"));

	/* There are no more fragments to transmit, so call completion handler */
	FreeNdisPacket(IFC->NdisPacket);
	IFC->Complete(IFC->Context, IFC->Datagram, NdisStatus);
	exFreePool(IFC);
    }
}

NTSTATUS IPSendFragment(
    PNDIS_PACKET NdisPacket,
    PNEIGHBOR_CACHE_ENTRY NCE,
    PIPFRAGMENT_CONTEXT IFC)
/*
 * FUNCTION: Sends an IP datagram fragment to a neighbor
 * ARGUMENTS:
 *     NdisPacket = Pointer to an NDIS packet containing fragment
 *     NCE        = Pointer to NCE for first hop to destination
 * RETURNS:
 *     Status of operation
 * NOTES:
 *     Lowest level IP send routine
 */
{
    TI_DbgPrint(MAX_TRACE, ("Called. NdisPacket (0x%X)  NCE (0x%X).\n", NdisPacket, NCE));

    TI_DbgPrint(MAX_TRACE, ("NCE->State = %d.\n", NCE->State));
    return NBQueuePacket(NCE, NdisPacket, IPSendComplete, IFC);
}

BOOLEAN PrepareNextFragment(
    PIPFRAGMENT_CONTEXT IFC)
/*
 * FUNCTION: Prepares the next fragment of an IP datagram for transmission
 * ARGUMENTS:
 *     IFC = Pointer to IP fragment context
 * RETURNS:
 *     TRUE if a fragment was prepared for transmission, FALSE if
 *     there are no more fragments to send
 */
{
    UINT MaxData;
    UINT DataSize;
    PIPv4_HEADER Header;
    BOOLEAN MoreFragments;
    USHORT FragOfs;

    TI_DbgPrint(MAX_TRACE, ("Called. IFC (0x%X)\n", IFC));

    if (IFC->BytesLeft != 0) {

        TI_DbgPrint(MAX_TRACE, ("Preparing 1 fragment.\n"));

        MaxData  = IFC->PathMTU - IFC->HeaderSize;
        /* Make fragment a multiplum of 64bit */
        MaxData -= MaxData % 8;
        if (IFC->BytesLeft > MaxData) {
            DataSize      = MaxData;
            MoreFragments = TRUE;
        } else {
            DataSize      = IFC->BytesLeft;
            MoreFragments = FALSE;
        }

	TI_DbgPrint(MID_TRACE,("Copying data from %x to %x (%d)\n",
			       IFC->DatagramData, IFC->Data, DataSize));

        RtlCopyMemory(IFC->Data, IFC->DatagramData, DataSize); // SAFE

        FragOfs = (USHORT)IFC->Position; // Swap?
        if (MoreFragments)
            FragOfs |= IPv4_MF_MASK;
        else
            FragOfs &= ~IPv4_MF_MASK;

        Header = IFC->Header;
        Header->FlagsFragOfs = FragOfs;

        /* FIXME: Handle options */

        /* Calculate checksum of IP header */
        Header->Checksum = 0;
        Header->Checksum = (USHORT)IPv4Checksum(Header, IFC->HeaderSize, 0);
	TI_DbgPrint(MID_TRACE,("IP Check: %x\n", Header->Checksum));

        /* Update pointers */
        IFC->DatagramData = (PVOID)((ULONG_PTR)IFC->DatagramData + DataSize);
        IFC->Position  += DataSize;
        IFC->BytesLeft -= DataSize;

        return TRUE;
    } else {
        TI_DbgPrint(MAX_TRACE, ("No more fragments.\n"));
        return FALSE;
    }
}

NTSTATUS SendFragments(
    PIP_PACKET IPPacket,
    PNEIGHBOR_CACHE_ENTRY NCE,
    UINT PathMTU,
    PIP_TRANSMIT_COMPLETE Complete,
    PVOID Context)
/*
 * FUNCTION: Fragments and sends the first fragment of an IP datagram
 * ARGUMENTS:
 *     IPPacket  = Pointer to an IP packet
 *     NCE       = Pointer to NCE for first hop to destination
 *     PathMTU   = Size of Maximum Transmission Unit of path
 * RETURNS:
 *     Status of operation
 * NOTES:
 *     IP datagram is larger than PathMTU when this is called
 */
{
    PIPFRAGMENT_CONTEXT IFC;
    NDIS_STATUS NdisStatus;
    PVOID Data;
    UINT BufferSize = MaxLLHeaderSize + PathMTU, InSize;
    PCHAR InData;

    TI_DbgPrint(MAX_TRACE, ("Called. IPPacket (0x%X)  NCE (0x%X)  PathMTU (%d).\n",
        IPPacket, NCE, PathMTU));

    /* Make a smaller buffer if we will only send one fragment */
    GetDataPtr( IPPacket->NdisPacket, 0, &InData, &InSize );
    if( InSize < BufferSize ) BufferSize = InSize;

    TI_DbgPrint(MAX_TRACE, ("Fragment buffer is %d bytes\n", BufferSize));

    IFC = exAllocatePool(NonPagedPool, sizeof(IPFRAGMENT_CONTEXT));
    if (IFC == NULL)
        return STATUS_INSUFFICIENT_RESOURCES;

    /* Allocate NDIS packet */
    NdisStatus = AllocatePacketWithBuffer
	( &IFC->NdisPacket, NULL, BufferSize );

    if( !NT_SUCCESS(NdisStatus) ) {
	exFreePool( IFC );
	return NdisStatus;
    }

    GetDataPtr( IFC->NdisPacket, 0, (PCHAR *)&Data, &InSize );

    IFC->Header       = ((PCHAR)Data) + MaxLLHeaderSize;
    IFC->Datagram     = IPPacket->NdisPacket;
    IFC->DatagramData = ((PCHAR)IPPacket->Header) + IPPacket->HeaderSize;
    IFC->HeaderSize   = IPPacket->HeaderSize;
    IFC->PathMTU      = PathMTU;
    IFC->NCE          = NCE;
    IFC->Position     = 0;
    IFC->BytesLeft    = IPPacket->TotalSize - IPPacket->HeaderSize;
    IFC->Data         = (PVOID)((ULONG_PTR)IFC->Header + IPPacket->HeaderSize);
    IFC->Complete     = Complete;
    IFC->Context      = Context;

    TI_DbgPrint(MID_TRACE,("Copying header from %x to %x (%d)\n",
			   IPPacket->Header, IFC->Header,
			   IPPacket->HeaderSize));

    RtlCopyMemory( IFC->Header, IPPacket->Header, IPPacket->HeaderSize );

    /* Prepare next fragment for transmission and send it */

    PrepareNextFragment(IFC);
    IPSendFragment(IFC->NdisPacket, NCE, IFC);

    return STATUS_SUCCESS;
}

NTSTATUS IPSendDatagram(PIP_PACKET IPPacket, PNEIGHBOR_CACHE_ENTRY NCE,
			PIP_TRANSMIT_COMPLETE Complete, PVOID Context)
/*
 * FUNCTION: Sends an IP datagram to a remote address
 * ARGUMENTS:
 *     IPPacket = Pointer to an IP packet
 *     RCN      = Pointer to route cache node
 * RETURNS:
 *     Status of operation
 * NOTES:
 *     This is the highest level IP send routine. It possibly breaks the packet
 *     into two or more fragments before passing it on to the next lower level
 *     send routine (IPSendFragment)
 */
{
    TI_DbgPrint(MAX_TRACE, ("Called. IPPacket (0x%X)  NCE (0x%X)\n", IPPacket, NCE));

    DISPLAY_IP_PACKET(IPPacket);
    /*OskitDumpBuffer( IPPacket->Header, IPPacket->TotalSize );*/

    /* Fetch path MTU now, because it may change */
    TI_DbgPrint(MID_TRACE,("PathMTU: %d\n", NCE->Interface->MTU));

    if ((IPPacket->Flags & IP_PACKET_FLAG_RAW) == 0) {
	/* Calculate checksum of IP header */
	TI_DbgPrint(MID_TRACE,("-> not IP_PACKET_FLAG_RAW\n"));
	((PIPv4_HEADER)IPPacket->Header)->Checksum = 0;

	((PIPv4_HEADER)IPPacket->Header)->Checksum = (USHORT)
	    IPv4Checksum(IPPacket->Header, IPPacket->HeaderSize, 0);
	TI_DbgPrint(MID_TRACE,("IP Check: %x\n", ((PIPv4_HEADER)IPPacket->Header)->Checksum));

	TI_DbgPrint(MAX_TRACE, ("Sending packet (length is %d).\n",
				WN2H(((PIPv4_HEADER)IPPacket->Header)->TotalLength)));
    } else {
	TI_DbgPrint(MAX_TRACE, ("Sending raw packet (flags are 0x%X).\n",
				IPPacket->Flags));
    }

    return SendFragments(IPPacket, NCE, NCE->Interface->MTU,
			 Complete, Context);
}

/* EOF */
