//
// DHCP
//
include "inc/cmdsys.plh"
include "inc/inet.plh"
sysflags nojitc // No need to JITC this temp loaded module
//
// Needed to init subnet
//
const IP_BROADCAST = $FFFF
const IP4ADR_SIZE  = 4
//
// DHCP message
//
struc t_dhcp
    byte      dhcp_op
    byte      dhcp_htype
    byte      dhcp_hlen
    byte      dhcp_hops
    byte[4]   dhcp_xid
    word      dhcp_secs
    word      dhcp_flags
    byte[4]   dhcp_clientip
    byte[4]   dhcp_yourip
    byte[4]   dhcp_serverip
    byte[4]   dhcp_gatewayip
    byte[16]  dhcp_clientha
    byte[192] dhcp_bootp
    byte[4]   dhcp_magic
    byte[60]  dhcp_opts
end
//
// DHCP messages
//
const DHCP_DISCOVER = $01
const DHCP_OFFER    = $02
const DHCP_REQUEST  = $03
const DHCP_DECLINE  = $04
const DHCP_ACK      = $05
const DHCP_NACK     = $06
const DHCP_RELEASE  = $07
//
// Track DHCP progress
//
byte retry
word timeout
//
// DHCP ports
//
const DHCP_CLIENT_PORT = 68
const DHCP_SERVER_PORT = 67
word portDHCP
byte[4] zeros
byte[4] ones = $FF, $FF, $FF, $FF
//
// Default static net IP addresses
//
byte localdns = 8,8,8,8
byte localgw  = 192,168,1,1
byte localip  = 192,168,1,10
byte localnet = 255,255,255,0
//
// Pre-configured DHCP packet
//
byte DHCP = $01// OP = BOOT
byte      = $01   // HTYPE = ETHERNET
byte      = $06   // HLEN  = 6
byte      = $00   // HOPS
byte[4]   = $01,$02,$03,$04 // TRANSACTION ID
word      = $0000 // SECONDS
word      = 0     // FLAGS
byte[4]   = 0     // CLIENT IP
byte[4]   = 0     // YOUR IP
byte[4]   = 0     // SERVER IP
byte[4]   = 0     // GATEWAY IP
byte[16]  = 0     // CLIENT HA
byte[64]  = 0     // SERVER HOST NAME
byte[128] = 0     // BOOT FILE NAME
byte[4]   = $63,$82,$53,$63 // MAGIC
byte optsOP  = 53,1,1  // DISCOVER
byte optsCID = 61,7, $01,$00,$00,$00,$00,$00,$00
byte         = 12, "AppleII"
byte optsIP  = 50,4, 0,0,0,0 // IP requested
byte         = 55,4, 1,3,6,42 // Parameter request list
byte optsSRV = 54,4, 0,0,0,0 // DHCP server
byte         = 255
byte[] endDHCP
//
// DEBUG
//
def putip(ipptr)#0
    byte i

    for i = 0 to 2
        puti(ipptr->[i]); putc('.')
    next
    puti(ipptr->[i])
end
//def dumpbytes(buf, len)
//    word i
//
//    for i = 0 to len - 1
//        putb(buf->[i])
//    if i & 15 == 15
//        putln
//    else
//        putc(' ')
//    fin
//    next
//end
//def dumpdhcp(pkt)
//    putb(pkt->dhcp_op);putln
//    putb(pkt->dhcp_htype);putln
//    putb(pkt->dhcp_hlen);putln
//    putb(pkt->dhcp_hops);putln
//    dumpbytes(@pkt->dhcp_xid, 4);putln
//    putip(@pkt->dhcp_clientip);putln
//    putip(@pkt->dhcp_yourip);putln
//    putip(@pkt->dhcp_serverip);putln
//    putip(@pkt->dhcp_gatewayip);putln
//    dumpbytes(@pkt->dhcp_opts, 48);putln
//end
def parseopts(opts, match)
    byte i

    i = 0
    while opts->[i] <> $FF and i < 64
        while !opts->[i] and i < 64
            i++
        loop
        if opts->[i] == match
            return i
        fin
        i = i + opts->[i + 1] + 2
    loop
    return -1
end
def recvDHCP(remip, remport, pkt, len, param)
    word servopts, maskopts, gwopts, dnsopts

    //puts("recvDHCP: ");putip(remip);putc(':');puti(remport);putln
    //dumpdhcp(pkt)
    if pkt=>dhcp_xid:0 == $0201 and pkt=>dhcp_xid:2 == $0403
        when pkt->dhcp_opts.[parseopts(@pkt->dhcp_opts, 53) + 2]
            is DHCP_OFFER
                //
                // Copy offer parameters to request
                //
                optsOP.2 = DHCP_REQUEST
                memcpy(@optsIP.2, @pkt->dhcp_yourip, IP4ADR_SIZE)
                servopts = parseopts(@pkt->dhcp_opts, 54) + 2
                if servopts >= 0
                    optsSRV = 54
                    memcpy(@optsSRV.2, @pkt->dhcp_opts.[servopts], IP4ADR_SIZE)
                fin
                iNet:sendUDP(portDHCP, 0, DHCP_SERVER_PORT, @DHCP, @endDHCP - @DHCP)
                break
            is DHCP_ACK
                //
                // Copy parameters to working copy
                //
                optsOP.2 = DHCP_ACK
                memcpy(@localip, @pkt->dhcp_yourip, IP4ADR_SIZE)
                maskopts = parseopts(@pkt->dhcp_opts, 1) + 2
                if maskopts >= 0
                    memcpy(@localnet, @pkt->dhcp_opts.[maskopts], IP4ADR_SIZE)
                fin
                gwopts = parseopts(@pkt->dhcp_opts, 3) + 2
                if gwopts >= 0
                    memcpy(@localgw, @pkt->dhcp_opts.[gwopts], IP4ADR_SIZE)
                fin
                dnsopts = parseopts(@pkt->dhcp_opts, 6) + 2
                if dnsopts >= 0
                    memcpy(@localdns, @pkt->dhcp_opts.[dnsopts], IP4ADR_SIZE)
                fin
                break
            otherwise
                //dumpdhcp(pkt)
        wend
    fin
    return 0
end
//
// Get the local hardware address into the DHCP packet
//
iNet:getInterfaceHA(@DHCP.dhcp_clientha)
iNet:getInterfaceHA(@optsCID.3)
//
// Clear our local IP address
//
iNet:setInterfaceIP(@zeros, @ones, @zeros)
//
// Prepare to receive DHCP packets from a server
//
portDHCP = iNet:openUDP(DHCP_CLIENT_PORT, @recvDHCP, 0)
//
// Service IP
//
retry = 0
repeat
    //
    // Broadcast DHCP DISCOVER message
    //
    optsOP.2         = DHCP_DISCOVER
    optsIP:2         = 0
    optsIP:4         = 0
    optsSRV          = 255
    DHCP.dhcp_secs.1 = retry
    iNet:sendUDP(portDHCP, 0, DHCP_SERVER_PORT, @DHCP, @optsSRV - @DHCP + 1)
    for timeout = 0 to 4000
        iNet:serviceIP()
        if optsOP.2 == DHCP_ACK
            break
        fin
    next
    retry++
until retry > 4 or optsOP.2 == DHCP_ACK
iNet:closeUDP(portDHCP)
iNet:setInterfaceIP(@localip, @localnet, @localgw)
puts("Apple II bound to:\n");putip(@localip);putc('/');putip(@localnet);putln
iNet:setDNS(@localdns)
puts("DNS:");putip(@localdns);putln
done
