ArpaTCPStatesImpl.mesa
Copyright (C) 1984 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:23 pm
Last Edited by: Taft, January 4, 1984 10:11 am
Last Edited by: HGM, March 27, 1984 3:23:42 am PST
Doug Terry, August 28, 1987 4:37:36 pm PDT
Hal Murray May 16, 1985 3:15:32 am PDT
John Larson, April 14, 1986 11:26:36 pm PST
DIRECTORY
Arpa USING [Address, nullAddress],
ArpaIP USING [AllocBuffers, Buffers],
BasicTime USING [Now],
List USING [Append, Nconc1, Remove],
Process USING [DisableTimeout, MsecToTicks, SetTimeout],
ArpaTCP USING [Error, maxTimeout, Port, Reason, TCPInfo],
ArpaTCPLogging USING [PrintStateChange],
ArpaTCPOps USING [defaultReceiveWindow, maxTCPDataLength, TCPHandle, TCPHandleRec, tcpHdrByteLength, TCPHeaderP],
ArpaTCPStates,
ArpaTCPTransmit USING [SendFIN, SendReset, SendSYN];
ArpaTCPStatesImpl: CEDAR MONITOR -- monitors the handleList
IMPORTS ArpaIP, BasicTime, List, Process, ArpaTCP, ArpaTCPLogging, ArpaTCPOps, ArpaTCPTransmit
EXPORTS ArpaTCPStates
~ BEGIN OPEN ArpaTCPOps, ArpaTCPStates;
handleList: LIST OF REF ANY; -- list of all open TCP connections
nextLocalPort: CARDINAL ← 256; -- avoid well-known sockets
lastSequenceNumber: INTLOOPHOLE[BasicTime.Now[]]; -- used to general initial sequence numbers
Close: PUBLIC PROC [handle: TCPHandle] ~ {
Close the TCP connection; returns an error if the connection does not exist or is already closing. Sends a FIN to the remote TCP. Note that the user may receive more data from the remote TCP after calling Close; when the connection is closed, the user will receive a message in which the data is a pointer to a ControlInfo record which contains a pointer to the TCB and the reason for closing. This is the last message that the user receives on the connection; on receipt, the user can delete all connection specific information.
ValidHandle[handle];
SELECT handle.state FROM
listen, synSent => CloseConnection[handle, localClose];
synRcvd, established => {-- send fin after all preceeding sends
ArpaTCPTransmit.SendFIN[handle]; -- send fin and ack
ArpaTCPLogging.PrintStateChange[handle, finWait1];
handle.state ← finWait1;
handle.reason ← localClose;
};
closeWait => {-- send fin after all preceeding sends
ArpaTCPTransmit.SendFIN[handle];
ArpaTCPLogging.PrintStateChange[handle, lastAck];
handle.state ← lastAck;
};
closing, lastAck, timeWait => -- connection is closing
NULL; -- so what?
ENDCASE;
}; -- Close
Abort: PUBLIC PROC [handle: TCPHandle] ~ {
Abort the connection. This routine sends a TCP Reset if necessary; it calls CloseConnection to dispose of all the connection specific storage and to send a message to the user. The data in this message is a pointer to a ControlInfo record containing a pointer to the TCB and the reason the connection was closed (UserAbort). On receipt of this message, the user can dispose of all connection specific information.
ValidHandle[handle ! ArpaTCP.Error => GO TO GiveUp];
SELECT handle.state FROM
listen, synSent =>
CloseConnection[handle, localAbort];
synRcvd, established, finWait1, finWait2, closeWait => {
ArpaTCPTransmit.SendReset[handle, handle.localPort, handle.foreignPort, handle.foreignAddr, handle.sndNxt, handle.rcvNxt];
CloseConnection[handle, localAbort];
};
closing, lastAck, timeWait =>
CloseConnection[handle, localAbort];
ENDCASE;
EXITS
GiveUp => NULL;
}; -- Abort
GetInitialSequenceNumber: PUBLIC PROC RETURNS [INT] = {
Generate an initial send sequence number for a connection.
lastSequenceNumber ← lastSequenceNumber*1037 + 12345;
RETURN [ABS[lastSequenceNumber]];
};
Open: PUBLIC ENTRY PROC [tcpInfo: ArpaTCP.TCPInfo] RETURNS [handle: TCPHandle] ~ {
Attempts to open a new TCP connection. Returns a TCPHandle to use or raises ArpaTCP.OpenFailed if the Open request conflicts with an existing request or is a duplicate Open request on the same connection. The connection is open either in the LISTEN or SYNSENT state. In the LISTEN state, wait for a SYN from the remote TCP; in the SYNSENT state, send a SYN to initiate the connection. If an Open call is done to open a connection in the LISTEN state, then another Open call can be done to send the SYN and put the connection in the SYNSENT state.
ENABLE UNWIND => NULL;
IF FindConflictingConn[tcpInfo] # NIL THEN
RETURN WITH ERROR ArpaTCP.Error[localConflict];
IF tcpInfo.active THEN
IF NOT (tcpInfo.matchForeignPort AND tcpInfo.matchForeignAddr) THEN
RETURN WITH ERROR ArpaTCP.Error[unspecifiedRemoteEnd]
ELSE {
handle ← CreateHandle[tcpInfo];
handle.iss ← GetInitialSequenceNumber[];
handle.sndUna ← handle.iss;
ArpaTCPLogging.PrintStateChange[handle, synSent];
handle.state ← synSent;
ArpaTCPTransmit.SendSYN[handle]; -- send SYN
}
ELSE {
open listening connection
handle ← CreateHandle[tcpInfo];
ArpaTCPLogging.PrintStateChange[handle, listen];
handle.state ← listen;
};
handleList ← List.Nconc1[handleList, handle];
};
CloseConnection: PUBLIC ENTRY PROC [handle: TCPHandle, reason: ArpaTCP.Reason] = {
Clear off the transmit and retransmit queues and remove this handle form the list.
ENABLE UNWIND => NULL;
Kill[handle.toNetQueue]; handle.toNetQueue ← NIL;
Kill[handle.rexmitQueue]; handle.rexmitQueue ← NIL;
ArpaTCPLogging.PrintStateChange[handle, closed];
handle.state ← closed;
handle.reason ← reason;
handleList ← List.Remove[handle, handleList];
BROADCAST handle.windowAvailable; -- make sure everyone finds out.
BROADCAST handle.urgentAvailable;
BROADCAST handle.dataAvailable;
BROADCAST handle.notListening;
}; -- CloseConnection
Kill: PROC [list: LIST OF REF ANY] = {
DO
temp: LIST OF REF ANY ← list;
IF temp = NIL THEN RETURN;
list ← list.rest;
temp.first ← NIL;
temp.rest ← NIL;
ENDLOOP; };
ValidHandle: PUBLIC ENTRY PROC [handle: TCPHandle] ~ {
Check if handle given as argument by user is valid. Called by Abort, Close, and Send. Raises ArpaTCP.StreamClosing if bad handle, else it just returns.
ENABLE UNWIND => NULL;
FOR l: LIST OF REF ANY ← handleList, l.rest WHILE l # NIL DO
IF l.first = handle THEN
RETURN;
ENDLOOP;
RETURN WITH ERROR ArpaTCP.Error[IF handle = NIL THEN neverOpen ELSE handle.reason];
};
FindHandle: PUBLIC ENTRY PROC [rcvdDatagram: ArpaIP.Buffers] RETURNS [handle: TCPHandle] ~ TRUSTED {
Find matching handle for segment received from the net.
ENABLE UNWIND => NULL;
tcpHdrPtr: TCPHeaderP ← LOOPHOLE[@rcvdDatagram.body];
sourceAddr: Arpa.Address;
sourcePort: ArpaTCP.Port;
dstnPort: ArpaTCP.Port;
goodHandle: TCPHandle;
sourceAddr ← rcvdDatagram.hdr1.source;
sourcePort ← tcpHdrPtr^.sourcePort;
dstnPort ← tcpHdrPtr^.dstnPort;
goodHandle ← NIL;
FOR l: LIST OF REF ANY ← handleList, l.rest WHILE l # NIL DO
h: ArpaTCPOps.TCPHandle ← NARROW[l.first];
IF h.localPort = dstnPort AND (h.foreignAddr = sourceAddr OR NOT h.matchForeignAddr) AND (h.foreignPort = sourcePort OR NOT h.matchForeignPort) THEN
IF h.matchForeignAddr AND h.matchForeignPort THEN
RETURN [h] -- found exact match, return TCB
ELSE -- match TCB with foreign address or port not specified
IF h.matchForeignAddr THEN -- address match is best
goodHandle ← h
ELSE
IF h.matchForeignPort AND goodHandle = NIL THEN
goodHandle ← h
ELSE
IF goodHandle = NIL THEN
goodHandle ← h;
ENDLOOP;
RETURN [goodHandle] -- best match found
}; -- FindTCB
CopyHandleList: PUBLIC ENTRY PROC RETURNS [handleListCopy: LIST OF REF ANY] ~ {
Make a copy of the handle list for the retransmit procedure to look at.
ENABLE UNWIND => NULL;
RETURN [List.Append[handleList, NIL]];
};
FindConflictingConn: INTERNAL PROC [info: ArpaTCP.TCPInfo] RETURNS [conflictingHandle: TCPHandle] = {
Checks for conflicting handles; i.e. handles which have the same local and foreign ports; if such a handle exists, then return a pointer to it; used in user open call to determine if user connection request conflicts with existing connection.
FOR q: LIST OF REF ANY ← handleList, q.rest WHILE q # NIL DO
thisHandle: TCPHandle ← NARROW[q.first];
IF thisHandle.localPort = info.localPort AND info.matchLocalPort AND thisHandle.foreignAddr = info.foreignAddress AND info.matchForeignAddr AND thisHandle.foreignPort = info.foreignPort AND info.matchForeignPort THEN
RETURN [thisHandle];
ENDLOOP;
RETURN [NIL]; -- no conflict
}; -- FindConflictingConn
CreateHandle: PROC [info: ArpaTCP.TCPInfo] RETURNS [handle: TCPHandle] = {
handle ← NEW[TCPHandleRec];
IF info.matchLocalPort THEN
handle.localPort ← info.localPort
ELSE DO -- assign local port automatically and ensure it doesn't match any existing one
handle.localPort ← nextLocalPort;
nextLocalPort ← MAX[nextLocalPort+1, 256]; -- avoid well-known sockets
FOR q: LIST OF REF ANY ← handleList, q.rest WHILE q#NIL DO
IF NARROW[q.first, TCPHandle].localPort=handle.localPort THEN EXIT; -- try again
REPEAT
FINISHED => GOTO ok;
ENDLOOP;
REPEAT
ok => NULL;
ENDLOOP;
IF info.matchForeignAddr THEN
handle.foreignAddr ← info.foreignAddress
ELSE
handle.foreignAddr ← Arpa.nullAddress;
IF info.matchForeignPort THEN
handle.foreignPort ← info.foreignPort
ELSE
handle.foreignPort ← 0;
handle.matchForeignAddr ← info.matchForeignAddr;
handle.matchForeignPort ← info.matchForeignPort;
handle.state ← closed;
handle.reason ← neverOpen;
handle.active ← info.active;
handle.maxSegmentSize ← maxTCPDataLength;
handle.rcvWnd ← defaultReceiveWindow;
handle.fromNetQueue ← NIL;
handle.readyToReadQueue ← NIL;
handle.currentInputBuffer ← NIL;
handle.toNetQueue ← NIL;
handle.rexmitQueue ← NIL;
handle.currentOutputDatagram ← ArpaIP.AllocBuffers[1];
handle.currentOutputPtr ← tcpHdrByteLength;
handle.currentOutputLimit ← handle.currentOutputPtr+handle.maxSegmentSize;
handle.dataTimeout ← MIN[info.timeout, ArpaTCP.maxTimeout];
TRUSTED {
IF handle.dataTimeout>=0 THEN {
Process.SetTimeout[@handle.dataAvailable, Process.MsecToTicks[MIN[handle.dataTimeout, 10000]]];
Process.SetTimeout[@handle.windowAvailable, Process.MsecToTicks[MIN[handle.dataTimeout, 10000]]]}
ELSE Process.DisableTimeout[@handle.dataAvailable];
};
}; -- CreateHandle
END.