  	.TITLE	MONLAV
	.IDENT	/V2.0/

; This program will dump information about Ethernet based clusters.  It
; uses the cluster multicast message to gather information about clusters.

; Author = David Gagne

	.LIBRARY	"SYS$LIBRARY:LIB.MLB"

	$IODEF				;Define I/O functions and modifiers
	$NMADEF				;Define Network Management parameters
	$LNMDEF				;Define logical name parameters
	$RMSDEF				;Define RMS data structs,status codes
	$SSDEF				; define System error codes
	$LIBDEF				; Define Library macros



; Modification history
;
;	2.0	David Gagne
;		01 Add the hardware address to the displays.

; RMS data structures for reading MONLAV.DAT

IO_FAB:	$FAB	FNM=<MONLAV.DAT>,ORG=SEQ,FAC=<GET>
IO_RAB:	$RAB	FAB=IO_FAB, UBF=RECORD, USZ=256


; QIO data structures for the LAVC channel.

; Setmode parameter buffer

SETPARM:
	.WORD	NMA$C_PCLI_PTY		; Protocol type
		.LONG	^X0760
	.WORD	NMA$C_PCLI_BFN		; Number of buffers to save
		.LONG	4
	.WORD	NMA$C_PCLI_BUS		; Max. receivable buffer size
		.LONG	1500
	.WORD	NMA$C_PCLI_MLT		; All multicast addresses
		.LONG	NMA$C_STATE_ON

SETPARMLEN = .-SETPARM

SETPARMDSC:
	.LONG		SETPARMLEN
	.ADDRESS	SETPARM

; Read buffer for LAVC multicast hello messages

RCVBUF:
	.BLKB	1504

RCVBUFLEN = .-RCVBUF

; Place to store incoming P5 buffer

RCVP5:
RCVDA:	.BLKB	6
RCVSA:	.BLKB	6
RCVPTY:	.BLKB	2


; QIO data structures for the MOP (SYSTEM ID) channel.

; P2 parameter buffer for finding devices of other nodes.

SIDPARM:
	.WORD	NMA$C_PCLI_PTY		; Protocol Type
		.LONG	^X0260

SIDPARMLEN = .-SIDPARM

SIDPARMDSC:
	.LONG		SIDPARMLEN
	.ADDRESS	SIDPARM

; P2 transmit data buffer for sending request SYSID messages.

XMTP2BUF:
	.BYTE	05			; SYSTEM ID request code
	.BYTE	00			; Reserved
XMTRCPT:
	.WORD	4242			; Receipt number (to be returned)

XMTP2LEN = .-XMTP2BUF

; Address of the node whose Device ID we would like to find.

SIDADDR:
	.BLKB	6

; Read buffer for receiving System IDs

RCVBUF2:
	.BLKB	1504

RCVBUFLEN2 = .-RCVBUF2

; Place to store incoming P5 buffer

RCVP52:
RCVDA2:	.BLKB	6
RCVSA2:	.BLKB	6
RCVPT2:	.BLKB	2

; Node name descriptor

NAMDSC:
	.LONG	6			; Node name is always 6 characters
	.LONG	RCVBUF+25		; Node name is always at byte 25
					; in the receive buffer
IDDESC:
	.LONG	40			; maximum Identifier string length
IDSTR:
	.BLKL	1			; address of ID string




; Message structures

; General message buffer

FAODESC:
FAOLEN:	.LONG		80
	.ADDRESS	FAOBUF
FAOBUF:
	.BLKB		80

HDRMSG1:
	.ASCID	"MONLAV - Ethernet LAVC monitoring program. Version 2.0"
INPMSG1:
	.ASCID	"Monitor requests available:"
INPMSG2:
	.ASCID	"  1) Monitor a single cluster node"
INPMSG3:
	.ASCID	"  2) Monitor all nodes in a specific cluster"
INPMSG4:
	.ASCID	"  3) Monitor all nodes in all clusters"
INPMSG5:
	.ASCID	"  any other input will stop the program"
IOMSG:
	.ASCID	"!/The contents of the 2nd longword in the IOSB is !XL"
NDMSG:
	.ASCID	"No device found.  Please define ETH appropriately."
DNEMSG:
	.ASCID	"MONLAV complete.  Thank you for your continued support."
BLNKMSG:
	.ASCID	""

; Output strings for monitoring a single cluster node

M1HDR0:
	.ASCID	"  Monitor output from a single cluster node"
M1HDR1:
	.ASCID	"  -----------------------------------------"
M1NAME:
	.ASCID	"    Node name:                 !AS"
M1NODE:
	.ASCID	"    Node number:               !2UB.!UW"
M1ADDR:
	.ASCID	"    Node address:              !XB-!XB-!XB-!XB-!XB-!XB"
M1GRUP:
	.ASCID	"    Cluster Group Number:      !UW"
M1CMCA:
	.ASCID	"    Cluster Multicast Address: AB-00-04-01-!XB-!XB"
M1IDEN:
	.ASCID	"    Identifier:                !AS"
M1DTYP:
	.ASCID	"    Ethernet controller:       !UB = !AS"
M1HWA:
	.ASCID	"    Hardware address:          !XB-!XB-!XB-!XB-!XB-!XB"
M1NHWA:
	.ASCID	"    Hardware address:          None found"
M1NOTC:
	.ASCID	"    This node is not in a cluster or cannot be monitored."

; Output strings for monitoring all nodes in a specific cluster

M2HDR0:
	.ASCID	"  Monitor output from all nodes in a specific cluster"
M2HDR1:
	.ASCID	"  ---------------------------------------------------"
M2GHDR:
	.ASCID	"  ====================================================                     "
M2GRUP:
	.ASCID	"  !UW) Cluster Group Number: !UW (AB-00-04-01-!XB-!XB)"
M2IDEN:
	.ASCID	"  Identifier: !AS"
M2HDR2:
	.ASCID	"       Name     Node      Address             Device      Hardware Address"
M2HDR3:
	.ASCID	"       ------   -------   -----------------   --------    -----------------"
M2INFO:
	.ASCID	"                                                                           "
M2INF1:
	.ASCID	"  !3UB) !6AS   !2UB.!UW     "
M2INF2:
	.ASCID	"AA-00-04-00-!XB-!XB   !2UB=!5AS    "
M2INF3:
	.ASCID	"!XB-!XB-!XB-!XB-!XB-!XB"
M2NIN3:
	.ASCID	"None found             "
M2NOTC:
	.ASCID	"    This cluster does not exist or cannot be monitored."

; Output strings for monitoring all nodes in all clusters

M3HDR0:
	.ASCID	"  Monitor output from all nodes in all clusters"
M3HDR1:
	.ASCID	"  ---------------------------------------------"
M3TRLR:
	.ASCID	"    Total of !UW clusters and !UL cluster nodes found"
M3NOTC:
	.ASCID	"    No clusters found."

; Strings for various device names

DEV01:	.ASCID	"DEUNA"
DEV05:	.ASCID	"DEQNA"
DEV11:	.ASCID	"DELUA"
DEV17:	.ASCID	"LANCE"
DEV23:	.ASCID	"DEBNA"
DEV37:	.ASCID	"DELQA"
DEV39:	.ASCID	"DESVA"
DEV65:	.ASCID	"DEBNI"
DEVNR:	.ASCID	"Noans"
DEVUK:	.ASCID	"Unkwn"
DEVMS:	.ASCID	"Misng"

DEVDSC:	.BLKQ	1			; General device name descriptor
DEVID:	.BLKB	1			; Device ID from SYSID response
HWA:	.BLKB	6			; Device HWA from SYSID response

; Buffers, variables, and strings for the time control of the program

HOURS:	.BLKB	1			; Storage of requested input
MINUTES:.BLKB	1
SECONDS:.BLKB	1

; The following variable are for reading input from the user after prompting.

INPSTRDSC:				; Input buffer descriptor
	.LONG		0
	.ADDRESS	INPSTR

INPSTR:	.BLKB		18		; Input buffer

INPSIZ:	.BLKL	1			

; The prompts are defined next.

RPRMT:	.ASCID	"Which monitor request would you like to make: "
HPRMT:	.ASCID	"How many hours   would you like to monitor: "
MPRMT:	.ASCID	"How many minutes would you like to monitor: "
SPRMT:	.ASCID	"How many seconds would you like to monitor: "
APRMT:	.ASCID	"Enter the address of the node (ex: AA-00-04-00-75-4C): "
CPRMT:	.ASCID	"Which Cluster Group Code: "

; Input variables from prompts

TESTNO:	.BLKB	1			; Test number
DSTADR: .BLKB	6			; Address to look for
CGC:	.BLKW	1			; Cluster Group Code to look for

ENDTIM:	.BLKQ	1			; Time to end test
TIME:	.BLKQ	1			; Temporary time buffer
DTIME:	.ASCID	/0 !2ZB:!2ZB:!2ZB.00/	; String for calculating delta time

; Miscellaneous variables

CMCA:	.BLKW	1			; Cluster Multicast Address (last
					; two bytes)
CLUSNO:	.BLKW	1			; Cluster number - used to count
					; the number of clusters
RCVTRY:	.BLKL	1			; Counter for receive attempts
IOSB:	.BLKQ	1			; I/O status block

; Device names

DEVDSC1:.ASCID	'ETH'			; Units to use for test
DEVDSC2:.ASCID	'ESA0'
DEVDSC3:.ASCID	'XQA0'
DEVDSC4:.ASCID	'ETA0'
DEVDSC5:.ASCID	'XEA0'
DEVDSC6:.ASCID	'EXA0'
DEVDSC7:.ASCID	'EZA0'

; Table of pointers to device names

DEVADR:	.ADDRESS	DEVDSC1
	.ADDRESS	DEVDSC2
	.ADDRESS	DEVDSC3
	.ADDRESS	DEVDSC4
	.ADDRESS	DEVDSC5
	.ADDRESS	DEVDSC6
	.ADDRESS	DEVDSC7
	.LONG	0

; Channels - one for LAVC and one for Remote Console

CHNLAV:	.BLKL	1
CHNRMC:	.BLKL	1

	.ENTRY	START,^M<>

; See if ther is a MONLAV.DAT file and read in the IDs if ther are any

	BSBW	DO_FILE

; Assign both channels to the first device found which is available

	CLRL	R5			; Check each channel name to see if one
10$:	TSTL	DEVADR(R5)		; is available until one is found: the
	BEQL	30$			; first name checked is "ETH", a dummy
	MOVL	DEVADR(R5),R4		; name which can be defined to the
	$ASSIGN_S-			; device desired if either:
		DEVNAM=(R4),-		;      1) An unregistered device is used
		CHAN=CHNLAV		;  or  2) One device is prefered
	BLBS	R0,20$			; If success, assign the 2nd channel
	ADDL	#4,R5			; Skip to next device name
	CMPW	R0,#SS$_NOSUCHDEV	; Was the error "no such device"?
	BEQL	10$			; If yes, try next device name
	BRW	ERROR			; Else, exit with error
20$:	$ASSIGN_S-			; Assign the 2nd channel to the same
		DEVNAM=(R4),-		; device name
		CHAN=CHNRMC
	BLBS	R0,ASSIGN_OK		; If success, continue
	BRW	ERROR			; Else, exit with an error

; No device was found.

30$:	BSBW	BLANK			; No device was found, so say so and
	PUSHAB	NDMSG			; then exit.
	CALLS	#1,G^LIB$PUT_OUTPUT
	BRW	EXIT

ASSIGN_OK:

; Start up the first channel for examining LAV packets.

	$QIOW_S	FUNC=#<IO$_SETMODE!IO$M_CTRL!IO$M_STARTUP>,-
		CHAN=CHNLAV,-
		IOSB=IOSB,-
		P2=#SETPARMDSC

	BLBS	R0,START_REQ_OK1
	BRW	ERROR

START_REQ_OK1:
	MOVZWL	IOSB,R0
	BLBS	R0,START_IO_OK1
	BRW	ERROR

START_IO_OK1:

; Start up the second channel for getting the device ID and name.

	$QIOW_S	FUNC=#<IO$_SETMODE!IO$M_CTRL!IO$M_STARTUP>,-
		CHAN=CHNRMC,-
		IOSB=IOSB,-
		P2=#SIDPARMDSC

	BLBS	R0,START_REQ_OK2
	BRW	ERROR

START_REQ_OK2:

	MOVL	IOSB,R0
	BLBS	R0,START_IO_OK2
	BRW	ERROR

START_IO_OK2:

; Print program header

	BSBW	BLANK
	PUSHAB	HDRMSG1
	CALLS	#1,G^LIB$PUT_OUTPUT

GET_TEST:

; Print the prompt that requests which monitor request the user wants.  This
; is also the top of the loop that allows the user to make multiple monitor
; requests.

	BSBW	BLANK
	PUSHAB	INPMSG1
	CALLS	#1,G^LIB$PUT_OUTPUT
	PUSHAB	INPMSG2
	CALLS	#1,G^LIB$PUT_OUTPUT
	PUSHAB	INPMSG3
	CALLS	#1,G^LIB$PUT_OUTPUT
	PUSHAB	INPMSG4
	CALLS	#1,G^LIB$PUT_OUTPUT
	PUSHAB	INPMSG5
	CALLS	#1,G^LIB$PUT_OUTPUT
	BSBW	BLANK

; Read the user's input to our prompt for the number of the test to run.

	MOVL	#2,INPSTRDSC		; Set number of bytes to read
	PUSHAB	INPSIZ			; Push input size parameter
	PUSHAB	RPRMT			; Push prompt string parameter
	PUSHAB	INPSTRDSC		; Push String descriptor parameter
	CALLS	#3,G^LIB$GET_INPUT	; Read the user's input

; Convert the input to a test number.

	CLRL	R1			; Start with zero in test number
	CLRL	R2			; Clear # of input characters done
10$:	CMPW	R2,INPSIZ		; Have we processed all the input?
	BGEQU	20$			; If EQL, yes
	MOVZBL	INPSTR(R2),R0		; Get an input character
	SUBL2	#^A/0/,R0		; Convert to a digit
	MULL2	#^D10,R1		; Shift current decimal digits
	ADDL2	R0,R1			; Add new digit
	INCL	R2			; Bump # of input characters done
	BRB	10$			; Check next character

; Clear the per-test global variables.

20$:	CLRL	NODCNT
	CLRB	HOURS
	CLRB	MINUTES
	CLRB	SECONDS

; Now perform the appropriate test based on the test number.

	MOVB	R1,TESTNO		; Store test number for later
	CMPL	R1,#1			; Check if test #1
	BNEQ	30$			; If NEQ, no
	BSBW	TEST_1			; Do test
	BRW	GET_TEST		; Check for another test to run
30$:	CMPL	R1,#2			; Check if test #2
	BNEQ	40$			; If NEQ, no
	BSBW	TEST_2			; Do test
	BRW	GET_TEST		; Check for another test to run
40$:	CMPL	R1,#3			; Check if test #3
	BNEQ	50$			; If NEQ, no
	BSBW	TEST_2			; Do test
	BRW	GET_TEST		; Check for another test to run

; Not a supported test, so exit.

50$:	BRW	EXIT			; Not any of above, so exit


; Test #1
;
; This test will monitor a single node.  It will request that the user enter
; the node's address.  We then wait for a message from that address.  If we
; don't get a message in 500 reads, then we exit and state that the node
; is not in a cluster or not able to be monitored.  If a message from that
; address is received, a report on that node is displayed.

TEST_1:

	BSBW	GET_TIME
	BSBW	BLANK
	BSBW	GET_ADDR		; Get the target node's address
	BSBW	SET_TIME

;==========================================================================
; Print the header

	BSBW	BLANK
	PUSHAB	M1HDR0
	CALLS	#1,G^LIB$PUT_OUTPUT
	PUSHAB	M1HDR1
	CALLS	#1,G^LIB$PUT_OUTPUT

; Read messages until we find one from the target node or until we've
; looked for the requested amount of time.

RCV_T1:

; Check if we've been looping for the requested amount of time.

	$GETTIM_S-			; Get current time
		TIMADR=TIME
	CMPL	TIME+4,ENDTIM+4		; Did we look long enough?
	BEQL	10$			; If equal, need to check low longword
	BLSSU	20$			; If LSSU, continue
	BRW	NOTC_T1			; Else end search for the node
10$:	CMPL	TIME,ENDTIM		; Check low longword
	BLSSU	20$			; If LSSU, continue
	BRW	NOTC_T1			; Else, end search for the node

20$:	$QIOW_S	FUNC=#IO$_READVBLK!IO$M_NOW,-
		CHAN=CHNLAV,-
		IOSB=IOSB,-
		P1=RCVBUF,-
		P2=#RCVBUFLEN,-
		P5=#RCVP5

	BLBS	R0,RCV_REQ_OK_T1
	BRW	ERROR

RCV_REQ_OK_T1:
	MOVZWL	IOSB,R0
	BLBS	R0,RCV_IO_OK_T1
	CMPW	R0,#SS$_ENDOFFILE
	BEQL	RCV_T1
	BRW	ERROR

RCV_IO_OK_T1:

; Check if this is a multicast message.  If not, throw it away and look for
; another message.

	BLBC	RCVDA,RCV_T1		; Ignore packet if sent to physical

; Now check to see if the source address is the desired one.

	CMPL	DSTADR,RCVSA		; First part of address okay?
	BNEQ	RCV_T1			; If not, read another one
	CMPW	DSTADR+4,RCVSA+4	; Second part of address okay?
	BEQL	PRINT_T1		; If so, print it
	BRW	RCV_T1			; Else, read another one

PRINT_T1:

; The address was the desired one, so print the results

;==========================================================================
; Print the node name

	MOVL	#80,FAOLEN
	$FAO_S	CTRSTR=M1NAME,-
		OUTLEN=FAOLEN,-
		OUTBUF=FAODESC,-
		P1=#NAMDSC
	PUSHAB	FAODESC
	CALLS	#1,G^LIB$PUT_OUTPUT

;==========================================================================
; Print the node number

	CMPL	#^X000400AA,DSTADR	; Is this a DECnet address?
	BNEQ	10$			; If NEQ, no, so don't print it
	MOVZWL	DSTADR+4,R0
	BICW	#^XFC00,R0
	MOVZBL	DSTADR+5,R1
	ASHL	#-2,R1,R1
	MOVL	#80,FAOLEN
	$FAO_S	CTRSTR=M1NODE,-
		OUTLEN=FAOLEN,-
		OUTBUF=FAODESC,-
		P1=R1,-
		P2=R0
	PUSHAB	FAODESC
	CALLS	#1,G^LIB$PUT_OUTPUT

;==========================================================================
; Print the node address

10$:	MOVL	#80,FAOLEN
	$FAO_S	CTRSTR=M1ADDR,-
		OUTLEN=FAOLEN,-
		OUTBUF=FAODESC,-
		P1=DSTADR,-
		P2=DSTADR+1,-
		P3=DSTADR+2,-
		P4=DSTADR+3,-
		P5=DSTADR+4,-
		P6=DSTADR+5
	PUSHAB	FAODESC
	CALLS	#1,G^LIB$PUT_OUTPUT

;==========================================================================
; Print the cluster group code number

	MOVL	#80,FAOLEN
	$FAO_S	CTRSTR=M1GRUP,-
		OUTLEN=FAOLEN,-
		OUTBUF=FAODESC,-
		P1=RCVBUF+6
	PUSHAB	FAODESC
	CALLS	#1,G^LIB$PUT_OUTPUT

;==========================================================================
; Print the cluster multicast address

	MOVW	RCVBUF+6,CMCA
	ADDW	#^D256,CMCA
	MOVL	#80,FAOLEN
	$FAO_S	CTRSTR=M1CMCA,-
		OUTLEN=FAOLEN,-
		OUTBUF=FAODESC,-
		P1=CMCA,-
		P2=CMCA+1
	PUSHAB	FAODESC
	CALLS	#1,G^LIB$PUT_OUTPUT

;==========================================================================
; Print the Ethernet controller ID and name

	MOVL	DSTADR,SIDADDR
	MOVW	DSTADR+4,SIDADDR+4
	BSBW	GET_IDHWA
	MOVL	#80,FAOLEN
	$FAO_S	CTRSTR=M1DTYP,-
		OUTLEN=FAOLEN,-
		OUTBUF=FAODESC,-
		P1=DEVID,-
		P2=#DEVDSC
	PUSHAB	FAODESC	
	CALLS	#1,G^LIB$PUT_OUTPUT

;========================================================================
; Check the IDTBL for an entry for this cluster group code and
; print the Identifier if one is found

	MOVZWL	RCVBUF+6,R0		; get the cluster group code of interest
	BSBW	FIND_ID			; try find an ID string for this
					; cluster group code
	CMPW	R0,#0			; did we find an ID?
	BEQL	DO_HWA			; no, => DO_HWA and continue

; Do the FAO for M1IDEN and print this ID

	MOVL	R0,IDSTR		; set up the template descriptor
	MOVL	#80,FAOLEN
	$FAO_S	CTRSTR=M1IDEN,-	; format the string
		OUTLEN=FAOLEN,-
		OUTBUF=FAODESC,-
		P1=#IDDESC
	PUSHAB	FAODESC	
	CALLS	#1,G^LIB$PUT_OUTPUT	; print it

;==========================================================================
; Print the Hardware address (if none, print "none found")

DO_HWA:
	CMPB	HWA,#^XFF
	BNEQ	20$
	PUSHAB	M1NHWA
	BRB	30$
20$:	MOVL	#80,FAOLEN
	$FAO_S	CTRSTR=M1HWA,-
		OUTLEN=FAOLEN,-
		OUTBUF=FAODESC,-
		P1=HWA,-
		P2=HWA+1,-
		P3=HWA+2,-
		P4=HWA+3,-
		P5=HWA+4,-
		P6=HWA+5
	PUSHAB	FAODESC	
30$:	CALLS	#1,G^LIB$PUT_OUTPUT

;==========================================================================

	RSB

NOTC_T1:

; We could not find a multicast message from the node, so print the
; appropriate output.

;==========================================================================
; Print the node number

	CMPL	#^X000400AA,DSTADR	; Is this a DECnet address?
	BNEQ	10$			; If NEQ, no, so don't print it
	MOVZWL	DSTADR+4,R0
	BICW	#^XFC00,R0
	MOVZBL	DSTADR+5,R1
	ASHL	#-2,R1,R1
	MOVL	#80,FAOLEN
	$FAO_S	CTRSTR=M1NODE,-
		OUTLEN=FAOLEN,-
		OUTBUF=FAODESC,-
		P1=R1,-
		P2=R0
	PUSHAB	FAODESC
	CALLS	#1,G^LIB$PUT_OUTPUT

;==========================================================================
; Print the node address

10$:	MOVL	#80,FAOLEN
	$FAO_S	CTRSTR=M1ADDR,-
		OUTLEN=FAOLEN,-
		OUTBUF=FAODESC,-
		P1=DSTADR,-
		P2=DSTADR+1,-
		P3=DSTADR+2,-
		P4=DSTADR+3,-
		P5=DSTADR+4,-
		P6=DSTADR+5
	PUSHAB	FAODESC
	CALLS	#1,G^LIB$PUT_OUTPUT

;==========================================================================
; Print the "node not in a cluster" message

	PUSHAB	M1NOTC
	CALLS	#1,G^LIB$PUT_OUTPUT

;==========================================================================

	RSB				; Return to caller


; Test #2 & Test #3
;
; 2) This test will monitor all nodes in a specific cluster.
; 3) This test will monitor all nodes in all clusters.

TEST_2:

	BSBW	GET_TIME
	BLBS	TESTNO,10$		; Skip over GET_CGC if test 3
	BSBW	BLANK
	BSBW	GET_CGC
10$:	BSBW	SET_TIME

;==========================================================================
; Print the header

	CLRW	CLUSNO			; Start with no clusters
	BSBW	BLANK
	BLBS	TESTNO,20$		; Skip over test 2 header if test 3
	PUSHAB	M2HDR0
	CALLS	#1,G^LIB$PUT_OUTPUT
	PUSHAB	M2HDR1
	CALLS	#1,G^LIB$PUT_OUTPUT
	BRB	RCV_T2			; Skip over test 3 header
20$:	PUSHAB	M3HDR0
	CALLS	#1,G^LIB$PUT_OUTPUT
	PUSHAB	M3HDR1
	CALLS	#1,G^LIB$PUT_OUTPUT

; Read and store messages for time requested.  Then print the results.

RCV_T2:

; Check if we've been looping for the requested amount of time.

	$GETTIM_S-			; Get current time
		TIMADR=TIME
	CMPL	TIME+4,ENDTIM+4		; Did we look long enough?
	BEQL	10$			; If equal, need to check low longword
	BLSSU	20$			; If LSSU, continue
	BRW	PRINT_T2		; Else stop monitoring
10$:	CMPL	TIME,ENDTIM		; Check low longword
	BLSSU	20$			; If LSSU, continue
	BRW	PRINT_T2		; Else, stop monitoring

20$:	$QIOW_S	FUNC=#IO$_READVBLK!IO$M_NOW,-
		CHAN=CHNLAV,-
		IOSB=IOSB,-
		P1=RCVBUF,-
		P2=#RCVBUFLEN,-
		P5=#RCVP5

	BLBS	R0,RCV_REQ_OK_T2
	BRW	ERROR

RCV_REQ_OK_T2:
	MOVZWL	IOSB,R0
	BLBS	R0,RCV_IO_OK_T2
	CMPW	R0,#SS$_ENDOFFILE
	BEQL	RCV_T2
	BRW	ERROR

RCV_IO_OK_T2:

; If this is not a multicast packet or if this packet is not for the cluster
; we are monitoring (test 2 only), then ignore the packet.

	BLBC	RCVDA,RCV_T2		; Ignore packet if sent to physical
	BLBS	TESTNO,10$		; Skip over CGC check if test 3
	CMPW	CGC,RCVBUF+6		; Ignore packet if not in the
	    				; specified cluster
	BNEQ	RCV_T2			; If NEQ, then wrong cluster
	
; Check to see if this node is already in the node table.  To match, the
; address and the cluster group code have to match an entry in the table.
; This allows us to log nodes that were in more than one cluster during the
; time of the monitoring.

10$:	CLRL	R1			; Clear the number of entries checked
	MOVAL	NODTBL,R2		; Start at beginning of table
20$:	CMPL	R1,NODCNT		; Have we checked the entire table?
	BGEQU	40$			; If GEQU, yes, so insert the node
	CMPL	RCVSA,(R2)		; Does the address match?
	BNEQ	30$			; If NEQ, no, try next table entry
	CMPW	RCVSA+4,4(R2)		; Does address match
	BNEQ	30$			; If NEQ, no, try next table entry
	CMPW	RCVBUF+6,6(R2)		; Does cluster group code match?
	BEQL	50$			; If EQL, yes, node already in table
30$:	INCL	R1			; Bump our counter
	ADDL	#NODSIZ,R2		; Bump table entry pointer
	BRB	20$			; Check next entry

; The node was not in the table, so add it if there are entries left.  If
; there is space, the R2 is already pointing to an empty slot.

40$:	CMPL	NODCNT,#MAXNOD		; Is the table full?
	BGEQU	PRINT_T2		; If GEQU, yes, so print results

; Store this node's information.

	MOVL	RCVSA,(R2)+		; Store 1st part of address
	MOVW	RCVSA+4,(R2)+		; Store 2nd part of address
	MOVW	RCVBUF+6,(R2)+		; Store cluster group code
	MOVL	RCVBUF+25,(R2)+		; Store 1st part of name
	MOVW	RCVBUF+29,(R2)+		; Store 2nd part of name
	MOVL	RCVSA,SIDADDR		; Set up to call GET_IDHWA
	MOVW	RCVSA+4,SIDADDR+4	; Set up to call GET_IDHWA
	PUSHL	R2
	BSBW	GET_IDHWA		; Get device information
	POPL	R2
	MOVB	DEVID,(R2)+		; Store Device ID
	TSTB	(R2)+			; Skip spare byte
	MOVQ	DEVDSC,(R2)+		; Store Device name descriptor
	MOVL	HWA,(R2)+		; Store HWA
	MOVW	HWA+4,(R2)+		; Store HWA
	INCL	NODCNT			; One more node stored
50$:	BRW	RCV_T2			; Read next message

PRINT_T2:

; If this is test 2, we should just start printing.  But if we do all the
; same checking for test 2 as we do for test 3, the output will still be
; the same.  So we will not special case test 2.

; If no nodes were found, print the appropriate message.

	TSTL	NODCNT			; Any nodes found?
	BNEQ	PRINT_T2NEXT		; Print the cluster information
	BSBW	BLANK
	BLBS	TESTNO,10$		; If test3, goto 10$
	INCW	CLUSNO			; Count this cluster
	MOVW	CGC,CMCA
	ADDW	#^D256,CMCA
	PUSHAB	M2GHDR
	CALLS	#1,G^LIB$PUT_OUTPUT
	MOVL	#80,FAOLEN
	$FAO_S	CTRSTR=M2GRUP,-
		OUTLEN=FAOLEN,-
		OUTBUF=FAODESC,-
		P1=CLUSNO,-
		P2=CGC,-
		P3=CMCA,-
		P4=CMCA+1
	PUSHAB	FAODESC
	CALLS	#1,G^LIB$PUT_OUTPUT
	BSBW	BLANK
	PUSHAB	M2NOTC
	BRB	20$
10$:	PUSHAB	M3NOTC
20$:	CALLS	#1,G^LIB$PUT_OUTPUT
	RSB

PRINT_T2NEXT:

; Locate the lowest cluster group code in the table (other than zero).  If
; none are found, then exit.

	BSBW	GET_SMCGC		; Get the smallest CGC in the table
	TSTW	CGC			; Did we get one?
	BNEQ	PRINT_CLUSTER		; If NEQ, yes, so print a report
	BLBC	TESTNO,10$		; If test2, goto 10$
	BSBW	BLANK
	BSBW	BLANK
	PUSHAB	M2GHDR
	CALLS	#1,G^LIB$PUT_OUTPUT
	MOVL	#80,FAOLEN
	$FAO_S	CTRSTR=M3TRLR,-
		OUTLEN=FAOLEN,-
		OUTBUF=FAODESC,-
		P1=CLUSNO,-
		P2=NODCNT
	PUSHAB	FAODESC
	CALLS	#1,G^LIB$PUT_OUTPUT
10$:	RSB

PRINT_CLUSTER:

; Print the information about each entry in the table.

;==========================================================================
; Print the cluster group number/multicast address and header

	BSBW	BLANK
	TSTW	CLUSNO			; Is this the first cluster?
	BEQL	10$			; If EQL, yes, only one blank line
	BSBW	BLANK
10$:	INCW	CLUSNO			; Count this cluster
	MOVW	CGC,CMCA
	ADDW	#^D256,CMCA
	PUSHAB	M2GHDR
	CALLS	#1,G^LIB$PUT_OUTPUT
	MOVL	#80,FAOLEN
	$FAO_S	CTRSTR=M2GRUP,-
		OUTLEN=FAOLEN,-
		OUTBUF=FAODESC,-
		P1=CLUSNO,-
		P2=CGC,-
		P3=CMCA,-
		P4=CMCA+1
	PUSHAB	FAODESC
	CALLS	#1,G^LIB$PUT_OUTPUT

;========================================================================
; Check the IDTBL for an entry for this cluster group code and
; print the Identifier if one is found

	MOVZWL	CGC,R0			; get the cluster group code of interest
	BSBW	FIND_ID			; try find an ID string for this
					; cluster group code
	CMPW	R0,#0			; did we find an ID?
	BEQL	DO_HDR			; no, => DO_HWA and continue

; Do the FAO for M2IDEN and print this ID

	MOVL	R0,IDSTR		; set up the template descriptor
	MOVL	#80,FAOLEN
	$FAO_S	CTRSTR=M2IDEN,-		; format the string
		OUTLEN=FAOLEN,-
		OUTBUF=FAODESC,-
		P1=#IDDESC
	PUSHAB	FAODESC	
	CALLS	#1,G^LIB$PUT_OUTPUT	; print it

;==========================================================================
; Print the table header

DO_HDR:
	BSBW	BLANK
	PUSHAB	M2HDR2
	CALLS	#1,G^LIB$PUT_OUTPUT
	PUSHAB	M2HDR3
	CALLS	#1,G^LIB$PUT_OUTPUT

;==========================================================================
; Print the information about each node - in a loop

	CLRL	R1			; Start our node counter at zero
20$:	BSBW	GET_NODE		; Get next node to print
	TSTL	R2			; Did we get one?
	BNEQ	30$			; If NEQ, yes, so print it
	BRW	PRINT_T2NEXT		; Else we're done with this cluster
30$:	CLRW	6(R2)			; Clear the CGC so we don't look at
					; this entry any more in GET_NODE
	INCL	R1			; Bump our counter - for printing
	MOVL	08(R2),RCVBUF+25	; Move the node name into place
	MOVW	12(R2),RCVBUF+29	; Move the node name into place
	CLRQ	R3			; Assume no node number
	CMPL	#^X000400AA,(R2)	; Is this a DECnet address?
	BNEQ	40$			; If NEQ, no, so don't print it
	MOVZWL	4(R2),R4		; Calculate node number
	BICW	#^XFC00,R4
	MOVZBL	5(R2),R3
	ASHL	#-2,R3,R3
40$:	MOVQ	R1,-(SP)
	MOVL	#80,FAOLEN		; Get the first part of the
	$FAO_S	CTRSTR=M2INF1,-		; output ready
		OUTLEN=FAOLEN,-
		OUTBUF=FAODESC,-
		P1=R1,-
		P2=#NAMDSC,-
		P3=R3,-
		P4=R4
	MOVC3	FAOLEN,FAOBUF,M2INFO+8	; Copy the 1st part into M2INFO
	MOVQ	(SP)+,R1
	MOVQ	16(R2),DEVDSC		; Retrieve Device name descriptor
	MOVQ	R1,-(SP)
	MOVL	#80,FAOLEN
	$FAO_S	CTRSTR=M2INF2,-
		OUTLEN=FAOLEN,-
		OUTBUF=FAODESC,-
		P1=4(R2),-
		P2=5(R2),-
		P3=14(R2),-
		P4=#DEVDSC
	MOVC3	FAOLEN,FAOBUF,M2INFO+8+26
					; Copy the 2nd part into M2INFO
	MOVQ	(SP)+,R1		; Retrieve R2
	MOVQ	R1,-(SP)
	CMPB	24(R2),#^XFF		; Is there a HWA?
	BNEQ	50$			; Br if yes
	MOVC3	#17,M2NIN3+8,M2INFO+8+58; Copy "none found" into M2INFO
	BRB	60$			; Continue in common code path
50$:	MOVL	#80,FAOLEN
	$FAO_S	CTRSTR=M2INF3,-
		OUTLEN=FAOLEN,-
		OUTBUF=FAODESC,-
		P1=24(R2),-
		P2=25(R2),-
		P3=26(R2),-
		P4=27(R2),-
		P5=28(R2),-
		P6=29(R2)
	MOVC3	FAOLEN,FAOBUF,M2INFO+8+58
					; Copy the 3rd part into M2INFO
60$:	PUSHAB	M2INFO
	CALLS	#1,G^LIB$PUT_OUTPUT
	MOVQ	(SP)+,R1
	BRW	20$			; Go to top of loop


GET_IDHWA:

; Get the node's device ID, device name, and HWA.  This is done by requesting
; a SYSID message from the node.  The response (if any) contains the device
; ID.  From the device ID, we will select the correct device name.  Upon
; completion of this routine, DEVID (a byte) will have the device ID and
; DEVDSC will point to the device name.  The response also contains the
; hardware address.  Upon completion of this routine, HWA will contain the
; hardware address or FF in the first byte if none was found.

; Transmit the request ID message to the node we want to find out about.

	$QIOW_S	FUNC=#IO$_WRITEVBLK,-
		CHAN=CHNRMC,-
		IOSB=IOSB,-
		P1=XMTP2BUF,-
		P2=#XMTP2LEN,-
		P5=#SIDADDR

	BLBS	R0,DXMT_REQ_OK
	BRW	ERROR

DXMT_REQ_OK:

	MOVL	IOSB,R0
	BLBS	R0,DXMT_IO_OK
	BRW	ERROR

DXMT_IO_OK:

; Receive the SYSID response.  If there is no data ready to receive, then the
; Ethernet driver will return immediately with SS$_ENDOFFILE.

	CLRL	RCVTRY			; Clear the number of read attempts

DRCV:

	$QIOW_S	FUNC=#IO$_READVBLK!IO$M_NOW,-
		CHAN=CHNRMC,-
		IOSB=IOSB,-
		P1=RCVBUF2,-
		P2=#RCVBUFLEN2,-
		P5=#RCVP52

	BLBS	R0,DRCV_REQ_OK
	BRW	ERROR

DRCV_REQ_OK:

	MOVL	IOSB,R0
	BLBS	R0,DRCV_IO_OK
	CMPW	IOSB,#SS$_ENDOFFILE
	BEQL	DRCV_TRY
	BRW	ERROR

DRCV_TRY:
	CMPL	RCVTRY,#4000		; Have we tried enough times?
	BGTR	20$			; If GTR, yes, so message is lost
	INCL	RCVTRY			; Count this attempt
	BRB	DRCV			; Try again
20$:	CLRB	DEVID			; Clear the Device ID number
	MOVQ	DEVNR,DEVDSC		; Set the device name to no response
	MOVB	#^XFF,HWA		; Set "no HWA found"
	RSB				; Return to caller

DRCV_IO_OK:

; Check that this is a SYSTEM ID message from the correct node.

	CMPB	RCVBUF2,#7		; Is this a SYSTEM ID message?
	BNEQ	DRCV_TRY		; If NEQ, no, try again
	CMPL	RCVSA2,SIDADDR		; Correct node?
	BNEQ	DRCV_TRY		; If NEQ, no
	CMPW	RCVSA2+4,SIDADDR+4	; Correct node?
	BNEQ	DRCV_TRY		; If NEQ, no

; We received a message from the remote node.  Look for the Device ID entry
; in this SYSID message.

	MOVL	#4,R3			; Skip over SYSID header

; Loop through the entries in the SYSID looking for the Device ID entry.

10$:	CMPW	R3,IOSB+2		; Any buffer left to look at?
	BLSSU	20$			; If LSSU, yes, so look for more
	BRW	210$			; Else report Device ID missing
20$:	CMPW	RCVBUF2(R3),#^D100	; Is this the device ID entry?
	BEQL	30$			; If EQL, yes
	MOVZBL	RCVBUF2+2(R3),R1	; Get size of this entry
	ADDL	#3,R3			; Skip over entry type and size
	ADDL	R1,R3			; Skip over entry value
	BRW	10$			; Check next entry

; The Device ID was found.  Store the Device ID and select a device name.

30$:	ADDL	#3,R3			; Skip over Device ID header
	MOVB	RCVBUF2(R3),DEVID	; Store the Device ID
	CMPB	DEVID,#^D01		; Is this Device 1?
	BNEQ	40$			; Branch if not
	MOVQ	DEV01,DEVDSC		; Store Device Name for ID 1
	BRW	300$			; Exit
40$:	CMPB	DEVID,#^D05
	BNEQ	50$
	MOVQ	DEV05,DEVDSC
	BRW	300$
50$:	CMPB	DEVID,#^D11
	BNEQ	60$
	MOVQ	DEV11,DEVDSC
	BRB	300$
60$:	CMPB	DEVID,#^D17
	BNEQ	70$
	MOVQ	DEV17,DEVDSC
	BRB	300$
70$:	CMPB	DEVID,#^D23
	BNEQ	80$
	MOVQ	DEV23,DEVDSC
	BRB	300$
80$:	CMPB	DEVID,#^D39
	BNEQ	90$
	MOVQ	DEV39,DEVDSC
	BRB	300$
90$:	CMPB	DEVID,#^D37
	BNEQ	100$
	MOVQ	DEV37,DEVDSC
	BRB	300$
100$:	CMPB	DEVID,#^D65
	BNEQ	200$
	MOVQ	DEV65,DEVDSC
	BRB	300$
200$:	MOVQ	DEVUK,DEVDSC		; This is an unknown device
	BRB	300$
210$:	CLRB	DEVID			; No Device ID was found
	MOVQ	DEVMS,DEVDSC		; Device ID is missing

; Now look for the Hardware address in the SYSID response.

300$:	MOVL	#4,R3			; Skip over SYSID header

; Loop through the entries in the SYSID looking for the HWA entry.

310$:	CMPW	R3,IOSB+2		; Any buffer left to look at?
	BLSSU	320$			; If LSSU, yes, so look for more
	BRW	340$			; Else report HWA missing
320$:	CMPW	RCVBUF2(R3),#^D7	; Is this the HWA entry?
	BEQL	330$			; If EQL, yes
	MOVZBL	RCVBUF2+2(R3),R1	; Get size of this entry
	ADDL	#3,R3			; Skip over entry type and size
	ADDL	R1,R3			; Skip over entry value
	BRW	310$			; Check next entry

; The HWA was found.  Store the HWA.

330$:	ADDL	#3,R3			; Skip over HWA header
	MOVL	RCVBUF2(R3),HWA		; Store first longword
	MOVW	RCVBUF2+4(R3),HWA+4	; Store last word
	RSB
340$:	MOVB	#^XFF,HWA		; Set "no HWA found"
	RSB


GET_ADDR:

; Get the address to look for from the user.

	MOVL	#18,INPSTRDSC		; Maximum input size is 18 bytes
	PUSHAB	INPSIZ			; Place to store # of bytes entered
	PUSHAB	APRMT			; Prompt to display
	PUSHAB	INPSTRDSC		; Place to store input string
	CALLS	#3,G^LIB$GET_INPUT

	CLRL	R1			; Index to next input character
	CLRL	R3			; Index to next address byte

; Get the first digit of the next address byte.

10$:	MOVZBL	INPSTR(R1),R0		; Get next input character
	INCL	R1			; Bump input character index
	CMPB	R0,#^A/A/		; Is this a letter or greater?
	BGEQU	20$			; If GEQU, yes, so branch
	SUBL3	#^A/0/,R0,R2		; Convert digit character to digit
	BRB	30$			; Get the next input character
20$:	SUBL2	#^A/A/,R0		; Convert letter character to digit
	ADDL3	#^D10,R0,R2		; Add ten to have correct digit

; Get the second digit of the next address byte.

30$:	MULL2	#^D16,R2		; Put the first digit into its place
	MOVZBL	INPSTR(R1),R0		; Get next input character
	INCL	R1			; Bump input character index
	CMPB	R0,#^A/A/		; Is this a letter or greater?
	BGEQU	40$			; If GEQU, yes, so branch
	SUBL2	#^A/0/,R0		; Convert digit character to digit
	BRB	50$			; Go to add the second digit
40$:	SUBL2	#^A/A/,R0		; Convert letter character to digit
	ADDL2	#^D10,R0		; Add ten to have correct digit
50$:	ADDL	R0,R2			; Add lower digit to address

; Store this address byte and see if we need to do more.

	MOVB	R2,DSTADR(R3)		; Store this address byte
	INCL	R3			; Bump to next address byte
	CMPL	R3,#^D06		; Have we processed 6 bytes?
	BLSS	60$			; Branch if not, more to do
	RSB				; Else return to caller
60$:	INCL	R1			; Skip over "-" in input string
	BRW	10$			; Do next address byte


GET_CGC:

; Get cluster group code to watch for.

10$:	MOVL	#5,INPSTRDSC
	PUSHAB	INPSIZ
	PUSHAB	CPRMT
	PUSHAB	INPSTRDSC
	CALLS	#3,G^LIB$GET_INPUT
	TSTW	INPSIZ			; Do we have any input?
	BEQL	10$			; If not, ask again

; Convert character string to cluster group code.

	CLRL	R1			; Start with zero
	CLRW	R2			; Start at offset 0 in string

20$:	MOVZBL	INPSTR(R2),R0		; Get next input character
	SUBL2	#^A/0/,R0		; Convert character to a digit
	MULL2	#^D10,R1		; Shift current results
	ADDL2	R0,R1			; Add new digit
	INCW	R2			; Increment counter/index
	CMPW	R2,INPSIZ		; Have we processed all characters?
	BLSSU	20$			; If LSSU, no, so loop again
	MOVW	R1,CGC			; Store cluster group code
	RSB

GET_TIME:

; Get number of hours to run test for

GET_HOUR:
	BSBW	BLANK
	MOVL	#6,INPSTRDSC
	PUSHAB	INPSIZ
	PUSHAB	HPRMT
	PUSHAB	INPSTRDSC
	CALLS	#3,G^LIB$GET_INPUT

; Convert number of hours

	CLRL	R1
	CLRW	R2
NUM_LOOP2:
	MOVZBL	INPSTR(R2),R0
	SUBL2	#^A/0/,R0
	MULL2	#^D10,R1
	ADDL2	R0,R1
	INCW	R2
	CMPW	R2,INPSIZ
	BLSS	NUM_LOOP2
	CMPL	R1,#^D23
	BGTR	GET_HOUR
	MOVL	R1,HOURS

; Get number of minutes to run test for

GET_MINUTE:
	MOVL	#6,INPSTRDSC
	PUSHAB	INPSIZ
	PUSHAB	MPRMT
	PUSHAB	INPSTRDSC
	CALLS	#3,G^LIB$GET_INPUT

; Convert number of minutes

	CLRL	R1
	CLRW	R2
NUM_LOOP3:
	MOVZBL	INPSTR(R2),R0
	SUBL2	#^A/0/,R0
	MULL2	#^D10,R1
	ADDL2	R0,R1
	INCW	R2
	CMPW	R2,INPSIZ
	BLSS	NUM_LOOP3
	CMPL	R1,#^D59
	BGTR	GET_MINUTE
	MOVL	R1,MINUTES

; Get number of seconds to run test for

GET_SECOND:
	MOVL	#6,INPSTRDSC
	PUSHAB	INPSIZ
	PUSHAB	SPRMT
	PUSHAB	INPSTRDSC
	CALLS	#3,G^LIB$GET_INPUT

; Convert number of seconds

	CLRL	R1
	CLRW	R2
NUM_LOOP4:
	MOVZBL	INPSTR(R2),R0
	SUBL2	#^A/0/,R0
	MULL2	#^D10,R1
	ADDL2	R0,R1
	INCW	R2
	CMPW	R2,INPSIZ
	BLSS	NUM_LOOP4
	CMPL	R1,#^D59
	BGTR	GET_SECOND
	MOVL	R1,SECONDS
	RSB

SET_TIME:

; Determine the time to stop the test

; Now put the total time into one string

	MOVL	#80,FAOLEN
	$FAO_S	CTRSTR=DTIME,-
		OUTLEN=FAOLEN,-
		OUTBUF=FAODESC,-
		P1=HOURS,-
		P2=MINUTES,-
		P3=SECONDS

; Change the ASCII string for the time to a quadword value.

	$BINTIM_S-
		TIMBUF=FAODESC,-
		TIMADR=TIME
	MNEGL	TIME+4,TIME+4
	MNEGL	TIME,TIME
	SBWC	#0,TIME+4

; Now get the present time and add the test time to get the end time.

	$GETTIM_S-
		TIMADR=ENDTIM
	ADDL	TIME,ENDTIM
	ADWC	TIME+4,ENDTIM+4
	RSB


GET_SMCGC:

; Locate the smallest CGC in the table (other than zero) and store its value
; in CGC.  If none are found, store a zero in CGC.

	MOVQ	R0,-(SP)		; Save R0 and R1
	CLRL	R1			; Start our counter at zero
	CLRL	R0			; Start with no CGC found
	MOVAL	NODTBL,R2		; Start at beginning of table

10$:	TSTW	6(R2)			; Is this a node in the table?
	BEQL	30$			; If EQL, no, so check next entry

; Is this node the first node found?  If so, save it, else compare its CGC
; with the smallest CGC found so far.

	TSTL	R0			; Did we find a CGC yet?
	BEQL	20$			; If EQL, no, so save this one
	CMPW	6(R2),R0		; Is the new one smaller?
	BGEQU	30$			; If GEQU, no, so skip this node
20$:	MOVW	6(R2),R0		; Save this as the smallest CGC

; Skip to next node and continue if there are more nodes to look at.

30$:	ADDL	#NODSIZ,R2		; Skip to next entry in table
	INCL	R1			; Bump node counter
	CMPL	R1,NODCNT		; Are we done?
	BLSSU	10$			; If LSSU, no, so loop
	MOVW	R0,CGC			; Return smallest CGC or zero
	MOVQ	(SP)+,R0		; Restore R0 and R1
	RSB


GET_NODE:

; Locate the next node in the table to be printed.  The Cluster Group Code
; must match the value in CGC.  We will locate the node in the matching
; cluster with the lowest address.  For now we assume all addresses are
; DECnet addresses.  We will return the address of the table entry that
; has the smallest address (within the cluster) in R2.  If there is no entry
; for this cluster, then we will return a zero in R2.

	MOVQ	R0,-(SP)		; Save R0 and R1
	CLRL	R1			; Start our counter at zero
	CLRL	R0			; Start with no node found
	MOVAL	NODTBL,R2		; Start at beginning of table

10$:	CMPW	6(R2),CGC		; Is this a node in our cluster?
	BNEQ	30$			; If NEQ, no, so check next node

; Is this node the first node found?  If so, save it, else compare it with
; the smallest node found so far.

	TSTL	R0			; Did we find a node yet?
	BEQL	20$			; If EQL, no, so save this one
	CMPW	4(R2),4(R0)		; Is the new one smaller?
	BGEQU	30$			; If GEQU, no, so skip this node
20$:	MOVL	R2,R0			; Save this as the smallest node

; Skip to next node and continue if there are more nodes to look at.

30$:	ADDL	#NODSIZ,R2		; Skip to next entry in table
	INCL	R1			; Bump node counter
	CMPL	R1,NODCNT		; Are we done?
	BLSSU	10$			; If LSSU, no, so loop
	MOVL	R0,R2			; Return pointer to smallest node
	MOVQ	(SP)+,R0		; Restore R0 and R1
	RSB


EXIT:
	BSBW	BLANK
	PUSHAB	DNEMSG
	CALLS	#1,G^LIB$PUT_OUTPUT
	BSBW	BLANK

	$DASSGN_S-
		CHAN=CHNLAV
	$DASSGN_S-
		CHAN=CHNRMC

	$EXIT_S


BLANK:

; Print blank line

	PUSHAB	BLNKMSG
	CALLS	#1,G^LIB$PUT_OUTPUT
	RSB


; An error has occured, so print R0 value and 2nd longword of IOSB.

ERROR:	PUSHL	R0			; Pass R0 error code
	CALLS	#1,RBL$ERCODE		; Print error code

; Print IOSB 2nd longword value in hex

	MOVL	#80,FAOLEN
	$FAO_S	CTRSTR=IOMSG,-
		OUTLEN=FAOLEN,-
		OUTBUF=FAODESC,-
		P1=IOSB+4

	PUSHAB	FAODESC
	CALLS	#1,G^LIB$PUT_OUTPUT

	BRW	EXIT			; Now exit normally

	.PAGE

; RBL$ERCODE
;
; This subroutine accepts a VMS error code in binary
; and print the message on the terminal.
;
; INPUT:	4(AP) = Error Code
;
; OUTPUT:	all registers saved except R0 and R1
;
; CALLING SEQ:	PUSHx	error code
;		CALL_S	#1,RBL$ERCODE
;

	.ENTRY	RBL$ERCODE,^M<R3,R4>

	MOVL	4(AP),R3		; Get error code

	$GETMSG_S-			; Convert error code to message
		MSGID=R3,-
		MSGLEN=ERRMSG_LEN,-
		BUFADR=ERRMSG_BUF_DESC

	PUSHAB	ERRMSG_BUF_DESC
	CALLS	#1,G^LIB$PUT_OUTPUT

	RET				; Return to caller

; DO_FILE
; This is a macro program to open a text file MONLAV.DAT and read records
; which are basically a cluster group number followed by an ASCII styring 
; of characters which are the identifier for a given cluster.
;
; Inputs: None
;
; Outputs: 
;	The records in the text file are converted and placed in the table
; 	IDTBL as pairs of cluster group numbers, and up to 40 characters
;	of text.

DO_FILE:
; fill the IDTBL with spaces

	MOVAB	IDTBL,R0		; address of the ID table
	MULL3	#IDSIZ,#MAXID,R1	; size of IDTBL => R1
	MOVC5	#0,(R0),#^X20,-		; fill with spaces ( 020 hex )
		R1,(R0)

; OPEN the MONLAV.DAT file
	$OPEN		FAB=IO_FAB	; open the file
	BLBS	R0,2$			; branch if no error
	BRW	DO_FILE.ERR		; error
2$:	$CONNECT	RAB=IO_RAB	; connect the RAB
	BLBS	R0,10$			; branch if no errror
	BRW	DO_FILE.ERR		; error

10$:
; clear out the data area used to read in records

	MOVAB	RECORD,R0	; get the address of the record storage area
	MOVC5	#0,(R0),#0,-	; fill it with zeros
		#256,(R0)
; Read a record
	CMPL	IDCNT,#MAXID	; have we reached max allowable entries?
	BLSSU	15$		; no keep going @ 15$ with read of next record
	$CLOSE	FAB=IO_FAB	; yes, close the file and ...
	BRW	100$		; => 100$

15$:	$GET	RAB=IO_RAB		; get the next record in the file
	CMPL	#RMS$_EOF, R0		; end of file?
	BNEQ	20$			; no => 20$
	$CLOSE	FAB=IO_FAB		; yes, close the file
	BRW	100$			; go to 100$
20$:
	BLBS	R0,22$			; branch if no error
	BRW	DO_FILE.ERR		; error
22$:	MOVL	#0,SKIPLEN		; we have processed zero characters
					; form this record so far
	MOVAB	RECORD,R2		; get the addresss of the beginnning
					; of the string
	BSBW	SKIPWS			; skip white space & control characters
					; updating SKILEN, and R2 as it goes
	BSBW	FLDLEN			; get the length of the first field
	CMPB	R0,#MAXCGLEN		; is string too long?
	BLEQ	25$			; branch if not too too long
	MOVW	SS$_SSFAIL,R0
	BRW	DO_FILE.ERR

; Convert the leading numeric characters to an integer

25$:	CLRL	R1			; Start with zero as CGN
	CLRL	R4			; Clear # of input characters done
30$:	CMPW	R4,R0			; Have we processed the field
	BGEQU	40$			; If EQL, yes
	MOVZBL	(R2),R3			; Get an input character
	SUBL2	#^A/0/,R3		; Convert to a digit
	MULL2	#^D10,R1		; Shift current decimal digits
	ADDL2	R3,R1			; Add new digit
	INCL	R4			; Bump # of input characters done
	INCL	R2
	INCL	SKIPLEN			; count up the characters already 
					; processsed
	BRB	30$			; Check next character

40$:
; the conversion should be done, check the result

	CMPL	R1,#65536		; is it too large?
	MOVL	#SS$_SSFAIL,R0		; assume failure
	BLEQU	41$
	BRW	DO_FILE.ERR			; yes, => error

; check the identifier string for illegal characters in the first 40

41$:	MOVAB	RECORD,R2	; get the beginning of the record
	ADDL	SKIPLEN,R2	; add the characters already processed
	BSBW	SKIPWS		; skip any more white space
	BSBW	STRLEN		; get the number of characters in the ID 
				; string
	CMPL	R0,#40		; is it too long?
	BLEQ	50$		; no, its OK,.. => 40$ and continue
	MOVL	#40,R0		; its too long,... only take the first 40
50$:

; add the new cgn/identifier to the 

	MOVAB	IDTBL,R3
	MULL3	#IDSIZ,IDCNT,R4	; size of laready inserted entry
	ADDL	R4,R3		; make R3 point to the next available entry
	MOVW	R1,(R3)+	; store the cluster group number
	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; preserve registers
	MOVC3	R0,(R2),(R3)	; copy the identifier
	POPR	#^M<R0,R1,R2,R3,R4,R5>	; preserve registers
	INCL	IDCNT		; count the new identifier
	BRW	10$		; do next entry

100$:
	MOVL	IDCNT,R4	; get the number of IDs logged into the table
	MOVAL	IDTBL,R5

;110$:	MOVAL	2(R5),IDDSC+4
;	MOVL	#80,FAOLEN
;	$FAO_S	CTRSTR=NONAME,-
;		OUTLEN=FAOLEN,-
;		OUTBUF=FAODESC,-
;		P1=(R5),-
;		P2=#IDDSC
;	PUSHAB	FAODESC
;	CALLS	#1,G^LIB$PUT_OUTPUT
;
;	ADDL	#IDSIZ,R5
;	SOBGTR	R4,110$
;
DO_FILE.ERR:
	RSB				; all done

SKIPWS:
;
; INPUTS:
;	R2 - the address of a character string which may or may not contain
;	     white space at the beginning...
;
; OUTPUTS:
;	R2 will poont to the first "non-white space" character in the string
;	  or a carriage return
;	SKIPLEN is incremented for each white space character found

SKIPWS.NEXT:
	CMPB	(R2),#^X15	; is it a carriage return?
	BEQL	SKIPWS.DONE	; if yes => DONE
	CMPB	(R2),#^X021	; is it a white space character
	BLSSU	SKIPWS.CONT	; yes, => NEXT
SKIPWS.DONE:
	RSB			; no,... we're done!
SKIPWS.CONT:
	INCL	SKIPLEN		; show that we skipped another character in this
				; string
	INCL	R2		; point to the next character
	BRB	SKIPWS		; go and check next character

FLDLEN:
;
; This routine returns the length of the next field in a string,...
; fields are seperated by spaces, tabs, or CR's
;
; INPUTS:
;	R2 - points to the beginning of the field
;
; OUTPUTS:
;	R0 contains the length in characters of the field
;
	PUSHL	R2
	MOVL	#0,R0			; set up R0 as an index
FLDLEN.NEXT:
	CMPB	(R2),#^A' '		; is next character white space?
	BLEQ	FLDLEN.DONE		; yes => DONE
	INCL	R0			; add one more to the length
	INCL	R2			; point to the next char
	BRB	FLDLEN.NEXT		; do the next
FLDLEN.DONE:
	POPL	R2			; restore R2
	RSB				; all done

STRLEN:
;
;	This routine returns the length of a string up to but not including
;	a tab or a carriage return
;
; INPUTS:
;	R2 points to the beginning of the string
;
; OUTPUTS:
;	R0 will contain the length of the string
;
	CLRL	R0		; zero the length output
STRLEN.NEXT:
	CMPB	(R2)[R0],#^X15	; is the next character a carriage control?
	BEQL	STRLEN.DONE	; yes,... => DONE
	CMPB	(R2)[R0],#^X10	; is the next character a tab?
	BEQL	STRLEN.DONE	; yes,... => DONE
	CMPB	(R2)[R0],#^X020	; is it a space or better?
	BLSSU	STRLEN.DONE	; no,.. its a control character,.. its illegal
				; so return now with the length of legal
				; characters in R0
	INCL	R0		; bump the length
	BRB	STRLEN.NEXT	; do next character
STRLEN.DONE:
	RSB


; FIND_ID
;
; This routine searches the IDTBL for an entry with the cluster group
; code that matches what is spoecified in GGC
;
; Inputs:
;	R0 contains the cluster group code of interest
;
; Outputs:
;	R0 will point to the first character in the ID string
;	   if one is found,.. 
;	OR
;	R0 = 0 if no match
;
FIND_ID:
	PUSHR	#^M<R1,R2>	; preserve registers
	CLRL	R2		; count of loops made
	MOVAB	IDTBL,R1	; base address of ID table
FIND_ID.NEXT:
	CMPL	R2,IDCNT	; have we already searched the whole table?
	BGEQU	FIND_ID.NOMATCH	; yes => NOMATCH
	CMPW	R0,(R1)		; does this entry match?
	BEQL	FIND_ID.MATCH	; yes, => match
	INCL	R2		; count the number of entries searched
	ADDL	#IDSIZ,R1	; make R1 point to the next entry
	BRB	FIND_ID.NEXT	; check the next entry

FIND_ID.MATCH:

	ADDL3	#2,R1,R0	; store the address of the ID string in R0
	BRB	FIND_ID.EXIT	; exit

FIND_ID.NOMATCH:

	MOVL	#0,R0		; Indicate no match found
;	BRB	FIND_ID.EXIT	; exit

FIND_ID.EXIT:
	POPR	#^M<R1,R2>	; restore registers
	RSB


; Structures used to build error message from error code

ERRMSG_BUF_DESC:
ERRMSG_LEN:
	.LONG	256
	.ADDRESS-
		ERRMSG_BUF
ERRMSG_BUF:
	.BLKB	256


; Table for information on nodes already found.

; Each entry consists of:
;
;	6 bytes of node address
;	2 bytes of cluster group code
;	6 bytes of node name
;	1 byte  of DEVICE ID
;	1 byte  of spare info
;	8 bytes of DEVICE NAME descriptor
;	6 bytes of hardware address (1st byte is FF if none)

NODSIZ = 30
MAXNOD = 1000

NODCNT:	.LONG	0
NODTBL:	.BLKB	MAXNOD*NODSIZ


; Symbolic constants and data structures for storing the Identifier Strings
; from MONLAV.DAT

NONAME:
	.ASCID	"!5UW = !AS"
IDDSC:
	.LONG	40
	.LONG	0

RECORD:					; to hold a single RNS record read from
	.BLKL	256			; MONLAV.DAT


MAXCGLEN = 5				; maximum length of the ascii string
					; that can represent a CGN
IDSIZ = 42				; size of a valid ID entry,...
					; 2 bytes for Cluster group number
					; 40 bytes for the ID string
MAXID = 100				; we will deal with up to 100 IDs

SKIPLEN:	.BLKL	1		; count of characters already 
					; processed for a given record from the 
					; MONLAV.DAT file
IDCNT:	.LONG	0			; number of IDs
IDTBL:	.BLKB	MAXID*IDSIZ		; Table of IDs

	.END	START





