include "inc/cmdsys.plh"
//
// iNet API - must match inet.plh
//
struc t_inet
    word initIP
    word serviceIP
    word openUDP
    word sendUDP
    word closeUDP
    word listenTCP
    word connectTCP
    word sendTCP
    word closeTCP
    word setInterfaceIP
    word getInterfaceHA
    word setDNS
    word resolveIP
    word setCallback
    word setParam
end
//
// 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
done
