version	equ	0

	include	defs.asm

;  Copyright, 1988, 1989, Russell Nelson

;   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.
;
;   You should have received a copy of the GNU General Public License
;   along with this program; if not, write to the Free Software
;   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

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

	org	2ch
phd_env	label	word

	org	5ch
phd_fcb1	label	byte

	org	6ch
phd_fcb2	label	byte

	org	80h
phd_dioa	label	byte

	org	100h
start:
	jmp	start_1

stack	label	byte

comment /

Plan:

Keep a circular queue of events.  The size of the queue is a settable
parameter.  Discard events that fall off the end.  Remember how many events
were discarded.  Remember when the events occurred.

Type of events to remember:
	calls to the packet driver
	upcalls to the receiver handler (both kinds)

/

; In addition to the function numbers specified in the FTP Software
; packet driver spec, the following pseudo-functions are defined:

EVENT_RECEIVE	equ	255		;receiver upcall.

event_struc	struc
event_length	dw	?		;length of this event.
event_function	db	?		;the event function number.
event_time	dw	?,?		;timer tick at the time of the call.
event_error	db	?		;set to value of dh after the SWI.
event_struc	ends

di_struc	struc
		db	(size event_struc) dup (?)
di_version	dw	?
di_class	db	?
di_type		dw	?
di_number	db	?
di_basic	db	?
di_struc	ends

at_struc	struc
		db	(size event_struc) dup (?)
at_if_class	db	?
at_if_type	dw	?
at_if_number	db	?
at_typelen	dw	?
at_handle	dw	?
at_struc	ends

handle_struc	struc
		db	(size event_struc) dup (?)
event_handle	dw	?
handle_struc	ends

ga_struc	struc
		db	(size event_struc) dup (?)
ga_handle	dw	?
ga_length	dw	?
ga_struc	ends


queue_length	dw	10000,?		;length of the queue.
queue_tail	dw	?		;points after the last item in the queue.
queue_ptr	dw	?		;points to the new item in the queue.
queue_head	dw	?		;points to the first item in the queue.
queue_end	dw	?		;points to the end of the queue, but
					;there is room for one more event after
					;this one.

packet_int_no	db	?,0,0,0

parm	dw	0
parm2	dw	their_dioa,?
parm3	dw	phd_fcb1,?
parm4	dw	phd_fcb2,?

comspec_env_str	db	"COMSPEC="
comspec_env_len	equ	$-comspec_env_str

program		db	64 dup(?)
their_dioa	db	0,0dh,128-2 dup(?)

saved_ax	label	word
saved_al	db	?
saved_ah	db	?
saved_bx	dw	?
saved_ds	dw	?
saved_f		dw	?

functions	label	word
	dw	f_driver_info		;function 1
	dw	f_access_type
	dw	f_release_type
	dw	f_send_pkt
	dw	f_terminate
	dw	f_get_address
	dw	f_reset_interface	;function 7
	dw	f_set_rcv_mode		;function 20
	dw	f_get_rcv_mode
	dw	set_multicast_list
	dw	get_multicast_list
	dw	f_get_statistics
	dw	f_set_address		;function 25

their_isr	dd	?

our_isr:
	jmp	our_isr_0		;the required signature.
signature	db	'PKT DRVR',0
signature_len	equ	$-signature

our_isr_0:
	assume	ds:nothing
	mov	saved_ds,ds
	mov	saved_bx,bx
	mov	saved_ax,ax
	cld

	mov	bx,sp
	mov	bx,ss:[bx+4]		;get the original flags.
	mov	saved_f,bx

	mov	bx,cs			;set up ds.
	mov	ds,bx
	assume	ds:code
	mov	bl,ah			;jump to the correct function.
	mov	bh,0
	cmp	bx,7			;highest function is 7.
	jbe	our_isr_3
	cmp	bx,20
	jb	our_isr_bad
	cmp	bx,25
	ja	our_isr_bad
	sub	bx,20-7-1		;map 20 right after 7.
our_isr_3:
	add	bx,bx			;*2
	jmp	functions-2[bx]		;table starts at 1.

our_isr_bad:
	call	do_their_isr
	jmp	our_isr_done

f_driver_info:
	mov	bx,(size di_struc)
	call	queue_advance
	call	do_their_isr
	jc	f_driver_info_1

	mov	ax,saved_bx
	mov	[bx].di_version,ax
	mov	[bx].di_class,ch
	mov	[bx].di_type,dx
	mov	[bx].di_number,cl
	mov	al,saved_al
	mov	[bx].di_basic,al
;we ignore the name -- too much work.
f_driver_info_1:
	jmp	our_isr_done

f_get_statistics:
;strictly speaking, we should remember the statistics, but I'm not going to now.
f_terminate:
f_reset_interface:
f_release_type:
	mov	bx,(size handle_struc)
	call	queue_advance
	mov	ax,saved_bx
	mov	[bx].event_handle,ax
	call	do_their_isr
	jmp	our_isr_done

f_access_type:
	mov	bx,(size at_struc)
	call	queue_advance
	mov	al,saved_al
	mov	[bx].at_if_class,al
	mov	ax,saved_bx
	mov	[bx].at_if_type,ax
	mov	[bx].at_if_number,dl
	mov	[bx].at_typelen,cx
	mov	their_recv.segm,es
	mov	their_recv.offs,di
	mov	ax,cs			;and stick our receiver in.
	mov	es,ax
	mov	di,offset our_recv
	call	do_their_isr
	jc	f_access_type_1
	mov	ax,saved_ax
	mov	[bx].at_handle,ax
f_access_type_1:
	les	di,their_recv		;now restore ds and si.
	jmp	our_isr_done


f_send_pkt:
	mov	bx,(size event_struc)
	add	bx,cx
	call	queue_advance
;make a copy of their packet.
	push	cx
	push	si
	push	di
	push	ds
	push	es
	lea	di,[bx] + (size event_struc)
	mov	ax,cs
	mov	es,ax
	mov	ds,saved_ds
	rep	movsb
	pop	es
	pop	ds
	pop	di
	pop	si
	pop	cx
	call	do_their_isr
	jmp	our_isr_done

f_get_address:
	mov	bx,(size ga_struc)
	add	bx,cx
	call	queue_advance
	mov	ax,saved_bx		;save their handle
	mov	[bx].ga_handle,ax
	call	do_their_isr
	jc	f_get_address_1
;make a copy of their address.
	mov	[bx].ga_length,cx	;we need to save this because it
					;might be less than the total allocated.
	push	cx
	push	si
	push	di
	push	ds
	push	es
	mov	si,di			;get es:di into ds:si
	mov	ax,es
	mov	ds,ax
	lea	di,[bx] + (size ga_struc)	;get our pointer into es:di.
	mov	ax,cs
	mov	es,ax
	rep	movsb
	pop	es
	pop	ds
	pop	di
	pop	si
	pop	cx
f_get_address_1:
	jmp	our_isr_done


f_set_rcv_mode:
f_get_rcv_mode:
set_multicast_list:
get_multicast_list:
f_set_address:
	mov	bx,(size event_struc)
	call	queue_advance
	call	do_their_isr

our_isr_done:
	push	saved_f			;restore their flags, see [2]
	popf

	mov	ax,saved_ax
	mov	bx,saved_bx
	mov	ds,saved_ds		;restore the two registers we destroyed.
	assume	ds:nothing

foobar	proc	far			;masm 4.0 is really stupid.
	ret	2			;return, popping their old flags, [2].
foobar	endp
	assume	ds:code


;do_their_isr executes their isr with the original registers.
;called with all their registers except f, ds, and bx.
;exits with all their registers except ds, ax, and bx.  bx is queue_ptr
do_their_isr:

;setup their context.
	mov	ax,saved_f		;restore their flags, see [1]
	and	ax,not 200h		;clear the interrupt flag, as required
	push	ax			;  when faking an interrupt.
	mov	ax,saved_ax
	mov	bx,saved_bx
	mov	ds,saved_ds		;restore the two registers we destroyed.
	assume	ds:nothing

;	[1] we pushed the flags earlier.
	call	their_isr		;now fake their interrupt.

;save their context.
	mov	saved_ax,ax		;save the new registers.
	mov	saved_bx,bx
	mov	saved_ds,ds
	mov	ax,cs			;set up a pointer to the next event.
	mov	ds,ax
	assume	ds:code
	pushf				;save the new flags.
	pop	ax
	and	saved_f,200h		;merge the interrupt flag in saved_f
	or	saved_f,ax		;  with the new flags.

;remember whether it succeeded or not.
	mov	bx,queue_ptr
	mov	[bx].event_error,NO_ERROR	;assume that all was okay.
	jnc	our_isr_1
	mov	[bx].event_error,dh	;it wasn't.
our_isr_1:
	ret


their_recv	dd	?

our_recv:
	assume	ds:nothing
	mov	saved_ds,ds
	mov	saved_bx,bx
	mov	saved_ax,ax
	cld

	mov	bx,cs			;set up ds.
	mov	ds,bx
	assume	ds:code

	or	ax,ax			;first call or second?
	je	our_recv_first

	mov	bx,(size handle_struc)
	add	bx,cx
	call	queue_advance
	mov	[bx].event_function,EVENT_RECEIVE	;not a real function.
	mov	[bx].event_error,0		;no errors possible.
	mov	ax,saved_bx
	mov	[bx].event_handle,ax	;remember which handle it was.

	push	cx
	push	si
	push	di
	push	ds
	push	es
	lea	di,[bx] + (size handle_struc)
	mov	ax,cs
	mov	es,ax
	mov	ds,saved_ds
	rep	movsb
	pop	es
	pop	ds
	pop	di
	pop	si
	pop	cx

	jmp	short our_recv_done
our_recv_first:
;ignore the first upcall.
our_recv_done:
	mov	ax,saved_ax
	mov	bx,saved_bx
	mov	ds,saved_ds		;restore the two registers we destroyed.
	assume	ds:nothing

	jmp	their_recv
	assume	ds:code


queue_advance:
;enter with bx = number of bytes that we require in the queue.
;exit with bx,queue_ptr set to a pointer to our entry.
;preserve everything but ds, bx, and the flags.
queue_advance_3:
	mov	ax,queue_tail
	add	ax,bx
	cmp	ax,queue_head		;if we don't overlap the head, we're
	jbe	queue_advance_1		;  okay.

	xchg	bx,queue_head		;get queue_head and save bx.
	add	bx,[bx].event_length
	cmp	bx,queue_end		;see if we hit the end.
	xchg	queue_head,bx		;store queue_head and restore bx.
	jb	queue_advance_3		;if we're less than the end, continue.
;we have to wrap here.
	push	bx
	mov	bx,queue_tail
	mov	ax,queue_end		;make an event length that's too large.
	sub	ax,offset queue_begin
	mov	[bx].event_length,ax
	mov	bx,offset queue_begin	;and restart from the beginning.
	mov	queue_tail,bx		;ensure that we nuke some more.
	mov	queue_head,bx
	pop	bx
	jmp	queue_advance_3
queue_advance_1:
	xchg	ax,queue_tail		;update the tail and get this ptr.
	mov	queue_ptr,ax		;save this pointer.
	xchg	ax,bx
	mov	[bx].event_length,ax	;store the length of this entry here.

	mov	ah,saved_ah		;store their function value.
	mov	[bx].event_function,ah

;remember when it happened.
	push	dx
	push	ds
	mov	ax,40h
	mov	ds,ax
	mov	ax,ds:6ch		;get the timer tick count.
	mov	dx,ds:6eh
	pop	ds
	mov	[bx].event_time+0,ax
	mov	[bx].event_time+2,dx
	pop	dx

	ret


copyleft_msg	label	byte
 db "Packet driver tracer version ",majver+'0','.',version+'0'," copyright 1988-89, Russell Nelson.",CR,LF
 db "This program is free software; see the file COPYING for details.",CR,LF
 db "NO WARRANTY; see the file COPYING for details.",CR,LF
crlf_msg	db	CR,LF,'$'

packet_int_no_name	db	"Packet interrupt number ",'$'
buffer_size_name	db	"Buffer size ",'$'

before_exec_msg	db	"Now run your network software and type 'exit' when finished",CR,LF,'$'
run_dump_msg	db	"Now run 'dump' to interpret 'trace.out'",CR,LF,'$'

disk_full_msg	db	"Disk Full!",'$'

already_msg	db	CR,LF,"There is no packet driver at ",'$'
packet_int_msg	db	CR,LF
		db	"Error: <packet_int_no> should be in the range 0x60 to 0x80"
		db	'$'

usage_msg	db	"usage: trace packet_int_no <buffer_size>",'$'

queue_error_msg	db	"Error: <buffer_size> should be larger than 2000 and less than 64000",'$'

trace_out	db	"TRACE.OUT",0	;filename that we write the dump to.

HT	equ	09h
CR	equ	0dh
LF	equ	0ah

usage_error:
	mov	dx,offset usage_msg
error:
	mov	ah,9
	int	21h
	int	20h

already_error:
	mov	dx,offset already_msg
	mov	di,offset packet_int_no
	call	print_number
	int	20h

start_1:
	mov	sp,offset stack

	mov	dx,offset copyleft_msg
	mov	ah,9
	int	21h

	mov	si,offset phd_dioa+1
	cmp	byte ptr [si],CR	;end of line?
	je	usage_error

	mov	di,offset packet_int_no	;parse the packet interrupt number
	mov	bx,offset packet_int_no_name
	call	get_number		;  for them.

	mov	di,offset queue_length	;parse the packet interrupt number
	mov	bx,offset buffer_size_name
	call	get_number		;  for them.

	cmp	byte ptr [si],CR	;end of line?
	jne	usage_error

	cmp	queue_length+2,0
	jne	start_3
	cmp	queue_length,2000
	ja	start_2
start_3:
	mov	dx,offset queue_error_msg
	jmp	error
start_2:

;initialize the queue
	mov	bx,offset queue_begin
	mov	queue_tail,bx
	add	bx,queue_length
	mov	queue_end,bx		;initialize the head of the queue.
	mov	[bx].event_length,1	;anything >0 will ensure that we're >end.
	mov	queue_head,bx

;do some error checking.
	mov	dx,offset packet_int_msg;make sure that the packet interrupt
	cmp	packet_int_no,60h	;  number is in range.
	jb	error
	cmp	packet_int_no,80h
	ja	error

	mov	ah,35h			;get their packet interrupt.
	mov	al,packet_int_no
	int	21h

	lea	di,3[bx]		;see if there is already a signature
	mov	si,offset signature	;  there.
	mov	cx,signature_len
	repe	cmpsb
	je	start_4			;yes, so we can trace it.
	jmp	already_error		;no, give them an error.
start_4:

	mov	ah,35h			;remember their packet interrupt.
	mov	al,packet_int_no
	int	21h
	mov	their_isr.offs,bx
	mov	their_isr.segm,es

	mov	ah,25h			;install our packet interrupt
	mov	dx,offset our_isr
	int	21h

	mov	dx,offset before_exec_msg
	mov	ah,9
	int	21h

;
; Now free the memory we don't need.
;
	mov	bx,queue_end
	add	bx,size event_struc	;leave room for one more.
	add	bx,0fh			;round up to next highest paragraph.
	mov	cl,4
	shr	bx,cl
	push	cs
	pop	es
	mov	ah,4ah
	int	21h

; Now we execute command.com

	mov	ax,cs
	mov	word ptr parm2+2,ax
	mov	word ptr parm3+2,ax
	mov	word ptr parm4+2,ax

	mov	si,offset their_dioa+1	;re-parse the two fcbs.
	mov	di,offset phd_fcb1
	push	ds
	pop	es
	mov	ax,2901h
	int	21h

	mov	di,offset phd_fcb2
	mov	ax,2901h
	int	21h

	mov	si,offset comspec_env_str	;see if this is the one.
	mov	cx,comspec_env_len
	mov	di,offset program
	call	getenv

	mov	ah,4bh
	mov	bx,offset parm
	mov	dx,offset program
	mov	al,0
	int	21h

	mov	bx,cs			;restore our segment registers.
	mov	ds,bx
	mov	es,bx
	mov	ss,bx
	mov	sp,offset stack

; Give up our packet interception.

	mov	al,packet_int_no	;release our_isr.
	mov	ah,25h
	push	ds
	lds	dx,their_isr
	int	21h
	pop	ds

; Now we write our captured information out to disk.

	mov	dx,offset trace_out	;create "trace.out".
	mov	ah,3ch
	mov	cx,0
	int	21h

	mov	bx,ax

	mov	si,queue_head
write_out:
	mov	dx,si
	mov	cx,[si].event_length	;write this event out.
	add	si,cx			;is this the end of the queue?
	cmp	si,queue_end		;
	ja	write_out_1

	mov	ah,40h
	int	21h
	cmp	ax,cx
	jne	write_out_full

	jmp	write_out

write_out_1:
	mov	si,offset queue_begin	;yes.
write_out_2:
	cmp	si,queue_tail		;quit when we hit the tail.
	jae	write_out_3

	mov	dx,si			;set dx for the file write below.
	mov	cx,[si].event_length	;write this event out.
	add	si,cx

	mov	ah,40h
	int	21h
	cmp	ax,cx
	jne	write_out_full

	jmp	write_out_2
write_out_3:
	mov	ah,3eh			;close the file.
	int	21h

	mov	dx,offset run_dump_msg
	mov	ah,9
	int	21h

	int	20h

write_out_full:
	mov	ah,9
	mov	dx,offset disk_full_msg
	int	21h
	int	20h


get_number:
	mov	bp,10			;we default to 10.
	jmp	short get_number_0

get_hex:
	mov	bp,16
;get a hex number, skipping leading blanks.
;enter with si->string of digits,
;	bx -> dollar terminated name of number,
;	di -> dword to store the number in.  [di] is not modified if no
;		digits are given, so it acts as the default.
;return cy if there are no digits at all.
;return nc, bx:cx = number, and store bx:cx at [di].
get_number_0:
	push	bx			;remember the name of this number.
	call	skip_blanks
	call	get_digit		;is there really a number here?
	jc	get_number_3
	or	al,al			;Does the number begin with zero?
	jne	get_number_4		;no.
	mov	bp,8			;yes - they want octal.
get_number_4:

	xor	cx,cx			;get a hex number.
	xor	bx,bx
get_number_1:
	lodsb
	cmp	al,'x'			;did they really want hex?
	je	get_number_5		;yes.
	cmp	al,'X'			;did they really want hex?
	je	get_number_5		;yes.
	call	get_digit		;convert a character into an int.
	jc	get_number_2		;not a digit (neither hex nor dec).
	xor	ah,ah
	cmp	ax,bp			;larger than our base?
	jae	get_number_2		;yes.

	push	ax			;save the new digit.

	mov	ax,bp			;multiply the low word by ten.
	mul	cx
	mov	cx,ax			;keep the low word.
	push	dx			;save the high word for later.
	mov	ax,bp
	mul	bx
	mov	bx,ax			;we keep only the low word (which is our high word)
	pop	dx
	add	bx,ax			;add the high result from earlier.

	pop	ax			;get the new digit back.
	add	cx,ax			;add the new digit in.
	adc	bx,0
	jmp	get_number_1
get_number_5:
	mov	bp,16			;change the base to hex.
	jmp	get_number_1
get_number_2:
	dec	si
	mov	[di],cx			;store the parsed number.
	mov	[di+2],bx
	clc
	jmp	short get_number_6
get_number_3:
	stc
get_number_6:
	pop	dx			;get the name of the number back.

	pushf				;save some stuff.
	push	bx
	push	cx
	push	si
	push	di
	call	print_number
	pop	di
	pop	si
	pop	cx
	pop	bx
	popf
	ret


print_number:
;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,'0'
	call	chrout
	mov	al,'x'
	call	chrout
	mov	ax,[di]			;print the number in hex.
	mov	dx,[di+2]
	call	hexout
	mov	al,' '
	call	chrout
	mov	al,'('
	call	chrout
	mov	ax,[di]			;print the number in decimal.
	mov	dx,[di+2]
	call	decout
	mov	al,')'
	call	chrout
	mov	al,CR
	call	chrout
	mov	al,LF
	call	chrout
	ret


skip_blanks:
	lodsb				;skip blanks.
	cmp	al,' '
	je	skip_blanks
	cmp	al,HT
	je	skip_blanks
	dec	si
	ret


get_digit:
;enter with al = character
;return nc, al=digit, or cy if not a digit.
	cmp	al,'0'			;decimal digit?
	jb	get_digit_1		;no.
	cmp	al,'9'			;. .?
	ja	get_digit_2		;no.
	sub	al,'0'
	clc
	ret
get_digit_2:
	or	al,20h
	cmp	al,'a'			;hex digit?
	jb	get_digit_1
	cmp	al,'f'			;hex digit?
	ja	get_digit_1
	sub	al,'a'-10
	clc
	ret
get_digit_1:
	stc
	ret


hexout:
	mov	cl,'0'			;prepare to eliminate leading zeroes.
	xchg	ax,dx			;just output 32 bits in hex.
	call	wordout			;output dx.
	xchg	ax,dx
	jmp	wordout			;output ax.

decout:
	mov	si,ax			;get the number where we want it.
	mov	di,dx

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

	mov	cx,32			;32 bits in two 16 bit registers.
decout_1:
	shl	si,1
	rcl	di,1
	xchg	bp,ax
	call	addbit
	xchg	bp,ax
	xchg	bx,ax
	call	addbit
	xchg	bx,ax
	adc	al,al
	daa
	loop	decout_1

	mov	cl,'0'			;prepare to eliminate leading zeroes.
	call	byteout			;output the first two.
	mov	ax,bx			;output the next four
	call	wordout			;output the next four
	mov	ax,bp
wordout:
	push	ax
	mov	al,ah
	call	byteout
	pop	ax
byteout:
	mov	ah,al
	shr	al,1
	shr	al,1
	shr	al,1
	shr	al,1
	call	digout
	mov	al,ah
digout:
	and	al,0fh
	add	al,90h	;binary digit to ascii hex digit.
	daa
	adc	al,40h
	daa
	cmp	al,cl			;leading zero?
	je	return
	mov	cl,-1			;no more leading zeros.
chrout:
	push	ax			;print the char in al.
	xchg	al,dl
	mov	ah,2
	int	21h
	xchg	al,dl
	pop	ax
return:
	ret


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


getenv:
;enter with ds:si -> environment string to look for, cx = length of string,
;  ds:di -> place to put the string's value.
	push	es
	push	di			;remember where we're supposed to put it.

	mov	es,phd_env		;search the environment.
	xor	di,di

getenv_2:
	cmp	byte ptr es:[di],0	;see if we're at the end.
	je	getenv_0

	push	cx
	push	si
	push	di
	repe	cmpsb
	pop	di
	pop	si
	je	getenv_3
	mov	cx,-1			;skip to the next null.
	xor	al,al
	repne	scasb
	pop	cx
	jmp	getenv_2
getenv_3:
;copy the environment string to current_dir.
	pop	cx
	add	di,cx			;go to the end of the string.
	pop	si			;pushed as di, -> place to put the string.
getenv_4:
	mov	al,es:[di]
	mov	[si],al
	inc	di
	inc	si
	or	al,al
	jne	getenv_4
	dec	si			;point si to the null again.
	pop	es
	clc
	ret
getenv_0:
	add	sp,2
	pop	es
	stc
	ret


end_code	label	byte

queue_begin	label	byte

code	ends

	end	start
