-- File: NetworkStreamImpl.mesa - last edit: -- AOF 9-Sep-87 8:26:44 -- SMA 9-Oct-86 9:30:26 -- MXI 3-Nov-87 11:50:45 -- Copyright (C) 1984, 1985, 1986, 1987 by Xerox Corporation. All rights reserved. -- Function: The implementation module for an instance of a SPP packet stream. DIRECTORY Buffer, ByteBlt USING [ByteBlt], NSBuffer USING [AccessHandle, Body, GetBuffer, Buffer, ReturnBuffer, Queue], Checksums USING [SetChecksum], CommFlags USING [doDebug, doStats], CommPriorities USING [receiver], CommUtil USING [PulsesToTicks], Driver USING [GetDeviceChain, Glitch, Device], Environment USING [Byte], NetworkStream USING [], NetworkStreamInternal USING [ControlObject, State], NSTypes USING [ bytesPerIDPHeader, bytesPerSppHeader, ConnectionID, WaitTime], PacketStream USING [ bytesPerSequencedPktHeader, ClassOfService, ConnectionFailed, unknownConnID, ConnectionSuspended, FailureReason, Handle, Object, State, SuspendReason], Process USING [ Abort, CancelAbort, DisableTimeout, EnableAborts, GetCurrent, MsecToTicks, Pause, SetPriority, SetTimeout], Protocol1 USING [EncapsulateAndTransmit, GetContext], Router USING [GetDelayToNet, NoTableEntryForNet], RouterInternal USING [FlushCacheEntry, SendErrorPacket], RoutingTable USING [NetworkContext], Socket USING [ ChannelHandle, Delete, GetPacket, SetPacketBytes, GetPacketBytes], SppNegotiation USING [InitializeSppState, NewInitializeSppState, NewStartPacketStreamProc], SppOps USING [ BufferTable, BufferTableObject, AttentionTableObject, nullAttention, StartByteStreamProc, StopByteStreamProc, StartPacketStreamProc, StopPacketStreamProc, PSProc, PSProcess], Stats USING [StatBump, StatIncr], Stream USING [ Byte, Word, Handle, Block, CompletionCode, InputOptions, LongBlock, Object, ShortBlock, SSTChange, SubSequenceType, TimeOut, Attention, defaultObject, GetByteProcedure, PutByteProcedure, GetWordProcedure, PutWordProcedure, GetProcedure, PutProcedure, SetSSTProcedure, GetSSTProcedure, SendAttentionProcedure, WaitAttentionProcedure, SendNowProcedure, SetTimeoutProcedure, GetTimeoutProcedure, EndRecord], System USING [ GetClockPulses, MicrosecondsToPulses, NetworkAddress, Pulses]; NetworkStreamImpl: MONITOR IMPORTS NSBuffer, ByteBlt, Checksums, CommUtil, Driver, PacketStream, Process, Protocol1, Router, RouterInternal, Socket, SppNegotiation, Stream, Stats, System EXPORTS Buffer, NetworkStream, SppOps = BEGIN Device: PUBLIC TYPE = Driver.Device; ConnectionID: PUBLIC TYPE = NSTypes.ConnectionID; attn: PUBLIC RECORD [table: SppOps.AttentionTableObject]; --connection control parameters connection: PUBLIC RECORD [ state: NetworkStreamInternal.State, --connection state socket: Socket.ChannelHandle, --the socket supporting the stream pool: NSBuffer.AccessHandle, --socket's supporting pool maxSppBytes: CARDINAL, --of packets supported by this stream hops: CARDINAL, --to the remote station localAddr, remoteAddr: System.NetworkAddress, localID, remoteID: NSTypes.ConnectionID, timeout: NSTypes.WaitTime, --specified by the client (Create|SetWaitTime) whyFailed: PacketStream.FailureReason, --connection failures whySuspended: PacketStream.SuspendReason, --after connection stateBeforeSuspension: NetworkStreamInternal.State]; --connection state --variables and parameters for transmission xmtr: PUBLIC RECORD [ unackedSeq: CARDINAL, --oldest packet in retrans table nextSeq: CARDINAL, --next sequence to use for transmission maxSeq: CARDINAL, --max sequence number we can send maxAlloc: CARDINAL, --upper limit on remote's window lastXmt: LONG CARDINAL, --last time we transmitted data interval: LONG CARDINAL, --minimum interval between data transmission blocked: BOOLEAN, --indicates local xmtr is blocked checksums: BOOLEAN, --are we using checksums on local net? rateControlling: BOOLEAN, --are we doing rate control stuff? quenchIncr: CARDINAL, --number of times we've doubled the interval newAllocation: CONDITION, --blocks client sending process clientProcess: PROCESS, --this is the client process transmitProc: PROC[Socket.ChannelHandle, NSBuffer.Buffer], context: RoutingTable.NetworkContext]; --used for expedited delivery xmtrDefaults: RECORD [ allocation, rateControlPause, delay1Hop, delayNHopT1, delayNHop64, delayNHop96, totalQuench: CARDINAL] = [ 1, 50, 50, 100, 120, 300, 16]; --variables and parameteres for receiving rcvr: PUBLIC RECORD [ --sequencing, duplicate suppression and flow control information waitTime: LONG CARDINAL, --milliseconds as requested by client timeout: LONG CARDINAL, --packet should arrive before timeout + interval interval: LONG CARDINAL, --amount of time willing to wait for next packet nextSeq: CARDINAL, --next sequence number expected acksReqested: BOOLEAN, --acks have been requested by remote spp maxSeq: CARDINAL, --max we expect (but will take one greater) maxAlloc: NATURAL, --used to determine window that we present blocked: BOOLEAN, --indicates remote sender is blocked table: SppOps.BufferTableObject, --input buffer table process: SppOps.PSProcess]; --receiver proces for joining in Delete rcvrDefaults: RECORD [accept, duplicate: INTEGER] = [2, 20]; --variables and parameters for retransmission --all times (LONG CARDINALs) are in pulses rexmtr: PUBLIC RECORD [ interval: LONG CARDINAL, --the retransmission interval (time in buffer.time) ceiling: LONG CARDINAL, --max allowed (see rexmtrDefaults.ceiling) floor: LONG CARDINAL, --min allowed (see rexmtrDefaults.floor) giveUp: CARDINAL, --number of times to retransmit before giving up count: CARDINAL, --number of data packets entered in delay calculation delay: LONG CARDINAL, --cumulative time that packets spent in output table calculation: LONG CARDINAL, --beginning of collection interval calculationInterval: LONG CARDINAL, --interval between calculations table: SppOps.BufferTableObject, --retransmitter buffer table process: SppOps.PSProcess, --needed to rejoin process during delete condition: CONDITION]; --block Retransmitter (based on rexmtr.interval) --default intervals are in milliseconds, others are packets rexmtrDefaults: RECORD [ flushRoute, giveUp, --numbers of packets initialLocal, initialRemote, ceiling, floor, calculationInterval: --msecs CARDINAL] = [8, 30, 500, 6000, 24000, 200, 5000]; --variables and parameters for probing --all times (LONG CARDINALs) are in pulses probe: PUBLIC RECORD [ lastTime: LONG CARDINAL, --probe again at "*" + interval inactiveInterval, allocInterval: LONG CARDINAL, --interval between probes unacked: CARDINAL, --number of unacked probes sent probing: BOOLEAN]; --are we probing at all? --intervals are in milliseconds, probes in packets probeDefaults: RECORD [ giveUp, inactiveInterval, allocInterval, hopDelay: CARDINAL] = [8, 5000, 2000, 2000]; packetStreamObject: PUBLIC PacketStream.Object; << Assumptions about buffers needed to support a stream (worst case) (These numbers are all biased by SppOps.sppWindowSize.) n are for sending data 1 for sending a system packet 2 for sending expedited packets n for receiving data 1 for receiving a system packet 2 for receiving an expedited packets 1 for extra receive packet >> streamParms: RECORD [send, receive, xmtrAlloc, rcvrAlloc: CARDINAL] = [ 3, 4, 0, 0]; --the following constant is used in setting wait times, etc maxMsecToPulses: LONG CARDINAL = LAST[LONG CARDINAL] / 1000; unknownCID: NSTypes.ConnectionID = PacketStream.unknownConnID; isEstablished: CONDITION; --we only need this at the very beginning startByteStream: PUBLIC SppOps.StartByteStreamProc; --for starting startPacketStream: PUBLIC SppOps.StartPacketStreamProc; --ditto stopByteStream: PUBLIC SppOps.StopByteStreamProc; --for stopping stopPacketStream: PUBLIC SppOps.StopPacketStreamProc; --ditto newStartPacketStream: PUBLIC SppNegotiation.NewStartPacketStreamProc; --for starting with negotiation --Glitchable signals TableScrambled: ERROR = CODE; StreamTerminating: ERROR = CODE; StreamNotEstablished: ERROR = CODE; MultipleBufferClients: ERROR = CODE; --local signals InvalidPacketSize: PUBLIC ERROR = CODE; --Used by procs that notify|wait without state checking required Notify: ENTRY PROC [c: LONG POINTER TO CONDITION] = INLINE {NOTIFY c­}; Wait: ENTRY PROC [c: LONG POINTER TO CONDITION] = INLINE {ENABLE UNWIND => NULL; WAIT c­}; --these modulo functions 'hide' the ugliness of 16 bit wrap around math -- a > b -- Greater: PROC [a, b: CARDINAL] RETURNS [BOOLEAN] = INLINE { RETURN[INTEGER[a - b] > 0]}; -- a < b -- Less: PROC [a, b: CARDINAL] RETURNS [BOOLEAN] = INLINE { RETURN[INTEGER[a - b] < 0]}; -- a < b -- a >= b -- GreaterOrEqual: PROC [a, b: CARDINAL] RETURNS [BOOLEAN] = INLINE { RETURN[INTEGER[a - b] >= 0]}; -- a <= b -- LessOrEqual: PROC [a, b: CARDINAL] RETURNS [BOOLEAN] = INLINE { RETURN[INTEGER[a - b] <= 0]}; Max: PROC [a, b: CARDINAL] RETURNS [CARDINAL] = INLINE { RETURN[IF INTEGER[a - b] > 0 THEN a ELSE b]}; Min: PROC [a, b: CARDINAL] RETURNS [CARDINAL] = INLINE { RETURN[IF INTEGER[a - b] < 0 THEN a ELSE b]}; --SANITY CHECKING AND DEBUGGING CODE fakeQueue: NSBuffer.Queue ¬ LOOPHOLE[NetworkStreamImpl]; ReturnBuffer: PROCEDURE [b: NSBuffer.Buffer] = {NSBuffer.ReturnBuffer[b]}; DebugInsertBuffer: INTERNAL PROC[b: NSBuffer.Buffer] = INLINE BEGIN IF b.fo.queue # NIL THEN Driver.Glitch[TableScrambled]; b.fo.queue ¬ fakeQueue; --it isn't a real queue, but it isn't NIL either END; --DebugInsertBuffer DebugExtractBuffer: INTERNAL PROC[b: NSBuffer.Buffer] = INLINE BEGIN IF b.fo.queue # fakeQueue THEN Driver.Glitch[TableScrambled]; b.fo.queue ¬ NIL; --now it's NIL END; --DebugExtractBuffer SanityCheck: ENTRY PROC[t: LONG POINTER TO SppOps.BufferTableObject] = BEGIN IF CommFlags.doDebug THEN BEGIN l: CARDINAL ¬ 0; b: NSBuffer.Buffer; FOR i: CARDINAL IN[0..LENGTH[t.slot]) DO SELECT TRUE FROM ((b ¬ t.slot[i]) = NIL) => NULL; --no action here (b.fo.queue # fakeQueue) => GOTO glitch; --not in my table (b.fo.function = receive) => l ¬ l + 1; --these use standard requeue (b.requeueProcedure # RequeueProcedure) => GOTO glitch; ENDCASE => l ¬ l + 1; --it's ok, just count it ENDLOOP; IF l # t.length THEN GOTO glitch; EXITS glitch => Driver.Glitch[TableScrambled]; END; END; --SanityCheck AcceptablePacket: PROC [b: NSBuffer.Buffer] = --PROCESS: RECEIVER BEGIN --acceptable, but may be early - be careful InsertRecvdPacketInTable: ENTRY PROC = BEGIN --no possibility of signals i: CARDINAL ¬ body.sequenceNumber MOD LENGTH[rcvr.table.slot]; --duplicate reception of early packet IF rcvr.table.slot[i] # NIL THEN {DuplicatePacket[b]; RETURN}; rcvr.table.slot[i] ¬ b; rcvr.table.length ¬ rcvr.table.length + 1; IF CommFlags.doDebug THEN DebugInsertBuffer[b]; IF body.sequenceNumber = rcvr.nextSeq THEN BEGIN --first loop of block is guaranteed - we just stuck in the buffer THROUGH [0..LENGTH[rcvr.table.slot]) DO --THIS BLOCK IS THE ONLY UPDATER OF rcvr.nextSeq!!! rcvr.nextSeq ¬ rcvr.nextSeq + 1; --one more in the sequence i ¬ (i + 1) MOD LENGTH[rcvr.table.slot]; --point to next slot in table IF rcvr.table.slot[i] = NIL THEN EXIT; --first empty slot stops search ENDLOOP; NOTIFY rcvr.table.condition; END; END; --InsertRecvdPacketInTable body: NSBuffer.Body = b.ns; IF body.destinationConnectionID = connection.localID THEN connection.state ¬ open; --he's seen us - we've seen him IF CommFlags.doStats AND body.sendAck THEN Stats.StatIncr[statAckRequestsReceived]; SELECT TRUE FROM (body.systemPacket) => BEGIN IF body.sendAck THEN rcvr.acksReqested ¬ TRUE; --ack sys packets direct IF CommFlags.doStats THEN Stats.StatIncr[statSystemPacketsReceived]; NSBuffer.ReturnBuffer[b]; --client never sees system packets END; (~body.attention) => BEGIN rcvr.blocked ¬ INTEGER[body.sequenceNumber - rcvr.maxSeq] >= 0; InsertRecvdPacketInTable[]; --packet MAY be saved in table END; (body.attention AND InsertAttention[b]) => InsertRecvdPacketInTable[]; --packet MAY be saved in table ENDCASE => NSBuffer.ReturnBuffer[b]; --duplicate attention packet END; --AcceptablePacket CleanupTable: ENTRY PROC [t: SppOps.BufferTable] = --PROCESS: RECEIVER (t = input) | RETRANSMITTER (t = output) BEGIN b: NSBuffer.Buffer; --No possibility of errors --Get all buffers out of this table FOR i: CARDINAL IN[0..LENGTH[t.slot]) DO IF (b ¬ t.slot[i]) # NIL THEN BEGIN t.slot[i] ¬ NIL; t.length ¬ t.length - 1; IF CommFlags.doDebug THEN DebugExtractBuffer[b]; NSBuffer.ReturnBuffer[b]; END; ENDLOOP; END; --CleanupTable DuplicatePacket: PROC [b: NSBuffer.Buffer] = --PROCESS: RECEIVER BEGIN <> rcvr.acksReqested ¬ TRUE; --always ack duplicates IF CommFlags.doStats THEN Stats.StatIncr[statDataPacketsReceivedAgain]; NSBuffer.ReturnBuffer[b]; END; --DuplicatePacket Establish: PROC [b: NSBuffer.Buffer] = --PROCESS: RECEIVER BEGIN body: NSBuffer.Body = b.ns; SELECT TRUE FROM (body.sequenceNumber # 0) => BEGIN IF CommFlags.doStats THEN Stats.StatIncr[statPacketsRejectedBadID]; NSBuffer.ReturnBuffer[b]; RETURN; END; (body.source.socket = connection.remoteAddr.socket) AND (body.destinationConnectionID = connection.localID) AND (body.sourceConnectionID # unknownCID) => NULL; (connection.state = activeEstablish) AND (body.source.socket # connection.remoteAddr.socket) AND (body.destinationConnectionID = connection.localID) AND (body.sourceConnectionID # unknownCID) => BEGIN connection.remoteAddr.socket ¬ body.source.socket; END; (body.source.socket = connection.remoteAddr.socket) AND (body.destinationConnectionID = unknownCID) AND (body.sourceConnectionID # unknownCID) => BEGIN IF connection.state = waitEstablish THEN rcvr.acksReqested ¬ TRUE; END; ENDCASE => BEGIN IF CommFlags.doStats THEN Stats.StatIncr[statPacketsRejectedBadID]; NSBuffer.ReturnBuffer[b]; RETURN; END; xmtr.maxSeq ¬ MIN[ body.allocationNumber, xmtr.unackedSeq + xmtr.maxAlloc]; connection.remoteID ¬ body.sourceConnectionID; connection.state ¬ established; --may be 'opened' by AcceptablePacket IF body.sendAck THEN rcvr.acksReqested ¬ TRUE; --need an ack on this one AcceptablePacket[b]; Notify[@isEstablished]; --notify any waiters END; --Establish ExtractAttention: ENTRY PROC RETURNS [byte: Environment.Byte] = --PROCESS: CLIENT WAIT FOR ATTENTION BEGIN ENABLE UNWIND => NULL; --Extract attention byte or wait for one to arrive i: CARDINAL; DO SELECT connection.state FROM established, open => BEGIN IF attn.table.length # 0 THEN BEGIN i ¬ attn.table.index MOD LENGTH[attn.table.slot]; attn.table.index ¬ attn.table.index + 1; IF attn.table.slot[i] = SppOps.nullAttention THEN LOOP; byte ¬ attn.table.slot[i].attn; attn.table.slot[i] ¬ SppOps.nullAttention; attn.table.length ¬ attn.table.length - 1; RETURN; END; END; suspended => RETURN WITH ERROR PacketStream.ConnectionSuspended[connection.whySuspended]; unestablished, activeEstablish, waitEstablish => IF CommFlags.doDebug THEN Driver.Glitch[StreamNotEstablished]; ENDCASE; WAIT attn.table.condition; ENDLOOP; END; --ExtractAttention ExtractRexmt: ENTRY PROC RETURNS [b: NSBuffer.Buffer] = --PROCESS: RETRANSMITTER BEGIN --No possibility of signals --extracts Buffer from table, doesn't consume i: CARDINAL = rexmtr.table.index MOD LENGTH[rexmtr.table.slot]; IF (b ¬ rexmtr.table.slot[i]) # NIL THEN BEGIN rexmtr.table.slot[i] ¬ NIL; rexmtr.table.length ¬ rexmtr.table.length - 1; IF CommFlags.doDebug THEN BEGIN IF (rexmtr.table.index # b.ns.sequenceNumber) THEN Driver.Glitch[TableScrambled]; DebugExtractBuffer[b]; END; END ELSE {xmtr.blocked ¬ FALSE; NOTIFY xmtr.newAllocation}; END; --ExtractRexmt FindAddresses: PROC RETURNS [local, remote: System.NetworkAddress] = {RETURN[connection.localAddr, connection.remoteAddr]}; GetForUser: ENTRY PROC RETURNS [b: NSBuffer.Buffer] = --PROCESS: CLIENT RECEIVING BEGIN ENABLE UNWIND => NULL; i: CARDINAL = rcvr.table.index MOD LENGTH[rcvr.table.slot]; rcvr.timeout ¬ System.GetClockPulses[]; DO --either EXITs or ERRORs out of loop IF (b ¬ rcvr.table.slot[i]) # NIL THEN BEGIN IF CommFlags.doDebug THEN BEGIN IF (rcvr.table.index # b.ns.sequenceNumber) THEN Driver.Glitch[TableScrambled]; DebugExtractBuffer[b]; END; rcvr.table.slot[i] ¬ NIL; rcvr.table.length ¬ rcvr.table.length - 1; rcvr.table.index ¬ rcvr.table.index + 1; IF CommFlags.doStats THEN BEGIN Stats.StatIncr[statDataPacketsReceived]; Stats.StatBump[statDataBytesReceived, b.ns.pktLength - PacketStream.bytesPerSequencedPktHeader]; END; --ONLY UPDATER OF rcvr.maxSeq!!! --don't take away previously allocated window rcvr.maxSeq ¬ Max[rcvr.table.index + rcvr.maxAlloc, rcvr.maxSeq]; IF (rcvr.table.length = 0) AND (rcvr.blocked OR b.ns.sendAck) THEN SystemPacket[FALSE]; EXIT; --RETURN[b # NIL] END; SELECT connection.state FROM established, open => BEGIN time: LONG CARDINAL = System.GetClockPulses[]; SELECT TRUE FROM (rcvr.waitTime = 0) => EXIT; --RETURN[b = NIL], didn't want to wait ((time - rcvr.timeout) > rcvr.interval) => BEGIN IF CommFlags.doDebug AND (rcvr.interval = LAST[LONG CARDINAL]) THEN Driver.Glitch[StreamTerminating]; EXIT; --RETURN[b = NIL], this is a timeout END; ENDCASE => WAIT rcvr.table.condition; --wait for packet, LOOP END; suspended => RETURN WITH ERROR PacketStream.ConnectionSuspended[connection.whySuspended]; terminating => IF CommFlags.doDebug THEN Driver.Glitch[StreamTerminating]; unestablished, activeEstablish, waitEstablish => IF CommFlags.doDebug THEN Driver.Glitch[StreamNotEstablished]; ENDCASE; ENDLOOP; END; --GetForUser GetOutputBuffer: PROC RETURNS[b: NSBuffer.Buffer] = BEGIN ClearClientProcess: ENTRY PROC = INLINE {xmtr.clientProcess ¬ NIL}; EnterOperation: ENTRY PROC RETURNS[suspended: BOOLEAN] = INLINE BEGIN SELECT TRUE FROM (CommFlags.doDebug AND (xmtr.clientProcess # NIL)) => Driver.Glitch[MultipleBufferClients]; (connection.state = suspended) => RETURN[TRUE]; ENDCASE => xmtr.clientProcess ¬ Process.GetCurrent[]; RETURN[FALSE]; END; --EnterOperation ExitOperation: ENTRY PROC = INLINE BEGIN SELECT TRUE FROM (xmtr.clientProcess = NIL) => NULL; --means we had multiple clients!?? (connection.state = suspended) => --we're suspended, we did the abort? {Process.CancelAbort[xmtr.clientProcess]; xmtr.clientProcess ¬ NIL}; ENDCASE => xmtr.clientProcess ¬ NIL; END; --ExitOperation BEGIN --protect only the call into the buffer manager IF EnterOperation[].suspended THEN GOTO returnSuspended; b ¬ NSBuffer.GetBuffer[aH: connection.pool, function: send, size: connection.maxSppBytes + NSTypes.bytesPerSppHeader + NSTypes.bytesPerIDPHeader ! UNWIND => ClearClientProcess[]; ABORTED => IF connection.state = suspended THEN GOTO suspended]; EXITS suspended => {ClearClientProcess[]; GOTO returnSuspended}; END; --protect only the call into the buffer manager ExitOperation[]; --just to cleanup state appropriately IF connection.state = suspended THEN {NSBuffer.ReturnBuffer[b]; GOTO returnSuspended}; b.ns.attention ¬ b.ns.endOfMessage ¬ b.ns.systemPacket ¬ FALSE; EXITS returnSuspended => RETURN WITH ERROR PacketStream.ConnectionSuspended[connection.whySuspended]; END; --GetOutputBuffer GetSenderSizeLimit: PROC RETURNS [CARDINAL] = {RETURN[connection.maxSppBytes]}; GetState: PROC RETURNS[PacketStream.State ¬ inactive] = BEGIN SELECT connection.state FROM suspended => NULL; established, open => RETURN[active]; terminating => Driver.Glitch[StreamTerminating]; ENDCASE => Driver.Glitch[StreamNotEstablished]; END; --GetState GetWaitTime: PROC RETURNS [time: NSTypes.WaitTime] = {time ¬ rcvr.waitTime}; InitializePSObject: PROC = BEGIN packetStreamObject ¬ [ destroy: NIL, --done from the outside(Mgr) put: TakeFromUser, get: GetForUser, waitForAttention: ExtractAttention, setWaitTime: SetWaitTime, findAddresses: FindAddresses, getSenderSizeLimit: GetSenderSizeLimit, setSenderSizeLimit: SetSenderSizeLimit, getSendBuffer: GetOutputBuffer, returnSendBuffer: ReturnBuffer, returnReceiveBuffer: ReturnBuffer, getState: GetState, setState: SetState]; END; InsertAttention: ENTRY PROC [b: NSBuffer.Buffer] RETURNS [BOOLEAN] = --PROCESS: RECEIVER BEGIN --No possibility of signals --Insert the attention byte into the table i: CARDINAL ¬ b.ns.sequenceNumber; IF CommFlags.doStats THEN Stats.StatIncr[statAttentionsReceived]; IF attn.table.length = 0 THEN attn.table.index ¬ i; i ¬ i MOD LENGTH[attn.table.slot]; IF attn.table.slot[i] # SppOps.nullAttention THEN RETURN[FALSE]; attn.table.slot[i] ¬ [ sequence: b.ns.sequenceNumber, mask: 377B, attn: b.ns.sppBytes[0]]; attn.table.length ¬ attn.table.length + 1; NOTIFY attn.table.condition; RETURN[TRUE]; END; --InsertAttention InsertRexmtOrConsume: PROC [b: NSBuffer.Buffer] = --PROCESS: RETRANSMITTER BEGIN InsertBuffer: ENTRY PROC[] = INLINE BEGIN rexmtr.table.slot[i] ¬ b; --return the buffer to the table rexmtr.table.length ¬ SUCC[rexmtr.table.length]; --adjust the length IF CommFlags.doDebug THEN DebugInsertBuffer[b]; --sanity check END; --InsertBuffer i: CARDINAL = b.ns.sequenceNumber MOD LENGTH[rexmtr.table.slot]; SELECT TRUE FROM (CommFlags.doDebug AND (rexmtr.table.slot[i] # NIL)) => Driver.Glitch[TableScrambled]; (connection.state = terminating) => NSBuffer.ReturnBuffer[b]; --daid stream (INTEGER[xmtr.unackedSeq - b.ns.sequenceNumber] > 0) => --already ack'd BEGIN rexmtr.delay ¬ rexmtr.delay + (System.GetClockPulses[] - b.fo.time); rexmtr.count ¬ SUCC[rexmtr.count]; --and the number of participants rexmtr.table.index ¬ SUCC[rexmtr.table.index]; --consume packet NSBuffer.ReturnBuffer[b]; --free the buffer IF rexmtr.table.length > 0 THEN Notify[@rexmtr.condition]; --on to next END; ENDCASE => InsertBuffer[]; --not finished with buffer END; --InsertRexmtOrConsume ProcessSppState: PROC [b: NSBuffer.Buffer] = --PROCESS: RECEIVER BEGIN bPrime: NSBuffer.Buffer; body: NSBuffer.Body = b.ns; unacked: CARDINAL ¬ body.acknowledgeNumber; IF INTEGER[body.acknowledgeNumber - xmtr.unackedSeq] > 0 THEN BEGIN --new acknowledge field xmtr.unackedSeq ¬ body.acknowledgeNumber; --copy new ack number UNTIL (bPrime ¬ ExtractRexmt[]) = NIL DO --all packets up to but not including 'xmtr.unackedSeq' are acked unacked: CARDINAL ¬ bPrime.ns.sequenceNumber; InsertRexmtOrConsume[bPrime]; --and put it back | consume it IF LessOrEqual[xmtr.unackedSeq, unacked] THEN EXIT; ENDLOOP; END; --new acknowlege field IF Greater[body.allocationNumber, xmtr.maxSeq] THEN BEGIN --new allocation from remote xmtr.maxSeq ¬ Min[xmtr.unackedSeq + xmtr.maxAlloc, body.allocationNumber]; Notify[@xmtr.newAllocation]; END; --new allocation from remote probe.unacked ¬ 0; --other end is alive probe.lastTime ¬ System.GetClockPulses[]; --to probe for whatever END; --ProcessSppState RcvdErrorPacket: PUBLIC PROCEDURE [b: NSBuffer.Buffer] = --PROCESS: RECEIVER BEGIN IF CommFlags.doStats THEN Stats.StatIncr[statErrorPacketsReceived]; SELECT connection.state FROM IN[unestablished..waitEstablish] => BEGIN SELECT b.ns.errorType FROM noSocket => <> SuspendStream[noRouteToDestination, noServiceAtDestination]; resourceLimits, listenerReject, connectionLimit => <> SuspendStream[noRouteToDestination, remoteReject]; < NULL;>> ENDCASE; END; established, open => SELECT b.ns.errorType FROM noSocket => <> SuspendStream[remoteServiceDisappeared, remoteReject]; congestionWarning, congestionDiscard => IF xmtr.rateControlling THEN SetQuencing[up]; ENDCASE; < NULL;>> ENDCASE; < NULL>> END; --RcvdErrorPacket RcvdSppPacket: PUBLIC PROC [b: NSBuffer.Buffer] = --PROCESS: RECEIVER BEGIN body: NSBuffer.Body = b.ns; SELECT connection.state FROM established, open => BEGIN SELECT TRUE FROM (body.source.socket # connection.remoteAddr.socket) => BEGIN --out of the blue came..... IF CommFlags.doDebug THEN Stats.StatIncr[statPacketsRejectedBadSource]; NSBuffer.ReturnBuffer[b]; END; (body.sourceConnectionID = connection.remoteID) AND (body.destinationConnectionID = connection.localID) => BEGIN --really is for this connection normalizedNext: INTEGER = INTEGER[rcvr.nextSeq - rcvr.maxSeq]; normalizedSeq: INTEGER = INTEGER[body.sequenceNumber - rcvr.maxSeq]; SELECT normalizedSeq FROM > rcvrDefaults.accept => BEGIN --this is a very early packet - just ignore it IF CommFlags.doStats THEN Stats.StatIncr[statDataPacketsReceivedEarly]; NSBuffer.ReturnBuffer[b]; END; < normalizedNext => IF normalizedSeq < normalizedNext - rcvrDefaults.duplicate THEN BEGIN --this is a very late packet - just ignore it IF CommFlags.doStats THEN Stats.StatIncr[statDataPacketsReceivedVeryLate]; NSBuffer.ReturnBuffer[b]; END --duplicate, but worth looking at state, acking if sender wishes ELSE {ProcessSppState[b]; DuplicatePacket[b]}; --the only case that's really productive ENDCASE => {ProcessSppState[b]; AcceptablePacket[b]}; END; (body.sourceConnectionID = connection.remoteID) OR (body.destinationConnectionID = connection.localID) => BEGIN rcvr.acksReqested ¬ TRUE; --hasn't seen our id yet? NSBuffer.ReturnBuffer[b]; --don't accept buffer though END; ENDCASE => BEGIN IF CommFlags.doDebug THEN Stats.StatIncr[statPacketsRejectedBadID]; NSBuffer.ReturnBuffer[b]; --just some trash END; END; activeEstablish, waitEstablish => Establish[b]; --suspended, teminating, unestablished => NSBuffer.ReturnBuffer[b];-- ENDCASE => NSBuffer.ReturnBuffer[b]; END; --RcvdSppPacket Receiver: PROC RETURNS [SppOps.PSProc] = --PROCESS: THIS IS THE RECEIVER BEGIN b: NSBuffer.Buffer; body: NSBuffer.Body; Process.SetPriority[CommPriorities.receiver]; <> DO ENABLE ABORTED => EXIT; --get next packet from socket queue b ¬ Socket.GetPacket[connection.socket]; body ¬ b.ns; IF CommFlags.doDebug THEN SanityCheck[@rcvr.table]; SELECT TRUE FROM body.source.host # connection.remoteAddr.host => BEGIN IF CommFlags.doStats THEN Stats.StatIncr[statPacketsRejectedBadSource]; NSBuffer.ReturnBuffer[b]; END; (body.packetType = sequencedPacket) AND (body.pktLength >= PacketStream.bytesPerSequencedPktHeader) => BEGIN RcvdSppPacket[b]; IF rcvr.acksReqested THEN SystemPacket[FALSE]; END; body.packetType = error => BEGIN RcvdErrorPacket[b]; NSBuffer.ReturnBuffer[b]; END; ENDCASE => BEGIN IF CommFlags.doStats THEN Stats.StatIncr[statPacketsRejectedBadType]; RouterInternal.SendErrorPacket[b, invalidPacketType]; END; IF CommFlags.doDebug THEN SanityCheck[@rcvr.table]; ENDLOOP; RETURN[LOOPHOLE[Receiver]]; END; --Receiver RequeueProcedure: PROC [b: NSBuffer.Buffer] = --PROCESS: BUFFER MANAGER BEGIN IF (b.fo.status ~IN[pending..aborted]) THEN SuspendStream[noRouteToDestination, SELECT b.fo.status FROM noRouteToNetwork => noRouteToDestination, noTranslationForDestination => noTranslationForDestination, noAnswerOrBusy => noAnswerOrBusy, circuitInUse => circuitInUse, circuitNotReady => circuitNotReady, dialerHardwareProblem => circuitNotReady, noDialingHardware => noDialingHardware, invalidDestAddr => noServiceAtDestination, ENDCASE => noRouteToDestination]; --hardware problem-- IF b.ns.systemPacket THEN NSBuffer.ReturnBuffer[b] --don't reQ sys packets ELSE InsertRexmtOrConsume[b]; --put the buffer back in the table IF CommFlags.doDebug THEN SanityCheck[@rexmtr.table]; END; --RequeueProcedure Retransmitter: PROC RETURNS [SppOps.PSProc] = --PROCESS: THIS IS THE RETRANSMITTER BEGIN b: NSBuffer.Buffer; body: NSBuffer.Body; <> DO ENABLE ABORTED => EXIT; lastProbe: LONG CARDINAL = probe.lastTime; timeNow: LONG CARDINAL ¬ System.GetClockPulses[]; IF CommFlags.doDebug THEN SanityCheck[@rexmtr.table]; BEGIN SELECT TRUE FROM --this connection is suspended....can it be restarted? (connection.state = suspended) => GOTO suspended; --we aren't dead....should we commit suicide? (probe.unacked > probeDefaults.giveUp) => GOTO suspend; ((b ¬ ExtractRexmt[]) # NIL) => BEGIN body ¬ b.ns; --make local copy SELECT TRUE FROM (Greater[xmtr.unackedSeq, body.sequenceNumber]) => BEGIN --packet already acked InsertRexmtOrConsume[b]; --all finished with this buffer LOOP; --and on to bigger and better things - don't wait END; ((timeNow - b.fo.time) < (rexmtr.interval * b.fo.tries)) => --whoops - didn't mean to do that - too soon to retrans this one InsertRexmtOrConsume[b]; --put it back ((b.fo.tries ¬ b.fo.tries + 1) > rexmtr.giveUp) => --this connection is almost dead BEGIN InsertRexmtOrConsume[b]; --put buffer back GOTO suspend; --and quit trying END; ENDCASE => --this is a retransmission - is there anything else to do? BEGIN --is it time to do something rash?? IF (b.fo.tries MOD rexmtrDefaults.flushRoute) = 0 THEN RouterInternal.FlushCacheEntry[body.destination.net]; --sending buffer with sendAck is equivalent to a probe (almost) IF ((timeNow - lastProbe) > probe.inactiveInterval) THEN BEGIN probe.unacked ¬ probe.unacked + 1; IF CommFlags.doStats THEN Stats.StatIncr[statProbesSent]; END; xmtr.blocked ¬ body.sendAck ¬ TRUE; --resynch stream probe.lastTime ¬ timeNow; --mark new probe interval SendPacket[b]; --retransmit the packet IF CommFlags.doStats THEN Stats.StatIncr[statDataPacketsRetransmitted]; END; END; --no traffic from other end in along time, no buffer to rexmit ((timeNow - lastProbe) > probe.inactiveInterval) => BEGIN probe.lastTime ¬ timeNow; --mark new interval start IF probe.probing THEN BEGIN SystemPacket[TRUE]; probe.unacked ¬ probe.unacked + 1; --count the probe IF CommFlags.doStats THEN Stats.StatIncr[statProbesSent]; END; END; --remote won't open window, perhaps we missed his response (~Less[xmtr.maxSeq, xmtr.nextSeq]) => NULL; --we're not trying to send ((timeNow - lastProbe) > probe.allocInterval) => BEGIN probe.lastTime ¬ timeNow; --mark new allocation interval start SystemPacket[TRUE]; --then send the probe probe.unacked ¬ probe.unacked + 1; --count the probe IF CommFlags.doStats THEN Stats.StatIncr[statProbesSent]; END; ENDCASE; --time to recompute retransmission interval? IF ((timeNow - rexmtr.calculation) > rexmtr.calculationInterval) THEN BEGIN rexmtr.calculation ¬ timeNow; --reset start of interval IF (rexmtr.count # 0) THEN BEGIN ri: LONG CARDINAL; << The new retransmission interval (ri) at time (n+1) is ri(n+1). ri(n+1) ¬ floor <= INT[f[ri(t)]] (t in[0..n])] <= ceiling; This uses a crude Euler integration of the approximate form f(n+1) ¬ (3 * f(n-1) + f(n)) / 4; The contribution or ri(n) is 1.5 (2.0) * the average time packets spent in the retransmit table waiting to be ack'd. >> ri ¬ rexmtr.delay / rexmtr.count; --that's the avg over the interval ri ¬ ri + (ri / 2); --times 1.5 so we don't drive too hard <> --weight old interval more than new (3 to 1) ri ¬ (((2 * rexmtr.interval) + rexmtr.interval) + ri) / 4; --Euler --interval is bounded both low and high rexmtr.interval ¬ MAX[rexmtr.floor, MIN[rexmtr.ceiling, ri]]; rexmtr.delay ¬ 0; rexmtr.count ¬ 0; --reset the collectors END; IF xmtr.rateControlling THEN SetQuencing[down]; --check out this END; EXITS suspend => SuspendStream[transmissionTimeout, timeout]; suspended => NULL; --it has already been done END; IF CommFlags.doDebug THEN SanityCheck[@rexmtr.table]; Wait[@rexmtr.condition]; ENDLOOP; RETURN[LOOPHOLE[Retransmitter]]; END; --Retransmitter SendPacket: PROC [b: NSBuffer.Buffer] = --PROCESS: RETRANSMITTER AND CLIENT SENDING BEGIN body: NSBuffer.Body = b.ns; IF CommFlags.doStats AND body.sendAck THEN Stats.StatIncr[statAckRequestsSent]; << Any packet going out carries the latest state information. That means that if there have been any acks requested, that this transmission satisfies that request. Also, we may have consumed some data that had our receiver in a blocked state. This is an opportunity to check for that too. >> body.unusedType ¬ 0; --unused field (at present) rcvr.acksReqested ¬ FALSE; --this is sufficient for any ack rcvr.blocked ¬ INTEGER[rcvr.nextSeq - rcvr.maxSeq] >= 0; --still blocked? body.acknowledgeNumber ¬ rcvr.nextSeq; --latest acknowledgement body.allocationNumber ¬ rcvr.maxSeq; --and latest allocation IF xmtr.rateControlling AND ~body.systemPacket THEN BEGIN << We don't control the rate of transmission of system packets. They go out in response to probes, duplicates, etc. And, we don't log the time we sent them. So we might transmit a system packet followed very closely by a data packet. If we registered the time we sent system packets, then we might get into a rut that prohibited us from sending anything but system packets. If we get ABORT'd (UNWIND) will dallying, just send the packet. That will get it back via the requeue machinery and we know how to handle that. >> UNTIL (System.GetClockPulses[] - xmtr.lastXmt) > xmtr.interval DO ENABLE UNWIND => xmtr.transmitProc[connection.socket, b]; --send it! Process.Pause[Process.MsecToTicks[xmtrDefaults.rateControlPause]]; ENDLOOP; xmtr.lastXmt ¬ System.GetClockPulses[]; --remember last time we xmt'd END; xmtr.transmitProc[connection.socket, b]; END; --SendPacket SendSequencedPacket: PROC [b: NSBuffer.Buffer] = --PROCESS: RETRANSMITTER, RECEIVER, CLIENT SENDER BEGIN body: NSBuffer.Body = b.ns; b.requeueProcedure ¬ RequeueProcedure; b.fo.time ¬ System.GetClockPulses[]; --record packet send time b.fo.tries ¬ 1; --number of times we transmitted this packet body.packetType ¬ sequencedPacket; --but of course body.destination ¬ connection.remoteAddr; body.sourceConnectionID ¬ connection.localID; body.destinationConnectionID ¬ connection.remoteID; SendPacket[b]; --da dah! END; --SendSequencedPacket SendToLocal: PROC[cH: Socket.ChannelHandle, b: NSBuffer.Buffer] = BEGIN --This is an optimization for local ethernet transmission body: NSBuffer.Body = b.ns; b.fo.status ¬ goodCompletion; body.source ¬ connection.localAddr; b.fo.type ¬ ns; b.fo.context ¬ xmtr.context; b.fo.network ¬ xmtr.context.network; body.transportControl ¬ [FALSE, 0, 0]; IF xmtr.checksums THEN Checksums.SetChecksum[body] ELSE body.checksum ¬ 177777B; Protocol1.EncapsulateAndTransmit[LOOPHOLE[b], @connection.remoteAddr.host]; END; --SendToLocal SetQuencing: ENTRY PROC[action: {up, down}] = BEGIN SELECT action FROM up => BEGIN << PROCESS: REQUEUE PROCEDURE (DISPATCHER) Routers are in trouble - start slowing transmit. How much? Double the current value, whatever that is. For how long? Some multiple of the rexmtr.calculation interval. This will up xmtr.quenchIncr by 4. The clock that decrements it is the RETRANSMITTER process and is running every 5 seconds. That gives us 20 seconds of quench before we drop by half. >> IF xmtr.quenchIncr < xmtrDefaults.totalQuench THEN BEGIN xmtr.quenchIncr ¬ xmtr.quenchIncr + 4; --notice the times xmtr.interval ¬ 2 * xmtr.interval; --double it END; END; down => BEGIN << PROCESS: RETRANSMITTER Check to see if it's time to try speeding up again. The "up" code will only double 4 times. The "down" code will only halve every 4th time through. The two are in competition with each other. If xmtr.quenchIncr = 0 then normalize using the current retransmission interval and the distance to the remote. If xmtr.quenchIncr goes to zero, find out how far away the remote is and normalize using the current retransmission interval. If the value of xmtr.quenchIncr MOD 4 = 0, then half the interval. >> SELECT TRUE FROM (xmtr.quenchIncr = 0) => --we're running free and easy xmtr.interval ¬ rexmtr.interval / (2 * connection.hops); ((xmtr.quenchIncr ¬ PRED[xmtr.quenchIncr]) = 0) => --just cleared BEGIN connection.hops ¬ Router.GetDelayToNet[connection.remoteAddr.net ! Router.NoTableEntryForNet => CONTINUE]; --it shouldn't fail, but xmtr.interval ¬ rexmtr.interval / (2 * connection.hops); END; ((xmtr.quenchIncr MOD 4) = 0) => --still backing out xmtr.interval ¬ xmtr.interval / 2; --halve the interval ENDCASE; --do nothing this time END; ENDCASE; END; --SetQuencing SetSenderSizeLimit: PROC [size: CARDINAL] = {connection.maxSppBytes ¬ size}; StartPacketStream: PUBLIC SppOps.StartPacketStreamProc = << PROC [local, remote: System.NetworkAddress, localID, remoteID: NSTypes.ConnectionID, timeout: NSTypes.WaitTime, class: PacketStream.ClassOfService, establish: BOOLEAN] RETURNS [ PacketStream.Handle, System.NetworkAddress, NSTypes.ConnectionID]; >> --PROCESS: CLIENT BEGIN InitializePSObject[]; SppNegotiation.InitializeSppState[local, remote, localID, remoteID, timeout]; StartPacketStreamInternal[timeout, establish]; RETURN[@packetStreamObject, connection.remoteAddr, connection.remoteID]; END; --StartPacketStream NewStartPacketStream: PUBLIC SppNegotiation.NewStartPacketStreamProc = << PROC [local, remote: NewNetworkStream.TSap, negotiationParameters: NewNetworkStream.NegotiationParameters, hints: SppNegotiation.NegotiationHints, timeout: NSTypes.WaitTime] RETURNS [PacketStream.Handle]; >> --PROCESS: CLIENT BEGIN InitializePSObject[]; SppNegotiation.NewInitializeSppState[ local, remote, negotiationParameters, hints, timeout]; StartPacketStreamInternal[timeout, hints.activeEstablish]; remote­ ¬ [connection.remoteAddr, connection.remoteID]; RETURN[@packetStreamObject]; END; --NewStartPacketStream StartPacketStreamInternal: PROCEDURE [timeout: NSTypes.WaitTime, establish: BOOLEAN] = BEGIN time: LONG CARDINAL ¬ System.GetClockPulses[]; SELECT connection.state FROM (unestablished) => BEGIN Process.EnableAborts[@isEstablished]; Process.SetTimeout[@isEstablished, Process.MsecToTicks[2000]]; connection.state ¬ IF establish THEN activeEstablish ELSE waitEstablish; SetWaitTime[MAX[4000, timeout]]; --force at least two xmts of request rexmtr.process ¬ FORK Retransmitter[]; rcvr.process ¬ FORK Receiver[]; DO --UNTIL ConnectionFailed | established | open IF establish THEN SystemPacket[TRUE, TRUE]; Wait[@isEstablished]; SELECT TRUE FROM (connection.state = established), (connection.state = open) => EXIT; (connection.state = suspended) => SIGNAL PacketStream.ConnectionFailed[connection.whyFailed]; ((System.GetClockPulses[] - time) > rcvr.interval) => BEGIN time ¬ System.GetClockPulses[]; SIGNAL PacketStream.ConnectionFailed[timeout]; END; ENDCASE; ENDLOOP; END; ENDCASE => BEGIN SystemPacket[FALSE, TRUE]; rexmtr.process ¬ FORK Retransmitter[]; rcvr.process ¬ FORK Receiver[]; END; SetWaitTime[timeout]; --now set the user's timeout request --LOCAL NETWORK EXPEDITED DELIVERY --In effect for directly connection ethernets only (and not ME) IF (connection.localAddr.host # connection.remoteAddr.host) THEN BEGIN FOR device: Device ¬ Driver.GetDeviceChain[], device.next UNTIL device = NIL DO IF device.device # ethernet THEN LOOP; xmtr.context ¬ Protocol1.GetContext[device, ns]; IF xmtr.context.netNumber = connection.remoteAddr.net THEN {xmtr.transmitProc ¬ SendToLocal; EXIT}; ENDLOOP; END; END; --StartPacketStreamInternal StopPacketStream: PUBLIC SppOps.StopPacketStreamProc = --PROCESS: CLIENT STREAM DELETION BEGIN OPEN pool: connection.pool; connection.state ¬ terminating; IF rcvr.process # NIL THEN {Process.Abort[rcvr.process]; [] ¬ JOIN rcvr.process}; IF rexmtr.process # NIL THEN {Process.Abort[rexmtr.process]; [] ¬ JOIN rexmtr.process}; --Flush the retransmitter and receiver tables CleanupTable[@rexmtr.table]; CleanupTable[@rcvr.table]; Socket.Delete[connection.socket]; END; --StopPacketStream SuspendStream: ENTRY PROCEDURE [ suspend: PacketStream.SuspendReason, fail: PacketStream.FailureReason] = --PROCESS: RECEIVER OR RETRANSMITTER BEGIN SELECT connection.state FROM suspended, terminating => NULL; ENDCASE => BEGIN --No possibility of signals connection.stateBeforeSuspension ¬ connection.state; connection.state ¬ suspended; connection.whySuspended ¬ suspend; connection.whyFailed ¬ fail; END; << The following processes might be waiting and if we don't notify them here, they may hang forever, a very long time. All of these are in loops that go back and test the current state before continuing and will signal "ConnectionSuspended" in this case. >> NOTIFY rcvr.table.condition; NOTIFY xmtr.newAllocation; NOTIFY attn.table.condition; << If there is a client waiting for buffers, then abort him. The GetBuffer code will trap the aborted and translate it into ConnectionSuspended. If we don't do this, the client may wait forever in the BufferMgr. >> IF xmtr.clientProcess # NIL THEN Process.Abort[xmtr.clientProcess]; END; --SuspendStream SystemPacket: PROC[sendAck, wait: BOOLEAN ¬ FALSE] = --PROCESS: RECEIVER OR RETRANSMITTER BEGIN b: NSBuffer.Buffer ¬ NSBuffer.GetBuffer[ aH: connection.pool, function: send, wait: wait, size: NSTypes.bytesPerSppHeader + NSTypes.bytesPerIDPHeader]; IF b # NIL THEN BEGIN body: NSBuffer.Body = b.ns; body.pktLength ¬ PacketStream.bytesPerSequencedPktHeader; body.systemPacket ¬ TRUE; --that's what this one is all about body.sendAck ¬ sendAck OR (connection.state < open); --still establishing body.attention ¬ body.endOfMessage ¬ FALSE; body.subtype ¬ 0; --all system packets have vanilla SST body.sequenceNumber ¬ xmtr.nextSeq; --assign sequence number IF CommFlags.doStats THEN Stats.StatIncr[statSystemPacketsSent]; SendSequencedPacket[b]; --get current state informaion and ship END; END; --SystemPacket SetState: PROC[newState: PacketStream.State] = BEGIN [] ¬ GetState[]; --might glitch, that's why we call it connection.state ¬ SELECT newState FROM active => established, ENDCASE => suspended; END; --SetState SetWaitTime: ENTRY PROCEDURE [time: NSTypes.WaitTime] = BEGIN --No possibility of signals --time IN[maxMsecToPulses..INFINITY] => silly client NEVER wake up! rcvr.waitTime ¬ time; --record in case he forgets rcvr.interval ¬ IF time > maxMsecToPulses THEN LAST[LONG CARDINAL] ELSE System.MicrosecondsToPulses[time*1000]; IF rcvr.interval = LAST[LONG CARDINAL] THEN Process.DisableTimeout[@rcvr.table.condition] ELSE Process.SetTimeout[ @rcvr.table.condition, CommUtil.PulsesToTicks[[rcvr.interval]]]; END; --SetWaitTime TakeFromUser: PROC [b: NSBuffer.Buffer] = --PROCESS: CLIENT SENDING BEGIN AssignSeqAndAssertSend: ENTRY PROC = BEGIN ENABLE UNWIND => NULL; assertion: INTEGER; sequenceNumber: CARDINAL; --assign current sequence number sequenceNumber ¬ body.sequenceNumber ¬ xmtr.nextSeq; --assign seq # xmtr.nextSeq ¬ xmtr.nextSeq + 1; --consume seq # DO --EXITs (success) or ERRORs (failure) SELECT connection.state FROM established, open => BEGIN assertion ¬ INTEGER[xmtr.maxSeq - sequenceNumber]; SELECT TRUE FROM --way past end of alloation (assertion < -rcvrDefaults.accept) => GOTO wait; --attention packet past end of allocation...expedited transmission (body.attention) => body.sendAck ¬ TRUE; --is xmtr blocked due to retransmission in progress? (xmtr.blocked) => GOTO wait; --at end of allocation...request ack this time, wait next time (assertion = 0) => body.sendAck ¬ TRUE; --past end of allocation with non-expedited packet..wait (assertion < 0) => GOTO wait; --pipe ~50% full...request ack this time, keep xmt'ng ((sequenceNumber - xmtr.unackedSeq) = SUCC[xmtr.maxAlloc]/2) => body.sendAck ¬ xmtr.transmitProc # SendToLocal; --pipe not full & not at end of allocation...go, go, go ENDCASE --(assertion > 0)-- => body.sendAck ¬ FALSE; RETURN; --in all cases that didn't GOTO wait EXITS wait => WAIT xmtr.newAllocation; END; suspended => RETURN WITH ERROR PacketStream.ConnectionSuspended[connection.whySuspended]; unestablished, activeEstablish, waitEstablish => IF CommFlags.doDebug THEN Driver.Glitch[StreamNotEstablished]; terminating => IF CommFlags.doDebug THEN Driver.Glitch[StreamTerminating]; ENDCASE; ENDLOOP; END; --AssignSeqAndAssertSend << Even if AssignSeqAndAssertSend fails, send the packet. This gets it into the retrans table since we already consumed the sequence number. This is most appropriate when the operation is ABORTED, but also works for suspended. >> body: NSBuffer.Body = b.ns; AssignSeqAndAssertSend[! UNWIND => SendSequencedPacket[b]]; SendSequencedPacket[b]; --so send the packet already IF CommFlags.doStats THEN BEGIN Stats.StatIncr[statDataPacketsSent]; Stats.StatBump[statDataBytesSent, body.pktLength - PacketStream.bytesPerSequencedPktHeader]; END; END; --TakeFromUser --BEGINNING OF BYTE STREAM IMPLEMENTATION LeftAndRight: TYPE = MACHINE DEPENDENT RECORD [left, right: Environment.Byte]; << A client will typicaly have three processes accessing this module. The first receives data, the second waits for attentions, and the third transmits data, sends attentions and changes the subsequence type. As a consequence it is not necessary for this module to be a monitor, since there is no interaction between the three processes in this module. The interaction occurs in the PktStreamInstance module, which is a monitor. Multiple client processes must not perform data transfer in one direction; the result is unpredicatble. Care should be taken when deleting the stream and therefore this module. >> --The vector of procedures for operating on, and controlling the network stream controlObject: PUBLIC NetworkStreamInternal.ControlObject; --input input: PUBLIC RECORD [ body: NSBuffer.Body, --pointer to interesting data b: NSBuffer.Buffer, --current input buffer in use|NIL finger: CARDINAL, --bytes consumed from input.buffer length: CARDINAL, --bytes of data in input.buffer sst: Stream.SubSequenceType, --current input sst attn, end: BOOLEAN]; --delayed status to report --output output: PUBLIC RECORD [ b: NSBuffer.Buffer, --current input buffer in use|NIL finger: CARDINAL, --bytes consumed from input.b sst: Stream.SubSequenceType, --current output sst sstSent: BOOLEAN, --has sst been reported to remote? bufferSize: CARDINAL]; --max bytes permitted in output GetByte: Stream.GetByteProcedure = BEGIN IF input.b # NIL AND input.finger + 2 < input.length THEN BEGIN --"+2" lets GetBlock give back the buffer if we take the last byte byte ¬ input.b.ns.sppBytes[input.finger]; input.finger ¬ input.finger + 1; RETURN; END ELSE BEGIN array: PACKED ARRAY [0..1] OF Stream.Byte; [] ¬ GetBlock[sH, [@array, 0, 1], [FALSE, FALSE, FALSE, TRUE, TRUE, TRUE]]; RETURN[array[0]]; END; END; GetWord: Stream.GetWordProcedure = BEGIN OPEN w: LOOPHOLE[word, LeftAndRight]; w ¬ [left: GetByte[sH], right: GetByte[sH]]; END; GetBlock: Stream.GetProcedure = BEGIN moved: CARDINAL; iBlock: Stream.Block; why ¬ normal; sst ¬ input.sst; bytesTransferred ¬ 0; << NOTE: gets with block.startIndex = block.stopIndexPlusOne. They do not alter the stream's state, i.e., they return the same results for multiple calls. >> WHILE block.startIndex < block.stopIndexPlusOne DO << PENDING ATTENTION Caused by a packet with an sst change and attention and the sst was returned or signalled and not resumed. >> IF input.attn THEN BEGIN input.attn ¬ FALSE; SELECT options.signalAttention FROM TRUE => SIGNAL Stream.Attention[block.startIndex]; ENDCASE => RETURN[bytesTransferred, attention, sst]; END; UNTIL (input.b # NIL) DO << PENDING END RECORD Caused by an empty packet with an sst change and an endRecord status where the sst was returned or signalled and not resumed. >> IF input.end THEN BEGIN input.end ¬ FALSE; IF options.terminateOnEndRecord THEN BEGIN SELECT options.signalEndRecord FROM TRUE => SIGNAL Stream.EndRecord[block.startIndex]; ENDCASE => RETURN[bytesTransferred, endRecord, sst]; END; END; <> IF (input.b ¬ GetForUser[]) = NIL THEN --timeout BEGIN SELECT options.signalTimeout FROM TRUE => {SIGNAL Stream.TimeOut[block.startIndex]; LOOP}; FALSE => RETURN[bytesTransferred, timeout, sst]; ENDCASE; END; input.body ¬ input.b.ns; --make local frame copy input.finger ¬ 0; --reset staring index input.length ¬ GetSppDataLength[input.b]; --won't change until next get input.attn ¬ input.body.attention; --in case both in one packet sst ¬ input.body.subtype; <> IF input.finger = input.length THEN --not much data BEGIN input.end ¬ input.body.endOfMessage AND options.terminateOnEndRecord; ReturnBuffer[input.b]; input.b ¬ NIL; END; <> IF (sst # input.sst) THEN --sst change BEGIN input.sst ¬ sst; --record for later SELECT options.signalSSTChange FROM TRUE => SIGNAL Stream.SSTChange[sst, block.startIndex]; ENDCASE => RETURN[bytesTransferred, sstChange, sst]; END; ENDLOOP; --UNTIL (input.b # NIL) <> IF input.attn THEN BEGIN input.attn ¬ FALSE; SELECT options.signalAttention FROM TRUE => SIGNAL Stream.Attention[block.startIndex]; ENDCASE => RETURN[bytesTransferred, attention, sst]; END; <> iBlock ¬ [@input.body.sppBytes, input.finger, input.length]; moved ¬ ByteBlt.ByteBlt[block, iBlock]; bytesTransferred ¬ bytesTransferred + moved; block.startIndex ¬ block.startIndex + moved; input.finger ¬ input.finger + moved; <> IF input.finger = input.length THEN BEGIN input.end ¬ input.body.endOfMessage AND options.terminateOnEndRecord; ReturnBuffer[input.b]; input.b ¬ NIL; <> IF (options.signalLongBlock AND (block.startIndex < block.stopIndexPlusOne)) THEN SIGNAL Stream.LongBlock[block.startIndex]; <> IF input.end THEN BEGIN input.end ¬ FALSE; SELECT options.signalEndRecord FROM TRUE => SIGNAL Stream.EndRecord[block.startIndex]; ENDCASE => RETURN[bytesTransferred, endRecord, sst]; END; END; ENDLOOP; --block.startIndex < block.stopIndexPlusOne <> IF (options.signalShortBlock AND (input.b # NIL)) THEN ERROR Stream.ShortBlock; END; --GetBlock << The strategy on the transmission side, is to allocate a buffer only when there is data to be copied into the buffer, or if an empty packet must be transmitted. State information is kept around for things like whether a new SST has been sent to the other end or not, incase it is changed without sending any intervening data. >> --Send a client's block of data in one or more packets. PutBlock: Stream.PutProcedure = BEGIN moved: CARDINAL; oBlock: Stream.Block; --client specified empty block? SELECT TRUE FROM (block.startIndex # block.stopIndexPlusOne) => WHILE block.startIndex < block.stopIndexPlusOne DO IF output.b = NIL THEN BEGIN output.b ¬ GetOutputBuffer[]; output.finger ¬ 0; output.bufferSize ¬ connection.maxSppBytes; END; oBlock ¬ [ blockPointer: @output.b.ns.sppBytes, startIndex: output.finger, stopIndexPlusOne: output.bufferSize]; moved ¬ ByteBlt.ByteBlt[oBlock, block]; block.startIndex ¬ block.startIndex + moved; --did we just fill the packet? IF (output.finger ¬ output.finger + moved) = output.bufferSize THEN --watch out for packet and client buffer ending at same time SendNow[sH, (block.startIndex >= block.stopIndexPlusOne) AND endRecord]; REPEAT << Check for client requested endRecord, watching out for packet and client buffer ending at same time. IF output.b = NIL then the packet was sent from inside the loop. >> FINISHED => IF endRecord AND (output.b # NIL) THEN SendNow[sH, TRUE]; ENDLOOP; (endRecord) => SendNow[sH, TRUE]; ENDCASE; END; --PutBlock PutByte: Stream.PutByteProcedure = BEGIN IF output.b # NIL AND output.finger + 2 < output.bufferSize THEN BEGIN --"+2" lets PutBlock flush the buffer if we fill the last byte output.b.ns.sppBytes[output.finger] ¬ byte; output.finger ¬ output.finger + 1; END ELSE BEGIN array: PACKED ARRAY [0..1] OF Stream.Byte ¬ [byte, ]; PutBlock[sH, [@array, 0, 1], FALSE]; END; END; PutWord: Stream.PutWordProcedure = BEGIN OPEN w: LOOPHOLE[word, LeftAndRight]; PutByte[sH, w.left]; PutByte[sH, w.right]; END; SendNow: Stream.SendNowProcedure = BEGIN IF output.b = NIL THEN {output.finger ¬ 0; output.b ¬ GetOutputBuffer[]}; output.b.ns.endOfMessage ¬ endRecord; FlushOutputBuffer[]; END; --SendNow << Set the SST to the specified value and has some side effects. We assume that the SST is initially = 0, and the first change causes no empty packet to be sent. >> SetSST: Stream.SetSSTProcedure = BEGIN IF sst # output.sst THEN BEGIN FlushOutputBuffer[]; --flush the last buffer if there was one IF NOT output.sstSent THEN --there was no buffer to flush for the old SST so send an empty packet BEGIN output.finger ¬ 0; output.b ¬ GetOutputBuffer[]; FlushOutputBuffer[]; END; --remember the new SST output.sst ¬ sst; output.sstSent ¬ FALSE END; END; --SetSSt --Send one byte of data in a packet with the attention bit set. SendAttention: Stream.SendAttentionProcedure = BEGIN FlushOutputBuffer[]; --flush the last buffer if there was one output.b ¬ GetOutputBuffer[]; output.b.ns.sppBytes[0] ¬ byte; output.finger ¬ 1; --alwasys one byte long output.b.ns.attention ¬ TRUE; --this is what this is all about FlushOutputBuffer[]; --with attention byte END; --SendAttention --Wait until an attention arrives or an ERROR is raised. WaitAttention: Stream.WaitAttentionProcedure = {RETURN[ExtractAttention[]]}; --Flush (i.e. sends out) the output.b if there is one. FlushOutputBuffer: PROCEDURE = BEGIN EnterMonitor: ENTRY PROC = INLINE {b ¬ output.b; output.b ¬ NIL}; b: NSBuffer.Buffer; EnterMonitor[]; --to flush the buffer IF b # NIL THEN BEGIN b.ns.subtype ¬ output.sst; output.sstSent ¬ TRUE; SetSppDataLength[b, output.finger]; TakeFromUser[b]; END; END; --FlushOutputBuffer --Set the length of the sequenced packet given the length of data. SetSppDataLength: PROCEDURE [b: NSBuffer.Buffer, length: CARDINAL] = INLINE {Socket.SetPacketBytes[b, length + NSTypes.bytesPerSppHeader]}; SetTimeout: Stream.SetTimeoutProcedure = {SetWaitTime[waitTime]}; GetTimeout: Stream.GetTimeoutProcedure = {RETURN[GetWaitTime[]]}; --This procedure returns the amount of data in the sequenced packet. GetSppDataLength: PROCEDURE [b: NSBuffer.Buffer] RETURNS [CARDINAL] = INLINE {RETURN[Socket.GetPacketBytes[b] - NSTypes.bytesPerSppHeader]}; GetSST: Stream.GetSSTProcedure = {sst ¬ output.sst}; StopByteStream: PUBLIC SppOps.StopByteStreamProc = BEGIN controlObject.streamObject ¬ Stream.defaultObject; --no longer usable IF input.b # NIL THEN {ReturnBuffer[input.b]; input.b ¬ NIL}; IF output.b # NIL THEN {NSBuffer.ReturnBuffer[output.b]; output.b ¬ NIL}; END; --StopByteStream StartByteStream: PUBLIC SppOps.StartByteStreamProc = BEGIN controlObject ¬ [streamObject: Stream.defaultObject, psH: psH]; --Now fill in NetworkStream Specifics controlObject.streamObject.getByte ¬ GetByte; controlObject.streamObject.putByte ¬ PutByte; controlObject.streamObject.getWord ¬ GetWord; controlObject.streamObject.putWord ¬ PutWord; controlObject.streamObject.get ¬ GetBlock; controlObject.streamObject.put ¬ PutBlock; controlObject.streamObject.setSST ¬ SetSST; controlObject.streamObject.getSST ¬ GetSST; controlObject.streamObject.sendAttention ¬ SendAttention; controlObject.streamObject.waitAttention ¬ WaitAttention; controlObject.streamObject.sendNow ¬ SendNow; controlObject.streamObject.setTimeout ¬ SetTimeout; controlObject.streamObject.getTimeout ¬ GetTimeout; --input input.b ¬ NIL; input.sst ¬ 0; input.attn ¬ input.end ¬ FALSE; --output output.b ¬ NIL; output.sst ¬ 0; output.sstSent ¬ TRUE; output.bufferSize ¬ 0; RETURN[@controlObject.streamObject]; END; --StartByteStream --MAINLINE CODE startByteStream ¬ StartByteStream; startPacketStream ¬ StartPacketStream; stopByteStream ¬ StopByteStream; stopPacketStream ¬ StopPacketStream; newStartPacketStream ¬ NewStartPacketStream; END. --of NetworkStreamImpl module LOG 17-May-84 10:49:49 AOF Post Klamath 5-Mar-85 17:59:29 AOF Treat error packet[resourceLimits] like listenerReject 20-Mar-85 11:23:17 AOF Fixing PutBlock of no bytes with endRecord = TRUE 20-Mar-85 17:02:58 AOF Deliver rec'd packet even if stream suspended 25-Mar-85 17:15:21 AOF Put in a 11.0 fix that was missed 'cause of fork 12-Apr-85 11:09:50 AOF Acceptance of early packets, 16 bit modulo arithmetic 25-Apr-85 9:30:35 AOF Delete LOOPHOLEs of TransferStatus 3-Jun-85 14:51:47 AOF Another shot at the 20-Mar-85 11:23:17 fix 25-Nov-85 8:13:32 AOF Support for INR's congestionWarning & congestionDiscard 25-Nov-85 8:13:32 AOF Cut rexmtr.ceiling from 36 to 24 seconds. 4-Dec-85 15:45:02 AOF 16 buffer rexmt and rcvr tables 4-Dec-85 16:55:05 AOF variable length send buffers, toggle idle probing 12-Dec-85 10:24:13 AOF Set ackReq on system packets when establishing 9-Jan-86 15:37:29 AOF Better test for 'opening' a connection 11-Apr-86 9:19:46 AOF Switch to disable checksums on local net 18-Apr-86 16:41:29 AOF Get buffer's length from Buffer.DataWordsPerRawBuffer[] 22-May-86 11:41:24 SMA Remove dependencies on Buffer, changes for new encap. 18-Aug-86 17:03:33 AOF Local caching of b.ns. 28-Aug-86 10:47:40 AOF Use priorityPilotRealtimeSwappable for receiver. 5-Oct-86 18:53:31 AOF Look'n at extending the buffer sizes. 15-Oct-86 15:06:18 AOF Reorder status checks in RequeueProcedure 2-Nov-86 16:18:10 AOF More large packet stuff 17-Nov-86 13:41:00 AOF Renaming priorities 20-Nov-86 9:45:03 AOF Toying with HGM's half step increase in acking 7-Jan-87 16:49:53 AOF MDS Tweeks (POINTERS got longer) 26-Mar-87 17:30:15 AOF Fixed AR#10142 (assign values to PutWord and GetTimeout) 13-Apr-87 18:39:31 AOF Tightened up monitoring logic in GetSendBuffer 7-May-87 11:20:07 AOF Copy value of lastProbe in rexmtr in case rcvr overwrites 13-May-87 12:12:41 AOF Rate controlled transmission 9-Aug-87 15:54:20 AOF Always ack duplicate packets (VAX, etc). 9-Sep-87 8:20:53 AOF User proc variables to start instance 3-Nov-87 11:50:37 MXI Added NewStartPacketStream. Moved InitializeSppState to NewNetworkStreamImpl