include "inc/cmdsys.plh"
include "inc/inet.plh"
//
// DNS message
//
struc t_dnshdr
    word dnsID
    word dnsCode
    word dnsQdCount
    word dnsAnCount
    word dnsNsCount
    word dnsArCount
end
//
// External interface to net class. Must be first.
//
res[t_inet] iNet
//
// List of loadable network device drivers
//
byte netDrivers = "UTHERNET2"
byte            = "UTHERNET"
byte            = ""
word driver = @netDrivers
//
// DNS address (default to Google DNS)
//
byte[4] dns = 8,8,8,8
const DNS_QUERY  = 1
const DNS_ANSWER = 2
byte stateDNS

def iNetSetDNS(ipptr)
    memcpy(@dns, ipptr, 4)
    return 0
end

//def dumpbytes(buf, len)
//    word i
//
//    for i = 0 to len - 1
//        putb(buf->[i])
//  if i & 7 == 7
//      putln
//  else
//      putc(' ')
//  fin
//    next
//end
//def putip(ipptr)
//    byte i
//
//    for i = 0 to 2
//        puti(ipptr->[i]); putc('.')
//    next
//    return puti(ipptr->[i])
//end

def parseIP(ipstr, ipaddr)
    byte i
    word endstr

    endstr = ipstr + ^ipstr
    for i = 0 to 3
        ipstr = ipstr + 1
            while ^ipstr >= '0' and ^ipstr <= '9' and ipstr <= endstr
            ipaddr->[i] = ipaddr->[i] * 10 + ^ipstr - '0'
            ipstr = ipstr + 1
        loop
        if ^ipstr <> '.' and ipstr < endstr
            return 0
        fin
    next
    return i == 3
end

def parseDomain(domstr, msgptr)
    byte i, l

    l = 0
    for i = 1 to ^domstr
        if domstr->[i] == '.'
            msgptr->[l] = i - l - 1
            l = i
        else
            msgptr->[i] = domstr->[i]
        fin
    next
    msgptr->[l] = i - l - 1
    msgptr  = msgptr + i
    ^msgptr = 0 // Terminate label list
    return msgptr + 1
end

def recvDNS(remip, remport, pkt, len, ipaddr)
    byte q, r
    word resptr

    if pkt=>dnsID == $BEEF
        q = pkt->dnsQdCount.1
        r = pkt->dnsAnCount.1 + pkt->dnsNsCount.1 + pkt->dnsArCount.1
        resptr = pkt + t_dnshdr
        while q
            while ^resptr
                //puts(resptr); putc('.')
                resptr = resptr + ^resptr + 1
            loop
            resptr = resptr + 1
            //putln; dumpbytes(resptr, 4); putln
            resptr = resptr + 4
            q--
        loop
        while r
            //dumpbytes(resptr, 40); putln
            if ^resptr & $C0 == $C0
                resptr = resptr + 2
            else
                while ^resptr
                    //puts(resptr); putc('.')
                    resptr = resptr + ^resptr + 1
                loop
                resptr = resptr + 1
            fin
            if resptr->1 == 1 and resptr->3 == 1 and resptr->9 == 4
                ipaddr=>0 = resptr=>10
                ipaddr=>2 = resptr=>12
            fin
            //putln; dumpbytes(resptr, 10); putc(':'); putln
            resptr = resptr + 8
            //dumpbytes(resptr + 2, ^(resptr + 1))
            resptr = resptr + 2 + ^(resptr + 1); putln
            r--
        loop
    fin
    stateDNS = DNS_ANSWER
    return 0
end

def iNetResolve(namestr, ipaddr)
    word dnspkt, msgptr, msglen
    word portDNS, timeout

    ipaddr=>0 = 0
    ipaddr=>2 = 0
    if not parseIP(namestr, ipaddr)
        //
        // Query Domain Name Server for address
        //
        dnspkt             = heapalloc(^namestr + t_dnshdr + 8) // Use heap as working DNS query packet
        msgptr             = dnspkt
        msgptr=>dnsID      = $BEEF
        msgptr=>dnsCode    = $0001 // RD (Recursion Desired)
        msgptr=>dnsQdCount = $0100 // BE count = 1
        msgptr=>dnsAnCount = 0
        msgptr=>dnsNsCount = 0
        msgptr=>dnsArCount = 0
        msgptr             = parseDomain(namestr, msgptr + t_dnshdr)
        msgptr=>0          = $0100 // BE TYPE  = Address
        msgptr=>2          = $0100 // BE CLASS = Internet
        msglen             = msgptr - dnspkt + 4
        //
        // Prepare to receive DNS answer from server
        //
        portDNS = iNet:openUDP(3999, @recvDNS, ipaddr)
        //
        // Service IP
        //
        stateDNS = DNS_QUERY
        iNet:sendUDP(portDNS, @dns, 53, dnspkt, msglen)
        for timeout = 1 to 1000
            iNet:serviceIP()
            if stateDNS == DNS_ANSWER
                break
            fin
        next
        iNet:closeUDP(portDNS)
        heaprelease(dnspkt)
    fin
    return ipaddr=>0 <> 0 or ipaddr=>2 <> 0
end
//
// Initialze network stack
//
def iNetInit
    //
    // Look for net hardware
    //
    while ^driver
        if cmdsys:modexec(driver) >= 0
            //
            // Get an IP address
            //
            cmdsys:modexec("DHCP")
            iNet:resolveIP = @iNetResolve
            return @iNet
        fin
        driver = driver + ^driver + 1
    loop
    puts("No network adapters found.\n")
    return NULL
end
//
// Fill iNet class
//
iNet:initIP = @iNetInit
iNet:setDNS = @iNetSetDNS
//
// Keep module in memory
//
return modkeep
done
