;+
;
; Ethernet communications test loopback server.
;
; By John Wilson.
;
; Usage:
;
; ECTLOOP /I:60
; /I -- INT # of ethernet packet driver (default is to search from 20-FF)
;
; 11/19/94	JMBW	Created.
; 06/22/97	JMBW	Changed to properly use multicast reception instead of
;			promiscuous mode (to test out packet driver).
;
;-
	.radix	8
;
lf=	12
cr=	15
;
timer_low=word ptr 046Ch	;low word of system time
;
mcmax=	100d*6			;max size of multicast list (in bytes)
;
code	segment
	assume	cs:code
	org	100h
start:	cld			;DF=0
	mov	dx,offset banner ;say hello
	mov	ah,09h		;func=print
	int	21h
	; parse command line
	mov	si,80h		;pt at it
	lodsb			;get length
	cbw			;ah=0
	mov	cx,ax		;copy
jcl1:	call	skip		;skip blanks
	jnc	$+5
	 jmp	jcl7
	cmp	al,'/'		;switch?
	je	jcl2
	cmp	al,'-'
	jne	jcl3
jcl2:	inc	si		;skip it
	dec	cx
jcl3:	call	skip		;get switch char
	jc	jcl7
	inc	si		;skip the char
	dec	cx
	and	ax,337		;convert to upper, ah=0
	push	ax		;save
	jcxz	jcl5
	lodsb			;get char
	cmp	al,':'		;: or =
	je	jcl4
	cmp	al,'='
	jne	jcl5
jcl4:	dec	cx		;count it
	call	ghex		;get numeric arg
	pop	di		;restore cmd
	cmp	di,'I'		;<I>nterrupt # of packet driver?
	je	jcl6
jcl5:	mov	dx,offset usage	;pt at msg
	mov	cx,lusage	;length
	call	stderr		;print it
	mov	ax,4C01h	;func=punt
	int	21h
jcl6:	; /I:nn (set INT vector number of packet driver)
	mov	ds:intno,al	;save
	jmp	jcl1
jcl7:	; done, init packet driver
	mov	al,ds:intno	;get int # of packet driver
	or	al,al		;got one?
	jz	init1
	call	probe		;give it a try
	jc	init3
	jmp	short init4
init1:	; check all int #'s in [20h,0FFh]
	mov	al,20h		;starting posn
init2:	push	ax		;save
	call	probe		;give it a try
	pop	ax		;[restore]
	jnc	init4		;got one
	inc	al		;+1
	jnz	init2		;keep going unless it was FF
init3:	; driver not found
	mov	dx,offset nodrv	;pt at msg
	mov	cx,lnodrv
	call	stderr		;print on stderr
	mov	ax,4C01h	;func=punt
	int	21h
init4:	; driver attached
	mov	dx,offset ctrlc	;pt at ^C vector
	mov	ax,2523h	;func=set INT 23h vector
	int	21h
	; display our address
	mov	si,offset us	;point at binary address
	mov	di,offset saddr1 ;point at where it goes
	call	paddr		;convert
	mov	byte ptr [di],cr ;PADDR overwrote the CR
	mov	dx,offset saddr	;pt at msg
	mov	ah,09h		;func=print
	int	21h
;+
;
; Main loop.  Wait for next packet.
;
;-
mloop:	xor	ax,ax		;load 0 into es
	mov	es,ax
	mov	cx,9d		;timeout=approx. 1/2 second
mlp1:	mov	bx,es:timer_low	;get low word of DOS timer
mlp2:	cmp	byte ptr ds:rcvd,0 ;have we received anything?
	jnz	mlp3
	cmp	bx,es:timer_low	;no, see if timer has changed
	je	mlp2		;spin if not
	loop	mlp1		;count the tick
	mov	ah,0Bh		;func=get STDIN status
	int	21h		;(allow user to ^C)
	jmp	short mloop	;loop
mlp3:	; got something
	mov	si,offset buf+14d+2 ;point past skip count
	add	si,[si-2]	;add in count
	add	word ptr [si-2],8d ;(update for looping)
	lodsw			;fetch function
	mov	dl,'R'		;assume we received a reply
	dec	ax		;=1 (reply)?
	jz	mlp4		;yes
	dec	ax		;=2 (forward)?
	jnz	mlp5		;no, drop
	push	ds		;copy DS to ES
	pop	es
	mov	di,offset buf	;set new dest addr
	movsw
	movsw
	movsw
	mov	si,offset us	;pt at our addr
	movsw			;set new source addr
	movsw
	movsw
	mov	si,offset buf	;pt at buffer
	mov	cx,ds:len	;length
	mov	ah,04h		;func=SEND_PKT
	call	pkdrv		;send reply
	mov	dl,'.'		;print .
mlp4:	mov	ah,02h		;func=CONOUT
	int	21h
mlp5:	dec	ds:rcvd		;clear flag (allow more input)
	jmp	short mloop
;+
;
; Frame received.  Ints may be off on entry.
;
; ax	0 (return CX-byte buf at ES:DI) or 1 (CX-byte buf loaded at DS:SI)
; ds	preserved
;
;-
ethin:	pushf			;save flags
	cli			;ints off if they weren't
	test	ax,ax		;;second call?
	jnz	ethin2		;;yes, collect the packet
	; buffer request, alloc it
	cmp	byte ptr cs:rcvd,0 ;;in use?
	jnz	ethin1
	; pack driver spec 1.10 or later needed for lookahead:
	mov	ax,cs		;;set segment
	mov	es,ax
	mov	di,offset buf	;;pt at buffer
	mov	cx,1514d	;;length
	popf
	retf
ethin1:	; drop packet
	xor	di,di		;;load 0:0
	xor	ax,ax
	mov	es,di
	popf
	retf
ethin2:	; buf copied, deal with it
	inc	cs:rcvd		;;set flag
	mov	cs:len,cx	;;save length
	popf
	retf
;+
;
; ^C received.
;
;-
ctrlc:	push	cs		;copy CS to DS
	pop	ds
	mov	dx,offset ccmsg	;pt at msg
	mov	cx,lccmsg	;length
	call	stderr
	cmp	byte ptr ds:initf,0 ;initted?
	jz	ctrlc1
	call	endei		;yes, deinstall
ctrlc1:	mov	ax,4C00h	;func=exit
	int	21h
;+
;
; Print a msg on STDERR.
;
; dx	ptr
; cx	length
;
;-
stderr:	mov	bx,0002h	;handle=stderr
	mov	ah,40h		;func=write
	int	21h
	ret
;+
;
; Check for the existence of a packet driver and install our connection
; to it if it is found.
;
; al	int # (in the range [20h,0FFh])
;
; CF=0 on success.
;
;-
probe:	push	cs		;copy cs
	pop	ds		;to ds
	mov	ds:intno,al	;patch in INT #
	mov	ah,35h		;func=get int vector
	int	21h
	mov	ax,es		;0000:0000?
	or	ax,bx
	jz	prob1
	lea	di,[bx+3]	;pt at signature
	mov	si,offset pktdrv ;pt at string
	mov	cx,9d		;length
	repe	cmpsb		;check for signature
	je	prob2
prob1:	stc			;error return
	ret
prob2:	; found one get handle
	mov	ax,0201h	;func=ACCESS_TYPE, class=DIX ethernet
	mov	bx,-1		;board type=any
	xor	dl,dl		;board #0 (only one per int anyway (?!))
	mov	cx,2		;len(type)=2
	mov	si,offset prot	;pt at type
	mov	di,cs		;copy CS to both
	mov	ds,di
	mov	es,di
	mov	di,offset ethin	;receive entry
	call	pkdrv		;call pktd
	jc	prob3		;punt
	inc	ds:initf	;set flag
	mov	ds:handle,ax	;save handle
	; check capabilities
	mov	bx,ax		;in case old driver
	mov	ax,01FFh	;func=DRIVER_INFO
	call	pkdrv		;call pktd
	jc	prob5		;failed
	cmp	al,2		;need basic+extended functions for multicast
	je	prob4
	cmp	al,6		;basic+high-performance+extended is OK too
	je	prob4		;no good
	; doesn't support extended functions, can't do multicasts
	mov	al,ds:intno	;get vector #
	mov	di,offset notex1 ;point at where it goes
	call	phex
	mov	dx,offset notex	;point at msg
	mov	cx,lnotex
	call	stderr		;print it
	jmp	short prob5	;close handle, die
prob3:	jmp	short prob6
prob4:	; set receive mode to enable multicasts (requires "extended" funcs)
	mov	cx,4		;receive stuff addressed to us, b'cast, multi
	mov	bx,ds:handle	;handle
	mov	ah,14h		;func=SET_RCV_MODE (extended driver function)
	call	pkdrv		;call pktd
	jc	prob5
	; get existing multicast address list
	mov	ah,17h		;func=GET_MULTICAST_LIST (extended drvr func)
	call	pkdrv1		;call pktd, don't preserve ES
	jc	prob5
	push	ds		;swap DS and ES
	push	es
	pop	ds
	pop	es
	cmp	cx,mcmax-6	;must be space for one more
	ja	prob5		;no
	mov	si,di		;point at their list
	mov	di,offset mcbuf	;and our buf
	rep	movsb		;copy into our buf
	push	es		;copy ES back to DS
	pop	ds
	; add our multicast address (ECT loopback assist)
	mov	si,offset mcast	;point at it
	movsw			;copy
	movsw
	movsw
	; write the whole mess back out
	mov	cx,offset mcbuf	;point at address
	sub	di,cx		;find length
	xchg	cx,di		;ES:DI=ptr, CX=length
	mov	ah,16h		;func=SET_MULTICAST_LIST (extended drvr func)
	call	pkdrv		;call pktd
	jc	prob5
	; get hardware addr
	mov	di,offset us	;pt at HA buf (ES loaded above)
	mov	cx,6		;ethernet hardware addr length
	mov	bx,ds:handle	;handle
	mov	ah,06h		;func=GET_ADDRESS
	call	pkdrv		;call pktd
	jnc	prob7		;successful, go return
prob5:	mov	bx,ds:handle	;get handle
	mov	ah,03h		;func=RELEASE_TYPE
	call	pkdrv
prob6:	stc
prob7:	ret
;+
;
; Deinstall our input handler.
;
;-
endei:	mov	bx,ds:handle	;get handle
	mov	ah,03h		;func=RELEASE_TYPE
	call	pkdrv
	; get our addr out of multicast list
	mov	ah,17h		;func=GET_MULTICAST_LIST (extended drvr func)
	call	pkdrv1		;call pktd, don't preserve ES
	jc	endei4		;nothing we can do about this
	push	ds		;swap DS and ES
	push	es
	pop	ds
	pop	es
	cmp	cx,mcmax	;must fit
	ja	endei4		;no, no recovery, sorry
	mov	si,di		;point at their list
	mov	di,offset mcbuf	;and our buf
	mov	dx,cx		;copy
endei1:	sub	dx,6		;more to do?
	jc	endei3		;no
	mov	ax,di		;save ptrs
	mov	bx,si
	mov	di,offset mcast	;point at our address
	mov	cx,6		;length
	repe	cmpsb		;is this one ours?
	je	endei2		;yes, don't copy it (CX=0)
	mov	si,bx		;no, back up source pointer
	mov	cl,6		;set length again
endei2:	mov	di,ax		;restore dest ptr
	rep	movsb		;copy it, or not if CX=0 from REPE CMPSB
	jmp	short endei1	;loop
endei3:	push	es		;copy ES back to DS
	pop	ds
	; write the whole mess back out
	mov	cx,offset mcbuf	;point at address
	sub	di,cx		;find length
	xchg	cx,di		;ES:DI=ptr, CX=length
	mov	ah,16h		;func=SET_MULTICAST_LIST (extended drvr func)
	call	pkdrv		;call pktd (can't recover from err, so ignore)
endei4:	ret
;
pkdrv:	; do INT instruction to call packet driver
	push	es		;save
	call	pkdrv1		;call
	pop	es		;restore
	ret
;
pkdrv1:	; do INT but don't preserve ES
	push	ds		;save
	db	0CDh		;INT nn
intno	db	0		;call the packet driver
	cld			;restore DF
	pop	ds		;restore
	ret
;+
;
; Skip blanks, control chars.
;
; Updates SI, CX, returns CF=1 if EOL.  Otherwise AL=first non-blank char.
;
;-
skip:	jcxz	skip3		;eol already
skip1:	lodsb			;get a byte
	cmp	al,' '		;blank or ctrl char?
	jbe	skip2		;yes
	dec	si		;unget, CF=0 already
	ret
skip2:	loop	skip1		;loop
skip3:	stc			;hit eol
	ret
;+
;
; Get a hex number (up to 48 bits)
;
; si	ptr to input line
; cx	# chars left
;
; dx:bx:ax returns number
;
;-
ghex:	xor	di,di		;init #
	xor	bx,bx
	xor	dx,dx
ghex1:	lodsb			;get a char
	cmp	al,'-'		;ignore embedded '-'
	je	ghex3
	cmp	al,'0'		;digit?
	jb	ghex4
	cmp	al,'9'
	jbe	ghex2		;yes
	and	al,not 40	;cvt to upper
	cmp	al,'A'		;letter?
	jb	ghex4
	cmp	al,'F'
	ja	ghex4
	sub	al,7		;make A follow 9
ghex2:	sub	al,'0'		;convert to binary
	and	ax,0Fh		;isolate new digit
	push	cx		;save
	mov	cl,4		;bit count
	rol	di,cl		;make space
	rol	bx,cl
	sal	dx,cl
	mov	cx,di		;get lowest
	and	di,0FFF0h	;make space for new digit
	xor	cx,di		;isolate old one
	or	di,ax		;OR in new digit
	mov	al,bl		;get middle
	and	bl,0F0h		;make space for new digit
	xor	al,bl		;isolate old one
	or	bl,cl		;OR in new digit
	or	dl,al		;OR in new digi
	pop	cx		;restore
ghex3:	loop	ghex1		;around for more
ghex4:	dec	si		;unget (or not if off end, doesn't matter)
	mov	ax,di		;get low digits
	ret
;+
;
; Put 2-digit hex # in AL into buf at ES:DI, incrementing DI.
;
;-
phex:	mov	ah,al		;copy
	mov	cl,4		;shift count
	shr	ah,cl		;right 4
	and	al,0Fh		;isolate
	mov	bx,offset hextab ;pt at table
	xlat			;translate low nibble
	xchg	al,ah		;swap
	xlat			;translate high nibble
	stosw			;save both
	ret
;+
;
; Convert ethernet addr at DS:SI to hex at ES:DI.
;
;-
paddr:	mov	cx,6		;byte count
	mov	dx,di		;copy addr
paddr1:	lodsb			;get a byte
	push	cx		;save
	call	phex		;print it
	mov	al,'-'		;add '-'
	stosb
	pop	cx		;restore
	loop	paddr1		;loop
	dec	di		;unput final '-'
	mov	cx,17d		;length
	ret
;
	subttl	pure data
;
banner	db	'ECTLOOP  V1.1  Ethernet Communications Test loopback server'
	db	cr,lf
	db	'By John Wilson <wilson@dbit.com>',cr,lf
	db	'$'
usage	db	'Usage:  ECTLOOP [/I:(hex int no of packet driver)]',cr,lf
lusage=	$-usage
;
nodrv	db	'?Unable to attach to packet driver',cr,lf
lnodrv=	$-nodrv
;
ccmsg	db	'Exiting...',cr,lf
lccmsg=	$-ccmsg
;
hextab	db	'0123456789ABCDEF'
;
pktdrv	db	'PKT DRVR',0	;signature to match
;
prot	db	90h,00h		;prot to look for (ECT=90-00)
mcast	db	0CFh,0,0,0,0,0	;loopback assist addr (CF-00-00-00-00-00)
;
	subttl	impure data
;
notex	db	'?Packet driver at INT '
notex1	db	'XX does not support extended driver functions',cr,lf
lnotex=	($-notex)
;
initf	db	0		;NZ => pktd initted (handle allocated)
;
rcvd	db	0		;set NZ when valid packet received
				;cleared when buf is once again available
;
saddr	db	'Station address:  '
saddr1	db	'aa-bb-cc-dd-ee-ff'
	db	cr,lf,'$'	;CR gets overwritten by PADDR
;
	subttl	pure storage
;
handle	dw	1 dup(?)	;handle for our packet type
us	db	6 dup(?)	;our addr
len	dw	1 dup(?)	;length of received packet
;
; received packet buffer
buf	db	1514d dup(?)	;received packet buffer
;
mcbuf	db	mcmax dup(?)	;multicast address list buf
;
code	ends
	end	start
