include "inc/cmdsys.plh"
include "inc/inet.plh"
//
// Predefine service routine
//
predef etherServiceIP
//
// Segment list element
//
struc t_segment
    word seg_buf
    word seg_len
end
//
// Max Ethernet frame size
//
const MAX_FRAME_SIZE = 1518
const MAC_BROADCAST  = $FFFF
const MAC_SIZE       = 6
//
// Ethernet header
//
struc t_ethrhdr
    byte[MAC_SIZE] ethr_dst
    byte[MAC_SIZE] ethr_src
    word           ethr_payload
end
const PAYLOAD_IP  = $0008 // BE format
const PAYLOAD_ARP = $0608 // BE format
//
// IP datagram header
//
const IP4ADR_SIZE = 4
struc t_iphdr
    byte ip_vers_hlen
    byte ip_service
    word ip_length
    word ip_id
    word ip_flags_fragofst
    byte ip_ttl
    byte ip_proto
    word ip_chksm
    byte[IP4ADR_SIZE] ip_src
    byte[IP4ADR_SIZE] ip_dst
    byte[] ip_options
end
const t_ethriphdr   = t_ethrhdr + t_iphdr
const IP_BROADCAST  = $FFFF
const IP_PROTO_ICMP = $01
const IP_PROTO_UDP  = $11
const IP_PROTO_TCP  = $06
//
// ICMP type/codes
//
const ICMP_ECHO_REQST = 8
const ICMP_ECHO_REPLY = 0
//
// ICMP message format
//
struc t_icmp
    byte icmp_type
    byte icmp_code
    word icmp_chksm
    word[2] icmp_header
end
//
// UDP IPv4 pseudo header
//
struc t_piphdr
    byte[IP4ADR_SIZE] pip_src
    byte[IP4ADR_SIZE] pip_dst
    byte              pip_zero
    byte              pip_proto
    word              pip_len
end
//
// UDP header
//
struc t_udphdr
    word udp_src
    word udp_dst
    word udp_len
    word udp_chksm
end
//
// TCP header
//
struc t_tcphdr
    word tcp_src
    word tcp_dst
    word tcp_len
    word tcp_chksm
end
//
// ARP packet
//
const HW_ETHER  = $0100 // BE format
const ARP_PROTO = $0008 // BE format
const ARP_REQST = $0100 // BE format
const ARP_REPLY = $0200 // BE format
struc t_arp
    word arp_hw
    word arp_prot
    byte arp_hlen
    byte arp_plen
    word arp_op
    byte[MAC_SIZE]    arp_senderha
    byte[IP4ADR_SIZE] arp_senderip
    byte[MAC_SIZE]    arp_targha
    byte[IP4ADR_SIZE] arp_targip
end
const t_earp = t_ethrhdr+t_arp
//
// Pre-configured Ethernet header
//
byte[] eFrame
//
// Destination MAC address
//
byte[MAC_SIZE] dstMAC
//
// My MAC address
//
byte[MAC_SIZE] myMAC
//
// Ethernet payload
//
word ePayload = PAYLOAD_ARP
//
// Pre-configured ARP packet - MUST follow ethernet header!
//
word ARP   = HW_ETHER    // HW TYPE
word       = ARP_PROTO   // PROTO TYPE
byte       = MAC_SIZE    // HLEN
byte       = IP4ADR_SIZE // PLEN
word opARP               // OP
export byte[MAC_SIZE]    localha
export byte[IP4ADR_SIZE] localip
byte[MAC_SIZE]           remoteha
byte[IP4ADR_SIZE]        remoteip
//
// Local network parameters
//
byte[IP4ADR_SIZE] netmask
byte[IP4ADR_SIZE] subnet
byte[IP4ADR_SIZE] gateway
const MAX_UDP_NOTIFIES    = 4
const MAX_TCP_NOTIFIES    = 4
//
// Notify callbacks
//
struc t_notify
    word notify_port
    word notify_func
    word notify_parm
end
byte[t_notify] portsUDP[MAX_UDP_NOTIFIES]
byte[t_notify] portsTCP[MAX_TCP_NOTIFIES]
//
// Service ICMP hook
//
export word hookICMP
//
// Ethernet driver entrypoints
//
word setFrameLen, writeFrame, getFrameLen, readFrame
//
// Defines for ASM routines
//
asm equates
    !SOURCE    "vmsrc/plvmzp.inc"
end
//
// Swap bytes in word
//
asm swab(val)
    LDA    ESTKL,X
    LDY    ESTKH,X
    STA    ESTKH,X
    STY    ESTKL,X
    RTS
end
//
// 1'S COMPLIMENT SUM BE format
// sum1(PREVSUM, BUF, LEN)
//
asm sum1(prevsum, buf, len)
    LDY    #$00
    LDA    ESTKL+1,X
    STA    SRCL
    LDA    ESTKH+1,X
    STA    SRCH
    LSR    ESTKH,X     ; CONVERT BYTE LEN TO WORD LEN
    LDA    ESTKL,X
    ROR
    ADC    #$00
    STA    ESTKL,X
    BEQ    +
    !BYTE  $A9
-   CLC
    INC    ESTKH,X
+   BCS    -
CHKLP     LDA  (SRC),Y
    PHA
    INY
    BNE    +
    INC    SRCH
+   LDA    (SRC),Y
    ADC    ESTKH+2,X
    STA    ESTKH+2,X
    PLA
    ADC    ESTKL+2,X
    STA    ESTKL+2,X
    INY
    BNE    +
    INC    SRCH
+   DEC    ESTKL,X
    BNE    CHKLP
    DEC    ESTKH,X
    BNE    CHKLP
    BCC    +
-   INC    ESTKH+2,X
    BNE    +
    INC    ESTKL+2,X
    BEQ    -
+   INX
    INX
    RTS
end
//
// Send IP datagram
//
def etherSendIP(ipdst, proto, seglist, size)
    byte[t_iphdr] hdr
    byte retry
    word timeout

    hdr.ip_vers_hlen      = $45
    hdr.ip_service        = 0
    hdr:ip_length         = swab(t_iphdr + size)
    hdr:ip_id             = 0
    hdr:ip_flags_fragofst = 0 //$40 // Don't fragment
    hdr.ip_ttl            = 10 // Is this reasonable?
    hdr.ip_proto          = proto
    hdr:ip_chksm          = 0
    memcpy(@hdr.ip_src, @localip, IP4ADR_SIZE)
    if !ipdst // IP_BROADCAST
        memset(@hdr.ip_dst, IP_BROADCAST, IP4ADR_SIZE)
        memset(@dstMAC, MAC_BROADCAST, MAC_SIZE)
    else
        if ipdst=>0 & netmask:0 <> subnet:0 or ipdst=>2 & netmask:2 <> subnet:2
            ipdst = @gateway // External net destination
        fin
        memcpy(@hdr.ip_dst, ipdst, IP4ADR_SIZE)
        retry = 0
        while hdr:ip_dst:0 <> remoteip:0 and hdr:ip_dst:2 <> remoteip:2
            if retry >= 3
                return -1 // ARP failed
            fin
            retry++
            memset(@dstMAC,   MAC_BROADCAST, MAC_SIZE)
            memset(@remoteha, 0,             MAC_SIZE)
            memcpy(@remoteip, @hdr.ip_dst,   IP4ADR_SIZE)
            ePayload = PAYLOAD_ARP
            opARP    = ARP_REQST
            setFrameLen(t_earp)
            writeFrame(@eFrame, t_earp)
            for timeout = 1000 downto 0
                etherServiceIP
                if remoteha:0 | remoteha:2 | remoteha:4
                    break
                fin
            next
        loop
        memcpy(@dstMAC, @remoteha, MAC_SIZE)
    fin
    //
    // Calculate checksum
    //
    hdr:ip_chksm = sum1(0, @hdr, t_iphdr) ^ $FFFF
    //
    // set the ethernet payload and frame length
    //
    ePayload = PAYLOAD_IP
    setFrameLen(t_ethriphdr + size)
    //
    // Write the ethernet header
    //
    writeFrame(@eFrame, t_ethrhdr)
    //
    // Write the IP header
    //
    writeFrame(@hdr, t_iphdr)
    //
    // Write the remaining segments
    //
    while size > 0
        writeFrame(seglist=>seg_buf, seglist=>seg_len)
        size    = size - seglist=>seg_len
        seglist = seglist + t_segment
    loop
    return 0
end
//
// Send UDP datagram
//
def etherSendUDP(port, ipdst, portdst, data, len)
    word[8] seglist // list of data and header segments
    byte[t_udphdr] hdr

    hdr:udp_src       = swab(port=>notify_port)
    hdr:udp_dst       = swab(portdst)
    hdr:udp_len       = swab(t_udphdr + len)
    hdr:udp_chksm     = 0
    seglist:0:seg_buf = @hdr
    seglist:0:seg_len = t_udphdr
    seglist:4:seg_buf = data
    seglist:4:seg_len = len
    return etherSendIP(ipdst, IP_PROTO_UDP, @seglist, t_udphdr + len)
end
//
// Notify on UDP datagram received
//
def etherOpenUDP(localport, callback, param)
    word port
    byte i

    if !localport; return -1; fin // invalid port
    //
    // Look for an existing notification on localport
    //
    port = @portsUDP
    for i = 1 to MAX_UDP_NOTIFIES
        if port=>notify_port == localport
            port=>notify_func = callback
            port=>notify_parm = param
            return port
        fin
        port = port + t_notify
    next
    //
    // Add notification on localport if room
    //
    port = @portsUDP
    for i = 1 to MAX_UDP_NOTIFIES
        if !port=>notify_port
            port=>notify_port = localport
            port=>notify_func = callback
            port=>notify_parm = param
            return port
        fin
        port = port + t_notify
    next
    return 0
end
//
// Clear notify on UDP port
//
def etherCloseUDP(port)
    word port
    byte i

    if isuge(port, @portsUDP) and isult(port, @portsUDP + MAX_UDP_NOTIFIES * t_notify)
        //
        // Clear notiications on this port
        //
        if port=>notify_port
            port=>notify_port = 0
            return 0
        fin
    fin
    //
    // Invalid port
    //
    return -1
end
//
// Open TCP socket in SERVER mode
//
def etherListenTCP(lclport, callback, param)
    puts("TCP/IP not yet implented for this hardware.\n")
    return 0
end
//
// Open TCP socket in CLIENT mode
//
def etherConnectTCP(remip, remport, lclport, callback, param)
    puts("TCP/IP not yet implented for this hardware.\n")
    return 0
end
//
// Write to TCP socket
//
def etherSendTCP(wiz, data, len)
    return 0
end
//
// Close TCP socket
//
def etherCloseTCP(wiz)
    return 0
end
//
// Update notify callback
//
def etherSetCallback(port, callback)
    if isuge(port, @portsUDP) and isult(port, @portsUDP + MAX_UDP_NOTIFIES * t_notify)
        //
        // Update callback on this port
        //
        if port=>notify_port
                port=>notify_func = callback
            return 0
        fin
    fin
    //
    // Invalid port
    //
    return -1
end
//
// Update notify param
//
def etherSetParam(port, param)
    if isuge(port, @portsUDP) and isult(port, @portsUDP + MAX_UDP_NOTIFIES * t_notify)
        //
        // Update callback on this port
        //
        if port=>notify_port
                port=>notify_parm = param
            return 0
        fin
    fin
    //
    // Invalid port
    //
    return -1
end
//
// Service incoming packets
//
def etherServiceIP
    word rxsize, rxpacket, rxptr
    word iphdr, opt, optlen, port, lclport, remport
    byte i,   echolen
    word[4]   seglist
    byte[128] echo_reply

    //
    // Check for received packet
    //
    rxsize = getFrameLen()
    if rxsize
        //
        // Read the entire packet into memory
        //
        rxpacket = heapalloc(rxsize)
        readFrame(rxpacket, rxsize)
        rxptr  = rxpacket + t_ethrhdr
        rxsize = rxsize   - t_ethrhdr
        //
        // What kind of packet is it?
        //
        when rxpacket=>ethr_payload
            is PAYLOAD_IP
                iphdr  = rxptr
                rxptr  = rxptr  + t_iphdr
                rxsize = rxsize - t_iphdr
                if iphdr->ip_vers_hlen <> $45
                    optlen = iphdr=>ip_vers_hlen
                    if optlen & $F0 <> $40
                        //
                        // Not IPv4, ignore
                        //
                        break
                    fin
                    optlen = (optlen & $0F) << 2
                    if optlen > t_iphdr
                        //
                        // Read the options and throw them on the ground!
                        //
                        rxptr  = rxptr + (optlen - t_iphdr)
                        rxsize = rxsize - (optlen - t_iphdr)
                    fin
                fin
                //
                // Filter valid destination address
                //
                if iphdr=>ip_dst:2 <> localip:2 // Yes, this is a little lazy
                    if (iphdr=>ip_dst:0|netmask:0) & (iphdr=>ip_dst:2|netmask:2) <> IP_BROADCAST
                        break
                    fin
                fin
                //
                // What kind of IP protocol is it?
                //
                when iphdr->ip_proto
                    is IP_PROTO_UDP
                        port = @portsUDP
                        if port
                            lclport = swab(rxptr=>udp_dst)
                            for i = 1 to MAX_UDP_NOTIFIES
                                if port=>notify_port == lclport
                                port=>notify_func(@iphdr=>ip_src,swab(rxptr=>udp_src),rxptr+t_udphdr,swab(rxptr=>udp_len)-t_udphdr,port=>notify_parm)
                                break
                            fin
                            port = port + t_notify
                            next
                            fin
                        break
                    is IP_PROTO_TCP
                        break
                    is IP_PROTO_ICMP
                        //
                        // Service ICMP packets
                        //
                        if rxptr->icmp_type == ICMP_ECHO_REQST
                            if rxsize > 128
                                            echolen = 128
                            else
                            echolen = rxsize
                            fin
                            memcpy(@echo_reply, rxptr, echolen)
                            echo_reply.icmp_type  = ICMP_ECHO_REPLY
                            echo_reply:icmp_chksm = 0
                            seglist:seg_buf = @echo_reply
                            seglist:seg_len = echolen
                            etherSendIP(@iphdr=>ip_src, IP_PROTO_ICMP, @seglist, echolen)
                        fin
                        if hookICMP
                            hookICMP(@iphdr=>ip_src, rxptr, rxsize)
                        fin
                wend
                break
            is PAYLOAD_ARP
                when rxptr=>arp_op
                    is ARP_REPLY
                        //
                        // Fill in ARP cache
                        //
                        memcpy(@remoteha, @rxptr=>arp_senderha, 10) // copy ha and ip
                        break
                    is ARP_REQST
                        //
                        // Is this a request for me?
                        //
                        if rxptr=>arp_targip:0 == localip:0 and rxptr=>arp_targip:2 == localip:2
                            memcpy(@dstMAC,   @rxptr=>arp_senderha, MAC_SIZE)
                            memcpy(@remoteha, @rxptr=>arp_senderha, 10) // copy ha and ip
                            ePayload = PAYLOAD_ARP
                            opARP    = ARP_REPLY
                            setFrameLen(t_earp)
                            writeFrame(@eFrame, t_earp)
                        fin
                wend
        wend
        heaprelease(rxpacket)
    fin
    return 0
end
//
// Initialize the driver interface
//
export def setEtherDriver(MAC, getlen, readfrm, setlen, writefrm)#0
    memcpy(@myMAC,   MAC, MAC_SIZE)
    memcpy(@localha, MAC, MAC_SIZE)
    getFrameLen = getlen
    readFrame   = readfrm
    setFrameLen = setlen
    writeFrame  = writefrm
end
//
// Set the local IP addresses
//
def setEtherIP(newIP, newNetmask, newGateway)
    if newIP;      memcpy(@localip, newIP,      IP4ADR_SIZE); fin
    if newNetmask; memcpy(@netmask, newNetmask, IP4ADR_SIZE); fin
    if newGateway; memcpy(@gateway, newGateway, IP4ADR_SIZE); fin
    subnet:0 = netmask:0 & gateway:0
    subnet:2 = netmask:2 & gateway:2
    return 0
end
//
// Get the interface hardware address
//
def getEtherHA(ha)
    if ha; memcpy(ha, @myMAC, MAC_SIZE); fin
    return MAC_SIZE
end
//
// Fill in iNet class
//
iNet:serviceIP      = @etherServiceIP
iNet:openUDP        = @etherOpenUDP
iNet:sendUDP        = @etherSendUDP
iNet:closeUDP       = @etherCloseUDP
iNet:listenTCP      = @etherListenTCP
iNet:connectTCP     = @etherConnectTCP
iNet:sendTCP        = @etherSendTCP
iNet:closeTCP       = @etherCloseTCP
iNet:setInterfaceIP = @setEtherIP
iNet:getInterfaceHA = @getEtherHA
iNet:setCallback    = @etherSetCallback
iNet:setParam       = @etherSetParam
done
