#define ARP_HASHTABLE_SIZE      1024

#define HTT_ARP 0x00100 //identical to HTT_DICT_WORD

#define ARP_REQUEST     0x01
#define ARP_REPLY       0x02

class CARPHeader
{
        U16     hardware_type;
        U16     protocol_type;

        U8      hardware_addr_len;
        U8      protocol_addr_len;

        U16     operation;

        U8      sender_hardware_addr[MAC_ADDRESS_LENGTH];
        U32     sender_protocol_addr;

        U8      target_hardware_addr[MAC_ADDRESS_LENGTH];
        U32     target_protocol_addr;

};

class CARPHash:CHash
{       //store U32 ip_address as CHash->str U8*, MStrPrint("%X")
//      U32 ip_address;
        U8      mac_address[MAC_ADDRESS_LENGTH];
};

class CARPGlobals
{
        U32     local_ipv4; // stored in Big Endian

} arp_globals;

CHashTable *arp_cache = NULL;

U0 ARPCacheInit()
{
        arp_cache = HashTableNew(ARP_HASHTABLE_SIZE);
        arp_globals.local_ipv4 = 0;
}

I64 ARPSend(U16 operation,
                        U8 *dest_mac_address,
                        U8 *send_mac_address,
                        U32 send_ip,
                        U8 *target_mac_address,
                        U32 target_ip)
{//method currently assumes send_ and target_ip EndianU16 already...

        U8                      *arp_frame;
        CARPHeader      *header;

        I64 de_index = EthernetFrameAllocate(&arp_frame,
                                                                                 send_mac_address,
                                                                                 dest_mac_address,
                                                                                 ETHERTYPE_ARP,
                                                                                 sizeof(CARPHeader));

        if (de_index < 0)
                return de_index; // error state

        header = arp_frame;

        header->hardware_type           = EndianU16(HTYPE_ETHERNET);
        header->protocol_type           = EndianU16(ETHERTYPE_IPV4);
        header->hardware_addr_len       = MAC_ADDRESS_LENGTH;
        header->protocol_addr_len       = IP_ADDRESS_LENGTH;
        header->operation                       = EndianU16(operation);

        MemCopy(header->sender_hardware_addr, send_mac_address, MAC_ADDRESS_LENGTH);
        header->sender_protocol_addr = send_ip;

        MemCopy(header->target_hardware_addr, target_mac_address, MAC_ADDRESS_LENGTH);
        header->target_protocol_addr = target_ip;

        EthernetFrameFinish(de_index);
        return 0;
}

CARPHash *ARPCacheFind(U32 ip_address)
{
        U8                      *ip_string      = MStrPrint("%X", ip_address);
        CARPHash        *entry          = HashFind(ip_string, arp_cache, HTT_ARP);

        if (entry == NULL)
        {
                NetLog("ARP CACHE FIND BY IP: Could not find an IP in ARP cache.");
        }

        Free(ip_string);
        return entry;
}

CARPHash *ARPCachePut(U32 ip_address, U8 *mac_address)
{
        CARPHash *entry;

        NetLog("ARP CACHE PUT: Attempting to look for entry in ARP Cache.");

        entry = ARPCacheFind(ip_address);

        if (!entry)
        {
                entry = CAlloc(sizeof(CARPHash));

                NetLog("ARP CACHE PUT: Attempting add to cache: addr, mac:");
                NetLog("               0x%0X, 0x%0X 0x%0X 0x%0X 0x%0X 0x%0X 0x%0X",
                                ip_address, mac_address[0], mac_address[1], mac_address[2], mac_address[3], mac_address[4], mac_address[5]);

                entry->str      = MStrPrint("%X", ip_address);
                entry->type = HTT_ARP;
                MemCopy(entry->mac_address, mac_address, 6);

                HashAdd(entry, arp_cache);
        }
        else
        {
                NetWarn("ARP CACHE Put: Entry was already found in Cache. Overwriting.");
                MemCopy(entry->mac_address, mac_address, 6);
        }

        return entry;
}

U0 ARPLocalIPV4Set(U32 ip_address)
{ // takes in little endian IP, stores into globals as Big Endian
        arp_globals.local_ipv4 = EndianU32(ip_address);

        ARPSend(ARP_REPLY,
                        ethernet_globals.ethernet_broadcast,
                        EthernetMACGet,
                        arp_globals.local_ipv4,
                        ethernet_globals.ethernet_broadcast,
                        arp_globals.local_ipv4);
}

I64 ARPHandler(CEthernetFrame *ethernet_frame)
{ // Use of ARPHandler must account for -1 error codes.
        CARPHeader *header;
        U16                     operation;

        NetLog("ARP HANDLER: Entering ARP Handler.");

        if (ethernet_frame->ethertype != ETHERTYPE_ARP)
        {
                NetErr("ARP HANDLER: Caught wrong frame ethertype.");
                return -1;
        }
        if (ethernet_frame->length < sizeof(CARPHeader))
        {
                NetErr("ARP HANDLER: Caught wrong frame length.");
                return -1;
        }

        header = ethernet_frame->data;
        operation = EndianU16(header->operation);

        if (EndianU16(header->hardware_type) != HTYPE_ETHERNET)
        {
                NetErr("ARP HANDLER: Caught wrong frame hardware type.");
                return -1;
        }
        if (EndianU16(header->protocol_type) != ETHERTYPE_IPV4)
        {
                NetErr("ARP HANDLER: Caught wrong frame protocol type.");
                return -1;
        }
        if (header->hardware_addr_len != HLEN_ETHERNET)
        {
                NetErr("ARP HANDLER: Caught wrong frame hardware address length.");
                return -1;
        }
        if (header->protocol_addr_len != PLEN_IPV4)
        {
                NetErr("ARP HANDLER: Caught wrong frame protocol address length.");
                return -1;
        }

        switch (operation)
        {
                case ARP_REQUEST:
                        if (header->target_protocol_addr == arp_globals.local_ipv4)
                        {
                                NetLog("ARP HANDLER: Saw request, sending back reply.");
                                ARPSend(ARP_REPLY,
                                                header->sender_hardware_addr,
                                                EthernetMACGet,
                                                arp_globals.local_ipv4,
                                                header->sender_hardware_addr,
                                                header->sender_protocol_addr);
                        }
                        else
                                NetWarn("ARP HANDLER: Saw request, target IP address is not this machine.");
                        break;

                case ARP_REPLY:
                        NetLog("ARP HANDLER: Saw reply, putting into ARP Cache.");
                        ARPCachePut(EndianU32(header->sender_protocol_addr), header->sender_hardware_addr);
                        break;

                default:
                        NetErr("ARP HANDLER: Unrecognized operation: 0x%X", operation);
                        break;
        }

        NetLog("ARP HANDLER: Exiting.");
}

U0 ARPRep()
{
        I64                      i, j;
        CARPHash        *temp_hash;
        U32                      address;

        "$LTBLUE$ARP Report:$FG$\n\n";
        "ARP Local Address:     %d.%d.%d.%d\n\n",
                arp_globals.local_ipv4.u8[0],
                arp_globals.local_ipv4.u8[1],
                arp_globals.local_ipv4.u8[2],
                arp_globals.local_ipv4.u8[3];

        for (i = 0; i <= arp_cache->mask; i++)
        {
                temp_hash = arp_cache->body[i];

                while (temp_hash)
                {
                        "ARP Hash @ 0x%X:\n", temp_hash;

                        address = EndianU32(Str2I64(temp_hash->str, 16)(U32));
                        "       IP Address:             %d.%d.%d.%d\n",
                                address.u8[0], address.u8[1], address.u8[2], address.u8[3]; // todo: kludge

                        "       MAC Address:    ";

                        for (j = 0; j < MAC_ADDRESS_LENGTH; j++)
                                "%02X ", temp_hash->mac_address[j];

                        "\n\n";

                        temp_hash = temp_hash->next;
                }
        }
        "\n";
}

ARPCacheInit;