Google
 

Trailing-Edge - PDP-10 Archives - BB-X116A-BB_1984 - llinks.mac
There are 21 other files named llinks.mac in the archive. Click here to see a list.
;DNET:LLINKS.MAC[10,36,MON,NEW], 02-Nov-1983 12:39:56, Edit by TARL
;MCO 11035 - Don't send link service until we have received an ACK for CC.
;DNET:LLINKS.MAC[10,36,MON,NEW], 22-Sep-1983 21:02:46, Edit by TARL
;MCO 10971 - Grand Re-merge of the -10/-20 sources
;TITLE	LLINKS - Network Services Layer of Phase III DECnet V042
	SUBTTL W. G. Nichols

	SEARCH D36PAR,SCPAR,MACSYM

;THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY BE ONLY USED
; OR COPIED IN ACCORDANCE WITH THE TERMS OF SUCH LICENSE.
;
IFN FTOPS10,<
.CPYRT<
COPYRIGHT (C) 1984 BY DIGITAL EQUIPMENT CORPORATION, MAYNARD, MASS.
>
> ;END IFN FTOPS10


	ENTRY NSP,NSPRTR,NSPINI,NSPJIF	;The entry points,
	ENTRY NSPCGT,NSPCGR		; see procedures for calling sequence

	SALL

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

	FTTROLL==0		;GIVE A NAME TO TROLL CODE

IFN FTOPS10,<
	TITLE LLINKS - Network Services Layer of Phase III DECnet
	SEARCH F,S
	>
IFN FTOPS20,<
	SEARCH PROLOG
	TTITLE (LLINKS,,< - Network Services Layer of Phase III DECnet>)
	>

	D36SYM			;Fix up problems with MACSYM in DECnet-36

	$RELOC			;Set up code and data segments according
	$HIGH			; 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. Resident Storage Allocation. . . . . . . . . . . . . .   15
;   7. NSP's Entry Vector Table . . . . . . . . . . . . . . .   17
;   8. NSPRCV - Receive a Message from Router . . . . . . . .   18
;   9. NSPRTR - The Entry Point from Router . . . . . . . . .   20
;  10. NSPINI - Initialization Entry Point. . . . . . . . . .   21
;  11. NSP Interlock Handlers
;       11.1.   NSPLCQ - Queue on Failure . . . . . . . . . .   22
;       11.2.   NSPLCW - Wait on Failure. . . . . . . . . . .   23
;       11.3.   NSPLCF - Flags set for Failure. . . . . . . .   24
;  12. Session Control Calls
;       12.1.   NSP - The Entry Point . . . . . . . . . . . .   26
;       12.2.   NSPOPN - Open a Link. . . . . . . . . . . . .   27
;       12.3.   NSPGOL - Set New Data Request Goals . . . . .   29
;       12.4.   NSPACT - Enter Active . . . . . . . . . . . .   30
;       12.5.   NSPACC - Accept Connect . . . . . . . . . . .   31
;       12.6.   NSPSEG - Send Data. . . . . . . . . . . . . .   33
;       12.7.   NSPDRQ - Data Request Call. . . . . . . . . .   35
;       12.8.   NSPREJ - Reject a Connection. . . . . . . . .   36
;       12.9.   NSPDSC - Synch Disconnect . . . . . . . . . .   38
;       12.10.  NSPABO - Abort Link . . . . . . . . . . . . .   39
;       12.11.  NSPCLS - Close Port . . . . . . . . . . . . .   40
;  13. CLSABO - Send a free "Abort by Object" . . . . . . . .   41
;  14. PROCXQ - Process the Transmit Queue. . . . . . . . . .   42
;  15. SCNIRS - Tell Session Control Port is Not in Run State   45
;  16. SNDRTR - Send a Message to Router. . . . . . . . . . .   46
;  17. ROUTER Calls
;       17.1.   NSPRCV - Receive a Message from Router. . . .   48
;       17.2.   NSPODN/NSPOND - Output Done/Not Done. . . . .   49
;       17.3.   NSPRTS - Return a Message to Sender . . . . .   50
;  18. Message Receivers
;       18.1.   RCVPRC - Preliminary Processing . . . . . . .   51
;       18.2.   PRCACK - Process Received ACKNUM Field. . . .   53
;       18.3.   Receive a Data Segment. . . . . . . . . . . .   55
;            18.3.1.     Check Length of Q. . . . . . . . . .   58
;       18.4.   PROCRQ - Process the Receive Queue. . . . . .   59
;       18.5.   RCVLKS - Receive a Link Service Message . . .   61
;       18.6.   RCVACK and RCVNOP - Little Ones . . . . . . .   66
;       18.7.   RCVCA - Receive a Connect ACK Message . . . .   67
;       18.8.   RCVCI - Receive a Connect Initiate Request. .   68
;       18.9.   RCVCC - Receive a Connect Confirm Message . .   70
;       18.10.  RCVDI - Disconnect Initiate Message . . . . .   72
;       18.11.  RCVDC - Disconnect Confirm Message. . . . . .   74
;  19. Clock-Driven Routines. . . . . . . . . . . . . . . . .   76
;  20. CLCXDQ - Calculate Transmit Data Requests. . . . . . .   81
;  21. NSPRJF - Request Jiffy Service . . . . . . . . . . . .   84
;  22. SECCHK - Once-a-Second Checks. . . . . . . . . . . . .   85
;  23. Memory Manager Calls
;       23.1.   NSPCGT - We're Congested. . . . . . . . . . .   91
;       23.2.   NSPCGR - Congestion is Relieved . . . . . . .   92
;  24. NSIRLV - Congestion-Relieved Processor . . . . . . . .   93
;  25. PESFLO - Put Link in Pessimistic Flow Control. . . . .   94
;  26. SCLOSE - Start the Close Process . . . . . . . . . . .   96
;  27. SENDRQ - Send a Link Service Message . . . . . . . . .  103
;  28. SACKMG - Get a Message Block and send ACK message if can  105
;  29. SNDACK - Check a sublink to see if it needs an ACK sent 106
;  30. SENDDI - Send a DI Message . . . . . . . . . . . . . .  107
;  31. SNDCAK - Send a Connect ACK Message. . . . . . . . . .  108
;  32. SNDDSC - Send a Disconnect Complete Message. . . . . .  109
;  33. MGACKD - A Message has been ACKed. . . . . . . . . . .  110
;  34. SENDNL - Send a No Link Message. . . . . . . . . . . .  111
;  35. PTIRUN - Put a Port in Run State . . . . . . . . . . .  113
;  36. MAKPRT - Make a New Port Block . . . . . . . . . . . .  114
;  37. GTRESP - Get a Reserved Port . . . . . . . . . . . . .  115
;  38. MAKPRS - Make this Port Reserved . . . . . . . . . . .  116
;  39. SETPRT - Make a New Port Block . . . . . . . . . . . .  117
;  40. FNDPRT - Hash an LLA to Find a Port Block. . . . . . .  118
;  41. Hash Table Routines. . . . . . . . . . . . . . . . . .  119
;  42. NEWLLA - Make a New Local Link Address . . . . . . . .  122
;  43. NEWSGN - Make a New Message Segment Number . . . . . .  123
;  44. NSPpid Control - GETPID & FNDPID . . . . . . . . . . .  124
;  45. DSTPRT - Destroy a Port. . . . . . . . . . . . . . . .  126
;  46. QSRTMB - Queue a Message on a Sorted Queue . . . . . .  127
;  47. MAKHDR - Initialize the NSP MSD to build a Message Header 129
;  48. UPDELAY - Update a Logical Link's Roundtrip Delay. . .  130
;  49. Free a Message Block . . . . . . . . . . . . . . . . .  131
;  50. Reserved Port Management . . . . . . . . . . . . . . .  132
;  51. Reserved Port Process. . . . . . . . . . . . . . . . .  133
;  52. NSPEVT - Queue an Event to Network Management. . . . .  136
;  53. Trace-to-TTY Facility. . . . . . . . . . . . . . . . .  139
;  54. End of Program . . . . . . . . . . . . . . . . . . . .  142
	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

;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
;
;
	EXTERN RTRADR		;MY NODE NUMBER
;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

;	T1/ Network Node Address of node
	EXTERN	FNDNOD
;	  Error Return if no resources
;	Normal Return with pointer to node block in T1

;FNDNOD will try to find an existing node block, if it can't it
;will make one.


;	T1/ Ptr to arg block with structure NE in it
	EXTERN NMXEVT		;EVENT CATCHER
;	Normal Return
;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,0		;0=3.2, 1=3.1, 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
	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 Resident Storage Allocation

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

	INTERNAL NSPVER		;NSP VERSION NUMBER
	INTERNAL NSPECO		;NSP VENDOR ECO NUMBER
	INTERNAL NSPUEC		;NSP USER ECO NUMBER
	INTERNAL NSPDLY		;DELAY FACTOR
	INTERNAL NSPWGT		;DELAY WEIGHT
	INTERNAL NSPINA		;INACTIVITY TIMER (SECONDS)
	INTERNAL NSPRTH		;RE-TRANSMIT THRESHOLD
	INTERNAL NSPAPQ		;ALL PORTS QUEUE, FOR NMX TO SEE QHMAX
;See previous page for list of INTERNAL symbols.

	$LOW		;IFN FTOPS20, THIS IS DEFINED IN D36PAR

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

NSPVRN:	EXP VER3.2	;NSP PROTOCOL VERSION NUMBER (AS SEEN IN CI MSGS)
NSPVER:	DEC 3		;NSP PROTOCOL VERSION NUMBER (AS SEEN BY USERS)
NSPECO:	DEC 2		;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)

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)

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

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

IFN FTOPS20,<
NSPLKO: BLOCK 1 	;SERIAL NUMBER OF CPU WHICH OWNS NSP LOCK
NSPLOK:	DEC -1		;NSPLCx'S SEMAPHORE, -1 IS FREE
>
	$HIGH		;IFN FTOPS20, THIS IS DEFINED IN D36PAR
	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>		;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.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

NSPRTR:	SAVEAC <P1,P2,MB,MS,FREE1> ;FREE2 NOT USED IN RTR,
	SKIPN NSPIFG		;NSP INITIALIZED?
	BUG.(CHK,LLIXNN,LLINKS,SOFT,<NSP not yet initialized>,,<

Cause:	This BUG is not documented yet.

>,RTN)

	CAIL T3,NV.RVF		;CHECK AGAINST 1ST RTR VECTOR OFFSET
	CAILE T3,NV.RVL		; AND LAST ROUTER VECTOR OFFSET
	BUG.(CHK,LLIXVO,LLINKS,SOFT,<Illegal Router call>,,<

Cause:	This BUG is not documented yet.

>,RTN)

	SKIPN MB,T4		;NSP EXPECTS MESSAGE BLOCK PTR IN MB
	BUG.(CHK,LLIXZM,LLINKS,SOFT,<NSP called with no message block>,,<

Cause:	This BUG is not documented yet.

>,RTN)
	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 NSPINI - Initialization Entry Point

;NSPINI - This is one of the few entry points to NSP
;
;Call:	T1/ Node address of this NSP
;	T2/ ignored
;	T3/ RT%PH2, see story below
;	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 BUG.s and the
;calls will be ignored.
;
;RT%PH2 is a flag passed to exactly one NSP in the system.  NSP
;ignores the flag except to pass it on to Router.  Router uses
;to flag to know which NSP (if there is more than one) gets the phase II
;messages, which have no destination node number attached to them.

NSPINI:	SAVEAC <P1,P2>
	MOVE P1,T1		;SAVE NODE ADDRESS FOR ROUTER LAYER
	MOVE P2,T3		;SEND T3 THROUGH UNCHANGED

;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
	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.

	XMOVEI T2,NSPRTR	;ADDRESS RTR IS TO CALL NSP AT
	MOVE T1,P1		;RESTORE NODE ADDRESS FOR ROUTER
	MOVE T3,P2		;RECOVER THE PHASE II FLAG PASSED TO US
	CALL RTRINI		;LET ROUTER VALIDATE THE NODE NUMBER
	  RET			;PROPOGATE THE ERROR RETURN
	RETSKP			;SUCCESS RETURN
	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

;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
IFN FTOPS20,CONSO PI,176B27	;AT INTERRUPT LEVEL?
	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

;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

NSPLWB:	SKIPL NSPLOK		;IS NSP LOCK FREE YET?
	JRST (T4)		;NO, SLEEP ON
	JRST 1(T4)		;YES, WAKE UP FORK

NSPLW2:	BUG.(CHK,LLIBWK,LLINKS,SOFT,<SCTNSF call from sched w/o lock>,
<<-2(P),CALLER>,<INSKED,INSKED>,<NSKED,NSKED>>
,<

Cause:	The DECnet entry point SCTNSF has been called from schedular
	level when the Session Control interlock was locked.

Action:	All schedular level routines which call SCTNSF should first
	check SCTLOK.  If SCTLOK is not -1, then the caller should
	wait for the next schedular cycle before calling SCTNSF.
>)
	  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             ;1B0 MEANS JIFFY REQUEST
	IFIW NSICGT             ;1B1 MEANS CONGESTION-DETECTED
	IFIW NSIRLV             ;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

NSP::	SAVEAC <P1,P2,MS,MB,FREE1,FREE2>

	SKIPN NSPIFG		;NSP INITIALIZED?
	BUG.(CHK,LLINNI,LLINKS,SOFT,<NSP not yet initialized>,,<

Cause:	NSP (LLINKS) will reject all messages received from either
	Session Control or Router until DECnet initialization is
	complete.

>,RTN)

	CAIL T3,0		;NEGATIVE OFFSET ILLEGAL
	CAILE T3,NV.MAX		;LEGAL OFFSET?
	BUG.(CHK,LLIIVO,LLINKS,SOFT,<Illegal call vector offset>,,<

Cause:	This BUG is not documented yet.

>,RTN)

	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?
	BUG.(CHK,LLIOAL,LLINKS,SOFT,<OPEN arg blk wrong length>,,<

Cause:	This BUG is not documented yet.

>,DNFWDS)
	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 [SETONE MBAR1,(MB) ;RETURN -1 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
	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:	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
	CALL MAKHDR		;MAKE NSPHDR MSD, BYTE PTR IN P1, COUNT IN P2
;MSGFLG
	MOVX T1,MGFCI		;GET "CI" MSGFLG CODE
	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.

	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
IFE FTPH4,<
	;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
	STOR T1,ELDTM,(EL)	;REMEMBER TIME FOR UPDELAY
	SETZRO ELDSG,(EL)	;CI MESSAGE IS SEGMENT ZERO
	SETZRO ELDTO,(EL)	; ON THE 'NORMAL' SUBLINK
	MOVX T1, ST%NRS ! ST%NAK ! ST%RQR ! ST%NTR
>;END IFE FTPH4
IFN FTPH4,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?
	BUG.(CHK,LLIAAL,LLINKS,SOFT,<Arg blk to NSPACC wrong length>,,<

Cause:	This BUG is not documented yet.

>,NSIAC1)
	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
	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
	TXC T1,MBBOM ! MBEOM	;IF EITHER
	TXCE T1,MBBOM! MBEOM	; EOM OR BOM IS OFF
	BUG.(CHK,LLIILI,LLINKS,SOFT,<Interrupt message must not be segmented>,,<

Cause:	This BUG is not documented yet.

>)
	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>,,<

Cause:	This BUG is not documented yet.

>)
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 BYTES SENT TO NODE

;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
WRTAK1:
IFE FTPH4,RET
IFN FTPH4,<			;THIS IS PHASE IV STUFF
	LOAD T1,ELVER,(EL)	;GET REMOTE NSP VERSION
	CAIE 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
	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 ACKNUM FIELD
>;END IFN FTPH4


;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
	  BUG.(CHK,LLIXM2,LLINKS,SOFT,<Duplicate msg queued for xmit>,,<

Cause:	This BUG is not documented yet.

>,FREMSG)
	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:	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		;NO FLOW CONTROL
	   IFIW PROCXS		;SEGMENT FLOW CONTROL
	   IFIW PROCXM		;MESSAGE FLOW CONTROL
	   IFIW PROCXI		;ILLEGAL FLOW CONTROL

;The No Flow Control case

PROCXN:	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
	STOR T1,ESXLD,(ES)	;XLD AND XRD ALWAYS IN SYNCH IN SEG MODE
	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>,,<

Cause:	This BUG is not documented yet.

>,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
	  BUG.(CHK,LLIXR2,LLINKS,SOFT,<Duplicate msg requeued for xmit>,,<

Cause:	This BUG is not documented yet.

>,FREMSG)
	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

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
	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)
	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

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

	MOVE T1,RTRADR		;GET MY NODE NUMBER
	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 STORED

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

	LOAD T1,ELNDB,(EL)	;GET PTR TO NODE BLOCK FOR THIS LINK
	INCR ELPKS,(EL)		;INCREMENT THE COUNT OF MESSAGES SENT
	INCR NNXMC,(T1)		;UPDATE COUNT OF MESSAGES SENT TO NODE

	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
	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
	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
	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
	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(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(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

;It is OK to use the port block pointer in the message block, because
;even if Session Control did try to close the port while the message
;block was languishing in Router, NSP will not actually close the
;port until all expected messages are returned from Router.

NSPODN:	JSP T6,NSPLCQ		;QUEUE UP THE REQUEST

NSIODN:	LOAD EL,NMPRT,(MB)	;SEE BLOCK COMMENT ABOVE
	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?
	BUG.(CHK,LLIODN,LLINKS,SOFT,<NSIODN got message with NMACK=0>,,<

Cause:	This BUG is not documented yet.

>,FREMSG)

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

Cause:	This BUG is not documented yet.

>)
	SETZRO ELORC,(EL)	;WEAK ATTEMPT TO RECOVER
NSIOD2:
	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
	  BUG.(CHK,LLIAK2,LLINKS,SOFT,<Duplicate msg put on ACK queue>,,<

Cause:	This BUG is not documented yet.

>,FREMSG)
	RET
	SUBTTL ROUTER Calls -- NSPRTS - Return a Message to Sender

;NSPRTS - Called by Router to return a message to sender
;
;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 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 or by a
;route-through Router 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.

;We don't have to decrement the number of messages outstanding in
;Router, because we didn't count this one when we sent it.

NSPRTS:	JSP T6,NSPLCQ		;QUEUE UP THE REQUEST

NSIRTS:	CALL INIHDR		;WE STILL HAVE THE PORT, SET UP TO READ MSG
;MSGFLG
	CALL DNGEBY		;GET AN EXTENSIBLE BYTE
	  EVENT(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(MSG,No DLA in RTS,FREMSG)
	MOVE P1,T1
;SLA
	CALL DNG2BY		;GET REMOTE LINK ADDRESS IN T1
	  EVENT(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:	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
	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(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(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
	LOAD T1,ELNDB,(EL)	;GET PTR TO NODE BLOCK FOR THIS LINK
	INCR ELPKR,(EL)		;INCREMENT THE NUMBER OF MESSAGES RECIEVED
	INCR NNRMC,(T1)		;UPDATE COUNT OF MESSAGES FROM NODE

	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(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
IFE FTPH4,JRST RCVPR2		;GO CHECK SEGNUM FIELD
IFN FTPH4,<			;PHASE IV CODE
	LOAD T1,ELVER,(EL)	;GET REMOTE NSP VERSION
	CAIE 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
>;END IFN FTPH4

RCVPR0:	TXNE P2,RT%ACK		;OK TO SKIP THE ACKNUM FIELD?
	EVENT(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(MSG,No predicted SEGNUM,FREMSG) ;BAD MESSAGE EVENT TYPE
RCVPR3:	STOR T1,NMSGN,(MB)	;STORE SEGMENT NUMBER IN MESSAGE BLOCK
	RETSKP			;SUCCESS RETURN
	SUBTTL Message Receivers -- PRCACK - Process Received ACKNUM Field

;PRCACK - Process the ACKNUM field of an incoming message
;
;Call:	T1/ The ACKNUM field
;	EL/ The Port Block
;	ES/ The Sublink Block
;	CALL PRCACK, only if ACKNUM 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
IFE FTPH4,<EVENT(MSG,Bad ACKNUM byte,RTN)> ;NO, BAD ACKNUM FIELD
IFN FTPH4,<			;PHASE IV CODE
	LOAD T4,ELVER,(EL)	;NO, GET VERSION # OF REMOTE NSP
	CAIN T4,VER4.0		;IS IT VERSION 4.0?
	CAILE P2,AK$CNK		;YES, IS IT A CROSS SUB-CHN ACK OR NAK?
	EVENT(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
>;END IFN FTPH4

;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
	TRCRET NSP,Ignoring stale ACK
	CMODLE P1,ESLMA,(ES)	;IF .GT. LAST MSG ASSIGNED (SENT)
	ETRCRET NSP,Ignoring early ACK
	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 (probably, but not necessarily from a Phase II
;NSP) we zero the NMTIM field in all the remaining unACKed msgs for
;this sublink.  Note that CHKRSN will not retransmit a msg to a phase
;II node unless its NMTIM field has been so zeroed.

	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
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>,,<

Cause:	We have just found a duplicate interrupt message on the
	unacked interrupt receive queue.  We should never be
	able to get this, for we should never get out of the
	NSP interlock with anything on this receive queue.

Cure:	Either the interrupt flow control screwed up and we sent
	more than one data request or the remote node sent an
	interrupt message without a data request.  Find out which.

>,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
	LOAD T1,ESGOL,(ES)	;GET CURRENT GOAL FOR LINK
	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,<
	TMNE ESGOL,(ES)		;IF NOT ALREADY PESSIMIZED, TELL WATCHER
	ETRACE NSP,<Busting overly optimistic link>
>;END OF IFN FTTRACE
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
	CALLRET PESFLO		;YES, 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,(ES)	; AND TELL CHKDRQ THAT 'OFF' HAS CHANGED
	CALL NSPRJF		;GET JIFFY SERVICE TO SEND LINK SRV MSG
PRCRQ1:	LOAD T1,ESGOL,(ES)	;GET DATA REQUEST GOAL
	LOAD T4,ESRFL,(ES)	;GET RECEIVE FLOW CONTROL TYPE
	JRST @.+1(T4)		;DISPATCH
	   IFIW PRCRQN		;NO FLOW CONTROL
	   IFIW PRCRQG		;SEGMENT FLOW CONTROL
	   IFIW PRCRQM		;MESSAGE FLOW CONTROL
	   IFIW PRCRQN		;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
	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.

	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, NO NAK NEEDED
	SETONE <ESNAK,ESACK>,(ES) ;YES, MISSING A MSG, 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)	;SEND AN ACK
	CALL NSPRJF		; AT THE NEXT JIFFY
	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

;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		;NO FLOW CONTROL
	   IFIW PRCRSS		;SEGMENT FLOW CONTROL
	   IFIW PRCRSM		;MESSAGE FLOW CONTROL
	   IFIW PRCRS6		;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>,,<

Cause:	This BUG is not documented yet.

>,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
	CALL SACKMG		;SEND ACK IF CAN, NSPRJF IF CAN'T

	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(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(MSG,No FCVAL field,FREMSG)
	LOAD T2,ESXFL,(ES)	;GET TRANSMIT FLOW CONTROL TYPE
	JRST @.+1(T2)		;CASE OF FLOW CONTROL TYPE
	   IFIW LKFCVN		;NO FLOW CONTROL, IGNORE THE VAL FIELD
	   IFIW LKFCVS		;SEGMENT FLOW CONTROL
	   IFIW LKFCVM		;MESSAGE FLOW CONTROL
	   IFIW LKSILG		;RESERVED FLOW CONTROL TYPE
;Here for no flow control

LKFCVN:	JUMPE T1,LKFCV2		;ASSURE FCVAL FIELD IS ZERO FOR NO FLOW CTL
	CALLRET LKSILG		;ELSE ILLEGAL FLOW CONTROL EVENT

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

;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.

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:	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

;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(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"
	CAIE T1,LS.MOF		;ARE WE?
	MOVEI T3,0		;NO WE'RE TURNING IT ON AGAIN
	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
	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(MSG,No DLA in CI,FREMSG)
	JUMPN T1,[EVENT(MSG,Non-zero DLA in CI,FREMSG)]
	STOR T1,NMLLA,(MB)	;SHOULD ALREADY BE ZERO, BUT...
;SLA
	CALL DNG2BY
	  EVENT(MSG,No SLA in CI,FREMSG)
	STOR T1,NMRLA,(MB)	;STORE THE REMOTE LINK ADDRESS
IFN FTPH4,<
	CALL CHKDCI		;(T1)DUPLICATE RECEIVED CI MSG? (smashes EL)
	  RET			;YES, IT HAS BEEN DEALT WITH
>;END IFN FTPH4
	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
	LOAD T1,ELNDB,(EL)	;GET POINTER TO NSP NODE BLOCK
	INCR NNRCC,(T1)		;INCR NUMBER OF RECEIVED CONNECT INITS
	INCR NNRMC,(T1)		;UPDATE COUNT OF MESSAGES FROM NODE
	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

IFN FTPH4,<

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

>;END IFN FTPH4
	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>

;DLA
	CALL DNG2BY
	  EVENT(MSG,No DLA in CC,FREMSG)
	STOR T1,NMLLA,(MB)	;LOCAL LINK ADDRESS
	MOVE P1,T1
;SLA
	CALL DNG2BY
	  EVENT(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
	LOAD T1,ELNDB,(EL)	;GET PTR TO NODE BLOCK FOR THIS LINK
	INCR NNRMC,(T1)		;UPDATE COUNT OF MESSAGES FROM NODE
	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(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(FLO,Reserved value in PROCCI,RTN) ;ILLEGAL FLOW CONTROL
	TXNE T1,SVFL1		;ASSURE THAT HIGH-ORDER ALL ZERO
	EVENT(MSG,PROCCI found foul,RTN) ;ILLEGAL MESSAGE
	ANDI T1,SVFL2		;ASSURE THAT LOW-ORDER IS MAGIC NUMBER
	CAIE T1,SV$FL2		;IS IT?
	EVENT(MSG,PROCCI found foul,RTN) ;ILLEGAL MESSAGE
;VERSION
	CALL DNGEBY		;GET EXTENSIBLE BYTE
	  EVENT(MSG,No VERSION field,FREMSG)
	CAIE T1,VER3.1		;IS IT A PHASE II NSP?
	MOVX T1,VER3.2		;NO, PHASE III OR MORE, TREAT AS PHASE III

;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
	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(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

	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(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 CLRRCQ		;CLEAR OUT THE RECEIVE QUEUES
	CALL DNG2BY		;GET REASON CODE FROM MESSAGE
	  EVENT(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,CD>
	  EVENT(MSG,DC recvd in bad state,FREMSG)

	LOAD T1,ELSCB,(EL)	;FIRST ARGUMENT FOR SESSION CONTROL
	MOVE T2,P1		;REASON CODE
	MOVX T3,SV.DCR		;SESSION CONTROL FUNCTION CODE
	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
	NEWSTATE CN		;GO INTO CLOSED NOTIFICATION STATE
	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(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


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		;LEAVE IF NO SHORT INT REQUEST
	TMNE QHBEG,+NSPJFQ	;TIME FOR SHORT INT SERVICE, ANY DEMAND?
	JRST NSPJF1		;YES
NSPJF0:	TMNE QHBEG,+NSPITQ	;NO, ANY TRANSACTIONS TO PROCESS?
	JRST NSPJF2		;YES, GET NSPULK TO PROCESS THEM NOW
	MOVE T1,NSPJSI		;NO, GET NSP JIFFY SHORT INTERVAL VALUE
	MOVEM T1,NSPJSC		;RESET THE SHORT INTERVAL COUNTER
	RET			;LEAVE NOW

NSPJF1:	SETONE LKJIF,+NSPLKF	;REQUEST A NSIJIF RUN
NSPJF2:				;NSIJIF WILL UPDATE NSPJSC WHEN ITS DONE
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.

NSIJIF:

;Here to process after a short interval (typically a jiffy)

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

	SKIPLE NSPJLC		;HAS A LONG INTERVAL ELAPSED?
	JRST NSIJFX		;NO

;Here to process after a long interval (typically a second)

	CALL SECCHK		;DO "ONCE-A-SECOND" PROCESSING
	MOVE T1,NSPJLI		;GET LENGTH OF A LONG INTERVAL
	MOVEM T1,NSPJLC		;RESET THE LONG INTERVAL COUNTER

;Common NSIJIF exit

NSIJFX:	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	;NO FLOW CONTROL
		IFIW CLCXD1	;SEGMENT FLOW CONTROL
		IFIW CLCXD1	;MESSAGE FLOW CONTROL
		IFIW CLCXDN](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

	BUG.(CHK,LLISCM,LLINKS,SOFT,<ELSCM should not have been set>,,<

Cause:	This BUG is not documented yet.

>,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>		;IF ITS IN RUN STATE
	  CALL CHKINA		; CHECK FOR INACTIVITY

;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
	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 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?
	JRST CHKRSX		;NOT YET, DON'T COMPLAIN

;Here if a message has timed out

	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
;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>,,<

Cause:	This BUG is not documented yet.

>,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
	  BUG.(CHK,LLIRQ2,LLINKS,SOFT,<Duplicate msg requeued>,,<

Cause:	This BUG is not documented yet.

>,RTN)
	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 <CALLRET @>,ELSCV,(EL) ;TELL SESSION CONTROL ABOUT IT
	SUBTTL Memory Manager Calls -- NSPCGT - We're Congested

;NSPCGT Memory Manager calls this when available memory falls
;	below a threshold.
;
;Call:	CALL NSPCGT
;	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.

NSPCGT::			;MAY BE AT INTERRUPT LEVEL HERE
	SAVEAC <P1,P2,MS,MB,FREE1,FREE2>
	SETONE LKCGT,+NSPLKF	;WE NEED CONGESTION-DETECTED PROCESSING
	CALLRET NSPLCF		;TRY FOR THE INTERLOCK WITH LKCGT SET

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
	TMNE QHBEG,+ES.RCQ(ES)	;IF THERE IS A RECEIVE Q,
	CALL PESFLO		; TRY TO RELIEVE CONGESTION
	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 -- NSPCGR - Congestion is Relieved

;NSPCGR Memory Manager calls this when available memory recovers
;	to above a threshold after having called NSPCGT.
;
;Call:	CALL NSPCGR
;	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
;	NSPCGT first.  This is fine now, since all we do is clear the
;	the congestion flag.  This should continue to be fine.

NSPCGR::SAVEAC MB		;SAVE MB FROM NSPLCF
	SETONE LKRLV,+NSPLKF	;WE NEED CONGESTION-RELIEVED PROCESSING
	CALL NSPLCF             ;TRY TO GET INTERLOCK NOW
	CALLRET SCTUCG		;SESSION CONTROL IS INTERESTED TOO
	SUBTTL	NSIRLV - Congestion-Relieved Processor

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

;This routine is the interlocked version of NSPCGR, 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>
	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	;NO FLOW CONTROL (SEND OFF MSG)
		IFIW PESSEG	;SEGMENT FLOW CONTROL (SEND NEG DRQS)
		IFIW PESOFF	;MESSGAGE F.C. (SEND OFF MSG)
		IFIW RTN](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
	  BUG.(CHK,LLICGT,LLINKS,SOFT,<Can't DNMINI a msg blk>,,<

Cause:	DNMINI has refused to initialize a message block.  This should
	never happen when the number of bytes requested is zero.

Cure:	Probably something wrong with the message block, its pointer or DNMINI

>,PESFL1)
	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
	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:	JN ELORC,(EL),RTN	;TRY AGAIN NEXT JIFFY IF ANYTHING IN RTR

	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

	BUG.(CHK,LLICLS,LLINKS,SOFT,<Tried to close in non-pre-close state>,,<

Cause:	This BUG is not documented yet.

>,RSKP)


;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

;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 not documented yet.

>)
	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
	  BUG.(CHK,LLISIF,LLINKS,SOFT,<SENDC0's DNMINI failed>,,<

Cause:	This BUG is not documented yet.

>,RTN)
	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
	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 TO SEND DRQ'S
	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
	XMOVEI T1,SCTL		;DEFAULT SESSION CONRTOL 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
	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
	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

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

Cause:	This BUG is not documented yet.

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

Cause:	This BUG is not documented yet.

>,RTN)
	MOVEM T1,NSPHTB		;STORE ADDRESS OF TABLE
	RET			;DNGWDZ ZEROS WORDS DELIVERED
;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:	BUG.(CHK,LLIRMH,LLINKS,SOFT,<RMVHSH didnt find port>,,<

Cause:	This BUG is not documented yet.

>,RTN)
	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>,,<

Cause:	This BUG is not documented yet.

>,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?
	  BUG.(CHK,LLIDDP,LLINKS,SOFT,<Tried to destroy non-DP port>,,<

Cause:	This BUG is not documented yet.

>,RTN)

	TRACE NSP,Port Destroyed

	LOAD T1,ELNDB,(EL)	;GET POINTER TO NSP NODE BLOCK
	DECR NNLKC,(T1)		;DECREMENT LINK COUNT FOR THIS NODE

	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:	This BUG is not documented yet.

>,RTN)
	TRASH MB,		;JUST BE CAREFULL
	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.

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 DNGWDZ		;GET ENOUGH WORDS FOR A PORT
	  BUG.(CHK,LLINRP,LLINKS,SOFT,<No memory for reserved ports>,,<

Cause:	This BUG is not documented yet.

>,RTN)


;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 DNGWDZ		;GET A NODE BLOCK
	  BUG.(CHK,LLINNP,LLINKS,SOFT,<No memory for reserved NDB>,,<

Cause:	This BUG is not documented yet.

>,RTN)
	MOVEM T1,NSPRND		;SAVE FOR GTRESP

	RET
	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
	CAIL T3,0		;IS THE FUNCTION CODE
	CAILE T3,RP.MAX		; LEGAL?
	BUG.(CHK,LLIRFN,LLINKS,SOFT,<NSP called RESPRC with bad fcn code>,,<

Cause:	LLINKS's reserved port handler has been called with an unknown
	Session Control function code in T3.

Cure:	If there is a new Session Control function code this routine doesn't
	know about, add it, else look on the stack to find which LLINKS
	routine called Session Control, which the reserved port handler
	is impersonating, with a bogus function code.
>,RTN)

	SKIPN MB,T4		;SET UP THE MESSAGE BLOCK POINTER
	BUG.(CHK,LLIRMG,LLINKS,SOFT,<NSP called RESPRC without msg blk>,,<

Cause:	LLINKS's reserved port handler has been called with no message
	block pointer, that is, T4 is zero.

Cure:	Look on the stack to find which LLINKS routine called Session Control,
	which the reserved port handler is impersonating, with T4/ 0.

>,RTN)

	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>


;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 NSPEVT - Queue an Event to Network Management

;NSPEVT - Queue an Event to Network Management
;
;Call:
;IFN FTUSERMODE, T1/ [ASCIZ /text/],,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.

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

NSPEVT::			;EVENT MACRO SAVES NECESSARY ACS
	SKIPL P2,T1		;SAVE EVENT CODE
	CAILE P2,1		;WE ONLY KNOW ABOUT 2 TYPES HERE
	BUG.(CHK,LLITNE,LLINKS,SOFT,<Unknown Event Type at NSPEVT>,,<

Cause:	T1 contains an illegal NSP event type.

Cure:	Note that NSPEVT is called by SCLINK as well as LLINKS.
	Caller address is on the stack.
>,RTN)
	MOVX T1,NE.LEN+<<EVTDLN+3>/4> ;EVENT ARG BLK + SPACE FOR DATA
	CALL DNGWDZ		;GET AN ARG BLK FOR NETWORK MGMT
	  BUG.(CHK,LLIWNE,LLINKS,SOFT,<Can't get event arg blk>,,<

Cause:	There isn't any free memory for an event argument block.
	Presumably it really ran out, but some may have been lost.

Cure:	Either allocate more free memory or accept that some events
	will be lost.

>,RTN)
	MOVE P1,T1		;POINTER TO ARG BLOCK

;Fill in the arg block we just got for Network Management

	STOR P2,NECTY,(P1)	;THE EVENT CODE TYPE
	MOVX T1,.NCNSP
	STOR T1,NECCL,(P1)	;THE EVENT CODE CLASS IS "NSP"
	MOVX T1,.NTNOD
	STOR T1,NEETP,(P1)	;THE ENTITY TYPE IS "NODE"
	MOVE T1,RTRADR		;NMX WILL FIGURE OUT NODE NAME
	STOR T1,NEEID,(P1)	;ENTITY ID IS MY NODE NUMBER
	CALL @[	IFIW NEVT.0	;TYPE 0 - INVALID MESSAGE HEADER
		IFIW NEVT.1](P2) ;TYPE 1 - INVALID FLOW CONTROL

	MOVE T1,P1		;GET PTR TO ARG BLK
	CALL NMXEVT		;TELL NMX ABOUT THE EVENT
	 JFCL			;SO WHAT IF WE LOST THE EVENT
	MOVE T1,P1		;GET BACK POINTER TO BLOCK
	CALLRET DNFWDS		;RETURN THE BLOCK OF MEMORY.
;Here for Invalid Message event

NEVT.0:	LOAD T1,NMMK1,(MB)	;GET NSP'S MARK 1 FOR THIS INPUT MSG
	CALL DNGPOS		;GO TO SAVED POSITION IN INPUT DATA
	XMOVEI T1,IN.MSD(MB)	;POINTER TO MSD WHOSE LENGTH TO COUNT
	CALL DNSLNG		;GET COUNT OF BYTES IN THIS MSD
	CAILE T1,EVTDLN		;LIMIT TO STATUTORY MAXIMUM
	MOVX T1,EVTDLN
	MOVE P2,T1		;SAVE BYTE COUNT FOR SOJG BELOW
	STOR P2,NEDLN,(P1)	;STORE AS DATA LENGTH (BYTE COUNT)
	XMOVEI T1,NE.LEN(P1)	;FULL-WORD ADDR OF STRING DATA
	STOR T1,NEDAT,(P1)	;POINTER TO STRING DATA
	PUSH P,[POINT 8,NE.LEN(P1)] ;INDEXED BYTE PTR FOR EXTENDED ADDR
NEVT01:	CALL DNG1BY		;GET A BYTE FROM RECEIVED MESSAGE
	  JRST NEVT02		;ALL DONE (SHOULD NOT HAPPEN)
	IDPB T1,(P)		;STORE IN ARG BLK FOR NMX
	SOJG P2,NEVT01		;LOOP FOR ALL BYTES IN COUNT
NEVT02:	ADJSP P,-1		;FORGET BYTE PTR ON STACK
	RET

;Here for Invalid Flow Control Event

NEVT.1:	MOVEI T1,1		;ASSOC DATA IS ONLY 1 BYTE LONG HERE
	STOR T1,NEDLN,(P1)	;TELL NMX THE DATA LENGTH
	XMOVEI T1,NE.LEN(P1)	;FULL-WORD ADDR OF STRING DATA
	STOR T1,NEDAT,(P1)	;POINTER TO STRING DATA
	LOAD T1,ESRFL,+EL.NSL(EL) ;GET CURRENT RECEIVE FLOW CONTROL
	DPB T1,[POINT 8,NE.LEN(P1),7] ;INDEXED PTR FOR EXTENDED ADDR
	RET
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

	END