Google
 

Trailing-Edge - PDP-10 Archives - tops10_704_monitoranf_bb-x140c-sb - 10,7/mon/llinks.mac
There are 21 other files named llinks.mac in the archive. Click here to see a list.
;TITLE	LLINKS - Network Services Layer of Phase III DECnet V047


;THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY BE USED
;  OR COPIED ONLY IN ACCORDANCE WITH THE TERMS OF SUCH LICENSE.
;
;COPYRIGHT (c) DIGITAL EQUIPMENT CORPORATION 1976,1985,1986,1988.
;ALL RIGHTS RESERVED.


	SUBTTL W. G. Nichols

	SEARCH D36PAR,SCPAR,MACSYM

IFN FTOPS10,<
.CPYRT<1976,1988>
> ;END IFN FTOPS10


	ENTRY NSP,NSPRTR,NSPINI,NSPJIF	;The entry points,
	ENTRY NSPCG,NSPCR		; see procedures for calling sequence

	SALL

;	The NSP link block definitions are in SCPAR so that they will
;	be available to DCNSPY and similar reporting programs.

	FTORC==FTDEBUG          ;Hang until all output dones return if FTORC=1
	FTTROLL==0		;GIVE A NAME TO TROLL CODE
	FTGOL==0		;Assemble goal code conditionally
	FTBFR==0		;Assemble buffer-rich code conditionally

IFN FTOPS10,<
	TITLE LLINKS - Network Services Layer of Phase IV DECnet
	SEARCH F,S
	$RELOC			;Set up code and data segments according
	.NTSHO==3		;define SHOW 'cuz we don't search UUOSYM
	>
IFN FTOPS20,<
	SEARCH PROLOG
	TTITLE (LLINKS,,< - Network Services Layer of Phase IV DECnet>)
	>

	D36SYM			;Fix up problems with MACSYM in DECnet-36

IFN FTTRACE,<
	PRINTX FTTRACE On: Note that TRACE code has not been updated
	PRINTX FTTRACE to deal with the 1-word global bytepointers
>

	XRESCD			; to FTOPSn0, macros defined in D36PAR

;This module implements the NSP layer of Phase III DECnet.  It is the
;system independent logical link service.  Below NSP is the Router
;layer, which handles node-to-node communication.  Above NSP is the
;Session Control layer, which provides the interface between the
;logical links of NSP and the user program.
;
;The interfaces between NSP and its neighbors are via call vectors, so
;that by changing the base of the call vector, a logical link may use
;a different module at the NSP level of the DECnet hierarchy.  Eg, a
;link may want to use APRAnet datagram service instead of DECnet
;logical link service.
	SUBTTL	Table of Contents


;		Table of Contents for LLINKS
;
;
;			   Section			      Page
;   1. Table of Contents. . . . . . . . . . . . . . . . . . .    2
;   2. Register Definitions . . . . . . . . . . . . . . . . .    3
;   3. EXTERN Declarations. . . . . . . . . . . . . . . . . .    4
;   4. NSP Network Protocol Definitions . . . . . . . . . . .   11
;   5. Code-Generating Macros . . . . . . . . . . . . . . . .   13
;   6. NSP Node Block Definition. . . . . . . . . . . . . . .   15
;   7. Resident Storage Allocation. . . . . . . . . . . . . .   16
;   8. NSP's Entry Vector Table . . . . . . . . . . . . . . .   18
;   9. NSPRCV - Receive a Message from Router . . . . . . . .   19
;  10. NSPRTR - The Entry Point from Router . . . . . . . . .   21
;  11. NSPINI - Initialization Entry Point. . . . . . . . . .   22
;  12. NSP Interlock Handlers
;       12.1.   NSPLCQ - Queue on Failure . . . . . . . . . .   23
;       12.2.   NSPLCW - Wait on Failure. . . . . . . . . . .   24
;       12.3.   NSPLCF - Flags set for Failure. . . . . . . .   25
;  13. Session Control Calls
;       13.1.   NSP - The Entry Point . . . . . . . . . . . .   27
;       13.2.   NSPOPN - Open a Link. . . . . . . . . . . . .   28
;       13.3.   NSPGOL - Set New Data Request Goals . . . . .   30
;       13.4.   NSPACT - Enter Active . . . . . . . . . . . .   31
;       13.5.   NSPACC - Accept Connect . . . . . . . . . . .   32
;       13.6.   NSPSEG - Send Data. . . . . . . . . . . . . .   34
;       13.7.   NSPDRQ - Data Request Call. . . . . . . . . .   36
;       13.8.   NSPREJ - Reject a Connection. . . . . . . . .   37
;       13.9.   NSPDSC - Synch Disconnect . . . . . . . . . .   39
;       13.10.  NSPABO - Abort Link . . . . . . . . . . . . .   40
;       13.11.  NSPCLS - Close Port . . . . . . . . . . . . .   41
;  14. CLSABO - Send a free "Abort by Object" . . . . . . . .   42
;  15. PROCXQ - Process the Transmit Queue. . . . . . . . . .   43
;  16. SCNIRS - Tell Session Control Port is Not in Run State   46
;  17. SNDRTR - Send a Message to Router. . . . . . . . . . .   47
;  18. ROUTER Calls
;       18.1.   NSPRCV - Receive a Message from Router. . . .   49
;       18.2.   NSPODN/NSPOND - Output Done/Not Done. . . . .   50
;       18.3.   NSPRTS - Return a Message to Sender . . . . .   51
;       18.4.   NSPRFR - Return a Message to Sender from Remote   51
;  19. Message Receivers
;       19.1.   RCVPRC - Preliminary Processing . . . . . . .   52
;       19.2.   PRCACK - Process ACKNUM/ACKOTH Field. . . . .   54
;       19.3.   Receive a Data Segment. . . . . . . . . . . .   56
;            19.3.1.     Check Length of Q. . . . . . . . . .   59
;       19.4.   PROCRQ - Process the Receive Queue. . . . . .   60
;       19.5.   RCVLKS - Receive a Link Service Message . . .   62
;       19.6.   RCVACK and RCVNOP - Little Ones . . . . . . .   67
;       19.7.   RCVCA - Receive a Connect ACK Message . . . .   68
;       19.8.   RCVCI - Receive a Connect Initiate Request. .   69
;       19.9.   RCVCC - Receive a Connect Confirm Message . .   71
;       19.10.  RCVDI - Disconnect Initiate Message . . . . .   73
;       19.11.  RCVDC - Disconnect Confirm Message. . . . . .   75
;  20. Clock-Driven Routines. . . . . . . . . . . . . . . . .   77
;  21. CLCXDQ - Calculate Transmit Data Requests. . . . . . .   82
;  22. NSPRJF - Request Jiffy Service . . . . . . . . . . . .   85
;  23. SECCHK - Once-a-Second Checks. . . . . . . . . . . . .   86
;  24. Memory Manager Calls
;       24.1.   NSPCG - We're Congested . . . . . . . . . . .   92
;       24.2.   NSPCR - Congestion is Relieved. . . . . . . .   93
;  25. NSIRLV - Congestion-Relieved Processor . . . . . . . .   94
;  26. PESFLO - Put Link in Pessimistic Flow Control. . . . .   95
;  27. SCLOSE - Start the Close Process . . . . . . . . . . .   97
;  28. SENDRQ - Send a Link Service Message . . . . . . . . .  104
;  29. SACKMG - Get a Message Block and send ACK message if can  106
;  30. SNDACK - Check a sublink to see if it needs an ACK sent 107
;  31. SENDDI - Send a DI Message . . . . . . . . . . . . . .  108
;  32. SNDCAK - Send a Connect ACK Message. . . . . . . . . .  109
;  33. SNDDSC - Send a Disconnect Complete Message. . . . . .  110
;  34. MGACKD - A Message has been ACKed. . . . . . . . . . .  111
;  35. SENDNL - Send a No Link Message. . . . . . . . . . . .  112
;  36. PTIRUN - Put a Port in Run State . . . . . . . . . . .  114
;  37. MAKPRT - Make a New Port Block . . . . . . . . . . . .  115
;  38. GTRESP - Get a Reserved Port . . . . . . . . . . . . .  116
;  39. MAKPRS - Make this Port Reserved . . . . . . . . . . .  117
;  40. SETPRT - Make a New Port Block . . . . . . . . . . . .  118
;  41. FNDPRT - Hash an LLA to Find a Port Block. . . . . . .  119
;  42. Hash Table Routines. . . . . . . . . . . . . . . . . .  120
;  43. NEWLLA - Make a New Local Link Address . . . . . . . .  123
;  44. NEWSGN - Make a New Message Segment Number . . . . . .  124
;  45. NSPpid Control - GETPID & FNDPID . . . . . . . . . . .  125
;  46. DSTPRT - Destroy a Port. . . . . . . . . . . . . . . .  127
;  47. QSRTMB - Queue a Message on a Sorted Queue . . . . . .  128
;  48. MAKHDR - Initialize the NSP MSD to build a Message Header 130
;  49. UPDELAY - Update a Logical Link's Roundtrip Delay. . .  131
;  50. Free a Message Block . . . . . . . . . . . . . . . . .  132
;  51. Reserved Port Management . . . . . . . . . . . . . . .  133
;  52. Reserved Port Process. . . . . . . . . . . . . . . . .  134
;  53. DCRORC - Decrement 'Out-in-router' count . . . . . . .  137
;  54. BLDRCI - Build a (R)CI message . . . . . . . . . . . .  138
;  55. RELCIM - Release saved (R)CI message . . . . . . . . .  139
;  56. Network management
;       56.1.   Dispatch. . . . . . . . . . . . . . . . . . .  140
;       56.2.   SET parameter . . . . . . . . . . . . . . . .  141
;       56.3.   READ parameter. . . . . . . . . . . . . . . .  141
;       56.4.   CLEAR parameter . . . . . . . . . . . . . . .  141
;       56.5.   SHOW counters . . . . . . . . . . . . . . . .  142
;       56.6.   SHOW and ZERO counters. . . . . . . . . . . .  142
;  57. Node data base
;       57.1.   Find an NSP node block. . . . . . . . . . . .  143
;       57.2.   Find an existing NSP node block . . . . . . .  144
;       57.3.   Free a node block . . . . . . . . . . . . . .  145
;       57.4.   Release a node block. . . . . . . . . . . . .  145
;  58. NSPJB0 - NSP periodic checks . . . . . . . . . . . . .  146
;  59. EVTINI - Initialize event logger interface . . . . . .  147
;  60. NSPEVT - Queue an Event to Network Management. . . . .  148
;  61. Trace-to-TTY Facility. . . . . . . . . . . . . . . . .  151
;  62. End of Program . . . . . . . . . . . . . . . . . . . .  154
	SUBTTL Register Definitions

;Registers T1 through T6 and P1 through P2 are defined in D36PAR for
;all of DECnet-36.  Register P4 is redefined as MS for
;the DNxyBY routines in D36COM.  Register MB is defined in D36PAR for
;NSP and Router only.  The registers defined below are additions
;used in NSP only.  The registers FREEn are defined in D36PAR to be
;registers not otherwise used in DECnet-36.

	MB=MB			;THESE ARE DEFINED AS .NODDT GLOBALS
	CX=CX			; IN THE UNIVERSAL, CHANGE THAT HERE

	EL=FREE1		;POINTER TO THE CURRENT PORT BLOCK
	ES=FREE2		;POINTER TO THE CURRENT SUBLINK BLOCK
	SUBTTL EXTERN Declarations

	EXTERN RTN		;NON-SKIP RETURN LABEL
	EXTERN RSKP		;SKIP RETURN LABEL
	EXTERN DNGWZP		;ROUTINE TO GET SOME ZEROED MEMORY

;The Interlock interface

IFN FTOPS10,<
	EXTERN NSPLOK		;INTERLOCK WORD
	EXTERN NSPLKO		;INTERLOCK OWNER
>

;The Router interface


;	T1/ Flags, see RT%RQR and RT%ODN in D36PAR.MAC
;	MB/ Pointer to the message block
	EXTERN RTRXMT		;SEND TO ROUTER
;	Normal Return
;
;
;	T1/ My Node Number
;	T2/ NSP's entry address (call vector base)
;	T3/ Flags, see RT%PH2 in D36PAR.MAC
	EXTERN RTRINI		;INITIALIZE ROUTER
;	Normal Return
;
;
;The Session Control interface

;	T1/ NSPpid for port just created
;	T2/ See BEGSTR IA in D36PAR
;	T3/ ignored
;	T4/ Message Block
	EXTERN SCTL		;NORMAL INTERFACE, CALLED VIA @EL.SCV(EL)
	EXTERN SCTLCI		;THIS ENTRY USED ONLY FOR CI MESSAGES
;	Normal Return		;ALL OTHER ENTRIES CALL @EL.SCV(EL)
				; SEE PROCEDURES NSPOPN AND NSPACC
;
;	T1/ SCTL Port id (ELSCB)
	EXTERN SCTRIB		;RESERVE INPUT BLOCK
;	  Error Return		; NO BLOCK AVAILABLE
;	Normal Return		;ONE BLOCK RESERVED
;
;	No arguments
	EXTERN SCTUCG		;CALL THIS ON MEMORY UNCONGESTION
;	Normal Return
;The Network Management Interface

;Network management parameters
;	T1/ parameter table
;	T2/ parameter table length
;	T3/ function code
	EXTERN NTPARM
;Network management counters
;	T1/ counter table
;	T2/ counter table length
;	T3/ function code
	EXTERN NTCTRS

;	T1/ Ptr to arg block with structure NE in it
	EXTERN NMXEVT		;EVENT CATCHER
;	Normal Return

;SCLINK node number/name mapping
	EXTERN SCTA2N

;SCLINK event parameter routine
	EXTERN LEVT.0		;Event parameter 0
;	Always +1 return

;NTMAN hook for counter NICE'zation
	EXTERN PRSCOU

;DECnet initialization block
	EXTERN IBBLK

;The following procedures manipulate message bytes.  They all have the
;following calling convention:
;
;	MS/ Pointer to Message Segment Descriptor (MSD)
;	T1/ Data to be written/Data returned
;
;The put-byte routines do not skip return.
;
;The get-byte routines non-skip return if byte count is zero when
;they are called, else they skip return.

	EXTERN DNP1BY		;Put a single byte
	EXTERN DNP2BY		;Put a double byte
	EXTERN DNPEBY		;Put an extensible byte

	EXTERN DNG1BY		;Get a single byte
	EXTERN DNG2BY		;Get a double byte
	EXTERN DNGEBY		;Get an extensible byte
	EXTERN DNGSBY		;Get a byte, skip if not extensible

;	T1/ Number of bytes to back up
	EXTERN DNBKBY		;Back up (T1) bytes in MS's MSD
;	Normal Return

;	T1/ Pointer to an MSD
	EXTERN DNPINI		;Init MS for DNPxBY
;	Normal Return

;	T1/ Pointer to an MSD
	EXTERN DNGINI		;Init MS for DNGxBY
;	Normal Return

;	T1/ Pointer to an Message Block
	EXTERN DNLENGTH		;Return sum of lengths (bytes) of
;	Normal Return		; text in all MSD(s) in T1

;	T1/ Pointer to an MSD
	EXTERN DNSLNG		;Return length (bytes) of
;	Normal Return		; text in MSD(s) in T1

;	MS/ Pointer to an MSD
	EXTERN DNRPOS		;Read current MSD position
;	Normal Return with position value in T1

;	MS/ Pointer to an MSD
;	T1/ Value returned from DNRPOS
	EXTERN DNGPOS		;GOTO a position in this MSD's text
;	Normal Return

;Continued on Next Page
;Continued from Previous Page

	EXTERN DCNCON		;NON-ZERO IF SYSTEM IS CONGESTED
;
;
;Message and message segment allocation routines
;
;	T1/ Length in bytes of message segment to go on UDMSD
	EXTERN DNGMSG		;GET A DECnet-36 MESSAGE BLOCK
;	  Error Return if no resources
;	Normal Return with pointer in T1
;
;
;Clear a message block, this is like deallocating and reallocating
;without fear of losing the block.
;	T1/ Pointer to the block
;	T2/ Number of bytes of User Data to get
	EXTERN DNMINI
;	  Error Return if couldn't get user data
;	Normal Return
;
;
;Message and message segment deallocation routines
;
;	T1/ Pointer to block being returned
	EXTERN DNFMSG		;FREE A DECnet-36 MESSAGE BLOCK
;	Normal Return
;
;
;Free memory allocation and deallocation routines
;
;	T1/ Number of contiguous words to allocate
	EXTERN DNGWDS		;GET WORDS
	EXTERN DNGWDZ		;GET ZEROED WORDS
;	  Error Return
;	Normal Return with pointer in T1
;
;	T1/ Address of start of block
;	T2/ Address of end of block
;	T3/ Value to put in block (typically zero)
	EXTERN DNSWDS		;Smear value into block
;	Normal Return		; using either BLT or XBLT
;
;	T1/ Pointer to block to free
;	T2/ Number of words in block
	EXTERN DNFWDS		;FREE WORDS
;	Normal Return
;
	EXTERN TIMBAS		;CONVERSION FROM DECNET TIME BASE INTO SECONDS
	EXTERN DNGTIM		;GET CURRENT TIME
;	Normal Return		;WITH T1 CONTAINING MS TIME
;Trace and associated macros

DEFINE CALLTRACE(proc,code<TRCNSP>),<
  IFN FTTRACE,<
	CALL [
   IFIDN <code>,<ETRNSP>,MOVE CX,S.ETRACE##
   IFDIF <code>,<ETRNSP>,MOVE CX,S.TRACE##
		TXNN CX,TRCNSP
		RET
		SAVEAC <T1,T2,T3,T4>
		CALLSCAN proc
		RET]
   >
>

DEFINE TRACE(prefix,message),<XTRACE(prefix,<message>,TRC)>
DEFINE ETRACE(prefix,message),<XTRACE(prefix,<message>,ETR)>
DEFINE XTRACE(prefix,message,code),<
  IFN FTTRACE,<
	CALL [	PUSH P,T1
		PPTRACE prefix,code
		XMOVEI T1,[ASCIZ \message]
\]
		CALLTRACE .TSTRG##,code''prefix
		POP P,T1
		RET]
   >
>

DEFINE PTRACE(prefix),<
  IFN FTTRACE,<
	CALL [	PUSH P,T1
		PPTRACE(prefix,TRC)
		POP P,T1
		RET]
   >
>
DEFINE PETRACE(prefix),<
  IFN FTTRACE,<
	CALL [	PUSH P,T1
		PPTRACE(prefix,ETR)
		POP P,T1
		RET]
   >
>
DEFINE PPTRACE(prefix,code<TRC>),<
  IFN FTTRACE,<
	XMOVEI T1,[ASCIZ \[prefix: \]
	CALLTRACE .TSTRG##,code''prefix
   >
>
DEFINE TRCRET(prefix,message),<
  IFE FTTRACE,RET
  IFN FTTRACE,<
	JRST [	TRACE(prefix,message)
		RET]
	>
>

DEFINE ETRCRET(prefix,message),<
  IFE FTTRACE,RET
  IFN FTTRACE,<
	JRST [	ETRACE(prefix,message)
		RET]
	>
>


DEFINE NEWSTATE(nstate),<
	MOVX CX,NPS.'nstate
	STOR CX,ELSTA,(EL)
	TRACE NSP,<New State: nstate>
>
	SUBTTL NSP Network Protocol Definitions

;The architecture version number of this implementation

	XP .NSVER,2		;0=3.2, 1=3.1, 2=4.0, others reserved
		VER3.2==0	;VERSION 3.1 (PHASE III) = 0
		VER3.1==1	;VERSION 3.0 (PHASE II)  = 1
		VER4.0==2	;VERSION 4.0 (PHASE IV)  = 2


;The contents of the MSGFLG field:

;The low-order two bits of the MSGFLG field are always zero, for this
;is how Router knows that this is an NSP MSGFLG field and not a
;Phase II routing header.  The high-order bit is always zero also.

DEFINE MGF(nam,type,subtype),<
	IFNB <nam>,<nam'== subtype'B31 ! type'B33>>

	MGF MGFMSG, 0, 0	;NORMAL SEGMENT WITH NO BOM OR EOM
	MGF MGFLKS, 0, 1	;LINK SERVICE MESSAGE
	MGF MGFBOM, 0, 2	;NORMAL SEGMENT WITH BOM
	MGF MGFINT, 0, 3	;INTERRUPT MESSAGE
	MGF MGFEOM, 0, 4	;NORMAL SEGMENT WITH EOM
	MGF 	  , 0, 5	;ILLEGAL
	MGF MGFONL, 0, 6	;NORMAL ONLY SEGMENT (BOTH BOM AND EOM)
	MGF 	  , 0, 7	;ILLEGAL
	MGF MGFACK, 1, 0	;NORMAL SUBLINK ACK
	MGF MGFOAK, 1, 1	;OTHER SUBLINK ACK
	MGF MGFCAK, 1, 2	;CONNECT ACK
	MGF 	  , 1, 3	;ILLEGAL
	MGF 	  , 1, 4	;ILLEGAL
	MGF 	  , 1, 5	;ILLEGAL
	MGF 	  , 1, 6	;ILLEGAL
	MGF 	  , 1, 7	;ILLEGAL
	MGF MGFNOP, 2, 0	;NO OP
	MGF MGFCI , 2, 1	;CONNECT INITIATE
	MGF MGFCC , 2, 2	;CONNECT CONFIRM
	MGF MGFDI , 2, 3	;DISCONNECT INITIATE
	MGF MGFDC , 2, 4	;DISCONNECT CONFIRM
	MGF 	  , 2, 5	;ILLEGAL, PHASE II NODE INITIATE
	MGF MGFRCI, 2, 6	;RETRANSMITTED CONNECT INITIATE
	MGF 	  , 2, 7	;ILLEGAL
	MGF 	  , 3, anything	;ILLEGAL


PURGE MGF
;The format of an ACKNUM field

;This structure is expected to be used to pull apart a value held
;in a register.

BEGSTR AK
	FILLER 20		;ONLY THE RIGHTMOST 16 BITS COUNT
	FIELD PNT, 1		;FLAG SET IF FIELD IS PRESENT
	FIELD QAL, 3		;QUALIFIER:
		AK$QAK==0	;  0 IS ACK
		AK$QNK==1	;  1 IS NAK
		AK$CAK==2	;  2 IS CROSS-SUB CHANNEL ACK
		AK$CNK==3	;  3 IS CROSS-SUB CHANNEL NAK
	FIELD NUM, 12		;THE ACK NUMBER, WE KNOW THIS IS RT-JUSTIFIED
ENDSTR				; NEGATIVE IF HIGH BIT OF BYTE IS SET
				; SEE LOADE MACRO (E IS AS IN HRRE)


;This structure is expected to be used to pull apart a value held
;in a register.

BEGSTR LS			;THE LSFLAGS FIELD OF A LINK SERVICE MESSAGE
	FILLER 28		;ONLY THE RIGHTMOST 8 BITS COUNT
	FIELD ZRO,4		;MUST BE ZERO
	FIELD INT,2		;INTERPRETATION
		LS.INR==0	;NORMAL DATA REQUEST
		LS.IOT==1	;OTHER DATA REQUEST (2 & 3 RESERVED)
	FIELD MOD,2		;THE ON/OFF INDICATOR
		LS.MNC==0	;NO CHANGE, CODE USES JUMPE
		LS.MOF==1	;TURN SUBLINK OFF (IGNORED ON "OTHER")
		LS.MON==2	;TURN SUBLINK ON  (IGNORED ON "OTHER")
		LS.MRS==3	;RESERVED
ENDSTR


;This structure is expected to be used to pull apart a value held
;in a register.

BEGSTR SV			;THE SERVICES FIELD OF A CI OR CC MSG
	FIELD FL1,32		;FILLER 1, CHECK FOR ALL ZEROES
	FIELD OPT,2		;THE FLOW CONTROL OPTION, SEE FCM.xx
	FIELD FL2,2		;FILLER 2, CHECK FOR BEING "01"
		SV$FL2==1	;MAGIC NUMBER THAT SVFL2 MUST BE
ENDSTR

;This structure is expected to be used to pull apart a value held
;in a register.

Repeat 0,< ;Architectural definition
BEGSTR SG			;The SEGNUM field in a NSP header
	FILLER 20		;Field is 16 bits wide
	FIELD MBZ,3		;Must be zero
	FIELD DLY,1		;ACK DELAY allowed
	FIELD NUM,12		;Segment number
ENDSTR
>

Repeat 1,< ;VMS definition
BEGSTR SG			;The SEGNUM field in a NSP header
	FILLER 20		;Field is 16 bits wide
	FILLER 1
	FIELD DLY,1		;ACK DELAY allowed
	FIELD MBZ,2		;Must be zero
	FIELD NUM,12		;Segment number
ENDSTR
>

	SUBTTL Code-Generating Macros

;Use:	AC/ One of the NPS.xx state codes
;
;a)	IFSTATE AC,<OP,CC,RJ>
;	  executed if match found
;b)	IFSTATE AC,<OP,CC,RJ>,LABEL    (Go there if match found)

DEFINE IFSTATE(ac,states,glabel),<IFST1(ac,<states>,<glabel>,N,GE,L)>



;Use:	AC/ One of the NPS.xx state codes
;
;a)	IFNSTATE AC,<OP,CC,RJ>
;	  executed if match NOT found
;b)	IFNSTATE AC,<OP,CC,RJ>,LABEL    (Go there if match NOT found)

DEFINE IFNSTATE(ac,states,glabel),<IFST1(ac,<states>,<glabel>,E,L,GE)>

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

DEFINE IFST1(ac,states,glabel,mod1,mod2,mod3),<
	ZZ==0
	ZZCNT==0
	IRP states,<ZZ==ZZ ! 1B<NPS.'states>
		    ZZCNT==ZZCNT+1>
  IFE ZZCNT-1,<
	CAI'mod1 ac,NPS.'states
	IFNB <glabel>,<
		IFIDN <glabel>,<RTN>,<RET>
		IFDIF <glabel>,<RTN>,<JRST glabel>
	>>
  IFG ZZCNT-1,<			;;IF MORE THAN ONE STATE TESTED
	MOVX CX,ZZ
	ROT CX,(ac)
	IFB  <glabel>,< SKIP'mod2 CX >
	IFNB <glabel>,< JUMP'mod3 CX,glabel >
	>
PURGE ZZCNT,ZZ
>
;Macros to do mod-mask-size compares on FIELDs
;These macros preserve ac passed as argument, use ac CX
;THESE MACROS ARE NOT SKIPPABLE

;Consider the set of numbers which can fit in the field to be a circle
;with values increasing in a clockwise direction until they return to
;zero after a full circle.  X is less than Y if X falls within the
;semicircle before (counterclockwise) of Y.  X is greater than Y if it
;falls within the semicircle after (clockwise) of Y.  The number
;exactly opposite Y is considered greater than Y.

;CMODL - Skip if AC is less than target (mod mask-size)
;	 NB, This macro is more expensive than CMODLE

DEFINE CMODL(ac,mask,offset),<CMODX(ac,<mask>,<offset>,E,0)>


;CMODLE - Skip if AC is less than or equal to target (mod mask-size)
DEFINE CMODLE(ac,mask,offset),<CMODX(ac,<mask>,<offset>,E)>


;CMODGE - Skip if AC is less than target (mod mask-size)
;	  NB, This macro is more expensive than CMODG
DEFINE CMODGE(ac,mask,offset),<CMODX(ac,<mask>,<offset>,N,1)>


;CMODG - Skip if AC is greater than target (mod mask-size)
DEFINE CMODG(ac,mask,offset),<CMODX(ac,<mask>,<offset>,N)>


;The following are just for compatibility, same as OPSTR <CAMxx...>

;CMODE - Skip if AC is equal to target
DEFINE CMODE(ac,mask,offset),<OPSTR <CAME ac,>,<mask>,<offset>>

;CMODN - Skip if AC is not equal to target
DEFINE CMODN(ac,mask,offset),<OPSTR <CAMN ac,>,<mask>,<offset>>

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;CMODX - Common macro for CMODLE and CMODG to call
DEFINE CMODX(ac,mask,offset,EorN,ecase,%fin),<
	LOAD CX,<mask>,<offset>
	SUB CX,ac
  IFNB <ecase>,<JUMPE CX,%fin+ecase>
	TXN'EorN CX,1B<^D36-WID(mask)> ;;TEST HIGH-ORDER BIT OF BYTE
%fin:!
>

	SUBTTL	NSP Node Block Definition

;The node block contains all the information LLINKS has to know about a
;node.  There is a node block for all nodes that we have active links to.
;The node block is created when someone tries to connect to a node that
;does not yet have a node block associated with it.
;
;When the number of active links goes to zero, the node block is subject
;to possible deletion.  If the number of node blocks is larger than NNDMAX,
;then the now unused node block is deleted after that its counters are
;logged with a 3.2 (database reused) event.
;
;The list of node blocks are pointed to by the queue header NMXNDQ.

;Note - many counters are full words, even though they only have to be
; 16 bits wide. This is so the OPSTR logic will generate a single
; read-modify-write instruction when updating them, and will spare
; us the worry of interlocking this data base.

;Defined in D36PAR
	SUBTTL Resident Storage Allocation

;The following INTERNALs are for NMXSER, which reads and writes these
;parameters.

	INTERNAL NSPVER		;NSP VERSION NUMBER (JNTMAN only)
	INTERNAL NSPECO		;NSP VENDOR ECO NUMBER (JNTMAN only)
	INTERNAL NSPUEC		;NSP USER ECO NUMBER (JNTMAN only)

	INTERNAL EVPBYT		;Event parameter typeout
	INTERNAL EVP2BT		;  "       "        "


;See previous page for list of INTERNAL symbols.

	RESDT

NSPBFR:	DEC 3		;REMOTE IS BUFFER-RICH IF GIVES .GE. NSPBFR DRQS

NSPVRN:	EXP VER4.0	;NSP PROTOCOL VERSION NUMBER (AS SEEN IN CI MSGS)
NSPVER:	DEC 4		;NSP PROTOCOL VERSION NUMBER (AS SEEN BY USERS)
NSPECO:	DEC 0		;DEC ECO NUMBER
NSPUEC:	DEC 0		;USER ECO NUMBER

NSPRPN:	DEC 2		;NUMBER OF RESERVED PORTS TO KEEP
NSPRND:	BLOCK 1		;POINTER TO THE RESERVED PORT'S NDB

IFN FTTROLL,<
NSPTRI:	DEC 0		;INITIALIZER FOR THE TROLL COUNTER
NSPTRL:	BLOCK 1		;THE TROLL COUNTER
>;END OF IFN FTTROLL

NSPJSI:	DEC 3		;JIFFIES IN A "SHORT INTERVAL"
NSPJSC:	BLOCK 1		;SHORT INTERVAL COUNTER
NSPJLI:	DEC 60		;JIFFIES IN A "LONG INTERVAL"
NSPJLC:	BLOCK 1		;LONG INTERVAL COUNTER

NSPWGT:	%NSWGT##	;ROUND-TRIP WEIGHTING FACTOR (SEE UPDELAY)
NSPFLR:	%NSFLR##	;FLOOR UNDER ROUND-TRIP DELAY (SEE UPDELAY)
NSPRUF:	%NSRUF##	;ROOF OVER ROUND-TRIP DELAY
NSPDLY: %NSDLY##	;MULT BY NNDLY FOR RESEND TIMER (16THS) (CHKRSN)
NSPRTH:	%NSRTH##	;RE-SEND THRESHOLD, HOW OFTEN TO RESEND (CHKRSN)
NSPINA:	%NSINA##	;SECONDS BEFORE LINK IS CALLED INACTIVE (CHKINA)
NSPADL:	%NSADL##	;ACK DELAY INTERVAL IN SECONDS
NSGOAL:	DEC 8		;System-wide goal

NMXNDQ:: BLOCK QH.LEN	;QUEUE HEADER FOR NMX NODE BLOCKS
NODFLG:	DEC 0		;Set if the node queue needs pruning
NNDMAX:	DEC 20		;Maximum # of node blocks kept

BRKFLG:	BLOCK 1		;Set if 'link broken' messages should be sent
MORFLG:	BLOCK 1		;Set if 'link broken' table overflowed
BRKLEN==^D5		;# of entries in link broken table
BRKTAB:	BLOCK BRKLEN	;The 'link broken' table

NSPHMX:	BLOCK 1		;MAX LENGTH OF A HASH BUCKET LIST
NSPHTB:	BLOCK 1		;HASH TABLE ADDRESS
NSPHTS:	DEC 73		;HASH TABLE SIZE IN WORDS (PRIME NUMBER)

;** The pipe size should be approximately the same as the link quota **
NSPPSZ:	DEC 8		;Maximum pipe size is 8

NSPIFG:	DEC 0		;NON-ZERO WHEN NSP IS INITIALIZED
NSPNLA:	BLOCK 1		;NEXT LINK ADDRESS TO ASSIGN

IFN FTOPS10,<
	INTERNAL NSPAPQ
>; END IFN FTOPS10
NSPAPQ:	BLOCK QH.LEN	;ALL-PORTS QUEUE
NSPJFQ:	BLOCK QH.LEN	;JIFFY-SERVICE PORTS QUEUE
NSPRPQ:	BLOCK QH.LEN	;QUEUE OF RESERVED PORTS

NSPITQ:	BLOCK QH.LEN	;INPUT TRANSACTION QUEUE FOR NSPLCx
			;SEE BEGSTR QH FOR FORMAT OF A Q HEADER
NSPLKF:	DEC 0		;BIT MAP OF NSPLCF REQUESTS PENDING

BEGSTR LK		;BITS IN NSPLKF
	FIELD FLG,6	;A JFFO WORD, SAME ORDER AS NSPULT
	  BIT JIF	;JIFFY SERVICE, MUST BE SIGN BIT FOR NSPJIF
	  BIT CGT	;CONGESTION-DETECTED SERVICE
	  BIT RLV	;CONGESTION-RELIEVED SERVICE
ENDSTR

NSPECP:	DEC 0		;Event Communication block Pointer

IFN FTOPS20,<
NSPLKO: BLOCK 1 	;SERIAL NUMBER OF CPU WHICH OWNS NSP LOCK
NSPLOK:	DEC -1		;NSPLCx'S SEMAPHORE, -1 IS FREE
>
	XRESCD
	SUBTTL NSP's Entry Vector Table

;See procedure NSP for a description of the calling sequence used to
;call NSP.

;The offsets NV.xxx into this table are defined in D36PAR.

DEFINE NV(nam),<
IFN .-NSPFNT-NV.'nam,<	PRINTX ?NSPFNT table not in same order
			PRINTX ?as NV.'nam in D36PAR.UNV
			PASS2
			END>
	IFIW <NSP'nam&777777>>	;ALLOW EXTENDED ADDRESSING INDIRECTION

NSPFNT:	NV OPN			;OPEN FROM SESSION CONTROL
	NV ACT			;ENTER ACTIVE FROM SESSION CONTROL
	NV ACC			;ACCEPT CONNECT FROM SESSION CONTROL
	NV REJ			;REJECT CONNECT FROM SESSION CONTROL
	NV SEG			;SEND DATA FROM SESSION CONTROL
	NV DRQ			;REQUEST DATA FOR SESSION CONTROL
	NV GOL			;SET QUOTA/GOAL FROM SESSION CONTROL
	NV DSC			;SEND DISCONNECT FROM SESSION CONTROL
	NV ABO			;SEND ABORT FROM SESSION CONTROL
	NV CLS			;CLOSE PORT FROM SESSION CONTROL

	NV RCV			;RECEIVE MESSAGE FROM ROUTER
	NV ODN			;OUTPUT DONE FROM ROUTER
	NV RTS			;RETURN TO SENDER FROM ROUTER
	NV RFR			;Return to sender from remote router

NV.MAX==.-NSPFNT-1		;THE HIGHEST RECOGNIZED ENTRY OFFSET

PURGE NV
	SUBTTL NSPRCV - Receive a Message from Router

;The MSGFLG Call Tables for NSIRCV, see also the next page

;The UPTO numbers must be in order, for they
; are used by SOJLs in procedure RCVPRC

	UPTOMG== 0		;READ UPTO THE MSGFLG FIELD
	UPTODL== 1		;READ UPTO THE DLA FIELD
	UPTOSL== 2		;READ UPTO THE SLA FIELD
	UPTOAK== 3		;** use this for MSGFLG== ACK message ONLY **
	UPTOSG== 4		;READ UPTO THE SEGNUM FIELD

	NORMAL== 0		;USE THE "NORMAL" SUBLINK
	OTHER==  1		;USE THE "OTHER" SUBLINK
	NRQACK== 0		;ACKNUM FIELD NOT REQUIRED
	REQACK== 1		;ACKNUM FIELD REQUIRED (MSGFLG ==  ACK)
	NO.RESP==0		;SENDER DOES NOT EXPECT A RESPONSE
	RESPOND==1		;SENDER EXPECTS A RESPONSE
	FLOW==1			;THIS MESSAGE TYPE IS FLOW CONTROLLED
	NOFLOW==0		;THIS MESSAGE TYPE IS NOT FLOW CNTRLD

DEFINE MGTYPS,<						  ;{subtype,type}
MSGTYP(MIDSEG,RCVMDS,UPTOSG,NORMAL,NRQACK,RESPOND,FLOW  ) ; 0:   {0,0}
MSGTYP(ACK,   RCVACK,UPTOAK,NORMAL,REQACK,NO.RESP,NOFLOW) ; 1:   {0,1}
MSGTYP(NOP,   RCVNOP,UPTOMG,NORMAL,NRQACK,RESPOND,NOFLOW) ; 2:   {0,2}
MSGTYP(<0,3>, RCVILM,UPTOMG,NORMAL,NRQACK,RESPOND,NOFLOW) ; 3:   {0,3}
MSGTYP(LNKSRV,RCVLKS,UPTOSG,OTHER ,NRQACK,RESPOND,NOFLOW) ; 4:   {1,0}
MSGTYP(OTHACK,RCVACK,UPTOAK,OTHER ,REQACK,NO.RESP,NOFLOW) ; 5:   {1,1}
MSGTYP(CI,    RCVCI, UPTOMG,NORMAL,NRQACK,RESPOND,NOFLOW) ; 6:   {1,2}
MSGTYP(<1,3>, RCVILM,UPTOMG,NORMAL,NRQACK,RESPOND,NOFLOW) ; 7:   {1,3}
MSGTYP(BEGSEG,RCVBGS,UPTOSG,NORMAL,NRQACK,RESPOND,FLOW  ) ;10:   {2,0}
MSGTYP(CA,    RCVCA, UPTODL,NORMAL,NRQACK,NO.RESP,NOFLOW) ;11:   {2,1}
MSGTYP(CC,    RCVCC, UPTOMG,NORMAL,NRQACK,RESPOND,NOFLOW) ;12:   {2,2}
MSGTYP(<2,3>, RCVILM,UPTOMG,NORMAL,NRQACK,RESPOND,NOFLOW) ;13:   {2,3}
MSGTYP(INTSEG,RCVONS,UPTOSG,OTHER ,NRQACK,RESPOND,FLOW  ) ;14:   {3,0}
MSGTYP(<3,1>, RCVILM,UPTOMG,NORMAL,NRQACK,RESPOND,NOFLOW) ;15:   {3,1}
MSGTYP(DI,    RCVDI, UPTOSL,NORMAL,NRQACK,RESPOND,NOFLOW) ;16:   {3,2}
MSGTYP(<3,3>, RCVILM,UPTOMG,NORMAL,NRQACK,RESPOND,NOFLOW) ;17:   {3,3}
MSGTYP(ENDSEG,RCVENS,UPTOSG,NORMAL,NRQACK,RESPOND,FLOW  ) ;20:   {4,0}
MSGTYP(<4,1>, RCVILM,UPTOMG,NORMAL,NRQACK,RESPOND,NOFLOW) ;21:   {4,1}
MSGTYP(DC,    RCVDC, UPTOSL,NORMAL,NRQACK,NO.RESP,NOFLOW) ;22:   {4,2}
MSGTYP(<4,3>, RCVILM,UPTOMG,NORMAL,NRQACK,RESPOND,NOFLOW) ;23:   {4,3}
MSGTYP(<5,0>, RCVILM,UPTOMG,NORMAL,NRQACK,RESPOND,NOFLOW) ;24:   {5,0}
MSGTYP(<5,1>, RCVILM,UPTOMG,NORMAL,NRQACK,RESPOND,NOFLOW) ;25:   {5,1}
MSGTYP(<5,2>, RCVILM,UPTOMG,NORMAL,NRQACK,RESPOND,NOFLOW) ;26:   {5,2}
MSGTYP(<5,3>, RCVILM,UPTOMG,NORMAL,NRQACK,RESPOND,NOFLOW) ;27:   {5,3}
MSGTYP(ONYSEG,RCVONS,UPTOSG,NORMAL,NRQACK,RESPOND,FLOW  ) ;30:   {6,0}
MSGTYP(<6,1>, RCVILM,UPTOMG,NORMAL,NRQACK,RESPOND,NOFLOW) ;31:   {6,1}
MSGTYP(RCI,   RCVCI, UPTOMG,NORMAL,NRQACK,RESPOND,NOFLOW) ;32:   {6,2}
>;END OF MGTYPS MACRO

;Continued on Next Page
;Continued from Previous Page

;Receive Table Definitions

BEGSTR RT
	FIELD FLG,6
	  BIT FLO		;MSG TYPE FLOW CONTROLLED, FOR CHKRSN
	  BIT OTH		;SET IF THIS IS "OTHER" SUBLINK
	  BIT ACK		;SET IF MSG MUST INCLUDE ACKNUM FIELD
	  BIT RSP		;SET IF SENDER EXPECTS A RESPONSE
	FIELD UPT,3		;THE "UPTO" FIELD, MOD 3 BITS FOR DDT
	HWORD RTN		;LOCAL ADDR OF ROUTINE TO PROCESS MSG
ENDSTR

DEFINE MSGTYP(type,routine,upto,sublink,ack,respond,flow),<
	;;must agree with BEGSTR RT, above;;
BYTE (1)flow, sublink, ack, respond, 0, 0 (3)upto (9)0 (18)routine>

MSGTBL:	MGTYPS
RCVMAX==.-MSGTBL-1


IFN FTTRACE,<
    DEFINE MSGTYP(type,routine,upto,sublink,ack,respond,flow),<
    [ASCIZ \type\]
    >				;;USED BY MSGTRC, MESSAGE TRACE ROUTINE
MGTYNM:	MGTYPS
    MGTMAX==.-MGTYNM-1
		[ASCIZ /Illegally large/]
    ILLMGT==.-MGTYNM-1
>
	PURGE NORMAL,OTHER,NRQACK,REQACK,NO.RESP,RESPOND,MSGTYP,MGTYPS
	PURGE FLOW,NOFLOW
	SUBTTL NSPRTR - The Entry Point from Router

;NSPRTR - The place where Router calls any NSP routine.
;
;Call:	T2/ Caller's Args, or pointer thereto
;	T2/ Caller's Args
;	T3/ Offset into NSP's entry vector
;	T4/ Full-word, direct pointer to a message block
;	CALL NSPRTR
;	Normal Return
;Preserves all but T1,T2,T3,T4

	XRESCD
NSPRTR:	SAVEAC <P1,P2,MB,MS,FREE1,FREE2>
	SKIPN NSPIFG		;NSP INITIALIZED?
	RET			; -no, just return
	MOVE MB,T4		;NSP EXPECTS MESSAGE BLOCK PTR IN MB
	STOR T1,MBAR1,(MB)	;STORE THE TWO WORDS OF ARG DATA
	STOR T2,MBAR2,(MB)

	CALLRET @NSPFNT(T3)	;GO PROCESS THE CALL
				;NO RETSKPS ALLOWED HERE
	SUBTTL	Initialization - Compute Memory Requirements

;NSPCCR computes LLINKS's core requirements.
;Call:
;	PUSHJ	P,NSPCCR
;Returns:
;	T1/ Size of LLINKS core

IFN FTOPS10,<
NSPCCR::SETZ	T1,		;NO ADDITIONAL MEMORY REQUIRED
	RET			;AND RETURN
>; END IFN FTOPS10
	SUBTTL NSPINI - Initialization Entry Point

;NSPINI - This is one of the few entry points to NSP
;
;Call:	CALL NSPINI
;	  Error Return if Router says node number was illegal
;	Normal Return
;Changes T1,T2,T3,T4

;This entry is called at system startup.  Before this initialization
;is done, all calls to NSP will result in continuable BUGs and the
;calls will be ignored.

	XSWAPCD
NSPINI:	SAVEAC <P1,P2>

;Here we initialize the next logical link number to a pseudo-random
;number.  This reduces the risk of a reloaded system continuing a
;logical link from before the crash.  We use the high-order 16 bits of the
;time to avoid any similarities in the number due to a similar number of
;seconds since startup.  System time will always start on an even second.

IFN FTOPS10,MOVE T1,TIME##	;JIFFIES SINCE MIDNIGHT
IFN FTOPS20,CALL LGTAD		;GET TIME & DATE
	ANDI T1,177777		;PARE IT DOWN TO 16 BITS FOR LINK ADDRESS
	MOVEM T1,NSPNLA		;STORE AS NEXT LINK NUMBER TO ASSIGN

	CALL INIHSH		;INITIALIZE THE PORT HASH TABLE
	CALL RPNINI		;INITIALIZE THE RESERVED PORTS
	CALL EVTINI		;Initialize event communication block
	LOAD T1,IBADR,+IBBLK	;Get executor node address
	CALL FNDNOD		;Create a node block for it
	 JFCL			; - at this point we do not care
	SETOM NSPIFG		;NSP IS NOW INITIALIZED

;Assure that we are initialized before we let RTR do a node init
;and thus open the door to incoming messages.
	CALLRET RTRINI		;Pass on the torch to ROUTER

	XRESCD			;Go resident again

	SUBTTL NSP Interlock Handlers -- NSPLCQ - Queue on Failure

;NSPLCQ - Get NSP interlock, Queue on Failure
;
;Call:	T1/ Arg to callee
;	T2/ Arg to callee
;	T6/ Address of NSP routine to process this message
;	MB/ Ptr to message block
;	CALL NSPLCQ
;	Normal Return
;Callee changes T1 - T6
;TOPS20 assumes NOINT, at least.

;NSPLCQ - Get interlock, Queue message block & return if can't lock.

NSPLCQ:	STOR T1,MBAR1,(MB)	;STORE THE TWO WORDS OF ARG DATA
	STOR T2,MBAR2,(MB)
	STOR T6,MBPRC,(MB)	;STORE ADDR OF PROCESSING PROCEDURE
	D36OFF			;INTERLOCK FOR ENDQUE BELOW
	AOSE NSPLOK		;NO, TEST AND SET THE SEMAPHORE
	JRST NSPLQ1		;LOCKED OR INT LEVEL, QUEUE MSG BLK AND LEAVE
	APRID NSPLKO		;WAS FREE, SET NEW OWNER CPU
	D36ON			;UNDO THE D36OFF
	OPSTR <CALL @>,MBPRC,(MB) ;GO PROCESS, NO ERROR RETURNS ALLOWED
	CALLRET NSPULK		;GO PROCESS.

NSPLQ1:	ENDQUE MB,NSPITQ,MB.NXT,T1
	D36ON			;UNDO THE D36OFF
	RET			;ONLY RETURN
	SUBTTL NSP Interlock Handlers -- NSPLCW - Wait on Failure

;NSPLCW - Get NSP interlock, Wait on Failure
;
;Call:	T1/ Passed through to callee
;	T2/   "       "     "   "
;	T6/ Address of NSP routine to process this message
;	CALL NSPLCW		;MB NOT REQUIRED
;	Normal Return
;Callee changes T1 - T6
;TOPS20 assumes NOINT, at least.

;NSPLCW - Get interlock, Wait if can't get lock.
;	  TOPS10 - Lock should always be free on entry except on SMP.
;	  TOPS20 - Another process may be blocked for page I/O.

NSPLCW:				;T1 & T2 SAVED FOR CALLEE
	SAVEAC MB		;CALLER NEEDS POINTER TO THIS MSG BLK
IFN FTOPS20,<
NSPLW1:!AOSN NSPLOK		;TEST AND SET THE SEMAPHORE
	JRST NSPLW3		;WAS FREE, GO USE IT
	SKIPG NSKED		;IN USE, ARE WE NOSKED
	SKIPE INSKED		; OR ARE WE AT SCHEDULAR LEVEL?
	JRST NSPLW2		;YES. BUG
	PUSH P,T1		;MUST SAVE T1 & T2 TO PASS TO CALLEE
	PUSH P,T2		; SINCE THEY ARE THE ARGS PASSED
	XMOVEI T1,NSPLWB	;NO, SET UP SCHEDULAR TEST
	MDISMS			;WAIT FOR COMPETING PROCESS TO FINISH
	POP P,T2		;T6 WILL BE SAVED ACROSS MDISMS - IT IS Q2
	POP P,T1
	JRST NSPLW1		;OK, LETS TRY AGAIN

;Must run in section 1 since the scheduler test data structure is currently
; only 18 bits wide.
	RESCD
NSPLWB:	SKIPL NSPLOK		;IS NSP LOCK FREE YET?
	JRST (T4)		;NO, SLEEP ON
	JRST 1(T4)		;YES, WAKE UP FORK
	XRESCD

NSPLW2:	BUG.(CHK,LLIBWK,LLINKS,SOFT,<SCTNSF call from sched w/o lock>,<<T6,CALLER>>,<

Cause:	The DECnet entry point NSP has been called from scheduler
	level when the NSP interlock was locked.  This should never
	happen.

Action:	Inspect the stack to find out who the offender was.

Data:	CALLER - The address of the routine that requested the interlock

>)
	  RET			;LET CALLER WORRY ABOUT NO DECNET ACTION

NSPLW3:
>;END IFN FTOPS20

IFN FTOPS10,<
NSPLW1:!AOSE NSPLOK		;TEST AND SET THE SEMAPHORE
	JRST NSPLW1		;LOCKED, SPIN-WAIT UNTIL LOCK IS FREE
>;END IFN FTOPS10
	APRID NSPLKO		;SET THE OWNER OF THE INTERLOCK
	CALL 0(T6)		;(T1,T2)GO PROCESS, NO ERROR RETURNS ALLOWED
	CALLRET NSPULK		;CHECK FOR QUEUED TRANSACTIONS
	SUBTTL NSP Interlock Handlers -- NSPLCF - Flags set for Failure

;NSPLCF - Get NSP interlock, Flags set for Failure
;
;Call:	No Arguments
;	CALL NSPLCF
;	Normal Return
;Callee changes T1 - T6

;NSPLCF - Get interlock, return if can't, caller has set Flag for NSPULK.

NSPLCF:	AOSE NSPLOK		;TEST AND SET THE SEMAPHORE
	RET			;LOCKED, CALLER HAS SET FLAG
	APRID NSPLKO		;SET OWNER OF THE INTERLOCK
	CALLRET NSPULK		;WAS FREE, NOW LOCKED, GO PROCESS MSG
;NSPULK - Called from NSPLCx to process message(s)
;
;Call:
;	CALL NSPULK
;	Normal Return
;Vicariously changes T1,T2,T3,T4,T5,T6,P1,P2,MS

;We don't need CSKED here, since we are always called either by
;Session Control which has CSKED or at some interrupt level.

NSPULK:
NSPUL1:	D36OFF			;TURN OFF NETPI, INTERLOCK SMP
	SKIPE T1,NSPLKF         ;GET LOCK FLAGS FOR NSPLCF REQUESTS
	JFFO T1,NSPUL3          ;SEE WHAT REQUESTS ARE PENDING
	LKJIF==LKJIF		;PUT SYMBOL IN CREF FOR JFFO'S ACCESS
	LKCGT==LKCGT		; DITTO
	LKRLV==LKRLV		; DITTO
                                ;NO FLAGGED REQUESTS NOW
	DEQUE MB,NSPITQ,MB.NXT,NSPUL4 ;GO TO NSPUL4 IF EMPTY
	D36ON			;GOT ONE, UNDO THE D36OFF
	OPSTR <CALL @>,MBPRC,(MB) ;GO PROCESS, NO ERROR RETURNS ALLOWED
	JRST NSPUL1		;PROCESS THIS MESSAGE NOW

NSPUL3:	MOVE T1,NSPULB(T2)      ;GET BIT TO CLEAR
	ANDCAM T1,NSPLKF        ;CLEAR FLAG JFFO JUST FOUND
	D36ON                   ;END OF D36OFF AT NSPUL1
	CALL @NSPULT(T2)        ;GO PROCESS ROUTINE INDICATED BY JFFO
	JRST NSPUL1		;SEE WHAT MORE THERE IS TO DO UNDER LOCK

NSPULB:	EXP 1B0                 ;BIT TO CLEAR FOR T2=0
	EXP 1B1                 ;BIT TO CLEAR FOR T2=1
	EXP 1B2                 ;BIT TO CLEAR FOR T2=2

NSPULT:	IFIW!<NSIJIF&777777>	;1B0 MEANS JIFFY REQUEST
	IFIW!<NSICGT&777777>	;1B1 MEANS CONGESTION-DETECTED
	IFIW!<NSIRLV&777777>	;1B2 MEANS CONGESTION-RELIEVED

;We have found both NSPLKF and NSPITQ empty in one D36OFF

NSPUL4:	SETOM NSPLKO		;NO CPU OWNS THE INTERLOCK
	SETOM NSPLOK		;FREE THE NSP INTERLOCK
	D36ON			;UNDO THE D36OFF
	RET			; RETURN TO NSPLCx
	SUBTTL Session Control Calls -- NSP - The Entry Point

;NSP - The main place where Session Control calls any NSP routine.
;
;Call:	T1/ Caller's Args, or pointer thereto
;	T2/ Caller's Args
;	T3/ Offset into NSP's entry vector
;	T4/ Full-word, direct pointer to a message block
;	CALL NSP
;	Normal Return
;Preserves all but T1,T2,T3,T4

	XRESCD
NSP:	SAVEAC <P1,P2,MS,MB,FREE1,FREE2>
	SKIPN NSPIFG		;NSP INITIALIZED?
	RET			; -no, just return
	MOVE MB,T4		;NSP EXPECTS MESSAGE BLOCK PTR IN MB
	CALLRET @NSPFNT(T3)	;GO PROCESS THE CALL
				;NO RETSKPS ALLOWED HERE
	SUBTTL Session Control Calls -- NSPOPN - Open a Link

;NSPOPN - Called by Session Control via NSP with NV.OPN offset.
;
;Call:	T1/ Pointer to argument block, see BEGSTR OA, in D36PAR.MAC
;	T2/ Length (words) of argument block T1 points to.
;	MB/ Message Block
;	CALL NSPOPN
;	Normal Return,	on success: T1/ new NSPpid
;			on failure: T1/ -1
;Changes T1,T2,T3,T4

;The OPEN call requires an immediate response, so it calls NSPLCW
;rather than NSPLCQ to check the interlock.  Unlike most interface
;routines, NSPOPN will return the message block it was called with.

NSPOPN:	XMOVEI T6,NSIOPN	;ADDRESS OF INTERLOCKED ROUTINE
	CALL NSPLCW		;WILL RETURN WHEN MSG HAS BEEN PROCESSED
	LOAD T1,MBAR1,(MB)	;RETRIEVE THE "T1" ARG FOR SESSION CONTROL
	RET

NSIOPN:	CAIE T2,OA.LEN		;IS IT THE RIGHT LENGTH?
	CALLRET DNFWDS		; -no, should never happen, deallocate
	PUSH P,T1		;SAVE POINTER TO ARGUMENT BLOCK
	CALL NS1OPN		;CALL MEAT OF OPEN ROUTINE
	POP P,T1
	CALLRET DNFWDS		;DEALLOCATE THE ARGUMENT BLOCK
;Here to process the OPEN function

NS1OPN:	MOVE P1,T1		;PUT ARG BLOCK POINTER IN P1 (P1 ALREADY SAVED)
	CALL NEWLLA		;RETURN NEW LLA IN T1
	HRRZS T1		;LLA IN RH, NO RLA IN LH
	LOAD T2,OANOD,(P1)	;GET NODE ID PASSED BY SESSION CONTROL
	CALL MAKPRT		;MAKE A NEW PORT BLOCK, PTR IN EL
	  JRST [SETZRO MBAR1,(MB) ;RETURN 0 FOR NO-RESOURCES
		RET]
	LOAD T1,OASCB,(P1)	;SCTL'S NAME FOR PORT
	STOR T1,ELSCB,(EL)
	LOAD T1,OAFLO,(P1)	 ;COPY THE RECEIVE FLOW ORDERS
	STOR T1,ESRFL,+EL.NSL(EL) ; INTO THE "NORMAL" SUBLINK BLOCK
  IFN FTGOL <
	LOAD T1,OAGOL,(P1)	;COPY THE GOALS FROM ARG BLOCK
	STOR T1,ESGOL,+EL.NSL(EL) ; TO THE NEW PORT BLOCK
	STOR T1,ESCGL,+EL.NSL(EL) ; CONGESTION GOAL TOO
  >
	LOAD T1,OASIZ,(P1)	;COPY MAX SEGMENT SIZE (BYTES)
	STOR T1,ELSIZ,(EL)
	LOAD T1,OASCV,(P1)	;SCTL'S ENTRY ADDRESS
	STOR T1,ELSCV,(EL)
	LOAD T1,OACIR,(P1)	;GET LOOPBACK CIRCUIT IF ANY
	STOR T1,ELCIR,(EL)
        NEWSTATE OP		;NEW PORT IS IN "OPEN" STATE
	CALL GETPID		;RETURN THE NSPPID IN T1
	STOR T1,MBAR1,(MB)	;TELL SESSION CONTROL THE NEW NSPPID
	RET
	SUBTTL Session Control Calls -- NSPGOL - Set New Data Request Goals

;NSPGOL - Session Control call to set new goals for a link
;
;Call:	T1/ NSPpid
;	T2/ Goal
;	MB/ Message Block
;	CALL NSPGOL
;	Normal Return
;Changes T1,T2

NSPGOL:	JSP T6,NSPLCQ		;QUEUE UP THE REQUEST

NSIGOL:
  IFN FTGOL <
	LOAD T1,MBAR1,(MB)	;RESTORE CALLER'S ARGS TO ACS T1 & T2
	LOAD P1,MBAR2,(MB)	;SAVE GOAL
	CALL FNDPID		;GET PORT POINTER IN EL, BUG IF FAIL
	  CALLRET FREMSG	;IGNORE CALL IF BUG GIVEN
	STOR P1,ESGOL,+EL.NSL(EL)
	STOR P1,ESCGL,+EL.NSL(EL) ;AFTER-CONGESTION GOAL TOO
  >
	CALLRET FREMSG

;This goal will take effect next time it is used.  We have nothing
;to do now to expedite the process.
	SUBTTL Session Control Calls -- NSPACT - Enter Active

;NSPACT - Enter Active Call from Session Control
;
;Call:	T1/ NSPpid
;	T2/ ignored
;	MB/ Message Block, whose User Data MSD points to DATA-CTL
;			   fields of the outgoing message.  The User Data
;			   field starts with a one-byte binary count of the
;			   bytes in the field.  Session Control must provide
;			   this count (even if it is zero).
;	CALL NSPACT
;	Normal Return
;Changes T1,T2,T3,T4

NSPACT:	JSP T6,NSPLCQ

NSIACT:	LOAD T1,MBAR1,(MB)	;RESTORE CALLER'S ARGS TO ACS T1 & T2
;	LOAD T2,MBAR2,(MB)	;...
	CALL FNDPID		;GET PORT POINTER IN EL, BUG IF FAIL
	  CALLRET FREMSG 	;IGNORE CALL IF BUG GIVEN

	MOVX T1,MGFCI		;Build a CI message NSP header
	CALL BLDRCI		; by using common routine

	LOAD T1,ELNDB,(EL)	;GET PTR TO NSP NODE BLOCK
	INCR NNXCC,(T1)		;INCREMENT NUMBER OF CI MSGS SENT
	NEWSTATE CI		;GO INTO CONNECT INIT STATE

	SETZRO MBOTH,(MB)	;SEND CI ON 'NORMAL' SUBLINK
	;Ask for the message back so we can remake it to a RCI and, if
	; necessary, retransmit it
	MOVX T1, ST%NRS ! ST%ACK ! ST%RQR ! ST%NTR
	CALLRET SNDRTR		;SEND TO ROUTER
	SUBTTL Session Control Calls -- NSPACC - Accept Connect

;NSPACC - Accept Connect Call from Session Control
;
;Call:	T1/ Pointer to argument block, see BEGSTR AA,
;	T2/ Length (words) of block T1 points to
;	MB/ Message Block, whose User Data MSD points to DATA-CTL
;			   fields of the outgoing message.  The User Data
;			   field starts with a one-byte binary count of the
;			   bytes in the field.  Session Control must provide
;			   this count (even if it is zero).
;	CALL NSPACC
;	Normal Return
;Changes T1,T2,T3,T4

NSPACC:	JSP T6,NSPLCQ		;QUEUE UP THE REQUEST

NSIACC:	LOAD T1,MBAR1,(MB)	;RESTORE CALLER'S ARGS TO ACS T1 & T2
	LOAD T2,MBAR2,(MB)	;...
	CAIE T2,AA.LEN		;IS IT THE RIGHT LENGTH?
	JRST NSIAC1		; -no, should never happen
	PUSH P,T1		;SAVE POINTER TO THE ARGUMENT BLOCK
	CALL NS1ACC		;CALL MEAT OF THE ACCEPT ROUTINE
	POP P,T1		;RESTORE PTR TO ARG BLK FOR FREE WORDS
	CALLRET DNFWDS		;DEALLOCATE THE ARGUMENT BLOCK

;Here after BUG

NSIAC1:	CALL DNFWDS		;FREE THE ARG BLK
	CALLRET FREMSG		;AND THE MSG BLK WHICH CARRIED IT

;Here to process the accept function

NS1ACC:	MOVE P1,T1		;POINTER TO ARG BLOCK (P1 ALREADY SAVED)
	LOAD T1,AAPID,(P1)	;GET PID SESSION CONTROL IS AIMING FOR
	CALL FNDPID		;GET PORT POINTER IN EL, BUG IF FAIL
	  CALLRET FREMSG	;IGNORE CALL IF BUG GIVEN
	LOAD T1,AASCB,(P1)	;GET SESSION CONTROL'S NAME FOR PORT
	STOR T1,ELSCB,(EL)	;STORE IN THE PORT BLOCK
	LOAD T1,AAFLO,(P1)	;GET FLOW CONTROL VARIABLE
	STOR T1,ESRFL,+EL.NSL(EL) ;STORE RECEIVE FLOW OF NORMAL SUBLINK
  IFN FTGOL <
	LOAD T1,AAGOL,(P1)	;COPY THE GOAL FROM ARG BLOCK
	STOR T1,ESGOL,+EL.NSL(EL) ; TO THE NEW PORT BLOCK
	STOR T1,ESCGL,+EL.NSL(EL) ; AFTER-CONGESTION GOAL TOO
  >
	LOAD T1,AASIZ,(P1)	;GET SCTL'S MAX SEGMENT SIZE (BYTES)
	OPSTR <CAMGE T1,>,ELSIZ,(EL) ;COMPARE WITH REMOTE'S MAX SIZE
	STOR T1,ELSIZ,(EL)	;STORE MINIMUM OF THE TWO MAXIMA
	LOAD T1,AASCV,(P1)	;SCTL'S ENTRY ADDRESS
	STOR T1,ELSCV,(EL)
	NEWSTATE CC		;PORT IS NOW IN "CONNECT CONFIRMED" STATE
	SETZRO ELSCM,(EL)	;SEE PROCEDURE JCHSCM FOR EXPLANATION

;Continued on Next Page
;Continued from Previous Page

;Procedure SENDCC sends a Connect Confirm message to the remote.
;If the remote is a Phase II node, there is no more to do.  If the
;remote is a Phase III node, the Connect Confirm message is error
;controlled.  We are expected to resend it after timeout just as a data
;message until we receive a "normal" data ACK from the remote.
;
;In order to fit the Connect Confirm message into the normal
;ACK-handling scheme of NSP-36, we pretend that the Connect Confirm
;message is message number zero (the first data message is defined to
;be message one).  Of course, the message number does not go into the
;message, there is no number field in a Connect Confirm message.  The
;message number in the message block is only used by the resend and ACK
;routines to figure out which message to resend or ACK.

	CALL MAKHDR		;INITIALIZE THE NSP HEADER MSD AND DNxyaBY
;MSGFLG
	MOVX T1,MGFCC		;GET "CONNECT CONFIRM" MSGFLG CODE
	CALL WRTMGF		;WRITE THE MSGFLG FIELD
;DLA
	LOAD T1,ELRLA,(EL)	;GET REMOTE LINK ADDRESS (DESTINATION)
	CALL DNP2BY		;WRITE TWO-BYTE FIELD
;SLA
	LOAD T1,ELLLA,(EL)	;GET THE LOCAL LINK ADDRESS (SOURCE)
	CALL DNP2BY		;WRITE TWO-BYTE FIELD
;SERVICES
	CALL WRTSRV		;WRITE THE SERVICES FIELD
;INFO
	MOVE T1,NSPVRN		;GET MY VERSION CODE (0=3.2, 1=3.1)
	CALL DNPEBY		;WRITE EXTENSIBLE BYTE
;SEGSIZE
	LOAD T1,ELSIZ,(EL)	;GET CALCULATED SEG SEIZE
	CALL DNP2BY		;WRITE TWO-BYTE FIELD
;The DATA-CTL field has already been provided by Session Control.

	SETZRO NMSGN,(MB)	;SEGMENT NUMBER, ...
	SETZRO MBOTH,(MB)	;GO ON THE "NORMAL" SUBLINK
	SETZRO ESLMA,+EL.NSL(EL) ;LAST MSG # ASSIGNED, SEE STORY ABOVE
	MOVX T1, ST%NRSC ! ST%NAK ! ST%NRQR ! ST%TRL
	LOAD T2,ELVER,(EL)	;GET REMOTE'S VERSION NUMBER
	CAIN T2,VER3.1		;IS IT PHASE II (NSP VERSION 3.1)?
	CALLRET SNDRTR		;YES, DON'T SET UP FOR ACK

;Here to set up for the ACK that a Phase III link expects for a CC msg.
;We could just let the CC msg sit on the AKQ until we get an ACK for
;the first real msg we send on the normal sublink, but if the traffic
;on this link will all be one-way receiving, we could tie up a msg blk
;for a long time.

	TXO T1,ST%ACK		;WE DO EXPECT AN ACK FOR OUR CC MSG
	SETONE ESLAR,+EL.NSL(EL) ;LAST ACK REC'D WAS -1 SO NSIODN
				 ; WON'T THINK THIS (0) HAS ALREADY
				 ; BEEN ACKED.
	CALLRET SNDRTR		;SEND TO ROUTER
	SUBTTL Session Control Calls -- NSPSEG - Send Data

;NSPSEG - Session Control wants to send data on either sublink
;
;Call:	T1/ NSPpid
;	T2/ Flags, see definitions in BEGSTR DA
;	CALL NSPSEG
;	Normal Return
;Changes T1,T2,T3,T4

NSPSEG:	JSP T6,NSPLCQ		;QUEUE UP THE REQUEST

NSISEG:	LOAD T1,MBAR1,(MB)	;RESTORE CALLER'S ARGS TO ACS T1 & T2
	LOAD P2,MBAR2,(MB)	;SAVE FLAGS IN A SAFE PLACE
	CALL FNDPID		;GET PORT POINTER IN EL, BUG IF FAIL
	  CALLRET FREMSG	;IGNORE CALL IF BUG GIVEN
	STOR EL,NMPRT,(MB)	;SAVE PTR TO PORT IN MESSAGE BLOCK
	LOAD T1,ELSTA,(EL)	;GET CURRENT PORT STATE
	IFNSTATE T1,<CC,RN,DN>,SCNIRS ; BY CALLING SC'S NOT IN RUN STATE ENTRY
	MOVE T1,MB.FLG(MB)	;GET THE PUBLIC FLAGS FROM MESSAGE
	XMOVEI ES,EL.NSL(EL)	;ASSUME THIS IS ON "NORMAL" SUBLINK
	TXNN T1,MBOTH		;ON THE "OTHER" SUBLINK?
	JRST NSISG1		;NO, SKIP "OTHER" PROCESSING

	XMOVEI ES,EL.OSL(EL)	;YES, POINT ES AT "OTHER" SUBLINK BLOCK
	SETONE <MBBOM,MBEOM>,(MB) ;SET THESE IN CASE WE CONTINUED A BUG
	TMNE QHBEG,+ES.XMQ(ES)	;IF NO XMIT QUEUE, ALL IS OK
	BUG.(CHK,LLIQIN,LLINKS,SOFT,<Queued interrupt message illegal>,<<EL,ELPTR>,<ES,ESPTR>,<MB,MBPTR>>,<

Cause:	LLINKS was asked to transmit two interrupt messages simultaneously.
	A maximum of one is allowed.  This is a software problem.  Please
	submit a SPR if it happens more than once, and include a dump of the
	system and the additional data.

Data:	ELPTR - Address of EL block
	ESPTR - Address of ES block
	MBPTR - Address of message block
>)
NSISG1:
	CALL NEWSGN		;PUT NEW SEG NUMBER IN MSG BLK
IFN FTTRACE,<
	LOAD T1,ESXFL,(ES)	;GET TRANSMIT FLOW CONTROL MODE
	CAIN T1,FCM.NO		;IS IT SEGMENT OR MESSAGE?
	JRST NSISG2		;NO, NO FLOW CONTROL, DON'T COMPLAIN
	LOADE T1,ESXLD,(ES)	;ESXLD MAY BE NEGATIVE
	JUMPG T1,NSISG2		;ANY LOCAL DRQS AVAILABLE?
	TRACE NSP,Session Control sent without DRQs
NSISG2:
>
;It is OK for Session Control to appear to send without DRQs here
;because it may have sent a message to NSP which was in NSPITQ
;while NSP was sending a negative data request to Session Control.

;Continued on Next Page
;Continued from Previous Page

;Update the NSP node block's counters for Network Management

	LOAD P1,ELNDB,(EL)	;GET PTR TO NSP NODE BLOCK
	XMOVEI T1,UD.MSD(MB)	;POINTER TO USER-DATA MSD
	CALL DNSLNG		;RETURN LENGTH (BYTES) OF USER DATA IN T1
	OPSTRM <ADDM T1,>,NNXBC,(P1) ;Update count of user bytes sent to node
	OPSTRM <AOS>,NNXMC,(P1)	;Update count of user messages

;The number of messags sent to the node is updated in SNXBKK, since
;all types of NSP messages are counted.

	CALLRET PROCXB		;SEND THIS MSG BLK NOW (SMASHES MB & MS)
	SUBTTL Session Control Calls -- NSPDRQ - Data Request Call

;NSPDRQ - Session Control's Data Request Call
;
;Call:	T1/ NSPpid
;	T2/ Flags (OFF) and count, see BEGSTR QA
;	CALL NSPDRQ
;	Normal Return
;Changes T1,T2,T3,T4

NSPDRQ:	JSP T6,NSPLCQ		;QUEUE UP THE REQUEST

NSIDRQ:	LOAD T1,MBAR1,(MB)	;RESTORE CALLER'S ARGS TO ACS T1 & T2
	LOAD P2,MBAR2,(MB)	;SAVE ARG FLAGS IN PRESERVED AC
	CALL FNDPID		;GET PORT POINTER IN EL, BUG IF FAIL
	  CALLRET FREMSG	;IGNORE CALL IF BUG GIVEN
	XMOVEI ES,EL.NSL(EL)	;ASSUME "NORMAL" SUBLINK
	TMNE MBOTH,(MB)		;SCTL ASKED FOR "OTHER" SUBLINK?
	XMOVEI ES,EL.OSL(EL)	;YES, LOAD IT INTO SUBLINK POINTER

	LOAD T1,QACNT,+P2	;GET THE COUNT FIELD PASSED, ALWAYS POSITIVE
	OPSTRM <ADDM T1,>,ESRLD,(ES) ;ADD TO THE RECEIVE, LOCAL COUNT
	CALL FREMSG		;FREE MSG WE GOT FROM SCTL
	CALLRET PROCRQ		;PROCESS RECEIVE Q AND SEND ANY
				; UNUSED DATA REQUESTS

;PROCRQ will check for any data requests that need to be sent to the
;remote and will send them or request jiffy service as appropriate.
	SUBTTL Session Control Calls -- NSPREJ - Reject a Connection

;NSPREJ - Reject Call from Session Control
;
;Call:	T1/ NSPpid
;	T2/ ignored
;	MB/ Message Block, whose User Data MSD points to reason and DATA-CTL
;			   fields of the outgoing message.  The User Data
;			   field starts with a one-byte binary count of the
;			   bytes in the field.  Session Control must provide
;			   this count (even if it is zero).
;	CALL NSPREJ
;	Normal Return
;Changes T1,T2,T3,T4

NSPREJ:	JSP T6,NSPLCQ

NSIREJ:	LOAD T1,MBAR1,(MB)	;RESTORE CALLER'S ARGS TO ACS T1 & T2
;	LOAD T2,MBAR2,(MB)	;...
	CALL FNDPID		;GET PORT POINTER IN EL, BUG IF FAIL
	  CALLRET FREMSG	;IGNORE CALL IF BUG GIVEN
	CALL MAKPRS		;MAKE THIS PORT A RESERVED PORT
	  CALLRET FREMSG	;IGNORE CALL IF BUG GIVEN
	XMOVEI ES,EL.NSL(EL)	;SEND THIS ON THE "NORMAL" SUBLINK
	NEWSTATE DR		;GET "DISCONNECT REJECT" STATE CODE
	SETZRO ELSCM,(EL)	;SEE PROCEDURE JCHSCM FOR EXPLANATION
	LOAD T1,ELNDB,(EL)	;GET PTR TO NSP NODE BLOCK
	INCR NNXRC,(T1)		;INCREMENT NUMBER OF REJECTS XMITTED

	CALL MAKHDR		;INITIALIZE THE NSP HEADER MSD AND DNxyaBY
;MSGFLG
	MOVX T1,MGFDI		;GET "DISCONNECT INIT" MSGFLG CODE
	CALL WRTMGF		;WRITE THE MSGFLG FIELD
;DLA
	LOAD T1,ELRLA,(EL)	;GET REMOTE LINK ADDRESS (DESTINATION)
	CALL DNP2BY		;WRITE TWO-BYTE FIELD
;SLA
	LOAD T1,ELLLA,(EL)	;ADMIT AN LLA SO WE CAN RECEIVE THE DC
	CALL DNP2BY		;WRITE TWO-BYTE FIELD

;The REASON and DATA-CTL fields have been passed from Session Control
;in the "user-data" MSD

	CALL NEWSGN		;MAKE NSIODN THINK THIS MESSAGE IS NEW
	MOVX T1, ST%NRSC ! ST%ACK ! ST%NRQR ! ST%TRL
	CALLRET SNDRTR		;SEND TO ROUTER
;WRTSRV - Write the SERVICES field of a CI or CC message
;
;Call:	EL/ Port Block
;	CALL WRTSRV
;	Normal Return
;Changes T1

WRTSRV:

;The SERVICES field is complicated, so here it is once with literals,
;this seemed simpler than trying to define the thing in structures and
;then fill it in with strange STORs that are just as "literal" as the
;1 and 2 below.


	LOAD T1,ESRFL,+EL.NSL(EL) ;THE FCOPT (RECEIVE FLOW CONTROL OPTION)
	LSH T1,2		;SHIFT IT INTO THE EXPECTED POSITION
	TRO T1,1		;LITERAL 1 EXPECTED IN LOW BIT
	CALLRET DNPEBY		;WRITE EXTENSIBLE BYTE


;WRTACK - Write out the ACKNUM field of the NSP message header
;
;Call:	EL/ The Port Block
;	ES/ The Sublink Block
;	MB/ The Message Block
;	CALL WRTACK
;	Normal Return
;Changes T1

WRTACK:	JE ESACK,(ES),WRTAK1	;TRY OTHER SUBLINK IF THIS DOESN'T NEED ACK
	LOAD T1,ESLMR,(ES)	;GET LAST MESSAGE RECEIVED NUMBER
	TMNE ESNAK,(ES)		;IS THIS ACK REALLY A NAK?
	TXOA T1,<AKPNT ! <FLD(AK$QNK,AKQAL)>> ;YES, QUALIFY IT AS A NAK
	TXO  T1,<AKPNT ! <FLD(AK$QAK,AKQAL)>> ;NO,  QUALIFY IT AS AN ACK
	SETZRO <ESACK,ESNAK>,(ES) ;NO LONGER NEED TO SEND AN ACK
	CALL DNP2BY		;ADD IN THE OPTIONAL ACKNUM FIELD
	SETZRO ESDLY,(ES)	;No more ACK DELAY until remote requests it
WRTAK1:

;Write optional ACKOTH field
	LOAD T1,ELVER,(EL)	;GET REMOTE NSP VERSION
	CAIGE T1,VER4.0		;IS IT VERSION 4.0?
	RET			;NO, MUSTN'T SEND CROSS-SUBCHN ACKS THEN
	XMOVEI T2,EL.OSL(EL)	;ASSUME WE WERE CALLED ON NSL, NOW ON OSL
	TMNE ESOTH,(ES)		;TRUE?
	XMOVEI T2,EL.NSL(EL)	;NO, WE WERE CALLED ON OSL, NOW ON NSL
	JE ESACK,(T2),RTN	;IF THIS DOESN'T NEED ACK, WE'RE DONE
	SETZRO ESDLY,(ES)	;No more ACK DELAYs until remote requests it
	LOAD T1,ESLMR,(T2)	;GET LAST MESSAGE RECEIVED NUMBER
	TMNE ESNAK,(T2)		;IS THIS ACK REALLY A NAK?
	TXOA T1,<AKPNT ! <FLD(AK$CNK,AKQAL)>> ;YES, QUALIFY IT CROSS NAK
	TXO  T1,<AKPNT ! <FLD(AK$CAK,AKQAL)>> ;NO,  CROSS-SUBCHANNEL ACK
	SETZRO <ESACK,ESNAK>,(T2) ;NO LONGER NEED TO SEND AN ACK
	CALLRET DNP2BY		;ADD IN THE OPTIONAL ACKOTH FIELD

;WRTMGF - Write the MSGFLG field of a message
;
;Call:	T1/ The message flag code (MGFxxx)
;	MB/ The Message Block
;	CALL WRTMGF
;	Normal Return
;Changes T1,T2,T3,T4

WRTMGF:	STOR T1,NMMGF,(MB)	;STORE THE MESSAGE TYPE IN MESSAGE BLOCK
	CALLRET DNPEBY		;WRITE AN EXTENSIBLE BYTE

	SUBTTL Session Control Calls -- NSPDSC - Synch Disconnect

;NSPDSC - Disconnect Call from Session Control
;
;Call:	T1/ NSPpid
;	MB/ Message Block, whose User Data MSD points to reason and DATA-CTL
;			   fields of the outgoing message.  The User Data
;			   field starts with a one-byte binary count of the
;			   bytes in the field.  Session Control must provide
;			   this count (even if it is zero).
;	CALL NSPDSC
;	Normal Return
;Changes T1,T2,T3,T4

NSPDSC:	JSP T6,NSPLCQ

NSIDSC:	LOAD T1,MBAR1,(MB)	;RESTORE CALLER'S ARGS TO ACS T1 & T2
;	LOAD T2,MBAR2,(MB)	;...
	CALL FNDPID		;GET PORT POINTER IN EL, BUG IF FAIL
	  CALLRET FREMSG	;IGNORE CALL IF BUG GIVEN
	LOAD T1,ELSTA,(EL)	;GET CURRENT PORT STATE

	IFNSTATE T1,<CR,CC,RN>,SCNIRS
				;SESSION CONTROL: NOT IN RUN STATE
				;AS LONG AS NOT CI STATE, NO CI MSG IN ELDIM
	STOR MB,ELDIM,(EL)	;STORE MSG POINTER TO SEND LATER
	NEWSTATE DI		;GO INTO "DI" STATE
	CALLRET SCLOSE		;START UP THE CLOSE FUNCTION
	SUBTTL Session Control Calls -- NSPABO - Abort Link

;NSPABO - Session Control Call to Abort Link
;
;Call:	T1/ NSPpid
;	MB/ Message Block, whose User Data MSD points to reason and DATA-CTL
;			   fields of the outgoing message.  The User Data
;			   field starts with a one-byte binary count of the
;			   bytes in the field.
;	CALL NSPABO
;	Normal Return
;Changes T1,T2,T3,T4

;We can't call RETBUF just now and be sure that it will clear out
;all the messages on the XMQ,RCQ and AKQ queues, because there may
;still be some messages Out In Router.  CHKCLS checks for this,
;and then checks the ELABO flag when ELORC (out in Router).
;When ELORC is zero, CHKCLS calls RETBUF for us.

NSPABO:	JSP T6,NSPLCQ

NSIABO:	LOAD T1,MBAR1,(MB)	;RESTORE CALLER'S ARGS TO ACS T1 & T2
	CALL FNDPID		;GET PORT POINTER IN EL, BUG IF FAIL
	  CALLRET FREMSG	;IGNORE CALL IF BUG GIVEN
	LOAD P1,ELSTA,(EL)	;GET CURRENT PORT STATE

	IFNSTATE P1,<CC,RN>,SCNIRS
				;SESSION CONTROL: NOT IN RUN STATE

	SETONE ELABO,(EL)	;SET THE ABORT FLAG
				;ELABO TELLS CHKCLS TO SEND VERY SOON
				;AS LONG AS NOT CI STATE, NO CI MSG IN ELDIM
	STOR MB,ELDIM,(EL)	;STORE MSG POINTER TO SEND LATER
	NEWSTATE DI		;GO INTO "DI" STATE
	CALLRET SCLOSE		;START UP THE CLOSE FUNCTION
	SUBTTL Session Control Calls -- NSPCLS - Close Port

;NSPCLS - Session Control Close Port Call
;
;Call:	T1/ NSPpid
;	T2/ SLBid in case this is an early release or RESET (or zero)
;	CALL NSPCLS
;	Normal Return
;Changes T1,T2,T3,T4

;NSPCLS can be called with or without a message block.  It is called
;with a message block if there is any chance that the call will have to
;be queued on the NSP interlock, eg, when NSP calls NSPCLS itself or
;when Session Control calls NSPCLS in a subroutine called by NSP.
;
;NSPCLS is called without a message block from RESET and RELEASE, which
;must not fail, and thus cannot affort the risk of requiring a message
;block.  Both these funtions are always called from UUO level and never
;from Session Control nested under NSP.

NSPCLS:	XMOVEI T6,NSICLS	;GET ADDRESS OF INTERLOCKED CODE
	JUMPN MB,NSPLCQ		;USE Q'D INTERLOCK IF WE HAVE MSG BLK
	CALLRET NSPLCW		;ELSE WAIT FOR INTERLOCK

NSICLS:	JUMPE MB,NSICL0		;NO SAVED ARGS IF NO MSG BLK
	LOAD T1,MBAR1,(MB)	;IF WE Q'D THE CALL,
	LOAD T2,MBAR2,(MB)	; WE SAVED THE ARGS
NSICL0:	DMOVE P1,T1		;KEEP NSPpid AND SLBid FOR LATER
	SKIPE T1,MB		;IF WE HAVE A MSG BLK,
	CALL DNFMSG		; FREE IT
	MOVE T1,P1		;RESTORE NSPpid
	CALL FNDPID		;(T1)GET PORT POINTER IN EL, BUG IF FAIL
	  RET			;IGNORE CALL IF BUG GIVEN
	STOR P2,ELSCB,(EL)	;IN CASE WE DON'T HAVE SLBid YET (RESET)
	SETZRO ELSCM,(EL)	;DON'T SEND CONNECT ACK IF WE WERE TEMPTED

	LOAD T1,ELSTA,(EL)	;GET OLD PORT STATE
	IFNSTATE T1,<CI>,NSICL1	;LINK IN CI STATE?
	OPSTR <SKIPE T1,>,ELDIM,(EL) ;YES, THERE A CONNECT INIT MESSAGE THERE?
	  CALL DNFMSG		;YES, FREE IT
	SETZRO ELDIM,(EL)	; AND FORGET IT
	JRST NSICL2		;SKIP RUN-STATE CHECKING

NSICL1:	IFSTATE T1,<CR,CC,RN>	;IF RUNNING,
	  CALL CLSABO		;  SEND AN ABORT IF WE CAN
NSICL2:	NEWSTATE CL		;NOW GO INTO "CL" STATE
	CALLRET SCLOSE		;START THE CLOSE SEQUENCE
	SUBTTL CLSABO - Send a free "Abort by Object"

;CLSABO - Send a free "Abort by Object" if closed from RUN state
;
;Call:
;	CALL CLSABO
;	Normal Return
;Changes T1,T2,T3,T4
;
;If a link is closed (by .NSFRL (release) function or by RESET), we
;send a DI msg with Abort by Object message.  Since the link will be
;closed immediately thereafter, we do not ask Router to return the
;message on output done; it is not error controlled.  If the message
;is lost, the remote node will have to wait for its inactivity timer
;to expire as DNA would have required anyway.  Note that since we tell
;Router this msg is not ACKable, we return the msg blk to the free
;pool quickly as required in comment above.

CLSABO:	SAVEAC ES
	MOVEI T1,0		;NO USER DATA
	CALL DNGMSG		;GET A MESSAGE BLK
	  RET			;CAN'T, WELL DON'T SEND THE ABORT THEN
	MOVE MB,T1		;PICK UP PTR TO NEW MSG BLK
	XMOVEI ES,EL.NSL(EL)	;WE'LL SEND THIS ON THE NSL
	CALL MAKHDR		;INITIALIZE DNPxBY FOR THE NSP HEADER
;MSGFLG
	MOVX T1,MGFDI		;ITS A DISCONNECT INITIATE MESSAGE
	CALL WRTMGF		;WRITE THE MSGFLG FIELD
;DLA
	LOAD T1,ELRLA,(EL)	;GET THE REMOTE LINK ADDRESS
	CALL DNP2BY
;SLA
	LOAD T1,ELLLA,(EL)	;GET THE LOCAL LINK ADDRESS
	CALL DNP2BY

;Here we give the DI message the next message number for the
;"normal" sublink. We do not update ESLMA so that CLSRMT won't
;think we're waiting for this message.

	LOAD T1,ESLMA,(ES)	;GET LAST MESSAGE NUMBER ASSIGNED
	AOS T1
	ANDI T1,MASK.(WID(ESLMA),35) ;RESULT IN T1 MUST BE CYCLED TOO
	STOR T1,NMSGN,(MB)
;REASON
	MOVX T1,RSNABO		;'ABORT BY OBJECT' REASON CODE
	CALL DNP2BY		;2 BYTE FIELD
;USER DATA
	MOVX T1,0		;ZERO BYTE USER DATA
	CALL DNP1BY		;1 BYTE COUNT FIELD

	MOVX T1, ST%NRS ! ST%NAK ! ST%NRQR ! ST%TRL
	CALLRET SNDRTR		;NO RETURN TO SESSION CONTROL
				;NO ACK REQUIRED
				;NO RETURN REQUESTED FROM ROUTER
				;TROLL ALLOWED
	SUBTTL PROCXQ - Process the Transmit Queue

;PROCXQ - Process the Transmit Queue, sending any messages we can
;
;Call:	EL/ The Port Block
;	ES/ The Sublink Block
;	CALL PROCXQ
;	Normal Return
;Changes T1,T2,T3,T4

;Alternate entry point for normal path, so it doesn't have to queue
;message only to dequeue it right away.

PROCXB:	LOAD T1,QHBEG,+ES.XMQ(ES) ;GET 1ST MSG ON TRANSMIT QUEUE
	TMNN ESXOF,(ES)		;TRANSMISSION TURNED OFF?
	JUMPE T1,PROCX2		;NO, DON'T QUEUE NEW MSG IF QUEUE WAS EMPTY

	XMOVEI T1,ES.XMQ(ES)	;GET A POINTER TO THE XMT-Q HEADER
	CALL QSRTMB		;QUEUE MESSAGE IN (MB) TO SEND
	   JRST FREMSG		;  Duplicate message queued for xmit, should
				;  never happen, free up message
	JRST PROCX0		;DON'T NEED THE SAVEAC FROM NSISEG

;Entry point from all routines other than NSISEG

PROCXQ:	SAVEAC <MB,MS>
PROCX0:	TMNE ESXOF,(ES)		;TRANSMISSION TURNED OFF?
	RET			;YES, IMMEDIATE SUCCESS RETURN

PROCX1:

;Instead of dequeueing the first MB (they are sorted in ascending segment
; number), peek and check if we are allowed to send it. If not, we save some
; cpu cycles not having to dequeue and then queue it again.
	LOAD MB,QHBEG,+ES.XMQ(ES) ;Get first MB on queue for this ES
	JUMPE MB,RTN		; -return if there is no MB
	LOAD T1,ESLAR,(ES)	;Get last ACK'ed
	OPSTR <ADD T1,>,ESCWS,(ES) ; and add current window size
	ANDI T1,<MASK.(WID(ESLAR),35)> ; and make highest allowed seg #
	CMODGE T1,NMSGN,(MB)	;Allowed to send it?
	RET			; -no, just return
;Yes, we are allowed to send this, dequeue it and proceed

	DEQUE MB,ES.XMQ(ES),MB.NXT,RTN ;RTN IF QUEUE IS EMPTY

PROCX2:	LOAD T1,ESXFL,(ES)	;CASE OF THE FLOW
	JRST @.+1(T1)		; CONTROL TYPE
	   IFIW <PROCXN&777777>	;NO FLOW CONTROL
	   IFIW <PROCXS&777777>	;SEGMENT FLOW CONTROL
	   IFIW <PROCXM&777777>	;MESSAGE FLOW CONTROL
	   IFIW <PROCXI&777777>	;ILLEGAL FLOW CONTROL

;The No Flow Control case
;
;Allow ACK to be delayed if the message number we are sending is less or
; equal to the number of the last ACK'ed message + half the windowsize.

PROCXN:	LOAD T1,ESCWS,(ES)	;Get current window size
	AOJ T1,			;Up by 1
	ASH T1,-1		;Divide by 2
	OPSTR <ADD T1,>,ESLAR,(ES) ;Add number of last ACK'ed
	ANDI T1,<MASK.(WID(ESLAR),35)> ; and make highest allowed seg #
	MOVX T2,NMDLY		;Get DELAY flag
	CMODL T1,NMSGN,(MB)	;Ok to delay?
	IORM T2,NM.DLY(MB)	; -yes

	CALL SNDATA		;NO FLOW CONTROL, JUST SEND THE DATA
	JRST PROCX1		;TRY TO DEQUEUE ANOTHER MESSAGE

;The Segment Flow Control case

PROCXS:	LOADE T1,ESXRD,(ES)	;GET XMIT-TO-REMOTE DRQS, EXTENDED LIKE HRRE
	SOJL T1,PROCXR		;IF NO DATA REQUESTS, REQUEUE MESSAGE
	STOR T1,ESXRD,(ES)	;DECREMENT XMIT-TO-REMOTE DATA REQUESTS
	JUMPLE T1,PRCXS1	;If this is the last DRQ do not delay ACK
	SETONE NMDLY,(MB)	; - it wasnt, allow ACK to be delayed
PRCXS1:	OPSTRM <SOS>,ESXLD,(ES)	;DECREMENT XMIT-FROM-LOCAL FDATA REQUESTS
	CALL SNDATA		;GO AHEAD AND SEND
	JRST PROCX1		;TRY TO DEQUEUE ANOTHER MESSAGE

;The Message Flow Control case; "other" sublink always comes here

PROCXM:	JN NMCNT,(MB),PRCXM2	;SEND NOW IF NOT FIRST SEND FOR THIS MSG
	TMNN ESOTH,(ES)		;IS THIS THE "OTHER" SUBLINK?
	JRST PRCXM1		;NO
	LOAD T1,ESLAR,(ES)	;YES, ANY MESSAGES TO BE ACKED?
	AOS T1			;WE KNOW THIS INT MSG IS NOT YET ACKED
	ANDX T1,MASK.(WID(ESLMA),35) ;WRAP AROUND
	CMODE T1,ESLMA,(ES)	;LAST MSG # ASSIGNED = LAST ACK REC'D?
	JRST PROCXR		;NO, CAN'T SEND ON "OTHER" SUBLINK.
PRCXM1:	LOAD T1,ESXRD,(ES)	;NO NEG DATA REQUESTS IN MESSAGE MODE
	SOJL T1,PROCXR		;LEAVE IF NO DATA REQUESTS AVAILABLE
	TMNN MBEOM,(MB)		;DO WE HAVE EOM?
	JRST PRCXM2		;NO, DON'T DIDDLE THE DRQS
	STOR T1,ESXRD,(ES)	;YES, DECR XMIT-TO-REMOTE DATA REQUESTS
	OPSTRM <SOS>,ESXLD,(ES)	; AND DECR WHAT SCTL THINKS IS LEFT
PRCXM2:	CALL SNDATA		;SEND A DATA MESSAGE
	JRST PROCX1		;TRY TO DEQUEUE ANOTHER MESSAGE

;The Illegal Flow Control case

PROCXI: BUG.(CHK,LLIIFC,LLINKS,SOFT,<Illegal flow control type>,<<EL,ELPTR>,<ES,ESPTR>,<MB,MBPTR>>,<

Cause:	An illegal flow control type was requested on transmit.  This
	should have been checked by a higher layer.  Inspect the stack
	to find the path that caused the bad value.  Please submit a SPR
	and include a dump and the additional data.

Data:	ELPTR - Pointer to EL block
	ESPTR - Pointer to ES block
	MBPTR - Pointer to message block

>,PROCX1)

;Here when PROCXx couldn't send the message in MB for some reason.  MB
;still points at the message, and we have to put it back on the
;transmit queue that we took it off.

PROCXR:	XMOVEI T1,ES.XMQ(ES)	;GET POINTER TO TRANSMIT QUEUE
	CALL QSRTMB		;PUT MB'S MESSAGE BACK ON QUEUE
	  JRST FREMSG		;  Duplicate message queued for transmit,
				;  should never happen, free message
	RET
;SNDATA - Routine called by PROCXx when it has decided to send a message
;
;Call:	EL/ The Port Block
;	ES/ The Sublink Block
;	MB/ The Message
;	CALL SNDATA
;	Normal Return
;Changes T1,T2,MS

Repeat 0,<
;We need to build the NSP header if this is the first transmission of
; a message, or if this is a retransmission of a message that had the
; DLY bit set in the first transmission.  Retransmitted messages should
; not have the DLY bit set.

SNDATA:	TMNN NMCNT,(MB)		;Is this a retransmission?
	IFSKP.			; -yes,
	  JE NMDLY,(MB),SNDAT2	;  If DLY was clear at first transmit, then
				;  we need not rebuild the NSP header.
	  SETZRO NMDLY,(MB)	;  Clear DLY flag for retransmit and rebuild
	ENDIF.

	CALL MAKHDR		;INITIALIZE THE NSP HEADER MSD AND DNxyBY
;MSGFLG
	MOVX T1,MGFINT		;ASSUME ITS AN INTERRUPT MESSAGE
	JN ESOTH,(ES),SNDAT1	;JUMP IF "OTHER" SUBLINK
	MOVX T1,MGFMSG		;ASSUME ITS "NORMAL", NO BOM OR EOM
	MOVE T2,MB.FLG(MB)	;GET THE MESSAGE BLOCK FLAGS
	TXNE T2,MBBOM		;BEG-OF-MESSAGE SET?
	IORI T1,MGFBOM		;YES
	TXNE T2,MBEOM		;END-OF-MESSAGE SET?
	IORI T1,MGFEOM		;YES
SNDAT1:	CALL WRTMGF		;WRITE THE MSGFLG FIELD

;DLA
	LOAD T1,ELRLA,(EL)	;REMOTE LINK ADDRESS IS DESTINATION
	CALL DNP2BY
;SLA
	LOAD T1,ELLLA,(EL)	;LOCAL LINK ADDRESS IS SOURCE
	CALL DNP2BY
;ACKNUM
	CALL WRTACK		;WRITE THE ACKNUM FIELD(S) & ZERO ESACK
;SEGNUM
	LOAD T1,NMSGN,(MB)	;GET SEGMENT NUMBER ALREADY ASSIGNED
	LOAD T2,ELVER,(EL)	;Get remote NSP version number
	TMNE NMDLY,(MB)		;Should DLY be set?
	CAIGE T2,VER4.0		; and is remote at NSP version 4 or higher?
	TRNA			;  -no
	TXO T1,SGDLY		;   -yes to both, OR in the DLY bit
	CALL DNP2BY

	JN NMCNT,(MB),SNDAT2	;If retransmitted, go to SNDAT2 for other flags

	LOAD T1,ESOTH,(ES)	;GET THE "OTHER" SUBLINK FLAG
	STOR T1,MBOTH,(MB)	;COPY IT TO THE MESSAGE BLOCK

	MOVX T1,ST%RSC ! ST%ACK ! ST%NRQR ! ST%TRL
				;RETURN TO SESSION CONTROL
				;MESSAGE NEEDS AN ACK
				;DON'T RETURN FROM ROUTER ON FAILURE
				;ALLOW THE TROLL
	CALLRET SNDRTR		;SEND TO ROUTER
>

Repeat 1,<
SNDATA:	JN NMCNT,(MB),SNDAT2	;JUMP IF THIS IS NOT FIRST SEND FOR MESSAGE
	CALL MAKHDR		;INITIALIZE THE NSP HEADER MSD AND DNxyBY
;MSGFLG
	MOVX T1,MGFINT		;ASSUME ITS AN INTERRUPT MESSAGE
	JN ESOTH,(ES),SNDAT1	;JUMP IF "OTHER" SUBLINK
	MOVX T1,MGFMSG		;ASSUME ITS "NORMAL", NO BOM OR EOM
	MOVE T2,MB.FLG(MB)	;GET THE MESSAGE BLOCK FLAGS
	TXNE T2,MBBOM		;BEG-OF-MESSAGE SET?
	IORI T1,MGFBOM		;YES
	TXNE T2,MBEOM		;END-OF-MESSAGE SET?
	IORI T1,MGFEOM		;YES
SNDAT1:	CALL WRTMGF		;WRITE THE MSGFLG FIELD

;DLA
	LOAD T1,ELRLA,(EL)	;REMOTE LINK ADDRESS IS DESTINATION
	CALL DNP2BY
;SLA
	LOAD T1,ELLLA,(EL)	;LOCAL LINK ADDRESS IS SOURCE
	CALL DNP2BY
;ACKNUM
	CALL WRTACK		;WRITE THE ACKNUM FIELD(S) & ZERO ESACK
;SEGNUM
	LOAD T1,NMSGN,(MB)	;GET SEGMENT NUMBER ALREADY ASSIGNED
	LOAD T2,ELVER,(EL)	;Get remote NSP version number
	TMNE NMDLY,(MB)		;Should DLY be set?
	CAIGE T2,VER4.0		; and is remote at NSP version 4 or higher?
	TRNA			;  -no
	TXO T1,SGDLY		;   -yes to both, OR in the DLY bit
	CALL DNP2BY

	LOAD T1,ESOTH,(ES)	;GET THE "OTHER" SUBLINK FLAG
	STOR T1,MBOTH,(MB)	;COPY IT TO THE MESSAGE BLOCK

	MOVX T1,ST%RSC ! ST%ACK ! ST%NRQR ! ST%TRL
				;RETURN TO SESSION CONTROL
				;MESSAGE NEEDS AN ACK
				;DON'T RETURN FROM ROUTER ON FAILURE
				;ALLOW THE TROLL
	CALLRET SNDRTR		;SEND TO ROUTER
>

;Here if we are resending the message

SNDAT2:	MOVX T1,ST%NRQR ! ST%TRL ;WE KNOW WE'LL ALLOW THE TROLL
				; AND WE DON'T REQUEST RETURN (ONLY CI)
	MOVE T2,NSPRTH		;Get retransmission threshold
	LSH T2,-1		;Divide by 2
	OPSTR <CAMGE T2,>,NMCNT,(MB) ;Larger than current retry count?
	TXO T1,ST%TRY		; -no, "try hard"
	LOAD T2,NMFLG,(MB)	;GET THE MSG BLK FLAGS
	TXNE T2,NM%ACK		;DID IT NEED AN ACK?
	TXO T1,ST%ACK		;YES, PASS ON THE IDEA
	TXNE T2,NM%RET		;DID IT WANT TO RETURN TO SCTL?
	TXO T1,ST%RSC		;YES, PASS ON THE IDEA
	CALLRET SNDRTR		;SEND TO ROUTER
	SUBTTL SCNIRS - Tell Session Control Port is Not in Run State

;SCNIRS - Send a Not in Run State transaction to Session Control
;
;Call:	MB/ The Message Block to return to Session Control
;	CALL SCNIRS
;	Normal Return
;Changes T1,T2,T3,T4

SCNIRS:	LOAD T1,ELSCB,(EL)	;GET SESSION CONTROL'S PORT ID
				;T2 IS IGNORED
	MOVEI T3,SV.NRN		;NOT-IN-RUN-STATE FUNCTION CODE
	MOVE T4,MB		;MESSAGE BLOCK POINTER IN T4
	OPSTR <CALLRET @>,ELSCV,(EL) ;CALL SESSION CONTROL
	SUBTTL SNDRTR - Send a Message to Router

;SNDRTR - Last chance to fondle messages bound for Router.
;
;Call:	T1/ Flags, see below
;	MB/ Message Block
;	EL/ Port Block
;	CALL SNDRTR
;	Normal Return
;Changes T1,T2,T3,T4

;This routine is called with a message to send and some flags that
;govern the sending.  Each flag has a zero counterpart so that all
;callers can specify all the flags.  That way its harder to miss
;what's going on.

	ST%RSC== 1B35		;RETURN THIS MESSAGE TO SC
	ST%NRS== 0B35		;DON'T

	ST%RQR== 1B34		;RETURN REQUESTED FROM ROUTER ON FAILURE
	ST%NRQ== 0B34		;DON'T

	ST%ACK== 1B33		;THIS MESSAGE NEEDS AN ACK
	ST%NAK== 0B33		;DOESN'T

	ST%TRL== 1B32		;ALLOW THE TROLL TO STEAL THIS MESSAGE
	ST%NTR== 0B32		;DON'T

	ST%TRY== 1B31		;Ask ROUTER to "try hard"
	ST%NTY== 0B31		;Don't

SNDRTR:	SAVEAC P1
	MOVE P1,T1		;SAVE FLAGS PASSED TO THIS PROCEDURE
	MOVX T1,NMRET		;GET THE RETURN TO SCTL FLAG
	TXNE P1,ST%RSC		;RETURN THIS MESSAGE TO SESSION CONTROL?
	IORM T1,NM.RET(MB)	;YES, TELL THE MESSAGE BLOCK

	LOAD T1,IBADR,+IBBLK	;Get source address
	STOR T1,MBSRC,(MB)	;STORE AS SOURCE NODE
	LOAD T1,ELNNM,(EL)	;GET REMOTE'S NODE NUMBER
	STOR T1,MBDST,(MB)	;STORE AS DESTINATION NODE

	STOR EL,NMPRT,(MB)	;ACKables and CIs need EL ptr stored
	LOAD T1,ELLLA,(EL)      ; and  Local Link address
	STOR T1,NMLLA,(MB)      ;  for NSIODN's consistency check

	;Session Control has already stored MBBOM and MBEOM
	; if this is a user's message segment

	XMOVEI T1,NM.MSD(MB)	;Get # of bytes in
	CALL DNSLNG		; NSP message
	MOVE T4,T1		;"Save" T1 in T4. *Note* We 'know' that T4
				; is not used by DNSLNG. This is to have as
				; few instructions as possible in this common
				; path.
	XMOVEI T1,UD.MSD(MB)	;Get # of bytes in
	CALL DNSLNG		; user message
	ADD T4,T1		;Add them together gives total # of bytes

	LOAD T1,ELNDB,(EL)	;GET PTR TO NODE BLOCK FOR THIS LINK
	INCR NNTMX,(T1)		;UPDATE COUNT OF MESSAGES SENT TO NODE
	OPSTRM <ADDM T4,>,NNTBX,(T1) ; and update count of bytes

	TXNN P1,ST%ACK		;WILL WE GET THIS MESSAGE BACK?
	JRST SNDRT1		;NO, DON'T COUNT OR TIME THIS MSG
	SETONE NMACK,(MB)	;TELL NSPODN WE NEED AN ACK
	INCR ELORC,(EL)		;INCR THE OUT-IN-ROUTER COUNT

   IFN FTPARANOID <		;Catch "ORQ" problem
	ENDQUE MB,EL.ORQ(EL),NM.ORQ,T1 ;Queue MB on "ORQ" queue
	MOVE T1,[ORCQUO: EXP ^D60] ;Give it 60 seconds to time out
	STOR T1,ELCLC,(EL)	;Store it in EL
	XMOVEI T1,SNDRTR	;Get initial value of trace word
	STOR T1,NMMAG,(MB)	;Store it in MB
   >

	INCR NMCNT,(MB)		;INCR # OF TIMES WE'VE SENT THIS MSG

	;Note that we timestamp all ST%ACK messages, whether first send
	;or not.  This is the resend timer, round-trip delay timer is
	;separate, see end of this routine.
	;
	;A CI message is timed with ELDTM, but is not acked, so NSIACT
	;has to fill in ELDTM for CI messages itself.

	CALL DNGTIM		;GET A TIMESTAMP IN T1
	TMNE NMDLY,(MB)		;DELAY?
	  ADD T1,NSPADL		;  Add ACK DELAY so we dont timeout too early
	STOR T1,NMTIM,(MB)	;STAMP IT NOW, SAVE T1 FOR STOR BELOW
	TMNE ELDTM,(EL)		;A DELAY-CALC TIMER GOING NOW?
	JRST SNDRT1		;YES, DON'T RESTART IT AGAIN
	TMNE NMDLY,(MB)		;Allowing delayed ACK's for this message?
	JRST SNDRT1		; -yes, dont time it
	STOR T1,ELDTM,(EL)	;NO, START ONE NOW
	LOAD T1,NMSGN,(MB)	;GET THIS MSG'S SEGMENT NUMBER
	STOR T1,ELDSG,(EL)	;THIS IS THE SEGMENT BEING TIMED
	LOAD T1,MBOTH,(MB)	;GET "OTHER" SUBLINK FLAG
	STOR T1,ELDTO,(EL)	;REMEMBER WHICH SUBLINK WE'RE TIMING
SNDRT1:
IFN FTTROLL,<
	CALL SNXTRL		;CHECK THE TROLL
	  RET			;TROLL ATE THE MESSAGE
>;END OF IFN FTTROLL
IFN FTTRACE,<
	LOAD T1,NMMGF,(MB)	;GET THE MESSAGE TYPE (MSGFLG FIELD)
	XMOVEI T2,[ASCIZ \Sent\]
	CALL TRCMSG
>;END OF IFN FTTRACE
	LOAD T1,ELCIR,(EL)	;GET LOOPBACK CIRCUIT IF ANY
	STOR T1,MBCHN,(MB)	;AND STORE FOR ROUTER
	MOVEI T1,0		;PREPARE THE FLAGS WORD FOR ROUTER CALL
	TXNE P1,ST%RQR		;TELL ROUTER TO RETURN IF SEND FAILS?
	TXO T1,RT%RQR		;YES, SET "RETURN REQUESTED"
	TXNE P1,ST%ACK		;DOES THIS MESSAGE NEED AN ACK?
	TXO T1,RT%ODN		;YES, TELL ROUTER TO GIVE US ODN CALL
	TXNE P1,ST%TRY		;Try hard?
	TXO T1,RT%TRY		;Yes, ask ROUTER to do so
	CALLRET RTRXMT		;SEND THE MESSAGE AND RETURN
;SNXTRL - See if the troll wants to eat this message
;
;Call:	P1/ Flags passed to SNDRTR
;	CALL SNXTRL
;	  Error Return if the troll ate the message
;	Normal Return
;
;Changes T1

IFN FTTROLL,<

SNXTRL:	TXNE P1,ST%TRL		;ALLOWED TO USE THE TROLL ON THIS CALL?
	SKIPN NSPTRI		;YES, TROLL INITIALIZED?
	RETSKP			;NO, GO SEND THE MESSAGE NOW
	SOSLE NSPTRL		;YES, DECREMENT THE TROLL, AT ZERO YET?
	RETSKP			;NO, GO SEND THE MESSAGE
	MOVE T1,NSPTRI		;YES, RE-INITIALIZE THE TROLL
	MOVEM T1,NSPTRL		;  FOR NEXT TIME
IFN FTTRACE,<
	LOAD T1,NMMGF,(MB)	;GET THE MESSAGE TYPE (MSGFLG FIELD)
	XMOVEI T2,[ASCIZ \Troll ate\]
	CALL TRCMSG
>;END OF IFN FTTRACE
	TXNN P1,ST%ACK ! ST%RQR	;IS ODN INTERESTED IN MESSAGE?
	JRST FREMSG		;NO, JUST DEALLOCATE AND RETURN
	TXNE P1,ST%RQR		;YES, ROUTER RETURN REQUESTED?
	JRST NSPRTS		;YES, PRETEND RETURN TO SENDER
	JRST NSPODN		;NO, MUST BE WAITING FOR ACK

>;END OF IFN FTTROLL
	SUBTTL ROUTER Calls -- NSPRCV - Receive a Message from Router

;NSPRCV - Receive a Message from Router
;
;Call:	The arguments are in the public area of the message block
;	MB/ The Message Block
;	CALL NSPRCV
;	Normal Return
;Changes T1,T2,T3,T4

;                           C A U T I O N
;This page has some careful coding for non-zero sections.  See comment
;above the BEGSTR RT definitions.
;
;In this routine, MS is for DNGxBY calls
;		  P1 holds the UPTO count, full-word count
;		  P2 holds the MSGTBL flags, right justified

NSPRCV:	JSP T6,NSPLCQ		;QUEUE UP THE REQUEST

NSIRCV:	CALL INIHDR		;SET UP MS (ALREADY SAVED) FOR DNGxBY
	CALL DNRPOS		;READ CURRENT POSITION INTO T1
	STOR T1,NMMK1,(MB)	;STORE AS NSP'S MARK 1
;MSGFLG
	CALL DNGSBY		;SKIP IF NOT EXTENSIBLE, BYTE IN T1
	  EVENT(NSP,MSG,Extensible MSGFLG field,FREMSG)
	STOR T1,NMMGF,(MB)	;TELL MESSAGE BLOCK THE MESSAGE TYPE
	LSH T1,-2		;THE LOW-ORDER 2 BITS ARE ALWAYS ZERO
	CAILE T1,RCVMAX		;IS IT A LEGAL MESSAGE TYPE?
	JRST RCVILM		;NO, EVENT AND TOSS IT
	MOVE P1,T1		;SAVE FOR CALLRET BELOW
	CALL RCVPRC		;DO PRELIMINARY RECEIVE PROCESSING
	  RET			;ERROR ALREADY GIVEN, AND MSG BLK FREED
				;SUCCESSFUL PRELIMINARY PROCESSING
	HRRZ T1,MSGTBL(P1)	;ADDRESS MUST BE IN THIS LOCAL SECTION!
	CALLRET (T1)		;GO DO THE MAIN RECEIVE ROUTINE

;Here if we get an illegal message type

RCVILM:	EVENT(NSP,MSG,Illegal message type recvd,FREMSG)
	SUBTTL ROUTER Calls -- NSPODN/NSPOND - Output Done/Not Done

;NSPODN - Get a message back from Router after message has been sent
;
;Call:	CALL NSPODN
;	Normal Return
;Changes T1,T2,T3,T4

NSPODN:	JSP T6,NSPLCQ		;QUEUE UP THE REQUEST

NSIODN:	LOAD EL,NMPRT,(MB)	;Pointer to ELB in use when MB was sent
	LOAD T1,NMLLA,(MB)      ;Get local link address from returning MB
	LOAD T2,ELLLA,(EL)      ;Get LLA from ELB it used to be attached to
	CAME T1,T2              ;That ELB still in use by same logical link?
	JRST FREMSG             ;No, toss the message
	LOAD T1,ELSTA,(EL)      ;Yes, get its state
	IFSTATE T1,<DP>,FREMSG  ;Ignore if in Destroy Port state

	CALL DCRORC		;Decrement 'out-in-router' count

;Check if a CI or RCI message came back.
;
; If the link state is not CI, then release the message. It is unlikely
; that we will have recived a CA before ROUTER returns the CI message, but
; it is possible that a CA/CC/DI/DC came in while the RCI message was out-
; standing.
;
; Finally save a pointer to the message so it can be retransmitted.

	LOAD T1,NMMGF,(MB)	;Get message type from message block
	CAIE T1,MGFCI		;Is it a CI
	CAIN T1,MGFRCI		; or a RCI message?
	IFNSK.			; -yes,
	  LOAD T2,ELSTA,(EL)	;  Get link state
	  IFNSTATE T2,<CI>,FREMSG ;  return message if not in CI state
	  SETZRO NMACK,(MB)	;  Clear 'needs ACK' just in case...
	  STOR MB,ELCIM,(EL)	;  and allow retransmission again
	  RET
	ENDIF.

	XMOVEI ES,EL.OSL(EL)	;ASSUME "OTHER" SUBLINK
	TMNN MBOTH,(MB)		;WAS IT THE "OTHER" SUBLINK?
	XMOVEI ES,EL.NSL(EL)	;NO, IT WAS "NORMAL"

	TMNN NMACK,(MB)		;DOES THE MESSAGE NEED AN ACK?
	JRST FREMSG		; -no, we should never get on back with
				;  NMACK=0, but if, just release it
	LOAD T1,NMSGN,(MB)	;GET THE MESSAGE NUMBER
	CMODLE T1,ESLAR,(ES)	;HAVE WE ALREADY RECEIVED ACK FOR IT?
	JRST NSIOD3		;NO, GO QUEUE IT FOR ACK
				;YES, A SPEEDY ACK, PERHAPS LOCAL TSK
	TRACE NSP,<Msg already ACKed at NSIDON>
	MOVX T1,MA%DONE		;SAY THE SEND WAS DONE
	CALLRET MGACKD		; AND TELL SESSION CONTROL THE MESSAGE
				; HAS BEEN ACKED
NSIOD3:	XMOVEI T1,ES.AKQ(ES)	;MESSAGE NOT ACKED, LOAD UP QUEUE HEADER
	CALL QSRTMB		;GO QUEUE IT UP IN SORTED ORDER
	  JRST FREMSG		;  -duplicate msg, just release it
	RET
	SUBTTL ROUTER Calls -- NSPRTS - Return a Message to Sender
	SUBTTL ROUTER Calls -- NSPRFR - Return a Message to Sender from Remote

;NSPRTS - Called by Router to return a message to sender
;NSPRFR - Called by Router to return a message to sender from remote Router
;
;Call:	The arguments are in the public area of the message block
;	MB/ The Message Block
;	CALL NSPRTS
;	Normal Return
;Changes T1,T2,T3,T4

;NSPRTS and NSPRFR is only called when a message sent with the RT%RQR
;flag set is not sent.  This can be returned either by the local Router
;(entry NSPRTS) or by a route-through remote Router (entry NSPRFR) when the
;target node is not available.  NSP only lights the RT%RQR flag for CI
;messages.

;We can't use anything stored in the message block, because this may
;not be the same message block that we sent out.  The message may have
;been sent out on the network and then returned by a route-through node
;which could not forward the message due to a change in the topology.
;Router guarantees that however we got this message, IN.MSD will be
;pointing at the beginning of the NSP header when we get it.

;If NSPRTS is called, then the message came back from local ROUTER
; and NSPODN has not been called, i.e. decrement ORC count
;
;If NSPRFR is called, then the message came back from a remote ROUTER
; and NSPODN was called before, i.e. it has already been decremented.
;
;P2 is used as a flag to distinguish between the two cases, since the
; code is common.

NSPRTS:	JSP T6,NSPLCQ		;QUEUE UP THE REQUEST

NSIRTS:	SETO P2,		;Decrement ORC count
	JRST NSIRTR		;Go join common code

NSPRFR:	JSP T6,NSPLCQ		;Queue up the request
NSIRFR:	SETZ P2,		;Do not decrement ORC count
;	JRST NSIRTR		;Go join common code

NSIRTR:
	CALL INIHDR		;WE STILL HAVE THE PORT, SET UP TO READ MSG
;MSGFLG
	CALL DNGEBY		;GET AN EXTENSIBLE BYTE
	  EVENT(NSP,MSG,No MSGFLG in RTS,FREMSG)
	STOR T1,NMMGF,(MB)	;SAVE MSGFLG TYPE IN THE MESSAGE BLOCK
	CAXE T1,MGFCI		;IS IT A CI MESSAGE?
	CAXN T1,MGFRCI		; OR A RETRANSMITTED CI MESSAGE?
	JRST NSIRT1		;YES, NO PROBLEM
	ETRACE NSP,<Non-CI message returned>
	CALLRET FREMSG		;NO, IGNORE IT (SHOULD NOT HAPPEN)
NSIRT1:
;DLA
	CALL DNG2BY		;GET THE LOCAL LINK ADDRESS
	  EVENT(NSP,MSG,No DLA in RTS,FREMSG)
	MOVE P1,T1
;SLA
	CALL DNG2BY		;GET REMOTE LINK ADDRESS IN T1
	  EVENT(NSP,MSG,No SLA in RTS,FREMSG)
	MOVE T2,P1		;GET BACK LLA FOR FNDPRT
	CALL FNDPRT		;SEE IF THE PORT STILL EXISTS
	  TRNA			;NO
	JRST NSIRT2		;YES
	ETRACE NSP,<Can't find port for returned msg>
	CALLRET FREMSG

NSIRT2:	SKIPE P2		;Should we decrement ORC count?
	CALL DCRORC		; -yes, do it

	LOAD T1,ELSCB,(EL)	;GET SESSION CONTROL'S PORT NAME
	MOVX T3,SV.NCM		;NO COMMUNICATION FUNCTION CODE
	MOVE T4,MB		;SESSION CONTROL WANTS IT THAT WAY
	OPSTR <CALL @>,ELSCV,(EL) ;CALL SESSION CONTROL

	NEWSTATE NC		;GO INTO NO COMMUNICATION STATE
	CALL INSBRK		;Insert into 'link broken' table
	CALLRET SCLOSE		;START THE CLOSE PROCESS
	SUBTTL Message Receivers -- RCVPRC - Preliminary Processing

;RCVPRC - Preliminary Processing
;
;Call:	T1/ Full-word pointer to the MSGTBL entry
;	MB/ The Message Block
;	EL/ The Port Block
;	CALL RCVPRC
;	  Error Return
;	Normal Return, if UPTO .GE. the SLA, sets up registers EL & ES
;Changes T1,T2,T3,T4

;MSGTBL defines an UPTO field for each message type. RCVPRC will
;process fields in each incoming message up to the field specified in
;MSGTBL.  NSP headers for messages with segment numbers (data, interrupt
;& link service) are fully processed here.  Headers for other messages
;are processed here to the extent that they correspond to numbered
;messages.

RCVPRC:	SAVEAC <P1,P2>
	LOAD P1,RTUPT,+MSGTBL(T1) ;GET "UPTO" COUNT FOR THIS MSG TYPE
	LOAD P2,RTFLG,+MSGTBL(T1) ;GET RECEIVE TABLE FLAGS FOR MSG TYPE

	MOVX T1,MBOTH		;GET MSG BLK'S "OTHER" SUBLINK FLAG
	ANDCAM T1,MB.OTH(MB)	;ASSUME WE'RE ON THE "NORMAL" SUBLINK
	TXNE P2,RT%OTH		;IS MESSAGE FOR THE "OTHER" SUBLINK?
	IORM T1,MB.OTH(MB)	;YES, SET FLAG IN THE MESSAGE BLOCK
IFN FTTRACE,<
	LOAD T1,NMMGF,(MB)	;GET MESSAGE TYPE
	CALL TRCRCV		;TRACE RECEIPT OF MESSAGE
>
	SOJL P1,RSKP		;UPTO INCLUDE THE DLA?

;DLA
	CALL DNG2BY
	  EVENT(NSP,MSG,No DLA,FREMSG) ;BAD MESSAGE EVENT TYPE
	STOR T1,NMLLA,(MB)	;STORE THE DLA AS THE LOCAL LINK ADDRESS
	SOJL P1,RSKP		;UPTO INCLUDE THE SLA?
;SLA
	CALL DNG2BY		;GET THE SLA
	  EVENT(NSP,MSG,No SLA,FREMSG) ;BAD MESSAGE EVENT TYPE
	STOR T1,NMRLA,(MB)	;STORE THE SLA AS THE REMOTE LINK ADDRESS
	MOVE T2,T1		;REMOTE LINK ADDRESS INTO T2 FOR FNDPRT
	LOAD T1,NMLLA,(MB)	;LOCAL LINK ADDRESS INTO T1  FOR FNDPRT
	CALL FNDPRT		;FIND THE PORT BLOCK, POINTER TO EL
	  JRST [TXNE P2,RT%RSP	;NO SUCH LINK, SUPPOSED TO RESPOND?
		CALLRET SENDNL	;YES, SEND A NO-LINK MESSAGE
		CALLRET FREMSG]	;NO, JUST IGNORE THE MESSAGE
				; AND THEN GIVE A NON-SKIP ERROR RETURN

	XMOVEI T1,IN.MSD(MB)	;Get length
	CALL DNSLNG		; of message

	LOAD T3,ELNDB,(EL)	;GET PTR TO NODE BLOCK FOR THIS LINK
	INCR NNTMR,(T3)		;UPDATE COUNT OF MESSAGES FROM NODE
	OPSTRM <ADDM T1,>,NNTBR,(T3) ;Update count of bytes received

	LOAD T2,MBVST,(MB)	;Get visit count
	IMULI T2,3		; times 3
	STOR T2,NNPSZ,(T3)	;  gives pipe size

	XMOVEI ES,EL.NSL(EL)	;ASSUME "NORMAL" SUBLINK
	TXNE P2,RT%OTH		;"OTHER" SUBLINK?
	XMOVEI ES,EL.OSL(EL)	;YES.

	SOJL P1,RSKP		;UPTO INCLUDE THE DLA?

;Continued on Next Page with ACKNUM field
;Continued from Previous Page, ready for ACKNUM field

;Note that there is a peculiarity in the ACKNUM field.  It is not
;there if the two bytes bytes in this position in the message do not
;have their high-order bit turned on.  If that bit is off, then the
;two byte field is the SEGNUM field.

;ACKNUM
	CALL DNG2BY		;GET 2 BYTES WHICH MIGHT BE THE ACKNUM
	  EVENT(NSP,MSG,No predicted ACKNUM,FREMSG) ;BAD MESSAGE EVENT TYPE
	TXNN T1,AKPNT		;IS ACK FIELD "PRESENT"?
	JRST RCVPR0		;NO, GO SEE WHO'S FIELD IT IS
	CALL PRCACK		;YES, PROCESS THE ACKNUM FIELD

;ACKOTH
	LOAD T1,ELVER,(EL)	;GET REMOTE NSP VERSION
	CAIGE T1,VER4.0		;IS IT VERSION 4.0?
	JRST RCVPR2		;NO, NO CROSS-SUBCHANNEL ACKING
	CALL DNG2BY		;YES, GET NEXT 2-BYTE FIELD
	  JRST RCVPR2		;NONE, SEE IF WE WANTED ONE
	TXNN T1,AKPNT		;IS CROSS ACK FIELD "PRESENT"?
	JRST RCVPR1		;NO, GO SEE WHO'S FIELD IT IS
	CALL PRCACK		;YES, PROCESS CROSS-SUBCHN ACKNUM FIELD
	JRST RCVPR2		;THEN LOOK FOR THE SEGNUM FIELD

RCVPR0:	TXNE P2,RT%ACK		;OK TO SKIP THE ACKNUM FIELD?
	EVENT(NSP,MSG,Missing required ACKNUM,FREMSG) ;NO, TELL NTMAN & TOSS
RCVPR1:	SOJGE P1,RCVPR3		;IF WE WANTED SEGNUM, STORE IT
	MOVEI T1,2		;NO SEGNUM?  BACK UP 2 BYTES
	CALL DNBKBY		; TO RE-INTERPRET FIELD WHICH IS NOT ACKNUM
RCVPR2:	SOJL P1,RSKP		;UPTO INCLUDE THE SEGNUM FIELD?
				;IF THIS SOJL JUMPS, ITS AN ACK MESSAGE
;SEGNUM
	CALL DNG2BY		;GET SEGNUM IF LAST ONE WAS INDEED ACKNUM
	  EVENT(NSP,MSG,No predicted SEGNUM,FREMSG) ;BAD MESSAGE EVENT TYPE
RCVPR3:	TXZN T1,SGDLY		;DLY bit set?
	IFSKP. <SETONE NMDLY,(MB)> ; -yes, set DLY bit in message block
	STOR T1,NMSGN,(MB)	;STORE SEGMENT NUMBER IN MESSAGE BLOCK
	RETSKP			;SUCCESS RETURN
	SUBTTL Message Receivers -- PRCACK - Process ACKNUM/ACKOTH Field

;PRCACK - Process the ACKNUM field of an incoming message
;
;Call:	T1/ The ACKNUM/ACKOTH field
;	EL/ The Port Block
;	ES/ The Sublink Block
;	CALL PRCACK, only if ACKNUM/ACKOTH field is "present"
;	Normal Return
;Changes T1,T2,T3,T4

PRCACK:	SAVEAC <MB,P1,P2>

	LOAD P2,AKQAL,+T1	;GET THE QUALIFIER FIELD
	CAIG P2,AK$QNK		;IS IT ACK OR NAK?
	JRST PRCAK1		;YES, OK
                                ;Cross subchannel ACK in phase IV?
	LOAD T4,ELVER,(EL)	;GET VERSION # OF REMOTE NSP
	CAIL T4,VER4.0		;IS IT VERSION 4.0?
	CAILE P2,AK$CNK		;YES, IS IT A CROSS SUB-CHN ACK OR NAK?
	EVENT(NSP,MSG,Bad ACKNUM byte,RTN) ;NO, BAD ACKNUM FIELD
				;YES, POINT ES ACROSS SUB-CHANNEL
	SAVEAC ES		;SAVE CALLER'S SUBLINK POINTER
	XMOVEI T4,EL.OSL(EL)	;ASSUME WE'RE MOVING FROM 'NORMAL' TO 'OTHER'
	TMNE ESOTH,(ES)		;CURRENTLY IN 'NORMAL' SUBLINK?
	XMOVEI T4,EL.NSL(EL)	;NO, MOVING FROM 'OTHER' TO 'NORMAL'
	MOVE ES,T4		;SET UP ES TO POINT TO 'CROSS' SUBCHANNEL

;T1 must be preserved from PRCACK to PRCAK1

PRCAK1:	LOAD P1,AKNUM,+T1	;PRESERVE THE ACK NUMBER
	CMODG P1,ESLAR,(ES)	;IF ACKNUM IS .LE. LAST ACK RECEIVED
	JRST PRCNAK             ; Ignore stale ACK, see if its a NAK
	CMODLE P1,ESLMA,(ES)	;IF .GT. LAST MSG ASSIGNED (SENT)
	RET                     ; Ignore early ACK, even if its a NAK

	MOVE T1,P1		;Get last ACK received
	OPSTR <CAMGE T1,>,ESLAR,(ES) ;Will we wrap around?
	  MOVEI T1,<1B<35-WID(ESLAR)>>(P1) ; -yes, T1 := P1 + MAX(ESLAR)+1
	OPSTR <SUB T1,>,ESLAR,(ES) ;# of messages ACK'ed by the ACK
	OPSTR <ADD T1,>,ESCDA,(ES) ;Add to # of ACK's since last window change
	ANDI T1,<MASK.(WID(ESCDA),35)> ;Protect againt overflow
	STOR T1,ESCDA,(ES)	; and save new value
	LOAD T1,ESCWS,(ES)	;Get current window size
	OPSTR <CAMLE T1,>,ESCDA,(ES) ;Compare with # of ACK's since last change
	IFSKP.			; Window size smaller
	  LOAD T2,ELNDB,(EL)	;  Get node block pointer
	  LOAD T3,NNPSZ,(T2)	;  Get pipe size
	  CAML T1,T3		;  Is window size LT than current pipe size
	  CAMGE T1,NSPPSZ	;   or LT than 'maximum pipe size' ?
	  ANNSK.		;  Its OK
	    INCR ESCWS,(ES)	;   -so increment window size
	    SETZRO ESCDA,(ES)	;    and clear # of ACK's since last window chg
	ENDIF.
	STOR P1,ESLAR,(ES)	;A NEW ACK, ITS NOW OUR LAST RECEIVED
	MOVE T1,P1		;PRESENT ACK NUMBER TO UPDELAY
	CALL UPDELAY		;SEE IF IT WAS BEING TIMED

	CALL DNGTIM		;GET A TIMESTAMP TO RESET ITS
	STOR T1,ELTMA,(EL)	; INACTIVITY TIMER

;Continued on Next Page
;Continued from Previous Page

;Here we free up any messages ACKed by this ACKNUM field

PRCAK2:	LOAD MB,QHBEG,+ES.AKQ(ES) ;GET PTR TO FIRST MESSAGE W/O DEQUING
	JUMPE MB,PRCAK3
	CMODGE P1,NMSGN,(MB)	;IF NEW ACK IS .LT. THIS MESSAGE NUMBER
	JRST PRCAK3		; NO MORE TO DO, SINCE QUEUE IS SORTED
	DEQUE T2,ES.AKQ(ES),MB.NXT,PRCAK3 ;GO TO PRCAK3 IF Q EMPTY (?)
	MOVX T1,MA%DONE		;SEND WAS DONE
	CALL MGACKD		;RETURN OR DESTROY MESSAGE
	JRST PRCAK2
PRCAK3:

;When we receive a NAK we zero the NMTIM field in all the remaining unACKed
;msgs for this sublink and then zero the remaining time in the long interval
;so CHKRSN will be called soon to resend the NAKed messages.  Note that CHKRSN
;will not retransmit a message to a phase II node unless its NMTIM field has
;been so zeroed.

PRCNAK:	CAIE P2,AK$QNK		;IS THIS A NAK
	CAIN P2,AK$CNK		; OR A CROSS-SUBCHANNEL NAK?
	CAIA			;YES
	JRST PRCAK5		;NO
	LOAD MB,QHBEG,+ES.AKQ(ES) ;YES, GET FIRST MSG ON ACK Q
	JUMPE MB,PRCAK5		;JUMP WHEN WE'VE FINISHED LIST
PRCAK4:	SETZRO NMTIM,(MB)	;ZERO TIMER SO MSG WILL BE RESENT SOON
	LOAD MB,MBNXT,(MB)	;GET PTR TO NEXT MSG BLK ON ACK Q
	JUMPN MB,PRCAK4		;RESEND ALL MSGS IN ACK QUEUE
	SETZM NSPJLC            ;MAKE LONG-INTERVAL PROCESSING HAPPEN NEXT TICK
PRCAK5:

;Only one message is allowed out on the "other" sublink at any one
;time. So if this is an "other" ACK, we request jiffy service to send
;any "other" sublink messages which may have been blocked waiting for
;this ACK.  We don't send the waiting message now, because if this ACK
;we are processing now is piggybacked on a link service message, we
;will be able to piggyback its ACK on the outgoing message when LKSACK
;gets a chance to request an ACK.

	TMNN ESOTH,(ES)		;IS THIS THE "OTHER" SUBLINK?
	RET			;NO, ONLY RETURN FROM PRCACK
	CALLRET NSPRJF		;YES, SEND INTERRUPT AND/OR DRQs SOON
	SUBTTL Message Receivers -- Receive a Data Segment

;RCVxxS - Envelope routines to receive a data segment
;	  The xx in the names varies with the BOM and EOM flags
;
;Call:	EL/ The Port Block
;	ES/ The Sublink Block
;	MB/ The Message Block
;	CALL RCVxxS
;	Normal Return
;Changes T1,T2,T3,T4

;When we get here, RCVPRC has read the NSP header, IN.MSD is now:
;	MSGFLG  DLA  SLA  [ACKNUM]  SEGNUM  <->  DATA


RCVONS:	SETONE <MBBOM,MBEOM>,(MB) ;ONLY SEGMENT, BOTH BOM AND EOM SET
	CALLRET RCVSEG

RCVBGS:	SETONE MBBOM,(MB)	;BEGIN SEGMENT, BOM SET, EOM CLEAR
	SETZRO MBEOM,(MB)
	CALLRET RCVSEG

RCVMDS:	SETZRO <MBBOM,MBEOM>,(MB) ;MIDDLE SEGMENT, BOTH BOM AND EOM CLEAR
	CALLRET RCVSEG

RCVENS:	SETONE MBEOM,(MB)	;END SEGMENT, BOM CLEAR, EOM SET
	SETZRO MBBOM,(MB)
	CALLRET RCVSEG
;RCVSEG - Process in incoming data segment, for either sublink
;
;Call:	EL/ The Port Block
;	ES/ The Sublink Block
;	MB/ The Message Block, with MBBOM and MBEOM filled in
;	CALL RCVSEG
;	Normal Return
;Changes T1,T2,T3,T4

RCVSEG:	LOAD T1,ELSTA,(EL)	;CHECK THAT WE'RE IN THE RIGHT STATE
	IFNSTATE T1,<CC,RN,DI>,FREMSG ;FREE THE MESSAGE BLOCK
	IFSTATE T1,CC		;IF IN CC STATE, WAITING FOR FIRST MESSAGE,
	  CALL PTIRUN		; PUT IN RUN STATE
	XMOVEI T1,ES.RCQ(ES)	;GET PTR TO APPROPRIATE RECEIVE QUEUE
	CALL QSRTMB		;ADD MESSAGE SORTED BY SEGNUM
	  JRST	RCVSG1		;DUPLICATE UNACKED MESSAGE RECEIVED
	CALL PROCRQ		;PROCESS THE RECEIVE QUEUE, SEND UNUSED DRQS
	TMNE QHBEG,+ES.RCQ(ES)	;IF ANYTHING ON THE QUEUE,
	CALLRET CHKRCQ		; TRIM IT IF STILL TOO LONG AFTER PROCRQ
	RET

;We do not decrement ESRRD,(ES) until we take the message off the
;receive queue for three reasons:  (1) the receive queue may be
;stripped if the memory manager needs free message blocks, (2) some
;received messages may be stripped by CHKRCQ (q.v.), and (3) some of
;the messages on the receive queue may be duplicates.  We do not find
;out that they are duplicates until we remove them from the receive
;queue.
;Here when we receive a message with the same number as a message we
;already have queued for Session Control.  If we receive such a
;message, it means that the remote NSP is getting tired of waiting for
;an ACK which we have not yet sent because the user program is slow.
;If we receive a duplicate msg any yet we still have data requests
;outstanding from SCTL, we are blocked because of lost messages, not
;our slowness, so clear the queue (to free up memory for cleaner links)
;but don't pessimize the link (its already got too much traffic on it
;another link service message will just add to the rush.  In this case
;the transmitter might consider holding back voluntarily.
;
;In this case, we discard all queued (unACKed) messages and send out
;some negative data requests to make the remote stop resending.  We
;don't want the remote to time out the line just because the
;application is slow (or swapped out) our system is still here and
;listening!

RCVSG1:	TMNE ESOTH,(ES)		;ASSURE WE'RE ON THE "NORMAL" SUBLINK
	BUG.(CHK,LLIDIR,LLINKS,SOFT,<Duplicate Interrupt Message Received>,<<EL,ELPTR>,<ES,ESPTR>,<MB,MBPTR>>,<

Cause:	There is a duplicate interrupt message on the unacked interrupt 
	receive queue.  This should not happen because the NSP interlock 
	should not release with anything on the receive queue.
  
Action:	Either the interrupt flow control is wrong and more than one data 
	request was sent or the remote node sent an interrupt message without  
	a data request.

Data:	ELPTR - Pointer to EL block
	ESPTR - Pointer to ES block
	MBPTR - Pointer to message block

>,CLRSRQ)
	LOAD T1,NMSGN,(MB)	;GET THIS MSG'S SEGMENT NUMBER
	SOSGE T1		;CALC PREVIOUS NUMBER
	ANDI T1,MASK.(WID(ESLMR),35) ;MOD MASK-SIZE
	CMODE T1,ESLMR,(ES)	;IS THIS EXPECTED NEXT MESSAGE?
	CALLRET FREMSG		;NO, Q ONLY OUT-OF-ORDER, DON'T PESSIMIZE
	CALL FREMSG		;YES, FREE THE DUPLICATE MESSAGE
	ETRACE NSP,<Duplicate unACKed msg rcvd, link 'pessimized'>
	CALLRET PESFLO		;PUT LINK IN PESSIMISTIC FLOW CONTROL
	SUBTTL Message Receivers -- Receive a Data Segment -- Check Length of Q

;CHKRCQ - Check the length of the receive queue.
;
;Call:	EL/ The Port Block
;	ES/ The Sublink Block
;	MB/ The Message Block
;	CALL CHKRCQ
;	Normal Return
;Changes T1,T2,T3,T4

;This routine is only called when this sublink's receive queue is not
;empty after calling PROCRQ.

;If the congestion flag is non-zero, we must not cache any incoming
;messages on the "normal" sublink.  The "other" sublink will never
;cache anyway.

;The receive queue will exceed the goal only with message or no flow
;control modes (barring bugs).  In these modes, the link will be
;turned on again when Session Control again sends a data request to NSP.

;We cannot queue out-of-order interrupt messages here for fear that
;they will interfere with link service messages.

CHKRCQ:	TMNE ESOTH,(ES)		;IS THIS THE "OTHER" SUBLINK?
	CALLRET CLRSRQ		;YES, CLEAR OUT "OTHER" QUEUE

;Here for the normal sublink

	SKIPE DCNCON		;SYSTEM-CONGESTION FLAG SET?
	JRST CHKRC1		;YES, CLEAR UNACKED INPUT QUEUE
  IFN FTGOL <
	LOAD T1,ESGOL,(ES)	;GET CURRENT GOAL FOR LINK
  >
  IFE FTGOL <
	MOVE T1,NSGOAL		;Get system-wide goal
  >
	LOADE T2,ESRLD,(ES)	;GET CURRENT QUOTA FOR LINK
	CAMGE T1,T2		;IF QUOTA IS LARGER THAN GOAL
	MOVE  T1,T2		; USE QUOTA FOR COMPARISON
	OPSTR <CAML T1,>,QHCNT,+ES.RCQ(ES) ;IS RECEIVE Q WITHIN RIGHTS?
	RET			;YES, WE'RE OK
  IFN FTTRACE*FTGOL,<
	TMNE ESGOL,(ES)		;IF NOT ALREADY PESSIMIZED, TELL WATCHER
	ETRACE NSP,<Busting overly optimistic link>
  >
CHKRC1:	LOAD T2,QHBEG,+ES.RCQ(ES) ;GET PTR TO FIRST MSG ON RECEIVE Q
	JUMPE T2,RTN		;SHOULD NOT JUMP
	LOAD T1,NMSGN,(T2)	;GET MSG'S SEGMENT NUMBER
	SOSGE T1		;CALC PREVIOUS NUMBER
	ANDI T1,MASK.(WID(ESLMR),35) ;MOD MASK-SIZE
	CMODE T1,ESLMR,(ES)	;IS THIS EXPECTED NEXT MESSAGE?
	CALLRET CLRSRQ		;NO, JUST CLEAR THE INPUT Q
	SETONE <ESACK,ESNAK>,(ES) ;Yes, NAK the other end
	CALLRET PESFLO		; and pessimize over-optimistic link
	SUBTTL Message Receivers -- PROCRQ - Process the Receive Queue

;PROCRQ - Process the receive queue, sending messages to Session Control
;
;Call:	EL/ The Port Block
;	ES/ The Sublink Block
;	CALL PROCRQ
;	Normal Return
;Changes T1,T2,T3,T4

;PROCRQ will check for data requests to be sent whether or not
;any messages have actually been passed to Session Control.  This
;is so that caller does not have to make a special check for the
;first call after a link is opened.  No data requests will be sent
;to the remote until Session Control makes its first request of NSP.

PROCRQ:	SAVEAC <MB,MS>		;FOLLOWING ROUTINES NEED THIS
	TMNE QHBEG,+ES.RCQ(ES)	;UNLESS THERE IS NOTHING ON IT,
	CALL PRCRQS		; PROCESS THE RECEIVE QUEUE

	JE ESROF,(ES),PRCRQ1	;DON'T BOTHER THE OFF FLAG IF ITS CLEAR
	JE ESRLD,(ES),PRCRQ1	;ITS OFF, JUMP IF NO DRQs FROM SCTL
	SETZRO ESROF,(ES)	;SHOULD BE ON NOW, TURN IT ON
	SETONE <ESROC,ESNAK,ESACK>,(ES) ;TELL CHKDRQ THAT 'OFF' HAS CHANGED
				; AND SEND AN ACK TO RESTART REMOTE NODE
	CALL NSPRJF		;GET JIFFY SERVICE TO SEND LINK SRV MSG
PRCRQ1:
  IFN FTGOL <
	LOAD T1,ESGOL,(ES)	;GET DATA REQUEST GOAL
  >
  IFE FTGOL <
	MOVE T1,NSGOAL		;Get system-wide goal
	SKIPE DCNCON		;System-wide congestion
	SETZ T1,		;Yes, then goal of zero
  >
	LOAD T4,ESRFL,(ES)	;GET RECEIVE FLOW CONTROL TYPE
	JRST @.+1(T4)		;DISPATCH
	   IFIW <PRCRQN&777777>	;NO FLOW CONTROL
	   IFIW <PRCRQG&777777>	;SEGMENT FLOW CONTROL
	   IFIW <PRCRQM&777777>	;MESSAGE FLOW CONTROL
	   IFIW <PRCRQN&777777>	;ILLEGAL FLOW CONTROL

PRCRQG:	LOADE T2,ESRLD,(ES)	;YES, GET RECEIVE LOCAL DRQ
	CAMGE T1,T2		;USE LARGER OF GOAL AND RLD
	MOVE T1,T2		; SO IF GOAL IS ZERO, WE USE RLD
PRCRQ2:	LOADE T2,ESRRD,(ES)	;GET RECEIVE REMOTE REQS OUTSTANDING
	SUB T1,T2		;FIND HOW MANY WE CAN SEND NOW
	JUMPL T1,PRCRQN		;DON'T SEND NEGATIVE REQUESTS UNTIL
				; OTHER SYSTEMS KNOW HOW TO RECEIVE THEM
	ADD T2,T1		;REMEMBER THE NEW TOTAL OUTSTANDING
	STOR T2,ESRRD,(ES)
	LOADE T2,ESRSD,(ES)	;GET CURRENT COUNT TO SEND
	ADD T1,T2		; MAKE NEW TOTAL (POSSIBLY NEGATIVE)
	STOR T1,ESRSD,(ES)	; AND TELL SENDRQ TO SEND THEM
	JRST PRCRQ3		;TOTAL DRQS TO SEND IN T1

;Please Note:
;The correct behavior of the "other" sublink depends on PRCRQM not
;sending any data requests when the data requests from SCTL fall to zero.
;The "other" sublink does not really use message flow control, so we fake
;it here.  We must not send a data request, even though the "other" goal
;is one, when we have no permission from session control, for we will never
;cache an interrupt message received.

PRCRQM:	JN ESRLD,(ES),PRCRQ2	;ONLY SEND MSG DRQS IF SCTL HAS AT
				; LEAST 1 OUTSTANDING RECV DRQ
PRCRQN:	MOVEI T1,0		;SEND NO DRQS
PRCRQ3:				;T1 CONTAINS DRQs TO SEND
	TMNN ESACK,(ES)		;ANY MESSAGES NEED ACKING?
	JRST PRCRQ4		;NO, PASS DRQs TO SEND IN T1 TO PRCRQ4
  IFN FTBFR <
	TMNE ESBFR,(ES)		;YES, IS THIS A BUFFER-RICH SUBLINK?
	JRST PRCRQ4		;YES, DON'T SEND THE ACK YET
  >

;If SNDACK is called on the "other" sublink, it will try to piggy
;back the ACKs on a link service message.  We know that we are also
;buffer-poor on the "other" sublink (by definition of the "other"
;protocol).  SNDACK on OSL will also send any DRQs outstanding.

;Buffer-richness has been superseded by ACK delay.

	CALL SNDACK		;POOR, SEND ACKS NOW (SEE COMMENT ABOVE)
	  CALLRET NSPRJF	;CAN'T SEND, TRY LATER

;We'll send DRQs right now on any sublink if we think the remote
;node has no more DRQs from us on this link.

	LOADE T1,ESRSD,(ES)	;HOW MANY DRQS DO WE HAVE TO SEND?
PRCRQ4:	JUMPE T1,RTN		;NONE, LEAVE NOW
	LOADE T2,ESRRD,(ES)	;TOTAL WE THINK REMOTE WILL HAVE FROM US
	CAMG T2,T1		;IS REMOTE STARVED?
	CAIG T1,0		;YES, ARE THERE ANY DRQ'S TO SEND?
	CALLRET NSPRJF		;NO, SEND WHEN WE'VE COLLECTED MORE
	CALL CHKSDQ		;REMOTE IS STARVED, SEND DRQS NOW
	  CALLRET NSPRJF	;CAN'T, SEND LATER
	TMNN ESACK,(ES)		;STILL NEED TO SEND AN ACK?
	RET			;NO, ALL DONE
	CALLRET NSPRJF		;YES, SEND AN ACK NEXT JIFFY
;PRCRQS - Subroutine called by PROCRQ to process entries on the queue
;
;Call:	MB/ saved by caller, available
;	EL/ The Port Block
;	ES/ The Sublink Block
;	CALL PRCRQS
;	Normal Return
;Changes T1,T2,T3,T4

PRCRQS:
PRCRS1:	LOADE T1,ESRLD,(ES)	;GET SIGN-EXTENDED RECEIVE LOCAL DRQS
	JUMPLE T1,RTN		;LEAVE IF NO MORE AVAILABLE
	LOAD MB,QHBEG,+ES.RCQ(ES) ;GET PTR TO FIRST MESSAGE W/O DEQUING
	JUMPE MB,RTN		;ALL DONE IF QUEUE IS EMPTY
	LOAD T1,NMSGN,(MB)	;GET THIS MESSAGE'S SEGMENT NUMBER
	SOSGE T1		;IS IT THE ONE WE'RE EXPECTING NEXT?
	ANDI T1,MASK.(WID(ESLMR),35) ;MOD MASK-SIZE
	CMODG T1,ESLMR,(ES)	;IS IT EXPECTED MSG OR DUPLICATE?
	JRST PRCRS2		;YES

;Here when we receive a message whose segment number is larger than
;expected, indicating that we have missed a message segment.

	LOAD T1,ELVER,(EL)	;Get remote NSP version #
	CAIE T1,VER3.1		;Is it phase II?
	RET			; -no, then do no more
	SETONE <ESNAK,ESACK>,(ES) ;Missing a message, send
	CALLRET NSPRJF		  ; a NAK next jiffy

;Here when the next message on the queue is expected or duplicate

PRCRS2:	DEQUE MB,ES.RCQ(ES),MB.NXT,RTN ;RETURN IF Q IS EMPTY (?)
	LOAD T1,NMSGN,(MB)	;GET THIS MSG'S SEGMENT NUMBER
	CMODLE T1,ESLMR,(ES)	;DUPLICATE?
	JRST PRCRS3		;NO
	CALL FREMSG		;YES, FREE THE MSG BLOCK
	SETONE ESACK,(ES)	;SEND AN ACK EVEN IF ITS A DUPLICATE
	CALL NSPRJF		; AT THE NEXT JIFFY
	JRST PRCRS1		;GO TRY NEXT ENTRY ON QUEUE

PRCRS3:	JN ESOTH,(ES),PRCRS4	;DON'T CHECK QUOTA FOR INTERRUPT MESSAGES
	LOAD T1,ELSCB,(EL)	;TELL SCTL WHICH LINK THIS IS
	CALL SCTRIB		;RESERVE AN INPUT BUFFER IN SESSION CONTROL
	 TRNA			;FAILED
	  JRST PRCRS4		;SUCCEEDED
	LOAD T1,ELVER,(EL)	;GET REMOTE NSP VERSION
	CAIE T1,VER3.1		;IS IT PHASE II?
	JRST PRCRS9		;NO, NO NAK NEEDED
	CALL FREMSG		;YES, FORGET MESSAGE
	SETONE <ESNAK,ESACK>,(ES) ;MISSING A MSG, SEND A
	CALLRET NSPRJF		  ; NAK NEXT JIFFY

PRCRS9:	XMOVEI T1,ES.RCQ(ES)	;GET APPROPRIATE RECEIVE QUEUE
	CALL QSRTMB		;RE-QUEUE MESSAGE
	  CALLRET FREMSG	;SHOULD NOT BE DUPLICATE
	RET			;ONLY RETURN

;Here when SCTRIB has blessed this receive

PRCRS4:	SETONE ESACK,(ES)	;Need an ACK

	TMNE NMDLY,(MB)		;ACK DELAY set in message?
	IFSKP.			; -no, queue jiffy service
	  CALL NSPRJF		;  Do ACK at next jiffy
	ELSE.			;-yes, ACK DELAY was set
	  TMNE ESDLY,(ES)	; Was DELAY already set?
	  IFSKP.		;  -no, no jiffy service needed
	    SETONE ESDLY,(ES)	;   Set the indicator
	    MOVE T1,NSPADL	;   Get # of seconds before timeout of DELAY
	    STOR T1,ESDLT,(ES)	;    and save for second-level check
	  ENDIF.
	ENDIF.

	LOAD T1,NMSGN,(MB)	;GET THIS MSG'S SEGMENT NUMBER AGAIN
	STOR T1,ESLMR,(ES)	;UPDATE LAST MSG FULLY RECEIVED NUMBER
	XMOVEI T1,IN.MSD(MB)	;GET LENGTH OF SESSION
	CALL DNSLNG		; CONTROL PART OF MESSAGE
	LOAD T4,ELNDB,(EL)	;GET POINTER TO NSP NODE BLOCK
	OPSTRM <ADDM T1,>,NNRBC,(T4) ;ADD TO TOTAL BYTES RECEIVED FROM NODE
	OPSTRM <AOS>,NNRMC,(T4)	; and increment # of user messages received

;Number of messages received from node is updated at RCVMSG, since
;all messages are counted, not just user data messages.

	LOAD T1,ESRFL,(ES)	;UPDATE FLOW CONTROL VARIABLES PROPERLY
	JRST @.+1(T1)		;CASE OF FLOW CONTROL MODE
	   IFIW <PRCRSN&777777>	;NO FLOW CONTROL
	   IFIW <PRCRSS&777777>	;SEGMENT FLOW CONTROL
	   IFIW <PRCRSM&777777>	;MESSAGE FLOW CONTROL
	   IFIW <PRCRS6&777777>	;ILLEGAL FLOW CONTROL

PRCRSM:	TMNN MBEOM,(MB)		;IS THIS SEGMENT AN END OF MESSAGE?
	JRST PRCRS5		;NO
PRCRSS:	OPSTRM <SOS>,ESRRD,(ES)	;DECREMENT REMOTE RECEIVE DRQS
PRCRS5:
PRCRSN:	OPSTRM <SOS>,ESRLD,(ES)	;ONE LESS RECEIVE LOCAL DRQ ANYWAY
	LOAD T1,ESOTH,(ES)	;GET THE "OTHER" SUBLINK FLAG
	STOR T1,MBOTH,(MB)	;COPY TO THE MESSAGE BLOCK
	LOAD T1,ELSCB,(EL)	;GET THE SCBID FOR SESSION CONTROL
	SETZ T2,		;IGNORED
	MOVEI T3,SV.SEG		;GET THE FUNCTION CODE FOR RECEIVE DATA
	MOVE T4,MB
	OPSTR <CALL @>,ELSCV,(EL) ;INDIRECT THROUGH SCTL'S VECTOR
	JRST PRCRS1		;ANY MORE?

;Here if the flow control variable is in illegal state

PRCRS6:	BUG.(CHK,LLIS2S,LLINKS,SOFT,<Illegal flow control at PRCRQS>,<<EL,ELPTR>,<ES,ESPTR>,<MB,MBPTR>>,<

Cause:	An illegal flow control type was found at PRCRQS when the receive
	queue was processed.  If a remote node had sent us a bad flow control
	type, it should have been found by the message parsing routines.
	Therefore this should never happen.

Action:	If this happens more than once, please submit a SPR and include the
	additional data and a dump of the system.

Data:	ELPTR - Address of EL block
	ESPTR - Address of ES block
	MBPTR - Address of message block

>,PRCRS1)
	SUBTTL Message Receivers -- RCVLKS - Receive a Link Service Message

;RCVLKS - Receive a Link Service (data request) message
;
;Call:	EL/ The Port Block
;	ES/ The Sublink Block, for the link service message itself
;	MB/ The Message Block
;	CALL RCVLKS
;	Normal Return
;Changes T1,T2,T3,T4
;
;	MSGFLG  DLA  SLA  [ACKNUM]  SEGNUM  <->  LSFLAGS  FCVAL
;
;If this link service message is not the next message expected on the
;"other" sublink, throw it away.  We do not cache messages on the
;"other" sublink because of the complexity of uncaching data and link
;service messages separately.  Interrupt data messages are also thrown
;away if they are not the next expected message on the "other" sublink.

RCVLKS:	LOAD T1,NMSGN,(MB)	;GET THE SEGMENT NUMBER
	LOAD T2,ESLMR,(ES)	;GET NUMBER OF LAST MESSAGE RECEIVED
	AOS T2			;INCREMENT BEFORE "ANDI" FOLLOWING
	ANDI T2,MASK.(WID(ESLMR),35) ;MAKE IT MOD MASK-SIZE (12 BITS)
	CAME T1,T2		;IS NEW MESSAGE THE NEXT EXPECTED?
	JRST LKSAK1		;NO, SEE IF WE NEED TO ACK IT
	STOR T1,ESLMR,(ES)	;YES, LAST "OTHER" MESSAGE RECEIVED

;We set the ACK flag here, and then call the rest of the routine as a
; coroutine.  The remaining part of the routine may cause another message
; to be sent, and if so, then we can piggy-back the ACK to the remote.
; When the coroutine returns we check whether that happened or not - if
; the latter, then we'll just call SACKMG to send the ACK.

	SETONE ESACK,(ES)	;Flag that we need an ACK on this sublink
	CALL RCVLK0		;Call coroutine to process the message
	TMNN ESACK,(ES)		;Still need to send an ACK?
	RET			;  -no, the ACK has been piggy-backed
	CALLRET SACKMG		;Yes, go send ACK if can, NSPRJF if cant

;RCVLK0 processes the link service message
RCVLK0:	SAVEAC <ES>		;Preserve sublink pointer
	LOAD T1,ELSTA,(EL)	;CHECK THAT WE'RE IN THE RIGHT STATE
	IFNSTATE T1,<CC,RN,DI>,FREMSG
	IFSTATE T1,CC		;IF IN CC STATE, WAITING FOR FIRST MESSAGE,
	  CALL PTIRUN		; PUT IN RUN STATE
;LSFLAGS
	CALL DNGEBY		;GET EXTENSIBLE BYTE INTO T1
	  EVENT(NSP,MSG,No LSFLAGS field,FREMSG)
	XMOVEI ES,EL.NSL(EL)	;ASSUME "NORMAL" SUBLINK
	JUMPE T1,RCVLK1		;JUMP IF NOTHING UNUSUAL
	CALL LKSLSF		;GO PROCESS THE LSFLAGS FIELD
	  CALLRET FREMSG	;ERROR, IGNORE LSFLAGS AS WELL
;FCVAL
RCVLK1:	CALL DNG1BY		;GET A SINGLE BYTE (FC VAL FIELD) INTO T1
	  EVENT(NSP,MSG,No FCVAL field,FREMSG)
	LOAD T2,ESXFL,(ES)	;GET TRANSMIT FLOW CONTROL TYPE
	JRST @.+1(T2)		;CASE OF FLOW CONTROL TYPE
	   IFIW <LKFCVN&777777>	;NO FLOW CONTROL, IGNORE THE VAL FIELD
	   IFIW <LKFCVS&777777>	;SEGMENT FLOW CONTROL
	   IFIW <LKFCVM&777777>	;MESSAGE FLOW CONTROL
	   IFIW <LKSILG&777777>	;RESERVED FLOW CONTROL TYPE
;The FCVAL field has not had its sign bit extended for this call.
;Only links in segment flow control mode can have negative data
;requests, so here we will see a negative data request (should one be
;sent by mistake in message mode) as an oversized postive request.

	SGNMAX== ^D 127		;MAX TOTAL DRQS ARE ALLOWED TO GET
	SGNMIN==-^D 128		;MIN TOTAL DRQS ARE ALLOWED TO GET

LKFCVM:	OPSTR <ADD T1,>,ESXRD,(ES) ;ADD CURRENT REMOTE XMIT DRQS
	CAILE  T1,SGNMAX	;IS THE RESULT A LEGAL TOTAL?
	JRST LKSILG		;NO, EVENT AND ERROR RETURN
	STOR T1,ESXRD,(ES)	;YES, USE THE NEW TOTAL
	JRST LKFCV1		;PASS T1 ACROSS SEGMENT FLOW CONTROL CODE

;Only a link in segment flow control can have negative data requests,
;so it is only in this routine that we bother to extend the sign of
;the FCVAL field.

LKFCVS:	TRNE  T1,200		;TEST HIGH-ORDER BIT OF 8-BIT BYTE
	ORCMI T1,377		;ITS NEGATIVE, EXTEND THE SIGN BIT
	LOADE T2,ESXRD,(ES)	;ADD IN SIGNED VERSION OF
	ADD T1,T2		; CURRENT XMIT DATA REQUESTS
	CAXL T1,SGNMIN		;IS THE RESULT A
	CAXLE T1,SGNMAX		; LEGAL TOTAL?
	JRST LKSILG		;NO, EVENT AND ERROR RETURN
	STOR T1,ESXRD,(ES)	;YES, USE THE NEW TOTAL
				;ESXLD WILL BE BROUGHT INTO SYNCH
				; BY CLCXDQ ASAP

;We also mark the 'normal' sublink as buffer rich in PROCCI if we
;find the remote node electing NO flow control.

LKFCV1:
  IFN FTBFR <
	MOVX T2,ESBFR		;IS TOTAL OF DRQS UP TO THE
	CAML T1,NSPBFR		; BUFFER-RICH THRESHOLD?
	IORM T2,ES.BFR(ES)	;YES, MARK LINK BUFFER RICH
  >

;Here for no flow control

LKFCVN:

;PROCXQ will use any of these new data requests that it can, then
;send the rest to Session Control.

LKFCV2:	TMNE QHBEG,+ES.XMQ(ES)	;NORMALLY WON'T BE ANYTHING ON XMIT Q
	CALL PROCXQ		;SOMETHING THERE, TRY TO SEND IT
	CALL CLCXDQ		;FIGURE HOW MANY XMIT DRQS SCTL GETS
				;# DRQS TO SEND RETURNED IN T1
	JUMPE T1,FREMSG		;DONE IF NO NEWS FOR SESSION CONTROL

	;... T1 still contains # of DRQs to send
	;... T1 still contains # of DRQs to send
;Now we have decided to send a message to Session Control

	LOAD T3,ESOTH,(ES)	;GET THE "OTHER" SUBLINK FLAG
	STOR T3,MBOTH,(MB)	;COPY TO THE MESSAGE BLOCK

	SETZ T2,		;CLEAR OUT FLAGS FIELDS
	STOR T1,QACNT,+T2	;LOADE/STOR IN CASE FIELDS NOT SAME SIZE
	SETZRO ESXSD,(ES)	;DON'T SEND THIS ONE ANY MORE

	LOAD T1,ELSCB,(EL)	;GET SESSION CONTROL'S SCBID FOR PORT
	MOVX T3,SV.DRQ		;FUNCTION CODE FOR A DATA REQUEST
	MOVE T4,MB		;SESSION CONTROL WANTS MB IN T4
	OPSTR <CALLRET @>,ELSCV,(EL) ;CALL SESSION CTL ON THE CALL VECTOR
;Here if we are to ignore the message as a duplicate.

LKSAK1:	CMODG T1,ESLMR,(ES)	;IS THIS A DUPLICATE MESSAGE?
	CALL SACKMG		;YES, SEND ACK IF CAN, NSPRJF IF CAN'T
	CALLRET FREMSG		;NOT EXPECTED MESSAGE, IGNORE IT


;Here if we receive an illegally formatted message.

LKSILG: EVENT(NSP,FLO,Illegal Flow Control Value)
	CALLRET FREMSG		;ILLEGAL MESSAGE FORMAT
;LKSLSF - Process non-zero Link Service LSFLAGS field for RCVLKS
;
;Call:	T1/ The LSFLAGS byte (only if non-zero)
;	EL/ The Port Block
;	ES/ Initialized to "normal" sublink
;	MB/ The Message Block
;	CALL LKSLSF
;	  Error Return if we have given an EVENT
;	Normal Return with ES set to appropriate sublink block
;Changes T2,T3,T4
;
;This routine is only called if the Link Service Flags field of the
;message is non-zero.


LKSLSF:	LOAD T2,LSINT,+T1	;GET THE INTERPRETATION FIELD OF LSFLAGS
	JUMPE T2,LKSLS2		;JUMP IF DATA REQUEST IS FOR "NORMAL" SUBLINK
	CAIE T2,LS.IOT		;NOT NORMAL IS IT "OTHER"
	CALLRET LKSILG		;NO, EVENT AND ERROR RETURN

;Here if the link service message is for the "other" sublink

	XMOVEI ES,EL.OSL(EL)	;ITS FOR "OTHER", SET UP ES APPROPRIATELY
	RETSKP			;IGNORE FC MOD FIELD FOR "OTHER" SUBLINK

;Here if the link service message is for the "normal" sublink

LKSLS2:				;CALLER HAS SET ES UP FOR NSL
	JUMPE T1,RSKP		;JUMP IF NO CHANGE TO "OFF" FLAG
	CAIN T1,LS.MRS		;IS IT THE RESERVED VALUE?
	CALLRET LKSILG		;EVENT AND ERROR RETURN

	MOVEI T3,1		;ASSUME TURNING FLAG "OFF"
	CAIN T1,LS.MOF		;Are we?
	IFSKP.			; -no, we're turning it on again
	  MOVEI T1,1		;   Lower window size to 1 (suspected
	  STOR T1,ESCWS,(ES)	;   congestion)
	  MOVEI T3,0		;    and set it on
	ENDIF.
	STOR T3,ESXOF,(ES)	;YES, STORE NEW VALUE
	RETSKP			;WE'RE NOW FINISHED WITH LSFLAGS FIELD
	SUBTTL Message Receivers -- RCVACK and RCVNOP - Little Ones

;RCVACK - Deal with the remains of the ACK message
;
;Call:	EL/ The Port Block
;	ES/ The Sublink Block
;	MB/ The Message Block
;	CALL RCVACK
;	Normal Return
;Changes T1,T2,T3,T4

;We've already done ACKNUM field at PRCACK

RCVACK:	LOAD T1,ELSTA,(EL)
	IFSTATE T1,CC		;IF WE'RE IN CC STATE,
	  CALL PTIRUN		; PUT PORT IN RUN STATE (uses ES)
RCVNOP:	CALLRET FREMSG		;OTHERWISE, IGNORE THE MSG



;RCVNOP - A phase II no-operation, ignore it completely
;
;Call:	MB/ The Message Block
;	CALL RCVNOP
;	Normal Return
;Changes T1,T2,T3,T4

;See null routine above
	SUBTTL Message Receivers -- RCVCA - Receive a Connect ACK Message

;RCVCA - Process an incoming Connect ACK Message
;
;Call:	MB/ The Message Block, EL and ES not filled in yet
;	CALL RCVCA
;	Normal Return
;Changes T1,T2,T3,T4

RCVCA:	LOAD T1,NMLLA,(MB)	;GET THE DLA SENT IN THE CA MESSAGE
	MOVEI T2,0		;WE DON'T KNOW THE RLA YET
	CALL FNDPRT		;DO WE KNOW OF SUCH A PORT?
	  JRST FREMSG		;NO, IGNORE THE MESSAGE
	CALL RELCIM		;Release saved CI message
	XMOVEI ES,EL.NSL(EL)	;POINT ES TO THE 'NORMAL' SUBLINK

	LOAD T1,ELSTA,(EL)	;GET PORT STATE
	IFNSTATE T1,<CI>,FREMSG	;UNLESS WE'RE IN CI STATE, IGNORE CONN ACK

	MOVEI T1,0		;THE CI MSG ALWAYS GOES OUT AS MSG 0
	CALL UPDELAY		;INITIALIZE THE ROUND-TRIP GUESSER

	OPSTR <SKIPE T1,>,ELDIM,(EL) ;IS THERE A CONNECT INIT MESSAGE THERE?
	  CALL DNFMSG		;YES, FREE IT
	SETZRO ELDIM,(EL)	; AND FORGET IT

	SETZRO ELTMA,(EL)	;CLEAR INACTIVITY TIMER, SEE ABOVE
	NEWSTATE CD		;GO INTO CD (CONNECT DELIVERED) STATE

;Send the Connect Ack info to Session Control which needs it to
;stop the Connect Initiate timer.

	LOAD T1,ELSCB,(EL)	;GET SCTL'S SCBid FOR THIS PORT
	MOVX T3,SV.CAK		;CONNECT ACK FUNCTION CODE
	MOVE T4,MB
	OPSTR <CALLRET @>,ELSCV,(EL)
	SUBTTL Message Receivers -- RCVCI - Receive a Connect Initiate Request

;RCVCI - Process an incoming Connect Initiate request
;
;Call:	MB/ The Message Block
;	EL/ not yet set up
;	ES/ not yet set up
;	CALL RCVCI
;	Normal Return
;Changes T1,T2,T3,T4

;MSGFLG <-> DLA  SLA  SERVICES  INFO  SEGSIZE  DATA-CTL

RCVCI:				;CI OR RETRANSMITTED CI MESSAGE

;DLA
	CALL DNG2BY		;GET THE DESTINATION LINK ADDRESS
	  EVENT(NSP,MSG,No DLA in CI,FREMSG)
	JUMPN T1,[EVENT(NSP,MSG,Non-zero DLA in CI,FREMSG)]
	STOR T1,NMLLA,(MB)	;SHOULD ALREADY BE ZERO, BUT...
;SLA
	CALL DNG2BY
	  EVENT(NSP,MSG,No SLA in CI,FREMSG)
	STOR T1,NMRLA,(MB)	;STORE THE REMOTE LINK ADDRESS
	CALL CHKDCI		;(T1)DUPLICATE RECEIVED CI MSG? (smashes EL)
	  RET			;YES, IT HAS BEEN DEALT WITH
	CALL NEWLLA		;NO, CREATE A NEW LLA FOR THE PORT
	STOR T1,NMLLA,(MB)	;MAKPRT WILL STORE IN THE NEW PORT BLOCK
	LOAD T2,NMRLA,(MB)	;GET BACK THE SLA (REMOTE LINK ADDR)
	HRL T1,T2		;SET UP HALF-WORDS FOR MAKPRT
	LOAD T2,MBSRC,(MB)	;GET THE SOURCE NODE ADDRESS FOR MAKPRT
	CALL MAKPRT		;MAKE A NEW PORT BLOCK
	  JRST SENDNR		;CAN'T, SEND "NO RESOURCES" MESSAGE
	SETZRO ELSIZ,(EL)	;TELL PROCCI TO STORE NEW ELSIZ ALWAYS
	CALL PROCCI		;PROCESS CI INFORMATION
	  JRST [NEWSTATE DP	;TELL NSISEC TO DELETE THE PORT BLOCK
 		CALLRET FREMSG]	;IGNORE THE CI MESSAGE
				;ROUTER HAS FILLED IN MBCHN FOR US
	XMOVEI T1,IN.MSD(MB)	;Get length
	CALL DNSLNG		; of CI message
	LOAD T2,ELNDB,(EL)	;GET POINTER TO NSP NODE BLOCK
	SETZRO NNMSG,(T2)	;We need a new message of this node goes off
	INCR NNRCC,(T2)		;INCR NUMBER OF RECEIVED CONNECT INITS
	INCR NNTMR,(T2)		;UPDATE COUNT OF MESSAGES FROM NODE
	OPSTRM <ADDM T1,>,NNTBR,(T2) ;Update count of bytes received
	NEWSTATE CR		;GO TO CONNECT RECEIVED STATE
	SETONE ELSCM,(EL)	;SEND A CONNECT ACK
	CALL NSPRJF		; NEXT JIFFY

	CALL GETPID		;GET THE NSPPID FOR EL INTO T1
	LOAD T3,ESXFL,+EL.NSL(EL) ;THE FLOW CONTROL TYPE CHOSEN BY REMOTE
	STOR T3,IAFLO,+T2
	LOAD T3,ELSIZ,(EL)	;GET SIZE OF SEGMENT ON THIS LOGICAL LINK
	STOR T3,IASIZ,+T2
				;T3 IS IGNORED HERE
	MOVE T4,MB		;T1,T2,T3,T4 NOW SET UP
	CALLRET SCTLCI		;THE SPECIAL ENTRY FOR CI MESSAGES
;CHKDCI - Check for Duplicate CI Message Received
;
;Call:	MB/ The Message Block
;	EL/ not yet set up, need not be saved
;	ES/ not yet set up, need not be saved
;	T1/ Remote Link Address in received CI or RCI message
;	CALL CHKDCI		;CALLED ONLY FROM RCVCI
;	 Error Return if duplicate, message freed as appropriate
;	Normal Return
;Changes T1,T2,T3,T4

CHKDCI:
	LOAD EL,QHBEG,+NSPAPQ	;PTR TO FIRST LINK ON NSP ALL LINKS QUEUE
CKDCI1:	JUMPE EL,RSKP		;IF NO LINKS, THIS CAN'T BE DUPLICATE
	LOAD T2,ELRLA,(EL)	;GET THIS LINK'S REMOTE LINK ADDRESS
	CAMN T1,T2		;DOES RECEIVED CI DUPLICATE THIS LINK'S RLA?
	JRST CKDCI2		;YES
	LOAD EL,ELAPQ,(EL)	;NO, TRY NEXT LINK
	JRST CKDCI1		;LOOP THROUGH ALL LINKS

;Here if received CI or RCI message is a duplicate

CKDCI2:	LOAD T2,ELSTA,(EL)	;GET STATE OF EXISTING LINK
				;IF LINK IS OR HAS BEEN RUNNING
	IFNSTATE T2,<CR,CC,DR>,FREMSG ; THEN DISCARD THIS MESSAGE

;Here if we are to send a Connect ACK again, using this message block.

	MOVE T1,MB		;ADDRESS OF MESSAGE BLOCK
	MOVEI T2,0		;NO USER DATA REQUIRED
	CALL DNMINI		;RE-INITIALIZE THE MESSAGE BLOCK
	  RET			;(?? NO USER DATA REQUESTED ??)
	CALLRET SNDCAK		;SEND A CONNECT ACK MESSAGE

	SUBTTL Message Receivers -- RCVCC - Receive a Connect Confirm Message

;RCVCC - Process an incoming Connect Confirm message
;
;Call:	MB/ The Message Block
;	EL/ not yet set up
;	ES/ not yet set up
;	CALL RCVCC
;	Normal Return
;Changes T1,T2,T3,T4

;MSGFLG <-> DLA  SLA  SERVICES  INFO  SEGSIZE  DATA-CTL

RCVCC:
;	SAVEAC <P1,P2>		;Not needed

;DLA
	CALL DNG2BY
	  EVENT(NSP,MSG,No DLA in CC,FREMSG)
	STOR T1,NMLLA,(MB)	;LOCAL LINK ADDRESS
	MOVE P1,T1
;SLA
	CALL DNG2BY
	  EVENT(NSP,MSG,No SLA in CC,FREMSG)
	STOR T1,NMRLA,(MB)	;REMOTE LINK ADDRESS
	MOVE P2,T1		;SAVE RLA FOR LATER
	MOVE T1,P1		;LOCAL LINK ADDRESS AGAIN
	SETZ T2,		;RLA NOT YET MARKED IN PORT BLOCK
	CALL FNDPRT		;LOOK UP PORT BLOCK FOR LLA
	  JRST SENDNL		;NOT THERE, SEND NO LINK AND IGNORE
	MOVE P1,T1		;FNDPRT LEAVES STATE IN T1
	CALL RELCIM		;Release CI message (in case no CA message)
	XMOVEI T1,IN.MSD(MB)	;Get length
	CALL DNSLNG		; of message
	LOAD T2,ELNDB,(EL)	;GET PTR TO NODE BLOCK FOR THIS LINK
	SETZRO NNMSG,(T2)	;We need a new message if this node goes off
	INCR NNTMR,(T2)		;UPDATE COUNT OF MESSAGES FROM NODE
	OPSTRM <ADDM T1,>,NNTBR,(T2) ;Update count of bytes
	XMOVEI ES,EL.NSL(EL)	;CONNECT IS ON 'NORMAL' SUBLINK
	IFNSTATE P1,<CI,CD,RN>,FREMSG ;RN STATE TOO, SEE RCVCC1, BELOW
	LOAD T1,ELVER,(EL)	;GET REMOTE'S VERSION NUMBER
	CAIN T1,VER3.1		;IS IT PHASE II?
	JRST RCVCC1		;YES, NO ACK REQUIRED
	SETONE ESACK,(ES)	;NO, SEND A "NORMAL" SUBLINK ACK
	CALL NSPRJF		; NEXT JIFFY
RCVCC1:	IFSTATE P1,<RN>,FREMSG	;IF ALREADY IN RUN STATE, SKIP STARTUP
	CALL PROCCI		;PROCESS CONNECT INFORMATION
	  JRST FREMSG		;IF ERROR, IGNORE THE MESSAGE
	STOR P2,ELRLA,(EL)	;OK, WE CAN SIGN UP THE RLA NOW
	CALL PTIRUN		;PUT PORT IN RUN STATE

	LOAD T1,ELSCB,(EL)	;GET SESSION CONTROL'S SCBID
	LOAD T3,ESXFL,+EL.NSL(EL) ;GET REMOTE'S FLOW SPECIFICATION
	STOR T3,IAFLO,+T2
	LOAD T3,ELSIZ,(EL)	;GET SIZE IN BYTES OF A SEGMENT
	STOR T3,IASIZ,+T2
	MOVX T3,SV.CCR		;CONNECT CONFIRM RECEIVED
	MOVE T4,MB
	OPSTR <CALLRET @>,ELSCV,(EL) ;TELL SESSION CONTROL ABOUT THE CC
;PROCCI - Process common part of Connect Init and Connect Confirm messages
;
;Call:	EL/ The Port Block
;	MB/ The Message Block
;	CALL PROCCI
;	  Error Return if bad format
;	Normal Return with message data copied to port block
;Changes T1,T2,T3,T4

;If ELSIZ,(EL) is zero, always store the value of the SIZE field in
;the incoming message.  If ELSIZ,(EL) is non-zero, store the minimum
;of it and the SIZE field of the incoming message.  RCVCI calls with
;NPSIZE,(EL) zero, RCVCC calls with it non-zero.

PROCCI:	SAVEAC P1

;SERVICES
	CALL DNGEBY		;GET EXTENSIBLE BYTE
	  EVENT(NSP,MSG,No SERVICES field,FREMSG)
	LOAD P1,SVOPT,+T1	;GET THE FLOW CONTROL OPTION FIELD
	CAIN P1,FCM.XX		;IS FLOW CONTROL MODE THE RESERVED VALUE?
	EVENT(NSP,FLO,Reserved value in PROCCI,RTN) ;ILLEGAL FLOW CONTROL
	TXNE T1,SVFL1		;ASSURE THAT HIGH-ORDER ALL ZERO
	EVENT(NSP,MSG,PROCCI found foul,RTN) ;ILLEGAL MESSAGE
	ANDI T1,SVFL2		;ASSURE THAT LOW-ORDER IS MAGIC NUMBER
	CAIE T1,SV$FL2		;IS IT?
	EVENT(NSP,MSG,PROCCI found foul,RTN) ;ILLEGAL MESSAGE
;VERSION
	CALL DNGEBY		;GET EXTENSIBLE BYTE
	  EVENT(NSP,MSG,No VERSION field,FREMSG)
	CAIN T1,VER3.1		;Is it a phase II NSP?
	IFSKP.			; -no, is it
	 CAIE T1,VER3.2		;   phase III?
	 MOVX T1,VER4.0		;    -no, treat as 4.0 (phase IV)
	ENDIF.

;We've passed all the errors, safe to start filling in the port block now

	STOR T1,ELVER,(EL)	;STORE THE REMOTE'S VERSION NUMBER
	STOR P1,ESXFL,+EL.NSL(EL) ;STORE OUR TRANSMIT FLOW CONTROL FOR NORMAL
				;"OTHER" SUBLINK IS DONE IN MAKPRT
  IFN FTBFR <
	MOVX T1,ESBFR		;GET 'BUFFER-RICH' FLAG
	CAIN P1,FCM.NO		;HAS REMOTE CHOSEN NO FLOW CONTROL?
	IORM T1,ES.BFR+EL.NSL(EL) ;YES, CONSIDER IT 'BUFFER-RICH'
  >
;SIZE
	CALL DNG2BY
	  EVENT(NSP,MSG,No SIZE field,FREMSG)
	LOAD T2,ELSIZ,(EL)	;GET SCTL'S MAX SEGMENT SIZE
	JUMPE T2,PRCCI1		;JUMP IF WE DON'T KNOW IT YET
	CAMGE T1,T2		;IS IT BIGGER THAN SCTL'S MAX?
PRCCI1:	STOR T1,ELSIZ,(EL)	;NO, STORE NEGOTIATED SIZE
	RETSKP			;SUCCESS RETURN
	SUBTTL Message Receivers -- RCVDI - Disconnect Initiate Message

;RCVDI - Process an incoming Disconnect Initiate Message
;
;Call:	EL/ The Port Block
;	MB/ The Message Block
;	CALL RCVDI
;	Normal Return
;Changes T1,T2,T3,T4

;There should not be any messages in the receive queues, because the
;remote node should not have sent a DI message unless it had received
;ACKs from us for all outstanding messages.  In the case that the
;remote is aborting and has not waited for the ACKs, we don't care
;about any messages that may be in the pipeline and we may delete them.
;We clear the receive queues in the CLRRCQ call on the next page.

RCVDI:
;	SAVEAC P1		;P1/ NEW STATE CODE, SEE NEXT PAGE
	CALL RELCIM		;Release any saved CI message
	LOAD T1,ELSTA,(EL)	;THE PORT STATE BEFORE RECEIVED DI MSG
	IFSTATE T1,<CI,CD>,RCVDI1 ;CONNECT INIT, CONNECT DELIVERED
	IFSTATE T1,<RN,CC>,RCVDI2 ;RUN OR CC (PSEUDO-RUN)
	IFSTATE T1,<DI>   ,RCVDI3 ;DISCONNECT INIT
	IFSTATE T1,<DR>   ,RCVDI4 ;DISCONNECT REJECT

;Fall through to here if port is not in an appropriate state for a DI

	CALLRET FREMSG		;IGNORE THE DI MESSAGE
;Here if port was in CI or CD state

RCVDI1:	LOAD T1,NMRLA,(MB)	;WE DON'T KNOW THE RLA YET,
	STOR T1,ELRLA,(EL)	; STORE SO WE CAN RETURN THE DC
	LOAD T1,ELNDB,(EL)	;IF WE WERE TRYING TO CONNECT,
	INCR NNRRC,(T1)		; THEN COUNT ONE MORE RECEIVED REJECT
	NEWSTATE RJ		;GO INTO DISCONNECT RECEIVED STATE
	CALL RCVDIS		;CALL COMMON CODE
	CALLRET SCLOSE		;GO START THE CLOSE PROCESS

;Here if port was in RUN state

RCVDI2:	NEWSTATE DN		;GO INTO DISCONNECT NOTIFICATION STATE
	CALL RCVDIS		;CALL COMMON CODE
	CALLRET SCLOSE		;GO START THE CLOSE PROCESS

;Here if port was in DI state

RCVDI3:	NEWSTATE IC		;GO INTO DISCONNECT INIT COMPLETE STATE
	CALL RCVDIS		;CALL COMMON CODE
	CALLRET SCLOSE		;GO START THE CLOSE PROCESS

;Here if port was in DR state

RCVDI4:	NEWSTATE RC		;GO INTO DISCONNECT REJECT COMPLETE
	CALL RCVDIS		;CALL COMMON CODE
	CALLRET SCLOSE		;GO START THE CLOSE PROCESS


;Common subroutine for RCVDIn

RCVDIS:	SETONE ELSDM,(EL)	;SEND A DISCONNECT CONFIRM MESSAGE
	CALL DNG2BY		;GET REASON CODE
	  EVENT(NSP,MSG,No REASON in DI,FREMSG)
	MOVE T2,T1		;SESSION CONTROL WANTS IT IN T2
	LOAD T1,ELSCB,(EL)	;FIRST ARGUMENT FOR SESSION CONTROL
	MOVX T3,SV.DIR		;SESSION CONTROL FUNCTION CODE
	MOVE T4,MB		;POINTER TO MESSAGE BLOCK
	OPSTR <CALLRET @>,ELSCV,(EL) ;CALL SESSION CONTROL
	SUBTTL Message Receivers -- RCVDC - Disconnect Confirm Message

;RCVDC - Process an incoming Disconnect Confirm Message
;
;Call:	EL/ The Port Block
;	MB/ The Message Block
;	CALL RCVDC
;	Normal Return
;Changes T1,T2,T3,T4

;We CALL CLRRCQ in this routine for the same reason
;as we did in RCVDI (q.v.).

RCVDC:
;	SAVEAC P1		;P1/ REASON CODE
	CALL RELCIM		;Release any saved CI message
	CALL CLRRCQ		;CLEAR OUT THE RECEIVE QUEUES
	CALL DNG2BY		;GET REASON CODE FROM MESSAGE
	  EVENT(NSP,MSG,No REASON in DC,FREMSG)
	MOVE P1,T1		;SAVE FOR LATER

	CAIN P1,RSNRES		;NO RESOURCES
	JRST RCVDCR
	CAIN P1,RSNNLK		;NO LINK
	JRST RCVDCL
	CAIN P1,RSNDSC		;DISCONNECT COMPLETE
	JRST RCVDCC

;Here if the reason code was not one of those expected for Phase III

	LOAD T1,ELSTA,(EL)	;GET PORT STATE
	IFNSTATE T1,<CI,CC,RN>	;ARE WE IN AN EXPECTED STATE?
	  EVENT(NSP,MSG,DC recvd in bad state,FREMSG)
	
;Extract the reason code, and pass it up to session control

	LOAD T1,ELSCB,(EL)	;FIRST ARGUMENT FOR SESSION CONTROL
	MOVE T2,P1		;REASON CODE
	MOVX T3,SV.DCR		;SCTL FUNCTION CODE DISCONNECT CONFIRM RCVD
	MOVE T4,MB		;POINTER TO MESSAGE BLOCK
	OPSTR <SKIPN T5,>,ELSCV,(EL) ;HAS SCTL BLESSED THIS LINK YET?
	  XMOVEI T5,FREMSG	;NO, FREE MESSAGE BLOCK
                                ; SCTL WILL FIND OUT SOON FROM NSP STATE
	CALL 0(T5)		;TELL SCTL ABOUT THE DC OR FREE MESSAGE
	SETZRO <ELSDM,ELSCM>,(EL) ;DON'T SEND A DISCONNECT CONFIRM MESSAGE
				  ; & WE NO LONGER NEED A CAK
	LOAD T1,ELSTA,(EL)	;GET THE STATE AGAIN
	IFNSTATE T1,<CI>        ;CONNECT INITIATE?
	IFSKP.
	  NEWSTATE RJ		;YES, ENTER REJECT CONNECTION STATE
	ELSE.
	  NEWSTATE CN		;NO,  ENTER CLOSE NOTIFICATION STATE
	ENDIF.
	CALLRET SCLOSE		;START THE CLOSE PROCESS AND RETURN

;Here if the DC message was "No Resources"

RCVDCR:	LOAD T1,ELSTA,(EL)
	IFNSTATE T1,<CI>
	  EVENT(NSP,MSG,<NR recvd in non-CI state>,FREMSG)

	LOAD T1,ELSCB,(EL)	;ARGUMENT FOR SESSION CONTROL
	MOVX T3,SV.NRS		;NO RESOURCES FUNCTION CODE
	MOVE T4,MB		;THE MESSAGE BLOCK POINTER
	OPSTR <SKIPN T5,>,ELSCV,(EL) ;HAS SCTL BLESSED THIS LINK YET?
	  XMOVEI T5,FREMSG	;NO, FREE MESSAGE BLOCK
                                ; SCTL WILL FIND OUT SOON FROM NSP STATE
	CALL 0(T5)		;TELL SCTL ABOUT THE DC OR FREE MESSAGE
	SETZRO <ELSCM,ELSDM>,(EL) ;DON'T SEND A DISCONNECT CONFIRM MESSAGE
				  ; & WE NO LONGER NEED THE CAK
	NEWSTATE NR		;GO INTO NO RESOURCES STATE
	CALLRET SCLOSE		;GO START THE CLOSE PROCESS


;Here if the DC message was "No Link"

RCVDCL:	LOAD T1,ELSCB,(EL)	;ARGUMENT FOR SESSION CONTROL
	MOVX T3,SV.NLK		;NO LINK FUNCTION CODE
	MOVE T4,MB		;THE MESSAGE BLOCK
	OPSTR <SKIPN T5,>,ELSCV,(EL) ;SESSION CONTROL INTERESTED IN THIS LINK?
                                ; SCTL WILL FIND OUT SOON FROM NSP STATE
	  XMOVEI T5,FREMSG	;NO, JUST FREE THIS MESSAGE BLOCK
	CALL 0(T5)		;TELL SCTL ABOUT THE DC OR FREE MESSAGE

	SETZRO <ELSCM,ELSDM>,(EL) ;DON'T SEND A DISCONNECT CONFIRM MESSAGE
				  ; & WE NO LONGER NEED THE CAK
	NEWSTATE CN		;GO INTO CLOSE NOTIFICATION STATE
	CALLRET SCLOSE		;GO START THE CLOSE PROCESS

;Here if the DC message was "Disconnect Complete"

RCVDCC:	LOAD T1,ELSTA,(EL)
	IFNSTATE T1,<DI,IC,DR,RC>
	  ETRCRET NSP,DCC recvd in unexpected state
	LOAD T1,ELSCB,(EL)	;ARGUMENT FOR SESSION CONTROL
	MOVX T3,SV.DCR		;DISCONNECT COMPLETE RECEIVED FUNCTION
	MOVE T4,MB		;THE MESSAGE BLOCK
	OPSTR <SKIPN T5,>,ELSCV,(EL) ;SESSION CONTROL INTERESTED IN THIS LINK?
                                ; SCTL WILL FIND OUT SOON FROM NSP STATE
	  XMOVEI T5,FREMSG	;NO, JUST FREE THIS MESSAGE BLOCK
	CALL 0(T5)		;TELL SCTL ABOUT THE DC OR FREE MESSAGE
				;DON'T CLEAR ELSDM IN CASE WE WERE
				; IN IC OR RC STATE AND NEED TO SEND DC
	NEWSTATE CN		;GO INTO CLOSE NOTIFICATION STATE
	CALLRET SCLOSE		;GO START THE CLOSE PROCESS
	SUBTTL Clock-Driven Routines

;NSPJIF - Called by the system once a jiffy (approximately)
;
;Call:	CALL NSPJIF
;	Normal Return
;Changes T1,T2,T3,T4

;Note that we save no ACs until we are sure that we will call
;NSPLCF and thence the rest of NSP.

IF2,IFN QHBEG+1,PRINTX ?NSPJIF requires that QHBEG be a full word


	XRESCD
NSPJIF:

;Notice that the SAVEAC is only called after we are pretty sure we need it.
;Don't use any sensitive ACs until then!

IFN FTOPS10,<
	XCT .CPSK0##		;SKIP IF BOOT CPU
	  RET			;JIFFY PROCESSING ONLY ON BOOT CPU
>;END IFN FTOPS10
	SOS T1,NSPJLC		;DECREMENT THE LONG INTERVAL TIMER
	SOS T2,NSPJSC		; AND THE SHORT INTERVAL TIMER
	TMNE LKJIF,+NSPLKF	;ALREADY A CLOCK REQUEST QUEUED?
	RET			;YES, LET IT DO THE WORK
	JUMPLE T1,NSPJF1	;ALWAYS DO A LONG INT PROCESS IF TIME
	JUMPG T2,NSPJF0		;Go check congestion if no jiffy demand
	TMNE QHBEG,+NSPJFQ	;TIME FOR SHORT INT SERVICE, ANY DEMAND?
	JRST NSPJF1		;YES
	MOVE T1,NSPJSI		;NO, GET NSP JIFFY SHORT INTERVAL VALUE
	MOVEM T1,NSPJSC		;RESET THE SHORT INTERVAL COUNTER
	RET			;LEAVE NOW

NSPJF0:	TMNN <LKCGT,LKRLV>,+NSPLKF ;Congestion processing queued?
	RET			; -no leave now
	JRST NSPJF2		;Go do deferred congestion processing

NSPJF1:	SETONE LKJIF,+NSPLKF	;REQUEST A NSIJIF RUN
				;NSIJIF WILL UPDATE NSPJSC WHEN ITS DONE
NSPJF2:
IFN FTOPS10,SEC1		;MAKE THIS RUN IN SECTION 1
	SAVEAC <T5,T6,P1,P2,MS,MB,FREE1,FREE2>
	CALLRET NSPLCF		;TRY FOR THE NSP INTERLOCK
;If we get to NSIJIF, we know that we want to run a short
;interval process, since we can only be here if there was a
;request for a short interval process or if a long interval has
;passed, in which case we run a short interval process anyway
;as an auditor.

;Note: we run the 'second check' before the 'jiffy check'. This is so that,
; if the second code generates any resends, then we will be able to piggy-back
; ACK's on the resends. 

NSIJIF:

;Did a long interval expire?
	SKIPLE NSPJLC		;Has a long interval elapsed?
	IFSKP.			; -yes,
	  CALL SECCHK		;  Do "once-a-second" processing
	  MOVE T1,NSPJLI	;  Get length of a long interval
	  MOVEM T1,NSPJLC	;  Reset the long interval counter
	ENDIF.

;Now do jiffy processing

	TMNE QHBEG,+NSPJFQ	;ANY DEMAND FOR SHORT INT?
	CALL JIFCHK		;YES, DO "ONCE-A-JIFFY" PROCESSING

	MOVE T1,NSPJSI		;GET LENGTH OF A SHORT INTERVAL
	MOVEM T1,NSPJSC		;RESET THE SHORT INTERVAL COUNTER
	RET

;JIFCHK - Once a jiffy processing
;
;Call:	CALL JIFCHK
;	Normal Return
;Changes T1,T2,T3,T4

;We must not continue processing after getting a no-resources return
;because the procedure which gave that return put the current port
;back on the jiffy-request queue.  If we were to continue along that
;queue, we would visit that port over and over forever.

JIFCHK:	SAVEAC <EL,ES,MB,MS,P1>	;SAVE THE SAME ACS AT AUDCHK
JIFCH1:	DEQUE EL,NSPJFQ,EL.JFQ,RTN ;RTN IF Q IS EMPTY
	SETZRO ELOJQ,(EL)	;NOT ON-JIFFY-QUEUE ANY MORE
	CALL JIFSER		;GIVE JIFFY SERVICE TO THIS PORT
	  JRST NSPRJF		;COULDN'T, ASK FOR SOME NEXT TIME
	JRST JIFCH1		;GO SERVICE NEXT PORT
;JIFSER - Once a jiffy processing
;
;Call:	EL/ The Port to check
;	ES/ available, caller has saved it
;	MB/ available, caller has saved it
;	MS/ available, caller has saved it
;	P1/ available, caller has saved it
;	CALL JIFSER
;	  Error Return if no resources
;	Normal Return
;Changes T1,T2,T3,T4

JIFSER:	LOAD P1,ELSTA,(EL)	;LOAD UP THE STATE FOR LATER CHECKS
	IFSTATE P1,<CL,DP>,JIFSR2 ;IGNORE TESTS IF PORT IS CLOSED
	CALL JCHSDQ		;BEFORE ACK, TRY TO PIGGYBACK "OTHER" ACKS
	  RET			;NO RESOURCES, MUST NOT CONTINUE
	CALL JCHACK		;SEND ANY ACKS NOT ALREADY PIGGYBACKED
	  RET			;NO RESOURCES, MUST NOT CONTINUE
	TMNN <ELSCM,ELSDM>,(EL)	;ANY DIS/CONNECT MSGS REQUIRED?
	JRST JIFSR1		;NO
	CALL JCHSCM		;YES, SEND ANY CONNECT MESSAGES
	  RET			;NO RESOURCES, MUST NOT CONTINUE
JIFSR1:

;Put more jiffy processors here

	IFNSTATE P1,<RC,CN,DI,IC,DN,RJ,NC,NR>,RSKP

JIFSR2:	CALL CHKCLS		;DO ANY DELAYED CLOSE PROCESSING
	  RET			;NO RESOURCES, MUST NOT CONTINUE

	RETSKP			;SUCCESS RETURN
;CHKSDQ - Send data requests
;
;Call:	EL/ The Port Block
;	ES/ available for JCHSDQ, caller has saved it
;	MB/ available for JCHSDQ, caller has saved it
;	MS/ available for JCHSDQ, caller has saved it
;	CALL CHKSDQ/JCHSDQ
;	  Error Return if no resources
;	Normal Return
;Changes T1,T2,T3,T4

CHKSDQ:	SAVEAC <MS,MB,ES>	;ENTRY FOR CALLERS WHO NEED SAVEAC
JCHSDQ:	LOAD T1,ELSTA,(EL)	;ENTRY FOR JIFFY SERVICE, ACS ALREADY SAVED
	IFSTATE T1,<CC,DN,CN,RJ,NC,IC,RC,CL,DP>,RSKP
				;LEAVE IF REMOTE NO LONGER INTERESTED
	LOAD T1,ESLAR,+EL.OSL(EL)  ;ANY "OTHER" MESSAGES TO BE ACKED?
	CMODE T1,ESLMA,+EL.OSL(EL) ;LAST MSG # ASSIGNED = LAST ACK REC'D?
	RETSKP			;NO, PRCACK WILL REQUEST JIFFY SERVICE AGAIN

	XMOVEI ES,EL.OSL(EL)	;CHECK THE "OTHER" SUBLINK FIRST
				; SINCE IF IT NEEDS IT, IT REALLY DOES!
	TMNN QHBEG,+ES.XMQ(ES)	;INTERRUPT MSG WAITING FOR XMIT?
	JRST CHKSD1		;NO, TRY TO SEND LINK SERVICE
	CALL PROCXQ		;YES, GO SEND INTERRUPT MESSAGE
	RETSKP			; WHICH WILL PREVENT LINK SERVICE FOR NOW

CHKSD1:	JN ESRSD,(ES),CHKSD2	;IF ANY INTERRUPT REQUESTS TO GO, SEND NOW
				;"OTHER" SUBLINK IS NEVER "OFF"
	XMOVEI ES,EL.NSL(EL)	;CHECK THE "NORMAL" SUBLINK NOW
	JN ESROC,(ES),CHKSD2	;IF "OFF" FLAG CHANGED, SEND NOW
	LOAD T1,ESRSD,(ES)	;GET NUMBER OF DRQS TO SEND
	JUMPE T1,RSKP		;LEAVE IF NO DRQS TO SEND
	LOAD T2,ESRRD,(ES)	;GET DRQs REMOTE WILL HAVE WHEN WE SEND
	SUBI T2,3(T1)		;DOES REMOTE HAVE LESS THAN 3 NOW?
	JUMPG T2,RSKP		;RETURN NOW IF REMOTE NOT DRQ-STARVED

CHKSD2:	MOVEI T1,0		;NO USER DATA NEEDED
	CALL DNGMSG		;GET A MESSAGE BLOCK
	  RET			;CAN'T, ERROR RETURN
	MOVE MB,T1		;MESSAGE BLOCK POINTER RETURNED IN T1
	CALL SENDRQ		;SEND LINK SERVICE MESSAGE
	  RET			;CAN'T SEND NOW, CALLER WILL CALL NSPRJF
	RETSKP			;SUCCESS RETURN
	SUBTTL CLCXDQ - Calculate Transmit Data Requests

;CLCXDQ - Calculate number of xmit data requests to send to Session Control.
;
;Call:	ES/ The Sublink Block
;	CALL CLCXDQ
;	Normal Return with total data requests to send in T1
;Changes T1,T2,T3,T4

CLCXDQ:	LOAD T1,ESXFL,(ES)	;GET THE TRANSMIT FLOW CONTROL MODE
	JRST @[	IFIW <CLCXDN&777777> ;NO FLOW CONTROL
		IFIW <CLCXD1&777777> ;SEGMENT FLOW CONTROL
		IFIW <CLCXD1&777777> ;MESSAGE FLOW CONTROL
		IFIW <CLCXDN&777777>](T1) ;ILLEGAL FLOW CONTROL

;Here to calculate the number of data requests to send to SCTL

CLCXD1:	LOADE T1,ESXRD,(ES)	;DRQS OUTSTANDING FROM REMOTE
	LOADE T2,ESXLD,(ES)	;DRQS OUTSTANDING TO SCTL
	STOR T1,ESXLD,(ES)	;SCTL NOW AGREES WITH REMOTE
	SUB T1,T2		;GET THE DIFFERENCE
	LOADE T2,ESXSD,(ES)	;GET ANY PREVIOUS SEND COUNT
	ADD T1,T2		;SIGNED ARITHMETIC
	STOR T1,ESXSD,(ES)	;SEND THESE TO SESSION CONTROL
	RET			;T1 HOLDS CURRENT VALUE OF ESXSD

;Here to send no data requests

CLCXDN:	MOVEI T1,0		;RETURN ZERO DATA REQUESTS
	RET
;JCHACK - Send any ACKs that need sending for both sublinks
;
;Call:	EL/ The Port Block
;	ES/ available, caller has saved it
;	MB/ available, caller has saved it
;	CALL JCHACK
;	  Error Return if no resources
;	Normal Return
;Changes T1,T2,T3,T4

JCHACK:	TMNN ESACK,+EL.OSL(EL)	;NEED AN 'OTHER' ACK?
	JRST JCHAK1		;NO
	XMOVEI ES,EL.OSL(EL)	;YES, SET UP FOR "OTHER" SUBLINK
	CALL SNDACK		;SEND 'OTHER' ACK
	  RET			;PROPOGATE ERROR RETURN

JCHAK1:	TMNN ESACK,+EL.NSL(EL)	;NEED AN ACK ON 'NORMAL' SUBLINK?
	RETSKP			;NO, SUCCESS RETURN
	XMOVEI ES,EL.NSL(EL)	;SET UP FOR "NORMAL" SUBLINK
	CALL SNDACK		;CALL COMMON CODE
	  RET			;PROPOGATE ERROR RETURN
	RETSKP			;SUCCESS RETURN
;JCHSCM - Send a Connect-type message if needed
;
;Call:	EL/ The Port Block
;	ES/ available, caller has saved it
;	MB/ available, caller has saved it
;	CALL JCHSCM
;	  Error Return if no resources
;	Normal Return
;Changes T1,T2,T3,T4
;
;Only called if ELSCM or ELSDM is TRUE by JIFSER

JCHSCM:	LOAD T1,ELSTA,(EL)		;GET PORT STATE
	IFSTATE T1,<CR,CC,DR>   ,CHKSC1 ;SEND A CONNECT ACK MESSAGE
	IFSTATE T1,<RC,IC,DN,CN>,RSKP	;LET CHKCLS SENT DISCON CONFIRM
	IFSTATE T1,<CL,DP>      ,CHKSC2 ;NO NEED TO SEND ANY MORE

;If we fall through to here, ELSxM should not have been set

	JRST CHKSC2			;IGNORE THE ELSCM FLAG

;Here if port is in a Connect state, send a Connect ACK Message

CHKSC1:	MOVEI T1,0		;NO USER DATA BUFFER NEEDED
	CALL DNGMSG		;GET A MESSAGE BLOCK
	  RET			;ERROR RETURN, REQUESTING JIFFY SERVICE
	MOVE MB,T1		;MESSAGE BLOCK POINTER RETURNED IN T1
	CALL SNDCAK		;SEND CONNECT ACK MESSAGE
				;FALL THROUGH TO CHKSC3
;Here if port is in CLose state, ELSCM is past due, ignore it

CHKSC2:	SETZRO <ELSCM,ELSDM>,(EL)
	RETSKP			;SUCCESS, CALLER DOESN'T CARE ABOUT
				; ERRORS OTHER THAN NO-RESOURCES
	SUBTTL NSPRJF - Request Jiffy Service

;NSPRJF - Request Jiffy Service
;
;Call:	EL/ The Port Block
;	CALL NSPRJF
;	Normal Return
;Changes T1,T2,T3,T4

NSPRJF:	MOVX T1,ELOJQ		;THE ON-JIFFY-QUEUE BIT
	TDNE T1,EL.OJQ(EL)	;ALREADY ON JIFFY-REQUEST QUEUE?
	RET			;YES, DON'T REPEAT
	IORM T1,EL.OJQ(EL)	;NO, BUT IT IS NOW
	ENDQUE EL,NSPJFQ,EL.JFQ,T1
	RET
	SUBTTL SECCHK - Once-a-Second Checks

;SECCHK - Once-a-Second Checks
;
;Call:	CALL SECCHK
;	Normal Return
;Changes T1,T2,T3,T4

SECCHK:	SAVEAC <EL,P1,ES,MB,MS>	;SAVED FOR CALLEES
	LOAD P1,QHBEG,+NSPAPQ	;GET FIRST PORT ON ALL-PORT-QUEUE
SECCH1:	SKIPN EL,P1		;POINT TO NEXT PORT IN QUEUE
	RET			;NO MORE PORTS, LEAVE
	LOAD P1,ELSTA,(EL)	;GET PORT STATE
	IFSTATE P1,<CR,CL,DP>,SECCH2 ;IGNORE CHECKS IF SCTL NOT YET
				; INVOLVED OR IF PORT IS CLOSED
	CALL CHKRSN		;CHECK FOR RESENDS  * BEFORE CHKCNF *
	CALL CHKCNF		;CHECK FOR CONFIDENCE IN PORT
	IFSTATE P1,<RN,DI>      ;IF RUN OR DI BEFORE DI MSG HAS BEEN SENT,
	  CALL CHKINA		; CHECK FOR INACTIVITY
	CALL CHKDLY		;Check for timeout of ACK DELAY

;This check must be last, since it may destroy the port block!
SECCH2:	MOVE T1,P1		;WE'LL NEED P1 FOR NEXT PORT POINTER
	LOAD P1,QPNXT,+EL.APQ(EL) ;GET NEXT POINTER WHILE WE HAVE PORT
	IFSTATE T1,<DP>		;IF ITS IN DESTROY-PORT STATE,
	  CALL DSTPRT		; DESTROY THE PORT
	JRST SECCH1		;GO SEE IF THERE ARE MORE PORTS TO DO
;CHKINA - Check Port for Inactivity
;
;Call:	EL/ The Port Block
;	ES/ available, saved by caller
;	MB/ available, saved by caller
;	CALL CHKINA
;	Normal Return
;Changes T1,T2,T3,T4

;This is only called when the port is in RUN state

CHKINA:	CALL DNGTIM		;GET CURRENT TIME INTO T1
	OPSTR <SUB T1,>,ELTMA,(EL) ;SUBTRACT TIME OF LAST ACTIVITY
	IDIVI T1,TIMBAS		;MAKE IT SECONDS
	CAMG T1,NSPINA		;IS IT TOO LONG?
	RET			;NO
	LOAD T2,ESXOF,+EL.NSL(EL)  ;GET "NORMAL" TRANSMIT OFF FLAG
	LOAD T1,ESLAR, +EL.NSL(EL) ;GET LAST "NORMAL" ACK RECEIVED
	CMODE T1,ESLMA,+EL.NSL(EL) ;SAME AS LAST MESSAGE SENT?
	JUMPE T2,RTN		   ;NO, LEAVE OF LINK STILL TURNED ON
	LOAD T1,ESLAR, +EL.OSL(EL) ;GET LAST "OTHER" ACK RECEIVED
	CMODE T1,ESLMA,+EL.OSL(EL) ;SAME AS LAST MESSAGE SENT?
	RET			  ;NO, STILL SOME OUTSTANDING

;Here if we have decided that the port has been inactive too long.  Try
;to send a null link service message and wait for the ACK.  If the ACK
;times out then we will claim no confidence in the logical link.

	MOVEI T1,0		;NO USER DATA AREA NEEDED
	CALL DNGMSG		;GET A MESSAGE BLOCK
	  RET			;CAN'T, HAVE TO TRY AGAIN LATER
	MOVE MB,T1		;POINTER TO MSG BLK RETURNED IN T1
	XMOVEI ES,EL.NSL(EL)	;SEND THE DRQ ON THE "NORMAL" SUBLINK
	CALL SENDRQ		;SEND A NULL LINK SERVICE MESSAGE.
	  RET			;(SHOULD NOT HAPPEN, WE'VE JUST CHECKED)

	CALL DNGTIM		;WE'RE MAKING SOME ACTIVITY, SO WE
	STOR T1,ELTMA,(EL)	; WON'T KEEP FINDING NONE EVERY SECOND.
	RET
;CHKRSN - Check for Resends
;
;Call:	EL/ The Port Block
;	ES/ available, saved by caller
;	MB/ available, saved by caller
;	CALL CHKRSN
;	Normal Return
;Changes T1,T2,T3,T4

;In order to avoid difficult mangling of the ACK queues, we only check
;the first message on the queue for timeout.  It will almost always be
;the oldest on the queue, and when it isn't it won't be much different.

CHKRSN:	TMNN ELCNF,(EL)		;DO WE HAVE CONFIDENCE IN LINK?
	RET			;NO, JUST WAIT FOR CLOSE
	CALL CHKRCI		;Check if we need to resend any RCIs
	XMOVEI ES,EL.NSL(EL)	;CHECK THE "NORMAL" SUBLINK
	CALL CHKRSC		;CALL COMMON CODE
	XMOVEI ES,EL.OSL(EL)	;CHECK THE "OTHER" SUBLINK

;Continued on Next Page
;Continued from Previous Page
;Common resend code for the "normal" and "other" sublinks

CHKRSC:
CHKRS1:!LOAD MB,QHBEG,+ES.AKQ(ES) ;GET POINTER TO FIRST MESSAGE ON QUEUE
	JUMPE MB,CHKRSX		;FINISH UP IF LIST NOW EMPTY
	CALL CHKRTM		;Check if time to resend
	  JRST CHKRSX		;  NOT YET, DON'T COMPLAIN

;Here if a message has timed out

	SETZRO ESCDA,(ES)	;# of successful ACK's := 0
	MOVEI T1,1		;Lower current window
	STOR T1,ESCWS,(ES)	; to 1
	LOAD T2,ELVER,(EL)	;GET REMOTE NSP'S VERSION CODE
	CAIE T2,VER3.1		;IS IT PHASE II?
	JRST CHKRS2		;NO
	LOAD T1,NMTIM,(MB)	;ZERO IF NAK FORCED RETRANSMISSION
	JUMPN T1,CHKRSX		;LEAVE UNLESS NAK FORCED RETRANS
	JRST CHKRS3		;PHASE II NEVER GETS NO CONFIDENCE
CHKRS2:				;NMCNT INCR'D BY SNDRTR
	LOAD T1,NMCNT,(MB)	;GET NUMBER OF TIMES WE'VE SENT MESSAGE
	CAMG T1,NSPRTH		;GREATER THAN RESEND THRESHOLD?
	JRST CHKRS3		;NO

;Here if a message has timed out more than the allowed number of times

	TMNN ELCNF,(EL)		;YES, DID WE HAVE CONFIDENCE?
	RET			;NO?, DON'T TELL SCTL AGAIN
	SETZRO ELCNF,(EL)	;YES, BUT NO MORE
	SETONE ELSNC,(EL)	;TELL SESSION CONTROL ABOUT IT
				; SECCHK CHECKS ELSNC NEXT
	RET			;ALL DONE IF NO CONFIDENCE

;Here to resend a message

CHKRS3:	LOAD T1,ELNDB,(EL)	;GET POINTER TO NSP NODE BLOCK
	INCR NNTMC,(T1)		;INCREMENT NUMBER OF TIMEOUTS
	DEQUE MB,ES.AKQ(ES),MB.NXT,CHKRS1 ;SHOULD NOT BE EMPTY
	CALL RSNMSG		;RESEND THIS MESSAGE
	JRST CHKRS1		;GO TRY THE NEXT MESSAGE

;Here when we have finished looking at the ACK queue
;See if we have put anything on the xmit queue for transmission

CHKRSX:	TMNE QHBEG,+ES.XMQ(ES)	;ANYTHING IN THE XMIT QUEUE NOW?
	CALLRET PROCXQ		;YES, TRY TO SEND IT NOW
	RET			;NORMAL RETURN


;CHKRCI - check if a RCI message needs retransmission
CHKRCI:	LOAD T1,ELSTA,(EL)	;Get link state
	IFNSTATE T1,<CI>,RTN	;Retransmit only if in CI state
	ASSUME ELCIM,EQ,-1	;Make sure fullword
	OPSTR <SKIPN MB,>,ELCIM,(EL) ;Get RCI message
	RET			; -there was none to worry about
	CALL CHKRTM		;Is it time to resend
	RET			; -no
	LOAD T1,NMCNT,(MB)	;Get # of times this message was retransmitted
	CAMLE T1,NSPRTH		;Reached threshold?
	  CALLRET RELCIM	; -yes, release message and wait for session
				;  control to time out
	SETZRO ELCIM,(EL)	;Clear pointer to disallow deallocation and
				; retransmission until the message has been
				; returned from ROUTER
	MOVX T1,MGFRCI		;Make it into a RCI message
	CALL BLDRCI		;Build the NSP header
	LOAD T1,ELNDB,(EL)	;Get pointer to node block
	INCR NNXCC,(T1)		;Increment # of CI msgs sent
	MOVX T1, ST%NRS ! ST%ACK ! ST%RQR ! ST%NTR
	CALLRET SNDRTR		;Send to ROUTER, and ask for it back

;Subroutine to calculate if time to resend a message
; Returns RET if not time to resend, RETSKP if time to resend
CHKRTM:
	CALL DNGTIM		;GET CURRENT TIME INTO T1
	OPSTR <SUB T1,>,NMTIM,(MB) ;GET TIME SINCE WE SENT THIS MESSAGE
	LOAD T3,ELNDB,(EL)	;GET ADDR OF NODE BLOCK FOR THIS LINK
	LOAD T2,NNDLY,(T3)	;GET EXPECTED ROUND-TRIP DELAY FOR NODE
	IMUL T2,NSPDLY		;MULTIPLY IT BY THE DELAY FACTOR
	ASH T2,-4		; WHICH IS IN 16ths
	CAMG T1,T2		;HAVE WE BEEN WAITING THAT LONG?
	RET			; -no, not yet
	RETSKP			;-yes, resend now

;RSNMSG - Resend a Message
;
;Call:	EL/ The Port Block
;	ES/ The Sublink Block
;	MB/ The Message to Resend
;	CALL RSNMSG
;	Normal Return
;Changes T1,T2,T3,T4

RSNMSG:	LOAD T1,NMMGF,(MB)	;GET MESSAGE TYPE
	LSH T1,-2		;SET UP TO READ MSGTBL (MESSAGE TABLE)
	CAILE T1,RCVMAX		;IS IT A LEGAL MESSAGE TYPE?
	BUG.(CHK,LLIPIM,LLINKS,SOFT,<PROCXQ found illegal message type>,<<MB,MBPTR>>,<

Cause:	A message that was being resent had a bad message type.  This means
	that the message was overwritten while it was waiting on the resend
	queue.  The message type was good when the message was sent the first
	time.

Action:	If this happens more than once, please submit a SPR with the
	additional data and a dump of the system.

Data:	MBPTR - Pointer to the message block describing the bad message

>,SNDATA)
	TMNN RTFLO,+MSGTBL(T1)	;THIS MESSAGE TYPE FLOW CONTROLLED?
	CALLRET SNDATA		;NO, SEND IT IMMEDIATELY

;Here if we are resending a flow controlled message

	LOAD T1,ESXFL,(ES)	;GET THE FLOW CONTROL MODE
	CAIE T1,FCM.SG		;IS IT SEGMENT FLOW CONTROL MODE?
	JRST RSNMS1		;NO
	OPSTRM <AOS T1,>,ESXRD,(ES) ;(GOOD AS LOADE ON AOS NEGATIVE)
	STOR T1,ESXLD,(ES)	;GIVE PERMISSION TO RESEND IT
				;XLD AND XRD ALWAYS IN SYNCH IN SEG MODE
RSNMS1:	XMOVEI T1,ES.XMQ(ES)	;NO, GET PTR TO THE XMT-Q HEADER PAIR
	CALL QSRTMB		;QUEUE MESSAGE IN (MB) TO SEND AGAIN
	  JFCL			;  Duplicate msg requeued - should never happen
	RET
;CHKCNF - Check confidence, tell Session Control if its news
;
;Call:	EL/ The Port Block
;	ES/ available, saved by caller
;	MB/ available, saved by caller
;	CALL CHKCNF
;	Normal Return
;Changes T1,T2,T3,T4

;We don't use the message block we just got, but we have to have one
;for the call to Session Control in case Session Control wants to
;queue the call.

CHKCNF:	TMNN ELSNC,(EL)		;HAS CONFIDENCE FLAG CHANGED?
	RET			;NO
				;ONCE OFF, ELCNF NEVER LIGHTS AGAIN
	MOVEI T1,0		;NO USER DATA NEEDED
	CALL DNGMSG		;GET A MESSAGE BLOCK
	  RET			;CAN'T, TRY AGAIN LATER
	SETZRO ELSNC,(EL)	;CONFIDENCE CHANGE NO LONGER NEEDED

	MOVE T4,T1		;THE MESSAGE BLOCK RETURNED
	MOVEI T3,SV.NCF		;NO CONFIDENCE FUNCTION CODE
	LOAD T1,ELSCB,(EL)	;GET SESSION CONTROL'S PORT ID
	OPSTR <CALL @>,ELSCV,(EL) ;TELL SESSION CONTROL ABOUT IT

	CALLRET INSBRK		;Go add this node to 'link broken' table

;CHKDLY - Check for timeout of ACK DELAY
;
;Call:	EL setup
;	ES available
;	CALL CHKDLY
;	Normal return

;We need only check the normal sublink for ACK DELAY timeouts, since
; the DLY bit may only be sent on the data sublink.

CHKDLY:	XMOVEI ES,EL.NSL(EL)	;Get pointer to ES
	TMNN ESDLY,(ES)		;ACK DELAY running?
	RET			; -no, just return
	OPSTRM <SOS T1,>,ESDLT,(ES) ;Decrement timer
	JUMPG T1,RTN		;No need to do any more if not yet timed out
	SETZRO ESDLY,(ES)	;Clear DELAY flag
	TMNN ESACK,(ES)		;Need to send an ACK?
	RET			; -no, just return
	CALLRET NSPRJF		;-yes, request jiffy ACK service

	SUBTTL Memory Manager Calls -- NSPCG - We're Congested

;NSPCG Memory Manager calls this when available memory falls
;	below a threshold.
;
;Call:	CALL NSPCG
;	Normal Return, NSP may have freed some buffers
;
;Uses: T1,T2,T3,T4,T5,T6 (in principle)

;If DCNCON is non-zero, RCVSEG will not cache incoming messages.

	XRESCD
NSPCG:

;We need congestion service, but we may come here on interrupt level.
; Since we do not want to run DECnet-36 on interrupt level on the 20,
; we defer the congestion processing by setting LKCGT.  The processing
; routine will happen the next time someone gives up the interlock, or
; at the next jiffy service, whatever comes first.

	SAVEAC <P1,P2,MS,MB,FREE1,FREE2>
	SETONE LKCGT,+NSPLKF	;We need congestion-detected processing
	RET			; but defer

NSICGT:	SKIPN DCNCON            ;ARE WE CONGESTED STILL?
	RET                     ;NO, LEAVE NOW

;For each port in the all ports queue, call PESFLO to return cached
;buffers, put the link into pessimistic flow control and send out
;negative data requests.
;
;Note that we do not have to worry about throwing away a message from a
;Phase II node, since Session Control will not allow a non-zero goal
;for a link to a Phase II node; thus there will never be a receive queue
;unless we miss a message.  If we miss a Phase II message, the link is
;sunk anyway.

	LOAD EL,QHBEG,+NSPAPQ	;GET HEAD OF ALL PORTS LIST
NSICG1:	JUMPE EL,RTN		;LEAVE IF LIST IS EMPTY
	XMOVEI ES,EL.NSL(EL)	;ONLY CHECK "NORMAL" SUBLINK
	JE QHBEG,+ES.RCQ(ES),NSICG2 ;Jump if there is no receive queue
	SETONE <ESACK,ESNAK>,(ES) ;NAK and
	CALL PESFLO		; TRY TO RELIEVE CONGESTION
NSICG2:	LOAD EL,QPNXT,+EL.APQ(EL) ;NEXT PORT ON THE ALL-PORTS Q
	JRST NSICG1		;DO NEXT IF THERE IS ONE
	SUBTTL Memory Manager Calls -- NSPCR - Congestion is Relieved

;NSPCR Memory Manager calls this when available memory recovers
;	to above a threshold after having called NSPCG.
;
;Call:	CALL NSPCR
;	Normal Return, NSP may have freed some buffers
;
;Uses: T1,T2,T3,T4,T5,T6 (in principle)

;Note:	The memory manager may call this routine without having called
;	NSPCG first.  This is fine now, since all we do is clear the
;	the congestion flag.  This should continue to be fine.

	XRESCD
NSPCR:

;See comment at NSPCG about deferring processing

	SAVEAC MB		;SAVE MB FROM NSPLCF
	SETONE LKRLV,+NSPLKF	;We need congestion-relieved processing
	RET			; and defer

	SUBTTL	NSIRLV - Congestion-Relieved Processor

;NSIRLV - Congestion-Relieved Processor
;
;Call:	CALL NSIRLV
;	Only return

;This routine is the interlocked version of NSPCR, which is called by
;the memory manager when congestion is relieved.

;The idea is to visit each link which was turned off when congestion
;was detected and to turn it back on again.

NSIRLV:	SAVEAC <EL,ES,P1,P2>
	CALL SCTUCG		;SESSION CONTROL IS INTERESTED TOO
	MOVE EL,NSPAPQ          ;POINTER TO FIRST OF ALL NSP PORTS
NSRLV1:	JUMPE EL,RTN            ;LEAVE WHEN WE'VE PROCESSED ALL PORTS
	XMOVEI ES,EL.NSL(EL)	;POINT PROCRQ TO NORMAL DATA SUBLINK
	TMNE ESROF,(ES)		;TURNED OFF?
	CALL PROCRQ		;YES, TRY TO TURN IT ON AGAIN
	LOAD EL,ELAPQ,(EL)      ;STEP TO NEXT
	JRST NSRLV1             ; PORT ON ALL PORTS LIST
	SUBTTL PESFLO - Put Link in Pessimistic Flow Control

;PESFLO - Put Link in Pessimistic Flow Control
;
;Call:	EL/ A port block with a non-zero receive queue
;	ES/ The Sublink Block (always the "normal" sublink)
;	CALL PESFLO
;	Normal Return
;
;Uses: T1,T2,T3,T4

PESFLO:	SAVEAC MB
	LOAD T1,ESRFL,(ES)	;GET THE RECEIVE FLOW CONTROL MODE
	CALL @[	IFIW <PESOFF&777777> ;NO FLOW CONTROL (SEND OFF MSG)
		IFIW <PESSEG&777777> ;SEGMENT FLOW CONTROL (SEND NEG DRQS)
		IFIW <PESOFF&777777> ;MESSGAGE F.C. (SEND OFF MSG)
		IFIW <RTN&777777>](T1) ;ILLEGAL F.C. (IGNORE IT)
	  JRST PESFL1		;ALREADY PESSIMIZED

;Use the first message block on the queue to send the link
;service message immediately.

	DEQUE MB,ES.RCQ(ES),MB.NXT,RTN ;RETURN IF QUEUE IS EMPTY
	MOVE T1,MB		;SET UP FOR CALL TO DNMINI
	MOVEI T2,0		;NO USER DATA REQUIRED
	CALL DNMINI		;CLEAN OUT THE MESSAGE BLOCK
	  JRST PESFL1		;  Should never happen when the # of
				;  bytes requested is zero.
	CALL SENDRQ		;SEND OUT THE LINK SERVICE MSG NOW
	  CALL NSPRJF		;CAN'T, OSL IS IN USE, HAVE TO WAIT

;Deallocate the rest of the messages on the receive queue

PESFL1:	CALLRET CLRSRQ		;CLEAR OUT SUBLINK'S RECEIVE Q
;Here to turn off a link.

PESSEG:
PESOFF:	TMNE ESROF,(ES)		;ALREADY OFF?
	RET			;YES, DON'T SEND OFF MSG AGAIN
	SETONE <ESROF,ESROC>,(ES) ;LINK IS OFF, OFF FLAG HAS CHANGED
  IFN FTGOL <
	SETZRO ESGOL,(ES)	;ZERO THE GOAL FOR CHKRCQ
  >
	RETSKP			;SENDRQ WILL SEND THE OFF MSG
	SUBTTL SCLOSE - Start the Close Process

;SCLOSE - Start the Close Process when Ready
;
;Call:	T1/ The new port state
;	EL/ The Port Block
;	CALL SCLOSE
;	Normal Return
;Changes T1,T2,T3,T4

SCLOSE:	CALL CHKCLS		;CALL THE VERSION THAT COMPLAINS
	  JRST NSPRJF		; IF CAN'T START THE CLOSE YET
	RET			;SMOOTH OUT THE RETURN
;CHKCLS - Inner routine for SCLOSE (q.v)
;
;Call:	EL/ The Port Block in its new state
;	CALL CHKCLS
;	  Error Return if need jiffy service
;	Normal Return
;Changes T1,T2,T3,T4

CHKCLS:

IFN FTORC,<
   IFE FTPARANOID <
	JN ELORC,(EL),RTN	;TRY AGAIN NEXT JIFFY IF ANYTHING IN RTR
   >
   IFN FTPARANOID <
	JE ELORC,(EL),CHKCL0	;Proceed if nothing in RTR
	OPSTRM <SOS T1,>,ELCLC,(EL) ;Count down retry count
	JUMPN T1,RTN		;Just return and try next jiffy unless time for
				;BUGCHK
	OPSTR <XMOVEI T1,>,ELORQ,(EL) ;Get queue header of "lost" MBs
	LOAD T1,QHBEG,(T1)	;Get first pointer
	LOAD T2,ELORC,(EL)	;Get ORC count
	SETZ T3,		;Default T3 in case MB pointer is zero
	SKIPE T1		;Is it?
	LOAD T3,NMMAG,(T1)	;Get magical debug word
	BUG.(CHK,LLIORQ,LLINKS,SOFT,<ORQ is non-empty at port close>,<<T1,MBADR>,<T2,ORCNT>,<T3,MAGIC>>,
<

This BUG. only appears in DEBUG monitors.

>)
	RET			;Just return after BUGCHK. Will loop each jiffy
				;and decrement ELCLC to greater negative #s
CHKCL0:
   >;end of IFN FTPARANOID
>;end of IFN FTORC

	LOAD T1,ELSTA,(EL)
	IFSTATE T1,<RJ,DI>   ,CHKCL1 ;IF ABORT CLEAR QS; CLOSE REMOTE
	IFSTATE T1,<DN,RC,IC>,CLSRMT ;CLOSE REMOTE
	IFSTATE T1,<CN>      ,CHKCL2 ;CLEAR QUEUES, CLOSE REMOTE
	IFSTATE T1,<CL>      ,CLSLOC ;CLOSE LOCAL PORT
	IFSTATE T1,<NC,NR,DP>,RSKP ;NOTHING TO DO IN THESE STATES

;Here if we are in an unexpected state
	RETSKP			;Should never get here


;Here for RJ or DI state

;If this is an abort, DI will go out now, since RETBUF
;took away any reason not to.

CHKCL1:	TMNE ELABO,(EL)		;ABORT?
	CALL RETBUF		;YES, RETURN ALL BUFFERS (XMQ,RCQ,AKQ)
	SETZRO ELABO,(EL)	;DON'T CLEAN OUT DI MSG NEXT TIME
	CALLRET CLSRMT		;THEN GO CLOSE REMOTE

;Here for CN or IC or RC state

CHKCL2:	CALL RETBUF		;RETURN ALL BUFFERS (XMQ,RCQ,AKQ)
	CALLRET CLSRMT		;THEN GO CLOSE REMOTE
;CLSRMT - The Remote Node has Closed this Link
;
;Call:	EL/ The Port Block
;	CALL CLSRMT
;	  Error Return if we need jiffy service
;	Normal Return
;Changes T1,T2,T3,T4

CLSRMT:	SAVEAC <MB,MS,ES>
	CALL JCHACK			;SEND AN ACK IF NEED BE
	  RET				;COULDN'T, TRY AGAIN NEXT JIFFY
	LOAD T1,ESLMA,+EL.NSL(EL)	;"NORMAL" SUBLINK FINISHED ACKING?
	OPSTR <CAME T1,>,ESLAR,+EL.NSL(EL)
	RET				;NO, TRY NEXT JIFFY
	LOAD T1,ESLMA,+EL.OSL(EL)	;"OTHER" SUBLINK FINISHED ACKING?
	OPSTR <CAME T1,>,ESLAR,+EL.OSL(EL)
	RET				;NO, TRY NEXT JIFFY

;Though ELDIM is timeshared to hold a CI message in CI state and a DI
;message in DI state, we are guaranteed that we only have a DI message
;here because you can't get to DI state from CI state without going
;through CC or RJ states, both of which clear out any CI message.

	LOAD MB,ELDIM,(EL)	;GET POINTER TO SAVED DI MSG
	JUMPE MB,CLSRM1		;JUMP IF NONE THERE
	SETZRO ELDIM,(EL)	;NOT THERE ANY MORE
	LOAD T1,ELSTA,(EL)	;GET LINK'S STATE
	XMOVEI T2,FREMSG	;ASSUME NOT DI STATE (MIGHT BE CI MSG)
	IFSTATE T1,<DI>		;IS IT IN DISCONNECT INITIATED STATE?
	  XMOVEI T2,SENDDI	;YES, WE'LL SEND THIS DI MESSAGE
	CALL 0(T2)		;SEND OR FREE MESSAGE IN MB
CLSRM1:	TMNN ELSDM,(EL)		;NEED TO SEND A DISCONNECT CONFIRM MSG?
	RETSKP			;NO, SUCCESS RETURN NOW
	MOVEI T1,0		;YES, NO USER DATA ON MESSAGE BLOCK
	CALL DNGMSG		;GET A MESSAGE BLOCK
	  RET			;TRY AGAIN NEXT JIFFY
	MOVE MB,T1
	CALL SNDDSC		;SEND DISCONNECT COMPLETE MESSAGE
	SETZRO ELSDM,(EL)	;DON'T NEED TO SEND IT ANY MORE
	RETSKP			;SUCCESS RETURN
;CLSLOC - Close local side of port
;
;Call:	EL/ The Port Block
;	CALL CLSLOC
;	  Error Return if need jiffy service
;	Normal Return
;Changes T1,T2,T3,T4

CLSLOC:	CALL RETBUF		;RETURN ALL BUFFERS ON PORT
	CALL RELCIM		;Return any CI message still around

;We deallocate any DI message hanging off ELDIM before calling CLSRMT
;because we don't want CLSRMT to send the message.  If the message
;were sent, ROUTER would return it at some later time.  NSIODN
;(output done) would then try to queue it on the link block we are now
;destroying.

	OPSTR <SKIPE T1,>,ELDIM,(EL);ANY SAVED DI MESSAGE?
	CALL DNFMSG		;YES, TOSS IT AWAY
	SETZRO ELDIM,(EL)	;NONE THERE NOW
	CALL CLSRMT		;TRY TO CLOSE REMOTE SOMEWHAT POLITELY
	  JFCL			;FAILED, HE'LL HAVE TO GET A NO LINK

;We know all the queues are empty now, cause we just RETBUFed them

	MOVEI T1,0		;NO USER DATA NEEDED
	CALL DNGMSG		;GET A MESSAGE BLOCK
	  RET			;TRY AGAIN NEXT JIFFY
	MOVE T4,T1		;SESSION CONTROL WANTS MSG PTR IN T4
	LOAD T1,ELSCB,(EL)	;GET SESSION CONTROL'S NAME FOR PORT
	MOVEI T3,SV.CLS		;PORT CLOSED FUNCTION CODE
	OPSTR <CALL @>,ELSCV,(EL) ;CALL SESSION CONTROL

	NEWSTATE DP		;DESTROY PORT NEXT SECCHK
	RETSKP			;SUCCESS RETURN
;RETBUF - Return all buffers (messages) on all queues of port
;
;Call:	EL/ The Port Block
;	CALL RETBUF
;	Normal Return
;Changes T1,T2,T3,T4

RETBUF:	CALL CLRRCQ		;CLEAR THE RECEIVE QUEUES
	CALL CLRXMQ		;CLEAR THE TRANSMIT QUEUES
	CALL CLRAKQ		;CLEAR THE ACK QUEUES
IFN FTPARANOID,<
	SAVEAC ES
	XMOVEI ES,EL.NSL(EL)	;DO THE "NORMAL" SUBLINK
	CALL RETBF1		;CALL COMMON CODE
	XMOVEI ES,EL.OSL(EL)	;DO THE "OTHER" SUBLINK
	RET

RETBF1:	LOAD T1,ESLAR,(ES)	;GET LAST ACK RECEIVED
	CMODN T1,ESLMA,(ES)	;SAME AS LAST MESSAGE SENT?
	RET			;YES, RETBUF WORKED PROPERLY

	BUG.(CHK,LLILMA,LLINKS,SOFT,<RETBUF left LAR # LMA>,,<

Cause:	This BUG is for debugging purposes only and will not be present in
	a production monitor.

>)
	STOR T1,ESLMA,(ES)	;TRY TO RECOVER
>;END OF IFN FTPARANOID
	RET
;CLRRCQ - Clear all messages on both receive queues
;
;Call:	EL/ The Port Block
;	CALL CLRRCQ
;	Normal Return
;Changes T1,T2,T3,T4

CLRRCQ:	SAVEAC ES
	XMOVEI ES,EL.NSL(EL)	;DO THE "NORMAL" SUBLINK
	CALL CLRSRQ		;CALL SINGLE-Q CLEARER
	XMOVEI ES,EL.OSL(EL)	;DO THE "OTHER" SUBLINK
				;FALL THROUGH TO SINGLE-Q CLEARER

;CLRSRQ - Clear all messages on a receive queue
;
;Call:	ES/ The Sublink Block
;	CALL CLRSRQ
;	Normal Return
;Changes T1,T2,T3,T4

CLRSRQ:	DEQUE T1,ES.RCQ(ES),MB.NXT,RTN ;RTN IF Q IS EMPTY
	CALL DNFMSG		;JUST TOSS THE MESSAGE FROM T1
	JRST CLRSRQ		;LOOP ALONG THE WHOLE QUEUE
;CLRXMQ - Clear all messages on both transmit queues
;
;Call:	EL/ The Port Block
;	CALL CLRXMQ
;	Normal Return
;Changes T1,T2,T3,T4

CLRXMQ:	SAVEAC <ES,MB,P1,P2>
	XMOVEI ES,EL.NSL(EL)	;DO THE "NORMAL" SUBLINK
	XMOVEI P2,ES.XMQ(ES)	;CLEAN OUT THE XMIT QUEUE
	CALL CLRP2Q		;CALL COMMON CODE
	XMOVEI ES,EL.OSL(EL)	;DO THE "OTHER" SUBLINK
	XMOVEI P2,ES.XMQ(ES)	;CLEAN OUT THE XMIT QUEUE
	CALLRET CLRP2Q		;CALL COMMON CODE

;CLRAKQ - Clear all messages on both ACK queues
;
;Call:	EL/ The Port Block
;	CALL CLRAKQ
;	Normal Return
;Changes T1,T2,T3,T4

CLRAKQ:	SAVEAC <ES,MB,P1,P2>
	XMOVEI ES,EL.NSL(EL)	;DO THE "NORMAL" SUBLINK
	XMOVEI P2,ES.AKQ(ES)	;CLEAN OUT THE ACK QUEUE
	CALL CLRP2Q		;CALL COMMON CODE
	XMOVEI ES,EL.OSL(EL)	;DO THE "OTHER" SUBLINK
	XMOVEI P2,ES.AKQ(ES)	;CLEAN OUT THE ACK QUEUE
				;FALL THROUGH TO COMMON CODE
;CLRP2Q - Clear the queue pointed to by P2 and ES

CLRP2Q:	TMNN QHBEG,(P2)		;ANYTHING IN THE QUEUE?
	RET			;NO, DON'T CHANGE ESLAR
CLRP21:	DEQUE MB,QH.BEG(P2),MB.NXT,CLRP22 ;CLRP22 IF Q IS EMPTY
	LOAD P1,NMSGN,(MB)	;GET THIS MSG'S SEGMENT NUMBER
	MOVX T1,MA%NDONE	;OUTPUT NOT DONE FLAG
	CALL MGACKD		;PRETEND THAT THE MESSAGE WAS ACKED
	JRST CLRP21		;LOOP OVER THE WHOLE QUEUE

;Here we store the number of the last message we MGACKD
;The queue is sorted in ascending order, so we know it was
;the highest segment number we have seen.

CLRP22:	CMODLE P1,ESLAR,(ES)	;IS LATEST MSG RETURNED HIGHEST?
	STOR P1,ESLAR,(ES)	;STORE AS LAST ACK RECEIVED,
	SETZRO ELDTM,(EL)	;WE'RE FINISHED WITH DELAY TIMER
	RET			;QUEUE LOOKS TRUELY EMPTY NOW
	SUBTTL SENDRQ - Send a Link Service Message

;SENDRQ - Send a Link Service Message
;
;Call:	EL/ The Port Block
;	ES/ The Sublink Block
;	MB/ The Message Block to send the link service message in
;	CALL SENDRQ
;	  Error Return if OSL is in use, message block freed
;	Normal Return
;Changes T1,T2,T3,T4,MS

;SENDRQ zeros the ESROC and ESRSD fields after it has filled in the
;appropriate fields of the link service message, so that the requests
;will not be honored again next jiffy.

;Note that SENDRQ will send a Link Service message every time it is
;called and the "other" sublink is available, whether or not one really
;needs to be sent.  It is the caller's responsibility to see if ESRSD
;and/or ESROC are non-zero if they want to avoid sending null link
;service messages.  The reason is that SECCHK (or its children) may
;call SENDRQ to send a null link service message on purpose if the
;inactivity timer expires.

;When we find that we cannot send a DRQ message because the "other"
;sublink is in use (has an ACK outstanding), we do not build the
;Link Service message and queue it.  Instead, we leave the number
;of DRQs to send in the sublink block (or inactivity timer) and wait
;for the next jiffy service to discover them.  This prevents queuing
;up several messages when we could add the DRQs and send a single
;message when we have a message block and the OSL is free.

SENDRQ:	LOAD T1,ESLAR,+EL.OSL(EL)  ;ANY OSL MESSAGES TO BE ACKED?
	CMODE T1,ESLMA,+EL.OSL(EL) ;LAST MSG # ASSIGNED = LAST ACK REC'D?
	CALLRET FREMSG		;NO, SEND IT LATER FROM JIFSER

;Its OK to send the Link Service message now.

	SAVEAC <ES,P1>		;P1 WILL HOLD CALLER'S ES PTR
	MOVE P1,ES		; SO THAT ES CAN POINT TO "OTHER" SL
	XMOVEI ES,EL.OSL(EL)	; SINCE THIS MSG IS GOING OUT ON OSL
	CALL MAKHDR		;INITIALIZE DNPxBY FOR THE NSP HEADER

;Continued on Next Page
;Continued from Previous Page
;Note that P1 holds the pointer to caller's sublink block
;ES points to the "other" sublink block, since we are sending
; the DRQ on the "other" sublink

;MSGFLG
	MOVX T1,MGFLKS		;ITS A LINK SERVICE MESSAGE
	CALL WRTMGF		;WRITE THE MSGFLG FIELD
;DLA
	LOAD T1,ELRLA,(EL)	;GET THE REMOTE LINK ADDRESS
	CALL DNP2BY
;SLA
	LOAD T1,ELLLA,(EL)	;GET THE LOCAL LINK ADDRESS
	CALL DNP2BY
;ACKNUM
	CALL WRTACK		;WRITE THE ACKNUM FIELD & ZERO ESACK
;SEGNUM
	CALL NEWSGN		;PUT NEW SGN IN MSG BLK & T1
	CALL DNP2BY
;LSFLAGS
	SETZ T1,		;SET TO BUILD LSFLAGS FIELD HERE
  ;FCVAL INT
	MOVX T2,LS.INR		;ASSUME A "NORMAL" SUBLINK DATA REQUEST
	TMNE ESOTH,(P1)		;IS IT THE "OTHER" SUBLINK?
	MOVX T2,LS.IOT		;YES
	STOR T2,LSINT,+T1	;STORE INTERPRETATION SUBFIELD
  ;FC MOD
	MOVX T2,ESROC		;LOAD UP THE RECEIVE OFF CHANGED FLAG
	TDNN T2,ES.ROC(P1)	;OFF FLAG CHANGED?
	JRST SENDR1		;NO, LEAVE FC MOD FIELD ZERO
	ANDCAM T2,ES.ROC(P1)	;YES, NOT ANY MORE
	MOVX T2,LS.MOF		;ASSUME TURNED OFF
	TMNN ESROF,(P1)		;IS IT OFF?
	MOVX T2,LS.MON		;NO, IT MUST BE ON
	STOR T2,LSMOD,+T1	;STORE FC MOD FIELD
SENDR1:	CALL DNPEBY		;WRITE EXTENSIBLE FIELD

;FCVAL
	LOADE T1,ESRSD,(P1)	;GET # OF DRQS TO SEND TO REMOTE
	CALL DNP1BY		;ONE BYTE FIELD (2S COMPLEMENT IF NEG)
	SETZRO ESRSD,(P1)	;WE'VE SENT THEM, ZERO DRQ REQUEST FIELD
	SETONE MBOTH,(MB)	;GO ON "OTHER" SUBLINK
	MOVX T1, ST%NRS ! ST%ACK ! ST%NRQR ! ST%TRL
	CALL SNDRTR		;NO RETURN TO SESSION CONTROL
				;ACK REQUIRED
				;NO RETURN REQUESTED FROM ROUTER
	RETSKP			;TROLL ALLOWED
	SUBTTL SACKMG - Get a Message Block and send ACK message if can

;SACKMG - Get a Message Block and send ACK message if can
;
;Call:	EL/ The Port Block
;	ES/ The Sublink Block
;	CALL SACKMG
;	Normal Return, message sent or NSPRJF called
;Changes T1,T2,T3,T4

SACKMG:	SAVEAC <MB,MS>		;SNDACK WILL SMASH THESE
	SETONE ESACK,(ES)	;WE NEED AN ACK ON THIS SUBLINK
	CALL SNDACK		;SEND THE ACK
	  JRST NSPRJF		;CAN'T, GET JIFFY SERVICE
	RET			;SUCCESS
	SUBTTL SNDACK - Check a sublink to see if it needs an ACK sent

;SNDACK - Check a sublink to see if it needs an ACK sent
;
;Call:	EL/ The Port Block
;	ES/ The Sublink Block
;	MB/ available, caller has saved it
;	MS/ available, caller has saved it
;	CALL SNDACK
;	  Error Return if can't get a message block
;	Normal Return
;Changes T1,T2,T3,T4

;ACKs on the "other" sublink must go out right away, since the sublink
;cannot be used while an ACK is outstanding.  While we are sending a
;message on the OSL, we might as well see if there are any DRQs waiting
;for a jiffy.  If so, we can piggyback the ACK on the DRQ message.

SNDACK:	TMNN ESOTH,(ES)		;WE ON THE "OTHER" SUBLINK?
	JRST SNDAK1		;NO
	CALL CHKSDQ		;YES, TRY TO PIGGYBACK THIS ACK ON A DRQ
	RET			;CAN'T GET A MSG BLK
	TMNN ESACK,(ES)		;DID WE SEND THE ACK?
	RETSKP			;YES, ALL DONE
				;NO, SEND IT ALONE
SNDAK1:	MOVEI T1,0		;NO USER DATA NEEDED
	CALL DNGMSG		;GET A MESSAGE BLOCK
	  RET			;CAN'T, ERROR RETURN TO TRY AGAIN
	MOVE MB,T1		;MESSAGE BLOCK POINTER RETURNED IN T1
	CALL MAKHDR		;INITIALIZE DNPxBY FOR THE NSP HEADER
;MSGFLG
	MOVX T1,MGFACK		;ASSUME ITS A NORMAL ACK
	TMNE ESOTH,(ES)		;IS THIS THE "OTHER" SUBLINK?
	MOVX T1,MGFOAK		;YES, LOAD UP AN "OTHER" ACK CODE
	CALL WRTMGF		;WRITE THE MSGFLG FIELD
;DLA
	LOAD T1,ELRLA,(EL)	;GET THE REMOTE LINK ADDRESS
	CALL DNP2BY
;SLA
	LOAD T1,ELLLA,(EL)	;GET THE LOCAL LINK ADDRESS
	CALL DNP2BY
;ACKNUM
	CALL WRTACK		;WRITE THE ACKNUM FIELD & ZERO ESACK

	LOAD T1,ESOTH,(ES)	;GET THE "OTHER" SUBLINK FLAG
	STOR T1,MBOTH,(MB)	;COPY TO THE MESSAGE BLOCK

	MOVX T1, ST%NRS ! ST%NAK ! ST%NRQR ! ST%TRL
	CALL SNDRTR		;NO RETURN TO SESSION CONTROL
				;NO ACK REQUIRED
				;NO RETURN REQUESTED FROM ROUTER
				;TROLL ALLOWED
	RETSKP			;SUCCESS RETURN
	SUBTTL SENDDI - Send a DI Message

;SENDDI - Send a Disconnect Initiate Message
;
;Call:	EL/ The Port Block
;	MB/ The Message Block with REASON and DATA-CTL field in User Data
;	CALL SENDDI
;	Normal Return
;Changes T1,T2,T3,T4

;Note that Session Control is expected to have put the REASON field (2
;bytes) and the DATA-CTL field, with leading byte of binary count, in
;the user data part of the message.  NSP only fills in the MSGFLG,DLA
;and SLA fields before the Session Control contributions.

SENDDI:	SAVEAC ES
	XMOVEI ES,EL.NSL(EL)	;WE'LL SEND THIS ON THE NSL
	CALL MAKHDR		;INITIALIZE DNPxBY FOR THE NSP HEADER
;MSGFLG
	MOVX T1,MGFDI		;ITS A DISCONNECT INITIATE MESSAGE
	CALL WRTMGF		;WRITE THE MSGFLG FIELD
;DLA
	LOAD T1,ELRLA,(EL)	;GET THE REMOTE LINK ADDRESS
	CALL DNP2BY
;SLA
	LOAD T1,ELLLA,(EL)	;GET THE LOCAL LINK ADDRESS
	CALL DNP2BY

;Here we give the DI message the next message number for the "normal"
;sublink, since there will be no more real messages, and we don't want
;NSIODN to think that the DI message has already been ACKed.

	CALL NEWSGN		;PUT NEW SGN IN MSG BLK

	MOVX T1, ST%NRS ! ST%ACK ! ST%NRQR ! ST%TRL
	CALLRET SNDRTR		;NO RETURN TO SESSION CONTROL
				;ACK REQUIRED
				;NO RETURN REQUESTED FROM ROUTER
				;TROLL ALLOWED
	SUBTTL SNDCAK - Send a Connect ACK Message

;SNDCAK - Send a Connect ACK Message
;
;Call:	EL/ The Port Block
;	MB/ The Message Block, initialized
;	CALL SNDCAK
;	Normal Return
;Changes T1,T2,T3,T4

SNDCAK:	CALL MAKHDR		;INITIALIZE DNPxBY FOR THE NSP HEADER

;MSGFLG
	MOVX T1,MGFCAK		;ITS A CONNECT ACK MESSAGE
	CALL WRTMGF		;WRITE THE MSGFLG FIELD
;DLA
	LOAD T1,ELRLA,(EL)	;GET THE REMOTE LINK ADDRESS
	CALL DNP2BY

	MOVX T1, ST%NRS ! ST%NAK ! ST%NRQR ! ST%TRL
	CALLRET SNDRTR		;NO RETURN TO SESSION CONTROL
				;NO ACK REQUIRED
				;NO RETURN REQUESTED FROM ROUTER
				;TROLL ALLOWED
	SUBTTL SNDDSC - Send a Disconnect Complete Message

;SNDDSC - Send a Disconnect Complete Message
;
;Call:	EL/ The Port Block
;	MB/ The Message Block
;	CALL SNDDSC
;	Normal Return
;Changes T1,T2,T3,T4

SNDDSC:	CALL MAKHDR		;INITIALIZE DNPxBY FOR THE NSP HEADER

;MSGFLG
	MOVX T1,MGFDC		;ITS A DISCONNECT INITIATE MESSAGE
	CALL WRTMGF		;WRITE THE MSGFLG FIELD
;DLA
	LOAD T1,ELRLA,(EL)	;GET THE REMOTE LINK ADDRESS
	CALL DNP2BY
;SLA
	LOAD T1,ELLLA,(EL)	;GET THE LOCAL LINK ADDRESS
	CALL DNP2BY
;REASON
	MOVX T1,RSNDSC		;DISCONNECT COMPLETE REASON
	CALL DNP2BY

	MOVX T1, ST%NRS ! ST%NAK ! ST%NRQR ! ST%TRL
	CALLRET SNDRTR		;NO RETURN TO SESSION CONTROL
				;NO ACK REQUIRED
				;NO RETURN REQUESTED FROM ROUTER
				;TROLL ALLOWED

	SUBTTL MGACKD - A Message has been ACKed

;MGACKD - Return or destroy an ACKed message
;
;Call:	T1/ Flags, see below
;	EL/ The Port Block
;	ES/ The Sublink Block
;	MB/ The Message Block
;	CALL MGACKD
;	Normal Return
;Changes T1,T2,T3,T4

	MA%DONE== 1B35		;SEND WAS DONE
	MA%NDONE==0B35		;SEND WAS NOT DONE

;The message has already been unlinked from any queue it may have been
;on.  Here we decide whether to send the message back to Session
;Control or to free the message block.

MGACKD:	TMNN NMRET,(MB)		;SESSION CONTROL WANT THE MESSAGE BACK?
	JRST FREMSG		;NO, JUST DEALLOCATE IT AND RETURN

				;T2 IS IGNORED ON THIS CALL
	MOVEI T3,SV.ODN		;ASSUME OUTPUT WAS DONE
	TXNN T1,MA%DONE		;WAS IT?
	MOVEI T3,SV.OND		;NO, OUTPUT NOT DONE
	MOVE T4,MB		;SESSION CONTROL WANTS MB IN T4
	LOAD T1,ELSCB,(EL)	;SESSION CONTROL'S SCB ID FOR PORT
	OPSTR <CALLRET @>,ELSCV,(EL) ;TELL SESSION CONTROL AND RETURN
	SUBTTL SENDNL - Send a No Link Message

;SENDNL - Send a No Link Message
;
;Call:	MB/ Message Block to use, may have IN.MSD pointing to garbage
;	The DLA and SLA should be filled into the message block
;	CALL SENDNL
;	Normal Return
;Changes T1,T2,T3,T4

;This routine is called by input routines when they discover that the
;DLA to which the message was bound does not exist.  In this case,
;there will probably be message text hanging off the IN.MSD message
;descriptor.  Before we use the message block to send the No Link
;message, we deallocate the unwanted input text.  We do not deallocate
;the entire message block and then allocate another, because there is
;a possibility that we might not get a message block again, and the
;code here is not prepared for that.

SENDNL:	MOVEI T1,RSNNLK		;"NO LINK" REASON CODE
	CALLRET SENDC0		;GO SEND A DISCONNECT CONFIRM
				; WITH A ZERO-LENGTH DATA-CTL FIELD



;SENDNR - Send a No Resources message, just like a No Link, really
;
;Call:	MB/ The Message Block
;	The DLA and SLA should be filled into the message block
;	CALL SENDNR
;	Normal Return
;Changes T1,T2,T3,T4

SENDNR:	MOVEI T1,RSNRES		;"NO RESOURCES" REASON CODE
	CALLRET SENDC0		;GO SEND A DISCONNECT CONFIRM
				; WITH A ZERO-LENGTH DATA-CTL FIELD
;SENDC0 - Send a No Link or No Resources Message
;
;Call:	T1/ Reason
;	MB/ The Message Block
;	CALL SENDC0
;	Normal Return
;Changes T1,T2,T3,T4

;This routine is called to send No Link and No Resources messages and
;the like.  See them for bigger comment.
;We get a reserved port so that SNDRTR will have somewhere to get
;its destination info from.  We put the port in DP state so that next
;time SECCHK is run it will destroy the port.  A DC message is not
;ACKed, so there is no need for the port block after this one send.

SENDC0:	SAVEAC <P1,EL,ES>
	MOVE P1,T1		;SAVE THE REASON CODE
	LOAD T1,NMLLA,(MB)	;LOAD UP THE LOCAL LINK ADDRESS
	LOAD T2,NMRLA,(MB)	; AND THE REMOTE LINK ADDRESS
	HRL T1,T2		;SET UP RLA,,LLA IN T1 FOR GTRESP
	LOAD T2,MBSRC,(MB)	;GET THE SOURCE NODE ADDRESS
	CALL GTRESP		;GET A RESERVED PORT TO SEND WITH
	  JRST FREMSG		;CAN'T, JUST IGNORE THE MESSAGE
	XMOVEI ES,EL.NSL(EL)	; (IT WILL COME AGAIN LATER)
	NEWSTATE DP		;TELL SECCHK TO DESTROY PORT NEXT SECOND
	MOVE T1,MB		;BLOCK TO BE CLEARED
	MOVEI T2,0		;BYTES OF USER DATA TO GET
	CALL DNMINI		;INITIALIZE THE MSG BLK
	  RET			;  This should never fail when zero user bytes
				;  are requested, but if, just RET
	CALL MAKHDR		;PREPARE AN NSP HEADER IN MESSAGE BLK
;MSGFLG
	MOVEI T1,MGFDC		;DISCONNECT CONFIRM MESSAGE TYPE/SUBTYPE
	CALL WRTMGF		;WRITE THE MSGFLG FIELD
;DLA
	LOAD T1,ELRLA,(EL)	;LOAD UP THE OLD LOCAL LINK ADDRESS
	CALL DNP2BY
;SLA
	LOAD T1,ELLLA,(EL)	;LOAD UP THE NEW LOCAL LINK ADDRESS
	CALL DNP2BY
;REASON
	MOVE T1,P1		;THE REASON CODE PASSED BY CALLER
	CALL DNP2BY		;TAKES 2 BYTES FOR THAT??
	MOVX T1,ST%NRS ! ST%NRQR ! ST%NAK ! ST%TRL
	CALLRET SNDRTR		;NO RETURN TO SESSION CONTROL
				;NO RETURN REQUESTED ON XMISSION FAILURE
				;NO ACK EXPECTED
				;TROLL ALLOWED
	SUBTTL PTIRUN - Put a Port in Run State

;PTIRUN - Put a port from CC, CI or CD state to RUN state
;
;Call:	EL/ The Port Block
;	CALL PTIRUN
;	Normal Return
;Changes T1,T2,T3,T4

PTIRUN:	LOAD T2,ELSTA,(EL)
	IFNSTATE T2,<CC>,PTIRN0	;IF WE WERE IN CC STATE
	SAVEAC ES
	XMOVEI ES,EL.NSL(EL)	;CONNECT CONFIRM MSG IN ON 'NORMAL' SUBLINK
	MOVX T1,<FLD(AK$QAK,AKQAL)> ;BUILD ACK FOR NORMAL DATA MSG ZERO
	CALL PRCACK		;ACK THE CONNECT MESSAGE WE SENT
	CALL NSPRJF		; and request jiffy service to send any LK's
	LOAD T2,ELSTA,(EL)	;RELOAD T2 WITH PORT STATE
PTIRN0:	IFNSTATE T2,<CI>,PTIRN1	;IF WE WERE IN CI STATE
	LOAD T1,ELDIM,(EL)	;IS THERE A CONNECT INIT MESSAGE THERE?
	JUMPE T1,PTIRN1		;JUMP IF NOT
	CALL DNFMSG		;YES, FREE IT
	SETZRO ELDIM,(EL)	; AND FORGET IT
	LOAD T2,ELSTA,(EL)	;RELOAD T2 WITH PORT STATE
PTIRN1:	IFSTATE T2,<CD>,PTIRN2	;IF WE MISSED THE CONNECT ACK
	MOVEI T1,0		;CI MSG ALWAYS GOES OUT AS MSG 0
	CALL UPDELAY		;INITIALIZE THE ROUND-TRIP DELAY

;Note that the call to UPDELAY must come before we change the
;state, since UPDELAY needs to know the old state to know how
;to handle the delay time.

PTIRN2:	NEWSTATE RN		;PUT THE PORT IN "RUN" STATE
	CALL DNGTIM		;GET A TIMESTAMP
	STOR T1,ELTMA,(EL)	;START THE INACTIVITY TIMER
	CALLRET NSPRJF		;AND REQUEST JIFFY SERVICE

	SUBTTL MAKPRT - Make a New Port Block

;MAKPRT - Make a New Port Block and Initialize It
;
;Call:	T1/ RLA,,LLA
;	T2/ Node Id
;	CALL MAKPRT
;	  Error Return if no resources for block(s)
;	Normal Return with pointer to new block in EL
;Changes T1,T2,T3,T4,EL,ES

MAKPRT:	SAVEAC <P1,P2>		;P1/ ADDRESS, P2/ NODEID
	DMOVEM T1,P1		;SAVE ADDRESSES IN P1 & P2
	MOVEI T1,EL.LEN		;LENGTH OF A PORT BLOCK
	CALL DNGWDZ		;GET A ZEROED BLOCK AT LEAST THAT LONG
	  RET			;ERROR RETURN IF FAILED
	MOVE EL,T1		;POINT TO THE NEW PORT BLOCK

	STOR P1,ELLLA,(EL)	;STORE THE LLA (RH OF P1)
	HLRZ T1,P1		;GET THE RLA
	STOR T1,ELRLA,(EL)

	STOR P2,ELNNM,(EL)	;STORE REMOTE'S NODE NUMBER FOR SNDRTR
	MOVE T1,P2		;NODEID FROM ARGS
	CALL FNDNOD		;LOOK UP OR BUILD NSP NODE BLOCK
	  JRST MAKPTE		;DEALLOCATE PORT BLOCK, RETURN
	STOR T1,ELNDB,(EL)	;SAVE PTR TO NSP NODE BLOCK
	CALL SETPRT		;SETUP THE NEW PORT BLOCK
	  JRST MAKPTE		;FAILED, DEALLOCATE THE PORT BLOCK
	MOVE T1,[XCDSEC,,SCTL]	;Default session control address to SCLINK
	STOR T1,ELSCV,(EL)	;MAKPRS WILL CHANGE FOR 'RESERVED PORTS'
				; NSPCLS CAN NEED THIS DEFAULT ADDRESS
	RETSKP			;SUCCESS



MAKPTE:	NEWSTATE DP		;TELL SECCHK TO DESTROY PORT BLOCK
	RET
	SUBTTL GTRESP - Get a Reserved Port

;GTRESP - Get a Reserved Port off the Reserved Port Queue
;
;Call:	T1/ RLA,,LLA
;	T2/ Node Id
;	CALL GTRESP
;	  Error Return if no resources for block(s)
;	Normal Return with pointer to new block in EL
;Changes T1,T2,T3,T4,EL,ES

GTRESP:	SAVEAC <P1,P2>		;P1/ ADDRESS, P2/ NODEID
	DMOVEM T1,P1		;SAVE ADDRESSES IN P1 & P2
	MOVEI T1,EL.LEN		;LENGTH OF A PORT BLOCK
	CALL DNGWDZ		;GET A ZEROED BLOCK AT LEAST THAT LONG
	  JRST GTRES1		;FAILED, TRY THE RESERVED PORT Q
	MOVE EL,T1		;POINT TO THE NEW PORT BLOCK
	JRST GTRES2		;BACK TO COMMON CODE

GTRES1:
	DEQUE EL,NSPRPQ,EL.APQ,RTN ;RTN IF Q IS EMPTY
	MOVE T1,EL		;ADDRESS OF BLOCK TO BE ZEROED
	XMOVEI T2,EL.LEN-1(EL)	;END OF BLOCK
	MOVEI T3,0		;VALUE TO SMEAR INTO BLOCK
	CALL DNSWDS		;USE BLT OR XBLT AS APPROPRIATE

GTRES2:	STOR P1,ELLLA,(EL)	;STORE THE LLA (RH OF P1)
	HLRZ T1,P1		;GET THE RLA
	STOR T1,ELRLA,(EL)

	STOR P2,ELNNM,(EL)	;STORE REMOTE'S NODE NUMBER FOR SNDRTR
	MOVE T1,NSPRND		;POINT TO THE "RESERVED" NODE
	STOR T1,ELNDB,(EL)	;SAVE PTR TO NSP NODE BLOCK
	CALL SETPRT		;SETUP THE NEW PORT BLOCK
	  JRST GTRESE		;FAILED, DEALLOCATE THE PORT BLOCK
	CALL MAKPRS		;MAKE THIS PORT A RESERVED PORT
	  JRST GTRESE		;FAILED, DEALLOCATE THE PORT BLOCK
	RETSKP			;SUCCESS


GTRESE:	NEWSTATE DP		;TELL SECCHK TO DESTROY PORT BLOCK
	RET
	SUBTTL MAKPRS - Make this Port Reserved

;MAKPRS - Change this port so that it is a reserved port
;
;Call:	EL/ The Port Block
;	CALL MAKPRS
;	  Error Return, if BUG given
;	Normal Return
;Changes T1,T2,T3,T4,EL,ES

MAKPRS:	XMOVEI T1,RESPRC	;POINTER TO THE RESERVED PROCESS CODE
	STOR T1,ELSCV,(EL)	;MAKE THIS THE SESSION CONTROL ENTRY
	CALL GETPID		;GET THE NSPpid FROM EL
	STOR T1,ELSCB,(EL)	;MAKE "SESSION CONTROL" PTR SAME AS NSP
				; SO RESPRC KNOWS WHO TO TALK TO
	RETSKP			;SUCCESS RETURN
	SUBTTL SETPRT - Make a New Port Block

;SETPRT - Setup a new port block or a newly assigned reserved port
;
;Call:	EL/ The Port Block, with LLA, RLA (or zero) and ELNNM filled in
;	CALL SETPRT
;	  Error Return if no resources for block(s)
;	Normal Return with pointer to new block in EL
;Changes T1,T2,T3,T4,EL,ES

SETPRT:	CALL ADDHSH		;PUT NEW PORT BLOCK IN HASH TABLE
	  RET			;CAN'T, NO RESOURCES

;No more errors, now its safe to deal with new Port Block
	SETONE ELCNF,(EL)	;WE HAVE CONFIDENCE, SO FAR
	LOAD T1,ELNDB,(EL)	;GET POINTER TO THE NSP NODE BLOCK
	OPSTRM <AOS T2,>,NNLKC,(T1)  ;ONE MORE ACTIVE LINK FOR THIS NODE
	OPSTR <CAMLE T2,>,NNLKM,(T1) ;THIS A NEW MAX?
	STOR T2,NNLKM,(T1)	     ;YES, STORE NEW MAX

	ENDQUE EL,NSPAPQ,EL.APQ,T1

;Fill in some static fields in the "other" sublink
	SETONE ESOTH,+EL.OSL(EL)  ;SET "OTHER" FLAG IN THE "OTHER" ES
				  ;ALREADY CLEAR IN "NORMAL" SUBLINK BLK
	MOVX T1,FCM.MG		  ;ALWAYS MESSAGE FLOW CONTROL MODE
	STOR T1,ESRFL,+EL.OSL(EL) ; IN BOTH DIRECTIONS
	STOR T1,ESXFL,+EL.OSL(EL)
	MOVEI T1,1		  ;ONE FREE XMIT DRQ IN BOTH DIRECTIONS
	STOR T1,ESXLD,+EL.OSL(EL) ;DON'T TELL SESSION CONTROL ABOUT IT,
	STOR T1,ESRLD,+EL.OSL(EL) ; SINCE S/HE ALREADY ASSUMES IT
	STOR T1,ESXRD,+EL.OSL(EL) ;ARCHITECTURE GRANTS A FREE DRQ
	STOR T1,ESRRD,+EL.OSL(EL) ; TO OSL IN BOTH DIRECTIONS
  IFN FTGOL <
	STOR T1,ESGOL,+EL.OSL(EL) ;SET THE DATA REQUEST GOAL
	STOR T1,ESCGL,+EL.OSL(EL) ;NEVER USED FOR "OTHER" BUT...
  >

;Initialize some dynamic fields which should not start out zero
	MOVX T1,FCM.XX		  ;GET THE ILLEGAL FLOW CONTROL MODE
	STOR T1,ESRFL,+EL.NSL(EL) ;ASSURE WE INIT BEFORE WE USE
	STOR T1,ESXFL,+EL.NSL(EL) ; THESE VARIABLES
	STOR EL,ELCHK,(EL)	  ;STORE THE CHECK FOR FNDPID

	LOAD T1,ELNDB,(EL)	;Get pointer to node block
	LOAD T2,NNPSZ,(T1)	;Get current pipe size
	STOR T2,ESCWS,+EL.NSL(EL) ; and use that for initial window size
	STOR T2,ESCWS,+EL.OSL(EL) ; for both normal and other sublink

	RETSKP			  ;SUCCESS RETURN
	SUBTTL FNDPRT - Hash an LLA to Find a Port Block

;FNDPRT - Given an LLA and RLA, return a port block pointer in EL
;
;Call:	T1/ The LLA to look for
;	T2/ The RLA to check for consistency, zero if no RLA yet
;	CALL FNDPRT
;	  Error Return if not found or in CL or DP states
;	Normal Return,	EL holds the address of the associated port block.
;			T1 holds the state of the port, callers count on it
;Changes T1,T2,T3,T4

FNDPRT:	DMOVE T3,T1		;SAVE LLA AND RLA IN T3 & T4
	IDIV T1,NSPHTS		;DIVIDE BY HASH TABLE SIZE
	ADD T2,NSPHTB		;ADD REMAINDER TO HASH TABLE ADDRESS
	SKIPN EL,(T2)		;GET POINTER TO FIRST PORT IN BUCKET
	RET			;NO MATCH IF BUCKET WAS EMPTY
FNDPT1:	OPSTR <CAMN T3,>,ELLLA,(EL) ;IS THIS OUR LOCAL LINK ADDR?
	JRST FNDPT3		;YES, GO CHECK REMOTE LINK ADDR
FNDPT2:	LOAD EL,ELHBQ,(EL)	;POINT TO NEXT PORT IN BUCKET
	JUMPN EL,FNDPT1		;AND CHECK IT IF IT EXISTS
	RET			;END OF LIST, NO MATCH RETURN

;Here when we have matched the Local Link Address

FNDPT3:	JUMPE T4,FNDPT4		;SUCCESS IF WE DON'T KNOW RLA
	LOAD T1,ELRLA,(EL)	;CHECK RLA TOO
	JUMPE T1,FNDPT4		;SUCCESS IF WE DON'T KNOW RLA
	CAMN T4,T1		;RLA MATCH?
	JRST FNDPT4		;WE FOUND IT!

	ETRACE NSP,LLAs match but RLAs do not
	JRST FNDPT2		;NOT IT, TRY NEXT

;Here when we have found a port which matches LLA and RLA

FNDPT4:	LOAD T1,ELSTA,(EL)	;GET ITS PORT STATE
	IFNSTATE T1,<CL,DP>,RSKP ;JUMP IF SUCCESS, POINTER IN EL, STATE IN T1
	RET			;CAN'T CALL <CL,DP> STATE A REAL PORT
	SUBTTL Hash Table Routines

;INIHSH - Initialize the hash table, called from NSPINI
;
;Call:	CALL INIHSH
;	Normal Return
;
;Changes T1,T2,T3,T4

	XSWAPCD
INIHSH:	SKIPG T1,NSPHTS		;GET THE HASH TABLE SIZE
	BUG.(HLT,LLIHTS,LLINKS,SOFT,<NSPHTS not set up>,,<

Cause:	The monitor has a bad value for the hash table size.

Action:	Rebuild or patch the monitor with a positive value in NSPHTS.

>,RTN)
	CALL DNGWDZ		;GET A HASH TABLE
	  BUG.(HLT,LLIHTG,LLINKS,SOFT,<INIHSH cant get a hash table>,,<

Cause:	The routine that initializes the LLINKS link hash table failed to get
	memory for the hash table.  If the value for the hash table size is
	reasonable, this should never fail.

Action:	Check that the contents of NSPHTS is a reasonable value.

>,RTN)
	MOVEM T1,NSPHTB		;STORE ADDRESS OF TABLE
	RET			;DNGWDZ ZEROS WORDS DELIVERED

	XRESCD

;ADDHSH - Add a port block to the hash table
;
;Call:	EL/ Pointer to a port block with LLA and RLA filled in
;	CALL ADDHSH
;	  Error Return if can't get core for new hash table
;	Normal Return
;
;Changes T1,T2

ADDHSH:	LOAD T1,ELLLA,(EL)	;GET THE LOCAL LINK ADDRESS
	IDIV T1,NSPHTS		;DIVIDE BY HASH TABLE SIZE
	ADD T2,NSPHTB		;ADD REMAINDER TO TABLE ADDRESS
	MOVE T1,(T2)		;GET PTR TO CURRENT OCCUPANT
	STOR T1,ELHBQ,(EL)	;WE ARE TO BE FIRST IN THIS QUEUE
	MOVEM EL,(T2)		;POINT BUCKET TO THIS PORT BLOCK

;Now calc the length of this hash bucket's list and keep the
;max length up to date.

	MOVE T1,EL		;START WITH PORT AFTER NEW ADDITION
	MOVEI T2,1		;WE KNOW WE HAVE AT LEAST ONE
ADDHS1:	OPSTR <SKIPE T1,>,ELHBQ,(T1) ;GET PTR TO NEXT PORT IN BUCKET
	AOJA T2,ADDHS1		;TRY FOR MORE

	CAMLE T2,NSPHMX		;BIGGER THAN CURRENT MAX?
	MOVEM T2,NSPHMX		;YES, A NEW MAXIMUM
	RETSKP			;ALWAYS SUCCEED UNTIL WE LEARN
				; TO GROW A HASH TABLE (IF EVER)
;RMVHSH - Remove a port block from the hash table
;
;Call:	EL/ Pointer to a port block with LLA filled in
;	CALL RMVHSH
;	Normal Return, even if the port was not found, so what?
;
;Changes T1,T2

IF2,<IFN ELHBQ+1,<PRINTX ?ELHBQ must be a full word structure>>

RMVHSH:	LOAD T1,ELLLA,(EL)	;GET THE LOCAL LINK ADDRESS
	IDIV T1,NSPHTS		;DIVIDE LLA BY HASH TABLE SIZE
	ADD T2,NSPHTB		;SET UP FIRST PREVIOUS POINTER
	SUBI T2,EL.HBQ		;PREPARE FOR EL.HBQ OFFSET LATER
RMVHS1:	MOVE T1,T2		;REMEMBER THE PREVIOUS PTR
	LOAD T2,ELHBQ,(T2)	;LOAD PTR TO NEXT PORT IN BUCKET
	JUMPE T2,RMVHS2		;CHECK OUT THAT PORT IF ITS THERE
	CAME T2,EL		;IS THIS THE PORT?
	JRST RMVHS1		;NO, TRY NEXT ON LIST
	LOAD T2,ELHBQ,(EL)	;GET OUR PORT'S NEXT PTR
	STOR T2,ELHBQ,(T1)	;MAKE PREVIOUS BLOCK PT TO NEXT
	RET			;OUR PORT IS NOW OUT OF LIST

;Here if we did not find the port in its hash bucket

RMVHS2:	RET			;Should never happen

	SUBTTL NEWLLA - Make a New Local Link Address

;NEWLLA - Make a new local link address
;
;Call:	CALL NEWLLA
;	Normal Return with LLA in T1
;Changes T1,T2

;The main reason this is a subroutine rather than just in-line code is
;so that we can change the method of allocating local link addresses
;if we need to.

NEWLLA:	SAVEAC EL
NEWLL1:	AOS T1,NSPNLA		;INCREMENT THE "NEXT LLA"
	ANDI T1,177777		;MOD 2**16
	CAIN T1,0		;IF NEW LLA WOULD BE ZERO
	MOVEI T1,1		; MAKE IT ONE INSTEAD
	MOVEM T1,NSPNLA		;RESTORE THE CYCLED NUMBER

	CALL FNDPRT		;THIS ONE ALREADY IN USE?
	  TRNA			;NO, ALL IS OK
	JRST NEWLL1		;YES, BETTER TRY NEXT INSTEAD

	MOVE T1,NSPNLA		;RETURN NEW LLA TO CALLER IN T1
	RET
	SUBTTL NEWSGN - Make a New Message Segment Number

;NEWSGN - Make a new message segment number
;
;Call:	ES/ The Sublink Block on which to send the message
;	MB/ The Message Block to receive the new Segment number
;	CALL NEWSGN
;	Normal Return with new segment number in T1
;		and in sublink and message blocks
;Changes T1

;The main reason this is a subroutine rather than just in-line code is
;so that we can change the method of allocating message segment numbers
;if we need to.

NEWSGN:	LOAD T1,ESLMA,(ES)	;GET LAST MESSAGE NUMBER ASSIGNED
	AOS T1			;NEXT HIGHER SEGMENT NUMBER
	ANDI T1,MASK.(WID(ESLMA),35) ;RESULT IN T1 MUST BE CYCLED TOO
	STOR T1,ESLMA,(ES)	;STORE NEW 'LAST MSG NUMBER ASSIGNED'
	STOR T1,NMSGN,(MB)	;STORE SEG NUMBER IN MSG BLK
	RET			;ONLY RETURN
	SUBTTL NSPpid Control - GETPID & FNDPID

;The only reason these procedures exist is to provide a single place
;where nature of the NSPpid can be changed.  For example, we may want
;to change the NSPpid from a direct memory pointer to an LLA such as
;we send across the network.  This would be safer and more expensive.
;At present, I don't think we have to be all that safe with Session
;Control.


;GETPID - Returns the NSPpid that we send to Session Control.
;
;Call:	EL/ Pointer to the port block in question
;	CALL GETPID
;	Normal Return with NSPpid in T1
;Changes T1

GETPID:	MOVE T1,EL
	RET
;FNDPID - Returns a pointer to the port block corresponding to an NSPpid
;
;Call:	T1/ The NSPpid
;	CALL FNDPID
;	  Error Return if NSPpid not found, BUG given
;	Normal Return with port block pointer in EL
;Changes T1

FNDPID:	SKIPG EL,T1			;PTR MUST BE POSITIVE NON-ZERO
	JRST FNSPD1			;NOPE
	LOAD T1,ELSTA,(EL)		;GET PORT'S STATE
	IFSTATE T1,<CL,DP>,FNSPD1	;WE DON'T ADMIT TO CLOSED PORTS
	OPSTR <CAME EL,>,ELCHK,(EL)	;IS THE CHECK ADDRESS THERE?
FNSPD1:	  BUG.(CHK,LLIFNS,LLINKS,SOFT,<SCTL passed bad NSPpid>,<<EL,ELPTR>>,<

Cause:	Session control gave LLINKS a bad ID.  This is a coding error
	in SCLINK, or a memory manager problem.

Action:	Inspect the stack to find out how the monitor got here.
	Inspect the ELB to see if it otherwise looks like an ELB.

Data:	ELPTR - Pointer to the bad ELB

>,RTN)
	RETSKP				;OK RETURN
	SUBTTL DSTPRT - Destroy a Port

;DSTPRT - Called only from SECCHK or its children
;
;Call:	EL/ The Port Block
;	CALL DSTPRT
;	Normal Return
;Changes T1,T2,T3,T4

;Removing a port from NSPAPQ (the all ports queue) decrements the
;count of all active ports, which is held in the NSPAPQ queue header.

;We don't zero the block here so that, for debugging, it will still be
;around after it has been freed until the memory allocation routine
;actually gives it away again.

DSTPRT:	LOAD T1,ELSTA,(EL)	;GET PORT'S STATE
	IFNSTATE T1,<DP>	;IS IT IN <DP> STATE?
	  RET			; Tried to desroy non-DP port, should never
				; happen.

	TRACE NSP,Port Destroyed

	LOAD T1,ELNDB,(EL)	;GET POINTER TO NSP NODE BLOCK
	DECR NNLKC,(T1)		;DECREMENT LINK COUNT FOR THIS NODE
	CALL CHKNOD		;Check if we need to remove node blocks

	RMVQUE EL,NSPAPQ,EL.APQ,T1 ;REMOVE EL FROM NSP ALL PORTS Q
	RMVQUE EL,NSPJFQ,EL.JFQ,T1 ;REMOVE EL FROM JIFFY-SERVICE Q

	CALL RMVHSH		;REMOVE PORT IN (EL) FROM HASH TABLE

	SETZRO ELCHK,(EL)	;DESTROY THE CHECK WORD FOR FNDPID

	MOVE T1,EL		;ADDRESS OF BLOCK TO FREE
	MOVEI T2,EL.LEN		;LENGTH OF A PORT BLOCK IN WORDS
	LOAD T4,QHCNT,+NSPRPQ	;GET CURRENT LENGTH OF RESERVED PORT Q
	CAML T4,NSPRPN		;LESS THAN TARGET RESERVED PORT NUMBER?
	JRST DNFWDS		;YES, FREE THE WORDS OF THIS BLOCK
	ENDQUE EL,NSPRPQ,EL.APQ,T1 ;NO, PUT BACK ON THE RES PORT QUEUE
	RET
	SUBTTL QSRTMB - Queue a Message on a Sorted Queue

;QSRTMB - Queue a message on a sorted queue
;
;Call:	T1/ Points to the queue header, see BEGSTR QH
;	MB/ The message block pointer, as usual
;	CALL QSRTMB
;	  Error Return if message is duplicate, message is queued
;	Normal Return
;Changes T2,T3,T4

;Note that this routine only queues message blocks, no other block
;types need apply.
;
;Make the empty queue case fast, it will be the most common.
;
;The CMODxx macros compare an AC with a structure instance, but they
;compare MOD the size of the byte.  See macro definitions for an
;example.

QSRTMB:	SETZRO MBNXT,(MB)	;ASSUME THIS MSG WILL BE END OF QUEUE
	LOAD T4,QHBEG,(T1)	;GET POINTER TO BEGINNING OF QUEUE
	JUMPN T4,QSRTM1		;JUMP IF QUEUE IS NOT EMPTY

;The simple case: the queue was empty

	STOR MB,QHBEG,(T1)	;MAKE THIS MESSAGE FIRST ON QUEUE
	STOR MB,QHEND,(T1)	; AND ALSO THE LAST ON THE QUEUE

;Here on success to increment queue counter

QSRTMS:	INCRQH <(T1)>,T2	;Q HEADER TO INCREMENT, TEMPAC FOR MACRO
	RETSKP			;FINISH UP AND LEAVE
	;The sorting cases: the queue was not empty

QSRTM1:	LOAD T2,QHEND,(T1)	;MOST LIKELY IT WILL FIT AT END
	LOAD T3,NMSGN,(MB)	;GET OUR NEW MSG'S SEGMENT NUMBER
	CMODG T3,NMSGN,(T2)	;GET # OF MSG AT END OF QUEUE
	JRST QSRTM2		;NO, NEW MSG FITS BACK IN QUEUE

	;Here if the new message goes on the end of the queue.

	STOR MB,MBNXT,(T2)	;POINT OLD LAST TO NEW LAST MSG
	STOR MB,QHEND,(T1)	;POINT EO-QUEUE TO NEW LAST MSG
	JRST QSRTMS		;FINISH UP AND LEAVE

QSRTM2:	CMODLE T3,NMSGN,(T4)	;IS NEW MSG LOWER THAN FIRST?
	JRST QSRTM3		;NOPE, NOT AT THE BEGINNING

	;Here if message is to be first on queue

	CMODN T3,NMSGN,(T4)	;YES, IS THIS A DUPLICATE?
	RET			;YES, ERROR RETURN
	STOR T4,MBNXT,(MB)	;NO, PT NEW FIRST TO OLD FIRST
	STOR MB,QHBEG,(T1)	;POINT QUEUE HEADER AT NEW FIRST
	JRST QSRTMS		;FINISH UP AND LEAVE

QSRTM3:	;Here if the new message goes somewhere in the middle.
	;This should be simple rather than fast, since it won't be
	;done very often.

QSRTM4:	LOAD T2,MBNXT,(T4)	;POINT TO NEXT MSG ON QUEUE
	CMODG T3,NMSGN,(T2)	;COMPARE WITH NEXT MSG ON QUEUE
	JRST QSRTM5		;GOT THE SLOT, GO INSERT IT
	MOVE T4,T2		;SET UP NEW PREVIOUS POINTER
	JRST QSRTM4		;GO TRY NEXT MSG

QSRTM5:	CMODN T3,NMSGN,(T2)	;IS THIS A DUPLICATE MSG NUMBER?
	RET			;YES, ERROR RETURN
	STOR T2,MBNXT,(MB)	;NO, STORE FORWARD PTR IN NEW MSG
	STOR MB,MBNXT,(T4)	;STORE FORWARD PTR IN PREV MSG
	JRST QSRTMS		;FINISH UP AND LEAVE
	SUBTTL MAKHDR - Initialize the NSP MSD to build a Message Header

;MAKHDR - Initialize the NSP MSD to build a Message Header
;
;Call:	CALL MAKHDR
;	Normal Return with MS set up for DNPxBY routines
;Changes T1,T2

;This routine initializes the NSP Message Segment Descriptor to accept
;an NSP message header. It sets up MS for the DNPxBY routines that
;store bytes into DECnet-36 messages.

MAKHDR:	XMOVEI T1,NM.MSD(MB)	;GET POINTER TO THE MSD TO INITIALIZE
	CALLRET DNPINI		;INITIALIZE MS FOR DNPxBY





;INIHDR - Initialize the input MSD for DNGxBY
;
;Call:	CALL INIHDR
;	Normal Return with MS set up
;Changes T1,T2,T3,T4

INIHDR:	MOVE T1,MB		;POINT TO THE RELEVANT MESSAGE BLOCK
	CALLRET DNGINI		;INITIALIZE MS FOR DNGXBY
	SUBTTL UPDELAY - Update a Logical Link's Roundtrip Delay

;UPDELAY - Update a Logical Link's Roundtrip Delay
;
;Call:	T1/ "NORMAL" Message segment number just ACKed
;	EL/ The Port Block
;	ES/ The Sublink Block
;	CALL UPDELAY
;	Normal Return
;Changes T1,T2

;Procedure UPDELAY is called whenever we get an ACK for a "normal"
;message.
;SNDRTR fills in ELDTM and ELDSG when it sends a message and the timer
;is not running.
;Note that the message timer is not reset when we resend a message, so
;resends are included in the average round-trip time.  If this were
;not the case, we could receive an ACK immediately after resending a
;message and our resend time would then be latched into an
;unrealistically small value:  once its too small the likelyhood of
;this same phenomenon increases greatly.
;
;We put a floor under the expected delay, because if the delay gets too
;small the delay factor becomes too small to cover normal delays
;in the network, thus leading to spurious retransmissions and
;no confidence conditions.
;
;we put a roof over the expected delay, because the delay can increase
;drastically if we are re-transmitting a lot. The specific case of a
;fast line leading to a slow line will cause the middle node to drop
;many messages, and will cause the delay timer to increase beyond all
;reason.
;
;    delta_t := {system uptime} - start_time;
;    n.NN_DELAY := n.NN_DELAY + ((delta_t - n.NN_DELAY) / NSPWGT);

UPDELAY:TMNN ELDTM,(EL)		;IS THERE A TIMED MESSAGE?
	RET			;NO TIMER GOING, LEAVE NOW
	LOAD T2,ESOTH,(ES)	;WHICH SUBLINK IS THIS ACK ON?
	LOAD T3,ELDTO,(EL)	;WHICH SUBLINK IS TIMED MSG ON?
	CAME T2,T3		;IS THIS ACK ON THE TIMED SUBLINK?
	RET			;NO
	CMODGE T1,ELDSG,(EL)	;DOES THIS ACK THE TIMED MESSAGE?
	RET			;NOT YET

	CALL DNGTIM		;GET THE CURRENT TIME IN T1
	OPSTR <SUB T1,>,ELDTM,(EL) ;GET THIS MESSAGE'S ROUND-TRIP TIME
	SETZRO ELDTM,(EL)	   ;WE'RE FINISHED WITH THIS TIMER
	LOAD T4,ELNDB,(EL)	;GET POINTER TO RELEVANT NODE BLOCK
	MOVX T2,NNGDL		;HAVE WE
	TDNN T2,NN.GDL(T4)	; "GOT DELAY" YET?
	JRST UPDLY2		;NO, USE THIS TIME UNDIMINISHED
	OPSTR <SUB T1,>,NNDLY,(T4) ;GET DIFFERENCE FROM OLD ESTIMATE
	IDIV T1,NSPWGT		;DIMINISH BY WEIGHTING FACTOR
	OPSTR <ADD T1,>,NNDLY,(T4) ;GET UPDATED DELAY
UPDLY1:	CAMGE T1,NSPFLR		;RESULT LOWER THAN THE FLOOR?
	MOVE T1,NSPFLR		;YES, LOAD UP THE FLOOR INSTEAD
	CAMLE T1,NSPRUF		;RESULT GREATER THAN THE ROOF?
	MOVE T1,NSPRUF		;YES, LOAD UP THE ROOF INSTEAD
	STOR T1,NNDLY,(T4)	;THAT'S THE NEW DELAY ESTIMATE
	RET

;Here if this is the first time for this remote node
UPDLY2:	IORM T2,NN.GDL(T4)	;WE'VE "GOT DELAY" NOW, THOUGH
	JRST UPDLY1		;GO STORE UNDIMINISHED VALUE
	SUBTTL Free a Message Block

;FREMSG - Free a message block, pointer in MB
;
;Call:	MB/ Pointer to message block
;	CALL FREMSG
;	Normal Return, message block deallocated
;Changes T1,T2,T3,T4

FREMSG:	SKIPG T1,MB		;PREPARE FOR DNFMSG
	BUG.(CHK,LLIFZM,LLINKS,SOFT,<Tried to free zero msg>,,<

Cause:	FREMSG was requested to free a message.  However, the pointer
	to the message block was zero.  This is a coding error in LLINKS.

Action:	Inspect the stack to find out which routine called FREMSG.

>,RTN)
	TRASH MB,		;JUST BE CAREFUL
	CALLRET DNFMSG		;FREE THE MESSAGE BLOCK
	SUBTTL Reserved Port Management

;RPNINI - Collect some ports for the reserved port list
;
;Call:	CALL RPNINI
;	Normal Return
;
;Changes T1,T2,T3

;Called only from NSPINI at system startup time.

	XSWAPCD
RPNINI:	SAVEAC P1
	SKIPG P1,NSPRPN		;NUMBER OF RESERVED PORTS TO KEEP
	RETSKP			;ALL DONE IF NO RESERVED PORTS
RPNIN1:	MOVEI T1,EL.LEN		;LENGTH OF A PORT
	CALL DNGWZP		;GET ENOUGH WORDS FOR A PORT
	  RET			;  -no memory, should seldom happen

;Set up the reserved port block here

	ENDQUE T1,NSPRPQ,EL.APQ,T2 ;PUT IT ON THE RES PORT QUEUE
	SOJG P1,RPNIN1		;DO ALL THE PORTS REQUESTED

;Set up the reserved node block here.  The reserved node
;block is used by the reserved port blocks in case anyone tries to
;access the node block attached to a reserved port.  We cannot
;presume to use a real node block, because such a block may not
;exist, and the reason we are using a reserved port is most likely
;that there is no memory left to allocate to new blocks.

	MOVEI T1,NN.LEN		;LENGTH OF A NODE BLOCK
	CALL DNGWZP		;GET A NODE BLOCK
	  RET			;  -should seldom heppen
	MOVEM T1,NSPRND		;SAVE FOR GTRESP
	RET

	XRESCD

	SUBTTL Reserved Port Process

;RESPRC - The Reserved Port Process
;	  Called with the standard SCTL calling sequence
;
;Call:	T1/ SCBid = EL (see proc MAKPRS)
;	T2/ Function-specific arguments
;	T3/ Function Code (offset into SV.xxx table)
;	T4/ Full-word pointer to the message block
;	CALL RESPRC
;	Normal Return
;
;Changes T1,T2,T3

;A pointer to this process is put into a reserved port so that
;calls to Session Control via that port will come here.  NSIREJ
;also puts such a pointer into the port whose connect initiate
;is being rejected so that Session Control will not have to hear
;about the port after it has rejected it.

RESPRC:	SAVEAC <P1,MB,MS,ES>
	MOVE P1,T1		;ELSCB HAS (EL), SEE MAKPRS
	MOVE MB,T4		;SET UP THE MESSAGE BLOCK POINTER
	CALLRET @RSPFNT(T3)	;CALL FUNCTION AND RETURN TO NSP
;The jump table by which the reserved port process interprets
;NSP calls.

;The offsets SV.xxx into this table are defined in D36PAR.

DEFINE RP(nam),<
IFN .-RSPFNT-SV.'nam,<	PRINTX ?RSPFNT table (nam) not in same order
			PRINTX ? as SV.'nam in D36PAR.UNV
			PASS2
			END>
	IFIW <RSP'nam&777777>>


;These are the function codes for the calls to Session Control from
;NSP.

RSPFNT:	RP CCR			;CONNECT CONFIRMED CALL
	RP DIR			;DISCONNECT INITIATE RECEIVED CALL
	RP DCR			;DISCONNECT CONFIRM RECEIVED CALL
	RP OND			;OUTPUT NOT DONE CALL
	RP ODN			;OUTPUT DONE CALL
	RP SEG			;SEGMENT RECEIVED CALL
	RP DRQ			;DATA REQUEST RECEIVED CALL
	RP NCF			;NO CONFIDENCE IN PORT CALL
	RP NRS			;NO RESOURCES CALL
	RP CLS			;CLOSE COMPLETED CALL
	RP NLK			;NO LINK CALL
	RP NCM			;NO COMMUNICATION CALL
	RP NRN			;NOT IN RUN STATE
	RP CAK			;CONNECT ACK CALL
RP.MAX==.-RSPFNT-1		;THE HIGHEST RECOGNIZED ENTRY OFFSET

;Connect Initiate calls		;The Connect Initiate call does not go
;never go to a reserved		;through the vectored interface, since
;port				;it must go to a single routine
				;capable of sorting out which Session
				;Control will handle the incoming
				;message.  the connect initiate call
				;goes to the global label SCTLCI.

PURGE RP
RSPCAK:				;CONNECT ACK CALL
RSPNRN:				;NOT IN RUN STATE
RSPCCR:				;CONNECT CONFIRMED CALL
RSPSEG:				;SEGMENT RECEIVED CALL
RSPDRQ:				;DATA REQUEST RECEIVED CALL
RSPCLS:				;CLOSE COMPLETED CALL
RSPOND:				;OUTPUT NOT DONE CALL
RSPODN:				;OUTPUT DONE CALL
RSPDIR:				;DISCONNECT INITIATE RECEIVED CALL
	CALLRET FREMSG		;IGNORE THIS CALL


RSPDCR:				;DISCONNECT CONFIRM RECEIVED CALL
RSPNLK:				;NO LINK CALL
RSPNCF:				;NO CONFIDENCE IN PORT CALL
RSPNRS:				;NO RESOURCES CALL
RSPNCM:				;NO COMMUNICATION CALL
	TRACE NSP,Reserved Port Process closing port
	MOVE T1,P1		;THE NSPpid SAVED ABOVE, SEE MAKPRS
	MOVE T2,P1		;SCBid IS SAME AS NSPpid FOR RESERVED PORT
	MOVX T3,NV.CLS		;CLOSE FUNCTION
	MOVE T4,MB		;PASS MSG BLK POINTER IN T4
	CALLRET NSP		;TELL NSP TO CLOSE THE PORT

	SUBTTL	DCRORC - Decrement 'Out-in-router' count

;DCRORC - called when a message is returned from ROUTER
;
;Call:	MB/ pointer to message block
;	EL/ pointer to port block
;	CALL DCRORC
;	+1 return always
;Changes temporary ACs

DCRORC:
   IFN FTPARANOID <		;Catch "ORQ" problem
	RMVQUE MB,EL.ORQ(EL),NM.ORQ,T1 ;Take entry off "ORQ" queue
   >

	OPSTRM <SOS T1,>,ELORC,(EL) ;ONE LESS OUT-IN-ROUTER
	JUMPGE T1,RTN		;JUMP IF NO BUG
	BUG.(CHK,LLIORC,LLINKS,SOFT,<ORC should never be negative>,,<

Cause:	LLINKS has requested that a message be returned from ROUTER
	after transmission.  ROUTER just returned such a message to
	LLINKS, but the count of outstanding messages was zero.

Action:	This is a difficult problem.  If it persists, send a SPR
	and include a dump of the system.

>)
	SETZRO ELORC,(EL)	;WEAK ATTEMPT TO RECOVER
	RET			; and return

	SUBTTL BLDRCI - Build a (R)CI message

;BLDRCI - build a (R)CI message NSP header
;
;Call:	T1/ MGFCI or MGFRCI
;	MB/ Pointer to message block containing CI message
;	EL/ Port pointer
;	CALL BLDRCI
;	Return
;	Changes T1-T4

BLDRCI:	PUSH P,T1		;Save T1 over call to MAKHDR
	CALL MAKHDR		;MAKE NSPHDR MSD, BYTE PTR IN P1, COUNT IN P2
	POP P,T1		;Retrieve message type
;MSGFLG
	CALL WRTMGF		;WRITE THE MSGFLG FIELD
;DLA
	MOVEI T1,0		;DON'T YET KNOW THE DLA
	CALL DNP2BY		;WRITE TWO-BYTE FIELD
;SLA
	LOAD T1,ELLLA,(EL)	;GET THE LOCAL LINK ADDRESS
	CALL DNP2BY		;WRITE TWO-BYTE FIELD
;SERVICES
	CALL WRTSRV		;WRITE SERVICES FIELD
;INFO
	MOVE T1,NSPVRN		;GET MY VERSION CODE (0=3.2, 1=3.1)
	CALL DNPEBY		;WRITE EXTENSIBLE BYTE
;SEGSIZE
	LOAD T1,ELSIZ,(EL)	;MAX SEGMENT SIZE WE WILL ALLOW IN BYTES
	CALL DNP2BY		;WE GOT ELSIZ FROM SCTL IN OPEN CALL

;The DATA-CTL field has already been provided by Session Control.
	RET			;CI/RCI message is now built

	SUBTTL RELCIM - Release saved (R)CI message

;RELCIM - Release a saved (R)CI message
;
; Call:	EL/ port block
;	CALL RELCIM
;	Return
;Changes T1-T4

	ASSUME ELCIM,EQ,-1	;Make sure fullword
RELCIM:	OPSTR <SKIPN T1,>,ELCIM,(EL) ;Saved (R)CI message block?
	RET			; -no
	CALL DNFMSG		;-yes, return it
	SETZRO ELCIM,(EL)	;Clear pointer to it
	RET			; and return

	SUBTTL	Network management -- Dispatch

;ECLNMX is the entry point for all network management calls to LLINKS
;
; Call:
;	T1/ function code
;	T2/ NF block address
;
; Return:
;	RET	Network management error code in T1
;	RETSKP	Operation succeded

	XSWAPCD

;Define dispatch table

DEFINE NF(nam),<
	ASSUME <.-ECLFNT>,EQ,NF.'nam
	IFIW <NMX'nam&777777>
>

ECLFNT:	NF SET			;SET parameter
	NF CLR			;CLEAR parameter
	NF RED			;READ parameter
	NF COU			;SHOW counters
	NF SZC			;SHOW and ZERO counters
	NF RET			;Return list of entity ids
	NF A2N			;Map node address to node name
	NF N2A			;Map node name to node address
	NF CET			;Check entity id
	NF CKL			;Check loopback name

	ASSUME <.-ECLFNT-1>,EQ,NF.MAX ;Verify highest recognized entry

;Network management parameter table
NODPAR:

PARAMETER(^D600,PANST!PANCL,,,,JFCL,<LOAD T2,NNLKC,(P2)>,JFCL,<Active links>)
PARAMETER(^D601,PANST!PANCL,,,,JFCL,<LOAD T2,NNDLY,(P2)>,JFCL,<Delay>)
PARAMETER(^D700,PANST!PANCL,,,,JFCL,<CALL RED700>,JFCL,<NSP version>)
PARAMETER(^D710,PANST!PANCL,,,,JFCL,<MOVEI T2,^D65535>,JFCL,<Maximum links allowed>)
PARAMETER(^D720,,,1,%NSDLY,<MOVEM T2,NSPDLY>,<MOVE T2,NSPDLY>,<MOVEM T2,NSPDLY>,<Delay>)
PARAMETER(^D721,,,1,%NSWGT,<MOVEM T2,NSPWGT>,<MOVE T2,NSPWGT>,<MOVEM T2,NSPWGT>,<Delay weight>)
PARAMETER(^D722,,,1,%NSINA,<MOVEM T2,NSPINA>,<MOVE T2,NSPINA>,<MOVEM T2,NSPINA>,<Inactivity timer>)
PARAMETER(^D723,,,1,%NSRTH,<MOVEM T2,NSPRTH>,<MOVE T2,NSPRTH>,<MOVEM T2,NSPRTH>,<Retransmit factor>)

NRNPAR==.-NODPAR

;Network management counter table

;Remote and executor node counters
NODCTR:

COUNTER(^D0,^D16,<CALL [SAVEAC <T2>	;;Save T2 so we can divide into it
			CALL DNGTIM	;;Get current time in T1
			OPSTR <SUB T1,>,NNTLZ,(P2) ;;Subtract time when zeroed
			IDIVI T1,TIMBAS	;;Make into seconds
			RET ]>,<CALL [CALL DNGTIM ;;Get current time
				      STOR T1,NNTLZ,(P2) ;;Store as when zeroed
				      RET ]>,,<Seconds since last zeroed>)
COUNTER(^D600,^D32,<LOAD T1,NNRBC,(P2)>,<SETZRO NNRBC,(P2)>,,<User byt rcvd>)
COUNTER(^D601,^D32,<LOAD T1,NNXBC,(P2)>,<SETZRO NNXBC,(P2)>,,<User byt xmit>)
COUNTER(^D602,^D32,<LOAD T1,NNRMC,(P2)>,<SETZRO NNRMC,(P2)>,,<User msgs rcvd>)
COUNTER(^D603,^D32,<LOAD T1,NNXMC,(P2)>,<SETZRO NNXMC,(P2)>,,<User msgs xmit>)
COUNTER(^D608,^D32,<LOAD T1,NNTBR,(P2)>,<SETZRO NNTBR,(P2)>,,<Total byt rcvd>)
COUNTER(^D609,^D32,<LOAD T1,NNTBX,(P2)>,<SETZRO NNTBX,(P2)>,,<Total byt xmit>)
COUNTER(^D610,^D32,<LOAD T1,NNTMR,(P2)>,<SETZRO NNTMR,(P2)>,,<Total msgs rcvd>)
COUNTER(^D611,^D32,<LOAD T1,NNTMX,(P2)>,<SETZRO NNTMX,(P2)>,,<Total msgs xmit>)
COUNTER(^D620,^D16,<LOAD T1,NNRCC,(P2)>,<SETZRO NNRCC,(P2)>,,<Connects rcvd>)
COUNTER(^D621,^D16,<LOAD T1,NNXCC,(P2)>,<SETZRO NNXCC,(P2)>,,<Connects xmit>)
COUNTER(^D630,^D16,<LOAD T1,NNTMC,(P2)>,<SETZRO NNTMC,(P2)>,,<Resp timeout>)
COUNTER(^D640,^D16,<LOAD T1,NNCRC,(P2)>,<SETZRO NNCRC,(P2)>,,<Rcv conn res er>)
	NRCCTR==.-NODCTR
;Executor node only counters
COUNTER(^D700,^D16,<LOAD T1,NNLKM,(P2)>,<SETZRO NNLKM,(P2)>,,<Max activ links>)
	EXCCTR==.-NODCTR

	INTERN ECLNMX		;Declare network management entry point
	XSWAPCD
ECLNMX:	CAIL T1,NF.SET		;Range check
	CAILE T1,NF.MAX		; the function code
	RNMXER (NF.UFO)		;"Unrecognized function or option"
	SAVEAC <P1,P2>		;Save preserved ACs
	MOVE P1,T2		;Put NF block address in P1
	JRST @ECLFNT(T1)	; and branch to processing routine

;Unsupported functions come here
NMXRET:				;Return list of items
NMXA2N:				;Map node address to node name
NMXN2A:				;Map node name to address
NMXCET:				;Check entity ID
NMXCKL:				;Check loopback node name
	RNMXER (NF.MPE)		; Return "management program error"

	SUBTTL	Network management -- SET parameter
	SUBTTL	Network management -- READ parameter
	SUBTTL	Network management -- CLEAR parameter

;NMXSET - SET parameter
;NMXRED - READ parameter
;NMXCLR - CLEAR parameter
;
; Call:
;	T1/ function code
;	P1/ NF block
;	NFEID/ node address
;	NFETY/ .NTNOD
;	NFBFF/ clear
;	NFBUF/ parameter value

NMXSET:
NMXRED:
NMXCLR:
	MOVE P2,T1		;Save function code for a while
	LOAD T1,NFETY,(P1)	;Verify entity type
	CAIE T1,.NTNOD		; is NODE
	RNMXER (NF.URC)		;  -no, unrecognized component
	LOAD T1,NFEID,(P1)	;Get entity ID
	CALL SRHNOD		;Locate node
	  RNMXND		;  -not there, return OK but no data
	MOVE T3,P2		;Function code to T3
	MOVE P2,T1		;Node block pointer to P2
	XMOVEI T1,NODPAR	;Load node table
	MOVEI T2,NRNPAR		; and its length
	CALLRET NTPARM		;  and let D36COM do most of the job

;RED700 - NODE parameter 700 (NSP version)
RED700:	SETZ T2,		;Clear return value
	MOVEI T3,.NSVER		;Get NSP version
	CAIE T3,VER3.2		;Is it 3.2?
	IFSKP.			; -yes
	  MOVEI T3,3		;  3.
	  MOVEI T4,2		;    2
	ELSE.			; -no,
	  CAIE T3,VER4.0	;  Is it 4.0?
	  IFSKP.		;   -yes,
	    MOVEI T3,4		;    4.
	    SETZ T4,		;      0
	  ELSE.			;   -no,
	    SETZB T3,T4		;    then unknown
	  ENDIF.
	ENDIF.
	STOR T3,VNVER,+T2	;Store version #
	STOR T4,VNECO,+T2	; and ECO #
	RNMXOK			;  and return success

	SUBTTL	Network management -- SHOW counters
	SUBTTL	Network management -- SHOW and ZERO counters

;NMXCOU - SHOW counters
;NMXSZC - SHOW and ZERO counters
;
; Call:
;	T1/ function code
;	P1/ NF block
;	NFEID/ entity ID (16-bit node address)
;	NFETY/ .NTNOD
;	NFBFF/ set
;	NFBUF/ pointer to buffer

NMXCOU:
NMXSZC:	MOVE P2,T1		;Save function code for a while
	LOAD T1,NFETY,(P1)	;Get entity type
	CAIE T1,.NTNOD		;NODE?
	RNMXER (NF.URC)		; -no, unrecognized component
	LOAD T1,NFEID,(P1)	;Get entity ID
	CALL SRHNOD		;Locate node
	  RNMXND		;  -not there, return OK but no data
	MOVE T3,P2		;Function code to T3
	MOVE P2,T1		;Node block pointer to P2
	XMOVEI T1,NODCTR	;Load node counter table
	LOAD T2,NFEID,(P1)	;Get node ID again
	OPSTR <CAME T2,>,IBADR,+IBBLK ;Is it executor?
	IFNSK.			; -no, its a remote node
	  MOVEI T2,NRCCTR	;  then get length without executor part
	ELSE.			; -yes, its executor
	  MOVEI T2,EXCCTR	;  so use full length
	ENDIF.
	CALLRET NTCTRS		;  and let D36COM do the job

	SUBTTL Node data base -- Find an NSP node block
;FNDNOD - Find an NMX Node Block, if fail, make a new one
;
;Call:	T1/ Network Node Address of node
;	CALL	FNDNOD
;	  Error Return if no resources
;	Normal Return with pointer to node block in T1
;Changes T1,T2,T3,T4

;Note that the new node block goes on the end of the queue of node
;blocks.  This causes the blocks which we hear about soonest to be at
;the beginning of the queue.  These are the nodes we are most likely
;to converse with, so having them at the beginning of the queue is a
;convenience for DDT if nothing else.

;This routine is called from LLINKS and is therefore resident
	XRESCD

FNDNOD: SAVEAC P1
	MOVE P1,T1		;SAVE THE ARG
	CALL SRHNOD		;FIRST, SEARCH FOR AN EXISTING ONE
	  JRST MAKND1		;CAN'T FIND IT, TRY TO MAKE ONE
	RETSKP			;FOUND IT, SUCCESS RETURN

MAKND1:	MOVEI T1,NN.LEN		;LENGTH OF A NMX NODE BLOCK
	CALL DNGWDZ		;ZERO SO MANY ZEROED WORDS
	  RET			;CAN'T, ERROR RETURN
	STOR P1,NNNOD,(T1)	;STORE NODE ID IN NEW NODE BLOCK
	MOVE P1,T1		;SAVE ADDRESS OF NODE BLOCK
	CALL DNGTIM		;GET A TIMESTAMP
	STOR T1,NNSLZ,(P1)	;STORE AS LAST TIME BLOCK WAS ZEROED

	ENDQUE P1,NMXNDQ,NN.NXT,T2

;Some initialization for NSP

	MOVEI T2,TIMBAS		;MACRO/LINK CAN'T MULTIPLY EXTERNAL RIGHT
	IMULI T2,5		;ESTIMATE 5 SECOND FOR INITIAL DELAY
	STOR T2,NNDLY,(P1)	;ZERO IS NOT GOOD FOR CONNECT CONFIRM!
				;SEE UPDELAY ABOUT THIS
	MOVE T1,NSPPSZ		;Initial pipesize = minimum 'maximum' pipe
				; size (* be optimistic *)
	STOR T1,NNPSZ,(P1)	;Store it
	CALL CHKNOD		;Check if we need to prune the node block queue
	MOVE T1,P1		;RETURN PTR TO BLOCK IN T1
	RETSKP

	SUBTTL Node data base -- Find an existing NSP node block
;SRHNOD - Search for an existing NMX Node Block
;
;Call:	T1/ Network Node Id
;	CALL	SRHNOD
;	  Error Return if not found
;	Normal Return with pointer to it in T1
;Changes T1,T2,T3
;This must save T5 and T6 if it evers changes them, since SCMUUO calls it.

;Still resident...

IFN FTOPS10,<
	INTERNAL SRHNOD
>; END IFN FTOPS10

SRHNOD:	MOVE T2,T1		;COPY NODE ID TO T2
	LOAD T1,QHBEG,+NMXNDQ	;GET HEAD OF NMX NODES LIST
FNDND1:	JUMPE T1,RTN		;LIST IS EMPTY, ERROR RETURN
	OPSTR <CAMN T2,>,NNNOD,(T1) ;IS THIS THE NODE WE'RE LOOKING FOR?
	RETSKP			;YES, SUCCESS RET WITH PTR IN T1
	LOAD T1,QPNXT,+NN.NXT(T1) ;NO, GET THE NEXT ENTRY IN LIST
	JRST FNDND1		;AND TRY IT

	SUBTTL Node data base -- Check if we need to delete node blocks

;CHKNOD - check if we need to prune the node block queue
;
; Call:	CALL CHKNOD
;
; Return:
;	+1 always

CHKNOD:	LOAD T1,QHCNT,+NMXNDQ	;Get current queue length
	CAMGE T1,NNDMAX		;On or over quota?
	RET			; -no, no need to do anything
	SETOM NODFLG		;-yes, flag that we need to prune queue
	RET

;End of resident code

	XRESCD			;End of network management

	SUBTTL Node data base -- Release node blocks

;RELNOD - release node blocks if necessary
;
; RELNOD runs as part of the CHKR cycle, i.e. in process context approximately
; every 10 seconds. It will check if the node block queue needs to be pruned.
;
; Call:	
;	CALL RELNOD
;
; Returns:
;	+1 always

	XSWAPCD
RELNOD:	SKIPN NODFLG		;Do we need to do anything?
	RET			; -no, return immediately
	SETZM NODFLG		;No longer needed

;Here to do processing
	SAVEAC <T6,P1,MB>
IFN FTOPS20,<	CSKED			;Critical section and NOINT>
	XMOVEI T6,RE1NOD	;Address of processing routine
	CALL NSPLCW		;Get the NSP interlock and process request
IFN FTOPS20,<	ECSKED			;Exit critical section and OKINT>
	RET

;RE1NOD does the actual processing.
; Come here with NSP interlock
RE1NOD:	LOAD P1,QHBEG,+NMXNDQ	;Point to first block on queue
	DO.			;LOOP over all node blocks on queue
	  LOAD T1,QHCNT,+NMXNDQ	;  Get current count of nodes queued
	  CAMGE T1,NNDMAX	;  On or over quota?
	  RET			;   -no, just return
	  SKIPE P1		;  Null pointer?
	  IFSKP.		;   -yes, we have scanned the entire queue
	    SETOM NODFLG	;    without pruning enough so set NODFLG
	    RET			;    so we run another pass next time
	  ENDIF.
	  OPSTR <SKIPE>,NNLKC,(P1) ;Any active links to this node?
	  IFSKP.		;  -no, interesting!
	    LOAD T1,NNNOD,(P1) ;     Get node ID
	    OPSTR <CAMN T1,>,IBADR,+IBBLK ;Is it executor?
	    ANSKP.		;    -no, go ahead and delete it
	      CALL GENE32	;       Generate event 3.2
	      RMVQUE P1,NMXNDQ,NN.NXT,T2 ;Remove it from the queue
	      MOVE T1,P1	;       Get pointer for DNFWDS
	      LOAD P1,QPNXT,+NN.NXT(P1) ;Pick up pointer to next node
	      CALL DNFWDS	;	Now OK to delete
	      LOOP.		;       Go to next node
	  ENDIF.
	  LOAD P1,QPNXT,+NN.NXT(P1) ; Go to next node block
	  LOOP.		;  Loop back to do next node block
	ENDDO.
;Can never get here

;GENE32 - generate an event 3.2 "Database reused"
;
; Call:	P1/ address of node block
;	CALL GENE32
;
; Returns:
;	+1 always

GENE32:	MOVE MB,P1		;Event logger wants node block ptr in MB
	EVENT (NSP,DAT,<Database reused>,)
	RET

;NEVT.2 - Database reused. The parameters for this event are all
; the NSP node counters that are flushed from the monitor.
;
; Call:
;	MB/ ptr to node block
;	P2/ byte pointer to data area
;	CALL NEVT.2
;
; Return:
;	RET always, with T5/ # of data bytes written

NEVT.2:
;** Note **
; This TRVAR must match the TRVAR in NTMAN at the .NTMAN jsys entry point
	TRVAR <<NMXVAR,NX.LST>,<NFWBLK,NF.LST>>	;Set up trvar
;** End note **

;Clear the NMXVAR block
	SETZM NMXVAR
	HRRI T2,1+NMXVAR
	HRLI T2,NMXVAR
	BLT T2,NX.LST-1+NMXVAR

;Clear the NFWBLK block
	SETZM NFWBLK
	HRRI T2,1+NFWBLK
	HRLI T2,NFWBLK
	BLT T2,NF.LST-1+NFWBLK

;Set up the NMXVAR block to read and format the counters for the
; node that we have decided to purge
	ASSUME NX.MCX,EQ,NX.WUS	;Make sure the two flags are in the same word
	SETONE <NXMCX,NXWUS>,+NMXVAR	;Set 'monitor context' and 'write user'
	ASSUME .NTNOD,EQ,0
	SETZRO NXENT,+NMXVAR	;NODE entity
	LOAD T1,NNNOD,(MB)	;Get the node id (node address)
	STOR T1,NXNUM,+NMXVAR
	MOVEI T1,.NTSHO		;Function is 'show'
	STOR T1,NXFNC,+NMXVAR
	XMOVEI T2,NX.DAT+NMXVAR	;Get pointer to byte pointer/count pair
	MOVEI T1,EVTDLN		;Get max event data length
	STOR T1,BPBYT,(T2)	;Store that
	STOR P2,BPBPT,(T2)	; and byte pointer
	CALL PRSCOU		;Call into NTMAN
	IFNSK.			; -fail return
	  SETZ T5,		;  Indicate no bytes written
	  RET			;   and return
	ENDIF.
	XMOVEI T2,NX.DAT+NMXVAR	;Get pointer to byte pointer/count pair again
	MOVEI T5,EVTDLN		;Get max # of bytes
	OPSTR <SUB T5,>,BPBYT,(T2) ; and subtract # of bytes left
	RET			;     gives # of bytes written.

	XRESCD

	SUBTTL NSPJB0 - NSP periodic checks

;NSPJB0 - called from DCNJB0 (that is called from CHKR) every 10 seconds.
;
;Call:	CALL NSPJB0
;	Always +1 return

	INTERNAL NSPJB0
	XSWAPCD
NSPJB0:
IFN FTOPS20,<
	CALL LNKBRK		;Generate 'link broken' messages if necessary
>; END IFN FTOPS20
	CALLRET RELNOD		;Release node blocks if needed

IFN FTOPS20,<
	SUBTTL	Link broke message - Job 0 code

;LNKBRK is called as part of the CHKR cycle. It generates link broken
; message to the operator if needed

	BRKMLN==<^D240+4>/5	;# of words in message

LNKBRK:	SKIPN BRKFLG		;Any broken links?
	RET			; -no, all done
	SETZM BRKFLG		;Clear the flag
	SAVEAC <P1,P2,P3>

;The operator message is built into OPRMSG. MSGSND is a flag that is set
; non-zero if we find any nodes to report. This is to exclude the possibility
; of sending an empty report
	TRVAR <<OPRMSG,BRKMLN>,MSGSND,<QUEARG,^D10>>
	SETZM MSGSND		;Initialize

;P1 will contain a byte pointer to the message string
	XMOVEI P1,OPRMSG	;Get address of it
	TXO P1,.P0736		; and make a byte pointer of it

	MOVE T1,[POINT 7,[ASCIZ /
Communication failure to the following nodes:
/]]
	CALL MOVSTR		;Copy the string to the message buffer

;Scan the link broken table to find nodes to report. By scanning backwards
; we dont have any interlocking problems with INSBRK.

	XMOVEI P2,<BRKTAB+BRKLEN-1> ;Point to last entry in table
	MOVEI P3,BRKLEN		; and load count
	DO.			;LOOP over link broken table
	  SETZ T1,		;  Prepare for EXCH
	  EXCH T1,(P2)		;  Zero table entry, and get contents
	  SKIPN T1		;  Is there an entry?
	  IFSKP.		;  - yes,
	    CALL SCTA2N		;    Find node and convert it to sixbit
	  ANSKP.		;  - yes, node was there
	    SETOM MSGSND	;    Flag that we should send a message
	    MOVE T2,P1		;    Byte pointer to T2
	    XCALL (MSEC1,GETSIX) ;     and call GETSIX to write sixbit string
	    MOVE P1,T2		;    Get byte pointer back
	    MOVEI T1,.CHTAB	;    Get a TAB
	    IDPB T1,P1		;     and output it
	  ENDIF.
	  SOS P2		;  Back up pointer one step
	  SOJG P3,TOP.		;  Loop back if more entries to do
	ENDDO.
	SKIPN MSGSND		;Did we find any nodes?
	RET			; -no, just return
	SKIPN MORFLG		;Did we lose any nodes before?
	IFSKP.			; -yes,
	  MOVE T1,[POINT 7,[ASCIZ /and more../]]
	  CALL MOVSTR		;  Output that
	  SETZM MORFLG		;  Clear flag
	ENDIF.
	MOVE T1,[POINT 7,[ASCIZ /
/]]				;And a CR
	CALL MOVSTR		; is always appreciated
	SETZ T1,		;End with a NULL byte
	IDPB T1,P1		; so put it

;The message is now built in OPRMSG. Set up the argument for the QUEUE
; jsys and go..

;The QUEUE argument block has the following layout
;
; Word	0	QU%NRS+.QUWTO	No response, function is WTOPR
;	1	0		No response pointer
;	2	Length+.QBTYP	Privileged WTOPR
;	3	Address of ASCIZ string (header)
;	4	Length+.QBMSG	Unprivileged WTOPR
;	5	Address of ASCIZ string (message)
;	6	QA%IMM+.QBDTY	Display type
;	7	.QBDLK		DECnet 'link broken' display type
;	8	QA%IMM+.QBDFG	Formatting flags
;	9	QU%SJI!QU%NFO	Suppress job info and no formatting

	MOVX T1,QU%NRS!.QUWTO
	MOVEM T1,.QUFNC+QUEARG	;Store in argument block
	SETZM .QURSP+QUEARG
	MOVX T1,FLD(5,QA%LEN)!FLD(.QBTYP,QA%TYP)
	MOVEM T1,2+QUEARG	;*Numeric offsets*
	XMOVEI T1,[ASCIZ /DECnet link message/]
	MOVEM T1,3+QUEARG
	MOVX T1,FLD(BRKMLN,QA%LEN)!FLD(.QBMSG,QA%TYP)
	MOVEM T1,4+QUEARG	;*Numeric offsets*
	XMOVEI T1,OPRMSG	;Get address of ASCIZ string
	MOVEM T1,5+QUEARG
	MOVX T1,QA%IMM!FLD(1,QA%LEN)!FLD(.QBDTY,QA%TYP)
	MOVEM T1,6+QUEARG
	MOVX T1,.QBDLK		;DECnet 'link broken' display type
	MOVEM T1,7+QUEARG
	MOVX T1,QA%IMM!FLD(1,QA%LEN)!FLD(.QBDFG,QA%TYP)
	MOVEM T1,^D8+QUEARG
	MOVX T1,QU%SJI!QU%NFO	;Formatting flags
	MOVEM T1,^D9+QUEARG

	MOVEI T1,^D10		;10 words in argument block
	XMOVEI T2,QUEARG	; and get address
	QUEUE%			;Shoot!
	ERJMP .+1		;Ignore errors

	RET

;MOVSTR - move a string to the message buffer
MOVSTR:	DO.			;LOOP
	  ILDB T2,T1		; Get a byte
	  JUMPE T2,RTN		;  Return if null byte
	  IDPB T2,P1		; Put a byte
	  LOOP.			; And go to next
	ENDDO.
;Can never get here
> ;END IFN FTOPS20

	SUBTTL	Link broke message - Add a node to table

;INSBRK is called when a link broken event is detected. It adds
; the node to the 'link broken table'.
;
; Call:
;	EL set up
;	CALL INSBRK
;
; Returns:
;	RET always

	XRESCD			;Can be called from any context
INSBRK:	LOAD T1,ELNDB,(EL)	;Get pointer to node block
	TMNE NNMSG,(T1)		;Did we already send a message for this node?
	RET			; -yes, nothing to do
	SETONE NNMSG,(T1)	;No, but we are, so set the flag
	LOAD T1,ELNNM,(EL)	;Get the node number
	MOVE T2,[-BRKLEN,,BRKTAB] ;Get AOBJN ptr to the table
	DO.			;LOOP over all entries
	  SKIPE (T2)		;  Is this entry used?
	  IFSKP.		;  -no,
	    MOVEM T1,(T2)	;   save the node number
	    SETOM BRKFLG	;   and set the flag so its processed
	    RET			;   and all is done
	  ENDIF.		;
	;This test will prevent race conditions - normally this test is
	; superfluous since we have already tested the MSG flag but if the
	; remote came up between the test and here, then we need the test.
	  CAMN T1,(T2)		;  This node present in table already?
	  RET			;   -yes, no need to do any more
	  AOBJN T2,TOP.		;  Loop back to do next node
	ENDDO.
	SETOM MORFLG		;Table is full, remember overflow
	SETOM BRKFLG		; and make code take a pass
	RET

	SUBTTL EVTINI - Initialize event logger interface

;EVTINI - Initialize event logger interface
;
;Call:	CALL EVTINI
;	Normal return
;Changes T1 and T2

	XSWAPCD
EVTINI:	MOVX T1,EV.GEC		;Get EC (Event Communication) block
	CALL NMXEVT
	  RET			;  -no memory, should seldom happen
	MOVEM T1,NSPECP		;Save pointer to EC block
	MOVX T2,2		;No more than 2 events on queue for NSP
	STOR T2,ECMAX,(T1)
	RET

	XRESCD

	SUBTTL NSPEVT - Queue an Event to Network Management

;NSPEVT - Queue an Event to Network Management
;
;Call:
;IFN FTUSERMODE, T1/ event class, T2/ event type
;IFE FTUSERMODE, T1/ type
;	MB/ The Message Block
;	MS/ Pointer to IN.MSD, the input MSD
;	CALL NSPEVT		;SKIPPABLE
;	Normal Return
;EVENT Macro Preserves T1,T2,T3,T4,T5,T6,P1,P2 ***

;Called only with the EVENT macro, (q.v., above)
;This routine is called by SCLINK as well as LLINKS, so we can't
;count on the NSP interlock in this routine.

;SCLINK and LLINKS in TOPS-20 6.1 will generate the following two events:
;	3.0	Invalid message
;	3.1	Invalid flow control
;	3.2	Database reused

	EVTDLN==^D120		;MAX LENGTH OF NSP HEADER DATA

	INTERN NSPEVT
	XRESCD
NSPEVT:
				;EVENT MACRO SAVES NECESSARY ACS
	DMOVE T5,T1		;Save event class and type
	STOR T5,FACCL,+T2	;Event class and type goes in T2
	STOR T6,FACTY,+T2
	MOVX T1,EV.FIL		;Function code 'filter event'
	CALL NMXEVT		;Apply global filters in NTMAN
	  RET			;  -throw away, no more to do

;Check that the event communication pointer is non-zero
	SKIPN NSPECP		;Is it?
	RET			;  -no, we probably got a BUGCHK at init time
				;  (or someone trashed the word...)

;Now allocate the event block
	MOVX T1,NE.LEN+<<EVTDLN+3>/4> ;EVENT ARG BLK + SPACE FOR DATA
	CALL DNGWDZ		;GET AN ARG BLK FOR NETWORK MGMT
	  RET			;  -couldnt get memory, just go away
	MOVE P1,T1		;POINTER TO ARG BLOCK

;Fill in the arg block we just got for Network Management
	MOVE T1,NSPECP		;The EC pointer
	STOR T1,NEECP,(P1)
	STOR T5,NECCL,(P1)	;Store the event class
	STOR T6,NECTY,(P1)	; and type
	SETONE NEETP,(P1)	;Assume 'no entity'
	SETZRO NEEID,(P1)	; and therefore 'no entity id'
	CAIN T5,.NCNSP		;Is it 'NSP layer'
	CAIE T6,EVTDAT		; and 'Database reused'
	IFSKP.			; -yes,
	  ASSUME .NTNOD,EQ,0	;  Verify 'node entity' is zero
	  SETZRO NEETP,(P1)	;   so we can use SETZRO!
	  LOAD T1,NNNOD,(MB)	;  Get node ID
	  STOR T1,NEEID,(P1)	;   and store as entity ID
	ENDIF.

	;T5 will be count of bytes written, P2 byte ptr
	; Move event class and type to T1/T2
	DMOVE T1,T5		; ...
	SETZ T5,		;No bytes written yet
	XMOVEI P2,NE.LEN(P1)	;Make byte pointer
	TXO P2,.P0836		;Global, 8-bit bytes, point to 1st byte
	
	;Select processing routine depending on event class and type
	CAIE T1,.NCNSP		;NSP event?
	IFSKP.
	  SKIPL T2		;  Verify event is between 0
	  CAILE T2,EVTDAT	;    and EVTDAT
	  JRST BADEVT		;      -no, bad event
	  CALL @[ IFIW <NEVT.0&777777> ;  Type 0 - Invalid message header
		  IFIW <NEVT.1&777777> ;	     1 - Invalid flow control
		  IFIW <NEVT.2&777777>](T2) ;  2 - Database reused
	ELSE.			;Not NSP event
	  CAIN T1,.NCLCG	;  then is it LCG event?
	  SKIPE T2		;    and event type 0
	  JRST BADEVT		;      -no to one or both, go complain
	  CALL LEVT.0		;     -ok, go process event
	ENDIF.

	STOR T5,NEDLN,(P1)	;Store count of data bytes
	XMOVEI T1,NE.LEN(P1)	;Get pointer to data area
	STOR T1,NEDAT,(P1)	; and store it in NE block

	MOVX T1,EV.LOG		;Function code "log an event"
	MOVE T2,P1		;NE block pointer
	CALL NMXEVT		;TELL NMX ABOUT THE EVENT
	 JFCL			;Always skip returns
	RET			;All done, NTMAN will deallocate block

;BADEVT - bad event class and/or type - bugcheck
BADEVT:	BUG.(CHK,LLITNE,LLINKS,SOFT,<Unknown event at NSPEVT>,<<T1,EVC>,<T2,EVT>>,<

Cause:	The caller of the NSPEVT routine supplied a bad event class and type.
	NSPEVT may be called by SCLINK as well as by LLINKS. The caller's
	address is on the stack.

Data:	EVC - Event class
	EVT - Event type
>)
	MOVE T1,P1		;Get address of NE block that was allocated
	CALLRET DNFWDS		;Deallocate and return

;NEVT.0 - Invalid message event
NEVT.0:	CALL NEP.0		;Do parameter 0 (MESSAGE)
	CALLRET NEP.2		;Do parameter 2 (SOURCE NODE)

;NEVT.1 - Invalid flow control event
NEVT.1:	CALL NEVT.0		;Do MESSAGE and SOURCE NODE
	CALLRET NEP.1		;Do parameter 1 (CURRENT FLOW REQUEST COUNT)

;NEP.0 - do parameter 0 (MESSAGE)
;
; I assume that the incoming message is at least 6 bytes long, i.e. that it
; contains MSGFLG DSTADDR SRCADDR and at least one more byte. I also assume
; implicitly that the MSGFLG field is exactly one byte long. This is to 
; simplify the generation of the event. If any added functionality is required
; in future, you have my blessing....

NEP.0:	STKVAR <COUNT>
	LOAD T1,NMMK1,(MB)	;Get NSP's mark 1 for this input MSD
	CALL DNGPOS		;Go there
	XMOVEI T1,IN.MSD(MB)	;Pointer to MSD we are interested in
	CALL DNSLNG		;How many bytes are there?
	SUBI T1,5		;Subtract 5 [MSGFLG DSTADDR SRCADDR]
	JUMPLE T1,RTN		;If nothing more than that, then return
	CAILE T1,10		;Max 10 bytes more
	MOVEI T1,10		; ..
	MOVEM T1,COUNT		;Count of 'data bytes'
	SETZ T1,		;Parameter 0
	CALL EVP2BT		;Output..
	MOVEI T1,304		;CM-4
	CALL EVPBYT
	MOVEI T1,041		;H-1
	CALL EVPBYT
	CALL DNGEBY		;Get extensible byte (really only one byte)
	  JFCL			;  Shouldnt happen
	CALL EVPBYT
	MOVEI T1,002		;DU-2
	CALL EVPBYT
	CALL DNG2BY		;Get DSTADDR
	  JFCL
	CALL EVP2BT
	MOVEI T1,002		;DU-2
	CALL EVPBYT
	CALL DNG2BY		;Get SRCADDR
	  JFCL
	CALL EVP2BT
	MOVEI T1,040		;HI-10
	CALL EVPBYT
	MOVE T1,COUNT		;Get count of bytes to do
	CALL EVPBYT
	DO.
	  CALL DNG1BY		;  Get a byte
	    JFCL
	  CALL EVPBYT		;   and output it
	  SOSLE COUNT		;  Any more bytes to do?
	  LOOP.			;   -yes, go do them
	ENDDO.
	RET			;All done, return

;NEP.1 - do parameter 1 (CURRENT FLOW REQUEST COUNT)
NEP.1:	MOVEI T1,1		;Parameter 1
	CALL EVP2BT		;Output it
	MOVEI T1,021		;DS-1
	CALL EVPBYT		;Output it
	LOAD T1,ESRFL,+EL.NSL(EL) ;Get flow request count
	CALLRET EVPBYT		;Output it and return

;NEP.2 - do parameter 2 (SOURCE NODE)
NEP.2:	MOVEI T1,2		;Parameter 2
	CALL EVP2BT		;Output it
	MOVEI T1,301		;CM-1
	CALL EVPBYT
	MOVEI T1,002		;DU-2
	CALL EVPBYT
	LOAD T1,MBSRC,(MB)	;Get source node
	CALLRET EVP2BT		;Put 2 bytes

;EVP2BT - write two bytes in pdp-11 order
EVP2BT:	CALL EVPBYT
	LSH T1,-^D8
	CALLRET EVPBYT

;EVPBYT - write a byte, and update count
EVPBYT:	IDPB T1,P2		;Write the byte
	AOJA T5,RTN		; and up the count and return

IFN FTTRACE,<

;EVTTRC -  Event Tracer, called only by EVENT macro
;	   Called from LLINKS and from SCLINK
;
;Call:	T1/ Extended Address of caller
;	T2/ Pointer to ASCIZ string
;	T3/ Event Type
;	CALL EVTTRC
;	Normal Return
;
;Uses T1,T2,T3,T4

EVTTRC::SAVEAC <P1,P2>
	DMOVE P1,T1		;SAVE T1 AND T2
	PUSH P,T3		;SAVE EVENT TYPE
	XMOVEI T1,[ASCIZ /NSP event: /]
	CALLTRACE .TSTRG##,ETRNSP
	POP P,T1		;EVENT TYPE
	SKIPL T1
	CAILE T1,3		;LEGAL EVENT TYPE?
	MOVEI T1,3		;NO, GET EVENT BUG TYPE
	MOVE T1,[[ASCIZ /Illegal message format/]
		 [ASCIZ /Illegal flow control/]
		 [ASCIZ /NSP data base changed/]
		 [ASCIZ /Unknown event type/]](T1)
	CALLTRACE .TSTRG##,ETRNSP
	XMOVEI T1,[ASCIZ / at PC /]
	CALLTRACE .TSTRG##,ETRNSP
	MOVE T1,P1		;CALLER'S TOP LITERAL LEVEL PC
	CALLTRACE .TOCTW##,ETRNSP
	CALLTRACE .TCRLF##,ETRNSP
	SKIPN P2		;POINTER TO TEXT
	RET			;LEAVE NOW IF NO TEXT TO TYPE
	CALLTRACE .TTABC##,ETRNSP ;TYPE A TAB
	MOVE T1,P2		;GET PTR TO ASCIZ STRING
	CALLTRACE .TSTRG##,ETRNSP
	CALLTRACE .TCRLF##,ETRNSP
	RET

>;END OF IFE FTTRACE
	SUBTTL Trace-to-TTY Facility

IFN FTTRACE,<			;AVAILABLE ONLY IN USER MODE, OF COURSE

;TRCSND/TRCRCV - Trace a message sent or received
;
;Call:	T1/ The MSGFLG field of the message
;	MB/ Pointer to the Message Block
;	CALL TRCSND/TRCRCV
;	Normal Return
;Preserves all ACs, except CX

DEFINE TRCSAV,<SAVEAC <T1,T2,T3,T4,T5,T6,P1,P2,MS>>

TRCRCV:	MOVE CX,S.TRACE##	;GET THE TRACE FLAGS WORD
	TXNN CX,TRCNSP		;ARE WE TRACING NSP?
	RET			;NO, SAVE A LITTLE OVERHEAD
	TRCSAV			;SAVE THEM ALL JUST TO BE SURE FOR LATER
	XMOVEI T2,[ASCIZ \Received\]
	CALL TRCMHD		;PUT OUT COMMON TRACE HEADER
	CALLTRACE .TRBRK##	;RIGHT SQUARE BRACKET
	CALLTRACE .TCRLF##
	LOAD T1,NMCNT,(MB)	;NUMBER OF TIMES WE'VE SENT MSG
	SOJG T1,RTN		;ONLY REPORT ON FIRST SEND
				;HERE WE USE THE 'MARK' COUNT
	LOAD T5,MDBYT,(MS)	;NUMBER OF BYTES NOW LEFT TO READ
	LOAD T4,NMMK1,(MB)	;BYTES LEFT WHEN MARK WAS TAKEN
	SUB T5,T4		;NEGATIVE THE DIFFERENCE
	LOAD T1,MDPTR,(MS)	;BYTE POINTER TO CURRENT POSITION
	OPSTR <ADJBP T5,>,MDPTR,(MS) ;BACK UP THE BYTE PTR TO BEG OF HDR
	LOAD T6,MDALA,(MS) 	;GET ALLOC ADDR FOR INDEX
	CALL TRCMGT		;T4,T5 AND T6 ARE ARGS TO TRCMGT
	CALLRET TRCMGX


TRCMSG:	MOVE CX,S.TRACE##	;GET THE TRACE FLAGS WORD
	TXNN CX,TRCNSP		;ARE WE TRACING NSP?
	RET			;NO, SAVE A LITTLE OVERHEAD
	TRCSAV			;SAVE THEM ALL JUST TO BE SURE FOR LATER
	CALL TRCMHD		;PUT OUT COMMON TRACE HEADER
	MOVEI T1,[ASCIZ \, # \]
	CALLTRACE .TSTRG##
	LOAD T1,NMSGN,(MB)	;GET THE MESSAGE NUMBER
	CALLTRACE .TDECW##	;TYPE IT IN DECIMAL
	CALLTRACE .TRBRK##	;RIGHT SQUARE BRACKET
	CALLTRACE .TCRLF##
	LOAD T1,NMCNT,(MB)	;NUMBER OF TIMES WE'VE SENT MSG
	SOJG T1,RTN		;ONLY REPORT ON FIRST SEND
	LOAD T4,MDBYT,+NM.MSD(MB) ;NUMBER OF BYTES INVOLVED
	LOAD T5,MDAUX,+NM.MSD(MB) ;BYTE POINTER TO NSP HEADER
	LOAD T6,MDALA,+NM.MSD(MB) ;GET ALLOC ADDR FOR INDEX
	CALL TRCMGT
	CALL TRCMGU
	CALLRET TRCMGX
;Here to put out the common trace header

TRCMHD:	MOVE P1,T1
	PUSH P,T2
	PTRACE NSP		;PUT OUT A TRACE PREFIX WITH LLA
	POP P,T1		;RECEIVED, SENT, TROLL ATE, ETC.
	CALLTRACE .TSTRG##
	XMOVEI T1,[ASCIZ / "Normal"/]
	TMNE MBOTH,(MB)
	XMOVEI T1,[ASCIZ / "Other"/]
	CALLTRACE .TSTRG##
	XMOVEI T1,[ASCIZ \ NSP message of type \]
	CALLTRACE .TSTRG##
	LSH P1,-2		;THE LOW-ORDER 2 BITS ARE ALWAYS ZERO
	CAILE P1,RCVMAX		;IS IT A LEGAL MESSAGE TYPE?
	XMOVEI P1,ILLMGT	;NO, FAKE UP AN ERROR MESSAGE
	XMOVEI T1,@MGTYNM(P1)	;ASCIZ NAME OF THE MESSAGE TYPE
	CALLTRACE .TSTRG##
	RET
;Here to trace appropriate MSD for both send and receive

;The following has knowlege of MSD secrets to which
; only D36COM should be privy.

TRCMGT:	PTRACE NSP		;PUT OUT A TRACE PREFIX
	XMOVEI T1,[ASCIZ /NSP Hdr:/]
	CALLTRACE .TSTRG##
	CALLRET TRCMGB		;OUTPUT BYTE STRING

;Here to trace UD.MSD for output messages only

TRCMGU:	XMOVEI T1,[ASCIZ /]
/]
	CALLTRACE .TSTRG##
	PTRACE NSP		;PUT OUT A TRACE PREFIX
	XMOVEI T1,[ASCIZ /User Data:/]
	CALLTRACE .TSTRG##
	LOAD T4,MDBYT,+UD.MSD(MB) ;NUMBER OF BYTES INVOLVED
	LOAD T5,MDAUX,+UD.MSD(MB) ;BYTE POINTER TO BEG OF DATA
	LOAD T6,MDALA,+UD.MSD(MB) ;PICK UP ALLOCATED ADDR FOR INDEX
	CALLRET TRCMGB		;OUTPUT BYTE STRING

;Here to finish off the trace

TRCMGX:	CALLTRACE .TRBRK##	;RIGHT ANGLE BRACKET
	CALLTRACE .TCRLF##
	RET

;Subr to output byte strings for trace routines
;T6 is loaded with the index for the byte pointer in T5

TRCMXT:	DEC 500			;ONLY TYPE THIS NUMBER OF BYTES

TRCMGB:	SAVEAC <P1,P2>
	SKIPG P1,T4		;SAVE FOR LATER
	RET			;RETURN NOW IF NO USER DATA
	CAMLE T4,TRCMXT		;LET'S LIMIT THE TYPOUT
	MOVE  T4,TRCMXT
	MOVE P2,T4		;NUMBER OF BYTES WE'LL TYPE OUT
TRCMU1:	CALLTRACE .TSPACE##
	ILDB T1,T5
	CALLTRACE .TOCTW##	;OUTPUT BYTE IN OCTAL
	SOJG P2,TRCMU1
	MOVEI T1,[ASCIZ / .../]
	CAMLE P1,TRCMXT		;DID WE TRUNCATE?
	CALLTRACE .TSTRG##	;YES, TELL WATCHER
	RET

>;END OF IFN FTTRACE
	SUBTTL End of Program

IFN FTOPS20,TNXEND

IFN FTOPS10,<
	RESDT
NSPLOW::!
	XRESCD
>; END IFN FTOPS10
	END