{ 
	Driver->Net->L3->ARP - Address Resolution Protocol Driver.
	
	@author(Kieron Morris <kjm@kieronmorris.me>)
}
unit arp;

interface

uses
    tracer, lmemorymanager,
    util, lists, console, terminal,
    net, nettypes, netutils,
    netlog,
    eth2, ipv4;

type
    PARPCacheRecord = ^TARPCacheRecord;
    TARPCacheRecord = record
        MAC : TMACAddress;
        IP  : TIPv4Address;
    end;

procedure register;
function  IPv4ToMAC(ip : puint8) : puint8;
function  MACToIIPv4(mac : puint8) : puint8;
procedure sendGratuitous;
procedure sendRequest(ip : puint8);
procedure send(hType : uint16; pType : uint16; op : uint16; p_context : PPacketContext);
function resolveIP(ip : puint8) : puint8;

implementation

var
    Registered : Boolean = false;
    Cache      : PLinkedListBase;

function findCacheRecordByMAC(mac : puint8) : PARPCacheRecord;
var
    i : uint32;
    r : PARPCacheRecord;

begin
    push_trace('arp.findCacheRecordByMAC');
    findCacheRecordByMAC:= nil;
    if LL_Size(Cache) > 0 then begin
        for i:=0 to LL_Size(Cache)-1 do begin
            r:= PARPCacheRecord(LL_Get(Cache, i));
            if MACEqual(mac, @r^.MAC[0]) then begin
                findCacheRecordByMAC:= r;
                break;
            end;
        end;
    end;
end;

function findCacheRecordByIP(ip : puint8) : PARPCacheRecord;
var
    i : uint32;
    r : PARPCacheRecord;

begin
    push_trace('arp.findCacheRecordByIP');
    findCacheRecordByIP:= nil;
    if LL_Size(Cache) > 0 then begin
        for i:=0 to LL_Size(Cache)-1 do begin
            r:= PARPCacheRecord(LL_Get(Cache, i));
            if IPEqual(ip, @r^.IP[0]) then begin
                findCacheRecordByIP:= r;
                break;
            end;
        end;
    end;
end;

procedure send(hType : uint16; pType : uint16; op : uint16; p_context : PPacketContext);
var
    buf : void;
    hdr : PARPHeader;
    hSize, pSize : uint8;

begin
    push_trace('arp.send');
    writeToLogLn('        L3: arp.send');
    if p_context <> nil then begin
        buf:= kalloc(sizeof(TARPHeader));
        hdr:= PARPHeader(buf);
        case hType of
            $1 : hSize:= 6;
            else hSize:= 0;
        end;
        case pType of
            $0800 : pSize:= 4;
            else pSize:= 0;
        end;
        if (hSize > 0) and (pSize > 0) then begin
            hdr^.Hardware_Type_Hi:= hType SHR 8;
            hdr^.Hardware_Type_Lo:= hType AND $FF;
            hdr^.Protocol_Type_Hi:= pType SHR 8;
            hdr^.Protocol_Type_Lo:= pType AND $FF;
            hdr^.Hardware_Address_Length:= hSize;
            hdr^.Protocol_Address_Length:= pSize;
            hdr^.Operation_Hi:= op SHR 8;
            hdr^.Operation_Lo:= op AND $FF;
            copyMAC(@p_context^.MAC.Source[0], @hdr^.Source_Hardware[0]);
            //copyMAC(@FORCE_MAC[0], @hdr^.Source_Hardware[0]);
            copyIPv4(@p_context^.IP.Source[0], @hdr^.Source_Protocol[0]);
            copyMAC(@p_context^.MAC.Destination[0], @hdr^.Destination_Hardware[0]);
            copyIPv4(@p_context^.IP.Destination[0], @hdr^.Destination_Protocol[0]);
            if MACEqual(@p_context^.MAC.Destination[0], @NULL_MAC[0]) then begin
                CopyMAC(@BROADCAST_MAC[0], @p_context^.MAC.Destination[0]);
            end;
            eth2.send(buf, sizeof(TARPHeader), $0806, p_context);
        end;
        kfree(buf);
    end;
end;

procedure sendGratuitous;
var
    context : PPacketContext;

begin
     context:= newPacketContext;
     CopyIPv4(@getIPv4Config^.Address[0], @context^.IP.Destination[0]);
     CopyIPv4(@getIPv4Config^.Address[0], @context^.IP.Source[0]);
     CopyMAC(GetMAC, @context^.MAC.Source[0]);
     CopyMAC(@NULL_MAC[0], @context^.MAC.Destination[0]);
     arp.send($1, $0800, $1, context);
     freePacketContext(context);
end;

procedure sendRequestGateway(ip : puint8);
var
    context : PPacketContext;
    CacheRecord : PARPCacheRecord;

begin
     context:= newPacketContext;
     CacheRecord:= findCacheRecordByIP(@getIPv4Config^.Gateway[0]);
     if CacheRecord <> nil then begin
        CopyMAC(@CacheRecord^.MAC[0], @context^.MAC.Destination[0]);
        CopyIPv4(ip, @context^.IP.Destination[0]);
        CopyIPv4(@getIPv4Config^.Address[0], @context^.IP.Source[0]);
        CopyMAC(GetMAC, @context^.MAC.Source[0]);
        arp.send($1, $0800, $1, context);
     end;
     freePacketContext(context);
end;

procedure sendRequest(ip : puint8);
var
    context : PPacketContext;
    CacheRecord : PARPCacheRecord;

begin
     context:= newPacketContext;
     CopyIPv4(ip, @context^.IP.Destination[0]);
     CopyIPv4(@getIPv4Config^.Address[0], @context^.IP.Source[0]);
     CopyMAC(GetMAC, @context^.MAC.Source[0]);
     CacheRecord:= findCacheRecordByIP(@getIPv4Config^.Gateway[0]);
     CopyMAC(@NULL_MAC[0], @context^.MAC.Destination[0]);
     arp.send($1, $0800, $1, context);
     freePacketContext(context);
     sendRequestGateway(ip);
end;

function resolveIP(ip : puint8) : puint8;
var
    CacheRecord : PARPCacheRecord;

begin
    CacheRecord:= findCacheRecordByIP(ip);
    resolveIP:= nil;
    if CacheRecord = nil then begin
        sendRequest(ip);
        sendRequestGateway(ip);
    end else begin
        resolveIP:= @CacheRecord^.MAC[0];
    end;
end;

procedure recv(p_data : void; p_len : uint16; p_context : PPacketContext);
var
    Header       : PARPHeader;
    AHeader      : TARPAbstractHeader;
    CacheElement : PARPCacheRecord;
    Merge        : boolean;
    context      : PPacketContext;

begin
    push_trace('arp.recv');
    writeToLogLn('        L3: arp.recv');
    console.redrawWindows;
    
    { Get our converted Header }
    Header:= PARPHeader(p_data);
    AHeader.Hardware_Type:= (Header^.Hardware_Type_Hi SHL 8) + Header^.Hardware_Type_Lo;
    AHeader.Protocol_Type:= (Header^.Protocol_Type_Hi SHL 8) + Header^.Protocol_Type_Lo;
    AHeader.Hardware_Address_Length:= Header^.Hardware_Address_Length;
    AHeader.Protocol_Address_Length:= Header^.Protocol_Address_Length;
    AHeader.Operation:= (Header^.Operation_Hi SHL 8) + Header^.Operation_Lo;
    copyMAC(@Header^.Source_Hardware[0], @AHeader.Source_Hardware[0]);
    copyIPv4(@Header^.Source_Protocol[0], @AHeader.Source_Protocol[0]);
    copyMAC(@Header^.Destination_Hardware[0], @AHeader.Destination_Hardware[0]);
    copyIPv4(@Header^.Destination_Protocol[0], @AHeader.Destination_Protocol[0]);
    
    { Process ARP Packet }
    CacheElement:= findCacheRecordByIP(@AHeader.Source_Protocol[0]);
    if CacheElement = nil then CacheElement:= findCacheRecordByMAC(@AHeader.Source_Hardware[0]);
    if CacheElement <> nil then begin
        copyMAC(@AHeader.Source_Hardware[0], @CacheElement^.MAC[0]);
        copyIPv4(@AHeader.Source_Protocol[0], @CacheElement^.IP[0]);
    end else begin
        CacheElement:= PARPCacheRecord(LL_Add(Cache));
        CopyMAC(@AHeader.Source_Hardware[0], @CacheElement^.MAC[0]);
        copyIPv4(@AHeader.Source_Protocol[0], @CacheElement^.IP[0]);
    end;
    if IPEqual(@AHeader.Destination_Protocol[0], @getIPv4Config^.Address[0]) then begin
        case AHeader.Operation of
            $1:begin { ARP Request }
                writeToLogLn('            arp.recv.arp.req');
                context:= newPacketContext;
                copyMAC(@AHeader.Source_Hardware[0], @context^.MAC.Destination[0]);
                copyIPv4(@AHeader.Source_Protocol[0], @context^.IP.Destination[0]);
                copyMAC(getMAC, @context^.MAC.Source[0]);
                copyIPv4(@getIPv4Config^.Address[0], @context^.IP.Source[0]);
                send($1, $0800, $2, context);
                freePacketContext(context);
            end;
            $2:begin { ARP Reply }
                writeToLogLn('            arp.recv.arp.rep');
            end;
            $3:begin { RARP Request }
                writeToLogLn('            arp.recv.rarp.req');
            end;
            $4:begin { RARP Reply }
                writeToLogLn('            arp.recv.rarp.rep');
            end;
            $5:begin { DRARP Request }
                writeToLogLn('            arp.recv.drarp.req');
            end;
            $6:begin { DRARP Reply }
                writeToLogLn('            arp.recv.drarp.rep');
            end;
            $7:begin { DRARP Error }
                writeToLogLn('            arp.recv.drarp.err');
            end;
            $8:begin { InARP Request }
                writeToLogLn('            arp.recv.inarp.req');
            end;
            $9:begin { InARP Reply }
                writeToLogLn('            arp.recv.inarp.rep');
            end;
        end;
    end;
end;

procedure terminal_command_arp(Params : PParamList);
var
    i : uint32;
    elm : PARPCacheRecord;
    sIP : pchar;
    _IP : puint8;

begin
    if ParamCount(Params) > 0 then begin
        sIP:= getParam(0, Params);
        _IP:= stringToIPv4(sIP);
        sendRequest(_IP);
        writestringlnWND('ARP Request Sent.', getTerminalHWND);
    end else begin
        if LL_Size(Cache) > 0 then begin
            writestringlnWND('MAC                IPv4', getTerminalHWND);
            For i:=0 to LL_Size(Cache)-1 do begin
                elm:= PARPCacheRecord(LL_Get(Cache, i));
                writeMACAddressEx(@elm^.MAC[0], getTerminalHWND);
                writestringWND('  ', getTerminalHWND);
                writeIPv4AddressEx(@elm^.IP[0], getTerminalHWND);
                writestringlnWND(' ', getTerminalHWND);
            end;
        end else begin
            writestringlnWND('No entries in ARP table.', getTerminalHWND);
        end;
    end;
end;

procedure register;
begin
    push_trace('arp.register');
    if not Registered then begin
        Cache:= LL_New(sizeof(TARPCacheRecord));
        eth2.registerTypePromisc($0806, @recv);
        terminal.registerCommand('ARP', @terminal_command_arp, 'Get ARP Table.');
        Registered:= true;
    end;
    pop_trace;
end;

function IPv4ToMAC(ip : puint8) : puint8;
var
     r : PARPCacheRecord;

begin
    push_trace('arp.IPv4ToMAC');
    register;
    IPv4ToMAC:= nil;
    r:= findCacheRecordByIP(ip);
    if r <> nil then begin
        IPv4ToMAC:= @r^.MAC[0];
    end;
    pop_trace;
end;

function MACToIIPv4(mac : puint8) : puint8;
var
     r : PARPCacheRecord;

begin
    push_trace('arp.MACToIPv4');
    register;
    MACToIIPv4:= nil;
    r:= findCacheRecordByMAC(mac);
    if r <> nil then begin
        MACToIIPv4:= @r^.IP[0];
    end;
    pop_trace;
end;

end.