	.title	tcpwatch
	.ident	"X1-002"

;+
; Version:	X1-002
;
; Facility:	Diagnostic Utilities.
;
; Abstract:	This is similar to LATWATCH but will only process TCP/IP
;		protocol messages.
;
; Environment:	PHY_IO privilege is needed.
;
;	The following is the CLD used in TCPWATCH:
;
; module tcpwatch_cld
;
; define verb tcpwatch
;	noparameters
;	qualifier debug
;	qualifier device,	value (required)
;	qualifier display,	default, value (type = display_options)
;	qualifier both
;	qualifier from,		default, value (default = "*")
;	qualifier to,		default, value (default = "*")
;	qualifier nonames
;	qualifier begin,	value (required, type = $datetime)
;	qualifier end,		value (required, type = $datetime)
;	qualifier count,	value (required, type = $number)
;	qualifier output,
;			value (type = $outfile, default = "TCPWATCH.LOG")
;	qualifier record,
;			value (type = $outfile, default = "TCPWATCH.RECORD")
;	qualifier playback,
;			value (type = $infile, default = "TCPWATCH.RECORD")
;	qualifier header
;
;	disallow (end and count)
;	disallow (record and playback)
;	disallow (playback and (begin or end))
;
; define type display_options
;	keyword	all
;	keyword both
;	keyword	none
;	keyword ascii, default
;	keyword	text
;	keyword	hexadecimal
;	keyword fast
;
; History:
;
;	26-Aug-1994, DBS; Version X1-001
; 001 -	Original version.  (Taken from LATWATCH.)
;	02-Sep-1994, DBS; Version X1-002
; 002 -	Added /HEADER to show the ethernet header stuff which is turned off
;	by default.
;-

	.subtitle Psect definitions, external definitions

	.library	"SYS$LIBRARY:LIB.MLB"
	.library	"SYS$LIBRARY:STARLET.MLB"
	.library	"DBSLIBRARY:SYS_MACROS.MLB"
	.link		"SYS$SYSTEM:SYS.STB" /selective_search

	.disable global

	.external	cli$dcl_parse
	.external	cli$get_value
	.external	cli$present
	.external	lan_format_header
	.external	lan_startup_prm
	.external	tcpwatch_cld
	.external	lib$free_vm
	.external	lib$get_foreign
	.external	lib$get_vm
	.external	lib$init_timer
	.external	lib$lookup_key
	.external	lib$show_timer
	.external	lib$show_vm
	.external	lib$signal
	.external	lib$stop
	.external	lib_cvt_t_l
	.external	lib_output_seg_t
	.external	lib_output_seg_tzb
	.external	lib_output_seg_zb
	.external	str$match_wild
	.external	str_collapse
	.external	str_len
	.external	str_uppercase
	.external	sys_find_ether_device
	.external	sys_trap_controlc

	enb_long			; for structured macro stuff
	_landef
	_lanhdrdef
	$clidef
	$climsgdef
	$fabdef
	$iodef
	$lnmdef
	$psldef
	$rabdef
	$rmsdef
	$ssdef
	$stsdef

	def_psect _util_data, type=DATA, alignment=LONG
	def_psect _util_code, type=CODE, alignment=LONG

	.subtitle Local macros

; .macro IF_PRESENT
; All we do here is set bits in cli_defaulted and cli_present to indicate
; which qualifiers were used and whether or not they used the default.
; This information is used later for matching packet information against
; what the user wants to see.

.macro if_present item

	call cli$present cli_'item
	if <eql,#cli$_defaulted,r0> then
	  bisl	#m_'item, cli_defaulted
	else
	if <eql,#cli$_present,r0> then
	  bisl	#m_'item, cli_supplied
	endif
	endif

.endm if_present

.macro vector location, value

	.address location
	.long	value

.endm vector

.macro check_protocol type, ?next

	cmpb	#apn_'type, r9
	bnequ	next
	call	display_'type'_packet
	brw	exit1
next:

.endm check_protocol

.macro swapbl source, destination

	.if blank source
	  .error 0 ; Input argument missing from SWAPBL
	  .mexit
	.endc
	.if blank destination
	  .error 0 ; Output argument missing from SWAPBL
	  .mexit
	.endc
	.if identical source,R0
	  .error 0 ; Input argument for SWAPBL cannot be R0
	  .mexit
	.endc

	movl	source, r0
	rotl	#8, r0, -(sp)
	movb	r0, 3(sp)
	extzv	#16, #8, r0, r0
	movb	r0, 1(sp)
	movl	(sp)+, destination

.endm swapbl

.macro swapbw source, destination

	.if blank source
	  .error 0 ; Input argument missing from SWAPBW
	  .mexit
	.endc
	.if blank destination
	  .error 0 ; Output argument missing from SWAPBW
	  .mexit
	.endc
	.if identical source,R0
	  .error 0 ; Input argument for SWAPBW cannot be R0
	  .mexit
	.endc

	extzv	#8, #8, source, r0
	extzv	#0, #8, source, r1
	ashl	#8, r1, r1
	bisl2	r1, r0
	movl	r0, destination

.endm swapbw

.macro def_protocol_name name

	movl	#apn_'name, r0
	movab	name_'name, protocol_names[r0]

.endm def_protocol_name

	.subtitle Impure data areas

	set_psect _util_data

program_id:	.ascid	"TCPWATCH  X1-002"
loaded_fao:	.ascid	"!UL names and addresses were loaded"
intro_line1:	.ascid	"Starting a watch on device !AS"
intro_line2:	.ascid	"TCP/IP packets from !AS [!AS] to !AS [!AS]!/"-
			"Display option is !AS"
playing_back:	.ascid	"Reading recorded data from !AS"
display_header:	.ascid	"!78*-!/From !AS [!AC] to !AS [!AC]"-
			"!/ !4UW byte buffer at !%D"
wildcard:	.ascid	"*"
null_ascic:	.ascic	""
empty_string:	.ascid	" -----"
vm_allocated:	.ascid	"LIB$GET_VM allocated !UL bytes at !XL"
tally:		.ascid	"Of the !UL TCP/IP packets read "-
			"(!UL total), !UL packets were !AS"
displayed:	.ascid	"displayed"
recorded:	.ascid	"recorded"
faol_prmlst:	.blkl	40
		.long	^XFFBADBAD

ip_header:
	.ascid	"IP Header: Protocol!4UB !19<<!AC>!> "-
			"!UB.!UB.!UB.!UB -> !UB.!UB.!UB.!UB!/"-
		" Chksum !XW  Timer !UB sec!%S  DG-ID !XW"-
		"  Ver !UB  IHL !UB  ServType !UB  DG-Len !UW"
tcp_header:
	.ascid	"TCP Header: Port !UW -> !UW  Seq/Ack !XL/!XL!/"-
		" Checksum !XW  Junk !XL !XW"
udp_header:
	.ascid	"UDP Header: Port !UW -> !UW  DG-Length !UW  Checksum !XW"
icmp_header:
	.ascid	"ICMP Header: Type !UB  Code !UB  Checksum !XW"

	alloc_string	_faobuf, 2048	; used for all output - needs to
					;  be big enough to hold an ethernet
					;  packet (in case /disp=fast is used)
	alloc_string	command, 256

; Here we have to fudge the command line by loading the verb and then
; tacking on whatever the user specifies to the end of it.  This we can
; then pass on the the cli routines.

command_buffer:		.long	command_buffer_extra-command_buffer_t
			.address command_buffer_t
command_buffer_t:	.ascii	"TCPWATCH"
command_buffer_extra:	.blkb	<8+256>

; Some general bits and pieces

	tcp_protocol = ^X0008

iosb:			.word	0
iosb_xfr_size:		.word	0
iosb_unused1:		.byte	0
iosb_status:		.byte	0
iosb_error_summary:	.byte	0
iosb_unused2:		.byte	0
log_channel:		.long	0
packets_read:		.long	0	; used for some stats on the way
tcp_packets_read:	.long	0	;  out...
packets_displayed:	.long	0	;  ...
stats_context:		.long	0	; used by lib$init/show_timer

; These next bits are used to convert decnet node numbers in the format
; area.node to aa type addresses

area_number:		.long	0
node_number:		.long	0
area_number_s:		.long	0
area_number_addr:	.long	0
node_number_s:		.long	0
node_number_addr:	.long	0
aa_format:		.ascid	"AA-00-04-00-!XB-!XB"

; The following definitions specifiy the sizes of the text representations
; of the various fields that are used.

	s_address = 17			; xx-xx-xx-xx-xx-xx
	s_name = 32			; maximum allowed name size
	s_sap = 23
	s_protocol = 5

; The next bits are used to set up some virtual memory to contain the
; name and address lists (for use by lib$lookup_key) and pointers to the
; lists and data area.

vm_bytecount:		.long	0	; used by lib$get_vm/lib$free_vm
vm_base_address:	.long	0
address_vector_size:	.long	0	; filled in later
name_vector_size:	.long	0
data_area_size:		.long	0

	max_nodecount = 30000		; we won't load any more than this
					;  many nodes from the nodelist

user_nodecount:		.long	0	; this is from reading nodelist
node_count:		.long	0	; this is how many real nodes we got
address_vector:		.long	0	; these will contain pointers to
name_vector:		.long	0	;  the memory allocated by lib$get_vm
name_address_data:	.long	0

; These are some dummy headers that we need for various routines to point
; to the header and data buffers.

packet_header_ds:	.long	lanhdr_s_lanhdrdef
packet_header_addr:	.address packet_header
packet_data_ds:		.long	lan_s_ethernet
packet_data_addr:	.address packet_data

; This is the buffer where the header and data are returned by $qio

packet_length:	.long	0		; length of received packet

; The following record buffer structure must remain intact...
; When /RECORD is used, the packet_buffer gets loaded by the $QIO, we then
; load the record type and date/time then write it out to the recording file.

record_buffer:
rec_type:
	.byte	0			; indicates binary/ascii
	rec_ascii = 1
	rec_binary = 2
rec_time:
	ASSUME REC_TIME EQ <REC_TYPE+1>
	.quad	0			; date/time the record was written
	rec_hdrsize = .-record_buffer
packet_buffer:
packet_header:
	ASSUME PACKET_HEADER EQ <REC_TIME+8>
	.blkb	lanhdr_s_lanhdrdef	; contains ethernet packet header
packet_data:
	ASSUME PACKET_DATA EQ <PACKET_HEADER+LANHDR_S_LANHDRDEF>
	.blkb	lan_s_ethernet		; contains ethernet packet data
packet_buffer_end:
	ASSUME PACKET_BUFFER_END EQ <PACKET_DATA+LAN_S_ETHERNET>
	rec_rsize = .-record_buffer

; Here we define some descriptors that will be used to check the details
; of the incoming packet.  We basically have three strings, the source and
; destination address and the sap details (returned by LAN_FORMAT_HEADER).
; We then use "fake" descriptors to address sub-fields within the sap details.
; The strings are those returned by lan_format_header and are therefore
; fixed length and a known format.
; pkt_destination and pkt_source will look like "XX-XX-XX-XX-XX-XX".
; pkt_sap will look like "XX-XX XX XX-XX-XX-XX-XX", where "XX-XX" is the
; protocol (also DSAP plus SSAP), the "XX" is the control byte and the rest
; is the PID consisting of COPID and IPID fields.

pkt_destination:	.long	s_address
			.address pkt_destination_t
pkt_destination_t:	.blkb	s_address
pkt_source:		.long	s_address
			.address pkt_source_t
pkt_source_t:		.blkb	s_address
pkt_sap:		.long	s_sap
			.address pkt_sap_t
pkt_sap_t:		.blkb	s_sap
pkt_protocol:		.long	s_protocol
			.address pkt_sap_t+0

; The following areas are used to store the items returned from the command
; line.  The sizes here should give enough room for valid addresses etc.
; and also allow for the use of keywords on those items that allow keywords.

	alloc_string	from, 64
	alloc_string	to, 64
	alloc_string	device, 64
	alloc_string	display, 32
	alloc_string	begin, 64
	alloc_string	end, 64
	alloc_string	count, 32
	alloc_string	output, 255
	alloc_string	record, 255

	alloc_string	from_name, s_name ; filled in if available from the
	alloc_string	to_name, s_name	; nodelist entries
from_ascic:		.long	0
to_ascic:		.long	0
default_end:		.ascid	"0 00:30:00.00"
begin_time:		.quad	0
end_time:		.quad	0
count_l:		.long	0	; use time limit

lnm_tabnam:	.ascid	"LNM$PROCESS_TABLE"
lnm_lognam:	.ascid	"SYS$OUTPUT"
lnm_acmode:	.long	psl$c_user
lnm_itmlst:	.word	0		; buffer length - filled in later
		.word	lnm$_string
		.long	0		; buffer address - filled in later
		.long	0		; return address - not used
		.long	0		; to end the list

destination_ascic:	.address null_ascic ; filled in by lookup_name when
source_ascic:		.address null_ascic ;  processing a packet

display_option:	.long	display_c_ascii	; the default
	display_c_all = 1
	display_c_none = 2
	display_c_ascii = 3
	display_c_hex = 4
	display_c_fast = 5

	alloc_string	display_choice, 16 ; filled in by lib$lookup_key to
					; be the full keyword
all_segment_size:	.long	16
hex_segment_size:	.long	20
ascii_segment_size:	.long	64
fast_format:		.ascid	"!AF"	; minimum formatting required

; Here are the valid command line qualifiers.

cli_debug:		.ascid	"DEBUG"
cli_device:		.ascid	"DEVICE"
cli_both:		.ascid	"BOTH"
cli_display:		.ascid	"DISPLAY"
cli_from:		.ascid	"FROM"
cli_to:			.ascid	"TO"
cli_nonames:		.ascid	"NONAMES"
cli_count:		.ascid	"COUNT"
cli_end:		.ascid	"END"
cli_begin:		.ascid	"BEGIN"
cli_output:		.ascid	"OUTPUT"
cli_record:		.ascid	"RECORD"
cli_playback:		.ascid	"PLAYBACK"
cli_header:		.ascid	"HEADER"

; The following definitions are used for checking whether a command line
; item was defaulted or not.  This is used in an attempt to increase the speed
; with which we determine whether or not we display a particular packet.
; The flags are set by the command parsing routine.

flag1:		.long	0		; this is a general flag area but uses
					;  the stuff defined below...
cli_supplied:	.long	0		; to keep track of what was given
cli_defaulted:	.long	0		;  and what was defaulted
	m_from		= 1
	v_from		= 0
	m_to		= 2
	v_to		= 1
	m_both		= 512
	v_both		= 9
	m_unknown	= 1024
	v_unknown	= 10
	m_from_unknown	= 2048
	v_from_unknown	= 11
	m_to_unknown	= 4096
	v_to_unknown	= 12
	m_device	= 8192
	v_device	= 13
	m_display	= 16384
	v_display	= 14
	m_source_unknown = 32768
	v_source_unknown = 15
	m_dest_unknown = 65536
	v_dest_unknown = 16
	m_nonames	= 131072
	v_nonames	= 17
	m_debug		= 262144
	v_debug		= 18
	m_count		= 1048576
	v_count		= 20
	m_end		= 2097152
	v_end		= 21
	m_begin		= 4194304
	v_begin		= 22
	m_output	= 8388608
	v_output	= 23
	m_record	= 16777216
	v_record	= 24
	m_playback	= 33554432
	v_playback	= 25
	m_header	= 67108864
	v_header	= 26

; The following keyword table (for lib$lookup_key) is used to check the
; single entry UNKNOWN to be used in /from and /to choices.

unknown_vector:	.long	1*2		; only one keyword
		.address unknown_keyword
		.long	m_unknown	; value to return
unknown_keyword:
		.ascic	"UNKNOWN"

unknown_ascid:	.ascid	"UNKNOWN"	; not part of the table, but used

; The following keyword table (for lib$lookup_key) contains the valid options
; available on the /display qualifier.

display_vector:	.long	7*2		; seven choices available
		vector	ascii_keyword, display_c_ascii
		vector	text_keyword, display_c_ascii
		vector	all_keyword, display_c_all
		vector	both_keyword, display_c_all
		vector	none_keyword, display_c_none
		vector	hex_keyword, display_c_hex
		vector	fast_keyword, display_c_fast
all_keyword:	.ascic	"ALL"
both_keyword:	.ascic	"BOTH"
none_keyword:	.ascic	"NONE"
ascii_keyword:	.ascic	"ASCII"
text_keyword:	.ascic	"TEXT"
hex_keyword:	.ascic	"HEXADECIMAL"
fast_keyword:	.ascic	"FAST"

; Protocol names array

protocol_names:
.repeat 256
	.address null_ascic
.endr

name_icmp:	.ascic	"ICMP"
name_ggp:	.ascic	"GGP"
name_stream:	.ascic	"STREAM"
name_tcp:	.ascic	"TCP"
name_egp:	.ascic	"EGP"
name_private:	.ascic	"PRIVATE"
name_nvp:	.ascic	"NVP"
name_udp:	.ascic	"UDP"
name_hmp:	.ascic	"HMP"
name_xnsidp:	.ascic	"XNSIDP"
name_rdp:	.ascic	"RDP"
name_irtp:	.ascic	"IRTP"
name_iso4:	.ascic	"ISO4"
name_bdtp:	.ascic	"BDTP"
name_any:	.ascic	"ANY"

; Here are the protocol definitions...

	apn_icmp = 1
	apn_ggp = 3
	apn_stream = 5
	apn_tcp = 6
	apn_egp = 8
	apn_private = 9
	apn_nvp = 11
	apn_udp = 17
	apn_hmp = 20
	apn_xnsidp = 22
	apn_rdp = 27
	apn_irtp = 28
	apn_iso4 = 29
	apn_bdtp = 30
	apn_any = 61

; Here we define offsets for the IP header

	ip_c_headersize = 20
	ip_b_ihl = 0
				ip_v_version = 0
				ip_s_version = 4
				ip_v_ihl = 4
				ip_s_ihl = 4
	ip_b_service_type = 1
	ip_w_dg_length = 2
				ip_v_dg_length_hi = 0
				ip_s_dg_length_hi = 8
				ip_v_dg_length_lo = 8
				ip_s_dg_length_lo = 8
	ip_w_dg_id = 4
				ip_v_dg_id_hi = 0
				ip_s_dg_id_hi = 8
				ip_v_dg_id_lo = 8
				ip_s_dg_id_lo = 8
	ip_w_frag_offset = 6
				ip_v_flags = 0
				ip_s_flags = 3
				ip_v_frag_offset = 3
				ip_s_frag_offset = 13
	ip_b_time_to_live = 8
	ip_b_protocol = 9
	ip_w_checksum = 10
	ip_l_source = 12
	ip_b_source1 = 12
	ip_b_source2 = 13
	ip_b_source3 = 14
	ip_b_source4 = 15
	ip_l_destination = 16
	ip_b_destination1 = 16
	ip_b_destination2 = 17
	ip_b_destination3 = 18
	ip_b_destination4 = 19

; Here we define offsets for the TCP header

	tcp_c_headersize = 20
	tcp_w_source = 0
				tcp_v_source_hi = 0
				tcp_s_source_hi = 8
				tcp_v_source_lo = 8
				tcp_s_source_lo = 8
	tcp_w_destination = 2
				tcp_v_destination_hi = 0
				tcp_s_destination_hi = 8
				tcp_v_destination_lo = 8
				tcp_s_destination_lo = 8
	tcp_l_seq = 4
	tcp_l_ack = 8
	tcp_l_junk2 = 12
	tcp_w_checksum = 16
	tcp_w_junk3 = 18

; Here we define offsets for ICMP stuff		*** more to be done here...

	icmp_c_headersize = 4
	icmp_b_type = 0
	icmp_b_code = 1
	icmp_w_checksum = 2

; Here we define the UDP header stuff

	udp_c_headersize = 8
	udp_w_source = 0
	udp_w_destination = 2
	udp_w_length = 4
	udp_w_checksum = 6

	.subtitle Work areas for reading of the nodelist file

	set_psect _util_data

	_rsize = 256			; not really but should be plenty
_record:	.blkb	_rsize		; buffer for the record
_record_ds:	.long	_rsize		; descriptor to point to the buffer
_record_addr:	.address _record

	alloc_string	play_thing, _rsize ; used for juggling the record
	alloc_string	play_thing2, _rsize

	_mbc = 127
	_mbf = 16
	_rtv = 255

	.align	long
_fab:	$fab	fac=<GET>, -
		fnm=<NODELIST>, -
		dnm=<ETHERWATCHER:NODELIST.DAT>, -
		fop=<SQO>, -
		rtv=_rtv, -
		shr=<GET,PUT,DEL,UPD>
_rab:	$rab 	fab=_fab, -
		mbc=_mbc, -
		mbf=_mbf, -
		rac=<SEQ>, -
		rop=<WAT,RAH,NLK,RRL,LOC>, -
		ubf=_record, -
		usz=_rsize

	reset_psect

	.subtitle Work areas for recording/playback file

	set_psect _util_data

	rec_alq = 2400			; initial allocation
	rec_deq = 2400			; default extension quantity
	rec_mbc = 127			; multi block count
	rec_mbf = 16			; multi buffer count
	rec_mrs = rec_rsize		; maximum record size
	rec_rtv = 255			; retreival pointers

	.align long
rec_fab:
	$fab	alq=rec_alq, -
		deq=rec_deq, -
		dnm=<TCPWATCH.RECORD>, -
		fop=<SQO,TEF>, -
		mrs=rec_mrs, -
		org=<SEQ>, -
		rat=<CR>, -
		rfm=<VAR>, -
		rtv=rec_rtv, -
		shr=<NIL>
rec_rab:
	$rab	fab=rec_fab, -
		mbc=rec_mbc, -
		mbf=rec_mbf, -
		rac=<SEQ>, -
		rbf=record_buffer, -
		rop=<RAH,WBH>, -
		ubf=record_buffer, -
		usz=rec_rsize

	reset_psect

	.subtitle Mainline

	set_psect _util_code

	.entry -
tcpwatch, ^m<>

	call lib$init_timer -		; initialize stats in case we
		stats_context		;  want to look at them

	call sys_trap_controlc -	; so we can exit gracefully
		process_controlc, -	; ... (hopefully)
		iosb

	call read_nodelist		; need some of this info when
					;  parsing the command...
	call parse_command		; get all the gory details

	call startup_device		; fire up the controller

	$hiber_s			; ...and wait

90$:	ret

	.subtitle Read the nodelist file

	.entry -
read_nodelist, ^m<r2,r3,r4,r5,r6,r7>

;++
; Functional Description:
;	The first thing we do is a quick scan of the nodelist file to see
;	how much memory we need to allocate for the names and addresses.
;	Here we open the nodelist file, read each record in the file and
;	pass it on to the process_nodelist_entry routine to validate it
;	and store it in the appropriate tables.  When complete we just
;	close the file.  If we don't find the file, we don't generate any
;	errors, we just continue but don't have any names and addresses in
;	the tables so every address is UNKNOWN when we display the packet
;	details.
;
; Calling Sequence:
;	call	read_nodelist
;
; Formal Argument(s):
;	None.
;
; Implicit Inputs:
;	None.
;
; Implicit Outputs:
;	None.
;
; Completion Codes:
;	None
;
; Side Effects:
;	None
;--

	call	scan_nodelist		; to see how many are there
	clrl	node_count		; we use this for counting
	movl	address_vector, r2	; we will use this later
	addl	#4, r2			; skip the count
	movl	name_vector, r3		; we need this also
	addl	#4, r3			; skip the count
	movl	name_address_data, r4	; we need this as well

	$open	fab=_fab		; open the nodelist
	blbc	r0, close
	$connect rab=_rab		; connect to it
	blbc	r0, close

10$:	$get	rab=_rab		; grab a record
	blbc	r0, close

	movzwl	_rab+rab$w_rsz, r5	; these get passed on to the
	movl	_rab+rab$l_rbf, r6	;  processing routine

	call	process_nodelist_entry

	cmpl	node_count, -		; how many have we done so far
		user_nodecount
	blss	10$			; still got room, try again

close:	$close	fab=_fab

	mull3	#2, node_count, -	; setup the vector count to be the
		@address_vector		;  number of longwords (2 per node)
	movl	@address_vector, -	; should be the same number of entries
		@name_vector		;  in each list

	ret

	.subtitle Routine to scan the nodelist and allocate virtual memory

	.entry -
scan_nodelist, ^m<>
;+
; Here we do a quick scan of the nodelist file to see how many entries are
; in it.  We assume each line is valid so that if there are comments and
; blank lines we will over allocate the real space that we need.  (I can live
; with that).
;-
	clrl	user_nodecount
	$open	fab=_fab		; open the nodelist
	blbc	r0, 90$
	$connect rab=_rab		; connect to it
	blbc	r0, 90$
10$:	$get	rab=_rab		; grab a record
	blbc	r0, 90$
	incl	user_nodecount
	brb	10$
90$:	$close	fab=_fab
	if <gtr,user_nodecount,#max_nodecount> then
	  movl	#max_nodecount, user_nodecount
	endif
	if <eql,user_nodecount> then	; if the file is missing, or empty
	  movl	#1, user_nodecount	;  allocate space for 1 node to
	  bisl	#m_nonames, cli_supplied;  prevent accvio's, and set /nonames
	endif
	mull3	#8, user_nodecount, -	; that's how many bytes in the vector
		address_vector_size
	addl2	#4, address_vector_size	; allow for the vector count
	movl	address_vector_size, -	; they will be the same size
		name_vector_size
	addl3	#s_address, #s_name, -	; that's the size of each entry
		data_area_size
	addl2	#2, data_area_size	; plus byte count overhead
	mull2	user_nodecount, -	; and this is how much we really need
		data_area_size		;  for the data portion
	mull3	#2, address_vector_size, -
		vm_bytecount
	addl2	data_area_size, -	; this is the total allocation we
		vm_bytecount		;  need
	call lib$get_vm -		; now try to allocate it
		vm_bytecount, -
		vm_base_address
	if <lbc,r0> then		;  and signal any problems
	  signal code=r0
	endif
	movl	vm_base_address, -	; that's the pointer to the address
		address_vector		;  vector
	addl3	address_vector_size, -	; this is the pointer to the name
		address_vector, -	;  vector
		name_vector
	addl3	name_vector_size, -	; and the pointer to the data area
		name_vector, -
		name_address_data
	clrl	@address_vector		; in case we have no entries
	clrl	@name_vector

	ret

	.subtitle Process a nodelist entry

	.entry -
process_nodelist_entry, ^m<>

;++
; Functional Description:
;	This routine will verify (in a limited way) the record from the
;	nodelist file.  Blank lines are allowed, comments can begin with
;	either "!" or ";" and can really be any line without an equals sign
;	in it.
;	The expected format of the record is
;		xx-xx-xx-xx-xx-xx = nodename
;	Where the xx-xx... stuff is the address of the node with name
;	nodename.  All characters are converted to uppercase.
;	Once we get a "valid" record, we load it into the address and name
;	tables, sort out the cross referencing between the two and that's
;	that.
;
; Calling Sequence:
;	call	process_nodelist_entry
;
; Formal Argument(s):
;	None.
;
; Implicit Inputs:
;	R2	pointer to next longword entry in address vector
;	R3	pointer to next longword entry in name vector
;	R4	pointer to next byte in name/address data
;	R5	size of record
;	R6	address of record buffer
;
; Implicit Outputs:
;	R5 and R6 are trashed to reflect the size of the record after we
;		have done an str_collapse.
;
; Completion Codes:
;	None
;
; Side Effects:
;	None
;--

	movl	r5, _record_ds		; load up a descriptor for us to
	movl	r6, _record_addr	;  use
	call	str_uppercase -		; convert the record to uppercase
			play_thing_ds, -
			_record_ds
	call	str_collapse -		; get rid of spaces etc...
			play_thing2_ds, -
			play_thing_ds
	call	str_len -		; and get the real length
			play_thing2_ds, -
			play_thing2
	movq	play_thing2, r5		; now point to the real data
	tstl	r5			; anything there?
	beql	90$			; nope, so go away
	cmpb	#^A/!/, (r6)		; is this a comment
	beql	90$			; yep, so ignore it
	cmpb	#^A/;/, (r6)		; allow ";" as a comment as well
	beql	90$			; yep, so ignore it
	cmpb	#^A/=/, (r6)		; see if address is null
	beql	90$			; yep, so ignore it
	locc	#^A/=/, r5, (r6)	; look for an "="
	cmpl	r0, #1			; how many characters left?
	bleq	90$			; not enough, so skip it
	subl3	r6, r1, r7		; R7 = address length
	movl	r4, (r2)+		; pointer to address in address vector
	movb	r7, (r4)+		; move the address byte count to the
					;  data area
10$:	movb	(r6)+, (r4)+		; now move the address
	sobgtr	r7, 10$			;  until we have it all
	movl	r4, (r2)+		; pointer to name in address table
	decl	r0			; this skips the "=" by reducing the
					;  length by one
	incl	r6			;  and upping the address by one
	cmpl	r0, #s_name		; check that the name is not too long
	bleq	15$			; it's ok, so keep going
	movl	#s_name, r0		; force it to our maximum length
15$:	movb	r0, (r4)+		; move the name byte count to the
					;  data area
20$:	movb	(r6)+, (r4)+		; now move the name
	sobgtr	r0, 20$			;  until it's all done
	movl	-4(r2), (r3)+		; pointer to name in name vector
	movl	-8(r2), (r3)+		; pointer to address in name vector
	incl	node_count		; keep track of how many we have

90$:	ret

	.subtitle Parse the command

	.entry -
parse_command, ^m<>

;++
; Functional Description:
;	Here we validate the command and extract everything we can and
;	validate any qualifier values that were suppplied.
;
; Calling Sequence:
;	call parse_command
;
; Formal Argument(s):
;	None
;
; Implicit Inputs:
;	None.
;
; Implicit Outputs:
;	None.
;
; Completion Codes:
;	None
;
; Side Effects:
;	None
;--

	pushaw	command			; here we get the command they
	pushl	#0			;  used to invoke us - we don't
	pushaq	command_ds		;  prompt even if they didn't give
	calls	#3, g^lib$get_foreign	;  any options

	pushr	#^m<r2,r3,r4,r5>
	movc3	command, -		; tack the foreign command
		command_t, -		;  onto the primed buffer
		command_buffer_extra
	popr	#^m<r2,r3,r4,r5>
	addl2	command, command_buffer	; and fixup the length

	call cli$dcl_parse -		; see if the cli routines think
		command_buffer, -	;  the command is ok
		tcpwatch_cld
	if <lbc,r0> then		; if it didn't work
	  bisl	#sts$m_inhib_msg, r0	;  then don't signal when we exit
	  pushl	r0			;    cli$... has already done that
	  calls	#1, g^lib$stop		;  bail out
	endif ;<lbc,r0> then

	if_present from			; here we check all the possible
	if_present to			;  qualifiers and set bits in
	if_present device		;  the cli_supplied and _defaulted
	if_present display		;  flags to say what was what
	if_present both
	if_present nonames
	if_present debug
	if_present count
	if_present end
	if_present output
	if_present record
	if_present playback
	if_present header

; get the output details if any

	if <bs,#v_output,cli_supplied> then
	  call cli$get_value cli_output, output_ds, output
	  movw	output, lnm_itmlst
	  movab output_t, lnm_itmlst+4
	  $crelnm_s -
		tabnam=lnm_tabnam, -
		lognam=lnm_lognam, -
		acmode=lnm_acmode, -
		itmlst=lnm_itmlst
	  if <lbc,r0> then
	    signal code=r0
	  endif
	endif

; now see if we are recording or playing back

	if <bs,#v_record,cli_supplied> then
	  call cli$get_value cli_record, record_ds, record
	  call create_record_file
	  bisl	#m_nonames, cli_supplied ; prevent name processing
	endif

	if <bs,#v_playback,cli_supplied> then
	  call cli$get_value cli_playback, record_ds, record
	endif

	display	program_id		; say who we are

	$fao_s	ctrstr=loaded_fao, -	; say how many names/addresses we
		outbuf=_faobuf_ds, -	;  loaded
		outlen=_faobuf, -
		p1=node_count
	display	_faobuf

; Now we get the values they supplied, if any

	if <bs,#v_device,cli_supplied> then
	  call cli$get_value -		; they supplied a device, so use it
		cli_device, device_ds, device
	else
	call sys_find_ether_device -	; else see what's on the system
		device_ds		;  and use that one
	if <lbc,r0> then		; if any problems at this point
	  pushl	r0
	  calls	#1, g^lib$stop		;  bail out
	endif ;<lbc,r0> then
	call str_len -			; we got one, now fix up the
		device_ds, -		;  string
		device
	endif ;<bs,#v_device,cli_supplied> then

; ...now get all the other values, there should be no errors since all these
; have defaults i.e. there should not be any cli$_absent returned

	call cli$get_value cli_from, from_ds, from
	call cli$get_value cli_to, to_ds, to
	call cli$get_value cli_display, display_ds, display

	call	check_display_option

	if <bc,#v_from,cli_defaulted> then ; if /from not defaulted
	  call	check_from_option	; check what they gave us
	else
	clrl	from_name
	endif

	if <bc,#v_to,cli_defaulted> then ; if /to not defaulted
	  call	check_to_option		; check what they gave us
	else
	clrl	to_name
	endif

	if <bs,#v_debug,cli_supplied> then
	  $fao_s ctrstr=vm_allocated, -
		outbuf=_faobuf_ds, -
		outlen=_faobuf, -
		p1=vm_bytecount, -
		p2=vm_base_address
	  display _faobuf
	  call lib$show_vm
	endif

	if <bs,#v_begin,cli_supplied> then
	  call cli$get_value cli_begin, begin_ds, begin
	  $bintim_s -
		timbuf=begin, -
		timadr=begin_time
	  $setimr_s -
		efn=#0, -
		daytim=begin_time
	  $wfland_s -
		efn=#0, -
		mask=#1
	endif

	if <bs,#v_count,cli_supplied> then
	  call cli$get_value cli_count, count_ds, count
	  call lib_cvt_t_l count, count_l
	endif
	if <eql,count_l> then		; if it's zero
	  decl	count_l			;  make it -l i.e. use time to end
	endif
	bbs	#v_count, cli_supplied, - ; if /count specified, then
		exit_parse		;  don't use the timer to exit

	if <bs,#v_end,cli_supplied> then
	  call cli$get_value cli_end, end_ds, end
	  $bintim_s -
		timbuf=end, -
		timadr=end_time
	else
	$bintim_s -
		timbuf=default_end, -
		timadr=end_time
	endif
	$setimr_s -
		daytim=end_time, -
		astadr=process_controlc

exit_parse:

	def_protocol_name icmp
	def_protocol_name ggp
	def_protocol_name stream
	def_protocol_name tcp
	def_protocol_name egp
	def_protocol_name private
	def_protocol_name nvp
	def_protocol_name udp
	def_protocol_name hmp
	def_protocol_name xnsidp
	def_protocol_name rdp
	def_protocol_name irtp
	def_protocol_name iso4
	def_protocol_name bdtp
	def_protocol_name any

	ret

	.subtitle Routines to check the command line qualifier values

	.entry -
check_display_option, ^m<>
;+
; Here we take what was given and return the full keyword for display
; purposes.  There should be no errors since the cli parsing would have
; picked them up and we wouldn't have got this far.
;-
	call lib$lookup_key -
		display, -		; this is what they said
		display_vector, -	; ...the table of valid keywords
		display_option, -	; ...the coded value
		display_choice_ds, -	; ...the full keyword
		display_choice		; ...and the keyword length

	ret

;+
; The check_from_option and check_to_option routines do the same work but
; just use different strings as the source.
; The strategy is as follows:
; 1.  See if the keyword UNKNOWN was used.  If so then set the appropriate
;	flag for later... that's all we need to do.
; 2.  Now check to see if the string is in the form area.node i.e. a DECnet
;	address.  If it is, then we convert it into a string of the form
;	AA-00-04-00-XX-XX and make it look like the user typed it.
; 3.  Assume the string is a name that will be found in the nodename list.
; 4.  If we find the name in the nodename list then use the associated address
;	as our address used for matching against packets and use the given
;	name to display in the header.  At this point we have finished.
; 5.  If the name is not in the nodename list, assume it is an address in the
;	address list.
; 6.  If we find it in the address list then use the address for matching
;	purposes and use the associated name to display in the header.
;	At this point we have finished.
; 7.  At this point the given value is neither a known name nor a known
;	address therefore just use what we were given as it is probably an
;	address that is not currently in the list or contains wildcards.
;-

	.entry -
check_from_option, ^m<>

	call lib$lookup_key from, unknown_vector
	if <lbs,r0> then		; if they said /from=unknown
	  bisl	#m_from_unknown, flag1	;  set the flag
	  movq	wildcard, from
	  movq	unknown_ascid, from_name
	else
	pushr	#^m<r10,r11>
	movaq	from_ds, r10		; check for a DECnet address
	movaq	from, r11
	call	convert_decnet_to_aa
	popr	#^m<r10,r11>
	call lib$lookup_key from, @name_vector, from_ascic, from_ds, from
	if <lbs,r0> then		; if it is a name
	  pushr	#^m<r2,r3,r4,r5>
	  movc3	from, @from+4, -	;  copy "from" into "from_name"
		@from_name+4
	  movl	from, from_name		;  and fix the length
	  movl	from_ascic, r0		;  just to play with
	  movzbl (r0)+, from		;  that's the address length
	  movc3	from, (r0), @from+4	;  copy the address to the "from" field
	  popr	#^m<r2,r3,r4,r5>
	else				; wasn't a name...
	call lib$lookup_key from, @address_vector, from_ascic
	if <lbs,r0> then		; if it is an address
	  pushr	#^m<r2,r3,r4,r5>
	  movl	from_ascic, r0
	  movzbl (r0)+, from_name
	  movc3	from_name, (r0), -	; copy the ascic stuff to the ascid
		@from_name+4		;   area
	  popr	#^m<r2,r3,r4,r5>
	else				; wasn't name or address - use as is
	clrl	from_name		; nothing to display
	endif ;<lbs,r0> then
	endif ;<lbs,r0> then
	endif ;<lbs,r0> then

	ret

	.entry -
check_to_option, ^m<>

	call lib$lookup_key to, unknown_vector
	if <lbs,r0> then		; if they said /to=unknown
	  bisl	#m_to_unknown, flag1	;  set the flag
	  movq	wildcard, to
	  movq	unknown_ascid, to_name
	else
	pushr	#^m<r10,r11>
	movaq	to_ds, r10		; see if it is a DECnet address
	movaq	to, r11
	call	convert_decnet_to_aa
	popr	#^m<r10,r11>
	call lib$lookup_key to, @name_vector, to_ascic, to_ds, to
	if <lbs,r0> then		; if it is a name
	  pushr	#^m<r2,r3,r4,r5>
	  movc3	to, @to+4, @to_name+4	;  copy "to" into "to_name"
	  movl	to, to_name		;  and fix the length
	  movl	to_ascic, r0		;  just to play with
	  movzbl (r0)+, to		;  that's the address length
	  movc3	to, (r0), @to+4		;  copy the address to the "to" field
	  popr	#^m<r2,r3,r4,r5>
	else				; wasn't a name...
	call lib$lookup_key to, @address_vector, to_ascic
	if <lbs,r0> then		; if it is an address
	  pushr	#^m<r2,r3,r4,r5>
	  movl	to_ascic, r0
	  movzbl (r0)+, to_name
	  movc3	to_name, (r0), -	; copy the ascic stuff to the ascid
		@to_name+4		;   area
	  popr	#^m<r2,r3,r4,r5>
	else				; wasn't name or address - use as is
	clrl	to_name			; nothing to display
	endif ;<lbs,r0> then
	endif ;<lbs,r0> then
	endif ;<lbs,r0> then

	ret

	.entry -
convert_decnet_to_aa, ^m<>
;+
; Here we check to see if the specified address is a possible decnet type
; address in the form area.node.  If it is then we convert it to the format
; AA-00-04-00-XX-XX and set this up to look like the user supplied this value.
;
; Inputs:
;	R10	Address of the output buffer descriptor
;	R11	Address of the input buffer descriptor
;-
	pushr	#^m<r8,r9>
	movq	(r11), r8		; grab the input buffer descriptor
	locc	#^A/./, r8, (r9)	; try to find a dot
	tstl	r0			; is there one?
	bneq	10$			; yes, so play with it
	brw	90$			; no, bail out
10$:	movq	(r11), area_number_s	; load the area number descriptor
	subl2	r0, area_number_s	;  and fix up the length
	decl	r0			; shorten length by one
	incl	r1			;  and increase address by one
					;  to skip the dot
	movq	r0, node_number_s	; load the node number descriptor
	call lib_cvt_t_l area_number_s, area_number
	blbc	r0, 90$			; bail out if no good
	call lib_cvt_t_l node_number_s, node_number
	blbc	r0, 90$			; bail out if no good
	mull3	#1024, area_number, r0	; now convert the two numbers into
	addl2	node_number, r0		;  a single word value
	movl	r0, r1			; we need a copy to split it
	bicl	#^XFFFFFF00, r0		; now do the jiggery pokery to
	bicl	#^XFFFF00FF, r1		;  get the bytes swapped and
	divl2	#^X100, r1		;  cleaned up for the aa format
	$fao_s	ctrstr=aa_format, -
		outbuf=(r10), -
		outlen=(r11), -
		p1=r0, -
		p2=r1
90$:	popr	#^m<r8,r9>

	ret

	.subtitle Startup the ethernet device in promiscuous mode

	.entry -
startup_device, ^m<>

;++
; Functional Description:
;	This routine attempts to startup the ethernet device, set it up so
;	we can see everything then queue the first read request to it.
;	It also displays some informational messages along the way.
;
; Calling Sequence:
;	call startup_device
;
; Formal Argument(s):
;	None
;
; Implicit Inputs:
;	None.
;
; Implicit Outputs:
;	None.
;
; Completion Codes:
;	None
;
; Side Effects:
;	None
;--

	if <bs,#v_playback,cli_supplied> then
	  $fao_s ctrstr=playing_back, -
		outbuf=_faobuf_ds, -
		outlen=_faobuf, -
		p1=#record
	  display _faobuf
	  call open_record_file
10$:	  $get	rab=rec_rab
	  blbc	r0, 20$
	  call process_packet
	  brb	10$
20$:	  call process_controlc
	else
	$fao_s	ctrstr=intro_line1, -
		outbuf=_faobuf_ds, -
		outlen=_faobuf, -
		p1=#device
	display	_faobuf

	call lan_startup_prm -
		device, -
		log_channel, -
		iosb
	if <lbc,iosb> then		; if anything goes wrong with that
	  pushl	iosb			;  then we can't go any further
	  calls	#1, g^lib$stop
	endif ;<lbc,r0> then

	$fao_s	ctrstr=intro_line2, -
		outbuf=_faobuf_ds, -
		outlen=_faobuf, -
		p1=#from, -
		p2=#from_name, -
		p3=#to, -
		p4=#to_name, -
		p5=#display_choice
	display	_faobuf
	call queue_async_read
	endif ;<bs,#v_playback,cli_supplied> then

	ret

	.subtitle Queue an asynchronous read I/O to the ethernet device

	.entry -
queue_async_read, ^m<>

;++
; Functional Description:
;	Here we issue a read qio to the ethernet device and setup an ast to
;	invoke the process_packet routine when we have something to read.
;
; Calling Sequence:
;
;	call queue_async_read
;
; Formal Argument(s):
;	None
;
; Implicit Inputs:
;	ether_watch_data common area.
;
; Implicit Outputs:
;	None
;
; Completion Codes:
;	None
;
; Side Effects:
;	None
;--

	$qio_s	chan=log_channel, -
		func=#io$_readvblk, -
		iosb=iosb, -
		astadr=process_packet, -
		p1=packet_data, -	; data location
		p2=#lan_s_ethernet, -	; size of data area
		p5=#packet_header	; header location
	if <lbc,r0> then
	  signal code=r0
	endif ;<lbc,r0> then

90$:	ret

	.subtitle Lookup a nodename given an address

	.entry -
lookup_name, ^m<>

;++
; Functional Description:
;	Check the ethernet address given and return the address of ascic
;	string for the associated name.  If the address was not found, then
;	we return the address of the name UNKNOWN.
;
; Calling Sequence:
;	call	lookup_name (%descr(address), %ref(name_location))
;
; Formal Argument(s):
;	address.rt.ds	Address of the descriptor for the string containing
;			the ethernet address to be checked.
;	name_location.wl.r  Address of a longword that will contain the
;			address of the ascic string containing the node name.
;
; Implicit Inputs:
;	4(ap)	Address of descriptor of the ethernet address
;	8(ap)	Address of a longword to receive the address of the ascic
;		string containing the node name
;
; Implicit Outputs:
;	None.
;
; Completion Codes:
;	The low bit in R0 can be checked on return to see if the address was
;	found or not (saves doing string comparisons etc.).
;
; Side Effects:
;	None
;--

	pushl	8(ap)
	pushl	address_vector
	pushl	4(ap)
	calls	#3, g^lib$lookup_key

	if <lbc,r0> then
	  movab	unknown_keyword, @8(ap)
	endif ;<lbc,r0> then

	ret

	.subtitle Lookup an address given a nodename

	.entry -
lookup_address, ^m<>

;++
; Functional Description:
;	Check the name given and return the address of ascic string for the
;	associated ethernet address.  If the name was not found, then we
;	return the address of the name UNKNOWN.
;
; Calling Sequence:
;	call	lookup_name (%descr(nodename), %ref(address_location))
;
; Formal Argument(s):
;	nodename.rt.ds	Address of the descriptor for the string containing
;			the node name to be checked.
;	address_location.wl.r  Address of a longword that will contain the
;			address of the ascic string containing the ethernet
;			address.
;
; Implicit Inputs:
;	4(ap)	Address of descriptor of the node name
;	8(ap)	Address of a longword to receive the address of the ascic
;		string containing the node address
;
; Implicit Outputs:
;	None.
;
; Completion Codes:
;	The low bit in R0 can be checked on return to see if the name was
;	found or not (saves doing string comparisons etc.).
;
; Side Effects:
;	None
;--

	pushl	8(ap)
	pushl	name_vector
	pushl	4(ap)
	calls	#3, g^lib$lookup_key

	if <lbc,r0> then
	  movab	unknown_keyword, @8(ap)
	endif ;<lbc,r0> then

	ret

	.subtitle Here's where we get to if they type control/c

	.entry -
process_controlc, ^m<>

;++
; Functional Description:
;	This is where we end up if a control c is used.  We just exit.
;
; Calling Sequence:
;	via an ast...
;
; Formal Argument(s):
;	None.
;
; Implicit Inputs:
;	None.
;
; Implicit Outputs:
;	None.
;
; Completion Codes:
;	None
;
; Side Effects:
;	None
;--

	if <bs,#v_record,cli_supplied> then
	  movaq recorded, r0
	else
	movaq	displayed, r0
	endif
	$fao_s	ctrstr=tally, -
		outbuf=_faobuf_ds, -
		outlen=_faobuf, -
		p1=tcp_packets_read, -
		p2=packets_read, -
		p3=packets_displayed, -
		p4=r0
	display	_faobuf

	call lib$free_vm -
		vm_bytecount, -
		vm_base_address

	if <bs,#v_debug,cli_supplied> then
	  call lib$show_vm
	  call lib$show_timer stats_context
	endif

	display	command_buffer

	if <bs,#v_record,cli_supplied> then
	  call close_record_file
	endif
	if <bs,#v_playback,cli_supplied> then
	  call close_record_file
	endif

	pushl	#^X10000001
	calls	#1, g^lib$stop

	ret

	.subtitle Process a packet

	.entry -
process_packet, ^m<>
	
;++
; Functional Description:
;	This routine gets the packet, formats the header and does the
;	necessary checking to see if we want to look at it based on the
;	selection criteria supplied by the user on the command line.
;	We also queue another i/o request for the next packet.
;
; Calling Sequence:
;	call	process_packet
;
; Formal Argument(s):
;	None.
;
; Implicit Inputs:
;	None.
;
; Implicit Outputs:
;	None.
;
; Completion Codes:
;	None
;
; Side Effects:
;	None
;--

	incl	packets_read

	if <bs,#v_playback,cli_supplied> then
	  movzwl rec_rab+rab$w_rsz, packet_length
	  subl	#rec_hdrsize, packet_length
	else
	movzwl	iosb_xfr_size, -	; grab the length of the packet we
		packet_length		;  are going to look at
	endif

	cmpw	#tcp_protocol, -	; see if this is a TCP packet
		packet_header+lanhdr_w_protocol
	beql	check_header
	brw	exit			; no good, so do no more

check_header:
	incl	tcp_packets_read	; 'tis TCP so count it

	call lan_format_header -	; format the header so we can start
		packet_header_ds, -	;  doing our matching
		pkt_destination, -
		pkt_source, -
		pkt_sap

	bicl	#<m_dest_unknown-	; reset the unknown flags
		 !m_source_unknown>, -
		flag1

	if <bc,#v_nonames,cli_supplied> then
	  call lookup_name -		; get the friendly names if they
		pkt_destination, -	;  exist in our list
		destination_ascic
	  if <lbc,r0> then		; couldn't find them in the list
	    bisl #m_dest_unknown, flag1	;   flag an unknown destination
	  endif
	endif

	if <bc,#v_nonames,cli_supplied> then
	  call lookup_name -		; see if the source is in our list
		pkt_source, -		;  of known addresses
		source_ascic
	  if <lbc,r0> then		; couldn't find this one either
	    bisl #m_source_unknown, flag1	;   flag an unknown source
	  endif
	endif

check_source:
	bbs	#v_from, cli_defaulted, - ; if /from was defaulted
		check_destination	; it always matches, check destination
	if <bs,#v_from_unknown,flag1> then ; if /from=unknown
	  if <bs,#v_source_unknown,flag1> then ; if source is unknown
	    brb check_destination	; then it's ours, check destination
	  else
	  if <bs,#v_both,cli_supplied> -
		and <bs,#v_dest_unknown,flag1> then
	    brw do_display
	  else
	  brw	exit			; else we don't want it
	  endif
	  endif
	endif
	call str$match_wild -		; check source of the packet
		pkt_source, from	;  against the /from value
	blbs	r0, check_destination	; it's a winner
	bbs	#v_both, cli_supplied, - ; no match, so bail out unless they
		10$			;  specified /both
	brw	exit
10$:	call str$match_wild -		; want /both so try to match dest
		pkt_destination, from	;  address
	blbs	r0, check_destination
	brw	exit			; if that failed, just get out
check_destination:
	bbs	#v_to, cli_defaulted, -	; if /to was defaulted
		do_display		;  it always matches, so display it
	if <bs,#v_to_unknown,flag1> then ; if /to=unknown
	  if <bs,#v_dest_unknown,flag1> then ; if destination is unknown
	    brb do_display		;  then we want to look at it
	  else
	  if <bs,#v_both,cli_supplied> -
		and <bs,#v_source_unknown,flag1> then
	    brw do_display
	  else
	  brb	exit			; else we don't want it
	  endif
	  endif
	endif
	call str$match_wild -		; check destination of the packet
		pkt_destination, to	;  against the /to value
	blbs	r0, do_display		; it matches, so look at it
	bbc	#v_both, cli_supplied, - ; no match, so bail out unless they
		exit			;  said /both in the command
	call str$match_wild -		; want /both so try to match source
		pkt_source, to		;  address
	blbc	r0, exit		; if that failed, try another packet

do_display:
	incl	packets_displayed
	if <bs,#v_record,cli_supplied> then
	  call record_a_packet
	else
	call display_a_packet
	endif

exit:	cmpl	tcp_packets_read, count_l ; see if we should finish yet
	blssu	10$			; not yet...
	call process_controlc		; finish, don't want to look anymore
10$:	bbs	#v_playback, cli_supplied, 90$
	call queue_async_read

90$:	ret

	.subtitle Display a packet

	.entry -
display_a_packet, ^m<r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>

;++
; Functional Description:
;	Here we display the packet header in a nice formatted way.  Then,
;	based on the display option chosen, display (or not) the contents
;	of the packet in the chosen way.
;	All registers are saved on the way in so that the display routines
;	can have their way with them.
;
; Calling Sequence:
;	call	display_a_packet
;
; Formal Argument(s):
;	None.
;
; Implicit Inputs:
;	None.
;
; Implicit Outputs:
;	None.
;
; Completion Codes:
;	None
;
; Side Effects:
;	None
;--

	if <bs,#v_playback,cli_supplied> then
	  movaq	rec_time, r0
	else
	clrl	r0
	endif
	if <bs,#v_header,cli_supplied> then
	  $fao_s ctrstr=display_header, -
		outbuf=_faobuf_ds, -
		outlen=_faobuf, -
		p1=#pkt_source, -
		p2=source_ascic, -
		p3=#pkt_destination, -
		p4=destination_ascic, -
		p5=packet_length, -
		p6=r0
	  display _faobuf
	else
	display empty_string
	endif

	movab	packet_data, r11	; address of packet data buffer
					;  used by all display routines
	moval	faol_prmlst, r10	; address of the parameter list
	movzbl	ip_b_protocol(r11), r9	; grab protocol for later
	movzbl	ip_b_protocol(r11), (r10)+
	movl	protocol_names[r9], (r10)+
	movzbl	ip_b_source1(r11), (r10)+
	movzbl	ip_b_source2(r11), (r10)+
	movzbl	ip_b_source3(r11), (r10)+
	movzbl	ip_b_source4(r11), (r10)+
	movzbl	ip_b_destination1(r11), (r10)+
	movzbl	ip_b_destination2(r11), (r10)+
	movzbl	ip_b_destination3(r11), (r10)+
	movzbl	ip_b_destination4(r11), (r10)+
	swapbw	ip_w_checksum(r11), (r10)+
	movzbl	ip_b_time_to_live(r11), (r10)+
	swapbw	ip_w_dg_id(r11), (r10)+
	extzv	#ip_v_ihl, #ip_s_ihl, ip_b_ihl(r11), (r10)+
	extzv	#ip_v_version, #ip_s_version, ip_b_ihl(r11), (r10)+
	movzbl	ip_b_service_type(r11), (r10)+
	swapbw	ip_w_dg_length(r11), (r10)+
	movl	r0, packet_length	; that's the tcp datagram length
	
	$faol_s	ctrstr=ip_header, -
		outbuf=_faobuf_ds, -
		outlen=_faobuf, -
		prmlst=faol_prmlst
	display	_faobuf

	addl2	#ip_c_headersize, r11	; skip the ip header
	check_protocol tcp
	check_protocol udp
	check_protocol icmp
	call	display_unknown_packet

exit1:	ret

	.subtitle Display a TCP packet

	.entry -
display_tcp_packet, ^m<>

	moval	faol_prmlst, r10	; address of the parameter list
	swapbw	tcp_w_source(r11), (r10)+
	swapbw	tcp_w_destination(r11), (r10)+
	swapbl	tcp_l_seq(r11), (r10)+
	swapbl	tcp_l_ack(r11), (r10)+
	swapbw	tcp_w_checksum(r11), (r10)+
	movl	tcp_l_junk2(r11), (r10)+
	movzwl	tcp_w_junk3(r11), (r10)+

	$faol_s	ctrstr=tcp_header, -
		outbuf=_faobuf_ds, -
		outlen=_faobuf, -
		prmlst=faol_prmlst
	display	_faobuf

	pushl	packet_length
	pushq	packet_data_ds
	subl2	#<ip_c_headersize+tcp_c_headersize>, packet_length
	movl	packet_length, packet_data_ds
	addl2	#<ip_c_headersize+tcp_c_headersize>, packet_data_addr
	call	display_packet_contents
	popq	packet_data_ds
	popl	packet_length

	ret

	.subtitle Display a UDP packet

	.entry -
display_udp_packet, ^m<>

	moval	faol_prmlst, r10	; address of the parameter list
	swapbw	udp_w_source(r11), (r10)+
	swapbw	udp_w_destination(r11), (r10)+
	swapbw	udp_w_length(r11), (r10)+
	swapbw	udp_w_checksum(r11), (r10)+

	$faol_s	ctrstr=udp_header, -
		outbuf=_faobuf_ds, -
		outlen=_faobuf, -
		prmlst=faol_prmlst
	display	_faobuf

	pushl	packet_length
	pushq	packet_data_ds
	subl2	#<ip_c_headersize+udp_c_headersize>, packet_length
	movl	packet_length, packet_data_ds
	addl2	#<ip_c_headersize+udp_c_headersize>, packet_data_addr
	call	display_packet_contents
	popq	packet_data_ds
	popl	packet_length

	ret

	.subtitle Display an ICMP packet

	.entry -
display_icmp_packet, ^m<>

	moval	faol_prmlst, r10	; address of the parameter list
	movzbl	icmp_b_type(r11), (r10)+
	movzbl	icmp_b_code(r11), (r10)+
	swapbw	icmp_w_checksum(r11), (r10)+

	$faol_s	ctrstr=icmp_header, -
		outbuf=_faobuf_ds, -
		outlen=_faobuf, -
		prmlst=faol_prmlst
	display	_faobuf

	pushl	packet_length
	pushq	packet_data_ds
	subl2	#<ip_c_headersize+icmp_c_headersize>, packet_length
	movl	packet_length, packet_data_ds
	addl2	#<ip_c_headersize+icmp_c_headersize>, packet_data_addr
	call	display_packet_contents
	popq	packet_data_ds
	popl	packet_length

	ret

	.subtitle Display the contents of an unknown packet

	.entry -
display_unknown_packet, ^m<>

	pushl	packet_length
	pushq	packet_data_ds
	subl2	#ip_c_headersize, packet_length
	movl	packet_length, packet_data_ds
	addl2	#ip_c_headersize, packet_data_addr
	call	display_packet_contents
	popq	packet_data_ds
	popl	packet_length

	ret

	.subtitle Display the packet contents according to their wishes

	.entry -
display_packet_contents, ^m<>

; Display the packet based on what /display option was chosen.

	if <neq,#display_c_none,display_option> then
	  if <eql,#display_c_ascii,display_option> then
	    pushal	ascii_segment_size
	    pushal	packet_length
	    pushaq	packet_data_ds
	    calls	#3, g^lib_output_seg_t
	  else
	  if <eql,#display_c_all,display_option> then
	    pushal	all_segment_size
	    pushal	packet_length
	    pushaq	packet_data_ds
	    calls	#3, g^lib_output_seg_tzb
	  else
	  if <eql,#display_c_hex,display_option> then
	    pushal	hex_segment_size
	    pushal	packet_length
	    pushaq	packet_data_ds
	    calls	#3, g^lib_output_seg_zb
	  else
	  $fao_s ctrstr=fast_format, -
		outbuf=_faobuf_ds, -
		outlen=_faobuf, -
		p1=packet_length , -
		p2=#packet_data
	  display _faobuf
	  endif ;<eql,#display_c_hex,display_option> then
	  endif ;<eql,#display_c_all,display_option> then
	  endif ;<eql,#display_c_ascii,display_option> then
	endif ;<neq,#display_c_none,display_option> then

	ret

	.subtitle Routines to handle the recording/playback file

	.entry -
create_record_file, ^m<>

	movab	record_t, rec_fab+fab$l_fna
	movb	record, rec_fab+fab$b_fns
	movb	#fab$m_put, rec_fab+fab$b_fac
	$create	fab=rec_fab
	if <lbc,r0> then
	  signal code=r0
	endif
	$connect rab=rec_rab
	if <lbc,r0> then
	  signal code=r0
	endif

	ret

	.entry -
open_record_file, ^m<>

	movab	record_t, rec_fab+fab$l_fna
	movb	record, rec_fab+fab$b_fns
	movb	#fab$m_get, rec_fab+fab$b_fac
	$open	fab=rec_fab
	if <lbc,r0> then
	  signal code=r0
	endif
	$connect rab=rec_rab
	if <lbc,r0> then
	  signal code=r0
	endif

	ret

	.entry -
close_record_file, ^m<>

	$close	fab=rec_fab

	ret

	.entry -
record_a_packet, ^m<>

	movb	#rec_binary, rec_type
	$gettim_s -
		timadr=rec_time
	addl3	packet_length, #rec_hdrsize, r0
	addl2	#lanhdr_s_lanhdrdef, r0
	movw	r0, rec_rab+rab$w_rsz
	$put	rab=rec_rab
	if <lbc,r0> then
	  signal code=r0
	endif

	ret

	.end	tcpwatch
