( ARP: Address Resolution Protocol           JCB 13:12 08/24/10)
module[ arp"

\  ARP uses a small cache of entries.  Each entry has an age counter; new
\  entries have an age of 0, any entry with an age >N is old.
\ 


d# 12 constant arp-cache-entry-size
d# 5 constant arp-cache-entries
TARGET? [IF]
    meta
    arp-cache-entry-size arp-cache-entries * d# 64 max
    target
    constant arp-size
    create arp-cache arp-size allot
    meta
    arp-cache-entries 1- arp-cache-entry-size * arp-cache +
    target
    constant arp-cache-last
[ELSE]
    arp-cache-entry-size arp-cache-entries * d# 64 max constant arp-size
    create arp-cache arp-size allot
    arp-cache-entries 1- arp-cache-entry-size * arp-cache + constant arp-cache-last
[THEN]

: arp-foreach                       \  (func -- )
    arp-cache-last 2>r
    begin
        2r@ swap            \  ptr func
        execute
        r> dup arp-cache-entry-size - >r
        arp-cache =
    until
    2r> 2drop
;

build-debug? [IF]
: arp-.
    dup @ hex4 space              \  age
    dup 2+ dup @ swap d# 2 + dup @ swap d# 2 + @ ethaddr-pretty space
    d# 8 + 2@ ip-pretty
    cr
;

: arp-dump
    ['] arp-. arp-foreach
;
[THEN]

: arp-del   h# ff swap !  ;
: arp-reset ['] arp-del arp-foreach ;
: used?     @ h# ff <> ;
: arp-age-1 dup used? d# 1 and swap +!  ;
: arp-age   ['] arp-age-1 arp-foreach ;
: arp-cmp   ( ptr0 ptr1 -- ptr) over @ over @ > ?: ;
: arp-oldest \  return the address of the oldest ARP entry
    arp-cache ['] arp-cmp arp-foreach ;

\  ARP offsets
\  d# 28 sender ethaddr
\  d# 34 sender ip
\  d# 38 target ethaddr
\  d# 44 target ip

d# 20 constant OFFSET_ARP_OPCODE
d# 22 constant OFFSET_ARP_SRC_ETH
d# 28 constant OFFSET_ARP_SRC_IP
d# 32 constant OFFSET_ARP_DST_ETH
d# 38 constant OFFSET_ARP_DST_IP

: arp-is-response
    OFFSET_ETH_TYPE packet@ h# 806 =
    OFFSET_ARP_OPCODE packet@ d# 2 =
    and
;

\  write the current arp response into the cache, replacing the oldest entry
: !--                   \  ( val ptr -- ptr-2 )
    tuck                \  ptr val ptr
    !
    2-
;

\  Current packet is an ARP response; write it to the given slot in the ARP cache, ageing all others

: arp-cache-write   \  ( ptr -- )
    arp-age         \  because this new entry will have age d# 0
    d# 0 over !        \  age d# 0
    >r

    d# 3 OFFSET_ARP_SRC_ETH mac-inoffset mac@n 
    r@ d# 6 + !-- !-- !-- drop
    d# 2 OFFSET_ARP_SRC_IP mac-inoffset mac@n 
    r> d# 8 + 2!

;

\  Comparison of IP
: arp-cmpip         \  (ip01 ip23 ptr/0 ptr -- ip01 ip23 ptr)
    dup used? if
        dup d# 8 + 2@ d# 2 2pick d<> ?:
    else
        drop
    then
;

: arp-cache-find ( ip01 ip23 -- ip01 ip23 ptr )
\  Find an IP.  Zero if the IP was not found in the cache, ptr to entry otherwise
    d# 0 ['] arp-cmpip arp-foreach ;


: arp-issue-whohas      \  (ip01 ip23 -- ptr)
    mac-pkt-begin
    ethaddr-broadcast mac-pkt-3,
    net-my-mac mac-pkt-3,
    h# 806                   \  frame type
    d# 1                       \  hard type
    h# 800                   \  prot type
    mac-pkt-3,
    h# 0604                  \  hard size, prot size
    d# 1                       \  op (1=request)
    mac-pkt-2,
    net-my-mac mac-pkt-3,
    net-my-ip mac-pkt-2,
    ethaddr-broadcast mac-pkt-3,
    mac-pkt-2,
    mac-pkt-complete drop
    mac-send
;

\  Look up ethaddr for given IP.
\  If found, return pointer to the 6-byte ethaddr
\  If not found, issue an ARP request and return d# 0.

: arp-lookup    \  ( ip01 ip23 -- ptr)
    2dup 
    ip-router 2@ dxor ip-subnetmask 2@ dand
    d0<>
    if
        2drop
        ip-router 2@
    then
    arp-cache-find          \  ip01 ip23 ptr
    dup 0= if
        -rot                \  d# 0 ip01 ip23
        arp-issue-whohas    \  d# 0
    else
        nip nip 2+          \  ptr
    then
;

\  If the current packet is an ARP request for our IP, answer it
: arp-responder
    \  is destination ff:ff:ff:ff:ff:ff or my mac
    d# 3 OFFSET_ETH_DST mac-inoffset mac@n
    and and invert 0=

    net-my-mac              \  a b c
    d# 2 OFFSET_ETH_DST 2+ mac-inoffset mac@n
    d= swap                 \  F a
    OFFSET_ETH_DST packet@ = and

    or
    OFFSET_ETH_TYPE packet@ h# 806 = and
    \  is target IP mine?
    d# 2 OFFSET_ARP_DST_IP mac-inoffset mac@n net-my-ip d= and
    if
        mac-pkt-begin

        d# 3 OFFSET_ARP_SRC_ETH mac-pkt-src
        net-my-mac mac-pkt-3,
        h# 806                \  frame type
        d# 1                  \  hard type
        h# 800                \  prot type
        mac-pkt-3,
        h# 0604               \  hard size, prot size
        d# 2                  \  op (2=reply)
        mac-pkt-2,
        net-my-mac mac-pkt-3,
        net-my-ip mac-pkt-2,
        d# 3 OFFSET_ARP_SRC_ETH mac-pkt-src
        d# 2 OFFSET_ARP_SRC_IP mac-pkt-src

        mac-pkt-complete drop
        mac-send
    then
;

: arp-announce
    mac-pkt-begin

    ethaddr-broadcast mac-pkt-3,
    net-my-mac mac-pkt-3,
    h# 806                \  frame type
    d# 1                  \  hard type
    h# 800                \  prot type
    mac-pkt-3,
    h# 0604               \  hard size, prot size
    d# 2                  \  op (2=reply)
    mac-pkt-2,
    net-my-mac mac-pkt-3,
    net-my-ip mac-pkt-2,
    ethaddr-broadcast mac-pkt-3,
    net-my-ip mac-pkt-2,

    mac-pkt-complete drop
    mac-send
    
;

: arp-handler
    arp-responder
    arp-is-response
    if
        d# 2 OFFSET_ARP_SRC_IP mac-inoffset mac@n 
        arp-cache-find nip nip
        dup 0= if
            drop arp-oldest
        then
        arp-cache-write
    then
;

]module