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.
TCPOpsImpl.mesa
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
Hal Murray May 16, 1985 3:14:24 am PDT
John Larson, April 14, 1986 11:23:52 pm PST
DIRECTORY
Basics USING [BITNOT],
BasicTime USING [GetClockPulses, MicrosecondsToPulses, Pulses, PulsesToMicroseconds],
List USING [Nconc1],
Process USING [Abort, DisableTimeout, MsecToTicks, Pause, SetTimeout],
IPDefs USING [Byte, CreateIPHandle, DByte, Datagram, DatagramRec, DestroyIPHandle, Error, Address, InternetHandle, nullAddress, Receive, TCPProtocol],
IPOps USING [OnesComplementAddBlock],
TCP USING [Error, Reason, TCPInfo, Timeout],
TCPOps USING [ConnectionState, recvBufferLength, sendBufferLength, TCPControlSet, TCPHandle, tcpHdrByteLength, TCPHeaderP, TCPSendBuffer],
TCPLogging USING [PrintStateChange],
TCPReceiving USING [ProcessRcvdSegment],
TCPStates USING [Abort, Close, CloseConnection, CopyHandleList, GetInitialSequenceNumber, Open, ValidHandle],
TCPTransmit USING [Rexmit, rexmitSleepTime, RepacketizeandRexmit, SendSYN, TryToSendData];
TCPOpsImpl: CEDAR MONITOR LOCKS handle USING handle: TCPHandle
IMPORTS Basics, BasicTime, IPDefs, IPOps, List, Process, TCP, TCPLogging, TCPReceiving, TCPStates, TCPTransmit
EXPORTS TCPOps
~ BEGIN OPEN TCPOps;
defaultReceiveWindow: PUBLIC INT ← TCPOps.recvBufferLength;
repacketizing: PUBLIC BOOLFALSE;
ourLocalAddress: PUBLIC IPDefs.Address;
tcpIPHandle: IPDefs.InternetHandle;
retransmitProcess: PROCESS;
receiverProcess: PROCESS;
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 TCPMain
Open: PUBLIC PROC [tcpInfo: TCP.TCPInfo] RETURNS [handle: TCPHandle] ~ {
Attempts to open a new TCP connection. Returns a TCPHandle to use or raises TCP.OpenFailed.
RETURN TCPStates.Open[tcpInfo];
};
Close: PUBLIC PROC [handle: TCPHandle] ~ {
Shut down the connection normally.
TCPStates.Close[handle];
};
Abort: PUBLIC PROC [handle: TCPHandle] ~ {
Shut down the connection abnormally.
TCPStates.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 ← NEW[IPDefs.DatagramRec];
handle.currentOutputPtr ← tcpHdrByteLength;
handle.currentOutputLimit ← handle.currentOutputPtr+handle.maxSegmentSize;
};
TCPStates.ValidHandle[handle];
control.psh ← push;
SELECT handle.state FROM
listen =>
IF NOT (handle.matchForeignPort AND handle.matchForeignAddr) THEN
ERROR TCP.Error[neverOpen]
ELSE {
handle.active ← TRUE;
handle.iss ← TCPStates.GetInitialSequenceNumber[];
handle.sndUna ← handle.iss;
TCPLogging.PrintStateChange[handle, synSent];
handle.state ← synSent;
TCPTransmit.SendSYN[handle];
QueueSendSegment[handle, handle.currentOutputDatagram, handle.currentOutputPtr-tcpHdrByteLength, control];
GetNewOutputDatagram[];
};
synSent, synRcvd, established, closeWait => {
QueueSendSegment[handle, handle.currentOutputDatagram, handle.currentOutputPtr-tcpHdrByteLength, control];
GetNewOutputDatagram[];
};
ENDCASE =>
ERROR TCP.Error[handle.reason];
};
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;
TCPStates.ValidHandle[handle];
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 TCP.Error[handle.reason];
IF TimedOut[timeoutTime] THEN {
SIGNAL TCP.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;
TCPStates.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;
TCPStates.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 TCP.Error[handle.reason];
};
Routines internal to the TCP stuff
TCPChecksum: PUBLIC PROC [data: IPDefs.Datagram] RETURNS [checksum: IPDefs.DByte] ~ TRUSTED {
Find the checksum to be used in the TCP header. Call when data.dataLength is the correct length of the packet.
PseudoHeader: TYPE ~ MACHINE DEPENDENT RECORD [
sourceAddr (0): IPDefs.Address,
dstnAddr (2): IPDefs.Address,
zeroes (4: 0..7): IPDefs.Byte,
protocol (4: 8..15): IPDefs.Byte,
tcpTotalByteCount (5): IPDefs.DByte];
pseudoHeader: PseudoHeader;
tcpHdrPtr: TCPHeaderP ← LOOPHOLE[@data.data];
cs: CARDINAL;
pseudoHeader.sourceAddr ← data.inHdr.source;
pseudoHeader.dstnAddr ← data.inHdr.destination;
pseudoHeader.zeroes ← 0;
pseudoHeader.protocol ← data.inHdr.protocol;
pseudoHeader.tcpTotalByteCount ← data.dataLength;
IF data.dataLength MOD 2 # 0 THEN data.data[data.dataLength] ← 0;
cs ← IPOps.OnesComplementAddBlock[ptr: tcpHdrPtr, count: (data.dataLength+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 ← IPOps.OnesComplementAddBlock[ptr: @pseudoHeader, count: PseudoHeader.SIZE, initialSum: cs];
RETURN [Basics.BITNOT[cs]]; -- return one's complement of computed sum
};
ChecksumsMatch: PUBLIC PROC [c1, c2: IPDefs.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 ← TCPStates.CopyHandleList[], l.rest WHILE l # NIL DO
handle: TCPHandle ← NARROW[l.first];
CheckRexmit[handle ! TCP.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;
TCPStates.ValidHandle[handle];
IF handle.state = timeWait AND TimedOut[handle.timeWaitTime] THEN
TCPStates.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[];
TCPStates.CloseConnection[handle, transmissionTimeout]; }
ELSE
IF TimedOut[tcpSendBufferPtr.rexmitTime] THEN
IF repacketizing THEN -- optionally repacketize on rexmit
TCPTransmit.RepacketizeandRexmit[handle, tcpSendBufferPtr]
ELSE
TCPTransmit.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: IPDefs.Datagram, dataByteCount: INT, ctl: TCPControlSet] ~ TRUSTED {
This function is called when there is data from the user to send. It fills in the TCP header, queues the packet on the TCB ToNet queue and if the connection is in a state in which data can be sent, it calls TryToSendData to try to send the data.
This routine is used by SendCurrentDatagram. It should be in TCPTransmit, but it does a WAIT, so it's here.
start: BasicTime.Pulses ← BasicTime.GetClockPulses[];
timeout: INT = handle.dataTimeout;
windowSize: INT;
tcpHdrPtr: TCPHeaderP ← LOOPHOLE[@sendDatagram.data];
Make sure there is enough buffer space for the data.
windowSize ← IF repacketizing THEN MIN[sendBufferLength, handle.sndWnd] ELSE handle.sndWnd;
IF windowSize - handle.nBytesToSend < dataByteCount THEN {
timeoutTime: BasicTime.Pulses ← SetTimeout[handle.dataTimeout];
DO
WAIT handle.windowAvailable;
SELECT handle.state FROM
synSent, synRcvd, established, closeWait => NULL;
ENDCASE => TCP.Error[handle.reason];
windowSize ← IF repacketizing THEN MIN[sendBufferLength, handle.sndWnd] ELSE handle.sndWnd;
IF windowSize - handle.nBytesToSend >= dataByteCount THEN EXIT;
IF TimedOut[timeoutTime] THEN {
now: BasicTime.Pulses ← BasicTime.GetClockPulses[];
duration: LONG CARDINAL = BasicTime.PulsesToMicroseconds[now-start];
SIGNAL TCP.Timeout;
timeoutTime ← SetTimeout[handle.dataTimeout]; };
ENDLOOP;
};
handle.nBytesToSend ← handle.nBytesToSend + dataByteCount;
IF repacketizing THEN { -- if repacketizing data
then copy data to TCP buffer
FOR i: INT IN [tcpHdrPtr.dataWordOffset*4..tcpHdrPtr.dataWordOffset*4+dataByteCount) DO
handle.sendBuffer[handle.fillSlot] ← sendDatagram.data[i];
handle.fillSlot ← (handle.fillSlot + 1) MOD sendBufferLength;
ENDLOOP;
};
IF ctl.urg THEN { -- fill in urgent control and pointer
tcpHdrPtr.urg ← TRUE;
tcpHdrPtr.urgentPtr ← dataByteCount
}
byte following end of packet
ELSE {
tcpHdrPtr.urg ← FALSE;
tcpHdrPtr.urgentPtr ← 0;
};
tcpHdrPtr.psh ← ctl.psh;
tcpHdrPtr.ack ← FALSE;
tcpHdrPtr.rst ← FALSE;
tcpHdrPtr.syn ← FALSE;
tcpHdrPtr.fin ← FALSE;
tcpHdrPtr.checksum ← 0;
sendDatagram.dataLength ← dataByteCount; -- set data length
handle.toNetQueue ← List.Nconc1[handle.toNetQueue, sendDatagram]; -- put buffer on queue to send
IF handle.state = established OR handle.state = closeWait THEN
TCPTransmit.TryToSendData[handle]; -- send it if window permits
IF ctl.psh THEN { -- Wait for what we have sent to get acked
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 => TCP.Error[handle.reason];
IF TimedOut[timeoutTime] THEN {
now: BasicTime.Pulses ← BasicTime.GetClockPulses[];
duration: LONG CARDINAL = BasicTime.PulsesToMicroseconds[now-start];
SIGNAL TCP.Timeout;
timeoutTime ← SetTimeout[handle.dataTimeout]; };
ENDLOOP; }
}; -- QueueSendSegment
RetransmitProcessProc: PROC ~ {
Loops and calls CheckRexmitQueues every so often.
DO
Process.Pause[Process.MsecToTicks[TCPTransmit.rexmitSleepTime]];
CheckRexmitQueues[];
ENDLOOP;
};
ReceiverProcessProc: PROC ~ {
Get the incoming datagrams for TCP.
ENABLE IPDefs.Error => GO TO GiveUp;
DO
data: IPDefs.Datagram ← IPDefs.Receive[tcpIPHandle];
IF data # NIL THEN
TCPReceiving.ProcessRcvdSegment[data];
ENDLOOP;
EXITS
GiveUp => NULL;
};
StartupTCP: PROC ~ {
tcpIPHandle ← IPDefs.CreateIPHandle[[matchProtocol~TRUE, protocol~IPDefs.TCPProtocol, matchAddr~FALSE, address~IPDefs.nullAddress, matchLocalAddr~TRUE, localAddress~IPDefs.nullAddress]];
ourLocalAddress ← tcpIPHandle.localAddress;
TRUSTED {
retransmitProcess ← FORK RetransmitProcessProc[];
receiverProcess ← FORK ReceiverProcessProc[];
};
};
ShutdownTCP: PROC ~ {
TRUSTED {
Process.Abort[receiverProcess];
Process.Abort[retransmitProcess];
};
IPDefs.DestroyIPHandle[tcpIPHandle];
};
StartupTCP[];
END.