ArpaTCPOpsImpl.mesa
Copyright (C) 1983 by Xerox Corporation. All rights reserved. The following program was created in 1983 but has not been published within the meaning of the copyright law, is furnished under license, and may not be used, copied and/or disclosed except in accordance with the terms of said license.
Last Edited by: Nichols, September 1, 1983 4:27 pm
Last Edited by: DCraft, November 26, 1983 6:59 pm
Last Edited by: Taft, January 8, 1984 3:24 pm
Last Edited by: HGM, April 23, 1984 4:58:48 pm PST
Doug Terry, November 22, 1987 10:44:02 pm PST
Hal Murray May 16, 1985 3:14:24 am PDT
John Larson, October 11, 1987 9:40:04 pm PDT
DIRECTORY
Arpa USING [Address, nullAddress],
ArpaBuf USING [Protocol],
ArpaIP USING [AllocBuffers, Buffers, CreateHandle, DestroyHandle, FreeBuffers, GetUserBytes, Handle, OnesComplementAddBlock],
Basics USING [BITNOT],
BasicTime USING [GetClockPulses, MicrosecondsToPulses, Pulses, PulsesToMicroseconds],
PrincOpsUtils USING [ByteBlt],
Process USING [Abort, DisableTimeout, MsecToTicks, Pause, SetTimeout],
ArpaTCP USING [DByte, Error, Reason, TCPInfo, Timeout],
ArpaTCPOps USING [Buffer, ConnectionState, defaultProbeTimeout, recvBufferLength, sillyWindowLimit, TCPControlSet, TCPHandle, tcpHdrByteLength, TCPHeaderP, TCPSendBuffer],
ArpaTCPLogging USING [PrintStateChange],
ArpaTCPReceiving USING [ProcessRcvdSegment],
ArpaTCPStates USING [Abort, Close, CloseConnection, CopyHandleList, GetInitialSequenceNumber, Open, ValidHandle],
ArpaTCPTransmit USING [Rexmit, rexmitSleepTime, RepacketizeandRexmit, SendSegment, SendSYN];
ArpaTCPOpsImpl: CEDAR MONITOR LOCKS handle USING handle: TCPHandle
IMPORTS ArpaIP, Basics, BasicTime, PrincOpsUtils, Process, ArpaTCP, ArpaTCPLogging, ArpaTCPReceiving, ArpaTCPStates, ArpaTCPTransmit
EXPORTS ArpaTCPOps
~ BEGIN OPEN ArpaTCPOps;
defaultReceiveWindow: PUBLIC INT ← ArpaTCPOps.recvBufferLength;
probeTimeout: PUBLIC INT ← ArpaTCPOps.defaultProbeTimeout;
repacketizing: PUBLIC BOOLFALSE;
ourLocalAddress: PUBLIC Arpa.Address ← Arpa.nullAddress;
tcpIPHandle: PUBLIC ArpaIP.Handle;
retransmitProcess: PROCESS;
sendBufferLength: PUBLIC INT ← 4000; -- max bytes of unacked data to queue for transmission or retransmission to net
maxTCPDataLength: PUBLIC INT ← 536; -- default TCP Maximum Segment Size is 536
zeroChecksum: BOOLEANFALSE;
Statistics
pktsSent: PUBLIC INT ← 0;
pktsRcvd: PUBLIC INT ← 0;
pktsRexmitted: PUBLIC INT ← 0;
pktsDuplicate: PUBLIC INT ← 0;
pktsWithNoConnection: PUBLIC INT ← 0;
pktsFromFuture: PUBLIC INT ← 0;
pktsFromPast: PUBLIC INT ← 0;
pktsWithBadChecksum: PUBLIC INT ← 0;
Routines used by ArpaTCPMain
Open: PUBLIC PROC [tcpInfo: ArpaTCP.TCPInfo] RETURNS [handle: TCPHandle] ~ {
Attempts to open a new TCP connection. Returns a TCPHandle to use or raises ArpaTCP.OpenFailed.
RETURN ArpaTCPStates.Open[tcpInfo];
};
Close: PUBLIC ENTRY PROC [handle: TCPHandle] ~ {
Shut down the connection normally.
ArpaTCPStates.Close[handle];
};
Abort: PUBLIC ENTRY PROC [handle: TCPHandle] ~ {
Shut down the connection abnormally.
IF handle # NIL THEN ArpaTCPStates.Abort[handle];
};
WaitForListenerOpen: PUBLIC ENTRY PROC [handle: TCPHandle, timeout: INT] ~ {
Wait until the listening connection on this socket is open.
startTime: BasicTime.Pulses;
IF handle.state = listen THEN {
startTime ← BasicTime.GetClockPulses[];
TRUSTED {IF timeout>0
THEN Process.SetTimeout[@handle.notListening, Process.MsecToTicks[MIN[timeout, 5000]]]
ELSE Process.DisableTimeout[@handle.notListening]};
DO
WAIT handle.notListening;
IF handle.state # listen THEN EXIT;
IF handle.dataTimeout >= 0 AND BasicTime.GetClockPulses[]-startTime > BasicTime.MicrosecondsToPulses[handle.dataTimeout*1000] THEN
EXIT;
ENDLOOP;
};
};
SendCurrentDatagram: PUBLIC ENTRY PROC [handle: TCPHandle, push: BOOL] ~ {
The current datagram in the handle is ready to send. Push is true if the TCP push option should be used. Empty datagrams are ignored unless push is true.
ENABLE UNWIND => NULL;
control: TCPControlSet;
GetNewOutputDatagram: PROC ~ {
handle.currentOutputDatagram ← ArpaIP.AllocBuffers[1];
handle.currentOutputPtr ← tcpHdrByteLength;
handle.currentOutputLimit ← handle.currentOutputPtr+handle.maxSegmentSize;
};
ArpaTCPStates.ValidHandle[handle];
control.psh ← push;
SELECT handle.state FROM
listen => {
IF NOT (handle.matchForeignPort AND handle.matchForeignAddr) THEN
ERROR ArpaTCP.Error[neverOpen];
handle.active ← TRUE;
handle.iss ← ArpaTCPStates.GetInitialSequenceNumber[];
handle.sndUna ← handle.iss;
ArpaTCPLogging.PrintStateChange[handle, synSent];
handle.state ← synSent;
ArpaTCPTransmit.SendSYN[handle];
};
synSent, synRcvd, established, closeWait => NULL;
ENDCASE => ERROR ArpaTCP.Error[handle.reason];
QueueSendSegment[handle, handle.currentOutputDatagram, handle.currentOutputPtr-tcpHdrByteLength, control];
GetNewOutputDatagram[];
};
GetNextDatagram: PUBLIC ENTRY PROC [handle: TCPHandle] ~ {
We are through with the current input datagram and would like another. Only returns when new datagram has usable data.
ENABLE UNWIND => NULL;
ArpaTCPStates.ValidHandle[handle];
IF handle.currentInputBuffer # NIL THEN {
ArpaIP.FreeBuffers[handle.currentInputBuffer.datagramPtr];
handle.currentInputBuffer ← NIL;
};
IF handle.readyToReadQueue = NIL THEN {
timeoutTime: BasicTime.Pulses ← SetTimeout[handle.dataTimeout];
WHILE handle.readyToReadQueue=NIL DO
IF NOT ValidRcvState[handle.state] THEN
ERROR ArpaTCP.Error[handle.reason];
IF TimedOut[timeoutTime] THEN {
SIGNAL ArpaTCP.Timeout;
timeoutTime ← SetTimeout[handle.dataTimeout];
};
WAIT handle.dataAvailable;
ENDLOOP;
};
handle.currentInputBuffer ← NARROW[handle.readyToReadQueue.first];
handle.readyToReadQueue ← handle.readyToReadQueue.rest;
handle.rcvWnd ← handle.rcvWnd + handle.currentInputBuffer.dataByteCount;
};
SetUrgent: PUBLIC ENTRY PROC [handle: TCPHandle] ~ {
The urgent pointer is set to the current position in the output stream.
ENABLE UNWIND => NULL;
ArpaTCPStates.ValidHandle[handle];
IF NOT handle.sndUrgent THEN {
handle.sndUrgent ← TRUE;
handle.sndUp ← handle.sndNxt + handle.nBytesToSend;
};
};
WaitForUrgentData: PUBLIC ENTRY PROC [handle: TCPHandle] RETURNS [urgentIndex: INT] ~ {
Wait asynchronously for urgent data indication.
ENABLE UNWIND => NULL;
ArpaTCPStates.ValidHandle[handle];
WHILE NOT handle.urgentMode AND ValidRcvState[handle.state] DO
WAIT handle.urgentAvailable;
ENDLOOP;
handle.urgentMode ← FALSE;
IF ValidRcvState[handle.state] THEN
RETURN [handle.rcvUp]
ELSE
ERROR ArpaTCP.Error[handle.reason];
};
Routines internal to the TCP stuff
TCPChecksum: PUBLIC PROC [data: ArpaIP.Buffers] RETURNS [checksum: ArpaTCP.DByte] ~ TRUSTED {
Find the checksum to be used in the TCP header. Call when the IP header has been properly filled in.
PseudoHeader: TYPE ~ MACHINE DEPENDENT RECORD [
sourceAddr (0): Arpa.Address,
dstnAddr (2): Arpa.Address,
zeroes (4: 0..7): BYTE,
protocol (4: 8..15): ArpaBuf.Protocol,
tcpTotalByteCount (5): ArpaTCP.DByte];
pseudoHeader: PseudoHeader;
tcpHdrPtr: TCPHeaderP ← LOOPHOLE[@data.body];
cs: CARDINAL;
pseudoHeader.sourceAddr ← data.hdr1.source;
pseudoHeader.dstnAddr ← data.hdr1.dest;
pseudoHeader.zeroes ← 0;
pseudoHeader.protocol ← data.hdr1.protocol;
pseudoHeader.tcpTotalByteCount ← ArpaIP.GetUserBytes[data].bodyBytes;
IF pseudoHeader.tcpTotalByteCount MOD 2 # 0 THEN data.body.bytes[pseudoHeader.tcpTotalByteCount] ← 0;
cs ← ArpaIP.OnesComplementAddBlock[ptr: tcpHdrPtr, count: (pseudoHeader.tcpTotalByteCount+1)/2, initialSum: Basics.BITNOT[tcpHdrPtr.checksum]]; -- Start with negative of the checksum that's in the header so that we don't have to smash it to zero to compute the real checksum.
cs ← ArpaIP.OnesComplementAddBlock[ptr: @pseudoHeader, count: PseudoHeader.SIZE, initialSum: cs];
IF zeroChecksum
THEN RETURN[0] ELSE
RETURN [Basics.BITNOT[cs]]; -- return one's complement of computed sum
};
ChecksumsMatch: PUBLIC PROC [c1, c2: ArpaTCP.DByte] RETURNS [BOOL] ~ {
RETURN [c1 = c2 OR ((c1 = 0 OR c1 = 65535) AND (c2 = 0 OR c2 = 65535))];
};
ValidRcvState: PROC [state: ConnectionState] RETURNS [BOOL] ~ INLINE {
Returns true if state is valid for receiving new segments.
RETURN [state IN [listen..finWait2]];
};
SetTimeout: PUBLIC PROC [delta: INT, base: BasicTime.Pulses ← BasicTime.GetClockPulses[]] RETURNS [BasicTime.Pulses] ~ {
Returns a timeout time computed by adding delta milliseconds to the specified base time. If delta is negative, meaning "never time out", returns zero, which is specifically defined (in TimedOut) to mean "never time out" and is never otherwise returned.
RETURN [IF delta>=0 THEN MAX[base+BasicTime.MicrosecondsToPulses[delta*1000], 1] ELSE 0];
};
TimedOut: PUBLIC PROC [timeoutTime: BasicTime.Pulses] RETURNS [BOOL] ~ {
Returns true if the specified time has been reached.
RETURN [timeoutTime#0 AND LOOPHOLE[BasicTime.GetClockPulses[]-timeoutTime, INT] >= 0];
};
Private
CheckRexmitQueues: PROC = {
This procedure is called periodically to check the RexmitQueue on each handle on the handleList. It checks the first segment on each retransmit queue; if the segment has timed out, the connection is deleted, if it is time to retransmit the segment, the segment is retransmitted.
l: LIST OF REF ANY;
FOR l ← ArpaTCPStates.CopyHandleList[], l.rest WHILE l # NIL DO
handle: TCPHandle ← NARROW[l.first];
CheckRexmit[handle ! ArpaTCP.Error => CONTINUE];
ENDLOOP; -- get next connection
}; -- CheckRexmitQueues
CheckRexmit: ENTRY PROC [handle: TCPHandle] ~ {
Check the retransmit queues for a handle. Put here to get the handle lock.
ENABLE UNWIND => NULL;
ArpaTCPStates.ValidHandle[handle];
IF handle.state = timeWait AND TimedOut[handle.timeWaitTime] THEN
ArpaTCPStates.CloseConnection[handle, handle.reason]
ELSE IF handle.rexmitQueue # NIL THEN {
tcpSendBufferPtr: REF TCPSendBuffer ← NARROW[handle.rexmitQueue.first];
IF TimedOut[tcpSendBufferPtr.timeoutTime] THEN { -- timed out, close connection
now: BasicTime.Pulses ← BasicTime.GetClockPulses[];
ArpaTCPStates.CloseConnection[handle, transmissionTimeout]; }
ELSE
IF TimedOut[tcpSendBufferPtr.rexmitTime] THEN
IF repacketizing THEN -- optionally repacketize on rexmit
ArpaTCPTransmit.RepacketizeandRexmit[handle, tcpSendBufferPtr]
ELSE
ArpaTCPTransmit.Rexmit[handle, tcpSendBufferPtr];
};
};
SequenceCompare: PROC [a, b: LONG CARDINAL] RETURNS [Basics.Comparison] ~ {
Determines whether a is "less", "equal", or "greater" than b in mod 2^32 arithmetic. That is, a<b if b-a < 2^31, i.e., the positive distance from a to b is less than halfway around the ring. This is useful for comparing sequence numbers, timeout times, and other quantities that are expected to wrap around.
RETURN[SELECT LOOPHOLE[a-b, INT] FROM
<0 => less,
=0 => equal,
>0 => greater,
ENDCASE => ERROR];
};
QueueSendSegment: INTERNAL PROC [handle: TCPHandle, sendDatagram: ArpaIP.Buffers, dataByteCount: INT, ctl: TCPControlSet] ~ TRUSTED {
Packets are only added to the ToNet queue if they fit within the current send window. If the window is smaller than the packet to be transmitted then the packet may be split into two or more packets such the the first piece can be sent immediately and the other pieces sent later (when the window opens up more).
timeoutTime: BasicTime.Pulses ← SetTimeout[handle.dataTimeout];
bytesInWindow: INT;
Send what I can now
bytesInWindow ← MIN[handle.sndWnd - handle.nBytesToSend, dataByteCount];
IF bytesInWindow > 0
THEN ArpaTCPTransmit.SendSegment[handle, sendDatagram, bytesInWindow, ctl]
ELSE bytesInWindow ← 0;
Send rest later by copying bytes from sendDatagram
WHILE bytesInWindow < dataByteCount DO
bytesToSend: INT;
newDatagram: Buffer ← ArpaIP.AllocBuffers[1];
WaitForOpenWindow[handle];
bytesToSend ← MIN[handle.sndWnd - handle.nBytesToSend, dataByteCount - bytesInWindow];
IF bytesToSend < 0 THEN bytesToSend ← 0; -- maybe the window shrunk?
Copy data to new buffer
bytesToSend ← PrincOpsUtils.ByteBlt[
from: [blockPointer: @sendDatagram.body, startIndex: tcpHdrByteLength+bytesInWindow, stopIndexPlusOne: tcpHdrByteLength+bytesInWindow+bytesToSend],
to: [blockPointer: @newDatagram.body, startIndex: tcpHdrByteLength, stopIndexPlusOne: tcpHdrByteLength+bytesToSend] ];
ArpaTCPTransmit.SendSegment[handle, newDatagram, bytesToSend, ctl];
bytesInWindow ← bytesInWindow + bytesToSend;
Timeout if transmission not completed in a reasonable period
IF TimedOut[timeoutTime] THEN {
SIGNAL ArpaTCP.Timeout;
timeoutTime ← SetTimeout[handle.dataTimeout]; };
ENDLOOP;
IF ctl.psh THEN WaitForAcks[handle];
}; -- QueueSendSegment
WaitForOpenWindow: INTERNAL PROC [handle: TCPHandle, minWindow: INT ← sillyWindowLimit] RETURNS [] ~ {
Wait until send window is at least minWindow in size (or the probeTimeout expires).
timeoutTime: BasicTime.Pulses ← SetTimeout[probeTimeout];
WHILE handle.sndWnd - handle.nBytesToSend < minWindow DO
WAIT handle.windowAvailable;
SELECT handle.state FROM
synSent, synRcvd, established, closeWait => NULL;
ENDCASE => ArpaTCP.Error[handle.reason];
IF TimedOut[timeoutTime] THEN EXIT;
ENDLOOP;
};
WaitForAcks: INTERNAL PROC [handle: TCPHandle] RETURNS [] ~ {
Wait for what we have sent to get acked
start: BasicTime.Pulses ← BasicTime.GetClockPulses[];
timeoutTime: BasicTime.Pulses ← SetTimeout[handle.dataTimeout];
WHILE handle.toNetQueue # NIL OR handle.rexmitQueue # NIL DO
WAIT handle.windowAvailable;
SELECT handle.state FROM
synSent, synRcvd, established, closeWait => NULL;
ENDCASE => ArpaTCP.Error[handle.reason];
IF TimedOut[timeoutTime] THEN {
now: BasicTime.Pulses ← BasicTime.GetClockPulses[];
duration: LONG CARDINAL = BasicTime.PulsesToMicroseconds[now-start];
SIGNAL ArpaTCP.Timeout;
timeoutTime ← SetTimeout[handle.dataTimeout]; };
ENDLOOP;
};
RetransmitProcessProc: PROC ~ {
Loops and calls CheckRexmitQueues every so often.
DO
Process.Pause[Process.MsecToTicks[ArpaTCPTransmit.rexmitSleepTime]];
CheckRexmitQueues[];
ENDLOOP;
};
StartupTCP: PROC ~ {
tcpIPHandle ← ArpaIP.CreateHandle[protocol: tcp, recvProc: ArpaTCPReceiving.ProcessRcvdSegment, recvErrorProc: NIL, acceptLongDatagrams: FALSE];
TRUSTED {
retransmitProcess ← FORK RetransmitProcessProc[];
};
};
ShutdownTCP: PROC ~ {
TRUSTED {
Process.Abort[retransmitProcess];
};
ArpaIP.DestroyHandle[tcpIPHandle];
};
StartupTCP[];
END.