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

#include "precomp.h"

PNDIS_PACKET PrepareARPPacket(
    PIP_INTERFACE IF,
    USHORT HardwareType,
    USHORT ProtocolType,
    UCHAR LinkAddressLength,
    UCHAR ProtoAddressLength,
    PVOID SenderLinkAddress,
    PVOID SenderProtoAddress,
    PVOID TargetLinkAddress,
    PVOID TargetProtoAddress,
    USHORT Opcode)
/*
 * FUNCTION: Prepares an ARP packet
 * ARGUMENTS:
 *     HardwareType       = Hardware type (in network byte order)
 *     ProtocolType       = Protocol type (in network byte order)
 *     LinkAddressLength  = Length of link address fields
 *     ProtoAddressLength = Length of protocol address fields
 *     SenderLinkAddress  = Sender's link address
 *     SenderProtoAddress = Sender's protocol address
 *     TargetLinkAddress  = Target's link address (NULL if don't care)
 *     TargetProtoAddress = Target's protocol address
 *     Opcode             = ARP opcode (in network byte order)
 * RETURNS:
 *     Pointer to NDIS packet, NULL if there is not enough free resources
 */
{
    PNDIS_PACKET NdisPacket;
    NDIS_STATUS NdisStatus;
    PARP_HEADER Header;
    PVOID DataBuffer;
    ULONG Size, Contig;

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

    /* Prepare ARP packet */
    Size = sizeof(ARP_HEADER) +
        2 * LinkAddressLength + /* Hardware address length */
        2 * ProtoAddressLength; /* Protocol address length */
    Size = MAX(Size, IF->MinFrameSize - IF->HeaderSize);

    NdisStatus = AllocatePacketWithBuffer( &NdisPacket, NULL, Size );
    if( !NT_SUCCESS(NdisStatus) ) return NULL;

    GetDataPtr( NdisPacket, 0, (PCHAR *)&DataBuffer, (PUINT)&Contig );
    ASSERT(DataBuffer);

    RtlZeroMemory(DataBuffer, Size);
    Header = (PARP_HEADER)((ULONG_PTR)DataBuffer);
    Header->HWType       = HardwareType;
    Header->ProtoType    = ProtocolType;
    Header->HWAddrLen    = LinkAddressLength;
    Header->ProtoAddrLen = ProtoAddressLength;
    Header->Opcode       = Opcode; /* Already swapped */
    DataBuffer = (PVOID)((ULONG_PTR)Header + sizeof(ARP_HEADER));

    /* Our hardware address */
    RtlCopyMemory(DataBuffer, SenderLinkAddress, LinkAddressLength);
    DataBuffer = (PVOID)((ULONG_PTR)DataBuffer + LinkAddressLength);

    /* Our protocol address */
    RtlCopyMemory(DataBuffer, SenderProtoAddress, ProtoAddressLength);

    if (TargetLinkAddress) {
        DataBuffer = (PVOID)((ULONG_PTR)DataBuffer + ProtoAddressLength);
        /* Target hardware address */
        RtlCopyMemory(DataBuffer, TargetLinkAddress, LinkAddressLength);
        DataBuffer = (PVOID)((ULONG_PTR)DataBuffer + LinkAddressLength);
    } else
        /* Don't care about target hardware address */
        DataBuffer = (PVOID)((ULONG_PTR)DataBuffer + ProtoAddressLength + LinkAddressLength);

    /* Target protocol address */
    RtlCopyMemory(DataBuffer, TargetProtoAddress, ProtoAddressLength);

    return NdisPacket;
}


VOID ARPTransmitComplete(
    PVOID Context,
    PNDIS_PACKET NdisPacket,
    NDIS_STATUS NdisStatus)
/*
 * FUNCTION: ARP request transmit 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 ARP request has been sent
 */
{
    TI_DbgPrint(DEBUG_ARP, ("Called.\n"));
    FreeNdisPacket(NdisPacket);
}


BOOLEAN ARPTransmit(PIP_ADDRESS Address, PVOID LinkAddress,
                    PIP_INTERFACE Interface)
/*
 * FUNCTION: Creates an ARP request and transmits it on a network
 * ARGUMENTS:
 *     Address = Pointer to IP address to resolve
 * RETURNS:
 *     TRUE if the request was successfully sent, FALSE if not
 */
{
    PNDIS_PACKET NdisPacket;
    UCHAR ProtoAddrLen;
    USHORT ProtoType;

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

    /* If Address is NULL then the caller wants an
     * gratuitous ARP packet sent */
    if (!Address)
        Address = &Interface->Unicast;

    switch (Address->Type) {
        case IP_ADDRESS_V4:
            ProtoType    = (USHORT)ETYPE_IPv4; /* IPv4 */
            ProtoAddrLen = 4;                  /* Length of IPv4 address */
            break;
        case IP_ADDRESS_V6:
            ProtoType    = (USHORT)ETYPE_IPv6; /* IPv6 */
            ProtoAddrLen = 16;                 /* Length of IPv6 address */
            break;
        default:
	    TI_DbgPrint(DEBUG_ARP,("Bad Address Type %x\n", Address->Type));
	    DbgBreakPoint();
            /* Should not happen */
            return FALSE;
    }

    NdisPacket = PrepareARPPacket(
        Interface,
        WN2H(0x0001),                    /* FIXME: Ethernet only */
        ProtoType,                       /* Protocol type */
        (UCHAR)Interface->AddressLength, /* Hardware address length */
        (UCHAR)ProtoAddrLen,             /* Protocol address length */
        Interface->Address,              /* Sender's (local) hardware address */
        &Interface->Unicast.Address.IPv4Address,/* Sender's (local) protocol address */
        LinkAddress,                     /* Target's (remote) hardware address */
        &Address->Address.IPv4Address,   /* Target's (remote) protocol address */
        ARP_OPCODE_REQUEST);             /* ARP request */

    if( !NdisPacket ) return FALSE;

    ASSERT_KM_POINTER(NdisPacket);
    ASSERT_KM_POINTER(PC(NdisPacket));
    PC(NdisPacket)->DLComplete = ARPTransmitComplete;

    TI_DbgPrint(DEBUG_ARP,("Sending ARP Packet\n"));

    (*Interface->Transmit)(Interface->Context, NdisPacket,
        0, NULL, LAN_PROTO_ARP);

    return TRUE;
}


VOID ARPReceive(
    PVOID Context,
    PIP_PACKET Packet)
/*
 * FUNCTION: Receives an ARP packet
 * ARGUMENTS:
 *     Context = Pointer to context information (IP_INTERFACE)
 *     Packet  = Pointer to packet
 */
{
    PARP_HEADER Header;
    IP_ADDRESS SrcAddress;
    IP_ADDRESS DstAddress;
    PCHAR SenderHWAddress, SenderProtoAddress, TargetProtoAddress;
    PNEIGHBOR_CACHE_ENTRY NCE;
    PNDIS_PACKET NdisPacket;
    PIP_INTERFACE Interface = (PIP_INTERFACE)Context;
    ULONG BytesCopied, DataSize;
    PCHAR DataBuffer;
    
    PAGED_CODE();

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

    Packet->Header = ExAllocatePoolWithTag(PagedPool,
                                           sizeof(ARP_HEADER),
                                           PACKET_BUFFER_TAG);
    if (!Packet->Header)
    {
        TI_DbgPrint(DEBUG_ARP, ("Unable to allocate header buffer\n"));
        Packet->Free(Packet);
        return;
    }
    Packet->MappedHeader = FALSE;

    BytesCopied = CopyPacketToBuffer((PCHAR)Packet->Header,
                                     Packet->NdisPacket,
                                     Packet->Position,
                                     sizeof(ARP_HEADER));
    if (BytesCopied != sizeof(ARP_HEADER))
    {
        TI_DbgPrint(DEBUG_ARP, ("Unable to copy in header buffer\n"));
        Packet->Free(Packet);
        return;
    }

    Header = (PARP_HEADER)Packet->Header;

    /* FIXME: Ethernet only */
    if (WN2H(Header->HWType) != 1) {
        TI_DbgPrint(DEBUG_ARP, ("Unknown ARP hardware type (0x%X).\n", WN2H(Header->HWType)));
        Packet->Free(Packet);
        return;
    }

    /* Check protocol type */
    if (Header->ProtoType != ETYPE_IPv4) {
        TI_DbgPrint(DEBUG_ARP, ("Unknown ARP protocol type (0x%X).\n", WN2H(Header->ProtoType)));
        Packet->Free(Packet);
        return;
    }

    DataSize = (2 * Header->HWAddrLen) + (2 * Header->ProtoAddrLen);
    DataBuffer = ExAllocatePool(PagedPool,
                                DataSize);
    if (!DataBuffer)
    {
        TI_DbgPrint(DEBUG_ARP, ("Unable to allocate data buffer\n"));
        Packet->Free(Packet);
        return;
    }

    BytesCopied = CopyPacketToBuffer(DataBuffer,
                                     Packet->NdisPacket,
                                     Packet->Position + sizeof(ARP_HEADER),
                                     DataSize);
    if (BytesCopied != DataSize)
    {
        TI_DbgPrint(DEBUG_ARP, ("Unable to copy in data buffer\n"));
        ExFreePool(DataBuffer);
        Packet->Free(Packet);
        return;
    }

    SenderHWAddress    = (PVOID)(DataBuffer);
    SenderProtoAddress = (PVOID)(SenderHWAddress + Header->HWAddrLen);
    TargetProtoAddress = (PVOID)(SenderProtoAddress + Header->ProtoAddrLen + Header->HWAddrLen);

    AddrInitIPv4(&DstAddress, *((PULONG)TargetProtoAddress));
    if (!AddrIsEqual(&DstAddress, &Interface->Unicast))
    {
        ExFreePool(DataBuffer);
        Packet->Free(Packet);
        return;
    }

    AddrInitIPv4(&SrcAddress, *((PULONG)SenderProtoAddress));

    /* Check if we know the sender */
    NCE = NBLocateNeighbor(&SrcAddress);
    if (NCE) {
        /* We know the sender. Update the hardware address
           and state in our neighbor address cache */
        NBUpdateNeighbor(NCE, SenderHWAddress, 0);
    } else {
        /* The packet had our protocol address as target. The sender
           may want to communicate with us soon, so add his address
           to our address cache */
        NBAddNeighbor(Interface, &SrcAddress, SenderHWAddress,
                      Header->HWAddrLen, 0, ARP_COMPLETE_TIMEOUT);
    }

    if (Header->Opcode != ARP_OPCODE_REQUEST)
    {
        ExFreePool(DataBuffer);
        Packet->Free(Packet);
        return;
    }

    /* This is a request for our address. Swap the addresses and
       send an ARP reply back to the sender */
    NdisPacket = PrepareARPPacket(
        Interface,
        Header->HWType,                  /* Hardware type */
        Header->ProtoType,               /* Protocol type */
        (UCHAR)Interface->AddressLength, /* Hardware address length */
        (UCHAR)Header->ProtoAddrLen,     /* Protocol address length */
        Interface->Address,              /* Sender's (local) hardware address */
        &Interface->Unicast.Address.IPv4Address,/* Sender's (local) protocol address */
        SenderHWAddress,                 /* Target's (remote) hardware address */
        SenderProtoAddress,              /* Target's (remote) protocol address */
        ARP_OPCODE_REPLY);               /* ARP reply */
    if (NdisPacket) {
        PC(NdisPacket)->DLComplete = ARPTransmitComplete;
        (*Interface->Transmit)(Interface->Context,
                               NdisPacket,
                               0,
                               SenderHWAddress,
                               LAN_PROTO_ARP);
    }

    ExFreePool(DataBuffer);
    Packet->Free(Packet);
}

/* EOF */
