
version equ     3

        include defs.asm

; PC/FTP Packet Driver source, conforming to version 1.09 of the spec
; Katie Stevens (dkstevens@ucdavis.edu)
; Computing Services, University of California, Davis
; Portions (C) Copyright 1988 Regents of the University of California

; This program is free software; you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, version 1.
;
; This program is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; GNU General Public License for more details.

; Change History
;
; 05-90         ks      Katie Stevens (dkstevens@ucdavis.edu):
;                       - Created
; 07-19-91      jgn     Jim Noble (noble_jim@po.gis.prc.com):
;                       - Added Change History and documented changes
;                       - Fixed a bug that caused AppleTalk network numbers to
;                         be byte-swapped when printed
;                       - Fixed a bug that caused the wrong buffer address to 
;                         be passed to the packet receiver upcall routine
;                       - Made changes in the driver information structure to 
;                         match LocalTalk:  size of MAC-layer address, MTU,
;                         and multicast buffer size
;                       - Changed the packet buffer sizes from 1024 to 586,
;                         the maximum amount of data in a DDP packet
;                       - Commented-out the noop_upcall completion routine and
;                         changed the completion routine address passed to
;                         the AppleTalk driver to 0000:0000 for all driver
;                         calls
;                       - Set the status field to -1 prior to the GetNetInfo
;                         AppleTalk driver call as per the recommendation of
;                         the driver interface spec.
;                       - Added structure definitions for short and long 
;                         format DDP packets and used them when adding a
;                         check to ensure that received packets are IP packets
; 07-22-91      jgn     Jim Noble (noble_jim@po.gis.prc.com):
;                       - Fixed a bug that would prevent the packet driver
;                         from initializing with a static address if there
;                         were no dynamic addresses available
; 07-23-91      jgn     Jim Noble (noble_jim@po.gis.prc.com):
;                       - Added the optional IP address to the usage message
;                       - Incremented version to 3
;

code    segment word public
        assume  cs:code, ds:code

; Definitions specific to the ATALK.SYS driver for PC LocalTalk cards:
; these include Apple LocalTalk PC Card, Sun/TOPS FlashCard
; For a complete description of the LocalTalk commands, structures and
; methods used in this driver, please refer to Apple APDA document #M7055,
; LocalTalk PC Card and Driver Preliminary Notes.

driverstring    db      'AppleTalk', 0          ; ATALK.SYS signature string
dot_char        db      '.', 0                  ; for IP address display

AT_INT          equ     5CH                     ; Software int# for ATALK.SYS

; General ATALK.SYS driver commands
AT_INIT                 equ     01H             ; Initialize driver software
AT_GETNETINFO           equ     03H             ; Get driver info

; Datagram Delivery Protocol commands for ATALK.SYS driver
DDP_OPENSOCKET          equ     20H
DDP_CLOSESOCKET         equ     21H
DDP_WRITE               equ     22H
DDP_READ                equ     23H
DDP_CANCEL              equ     24H

; Name Binding Protocol commands for ATALK.SYS driver
NBP_REGISTER            equ     30H
NBP_REMOVE              equ     31H
NBP_LOOKUP              equ     32H
NBP_CONFIRM             equ     33H
NBP_CANCEL              equ     34H

; AppleTalk Transaction Protocol commands for ATALK.SYS driver
ATP_SEND_REQUEST        equ     42H

; ATALK.SYS command qualifiers
ASYNC_MASK      equ     8000H           ; Start command, then return
INTR_MASK       equ     4000H           ; Wait for intr service to complete

XO_BIT          equ     20H             ; ATP - exactly once transaction

; Structure for short format DDP packet [jgn]
DDPshort struc
        lap_dst         db      ?       ; destination node
        lap_src         db      ?       ; source node
        lap_type        db      ?       ; 1 for short format DDP packet
        ddps_len        dw      ?       ; packet length
        ddps_dskt       db      ?       ; destination socket
        ddps_sskt       db      ?       ; source socket
        ddps_type       db      ?       ; 22 for IP packets
DDPshort ends

; Structure for long format DDP packet [jgn]
DDPlong struc
        lap_dst         db      ?       ; destination node
        lap_src         db      ?       ; source node
        lap_type        db      ?       ; 2 for long format DDP packets
        ddpl_len        dw      ?       ; packet length
        ddpl_chksum     dw      ?       ; checksum
        ddpl_dnet       dw      ?       ; destination network
        ddpl_snet       dw      ?       ; source network
        ddpl_dnode      db      ?       ; destination node
        ddpl_snode      db      ?       ; source node
        ddpl_dskt       db      ?       ; destination socket
        ddpl_sskt       db      ?       ; source socket
        ddpl_type       db      ?       ; 22 for IP packets
DDPlong ends

; Structure for AppleTalk node addressing
AddrBlk struc
        ablk_network    dw      0
        ablk_nodeid     db      0
        ablk_socket     db      0
AddrBlk ends

; Structure for general calls to AppleTalk driver (ATALK.SYS)
InfoParams struc
        atd_command     dw              AT_GETNETINFO
        atd_status      dw              0
        atd_compfun     segmoffs        <>
        inf_network     dw              0
        inf_nodeid      db              0
        inf_abridge     db              0
        inf_config      dw              0
        inf_buffptr     segmoffs        <>
        inf_buffsize    dw              0
InfoParams ends
; Parameter block for general calls to AppleTalk driver (ATALK.SYS)
MyInfo  InfoParams      <>

; Address block for our gateway
MyGateway       AddrBlk         <>

; Structure for calls to AppleTalk driver (ATALK.SYS) for Datagram
; Delivery Protocol (DDP) service
DDPParams struc
        ddp_command     dw              0
        ddp_status      dw              0
        ddp_compfun     segmoffs        <>
        ddp_addr        AddrBlk         <>
        ddp_socket      db              0
        ddp_type        db              0
        ddp_buffptr     segmoffs        <>
        ddp_buffsize    dw              0
        ddp_chksum      db              0
DDPParams ends
; Parameter blocks for AppleTalk DDP access
DDPio           DDPParams       <>              ; Write on DDP socket
; 2 buffers for packet receive from ATALK.SYS
DDP1inuse       db      0                       ; Buffer occupied flag
DDP1buffsize    dw      0                       ; Buffer length during reads
DDP1buffer      db      586 dup (0)             ; Buffer for DDP read [jgn]
DDP2inuse       db      0                       ; 2nd Buffer occupied flag
DDP2buffsize    dw      0                       ; 2nd Buffer length during read
s
DDP2buffer      db      586 dup (0)             ; 2nd Buffer for DDP read [jgn]

; Structure for calls to AppleTalk driver (ATALK.SYS) for Name
; Binding Protocol (NBP) service
NBPParams struc
        nbp_command     dw              0
        nbp_status      dw              0
        nbp_compfun     segmoffs        <>
        nbp_addr        AddrBlk         <>
        nbp_toget       dw              0
        nbp_buffptr     segmoffs        <>
        nbp_buffsize    dw              0
        nbp_interval    db              0
        nbp_retry       db              0
        nbp_entptr      segmoffs        <>
NBPParams ends
; Parameter block for AppleTalk NBP access
NBP             NBPParams       <>

; Structure for name-to-address bind entries
NBPTuple struc
        tup_address     AddrBlk         <>
        tup_enum        db              0
        tup_name        db              99 dup(0)
NBPTuple ends
; Name Binding Tuple for our IP gateway
NBPt            NBPTuple        <>

; Structure for name-to-address table
NBPEntry struc
        tab_next        segmoffs        <>
        tab_entry       NBPTuple        <>
NBPEntry ends
NBPtable        NBPEntry        <>

; Structure for calls to AppleTalk driver (ATALK.SYS) for AppleTalk
; Transaction Protocol (ATP) service
ATPParams struc
        atp_command     dw              0
        atp_status      dw              0
        atp_compfun     segmoffs        <>
        atp_addrblk     AddrBlk         <>
        atp_socket      db              0
        atp_fill        db              0
        atp_buffptr     segmoffs        <>
        atp_buffsize    dw              0
        atp_interval    db              0
        atp_retry       db              0
        atp_flags       db              0
        atp_seqbit      db              0
        atp_tranid      dw              0
        atp_userbytes   db              4 dup(0)
        atp_bdsbuffs    db              0
        atp_bdsresps    db              0
        atp_bdsptr      segmoffs        <>
ATPParams ends
; Parameter block for AppleTalk ATP access
ATP             ATPParams       <>

; Structure for BDS elements
BDSElement struc
        bds_buffptr     segmoffs        <>
        bds_buffsize    dw              0
        bds_datasize    dw              0
        bds_userbytes   db              4 dup(0)
BDSElement ends
; Parameter block for our BDS element
BDS             BDSElement      <>

; Struct for IP gateway information
IPGInfo struc
        ipg_opcode      db              0,0,0,1 ; IPGP_ASSIGN
        ipg_ipaddress   dd              0       ; our IP address
        ipg_ipname      dd              0       ; nameserver IP address
        ipg_ipbroad     dd              0       ; broadcast IP address
        ipg_ipfile      dd              0       ; file server IP address
        ipg_ipother     dd              4 dup (0)
        ipg_string      db              128 dup (0), '$'
IPGInfo ends
; Parameter block for info about our IP gateway
IPG             IPGInfo         <>

IPG_ERROR       equ     -1

static_address  db      0, 0, 0, 0
use_static      db      0
test_address    db      0, 0, 0, 0
temp_4bytes     db      0, 0, 0, 0

; End of Appletalk parameter definitions

; The following values may be overridden from the command line.
; If they are omitted from the command line, these defaults are used.

        public  int_no
int_no  db      0,0,0,0                 ;must be four bytes long for get_number
.

        public  driver_class, driver_type, driver_name, driver_function, parame
ter_list
driver_class    db      5,0             ;from the packet spec
driver_type     db      1               ;from the packet spec
driver_name     db      'LocalTalk',0   ;name of the driver.
driver_function db      2
parameter_list  label   byte
        db      1       ;major rev of packet driver
        db      9       ;minor rev of packet driver
        db      14      ;length of parameter list
        db      4       ;length of MAC-layer address [jgn]
        dw      586     ;MTU, including MAC headers [jgn]
        dw      0       ;buffer size of multicast addrs [jgn]
        dw      0       ;(# of back-to-back MTU rcvs) - 1
        dw      0       ;(# of successive xmits) - 1
int_num dw      0       ;Interrupt # to hook for post-EOI
                        ;processing, 0 == none,

        public  rcv_modes
rcv_modes       dw      4               ;number of receive modes in our table.
                dw      0,0,0,rcv_mode_3

        public bad_command_intercept
bad_command_intercept:
;called with ah=command, unknown to the skeleton.
;exit with nc if okay, cy, dh=error if not.
        mov     dh,BAD_COMMAND
        stc
        ret

        public  as_send_pkt
; The Asynchronous Transmit Packet routine.
; Enter with es:di -> i/o control block, ds:si -> packet, cx = packet length,
;   interrupts possibly enabled.
; Exit with nc if ok, or else cy if error, dh set to error number.
;   es:di and interrupt enable flag preserved on exit.
as_send_pkt:
        ret

        public  drop_pkt
; Drop a packet from the queue.
; Enter with es:di -> iocb.
drop_pkt:
        assume  ds:nothing
        ret

        public  xmit
; Process a transmit interrupt with the least possible latency to achieve
;   back-to-back packet transmissions.
; May only use ax and dx.
xmit:
        assume  ds:nothing
        ret

        public  send_pkt
send_pkt:
;enter with es:di->upcall routine, (0:0) if no upcall is desired.
;  (only if the high-performance bit is set in driver_function)
;enter with ds:si -> packet, cx = packet length.
;if we're a high-performance driver, es:di -> upcall.
;exit with nc if ok, or else cy if error, dh set to error number.
        assume  ds:nothing
; send packet to AppleTalk/DDP/IP gateway
        ; load info about the packet we are sending
        mov     DDPio.ddp_buffptr.offs, si
        mov     DDPio.ddp_buffptr.segm, ds      ; DDPio.buffptr -> IP packet
        mov     DDPio.ddp_buffsize, cx          ; DDPio.buffsize = packet len

        ; send all packets to the IP gateway
        mov     cx, (size AddrBlk)              ; DDPio.ddp_addr = MyGateway
        mov     ax, cs
        mov     ds, ax
        mov     es, ax
        mov     si, offset MyGateway
        mov     di, offset DDPio.ddp_addr
        rep     movsb

        mov     bx, offset DDPio
        call    doATint                         ; Ask ATALK.SYS to send packet

        cmp     DDPio.ddp_status, 00H           ; Packet sent okay?
        je      send_ret                        ; Yes, status is good
                                                ; No, status gives error
send_err:
        call    count_out_err
        mov     dh, CANT_SEND                   ; set error flag
        stc
        ret

send_ret:
        clc                                     ; packet sent successfully
        ret

        public  set_address
set_address:
;enter with ds:si -> Ethernet address, CX = length of address.
;exit with nc if okay, or cy, dh=error if any errors.
        assume  ds:nothing
        mov     dh, BAD_COMMAND
        stc
        ret


rcv_mode_3:
;receive mode 3 is the only one we support, so we don't have to do anything.
        ret


        public  set_multicast_list
set_multicast_list:
;enter with ds:si ->list of multicast addresses, ax = number of addresses,
;  cx = number of bytes.
;return nc if we set all of them, or cy,dh=error if we didn't.
        mov     dh,NO_MULTICAST
        stc
        ret


        public  terminate
terminate:
        push    ds
        push    cs
        pop     ds

terminate_write:
        ; close the DDP socket
        mov     DDPio.ddp_command, DDP_CLOSESOCKET
        mov     bx, offset DDPio
        call    doATint

        mov     NBP.nbp_command, NBP_REMOVE
        mov     NBP.nbp_entptr.offs, offset NBPtable.tab_entry.tup_name
        mov     NBP.nbp_entptr.segm, ds
        mov     bx, offset NBP
        call    doATint

        pop     ds
        ret

        public  reset_interface
reset_interface:
;reset the interface.
        assume  ds:code
        ret


;called when we want to determine what to do with a received packet.
;enter with cx = packet length, es:di -> packet type, dl = packet class.
        extrn   recv_find: near

;called after we have copied the packet into the buffer.
;enter with ds:si ->the packet, cx = length of the packet.
        extrn   recv_copy: near

;call this routine to schedule a subroutine that gets run after the
;recv_isr.  This is done by stuffing routine's address in place
;of the recv_isr iret's address.  This routine should push the flags when it
;is entered, and should jump to recv_exiting_exit to leave.
;enter with ax = address of routine to run.
        extrn   schedule_exiting: near

;recv_exiting jumps here to exit, after pushing the flags.
        extrn   recv_exiting_exit: near

        extrn   count_in_err: near
        extrn   count_out_err: near

        public  recv
recv:
;called from the recv isr.  All registers have been saved, and ds=cs.
;Upon exit, the interrupt will be acknowledged.
;NOTE: this packet driver merely makes calls to another hardware
;driver, ATALK.SYS. ATALK.SYS handles the hardware interrupt service;
;ATALK.SYS then calls this packet driver with FAR subroutine calls.
;the ATALK.SYS FAR subroutine is recv_at_upcall
        assume  ds:nothing
        ret

;*******************************************

; [jgn] Null completion routine is not needed.  Just specify 0000:0000 as the
; address of the completion routine for no call.
;
; NULL completion routine for ATALK.SYS drivers calls
;noop_upcall proc far
;       ret
;noop_upcall endp

;First half routine for DDP socket.
;ATALK.SYS calls this routine when a packet is received.
;ATALK.SYS assumes we are a far procedure.
;       AH = socket
;       CX = size of data packet
;       DS:BX = address of buffer
preview_upcall proc far
        assume  ds:nothing

;       maximum packet we can receive is 586 bytes [jgn]
        cmp     cx, 586                         ; Max size of DDP data field [j
gn]
        ja      preview_drop

;       Is it an IP packet?  DDP type should be 22 [jgn]
        cmp     byte ptr ds:[bx].lap_type, 1    ; Is it a short DDP packet?
        jne     ddp_long                        ; No, must be long DDP
        cmp     byte ptr ds:[bx].ddps_type, 22  ; Is it an IP packet?
        jne     preview_drop                    ; No, refuse packet
        jmp     preview_buff1
ddp_long:
        cmp     byte ptr ds:[bx].ddpl_type, 22  ; Is it an IP packet?
        jne     preview_drop                    ; No, refuse packet

preview_buff1:
        cmp     DDP1inuse, 00H
        jne     preview_buff2
        mov     DDP1inuse, 01H

;       repeat buffer size back to ATALK.SYS in CX
;       ask ATALK.SYS driver to pass us the buffer at DS:BX
;       tell ATALK.SYS address of 2nd half routine in ES:DX
        movseg  ds,cs
        mov     bx, offset DDP1buffer           ; ds:bx->buffer
        movseg  es,cs
        mov     dx, offset recv_at_upcall       ; es:dx->2nd half routine
        jmp     preview_ret

preview_buff2:
        cmp     DDP2inuse, 00H
        jne     preview_drop
        mov     DDP2inuse, 01H

;       repeat buffer size back to ATALK.SYS in CX
;       ask ATALK.SYS driver to pass us the buffer at DS:BX
;       tell ATALK.SYS address of 2nd half routine in ES:DX
        movseg  ds,cs
        mov     bx, offset DDP2buffer           ; ds:bx->buffer
        movseg  es,cs
        mov     dx, offset recv_at_upcall       ; es:dx->2nd half routine
        jmp     preview_ret


preview_drop:
;       ask ATALK.SYS to drop the packet
        call    count_in_err
        mov     cx, 00h

preview_ret:
        ret
preview_upcall endp

;Second half routine for DDP socket.
;ATALK.SYS calls this routine when the packet has been copied to our buffer.
;ATALK.SYS assumes we are a far procedure.
;       CX = size of data packet
;       DS:BX = address of buffer
recv_at_upcall proc far
        assume  ds:nothing

recv_buff1:
        cmp     bx, offset DDP1buffer
        jne     recv_buff2

;       check if we have a client waiting for packets
;       pass to recv_find    es:di->driver_type, cx=#bytes in packet
        mov     DDP1buffsize, cx
        mov     di, offset driver_type
        movseg  es,cs
        mov     dl,cs:driver_class
        call    recv_find

;       es:di->client buffer, or es:di=0 means drop the packet
        mov     ax, es
        or      ax, di
        je      recv_pass1

;       copy ds:si->es:di for cx bytes
        push    es                              ; save the client buffer
        push    di                              ; address (es:di) [jgn]
        movseg  ds,cs
        mov     si, offset DDP1buffer
        mov     cx, DDP1buffsize
        rep     movsb

;       tell receiver copy has been made; ds:si->the packet, cx=length
        pop     si                              ; restore the client buffer
        pop     ds                              ; address into ds:si [jgn]
        mov     cx, DDP1buffsize
        call    recv_copy

recv_pass1:
;       first buffer is free for use again
        mov     DDP1inuse, 00H
        jmp     recv_ret


recv_buff2:
        cmp     bx, offset DDP2buffer
        jne     recv_ret

;       check if we have a client waiting for packets
;       pass to recv_find    es:di->driver_type, cx=#bytes in packet
        mov     DDP2buffsize, cx
        mov     di, offset driver_type
        movseg  es,cs
        call    recv_find

;       es:di->client buffer, or es:di=0 means drop the packet
        mov     ax, es
        or      ax, di
        je      recv_pass2

;       copy ds:si->es:di for cx bytes
        push    es                              ; save the client buffer
        push    di                              ; address (es:di) [jgn]
        movseg  ds,cs
        mov     si, offset DDP2buffer
        mov     cx, DDP2buffsize
        rep     movsb

;       tell receiver copy has been made; ds:si->the packet, cx=length
;       push    es
        pop     si                              ; restore the client buffer
        pop     ds                              ; address into ds:si [jgn]
;       mov     si, offset DDP2buffer
        mov     cx, DDP2buffsize
        call    recv_copy
recv_pass2:
;       second buffer is now free for use
        mov     DDP2inuse, 00H

recv_ret:
        ret
recv_at_upcall endp

;*******************************************

; Call DOS software interrupt for AppleTalk
; caller must set ds:bx -> parameter block for ATALK.SYS
doATint:
        int     AT_INT                          ; Interrupt ATALK.SYS driver
        ret



        public  timer_isr
timer_isr:
;if the first instruction is an iret, then the timer is not hooked
        iret

;any code after this will not be kept after initialization. Buffers
;used by the program, if any, are allocated from the memory between
;end_resident and end_free_mem.
        public end_resident,end_free_mem
end_resident    label   byte
end_free_mem    label   byte
;****************************************************************************

        public  usage_msg
usage_msg       db      "usage: localtlk [options] <packet_int_no> [ <IP addres
s> ]",CR,LF,'$' ; [jgn]

        public  copyright_msg
copyright_msg   db      "Packet driver for Apple LocalTalk PC Card, version ",'
0'+(majver / 10),'0'+(majver mod 10),".",'0'+version,CR,LF
                db      "Portions Copyright 1990, Regents of the University of 
California",CR,LF,'$'

no_atalk_sys_msg:
        db      "Couldn't locate ATALK.SYS -- packet driver not installed",CR,L
F,'$'
atalk_sys_found_msg:
        db      "ATALK.SYS driver located at software interrupt ",'$'
inf_nodeid_name:
        db      "Attaching to AppleTalk network as node ",'$'
inf_abridge_name:
        db      "AppleTalk network bridge is node ",'$'
ddp_failed_msg:
        db      "Datagram Delivery Protocol socket open failed; return status: 
",'$'
ddp_wrong_socket_msg:
        db      "Datagram Delivery Protocol failed; unable to aquire requested 
socket",CR,LF,'$'
ddp_open_msg:
        db      "Datagram Delivery Protocol open on socket ",'$'
atalk_open_msg:
        db      "Attached to AppleTalk network as (net:node:sock): ",'$'
nbp_no_gateway_msg:
        db      "NBP: IPGATEWAY lookup failed; return status: ",'$'
nbp_ipg_addr_msg:
        db      "IPGATEWAY located on AppleTalk network as (net:node:sock): ",'
$'
atp_no_gateway_msg:
        db      "ATP: IPGATEWAY transport setup failed; return status: ",'$'
ipg_gateway_err_msg:
        db      "IP Gateway error: ",'$'
myip_addr_msg:
        db      "My IP address: ",'$'
ns_ip_addr_msg:
        db      "Name Server IP address: ",'$'
bd_ip_addr_msg:
        db      "Broadcast IP address: ",'$'
fs_ip_addr_msg:
        db      "File Server IP address: ",'$'
opcode_msg:
        db      "IPG opcode: ",'$'
nbp_no_register_msg:
        db      "NBP: failed, couldn't register our name; return status: ",'$'
ddp_cant_recv:
        db      "DDP: couldn't initiate read on socket; return status: ",'$'
test_arg_msg:
        db      "Test IP arg parsing: ",'$'
null_msg        db      '$'

ipgateway_name:
        db      01H, '=', 09H, "IPGATEWAY", 01H, '*', '0'
myip_name:
        db      09H, "IPADDRESS", 01H, '*', '0'
myip_name_len   equ     12

; Temporary storage for calls to print_number
dtemp   dw      ?,0

        extrn   set_recv_isr: near

;enter with si -> argument string, di -> wword to store.
;if there is no number, don't change the number.
        extrn   get_number: near

;enter with ds:dx -> argument string, ds:di -> dword to print.
        extrn   print_number: near

;enter with al = char to display
        extrn   chrout: near
;enter with ax,dx holding 32 bits to display in decimal (ax holds low word)
        extrn   decout: near
        extrn   byteout: near
        extrn   wordout: near

        extrn   skip_blanks: near

;enter with si -> argument string, di -> word to store.
;if there is no number, don't change the number.
        extrn   get_number: near

;called with ds:si -> immediately after the entry_point
        public  parse_args
parse_args:
        call    skip_blanks
        lodsb
        cmp     al, CR
        je      no_more_args
        cmp     al, '['                         ; check for square brackets
        je      past_brackets
        dec     si                              ; not a bracket, back up
past_brackets:
        mov     di, offset temp_4bytes          ; get first IP address byte
        call    get_number
        mov     byte ptr test_address, cl
        lodsb
        cmp     al, '.'
        jne     no_more_args

        mov     di, offset temp_4bytes          ; get second IP address byte
        call    get_number
        mov     byte ptr test_address+1, cl
        lodsb
        cmp     al, '.'
        jne     no_more_args

        mov     di, offset temp_4bytes          ; get third IP address byte
        call    get_number
        mov     byte ptr test_address+2, cl
        lodsb
        cmp     al, '.'
        jne     no_more_args

        mov     di, offset temp_4bytes          ; get first IP address byte
        call    get_number
        mov     byte ptr test_address+3, cl

;       mov     dx, offset test_arg_msg
;       mov     di, offset test_address
;push   si
;       call    print_ip_addr
;pop    si

        mov     ax, word ptr test_address
        mov     word ptr static_address, ax
        mov     ax, word ptr test_address+2
        mov     word ptr static_address+2, ax
        mov     use_static, 01H

        lodsb
        cmp     al, ']'
        je      arg_return

;exit with nc if all went well, cy otherwise.
no_more_args:
        dec     si
arg_return:
        clc
        ret


; Initialize our interface to the ATALK.SYS driver.
; NOTE: this initialization code is modeled after the PC/IP LocalTalk
; driver written by Dan Lanciani (ddl@harvard.harvard.edu); the PCIP
; software package can found at husc6.harvard.edu:pub/pcip/pcip.tar.Z
        public  etopen
etopen:
        assume  ds:code

; ATALK.SYS driver may be loaded at a software interrupt somewhere
; between 5CH and 70H. Locate ATALK.SYS driver by scanning for signature.
isATLoaded:                                     ; Look for ATALK.SYS driver
        cld
        call    ATGetInt                        ; Load start of intr range
        mov     dx, ax                          ; Save start value in DX
chkloop:
        cmp     dx, 70H                         ; Scanned all possible vectors?
        jne     checkstring                     ; No, check this vector
        xor     ax, ax                          ; Yes, driver not found
        jmp     chksplit                        ; Skip ahead to return
checkstring:
        mov     bx, dx                          ; Load intr# for scan
        shl     bx, 1                           ; Multiply by 2 (for seg bytes)
        shl     bx, 1                           ; Multiply by 2 (for off bytes)
        xor     ax, ax
        mov     es, ax                          ; Lowest page of memory
        lds     si, es:[bx]                     ; Load vector for scan intr#
        mov     ax, ds                          ; Load segment this scan intr#
        or      ax, si                          ; OR with off this scan intr#
        jz      keepchecking                    ; Keep checking if no bits
        sub     si, 16                          ; Signature is just before code
        mov     di, offset driverstring         ; Load compare string
        mov     cx, 9                           ; Load length of compare string
        movseg  es,cs
        repe    cmpsb                           ; Compare ds:si to es:di
        jne     keepchecking                    ; Keep checking if not matched
        call    ATGetInt                        ; Matched, get INT# again
        cmp     ax, dx                          ; INT# already set properly?
        jz      chksplit                        ; Yes, use this INT#
                                                ; No, we found INT# by scanning
        call    ATPatch                         ; Modify code to match scan
        call    ATGetInt                        ; Retrieve final INT#
        jmp     chksplit                        ; Skip ahead to return

keepchecking:                                   ; Havent found ATALK.SYS driver
        inc     dx                              ; Check next possible INT#
        jmp     chkloop                         ; Loop back to check next INT#

chksplit:                                       ; Done with scan for ATALK.SYS
        cmp     ax, 00H                         ; ATALK.SYS driver found?
        jne     atalk_sys_found                 ; Yes, skip ahead to continue

        mov     dx, offset no_atalk_sys_msg     ; No, ATALK.SYS not loaded
        jmp     error_wrt                       ; Skip ahead to report error

atalk_sys_found:                                ; ATALK.SYS driver found
        movseg  ds,cs
        mov     dtemp, dx                       ; Report intr# of ATALK.SYS
        mov     di, offset dtemp
        mov     dx, offset atalk_sys_found_msg
        call    print_number

; We need to establish our Appletalk node
get_our_info:                                   ; Get info params from ATALK
        mov     MyInfo.atd_command, AT_GETNETINFO
        mov     MyInfo.atd_status, -1           ; As per PC LocalTalk spec. [jg
n]
        mov     MyInfo.atd_compfun.offs, 0      ; No completion routine [jgn]
        mov     MyInfo.atd_compfun.segm, 0
        mov     bx, offset MyInfo
        call    doATint

        cmp     MyInfo.atd_status, 00H          ; Already initialized?
        je      get_ddp_socket                  ; Yes, skip ahead

        mov     MyInfo.atd_command, AT_INIT     ; No, initialize our node
        mov     MyInfo.atd_compfun.offs, 0      ; No completion routine [jgn]
        mov     MyInfo.atd_compfun.segm, 0
        mov     bx, offset MyInfo
        call    doATint

; We need to establish our AppleTalk/DDP socket
get_ddp_socket:                                 ; Open a DDP socket
        mov     DDPio.ddp_command, DDP_OPENSOCKET
        mov     DDPio.ddp_compfun.offs, 0       ; No completion routine [jgn]
        mov     DDPio.ddp_compfun.segm, 0
        mov     DDPio.ddp_buffptr.offs, offset preview_upcall
        mov     DDPio.ddp_buffptr.segm, cs
        mov     DDPio.ddp_socket, 72            ; ask for experimental sock#
        mov     DDPio.ddp_type, 22              ; ask for IP socket type
        mov     bx, offset DDPio                        ; ds:bx-> DDP param blo
ck
        call    doATint                         ; ask ATALK.SYS for a socket

        cmp     DDPio.ddp_status, 00H           ; error return from ATALK.SYS?
        je      chk_ddp_socket                  ; no, skip ahead to continue
                                                ; yes, no socket for us
        mov     ax, DDPio.ddp_status
        mov     dtemp, ax
        mov     di, offset dtemp
        mov     dx, offset ddp_failed_msg
        call    print_number                    ; report error and stat return
        jmp     error_ret

;**** do we really require socket 72?
chk_ddp_socket:                                 ; check the socket we opened
        cmp     DDPio.ddp_socket, 72            ; did we get the one requested?
        je      ddp_ready                       ; yes, socket is as expected
                                                ; no, but must have socket 72
        mov     DDPio.ddp_command, DDP_CLOSESOCKET
        mov     bx, offset DDPio
        call    doATint                         ; close the assigned socket

        mov     dx, offset ddp_wrong_socket_msg ; load error msg
        jmp     error_wrt                       ; skip ahead to display and ret

ddp_ready:                                      ; DDP socket 72 is ready
        mov     DDPio.ddp_command, DDP_WRITE    ; Use param block for WRITE now

; AppleTalk node and DDP socket have been established
        mov     ax, MyInfo.inf_network
        mov     word ptr dtemp, ax
        mov     al, MyInfo.inf_nodeid
        mov     ah, DDPio.ddp_socket
        mov     byte ptr dtemp+2, al
        mov     byte ptr dtemp+3, ah
        mov     di, offset dtemp
        mov     dx, offset atalk_open_msg
        call    print_at_addr                   ; display AppleTalk node info
        mov     ax, 00H
        mov     dtemp+2, ax

; We need an IP gateway node
nbp_ipgateway:                                  ; Locate our IP gateway node
        movseg  ds,cs
        mov     NBP.nbp_command, NBP_LOOKUP
        mov     NBP.nbp_compfun.offs, 0         ; No completion routine [jgn]
        mov     NBP.nbp_compfun.segm, 0
        mov     NBP.nbp_toget, 01H
        mov     NBP.nbp_buffptr.offs, offset NBPt
        mov     NBP.nbp_buffptr.segm, ds
        mov     NBP.nbp_buffsize, (size NBPTuple)
        mov     NBP.nbp_interval, 5
        mov     NBP.nbp_retry, 12
        mov     NBP.nbp_entptr.offs, offset ipgateway_name
        mov     NBP.nbp_entptr.segm, ds
        mov     bx, offset NBP
        call    doATint                         ; do name-bind lookup

        cmp     NBP.nbp_status, 00H             ; status return=error?
        jne     nbp_no_gateway                  ; yes, report error and exit

        cmp     NBP.nbp_toget, 01H
        je      atp_setup

nbp_no_gateway:                                 ; NBP lookup failed
        mov     ax, NBP.nbp_status
        mov     dtemp, ax
        mov     di, offset dtemp
        mov     dx, offset nbp_no_gateway_msg   ; display error msg
        call    print_number

        mov     DDPio.ddp_command, DDP_CLOSESOCKET
        mov     bx, offset DDPio
        call    doATint                         ; close the assigned socket

        jmp     error_ret                       ; skip ahead to return

; We need a transport layer to the IP gateway
atp_setup:
        mov     cx, (size AddrBlk)              ; MyGateway = NBPt.tup_addr
        movseg  es,cs
        mov     si, offset NBPt.tup_address
        mov     di, offset MyGateway
        rep     movsb

        mov     di, offset NBPt.tup_address     ; Display our gateway node
        mov     dx, offset nbp_ipg_addr_msg
        call    print_at_addr

        cmp     use_static, 00H                 ; Do we have a static address? 
[jgn]
        je      bds_setup                       ; No, don't change anything
        mov     IPG.ipg_opcode+3, 3             ; Yes, change to IPG_SERVER

bds_setup:
        mov     BDS.bds_buffptr.offs, offset IPG
        mov     BDS.bds_buffptr.segm, ds
        mov     BDS.bds_buffsize, (size IPGInfo)

        mov     ATP.atp_command, ATP_SEND_REQUEST
        mov     ATP.atp_compfun.offs, 0         ; No completion routine [jgn]
        mov     ATP.atp_compfun.segm, 0
        mov     cx, (size AddrBlk)              ; ATP.atp_addr = NBPt.tup_addr
        movseg  es,cs
        mov     si, offset NBPt.tup_address
        mov     di, offset ATP.atp_addrblk
        rep     movsb
        mov     ATP.atp_buffptr.offs, offset IPG
        mov     ATP.atp_buffptr.segm, ds
        mov     ATP.atp_buffsize, (size IPGInfo)
        mov     ATP.atp_interval, 05H
        mov     ATP.atp_retry, 05H
        mov     ATP.atp_flags, XO_BIT
        mov     ATP.atp_bdsbuffs, 01H
        mov     ATP.atp_bdsptr.offs, offset BDS
        mov     ATP.atp_bdsptr.segm, ds
        mov     bx, offset ATP
        call    doATint

        cmp     ATP.atp_status, 00H             ; status return=error?
        jne     atp_no_gateway                  ; yes, report error and exit

        cmp     ATP.atp_bdsbuffs, 01H
        je      chk_ip_opcode

atp_no_gateway:                                 ; ATP setup failed
        mov     ax, ATP.atp_status
        mov     dtemp, ax
        mov     di, offset dtemp
        mov     dx, offset atp_no_gateway_msg   ; display error msg
        call    print_number

        mov     DDPio.ddp_command, DDP_CLOSESOCKET
        mov     bx, offset DDPio
        call    doATint                         ; close the assigned socket

        jmp     error_ret                       ; skip ahead to return

chk_ip_opcode:
        cmp     IPG.ipg_opcode.offs, IPG_ERROR  ; opcode is 32 bit
        jne     save_ipaddr                     ; check one word at a time
        cmp     IPG.ipg_opcode.segm, IPG_ERROR  ; error from IP gateway?
        jne     save_ipaddr                     ; no, transport established
                                                ; yes, ATP setup failed
        mov     dx, offset ipg_gateway_err_msg  ; display IPG error msg
        mov     ah, 9
        int     21H
        mov     dx, offset IPG.ipg_string
        mov     ah, 9
        int     21H
        mov     al, 13                          ; display CR-LF
        call    chrout
        mov     al, 10
        call    chrout

        mov     DDPio.ddp_command, DDP_CLOSESOCKET
        mov     bx, offset DDPio
        call    doATint                         ; close the assigned socket

        jmp     error_ret

; AppleTalk/IP transport layer established
save_ipaddr:
        mov     dx, offset myip_addr_msg
        cmp     use_static, 00H
        jne     show_static
show_dynamic:
        mov     di, offset IPG.ipg_ipaddress
        jmp     show_ipaddr
show_static:
        mov     di, offset static_address
show_ipaddr:
        call    print_ip_addr

        mov     dx, offset ns_ip_addr_msg
        mov     di, offset IPG.ipg_ipname
        call    print_ip_addr

        mov     dx, offset bd_ip_addr_msg
        mov     di, offset IPG.ipg_ipbroad
        call    print_ip_addr

        mov     dx, offset fs_ip_addr_msg
        mov     di, offset IPG.ipg_ipfile
        call    print_ip_addr

; We need to register ourself with the AppleTalk Name Binding Agent
nbp_register_ourself:
        mov     al, MyInfo.inf_nodeid
        mov     NBPtable.tab_entry.tup_address.ablk_nodeid, al
        mov     al, DDPio.ddp_socket
        mov     NBPtable.tab_entry.tup_address.ablk_socket, al

        ; print our IP address in our NBP table entry
        mov     bx, offset NBPtable.tab_entry.tup_name
        inc     bx
        xor     dx, dx
        cmp     use_static, 00H
        jne     reg_static1
reg_dynamic1:
        mov     dl, byte ptr IPG.ipg_ipaddress
        jmp     reg_format1
reg_static1:
        mov     dl, byte ptr static_address
reg_format1:
        call    decstr
        mov     al, dot_char
        mov     byte ptr ds:[bx], al
        inc     bx
        cmp     use_static, 00H
        jne     reg_static2
reg_dynamic2:
        mov     dl, byte ptr IPG.ipg_ipaddress+1
        jmp     reg_format2
reg_static2:
        mov     dl, byte ptr static_address+1
reg_format2:
        call    decstr
        mov     al, dot_char
        mov     ds:[bx], al
        inc     bx
        cmp     use_static, 00H
        jne     reg_static3
reg_dynamic3:
        mov     dl, byte ptr IPG.ipg_ipaddress+2
        jmp     reg_format3
reg_static3:
        mov     dl, byte ptr static_address+2
reg_format3:
        call    decstr
        mov     al, dot_char
        mov     ds:[bx], al
        inc     bx
        cmp     use_static, 00H
        jne     reg_static4
reg_dynamic4:
        mov     dl, byte ptr IPG.ipg_ipaddress+3
        jmp     reg_format4
reg_static4:
        mov     dl, byte ptr static_address+3
reg_format4:
        call    decstr

        mov     ax, bx
        sub     ax, offset NBPtable.tab_entry.tup_name
        sub     ax, 1
        mov     NBPtable.tab_entry.tup_name, al

        mov     cx, myip_name_len       ; append IPADDR command to our IP
        movseg  es,cs
        mov     si, offset myip_name    ; ds:si -> source
        mov     di, bx                  ; es:di -> dest
        rep     movsb

        ; Register our name with NBP agent
        mov     NBP.nbp_command, NBP_REGISTER
        mov     NBP.nbp_compfun.offs, 0         ; No completion routine [jgn]
        mov     NBP.nbp_compfun.segm, 0
        mov     NBP.nbp_buffptr.offs, offset NBPtable
        mov     NBP.nbp_buffptr.segm, ds
        mov     NBP.nbp_interval, 01H
        mov     NBP.nbp_retry, 03H
        mov     bx, offset NBP
        call    doATint

        cmp     NBP.nbp_status, 00H
        je      atinit_done

nbp_no_register:
        mov     ax, NBP.nbp_status
        mov     dtemp, ax
        mov     di, offset dtemp
        mov     dx, offset nbp_no_register_msg  ; display error msg
        call    print_number

        mov     DDPio.ddp_command, DDP_CLOSESOCKET
        mov     bx, offset DDPio
        call    doATint                         ; close the assigned socket

        jmp     error_ret                       ; skip ahead to return

;**** LocalTalk PC Card initialized, ready to TSR
atinit_done:
        movseg  ds,cs
        clc
        ret

;**** Got an error while initializing LocalTalk PC Card
error_wrt:                                      ; Display an error message
        movseg  ds,cs
        stc
        ret
error_ret:                              ; Board not initialized
        mov     dx,offset null_msg      ;error message already printed.
        stc
        ret

        public  print_parameters
print_parameters:
;echo our command-line parameters
        ret

;*******************************************

; Modify ATALK.SYS interrupt number in doATint code (self-modifying code!)
ATPatch:
        mov     al, dl                          ; Load new interrupt number
        movseg  es,cs
        lea     bx, doATint                     ; es:bx=offset of doATint code
        inc     bx                              ; skip to operator for INT
        mov     es:[bx], al                     ; modify the code
        ret

; Get ATALK.SYS interrupt number
ATGetInt:
        movseg  es,cs
        lea     bx, doATint                     ; es:bx=offset of doATint code
        inc     bx                              ; skip to operator for INT
        mov     al, es:[bx]                     ; load operator for INT
        xor     ah, ah                          ; zero high byte
        ret                                     ; return INT# to caller

;*******************************************

;*******************************************

; caller must set ds:si -> dest for string, dx 16-bit value to sprint
decstr:
        mov     di,dx
        cmp     dx, 0
        jne     decstr_nonzero
        mov     al,'0'                  ;yes - easier to just print it, than
        jmp     chrstr                  ;  to eliminate all but the last zero.
decstr_nonzero:

        xor     ax,ax                   ;start with all zeroes in al,bx,bp
        mov     bp,ax

        mov     cx,16                   ;16 bits in one 16 bit registers.
decstr_1:
        rcl     di,1
        xchg    bp,ax
        call    addbit
        xchg    bp,ax
        adc     al,al
        daa
        loop    decstr_1

        mov     cl,'0'                  ;prepare to eliminate leading zeroes.
        call    bytestr                 ;output the first two.
        mov     ax,bp
        jmp     wordstr                 ;output the next four.

addbit: adc     al,al
        daa
        xchg    al,ah
        adc     al,al
        daa
        xchg    al,ah
        ret

;print the char in al at ds:bx
chrstr:
        mov     byte ptr [bx], al
        inc     bx
        ret

wordstr:
        push    ax
        mov     al,ah
        call    bytestr
        pop     ax
bytestr:
        mov     ah,al
        shr     al,1
        shr     al,1
        shr     al,1
        shr     al,1
        call    digstr
        mov     al,ah
digstr:
        and     al,0fh
        add     al,90h  ;binary digit to ascii hex digit.
        daa
        adc     al,40h
        daa
        cmp     al,cl                   ;leading zero?
        je      digstr_1
        mov     cl,-1                   ;no more leading zeros.
        jmp     chrstr
digstr_1:
        ret

; caller must set ds:dx -> argument string, ds:di -> AddrBlk struct
print_at_addr:
;enter with dx -> dollar terminated name of number, di ->dword.
;exit with the number printed and the cursor advanced to the next line.
        mov     ah,9                    ;print the name of the number.
        int     21h
        mov     ax, [di].ablk_network   ;print the network number
        xchg    ah, al                  ; byte-swap network number [jgn]
        mov     dx, 00H
        push    di
        call    decout
        pop     di
        mov     al, ':'
        call    chrout
        xor     ax, ax
        mov     al, [di].ablk_nodeid    ; print the nodeid number
        push    di
        call    decout
        pop     di
        mov     al, ':'
        call    chrout
        xor     ax, ax
        mov     al, [di].ablk_socket    ; print the socket number
        call    decout
        mov     al,CR
        call    chrout
        mov     al,LF
        call    chrout
        ret

; caller must set ds:dx -> argument string, ds:di -> 32 bit ip address
print_ip_addr:
;enter with dx -> dollar terminated name of number, di ->dword.
;exit with the number printed and the cursor advanced to the next line.
        mov     ah,9                    ;print the name of the number.
        int     21h
        mov     al, '['
        call    chrout
        xor     ax, ax
        mov     al, [di]                ;print first byte in decimal.
        mov     dx, 00H
        push    di
        call    decout
        pop     di
        mov     al, '.'
        call    chrout
        xor     ax, ax
        mov     al, [di+1]              ; print second byte in decimal
        push    di
        call    decout
        pop     di
        mov     al, '.'
        call    chrout
        xor     ax, ax
        mov     al, [di+2]              ; print third byte in decimal
        push    di
        call    decout
        pop     di
        mov     al, '.'
        call    chrout
        xor     ax, ax
        mov     al, [di+3]              ; print fourth byte in decimal
        call    decout
        mov     al, ']'
        call    chrout
        mov     al,CR
        call    chrout
        mov     al,LF
        call    chrout
        ret

code    ends

        end


