Google
 

Trailing-Edge - PDP-10 Archives - QT020_T20_4.1_6.1_SWSKIT_851021 - swskit-hacks/eisrv.mac
There are no other files named eisrv.mac in the archive.
	TITLE	EISRV - Ethernet server

COMMENT \

	EISRV  is not meant as a fully functional server.  It is a demonstration
piece, written to show how the NI% jsys can be used.  In particular, it delivers
packets containing asciz strings, only, and does not guarantee order of
delivery.  As it currently stands, it can do the following functions across
systems connected by an Ethernet cable:

	Report on the load average of another system.
	Determine if <username> is logged in on another system.
	Determine if <name> is signed on with EISRV at a remote node, and return
	 his EISRV number.
	Deliver a packet of text to a process that has "signed on" with EISRV.

	Signing on is accomplished by senfding an IPCF page containing a unique
name string.  It responds with an IPCF message that OKs or rejects the
sign on.  Thereafter, IPCFs packets from that process are interpreted as
commands to the server.  One such command might be "deliver this text to
EISRV user 12 on system FOOBAR."  Another might be "have the server on system
FROBAZ return the load average there."

	Transmissions over the Ethernet cable can be "lost."  This occurs
when two systems try to use the cable to transmit simultaneously.  When this
happens, the sending system can normally determine that a failure occured
and try again, and the NI% jsys implements this.  However, if the receiving
process did not "post a buffer" to receive the message, the message will
arrive and immediately be dropped, and the sender is not made aware of this
condition.  Hence, in this server, all messages sent cause an acknowledgment
message to be returned.  If an acknowledgment is not received ffor any
given message, the original sender retransmits it.  If a message is
retransmitted several times and still not acknowledged, the receiving
node is assumed to be dead.

	The NI% jsys can work in either interrupt or non-interrupt mode.
This code demonstrates receiving in interrupt mode.
\

	SEARCH	MONSYM,MACSYM

	ftlogf==1			;Have the server write a log file

	F=0
	T1==1
	T2==2
	T3==3
	T4==4
	T5==5
	T6==6
	T7==7


	P==17

;FLAGS
	F.SHUT==1B0	;SHUTDOWN SOON
	F.WAIT==1B1	;WAITING (WAIT JSYS), OK TO BREAK FROM JSYS
	F.SKIP==1B2	;BREAK FROM WAIT IF WAITING
	F.TBPA==1B3	;ENTRY NEEDS ADDING TO TBP
 ;FLAGS IN RH SHOULD NOT BE TOUCHED BY INTERRUPT ROUTINES
	F.INTE==1B35	;CALLER OF ROUTINE HAD INTERRUPTS ENABLED

	NIRINT==1	;NI RECEIVED INTERRUPT CHANNEL
	NICINT==2	;NI CHANGE OF STATE INTERRUPT
	TIMINT==3	;TIMER INTERRUPT
	IPRINT==4	;IPCF RECEIVED INTERRUPT

	PROTOC=1137	;ARBITRARIALLY CHOSEN ETHERNET "PORT" NUBER

	MSGLEN==1440	;MAXIMUM LENGTH OF TEXT SENDABLE IN ONE PACKET

	MAXUSR==70	;MAXIMUM NUMBER OF USERS THAT CAN SIGNON

; offsets in the first page of a buffer in freespace.  Do not change the
;  order here.
	NXTBUF==0	;next buffer (MUST BE IN 0 POSITION)
	SIZBUF==1	;number of pages in this buffer
	PNTBUF==2	;pointer into MAPFRE used by DELPGS
	TIMBUF==3	;time stamp (optional)
	ERRBUF==4	;# times Ack hasn't come
	SRCBUF==5	;source of buffer (0 if from NI, PID if from MRECV)
	USRBUF==6	;user# the buffer is from (0 if not known)
	NODBUF==7	;pointer to node entry this buffer is from
	CREBUF==10	;the caller's PC
	DATAST==11	;first word available for data

;FORMAT OF NODES QUEUE
	$NDSAD==0	;where ethernet address lives (2 words)
	$NDNAM==2	;asciz name of node (2 words)
	$NDFLG==4	;flags, see below
	$NDOFF==11	;last command # entry added at offset
	$NDCMD==12	;list of old command #'s (FINLEN long)

	ND%OFF==1B0	;node seems to have died

;INTERNAL NI-NI MESSAGE FORMAT (NUMBER OF 8 BIT BYTES/FIELD)
	$SIZE==0	;free(2), #bytes data after $CMD(2)
	$NUM==1		;msg#(4)
	$SAD==2		;first four bytes of SAD(4)
	$SUSR==3	;last two bytes of SAD(2), sender user#(2)
	$TYP==4		;dest user#(2), type, flags
	$CMD==5		;CMD, transaction #(3)
	$ARGS==6	;data for the command

	MS%NOA==1	;Message needs/expects no Ack
	MS%NEW==2	;New node message

;USER LIST FORMAT
	U$LEN==8	;LENGTH OF ENTRIES
	U$NUM==0	;FLAGS,,USER#
	U$PID==1	;PID
	U$NAM==2	;USER NAME

	U%LAST==1B0	;BOTTOM ENTRY

;IPCF MSG FORMAT
	I$CMD==0	;COMMAND WORD
	I$NUM==1	;NUMBER OF TRANSACTION
	I$ARG==2	;ARGS

;VARIOUS CONSTANTS
	FINLEN==550
	TIMWAT=23
	IPRPAG==77
	FRESPC==IPRPAG+1
	NUMPAG==760-FRESPC+1

	IPRBUF=IPRPAG*1000

	.SCVMX==2	;Max allowed NI to NI message type
	.IPMAX==6	;Max legal IPCF command number

	ifn ftlogf,<
	DEFINE	CALL(where),<
	 PUSHJ	P,[
		SKIPN	LOGJFN
		JRST	where
		PUSHJ	P,BLAT
		[ASCIZ/
 /]
		PUSH	P,T2
		AOS	T2,DEEP
		PUSHJ	P,OCTLOG
		PUSHJ	P,BLAT
		[ASCIZ/	CALL 'where', from /]
		HRRZ	T2,-1(P)
		SUBI	T2,1
		PUSHJ	P,OCTLOG
		POP	P,T2
		JRST	where] >

	DEFINE	RET,<
	 JRST	[
		SKIPN	LOGJFN
		POPJ	P,
		PUSHJ	P,BLAT
		[ASCIZ/
 /]
		PUSH	P,T2
		SOS	T2,DEEP
		ADDI	T2,1
		PUSHJ	P,OCTLOG
		POP	P,T2
		PUSHJ	P,BLAT
		[ASCIZ/ returning /]
		POPJ	P,] >

 >

ST:	RESET%
	SETOM	INTLCK#		;INTERRUPT ENABLE/DISABLE FLAG
	MOVE	P,[-100,,STACK-1] ;GET STACK
	CALL	SETPGS		;SET UP FREESPACE
	SETZB	F,ZERO		;CLEAR FLAGS AND VERIABLES
	MOVE	T1,[ZERO,,ZERO+1]
	BLT	T1,ENDZER
	SETOM	USRLST		;NO USERS SIGNED ON YET

	MOVX T1,.RCADR		; Get LLMOP function code
	MOVX T2,LLMBLK		; Get arg block address
	LLMOP%			; Read our local address
	 OURSAD=LLMBLK+.LMPYA	; WHERE LOCAL ADDRESS CAN BE FOUND
;get nodename, 8bit asciz, 6 bytes, in OURNAM, and 7bit asciz in MYNAME
	MOVEI	T1,.NDGLN
	MOVEI	T2,T3
	HRROI	T3,MYNAME
	NODE%
	MOVE	T1,[POINT 7,MYNAME]
	MOVE	T2,[POINT 8,OURNAM]
CPYNDM:	ILDB	T3,T1		;COPY INTO OURNAM
	IDPB	T3,T2
	JUMPN	T3,CPYNDM

;OPEN UP INTERRUPT CHANNELS
	MOVX	T1,.FHSLF
	MOVE	T2,[LEVTAB,,CHNTAB]
	SIR%
	EIR%
	MOVX	T2,1B<NIRINT>+1B<NICINT>+1B<TIMINT>+1B<IPRINT>
	AIC%
;OPEN PORTAL
	MOVX	T1,.EIOPN
	HRRM	T1,NIBLOK+.EIFCN
	MOVEI	T1,NIBLOK
	NI%				;OPEN A PORTAL TO TALK THROUGH
	 ERCAL	FATAL			;PROBABLY ALREADY IN USE
	HRRZ	T1,NIPID
	HRRM	T1,NIINTB+.EIPID
	MOVX	T1,.EIEMA		;RECEIVE MULTICASTS
	HRRM	T1,NIBLOK+.EIFCN
	DMOVE	T4,MULTI
	DMOVEM	T4,NIBLOK+.EIAR1
	MOVEI	T1,NIBLOK
	NI%
	 ERCAL	FATAL

	HRROI	T1,[ASCIZ/
EISRV, job /]
	PSOUT%				;TYPE STARTUP BANNER
	SETO	T1,
	HRROI	T2,T3
	MOVEI	T3,.JIJNO
	GETJI%
	 ERJMPS	.+1
	MOVE	T2,T3
	CALL	DECOUT
	IFE ftlogf,<
	HRROI	T1,[ASCIZ/, detaching...
/]
	PSOUT%
BYEBYE:	DTACH%
>
	SETZM	LOGJFN#
	IFN ftlogf,<
	MOVX	T1,GJ%FOU+GJ%SHT
	HRROI	T2,[ASCIZ/PS:EISRV.LOG/]
	GTJFN%
	 ERJMPS	NOLOG
	MOVE	T2,[7B5+OF%WR]
	OPENF%
	 ERJMPS	NOLOG
	MOVEM	T1,LOGJFN
NOLOG:>

;POST 1ST RECEIVING BUFFERS
	MOVX	T1,.FHSLF
	MOVX	T2,1B<NIRINT>		;POST RECEIVE BUFFERS BY FAKING AN..
	IIC%				;NI RECEIVED INTERRUPT

;SEND MULTICAST HELLO
RECAST:	AOS	NI.CST			;TELL OTHER NODES WE ARE ONLINE NOW
	MOVEI	T1,1
	CALL	GETPGS			;GET 1 PAGE OF MEMORY
	DMOVE	T3,OURNAM		;BUILD "HELLO" MESSAGE
	DMOVEM	T3,DATAST+.BXBMX+$ARGS(T2)
	MOVEI	T1,6
	MOVEI	T3,100		;MULTICAST HELLO COMMAND NUMBER
	SETZ	T4,		;DEST IS SERVER
	CALL	SNDSTD
	MOVX	T1,MS%NOA+MS%NEW ;NO ACK NEEDED, AND NEW NODE MESSAGE
	DPB	T1,[POINT 8,DATAST+.BXBMX+$TYP(T2),31] ;SO SET FLAGS
	SETZM	DATAST+.BXBMX+$NUM(T2)	;MSG NUMBER 0
	MOVEI	T3,NIBLOK
	DMOVE	T4,MULTI
	CALL	SNDNIM		;SAY "HELLO, EVERYONE!"
	MOVEI	T1,^D8*^D1000
	DISMS%			;WAIT FOR RESPONSES
	SKIPN	EADQUE		;IF WE GOT ANY RESPONSES, THEN EVERYONE...
	JRST	RECAST		;HEARD, OTHERWISE, TRY AGAIN

	CALL	DIRINT		;TURN OFF INTERRUPTS FOR A MOMENT
	MOVEI	T1,4
	MOVEI	T2,IPCFSI
	MSEND%			;GET A PID, DECALRE OURSELF TO SYS[INFO]
	 ERCAL	FATAL
	MOVE	T1,SRVPID
	MOVEM	T1,MRECVB+.IPCFR
	MOVEM	T1,MUTBLK+1
	MOVX	T1,.MUPIC
	MOVEM	T1,MUTBLK
	MOVEI	T1,3
	MOVEI	T2,MUTBLK
	MUTIL%
	 ERCAL	FATAL
	CALL	EIRINT		;HANDLE INTERRUPTS AGAIN

;ENTER THE "DO" LOOP
;ONCE THE WAIT% JSYS IS STARTED, THE INTERRUPT ROTUTINES ARE THE ONLY
; THINGS ACTIVE.  IF THEY PUT SOMETHING IN THE TBP QUEUE, THEY WILL
; ABORT THE WAIT% JSYS.
DOLOOP:	TXNE	F,F.SHUT	;SHUTDOWN REQUESTED SOMEWHERE?
	CALL	FATAL		;YES, DIE
	SKIPE	TBPQUE		;ANYTHING IN THE "TO BE PROCESSED" QUEUE?
	CALL	DOTASK		;YES, DO TASKS
	TXO	F,F.WAIT	;WE ARE ENTERING A WAIT STATE, OK TO BREAK
	TXZN	F,F.SKIP	;HAVE WE BEEN ASKED NOT TO WAIT?
	WAIT%			;WAIT FOR INTERRUPT
WAITIN:	TXZ	F,F.SKIP+F.WAIT	;CLEARING WAIT STATE
	JRST	DOLOOP		;GO LOOP FOR THINGS TO DO

DOTASK:	CALL	DIRINT			;SO WE CAN MODIFY THE QUEUE SAFELY
	MOVE	T2,TBPQUE	;PULL THE TOP REQUEST OUT
	MOVE	T3,NXTBUF(T2)
	MOVEM	T3,TBPQUE
	MOVEM	T2,TSKINP#	;STORE REQUEST BLOCK ADDRESS
	CALL	EIRINT		;ENABLE INTERRUPTS
	JUMPE	T2,NXTTSK	;IF NO REQUEST HERE, CHECK FOR NEXT REQUEST
	SKIPN	SRCBUF(T2)	;FROM LOCAL USER OR OVER WIRE?
	JRST	NITASK		;BUFFER PUT ON QUEUE BY NIRECV
 ifn ftlogf,<
	PUSHJ	P,BLAT
	[ASCIZ/
IPCF command begun: /]
	PUSH	P,T2
	MOVE	T2,DATAST(T2)
	PUSHJ	P,OCTLOG
	POP	P,T2 >
	MOVE	T3,USRBUF(T2)	;LOAD USER NUMBER OR 0
	SKIPL	T1,DATAST(T2)	;FETCH COMMAND; LEGAL?
	CAILE	T1,.IPMAX	;??
	JRST	NXTTSK
	CAIN	T3,0		;FROM A KNOWN USER?
	JUMPN	T1,WDYTUA	;NO; COMMAND OTHER THAN SIGNON?
	JRST	@[EXP	SIGNON, TEXTOT, LAREQU, DMPNDS, USRFNR, WHOISN, STATS
		EXP	BLATRQ, FNJOBU](T1)
;LEAP WITH T2/ BUFFER FROM TBP (TSKINP ALSO)
;T3/ USER #

SIGNON:	SETZ	T1,		;MAKE SURE SIGNON NAME TERMINATES
	DPB	T1,[POINT 8,DATAST+.BXBMX+I$ARG+5(T2),35]
	MOVE	T4,SRCBUF(T2)
	AOS	T1,USRCNT	;DO WE HAVE ROOM FOR ANOTHER?
	CAIL	T1,MAXUSR	;CHECK...
	JRST	SNFAL1		;NO...
	MOVEI	T6,USRLST	;SET UP TO SCAN
	SETZ	T3,		;NO FREE ENTRY SEEN YET
SCNNUS:	HRRE	T5,U$NUM(T6)	;FAKE ENTRY?
	JUMPL	T5,NXTNUS	;YES, SKIP ON
	JUMPN	T5,SCNCOL	;NO, FREE ENTRY?
	CAIN	T3,0		;YES, DO WE HAVE ONE ALREADY?
	MOVEI	T3,(T6)		;NO, REMEMBER IT
	JRST	NXTNUS		;AND CAN'T BE A COLLISION, GO ON
SCNCOL:	CAMN	T4,U$PID(T6)	;PID ALREADY IN TABLE?
	JRST	SNFAL3		;YES, GO AWAY
	HRROI	T1,U$NAM(T6)	;CHECK FOR NAME
	MOVE	T2,TSKINP
	HRROI	T2,DATAST+I$ARG(T2)
	STCMP%
	JUMPE	T1,SNFAL2	;NAME ALREADY IN?
NXTNUS:	ADDI	T6,U$LEN	;NO PROBLEM YET, ADVANCE
	SKIPL	U$NUM-U$LEN(T6)	;WAS LAST ENTRY REALLY LAST?
	JRST	SCNNUS		;NO, GO ON
	CALL	DIRINT		;TO PLAY WITH USER QUEUE
	CAIN	T3,0		;DID WE FIND A FREE ENTRY?
	MOVEI	T3,(T6)		;NO, USE END OF LIST
	AOS	T5,USERNM#	;ASSIGN UNIQUE NUMBER TO USER
	TRZ	T5,700000	;15 BITS ONLY
	HRRM	T5,U$NUM(T6)	;SET
	MOVEM	T4,U$PID(T6)	;SET PID
	MOVE	T2,TSKINP
	MOVSI	T2,DATAST+I$ARG(T2) ;BLT POINTER TO NAME...
	HRRI	T2,U$NAM(T6)	;DEST POINTER..
	BLT	T2,U$LEN-1(T6)	;COPY IN
	SKIPL	U$NUM-U$LEN(T6)	;IS ABOVE ENTRY THE OLD LAST?
	JRST	USEROK		;NO, DONE
	MOVX	T2,U%LAST	;YES, MUST CLEAR FOR OLD AND SET FOR NEW
	IORM	T2,U$NUM(T6)	;SET FOR NEW
	ANDCAM	T2,U$NUM-U$LEN(T6) ;CLEAR FOR OLD
USEROK:	CALL	EIRINT			;DONE
	PUSH	P,T4		;SAVE USER'S PID
	MOVEI	T1,2
	CALL	GETPGS		;NEED TO TELL SYS[INFO]
	POP	P,1000+.IPCI2(T2) ;WATCH USER'S PID
	MOVEI	T1,.IPCIK
	MOVEM	T1,1000+.IPCI0(T2) ;SET FUNCTION
	SETZ	T1,		;0 IS SYS[INFO]
	CALL	MSGUSR
	SETZM	IPSCMD#		;COMMAND TO RETURN
	JRST	DMPND2		;RETURN SAME AS DUMP NODES COMMAND

USRFNR:	MOVEI	T1,4
	JRST	ASCCMD
BLATRQ:	MOVEI	T1,7
	JRST	ASCCMD
FNJOBU:	MOVEI	T1,10
	JRST	ASCCMD
TEXTOT:	MOVEI	T1,1		;SEND TEXT COMMAND
ASCCMD:	MOVEM	T1,NISCMD#	;SAVE FOR A MOMENT
GENASN:	MOVEI	T1,1		;GET A PAGE TO SEND
	CALL	GETPGS
	MOVEM	T2,MSGNIS#	;SAVE LOC
	MOVE	T1,TSKINP	;GET LOC OF REQUEST
	SETZM	DATAST+5+^D1500/4(T1)	;MAKE SURE USER STRING TERMINATES
	HRROI	T1,DATAST+I$ARG+3(T1) ;POINT TO STRING
	HRROI	T2,DATAST+.BXBMX+$ARGS(T2) ;WILL COPY HERE IN 8 BIT
	CALL	CPYSTR		;COPY IT, RETURN LEN IN T4
	CAILE	T4,^D1401	;ALLOW THIS LENGTH
	MOVEI	T4,^D1401	;TOO BAD, TRUNCATE
	MOVEI	T1,(T4)		;MOVE LENGTH TO T1
	MOVE	T2,MSGNIS	;BUFFER ADDR IN T2
	MOVE	T3,NISCMD	;CMD NUMBER IN T3
	MOVE	T4,TSKINP	;GET USER NUMBER TO SEND TO IN T4
	MOVE	T4,DATAST+I$ARG+2(T4)	;..
GENMSG:	SKIPG	CREBUF(T2)
	CALL	FATAL
	MOVEM	T2,GENTMP#
	CALL	SNDSTD		;SET UP MSG BLOCK
	MOVE	T1,TSKINP
	CALL	IPRSTD		;SET UP IPCF SENDER'S INFO
	MOVEI	T3,NIBLOK	;GET BLOCK TO USE FOR SEND
	CALL	SNDNIM		;AND SEND IT
	JRST	NXTTSK		;NEXT OP?

LAREQU:	MOVEI	T1,1
	CALL	GETPGS
	MOVEI	T3,2		;COMMAND: LOAD AVERAGE REQUEST
	SETZB	T1,T4		;NO ARGS, NO USER DESTINATION
	JRST	GENMSG		;FIRE A GENERAL NI MESSAGE

DMPNDS:	MOVEI	T1,3		;IPCF RESPONSE: NODELIST
	MOVEM	T1,IPSCMD	;REMEMBER IT
DMPND2:	MOVEI	T1,2		;2 PAGES FOR IPCF SEND (ENTRY FROM SIGNON)
	CALL	GETPGS
	MOVE	T1,IPSCMD	;WHAT IPCF RESPONSE NUMBER?
	MOVEM	T1,1000+I$CMD(T2)	;PUT IN COMMAND SLOT
	SETOM	1000+I$NUM(T2)	;NOT A NUMBERED TRANSACTION
	MOVEI	T4,1000+I$ARG(T2) ;POINTER TO BUILD RESPONSE WITH
	SKIPA	T3,EADQUE	;PERUSE THE NODES QUEUE
DMPND3:	MOVE	T3,NXTBUF(T3)	;ADVANCE
	JUMPE	T3,DMPNDE	;AT END, GO FINISH AND SEND
	SKIPGE	DATAST+$NDFLG(T3) ;DETELED ENTRY?
	JRST	DMPND3		;YES, ADVANCE
	MOVSI	T1,DATAST+$NDSAD(T3) ;NO, BLT INFO IN
	HRRI	T1,(T4)		;..
	BLT	T1,3(T4)	;..
	ADDI	T4,4		;ADVANCE DEST POINTER
	JRST	DMPND3		;GET NEXT NODE
DMPNDE:	MOVE	T1,TSKINP
	MOVE	T1,SRCBUF(T1)
	CALL	MSGUSR		;T1/PID, T2/BUFFER
	JRST	NXTTSK

WHOISN:	MOVEI	T1,1
	CALL	GETPGS
	MOVE	T1,TSKINP
	MOVE	T4,DATAST+I$ARG+2(T1)
	MOVEI	T3,7
	SETZ	T1,
	CALL	SNDSTD
	MOVE	T1,TSKINP
	CALL	IPRSTD
	MOVEI	T3,NIBLOK
	CALL	SNDNIM
	JRST	NXTTSK

STATS:	MOVEI	T1,2
	CALL	GETPGS
	MOVEI	T1,5
	MOVEM	T1,1000+I$CMD(T2)
	SETOM	T1,1000+I$NUM(T2)
	MOVE	T1,[NISTL,,REALUS-NI.STA+1]
	MOVEM	T1,1000+I$ARG(T2)
	MOVSI	T1,NI.STA
	HRRI	T1,1000+I$ARG+1(T2)
	BLT	T1,1000+I$ARG+NI.LEN(T2)
	MOVE	T1,TSKINP
	MOVE	T1,SRCBUF(T1)
	CALL	MSGUSR
	JRST	NXTTSK


SNFAL1:	SETO	T1,		;NO ROOM FOR ANOTHER USER
	JRST	CANTDO
SNFAL2:	HRROI	T1,-2		;DUPLICATE NAME
	JRST	CANTDO
SNFAL3:	HRROI	T1,-3		;DUPLICATE PID
	JRST	CANTDO
WDYTUA:	HRROI	T1,-4		;PLEASE SIGNON MSG
CANTDO:	MOVEM	T1,USRERR#
	MOVEI	T1,2
	CALL	GETPGS
	MOVE	T1,USRERR
	MOVEM	T1,1000(T2)
	MOVE	T1,TSKINP
	MOVE	T1,SRCBUF(T1)
	CALL	MSGUSR
	;JRST	NXTTSK

NXTTSK:	SKIPE	T1,TSKINP
	CALL	RELPGS
ANOTHE:	SKIPN	TBPQUE
	RET
	JRST	DOTASK

NITASK: ifn ftlogf,<
	PUSHJ	P,BLAT
	[ASCIZ/
Starting NI task: /]
	LDB	T2,[POINT 8,DATAST+.BXBMX+$CMD(T2),7]
	PUSHJ	P,OCTLOG >
	MOVE	T2,TSKINP
	MOVE	T1,T2
	ADD	T1,[POINT 8,DATAST+.BXBMX+$ARGS]
	MOVEM	T1,TSKDAT#			;POINTER TO ARGS IN TSKDAT
	LDB	T3,[POINT 16,DATAST+.BXBMX+$SIZE(T2),31] ;# BYTES IN T3
	ADJBP	T3,T1
	SETZ	T1,
	IDPB	T1,T3
	LDB	T1,[POINT 8,DATAST+.BXBMX+$CMD(T2),7]
	CAIL	T1,100		;HELLO/GOODBYE STUFF?
	JRST	HELLO		;GO HANDLE
	CAILE	T1,11
	JRST	BADCMD
	MOVE	T5,DATAST+.BXBMX+$NUM(T2)
	MOVE	T6,NODBUF(T2)
	AOS	T7,DATAST+$NDOFF(T6)
	CAIL	T7,FINLEN
	SETZB	T7,DATAST+$NDOFF(T6)
	ADDI	T6,(T7)
	MOVEM	T5,DATAST+$NDCMD(T6)
NIJUMP:	JRST	@[EXP	NITNUL, TEXTDL, LAASK, NXTTSK, FNDUSR, FONUSR, NDELI
		EXP	WHOISQ, BLATTM, JOBIN](T1)
BADCMD:	IFN	ftlogf,<
	PUSHJ	P,BLAT
	[ASCIZ/
Bad NI cmd received/]
	>
	JRST	NXTTSK
NITNUL:	IFN	ftlogf,<
	PUSHJ	P,BLAT
	[ASCIZ/
NULL NI CMD received/]
	>
	JRST	NXTTSK


TEXTDL:	LDB	T2,[POINT 16,DATAST+.BXBMX+$TYP(T2),15] ;GET DEST USER
	JUMPE	T2,CANTDM		;I DON'T ACCEPT TEXT MESSAGES
	CALL	FNDUSN			;DO WE HAVE A PID FOR THIS USER?
	JUMPE	T1,CANTDM		;SAY CANT DELIVER MAIL
	MOVEM	T1,USRPID#
	MOVEI	T1,2			;IPCF OUT
	CALL	GETPGS
	MOVEI	T1,1			;COMMAND TYPE 1
	CALL	IPSSTD
	MOVE	T7,[POINT 7,1000+I$ARG+3(T2)]
CPYMGU:	ILDB	T1,T4
	IDPB	T1,T7
	SOJG	T5,CPYMGU
	IDPB	T5,T7
	MOVE	T1,USRPID
	CALL	MSGUSR
	JRST	NXTTSK

;Call with T2/ IPCF buffer and TSKINP/ NI request buffer, T1/ CMD to return
; Returns T4/ byte pointer to data portion of the NI request buffer
IPSSTD:	MOVEM	T1,1000+I$CMD(T2)
	MOVE	T4,TSKINP
	LDB	T1,[POINT 24,DATAST+.BXBMX+$CMD(T4),31]
	MOVEM	T1,1000+I$NUM(T2)
	DMOVE	T5,DATAST+.BXSAD(T4)
	DMOVEM	T5,1000+I$ARG(T2)
	LDB	T1,[POINT 16,DATAST+.BXBMX+$SUSR(T4),31]
	MOVEM	T1,1000+I$ARG+2(T2)
	LDB	T5,[POINT 16,DATAST+.BXBMX+$SIZE(T4),31]
	ADD	T4,[POINT 8,DATAST+.BXBMX+$ARGS]
	RET

CANTDM:	MOVEM	T2,GONEUN#	;USER NUMBER THAT IS GONE
	MOVEI	T1,1
	CALL	GETPGS
	MOVEM	T2,TSKOUT#
	MOVE	T1,TSKINP
	MOVE	T3,DATAST+.BXBMX+$NUM(T1)
	MOVEM	T3,DATAST+.BXBMX+$NUM(T2)
	MOVE	T3,GONEUN
	DPB	T3,[POINT 16,DATAST+.BXBMX+$SUSR(T2),31] ;SENDER IS DEAD USER
	LDB	T4,[POINT 16,DATAST+.BXBMX+$SUSR(T1),31] ;GET RECEIVER
	SETZ	T1,
	MOVEI	T3,6
	CALL	SNDSTD
	MOVE	T6,TSKINP
	DMOVE	T4,DATAST+.BXSAD(T6)	;GET SOURCE TO SEND BACK TO
	MOVEI	T3,NIBLOK
	CALL	SNDNIM
	JRST	NXTTSK

LAASK:	MOVEI	T1,1
	CALL	GETPGS
	MOVEM	T2,LASTMP#
	HRROI	T1,[ASCIZ/Node /]
	ADD	T2,[POINT 8,DATAST+.BXBMX+$ARGS]
	CALL	CSTRB
	HRROI	T1,MYNAME
	CALL	CSTRB
	HRROI	T1,[ASCIZ/ Load average	/]
	CALL	CSTRB
	MOVE	T1,[14,,.SYSTA]
	GETAB%
	 SETZ	T1,
	EXCH	T1,T2
	MOVE	T3,[FL%ONE+FL%PNT+2B17+3B23+2B29]
	FLOUT%
	 ERJMPS	.+1
	MOVEI	T1,100
	MOVE	T2,LASTMP
	MOVEI	T3,1
	CALL	NINI
	JRST	NXTTSK

FNDUSR:	MOVEI	T1,1
	CALL	GETPGS
	MOVEM	T2,TSKOUT
	MOVEI	T4,USRLST
	MOVE	T1,TSKDAT
	MOVEM	T1,USNMAT#	;WORST CASE, RETURN USER'S STRING
SCNINL:	HRROI	T1,U$NAM(T4)
	MOVE	T2,TSKDAT
	STCMP%
	JUMPE	T1,SGNYES
	ADDI	T4,U$LEN
	SKIPL	U$NUM-U$LEN(T4)
	JRST	SCNINL
	TDZA	T1,T1
SGNYES:	HRRZ	T1,U$NUM(T4)
QRYRES:	MOVE	T2,TSKOUT
	DPB	T1,[POINT 32,DATAST+.BXBMX+$ARGS(T2),31]
	HRROI	T2,DATAST+.BXBMX+$ARGS+1(T2)
	MOVE	T1,USNMAT
	CALL	CPYSTR
	MOVEI	T1,4(T4)
	MOVE	T2,TSKOUT
	MOVEI	T3,5
	CALL	NINI
	JRST	NXTTSK

WHOISQ:	MOVEI	T1,1
	CALL	GETPGS
	MOVEM	T2,TSKOUT
	LDB	T2,[POINT 16,DATAST+.BXBMX+$TYP(T2),15]
	CALL	FNDUSN
	SETZM	USNMAT
	JUMPE	T1,QRYRES
	HRROI	T3,U$NAM(T3)
	MOVEM	T3,USNMAT
	JRST	QRYRES

;Find all jobs owned by username in TSKDAT, store them and count in LCLUSR
FNDLCL:	SETZM	LCLUSR
	;GET JOB MAX FOR SYSTEM
	MOVEI	T1,^D128*4
	MOVEM	T1,JOBMAX#
	SETZM	RCUSRN#
	MOVE	T2,TSKDAT
	MOVX	T1,RC%PAR
	RCUSR%
	 ERJMPS	NOMATC
	TXNE	T1,RC%NOM
	JRST	NOMATC
	MOVEM	T3,RCUSRN
	;GET MIN JOB IN T1
	SETZ	T1,
ADDLCL:	HRROI	T2,T4
	MOVEI	T3,.JIUNO
	GETJI%
	 ERJMPS	NXTJOB
	CAME	T4,RCUSRN
	JRST	NXTJOB
	AOS	T2,LCLUSR
	MOVEM	T1,LCLUSR(T2)
NXTJOB:	CAMGE	T1,JOBMAX
	AOJA	T1,ADDLCL
NOMATC:	RET

BLATTM:	MOVE	T1,TSKDAT
FNDTAB:	ILDB	T2,T1
	JUMPE	T2,NXTTSK		;NULL BEFORE TAB? BAD OPERATION
	CAIE	T2,.CHTAB
	JRST	FNDTAB
	MOVEM	T1,TEXTIS#		;SAVE POINTER TO REAL TEXT TO SEND
	SETZ	T2,
	DPB	T2,T1		;TERMINATE USER NAME
	CALL	FNDLCL
	SKIPN	T7,LCLUSR		;GET ANY JOBS?
	JRST	NXTTSK		;NO
BLATLP:	MOVE	T1,LCLUSR(T7)	;GET A JOB NUMBER
	HRROI	T2,T1		;SET UP FOR TTY NUMBER IN T1
	MOVEI	T3,.JITNO
	GETJI%
	 ERJMPS	BLATEN		;COULD HAVE LOGGED OUT
	JUMPL	T1,BLATEN	;DON'T TRY IF DETACHED
	TRO	T1,400000	;MAKE A TERMINAL CODE
	MOVE	T2,TEXTIS
	TTMSG%			;FIRE AWAY
	 ERJMPS	BLATEN		;SHN
BLATEN:	SOJG	T7,BLATLP
	JRST	NXTTSK

JOBIN:	MOVEI	T1,1
	CALL	GETPGS
	MOVEM	T2,TSKOUT
	MOVE	T1,TSKDAT
	MOVEM	T1,USNMAT	;GET POINTER TO USERNAME WHERE IT WILL BE SEEN
	CALL	FNDLCL		;GET JOBS BELONGING TO USERNAME
	SETZ	T7,		;FLAGS IN T7 (ASSUME NONE TRUE)
	SKIPN	RCUSRN		;WAS THERE A USERNUMBER?
	JRST	RETFLG		;NO, JUST RETURN THE 0
	TXO	T7,1B<7+4>	;4 OFFSET IS TO STORE 32 BIT VALUE IN 36 BITS
	SKIPN	T6,LCLUSR	;FIND ANY JOBS?
	JRST	RETFLG		;NO, CURRENT FLAGS REPRESENT WHOLE TRUTH
	TXO	T7,1B<15+4>	;LIGHT THE LOGGED IN BIT
SCNJBI:	MOVE	T1,LCLUSR(T6)	;LOOK AT EACH JOB
	HRROI	T2,T4
	MOVEI	T3,.JITNO	;GET TERM NUMBER IN T4
	GETJI%
	 ERJMPS	NXTJBI		;GONE
	JUMPL	T4,NXTJBI	;IF DETACHED, CAN'T MARK REAL TERMINAL
	HRROI	T2,T4
	MOVEI	T3,.JIBAT	;MAYBE UNDER BATCH?
	GETJI%
	 ERJMPS	NXTJBI
	CAIL	T4,0		;-1 IF BATCH
	TXO	T7,1B<14+4>	;NOT BATCH OR DETACHED, HENCE REAL TERMINAL
NXTJBI:	SOJG	T6,SCNJBI
RETFLG:	MOVE	T1,T7		;COPY FLAGS TO TI
	JRST	QRYRES

FONUSR:	MOVE	T2,TSKINP
	LDB	T2,[POINT 16,DATAST+.BXBMX+$TYP(T2),15]
	JUMPE	T2,NXTTSK	;*I* DON'T WANT IT
	CALL	FNDUSN
	JUMPE	T1,CANTDM	;RECEIVER NOT HERE, RETURN TO SENDER
	MOVEM	T1,USRPID
	MOVEI	T1,2
	CALL	GETPGS
	MOVEM	T2,TSKOUT
	MOVEI	T1,4
	CALL	IPSSTD		;SET UP STANDARD IPCF STUFF
	LDB	T1,[POINT 16,(T4),15] ;FIRST 2 BYTES IS JOB INFO
	MOVEM	T1,1000+I$ARG+3(T2)
	LDB	T1,[POINT 16,(T4),31]	;USER #
	MOVEM	T1,1000+I$ARG+4(T2)
	MOVEI	T1,1(T4)	;POINT TO USER NAME RETURNED
	HRLI	T1,(POINT 8)	;IN 8 BIT BYTES
	HRROI	T2,1000+I$ARG+5(T2)
	CALL	CSTR
	MOVE	T2,TSKOUT
	MOVE	T1,USRPID
	CALL	MSGUSR
	JRST	NXTTSK

NDELI:	LDB	T2,[POINT 16,DATAST+.BXBMX+$TYP(T2),15]
	JUMPE	T2,NXTTSK
	CALL	FNDUSN
	JUMPE	T1,NXTTSK
	MOVEM	T1,USRPID
	MOVEI	T1,2
	CALL	GETPGS
	HRROI	T1,-6
	CALL	IPSSTD
	MOVE	T1,TSKINP
	LDB	T1,[POINT 16,DATAST+.BXBMX+$SUSR(T1),31]
	MOVEM	T1,1000+I$ARG+2(T2)
	MOVE	T1,USRPID
	CALL	MSGUSR
	JRST	NXTTSK

HELLO:	SUBI	T1,100
	CAILE	T1,2
	JRST	BADCMD
	JRST	@[EXP NEWSRV, IAM..., LATER](T1)

NEWSRV:	CALL	NEWNDE
	TIME%				;GET UPTIME AS A RANDOM NUMBER
	MOVS	T2,OURNAM+1
	XOR	T2,OURNAM
	IDIVI	T2,37
	ADDI	T1,(T2)			;SCRAMBLE BY NODENAME
	ANDI	T1,7777			;CUT TO 0-4 SECONDS (ABOUT)
	ADDI	T1,100(T3)		;ADD AN OFFSET
	MOVEM	T1,RANDOM#
	DISMS%				;THIS HELPS PREVENT COLLISIONS
	MOVEI	T1,1
	CALL	GETPGS
	DMOVE	T4,OURNAM		;OUR SYSTEM NAME, 6 8BIT BYTES
	DMOVEM	T4,DATAST+.BXBMX+$ARGS(T2)
	MOVEI	T1,6
	MOVEI	T3,101			;NORMAL HELLO
	SETZ	T4,
	CALL	SNDSTD
	MOVEI	T3,NIBLOK
	MOVE	T5,TSKINP
	DMOVE	T4,DATAST+.BXSAD(T5)
	CALL	SNDNIM
	JRST	NXTTSK

NEWNDE:	AOS	NI.HLO			;COUNT HELLOS
	DMOVE	T4,DATAST+.BXSAD(T2)
	SKIPA	T1,EADQUE
NDEFND:	SKIPE	T1,NXTBUF(T1)
	JUMPE	T1,[CALL FATAL]		;NIRECV SHOULD HAVE PUT IT IN
	CAMN	T4,DATAST+$NDSAD(T1)	
	CAME	T5,DATAST+$NDSAD+1(T1)
	JRST	NDEFND
	DMOVE	T6,DATAST+.BXBMX+$ARGS(T2) ;FETCH 8BIT NODENAME
	TDZ	T7,[3,,-1]		;CLEAR OUT TRASH
	MOVE	T4,T1
	CALL	DIRINT
	DMOVEM	T6,DATAST+$NDNAM(T4)
	SETZM	DATAST+$NDFLG(T4)	;NODE IS "ONLINE"
	LDB	T5,[POINT 8,DATAST+.BXBMX+$TYP(T2),31]	;GET FLAGS
	TXNN	T5,MS%NEW		;NEW NODE MESSAGE?
	JRST	NOTRLN			;NO, DON'T CLEAR CMD MEMORY
	SETZM	DATAST+$NDOFF(T4)
	SETZM	DATAST+$NDCMD(T4)
	MOVEI	T3,DATAST+$NDCMD+1(T4)
	HRLI	T3,-1(T3)
	BLT	T3,DATAST+$NDCMD+FINLEN(T4)
NOTRLN:	CALL	EIRINT
	RET

IAM...:	CALL	NEWNDE
	JRST	NXTTSK

LATER:	JRST	NXTTSK
STATCH:	CIS%		;LOSE INTERRUPTS
	JRST	ST	;AND RESTART

IPCFRV:	MOVEM	T1,INT1AC+1
	MOVE	T1,[2,,INT1AC+2]
	BLT	T1,INT1AC+17		;SAVE ALL AC'S
	MOVE	P,[-50,,STACK1-1]
	MOVEI	T1,IPCFRV
	MOVEM	T1,ATINTR#
 IFN	FTLOGF,<
	PUSHJ	P,BLAT
	[ASCIZ/ <int-IPCFRV> /] >

IPREAD:	SETO	T1,
	MOVE	T2,[.FHSLF,,IPRPAG]
	SETZ	T3,
	PMAP%				;MAKE RECEIVING PAGE EMPTY
	MOVE	T1,SRVPID
	MOVEM	T1,MRECVB+.IPCFR
	MOVE	T1,[1000,,IPRPAG]
	MOVEM	T1,MRECVB+.IPCFP
	MOVX	T1,IP%CFV
	MOVEM	T1,MRECVB+.IPCFL	;RECEIVE IN PAGE MODE
	MOVEI	T1,4
	MOVEI	T2,MRECVB
RERECV:	MRECV%				;READ THE MSG
	 ERJMPR	[CAIE	T1,IPCF16	;CAN'T BECAUSE WRONG MODE?
		 JRST	IN1RET		;TRY TO IGNORE
		 SETZM	MRECVB+.IPCFL	;YES, CLEAR FLAG BITS
		 MOVE	T1,[1000,,IPRBUF]
		 MOVEM	T1,MRECVB+.IPCFP ;TRY WORD MODE
		 MOVEI	T1,4		;AND TRY AGAIN
		 JRST	RERECV]
	MOVEM	T1,ASSOCV#
	LDB	T1,[POINT 3,MRECVB+.IPCFL,32] ;WHO IS THIS FROM?
	JUMPE	T1,IPRUSR		;FROM SOME USER
;HOPEFULLY FROM SYS[INFO]
SYSINF:	LDB	T1,[POINT 6,MRECVB+.IPCFL,29]
	JUMPE	T1,IPCFRF		;NO PROBLEM? LEAVE
	CAIE	T1,.IPCKM		;TELLING ME "DEAD PID"?
	CALL	FATAL			;NO, WE IN TROUBLE
	MOVE	T1,IPRBUF		;YES, WHICH PID LEFT?
	CALL	FNDPID			;FIND THE ENTRY FOR IT
	 JRST	IPCFRF			;VERY ODD!
	SOS	USRCNT			;ABOUT TO BE LESS ONE USER
	HLLZS	T1,U$NUM(T2)
	SETZM	U$NAM(T2)
	JUMPGE	T1,IPCFRF		;WAS IT BOTTOM ENTRY IN LIST?
	MOVX	T1,U%LAST		;YES
	IORM	T1,U$NUM-U$LEN(T2)	;MAKE NEXT ONE UP BOTTOM
	JRST	IPCFRF			;SIGNOUT DONE

IPRUSR:	MOVE	T1,MRECVB+.IPCFS	;WHICH PID SENT THIS?
	CALL	FNDPID
	 TDZA	T3,T3
	HRRZ	T3,U$NUM(T2)
	PUSH	P,T3			;USER # OR 0
	PUSH	P,T1			;PID
	MOVEI	T1,1			;GET A PAGE FOR TBP QUEUE
	CALL	GETPGS
	POP	P,SRCBUF(T2)		;NONZERO: FROM IPCF (PID)
	POP	P,USRBUF(T2)		;USER # OR 0
	MOVSI	T1,IPRBUF		;BLT DATA FROM USER IPCF MESSAGE
	HRRI	T1,DATAST(T2)		;TO QUEUE ENTRY
	BLT	T1,777(T2)
	MOVE	T1,T2			;GO INSERT INTO QUEUE
	CALL	INSTBP
IPCFRF:	SKIPN	ASSOCV
	JRST	IN1RET
	JRST	IPREAD
TIMOUT:	SKIPN	TBAQUE			;ANYTHING TO DO?
	DEBRK%				;NO, JUST LEAVE
	MOVEM	T1,INT1AC+1
	MOVE	T1,[2,,INT1AC+2]
	BLT	T1,INT1AC+17		;SAVE ALL AC'S
	MOVE	P,[-50,,STACK1-1]
 IFN	FTLOGF,<
	PUSHJ	P,BLAT
	[ASCIZ/ <int-TIMOUT> /] >
	MOVEI	T1,TIMOUT
	MOVEM	T1,ATINTR
TIMAGA:	GTAD%
	MOVE	T6,T1
	MOVEI	T1,TBAQUE		;SCAN TBA QUEUE
	SKIPN	T2,TBAQUE
	JRST	IN1RET			;IF NOTHING TO DO, LEAVE
TIMSCN:	CAML	T6,TIMBUF(T2)		;IS THIS BUFFER DUE?
	JRST	OVERDU
NOTYET:	MOVE	T1,T2
	SKIPE	T2,NXTBUF(T1)
	JRST	TIMSCN
	JRST	IN1RET

OVERDU:	MOVEM	T2,OVRBUF#		;THIS BUFFER IS OVERDUE
	MOVE	T3,NXTBUF(T2)
	MOVEM	T3,NXTBUF(T1)		;REMOVE FROM QUEUE
	SETZM	NXTBUF(T2)
	AOS	T5,ERRBUF(T2)		;INCR FAILED COUNT
	DMOVE	T1,DATAST+.BXDAD(T2)	;WHO WAS THIS TO?
	SKIPA	T4,EADQUE		;FIND IT IN QUEUE OF NODES
NODSTA:	MOVE	T4,NXTBUF(T4)
	JUMPE	T4,LSTNDE		;NEVER FOUND IT
	CAMN	T1,DATAST+$NDSAD(T4)		;EADDRESS MATCH?
	CAME	T2,DATAST+$NDSAD+1(T4)		;..?
	JRST	NODSTA			;NO, ADVANCE THROUGH NODE QUEUE
	MOVE	T6,DATAST+$NDFLG(T4)		;HAVE WE DECLARED IT DEAD?
	TXNE	T6,ND%OFF		;..?
	JRST	LSTNDE			;YES
	IMULI	T5,TIMWAT		;NO, HOW LONG HAVE WE WAITED?
	CAIG	T5,^D167		;OVER 40+ SECS?
	JRST	RESEND			;NO, TRY AGAIN
	IFN ftlogf,<
	PUSHJ	P,BLAT
	[ASCIZ/
Declaring node dead/]
	>
	AOS	NI.DND			;COUNT HOW OFTEN THIS HAPPENS
	MOVX	T1,ND%OFF		;NODE SILENT FOR TOO LONG
	IORM	T1,DATAST+$NDFLG(T4)	;DECLARE IT DEAD
LSTNDE:	MOVEM	T4,NODNOR#		;NODE ENTRY ADDRESS OR 0
	MOVE	T1,OVRBUF		;MSG THAT WAS NEVER ANSWERED
	LDB	T6,[POINT 16,DATAST+.BXBMX+$SUSR(T1),31] ;WHO CREATED IT?
	JUMPE	T6,NOCARE		;I DID, NO ONE NEEDS TO KNOW
	MOVEI	T1,2			;GET 2 PAGES FOR IPCF SEND
	CALL	GETPGS
	MOVEM	T2,LIRTMP#
	HRROI	T1,-5			;IPCF MSG TYPE 5, NODE NOT RESPONDING
	MOVEM	T1,1000+I$CMD(T2)		;2ND PAGE HAS MESSAGE
	MOVE	T3,OVRBUF
	DMOVE	T3,DATAST+.BXDAD(T3)
	DMOVEM	T3,1000+I$ARG(T2)
	SETZM	1000+I$ARG+2(T2)
	SKIPE	T1,NODNOR		;DO WE KNOW THE NODE?
	SKIPN	DATAST+$NDNAM(T1)	;DO WE HAVE THE NAME?
	JRST	NDENNM			;CAN'T GIVE NODENAME
	HRROI	T1,DATAST+$NDNAM(T1)
	HRROI	T2,1000+I$ARG+3(T2)
	CALL	CSTR			;COPY STRING IN
NDENNM:	MOVE	T2,T6			;GET PID FOR USER#
	CALL	FNDUSN			;RET T1/PID OR 0
	MOVE	T2,LIRTMP		;2 PAGE BUFFER FOR IPCF SENDER
	JUMPE	T1,NOSNNA
	CALL	MSGUSR			;SEND IT AND DELETE BUFFER
	JRST	NOCARE
NOSNNA:	MOVE	T1,T2
	CALL	RELPGS
NOCARE:	MOVE	T1,OVRBUF
	CALL	RELPGS
	JRST	TIMAGA			;OK, GO SCAN AGAIN

RESEND:	IFN ftlogf,<
	PUSHJ	P,BLAT
	[ASCIZ/
TIMOUT resend --/]
	>
	AOS	NI.SAG
	MOVE	T2,OVRBUF		;WE NEED TO RESEND
	DMOVE	T4,DATAST+.BXDAD(T2)	;ADDRESS IN T4,5
	MOVEI	T3,NIINTB		;BLOCK TO USE IN T3
	MOVEM	T2,RESTMP#
	CALL	SNDNIM			;T2 HAS PAGE, SEND NI MSG AGAIN
	JRST	TIMAGA			;OK, RESTART TBA SCAN
;HERE ON A NI RECEIVED INTERRUPT
; TAKE THE RECEIVED PACKET AND DECIDE WHAT TO DO WITH IT.  GENERALLY,
; IT IS EITHER A REQUEST (AND GETS PUT ON THE TBP QUEUE) OR AN ACKNOWLEDGEMENT
; TO A REQUEST (AND CAUSES THE ORIGINAL BUFFER TO BE REMOVED FROM THE TBA QUEUE)
NIRECV:	MOVEM	T1,INT1AC+1
	MOVE	T1,[2,,INT1AC+2]
	BLT	T1,INT1AC+17		;SAVE ALL AC'S
	MOVE	P,[-50,,STACK1-1]
 IFN	FTLOGF,<
	PUSHJ	P,BLAT
	[ASCIZ/ <int-NIRECV> /] >
	MOVEI	T1,NIRECV
	MOVEM	T1,ATINTR
	MOVX	T1,EI%BLK
	ANDCAM	T1,NIINTB+.EIFLG
NIRECA:	MOVE	T1,TBRCNT		;MAKE SURE WE HAVE AT LEAST..
POSTBF:	CAIL	T1,6			;6 POSTED BUFFERS NOW
	JRST	FINFIL			;WE DO
	MOVEI	T1,1			;WE DONT, POST A NEW RECEIVING BUFFER
POSTIN:	CALL	GETPGS
	MOVEI	T3,DATAST(T2)
	MOVEI	T1,(T3)
	HRLI	T1,BXBFFR		;SET UP TO COPY BX BLOCK INTO PLACE
	BLT	T1,.BXBMX-1(T3)
	MOVEI	T4,.BXBMX(T3)		;POINT TO DATA PORTION
	HRLI	T4,(POINT 8)
	MOVEM	T4,.BXBFA(T3)
	MOVEM	T2,.BXBID(T3)		;BUFFER IDENT IS ADDRESS OF PAGE
	MOVEM	T3,NIINTB+.EIBCP
	MOVX	T1,.EIRCV
	HRRM	T1,NIINTB+.EIFCN
	MOVEI	T1,NIINTB
	NI%				;POST THAT BUFFER
	 ERCAL	FATAL
	MOVE	T1,TBRQUE		;PUT ON TBR QUEUE
	MOVEM	T1,NXTBUF(T2)
	MOVEM	T2,TBRQUE
	AOS	T1,TBRCNT
	JRST	POSTBF

FINFIL:	AOS	T1,RECVCT		;HOW MANY TIMES CALLED?
	CAIG	T1,1			;FIRST TIME IS JUST AN INIT RUN..
	JRST	IN1RET			;TO POST LOTS OF RECEIVE BUFFERS.

	MOVE	T1,[BXBFFR,,BXREAD]	;OK, TIME TO READ RECEIVED BUFFER
	BLT	T1,BXREAD+.BXBMX-1
	MOVX	T3,.EIRRQ
	HRRM	T3,NIINTB+.EIFCN
	MOVEI	T3,BXREAD
	MOVEM	T3,NIINTB+.EIBCP	;SET UP READ RECEIVE QUEUE
	MOVEI	T1,NIINTB
	NI%				;TELL IT WE'LL HANDLE BUFFER NOW
	 ERCAL	FATAL
	MOVE	T2,BXREAD+.BXBFA
	ANDI	T2,777000
	CAIN	T2,0
STOP2:	JRST	NIRMOR			;GOT NOTHING
	MOVEM	T2,CURRCV		;STORE FOR LATER USE
	HRRZ	T1,CREBUF(T2)
	CAIE	T1,POSTIN
STOP6:	CALL	FATAL
	CAME	T2,BXREAD+.BXBID
STOP5:	CALL	FATAL
	AOS	NI.RCV			;INCR # RECEIVED
	MOVEI	T1,TBRQUE
	MOVE	T3,TBRQUE
REMRCV:	CAMN	T3,T2
	JRST	DELPSL
	MOVE	T1,T3
	SKIPE	T3,NXTBUF(T3)
	JRST	REMRCV
	CALL	FATAL
DELPSL:	MOVE	T4,NXTBUF(T3)
	MOVEM	T4,NXTBUF(T1)
	SETZM	NXTBUF(T2)
	SOS	TBRCNT
	HRRZ	T1,BXREAD+.BXSTA	;NOW SEE IF GOOD
	JUMPN	T1,DMPRCV		;ERROR, DUMP IT
	MOVSI	T1,BXREAD		;COPY BX INFO INTO PROPER PLACE IN PAGE
	HRRI	T1,DATAST(T2)
	BLT	T1,DATAST+.BXBMX-1(T2)
	MOVEI	T3,DATAST(T2)
	MOVE	T4,.BXBFA(T3)		;FETCH PROPER POINTER TO DATA
	IBP	T4
	IBP	T4			;SKIP NULL BYTES
	ILDB	T5,T4
	LSH	T5,8
	ILDB	T6,T4
	ADDI	T5,$ARGS*4(T6)
	CAILE	T5,^D1500		;POSSIBLE?
	JRST	DMPRCV			;NO, DUMP IT
	MOVE	T1,.BXBFA(T3)
	MOVE	T7,[POINT 8,.BXBMX(T3)]
FIXRCV:	ILDB	T6,T1
	IDPB	T6,T7
	SOJG	T5,FIXRCV
	MOVEI	T4,.BXBMX(T3)
	LDB	T1,[POINT 8,$TYP(T4),23]	;GET MESSAGE TYPE
	CAILE	T1,.SCVMX		;LEGAL?
	JRST	DMPRCV			;NO, TOSS
 IFN ftlogf,<
	PUSHJ	P,BLAT
	[ASCIZ/
 RCV type /]
	PUSH	P,T3
	MOVE	T2,T1
	PUSHJ	P,OCTLOG
	PUSHJ	P,BLAT
	[ASCIZ/, n=/]
	MOVE	T2,$NUM(T4)
	LSH	T2,-4
	PUSHJ	P,OCTLOG
	MOVE	T2,CURRCV
	PUSHJ	P,BLAT
	[ASCIZ/ @/]
	MOVE	T2,CURRCV
	PUSHJ	P,OCTLOG
	POP	P,T3
 >
	JRST	@[EXP RCVACK, RCVCMD, RCVREQ](T1)
;JUMP WITH T3 POINTING TO BX, T4 TO DATA BLOCK

RCVACK:	AOS	NI.RAC
	MOVEI	T2,TBAQUE		;SET UP TO SCAN TBA QUEUE
	JRST	NOTMTA			;START AT LOOP BOTTOM
SCNTBA:	MOVE	T5,DATAST+.BXBMX+$NUM(T2) ;FETCH MSG# IN QUEUE
	CAME	T5,$NUM(T4)		;MATCH?
	JRST	NOTMTA			;NO, CAN'T BE RIGHT ENTRY
	DMOVE	T5,DATAST+.BXDAD(T2)	;WHERE WAS THIS ENTRY SENT TO
	CAMN	T5,.BXSAD(T3)		;SAME AS WHERE THIS CAME FROM?
	CAME	T6,.BXSAD+1(T3)		;..?
	JRST	NOTMTA			;NO, FALSE ALARM.
	MOVE	T5,NXTBUF(T2)		;MATCH! REMOVE THE ENTRY
	MOVEM	T5,NXTBUF(T1)		;..
	MOVE	T1,T2			;AND DELETE IT
	CALL	RELPGS
	JRST	DMPRCV			;TOSS MESSAGE TOO
NOTMTA:	MOVE	T1,T2			;DIDN'T MATCH, ADVANCE TO NEXT
	SKIPE	T2,NXTBUF(T2)		;..
	JRST	SCNTBA			;AND GO AGAIN
;HERE TO TOSS INCOMING MESSAGE IF IT'S SERVED IT'S PURPOSE (LIKE AN ACK)
DMPRCV:	MOVE	T1,CURRCV		;GET JUST-RECEIVED MSG
	CALL	RELPGS			;AND FREE IT
NIRMOR:	MOVE	T1,NIINTB+.EIFLG
	TXNN	T1,EI%RBA
	JRST	IN1RET			;AND DONE
	JRST	NIRECA

RCVREQ:	JRST	DMPRCV			;FOR NOW

;RECEIVE A REQUEST. ACK IT, AND QUEUE IT IF IT ISN'T A DUP
RCVCMD:	MOVE	T1,CURRCV
	SETZM	SRCBUF(T1)	;BUFFER IS FROM NI
	LDB	T2,[POINT 8,DATAST+.BXBMX+$TYP(T1),31]
	TXNE	T2,MS%NOA		;NO ACK REQUESTED?
	JRST	NOACKS			;CORRECT - AND ALWAYS TBP IT
	MOVEI	T1,1
	CALL	GETPGS
	MOVE	T1,CURRCV
	DMOVE	T4,DATAST+.BXSAD(T1)
	MOVSI	T1,DATAST+.BXBMX(T1)
	HRRI	T1,DATAST+.BXBMX(T2)
	BLT	T1,DATAST+.BXBMX+MSGLEN/4(T2) ;GIVE MESSAGE BACK
	SETZ	T1,
	DPB	T1,[POINT 8,DATAST+.BXBMX+$TYP(T2),23] ;CLEAR ACK FIELD
	MOVEI	T3,NIINTB
	CALL	SNDNIM

NOACKS:	MOVE	T3,CURRCV
	TXO	F,F.TBPA		;ASSUME NOT A DUP FOR THE MOMENT
	SKIPN	T1,DATAST+.BXBMX+$NUM(T3)		;FETCH MSG#
	JRST	SCNFIN			;0, THIS IS SERVER SIGNON
	SKIPN	T2,TBPQUE		;SEARCH ONLY SCAN
	JRST	SCNFIN
MLTRCS:	SKIPE	SRCBUF(T2)		;AN NI BUFFER?
	JRST	NOTMTP			;NO, CANT BE A DUP NI BUFFER
	DMOVE	T5,DATAST+.BXSAD(T2)	;GET SENDER'S ADDRESS OF Q'D MSG
	CAMN	T5,DATAST+.BXSAD(T3)		;SAME ADDRESS?
	CAME	T6,DATAST+.BXSAD+1(T3)		;..?
	JRST	NOTMTP			;NO, FALSE ALARM
	CAME	T1,DATAST+.BXBMX+$NUM(T2) ;MATCH?
	JRST	NOTMTP			;NO, KEEP SCANNING TBP
	TXZ	F,F.TBPA		;YES - FLAG A DUP
	JRST	SCNFIN			;AND LOOK FOR NODE
NOTMTP:	SKIPE	T2,NXTBUF(T2)
	JRST	MLTRCS
SCNFIN:	SKIPA	T2,EADQUE
SCNNNT:	MOVE	T2,NXTBUF(T2)
	JUMPE	T2,ADDNOD		;NOT A KNOWN NODE.  ADD IT IN
SCNNOD:	DMOVE	T5,DATAST+.BXSAD(T3)		;FETCH MSG SOURCE ADDR
	CAMN	T5,DATAST+$NDSAD(T2)
	CAME	T6,DATAST+$NDSAD+1(T2)	;MATCH THIS NODE?
	JRST	SCNNNT			;NO, CHECK NEXT
	SETZM	DATAST+$NDFLG(T2)	;NODE IS ONLINE
	JUMPE	T1,NEWSTT		;IF SIGNON MSG, SKIP THE REST
	TXNN	F,F.TBPA		;DO WE KNOW IT'S A DUP MSG IN TBP?
	JRST	TBPSET			;YES, NEEDN'T LOOK HERE
	MOVSI	T6,-FINLEN
	HRRI	T6,DATAST+$NDCMD(T2)
SCNFNL:	CAMN	T1,(T6)
	JRST	ALRINQ
	AOBJN	T6,SCNFNL
	JRST	NEWSTT
ADDNOD:	DMOVE	T5,DATAST+.BXSAD(T3)		;GET SAD ADDRESS
	MOVEI	T1,1
	CALL	GETPGS
	DMOVEM	T5,DATAST+$NDSAD(T2)	;STORE ADDRESS
	MOVE	T1,EADQUE
	MOVEM	T1,NXTBUF(T2)
	MOVEM	T2,EADQUE		;ADD TO TOP OF QUEUE (EASIEST)
NEWSTT:	MOVE	T1,CURRCV
	MOVEM	T2,NODBUF(T1)
ADDTBP:	TXOA	F,F.TBPA
ALRINQ:	TXZ	F,F.TBPA
TBPSET:	MOVE	T1,CURRCV
	TXZE	F,F.TBPA	;INSERT INTO TBP OR TOSS?
	JRST	INSCAL
	IFN ftlogf,<
	PUSHJ	P,BLAT
	[ASCIZ/
	DUP msg toss, n=/]
	MOVE	T2,DATAST+.BXBMX+$NUM(T1)
	LSH	T2,-4
	PUSHJ	P,OCTLOG
	PUSHJ	P,BLAT
	[ASCIZ/ @/]
	MOVE	T2,CURRCV
	PUSHJ	P,OCTLOG
	>
	CALL	RELPGS		;IT WAS A DUP, TOSS IT
	AOS	NI.RDP
	JRST	NIRMOR			;AND DONE
INSCAL:	CALL	INSTBP
	JRST	NIRMOR

INSTBP:	MOVEI	T2,TBPQUE		;FIND END OF TBP QUEUE
IN2TBP:	SKIPN	T3,NXTBUF(T2)		;END?
	JRST	OKADDP			;YES, ADD IT IN
	MOVE	T2,T3
	JRST	IN2TBP
OKADDP:	MOVEM	T1,NXTBUF(T2)
	SETZM	NXTBUF(T1)		;NEW END
	TXO	F,F.SKIP
	RET

IN1RET:	TDZA	T1,T1
IN2RET:	MOVEI	T1,1
 IFN	FTLOGF,<
	PUSHJ	P,BLAT
	[ASCIZ/ <dbk> /] >
	TXNE	F,F.WAIT		;WAITING?
	TXNN	F,F.SKIP		;YES. BREAK OUT
	JRST	NOBREK			;DON'T BREAK OUT
	MOVE	T2,@[EXP INT1BK,INT2BK,INT3BK](T1)
	TXO	T2,PC%USR
	MOVEM	T2,@[EXP INT1BK,INT2BK,INT3BK](T1)
NOBREK:	MOVE	T2,[	3,,INT1AC+3
			3,,INT2AC+3
			3,,INT3AC+3](T1)
	BLT	T2,17
	DMOVE	T1,@[EXP INT1AC+1,INT2AC+1,INT3AC+1](T1)
	SETZM	ATINTR
	DEBRK%
;Support
DIRINT:	MOVX	T1,.FHSLF
	DIR%
	AOS	INTLCK
	RET

EIRINT:	SOSL	T1,INTLCK
	RET
	CAME	T1,[-1]
	CALL	FATAL
	MOVX	T1,.FHSLF
	EIR%
	RET

NINI:	MOVE	T4,TSKINP
	LDB	T4,[POINT 16,DATAST+.BXBMX+$SUSR(T4),31]
	CALL	SNDSTD
	MOVEI	T3,NIBLOK
	MOVE	T6,TSKINP
	DMOVE	T4,DATAST+.BXSAD(T6)
	;JRST	SNDNIM

;This sends the message, blocking until sent
;T2/ buffer, with data at DATAST+.BXBMX(T2) set up
;T3/ NI% block to use
;T4,5/ Ethernet address to send to
SNDNIM:	MOVEI	T1,.BXBMX		;CREATE THE BX BLOCK
	MOVEM	T1,DATAST+.BXLEN(T2)
	DMOVEM	T4,DATAST+.BXDAD(T2)
	MOVEI	T1,DATAST+.BXBMX(T2)
	HRLI	T1,(POINT 8)
	MOVEM	T1,DATAST+.BXBFA(T2)
	LDB	T1,[POINT 16,DATAST+.BXBMX+$SIZE(T2),31] ;GET SIZE
	ADDI	T1,$ARGS*4
	MOVEM	T1,DATAST+.BXBSZ(T2)
	MOVX	T1,.EIXMT		;FUNCTION IS TRANSMIT
	HRRM	T1,.EIFCN(T3)
	MOVX	T1,EI%BLK
	IORM	T1,.EIFLG(T3)		;REQUEST BLOCKING
	MOVEI	T1,DATAST(T2)		;POINT TO BX BLOCK
	MOVEM	T1,.EIBCP(T3)		;NI BLOCK POINTS TO BX

	CALL	DIRINT
	SETZM	TIMBUF(T2)		;NO TIMESTAMP YET
	LDB	T1,[POINT 16,DATAST+.BXBMX+$TYP(T2),31] ;GET TYPE AND FLAGS
	TXNE	T1,MS%NOA		;NO ACK REQUESTED?
	JRST	NOACKE
	TRNN	T1,377_8		;OR IS TYPE "ACK"?
	AOSA	NI.SAC			;YES, RECORD AN ACK
	SKIPA	T1,[.INFIN]		;NO, PICK UP FAR-FUTURE DATE AND SKIP
	JRST	NOACKE			;YES, JUMP OVER ACK EXPECTATION CODE
	MOVEM	T1,TIMBUF(T2)		;FLAG AS NEEDING TIMESTAMP AND ACK
	MOVE	T4,TBAQUE		;ADD TO "TO BE ACK'D" QUEUE
	MOVEM	T4,NXTBUF(T2)
	MOVEM	T2,TBAQUE
NOACKE:	MOVEM	T2,SENTMP#
	MOVEI	T1,(T3)
	NI%				;FIRE!
	 ERCAL	FATAL
	AOS	NI.SND			;COUNT ANOTHER SENT
 IFN ftlogf,<
	PUSHJ	P,BLAT
	[ASCIZ/
 snd typ /]
	PUSH	P,T2
	LDB	T2,[POINT 8,DATAST+.BXBMX+$TYP(T2),23]
	PUSHJ	P,OCTLOG
	PUSHJ	P,BLAT
	[ASCIZ/, n=/]
	MOVE	T2,(P)
	MOVE	T2,DATAST+.BXBMX+$NUM(T2)
	LSH	T2,-4
	PUSHJ	P,OCTLOG
	PUSHJ	P,BLAT
	[ASCIZ/ @/]
	MOVE	T2,(P)
	PUSHJ	P,OCTLOG
	POP	P,T2
 >
	HRRZ	T1,DATAST+.BXSTA(T2)	;FETCH RESULT
	JUMPE	T1,SENDOK		;IS IT OK?
	CAIE	T1,NIEOPN		;NO. SOME ERRORS ARE FATAL
	CAIN	T1,NIESHT
	JRST	CBLDIE			;DEAD CABLE
	CAIN	T1,NIECCF
	JRST	CBLDIE
SENDOK:	SKIPN	TIMBUF(T2)		;EXPECT AN ACK SOMETIME?
	JRST	GODROP			;NO, GO DROP BUFFER
TIMENT:	MOVE	T2,SENTMP		;GET BUFFER TO TIMESTAMP
	GTAD%				;GET "NOW"
	ADDI	T1,TIMENT
	MOVEM	T1,TIMBUF(T2)		;SAY WHEN BUFFER IS DUE
	AOS	T2,T1			;ADD A TICK AND MOVE TO T2
	MOVEI	T3,TIMINT		;CHANNEL TO INTERRUPT ON
	MOVE	T1,[.FHSLF,,.TIMDT]	;INTERRUPT AT GIVEN TIME
	TIMER%				;FIRE
	 ERJMPR	[CAIE T1,TIMX6		;CLOCK CHANGE OR SLOW SYSTEM??
		 CALL	FATAL		;NO, PROBABLY CANT SET CLOCK
		 JRST	TIMENT]		;TRY AGAIN
	JRST	UNLOCK
GODROP:	MOVE	T1,T2
	CALL	RELPGS			;MSG NEEDS NO ACK, SO DON'T SAVE IT
UNLOCK:	CALL	EIRINT
	RET

CBLDIE:	SETOM	REASON#
	CALL	FATAL			;NO RETURN

;T1/# of bytes of data
;T2/buffer
;T3/ CMD
;T4/ dest user #
;This assumes type 1 msg.  It helps build the msg block.
SNDSTD:	DMOVE	T5,OURSAD		;LOAD OUR (SENDER'S) ADDRESS INTO..
	DMOVEM	T5,DATAST+.BXBMX+$SAD(T2) ;THE MESSAGE
	LSH	T1,4			;ADJUST FOR 8 BIT FORMAT
	MOVEM	T1,DATAST+.BXBMX+$SIZE(T2)
	AOS	T1,MSGID#		;UNIQUE MSG ID
	LSH	T1,4
	MOVEM	T1,DATAST+.BXBMX+$NUM(T2)
	LSH	T3,8+8+8+4		;PUT CMD IN RIGHT BYTE
	MOVEM	T3,DATAST+.BXBMX+$CMD(T2)
	LSH	T4,8+8+4		;JUSTIFY DEST USER
	ADDI	T4,1*400*20		;ADD IN TYPE CODE (1)
	MOVEM	T4,DATAST+.BXBMX+$TYP(T2)
	RET

;Called when an IPCF message will create a NI request.  Copies pertinent
; data into msg block and AC's T4,5
IPRSTD:	MOVE	T3,USRBUF(T1)
	DPB	T3,[POINT 16,DATAST+.BXBMX+$SUSR(T2),31]	;SET USER NUMBER
	MOVE	T3,DATAST+I$NUM(T1)
	DPB	T3,[POINT 24,DATAST+.BXBMX+$CMD(T2),31] ;AND TRANSACTION NUMBER
	DMOVE	T4,DATAST+I$ARG(T1)		;LOFT SAD FROM IPCF MESSAGE
	RET

;HERE WITH PID IN T1, RETURNS +2 WITH T2 POINTING TO USER BLOCK, OR
; +1 WITH T1 STILL CONTAINING PID
FNDPID:	MOVEI	T2,USRLST
FN2PID:	CAMN	T1,U$PID(T2)
	JRST	CPOPJ1
	SKIPGE	U$NUM(T2)
	RET
	ADDI	T2,U$LEN
	JRST	FN2PID

;Call with T2/ user#
;Return with T1/ PID or 0 if none
FNDUSN:	MOVEI	T3,USRLST
FN2USN:	HRRZ	T4,U$NUM(T3)
	CAIN	T4,(T2)
	JRST	MATNUM
	ADDI	T3,U$LEN
	SKIPL	U$NUM-U$LEN(T3)
	JRST	FN2USN
	TDZA	T1,T1	;NO MATCH, RETURN 0
MATNUM:	MOVE	T1,U$PID(T3)
	RET


;T1/ PID
;T2/ ADDR OF 2 PAGE BUFFER, 2ND PAGE HAS MSG, FIRST IS FOR ME
MSGUSR:	MOVX	T3,IP%CFP+IP%CFV
	MOVEM	T3,DATAST+.IPCFL(T2)
	MOVE	T3,SRVPID
	MOVEM	T3,DATAST+.IPCFS(T2)
	MOVSI	T3,1000
	MOVEI	T4,1000(T2)
	LSH	T4,-9
	ADD	T3,T4
	MOVEM	T3,DATAST+.IPCFP(T2)
	MOVEI	T2,DATAST(T2)
GOTPDS:	MOVEM	T1,.IPCFR(T2)
	MOVEI	T1,4
	MSEND%
	 ERJMPS	.+1
KILSBF:	MOVEI	T1,-DATAST(T2)
	JRST	RELPGS

;T1/ source (default is 7bit)
;T2/ dest (default is 8bit)
;return T4/ count of bytes copied (trailing null not copied or counted)
CPYSTR:	TLCE	T1,-1
	TLCN	T1,-1
	HRLI	T1,(POINT 7)
	TLCE	T2,-1
	TLCN	T2,-1
	HRLI	T2,(POINT 8)
	SETZ	T4,
CPYST1:	ILDB	T3,T1
	JUMPE	T3,CPOPJ
	IDPB	T3,T2
	AOJA	T4,CPYST1

BLAT:	SKIPN	LOGJFN
	JRST	BLTRET
	PUSH	P,T4
	PUSH	P,T3
	PUSH	P,T2
	PUSH	P,T1
	MOVE	T1,LOGJFN
	HRRZ	T2,-4(P)
	MOVE	T2,(T2)
	TLCE	T2,-1
	TLCN	T2,-1
	HRLI	T2,(POINT 7)
	SETZ	T3,
	SOUT%
	POP	P,T1
	POP	P,T2
	POP	P,T3
	POP	P,T4
BLTRET:	AOS	(P)
	POPJ	P,
CPOPJ1:	AOS	(P)
CPOPJ:	RET
;Here we set up the values we need to make the memory manager routines run
; quickly.  MAPEND is the offset from MAPFRE to the last word in the map.
; LBIINI is the value that last word is set to to indicate what pages are
; available in that last set of 36.  The last word always flags at least
; one pair of pages (or it wouldn't be needed).
MAPEND=NUMPAG/^D36		;OFFSET TO LAST WORD IN MAP
%%C==<NUMPAG-<<NUMPAG/^D36>*^D36>>	;NUMBER OF BITS USED IN LAST WORD -1
LBIINI==1B0			;BUILD VALUE TO INIT LAST WORD WITH
REPEAT %%C,< LBIINI== <LBIINI_<-1>>!<1B0> > ;..

;Call here to set up the memory manager.
SETPGS:	SETOM	MAPFRE
	MOVE	T1,[MAPFRE,,MAPFRE+1]
	BLT	T1,MAPFRE+MAPEND-1	;MARK PAGES FREE SANS LAST SET
	MOVX	T1,LBIINI	;VALUE LAST WORD GETS
	MOVEM	T1,MAPFRE+MAPEND;INIT LAST SET SPECIALLY
	SETO	T1,
	MOVE	T2,[.FHSLF,,FRESPC]
	MOVE	T3,[PM%CNT!NUMPAG]
	PMAP%
	 ERJMPS	.+1
	RET

;Here to get a buffer (a set of contiguous pages) for any purpose.
;Enter with T1/ # of pages needed.  Return with T2/ address of buffer
GETPGS:	MOVE	T2,T1
	CALL	DIRINT
	MOVEM	T2,GETTMP#
GETPGA:	SETZ	T1,
GETPG1:	SKIPE	MAPFRE(T1)
	JRST	GETSCN
	CAIGE	T1,MAPEND	;AT END OF MAP?
	AOJA	T1,GETPG1	;NO, KEEP LOOKING
	JRST	NOMEM		;NO PAGES AVAILABLE
GETSCN:	MOVE	T2,T1		;GEN. A BIT POINTER TO FIRST WORD..
	ADD	T2,[POINT 1,MAPFRE];WITH AN AVAILABLE PAGE
	IMULI	T1,^D36
	MOVEI	T3,NUMPAG
	SUBI	T3,(T1)		;THE NUMBER OF BITS TO CHECK
FNDCLU:	SOJL	T3,NOMEM	;NO MORE BITS TO CHECK, FAILED
	ILDB	T1,T2		;IS THIS PAGE AVAILABLE?
	JUMPE	T1,FNDCLU	;0 MEANS TAKEN
	MOVEM	T2,GE2TMP#	;SAVE POINTER TO POSSIBLE CANDIDATE
	MOVE	T4,GETTMP	;GET NUMBER OF PAGES NEEDED
MEACLU:	SOJE	T4,GOTCLU	;IF GOT ENOUGH, DONE
	SOJL	T3,NOMEM	;END OF POSSIBILITIES? DONE.
	ILDB	T1,T2
	JUMPN	T1,MEACLU	;IF AVAILABLE, KEEP GOING
	JRST	FNDCLU		;NOT LONG ENOUGH.  KEEP LOOKING.
GOTCLU:	SETO	T1,		;BACK UP POINTER 1 FOR IDPB
	ADJBP	T1,GE2TMP
	MOVEM	T1,GE2TMP
	MOVE	T2,GETTMP	;# OF PAGES WANTED
	IDPB	T4,T1		;WRITE 0'S
	SOJG	T2,.-1		;..
	MOVEI	T2,FRESPC+NUMPAG;CAL. WORD ADDRESS
	SUBI	T2,(T3)
	MOVE	T3,GETTMP
	SUBI	T2,(T3)
	LSH	T2,9

	SKIPLE	CREBUF(T2)
	CALL	FATAL		;ALREADY TAKEN AND NOT DELETED??
	PUSH	P,T1
	PUSH	P,T3
	MOVEI	T1,3
GETCKA:	MOVE	T3,[EXP TBPQUE, TBAQUE, EADQUE,TBRQUE](T1)
GETCHK:	SKIPN	T3,NXTBUF(T3)
	JRST	GETCH2
	CAMN	T2,T3
	CALL	FATAL
	JRST	GETCHK
GETCH2:	SOJGE	T1,GETCKA
	POP	P,T3
	POP	P,T1

	MOVE	T4,GE2TMP	;T3/ SIZE AND T4/ POINTER INTO MAPFRE
	DMOVEM	T3,SIZBUF(T2)	;STORE INTO SIZBUF AND PNTBUF
	SETZM	NXTBUF(T2)	;NO NEXT YET
	SETZM	TIMBUF(T2)	;CLEAR BUFFER OUT TOO
	MOVEI	T1,TIMBUF(T2)	;POINT TO 1ST WORD
	HRLI	T1,1(T1)	;AND NEXT WORD WOTH LH
	MOVSS	T1		;SWAP TO N,,N+1
	LSH	T3,9		;CONVERT PAGES TO WORDS
	ADDI	T3,-1(T2)	;LAST WORD IN BUFFER
	BLT	T1,(T3)		;ZERO BUFFER OUT
	MOVE	T3,(P)
	SUBI	T3,1
	HRRZM	T3,CREBUF(T2)
	CALL	EIRINT
	RET

;Enter with word address of buffer to free up in T1
RELPGS:
	MOVE	T2,T1
	MOVE	T3,SIZBUF(T1)
RELKIL:	SETOM	CREBUF(T2)
	ADDI	T2,1000
	SOJG	T3,RELKIL

	MOVEI	T3,3
RELPGN:	MOVE	T2,[EXP TBRQUE,TBAQUE,EADQUE,TBPQUE](T3)
RELPGX:	SKIPN	T2,NXTBUF(T2)
	JRST	RELPGA
	CAMN	T2,T1
	CALL	FATAL
	JRST	RELPGX
RELPGA:	SOJGE	T3,RELPGN



	DMOVE	T2,SIZBUF(T1)	;T2/ SIZE AND T3/ POINTER
	CAIG	T2,0
	CALL	FATAL
	SETOB	T1,SIZBUF(T1)	;A SOURCE OF 1'S, AND CLEAR SIZE
RELPGL:	IDPB	T1,T3
	SOJG	T2,RELPGL
	RET

NOMEM:	MOVEI	T1,1
	MOVEM	T1,REASON
FATAL:	DMOVEM	F,CRSHAC
	MOVE	F,[2,,CRSHAC+2]
	BLT	F,CRSHAC+17
	MOVE	T1,['EISRV?']
	SETNM%
	SKIPE	T1,LOGJFN
	CLOSF%
	 ERJMPS	.+1
	SETZM	LOGJFN
	HRROI	T1,[ASCIZ/
?EISRV fatal error, PC /]
	PSOUT%
	POP	P,T2
	MOVX	T1,.PRIOU
	MOVEI	T2,-1(T2)
	CALL	OCTOUT
	HRROI	T1,[ASCIZ/
 Shutdown flagged/]
	TXNE	F,F.SHUT
	PSOUT%
	HRROI	T1,[ASCIZ/
 Reason code: /]
	PSOUT%
	MOVE	T2,REASON
	CALL	DECOUT
	HRROI	T1,[ASCIZ/
 Last Monitor error: /]
	PSOUT%
	MOVX	T1,.PRIOU
	HRLOI	T2,.FHSLF
	SETZ	T3,
	ERSTR%
	 JFCL
	 JFCL
	RESET%
	HALTF%
	JRST	ST

OCTOUT:	SKIPA	T3,[8]
DECOUT:	MOVEI	T3,^D10
	PUSH	P,T1
	MOVX	T1,.PRIOU
	NOUT%
	 ERJMPS	.+1
	POP	P,T1
	RET

OCTLOG:	SKIPN	LOGJFN
	POPJ	P,
	PUSH	P,T1
	PUSH	P,T3
	MOVE	T1,LOGJFN
	MOVEI	T3,^D8
	NOUT%
	 ERJMPS	.+1
	POP	P,T3
	POP	P,T1
	POPJ	P,

;copy string and back up T2 on exit.
CSTRB:	CALL	CSTR
	SETO	T3,
	ADJBP	T3,T2
	MOVE	T2,T3
	RET

;Back up T2 and do a copy string
APPSTR:	SETO	T3,
	ADJBP	T3,T2
	MOVE	T2,T3		;DESTINATION BACKED UP BY 1
CSTR:	TLCE	T1,-1
	TLCN	T1,-1
	HRLI	T1,(POINT 7)
	TLCE	T2,-1
	TLCN	T2,-1
	HRLI	T2,(POINT 7)
CSTRA:	ILDB	T3,T1
	IDPB	T3,T2
	JUMPN	T3,CSTRA
	RET

MULTI:	BYTE(8) 253,"E","I","S","R","V"
MAPFRE:	BLOCK	NUMPAG/^D36+1		;MEMORY MANAGEMENT BIT ARRAY

STACK:	BLOCK	101
STACK1:	BLOCK	51

LEVTAB:	INT1BK
	INT2BK
	INT3BK

CHNTAB:	0
	1,,NIRECV
	1,,STATCH
	1,,TIMOUT
	1,,IPCFRV
	BLOCK	^D32

INT1BK:	BLOCK	2
INT2BK:	BLOCK	2
INT3BK:	BLOCK	2

INT1AC:	BLOCK	20
INT2AC:	BLOCK	20
INT3AC:	BLOCK	20
CRSHAC:	BLOCK	20

NIBLOK:	.EIBMX,,.EIOPN
NIPID:	EI%PAD			;PADDING
	0,,PROTOC		;CHN,,PROTOCOL
	BYTE(12) -1,NIRINT,NICINT
	0
	BXBFFR
	BLOCK	2

NIINTB:	.EIBMX,,0
	EI%PAD
	0,,PROTOC
	BYTE(12) -1,NIRINT,NICINT
	BLOCK	4

BXREAD:	.BXBMX
	BLOCK	.BXBMX

BXBFFR:	.BXBMX
	0			;NO NEXT
	MSGLEN+6		;COUNT, BYTES + 6 OVERHEAD
	0			;BP, FILLED IN
	0			;0
	0			;ID
	0			;STATUS
	0			;DEST
	0
	0			;SOURCE
	0
	0

MUTBLK:	.MUPIC
	0
	IPRINT

IPCFSI:	IP%CPD+IP%NOA
SRVPID:	0
	0
	5,,IPSYSI

IPSYSI:	.IPCII
	0
	ASCIZ/EISRV/
	BLOCK	2

MRECVB:	IP%CFV
	BLOCK	2
	1000,,IPRPAG

OURNAM:	BLOCK	2
MYNAME:	BLOCK	2
LLMBLK:	BLOCK	30

LCLUSR:	BLOCK	^D129

ZERO:
DEEP:	0
RECVCT:	0			;NUMBER OF CALLS TO NIRECV
CURRCV:	0
TBRQUE:	0			;QUEUE OF POSTED BUFFERS
TBRCNT:	0			;COUNT OF POSTED BUFFERS
TBAQUE:	0			;NO DATAGRAMS TO BE ACK'D
TBPQUE:	0			;NO TASKS TO BE DONE
EADQUE:	0			;NODES LIST
USRCNT:	0			;NO USERS IN
 NI.STA:
NI.RCV:	0			;STATS - messages received
NI.RAC:	0			;received acks
NI.RDP:	0			;received duplictaes
NI.SND:	0			;msgs sent
NI.SAC:	0			;acks sent
NI.SAG:	0			;msgs resent
NI.CST:	0			;times looped waiting for hello response
NI.DND:	0			;times declared a node dead
NI.HLO:	0			;number of hellos received
NI.RQN:	0			;number of times missed a node signon
   NISTL=.-NI.STA

	ENDZER=.
;KEEP ALL THIS ADJACENT
USRLST:	-1
	-1
	-1
	BLOCK	U$LEN-3
REALUS:	BLOCK	U$LEN*MAXUSR

 NI.LEN=.-NI.STA+1
IFG NI.LEN-777,<PRINTX ?NI.LEN does not fit on one page>

STRINN:	BLOCK	100
TSKTMP:	BLOCK	2
MISSED:	BLOCK	2
	END	ST