MODULE TCP;
IMPORT
WSock32, Modules, Kernel, Streams, IP, Objects, KernelLog,SYSTEM;
CONST
Trace = FALSE;
NilPort* = 0;
Ok* = 0; ConnectionRefused* = 3701; NotConnected* = 3705; TimedOut* = 3704;
NumStates* = 12; Closed* = 0; Listen* = 1; SynSent* = 2;
SynReceived* = 3; Established* = 4; CloseWait* = 5; FinWait1* = 6;
Closing* = 7; LastAck* = 8; FinWait2* = 9; TimeWait* = 10;
Unused* = 11;
OpenStates* = {Listen, SynReceived, Established, CloseWait, FinWait1, FinWait2};
ClosedStates* = {Unused, Closed, Closing, LastAck, TimeWait};
HalfClosedStates* = ClosedStates + {FinWait1, FinWait2};
FinStates* = {Unused, Closed, CloseWait, Closing, LastAck, TimeWait}; Timeout = 14;
NoDelay = 2;
DoKeepAlive = 3;
TYPE
Connection* = OBJECT (Streams.Connection)
VAR
int-: IP.Interface;
lport-: LONGINT;
fip-: IP.Adr;
fport-: LONGINT;
state*: SHORTINT;
sndnxt-: LONGINT;
iss-: LONGINT;
rcvnxt-: LONGINT;
irs-: LONGINT;
socket: WSock32.Socket;
sndwnd-: LONGINT;
sndcwnd-: LONGINT;
sndcc-: LONGINT;
rcvwnd-: LONGINT;
srtt-: LONGINT;
timeout: Objects.Timer;
flags: SET;
PROCEDURE & Init*;
BEGIN
state := Unused;
socket := WSock32.InvalidSocket;
END Init;
PROCEDURE Open*( lport: LONGINT; fip: IP.Adr; fport: LONGINT; VAR res: LONGINT );
VAR adr: WSock32.sockaddrIn; err: LONGINT; str: ARRAY 64 OF CHAR;
BEGIN {EXCLUSIVE}
IF Trace THEN
KernelLog.Enter; KernelLog.String( "Open connection: lport=" ); KernelLog.Int( lport, 1 );
KernelLog.String( " ,fip=" ); IP.AdrToStr( fip, str ); KernelLog.String( str );
KernelLog.String( " ,fport=" ); KernelLog.Int( fport, 1 ); Report( SELF ); KernelLog.Exit;
END;
ASSERT ( (state = Unused) & (lport >= 0) & (lport < 10000H) & (fport >= 0) & (fport < 10000H) );
IF (fip.usedProtocol # IP.IPv4) THEN
KernelLog.String("TCP.Connection.Open: Warning: Connection to non-IPv4 host not supported!"); KernelLog.Ln;
res := NotConnected;
RETURN;
END;
IF socket = WSock32.InvalidSocket THEN
socket := WSock32.socket( WSock32.AFINet, WSock32.SockStream, WSock32.IPProtoTCP );
ASSERT ( socket # WSock32.InvalidSocket );
pool.Add( SELF, SELF.Finalize )
END;
IF ~IP.IsNilAdr(fip) & (fport # NilPort) THEN
IF Trace THEN KernelLog.Enter; KernelLog.String( "Active open" ); Report( SELF ); KernelLog.Exit; END;
int := IP.InterfaceByDstIP( fip );
SELF.lport := lport; SELF.fip := fip; SELF.fport := fport;
IF lport # NilPort THEN
adr.sinFamily := WSock32.PFINet; adr.sinAddr := 0;
adr.sinPort := WSock32.htons( SHORT( lport ) );
err := WSock32.bind( socket, adr, SYSTEM.SIZEOF( WSock32.sockaddrIn ) );
IF err # 0 THEN
res := NotConnected; state := Closed; WSock32.DispError;
RETURN
END
END;
adr.sinFamily := WSock32.PFINet;
adr.sinAddr := (fip.ipv4Adr); adr.sinPort := WSock32.htons( SHORT( fport ) );
err := WSock32.connect( socket, adr, SYSTEM.SIZEOF( WSock32.sockaddrIn ) );
IF err # 0 THEN
res := NotConnected; WSock32.DispError; state := Closed;
err := WSock32.closesocket( socket );
ELSE res := Ok; state := Established; SetPortAndIp;
END
ELSE
IF Trace THEN KernelLog.Enter; KernelLog.String( "Passive open" ); Report( SELF ); KernelLog.Exit; END;
ASSERT ( (fport = NilPort) & IP.IsNilAdr(fip));
SELF.int := NIL; SELF.lport := lport; SELF.fip := IP.NilAdr;
SELF.fport := NilPort; adr.sinFamily := WSock32.PFINet;
adr.sinAddr := 0; adr.sinPort := WSock32.htons( SHORT( lport ) );
err := WSock32.bind( socket, adr, SYSTEM.SIZEOF( WSock32.sockaddrIn ) );
IF err = 0 THEN err := WSock32.listen( socket, WSock32.SOMaxConn ) END;
IF err # 0 THEN
res := NotConnected; state := Closed;
WSock32.DispError;
ELSE
SetPortAndIp; res := Ok; state := Listen
END
END;
IF Trace THEN
KernelLog.Enter; KernelLog.String( "Open connection, result = " ); ReportResult( res ); Report( SELF ); KernelLog.Exit;
END;
IF state = Established THEN END;
END Open;
PROCEDURE Send*( CONST data: ARRAY OF CHAR; ofs, len: LONGINT; propagate: BOOLEAN; VAR res: LONGINT );
VAR err : LONGINT;
BEGIN
ASSERT((ofs >= 0) & (ofs + len <= LEN(data)));
IF state = Closed THEN res := NotConnected; RETURN
ELSIF state = Closing THEN
ELSE
ASSERT ( (state = Established) & (socket # WSock32.InvalidSocket) );
END;
res := Streams.Ok;
err := WSock32.send( socket, data[ofs], len, {} );
IF (err < 0) OR ((err = 0) & (len > 0)) THEN
KernelLog.String( "TCP.Send :" ); WSock32.DispError; res := NotConnected;
END;
END Send;
PROCEDURE Receive*( VAR data: ARRAY OF CHAR; ofs, size, min: LONGINT; VAR len, res: LONGINT );
VAR ret: LONGINT;
BEGIN
ASSERT ( (ofs >= 0) & (ofs + size <= LEN( data )) & (min <= size) );
len := 0; res := Streams.Ok;
BEGIN {EXCLUSIVE}
IF state = Closed THEN res := NotConnected; RETURN
ELSIF state=CloseWait THEN res := Streams.EOF; RETURN
END;
END;
IF socket = WSock32.InvalidSocket THEN res := NotConnected; RETURN
END;
IF (size = 0) OR ((min = 0) & (Available() = 0)) THEN res := Streams.Ok; RETURN
END;
REPEAT
ret := WSock32.recv( socket, data[ofs], size, {} );
IF ret > 0 THEN INC( len, ret ); INC(ofs, ret); DEC(size, ret); END;
UNTIL (size <= 0) OR (len >= min) OR (ret <= 0);
IF ret < 0 THEN
IF Trace THEN KernelLog.String( "TCP.Receiver.Receive" ); WSock32.DispError; END;
BEGIN {EXCLUSIVE}
res := WSock32.shutdown( socket, WSock32.SDboth );
res := WSock32.closesocket( socket );
state := Closed; res := Streams.EOF
END;
ELSIF ret = 0 THEN
IF Trace THEN
KernelLog.Enter; KernelLog.String( "TCP.Connection.Receive, graceful shutdown by remote side " ); Report( SELF ); KernelLog.Exit;
END;
BEGIN {EXCLUSIVE}
IF state = Established THEN res := WSock32.shutdown( socket, WSock32.SDReceive ); state := CloseWait;
ELSIF state IN {FinWait1, FinWait2, Closing} THEN state := Closed; res := WSock32.shutdown( socket, WSock32.SDboth );
res := WSock32.closesocket( socket ); socket := WSock32.InvalidSocket; pool.Remove( SELF );
END;
res := Streams.EOF
END;
IF Trace THEN
KernelLog.Enter; KernelLog.String( "Receive Result " ); ReportResult( res );
Report( SELF ); KernelLog.Exit;
END;
END;
END Receive;
PROCEDURE State*( ): LONGINT;
BEGIN {EXCLUSIVE}
RETURN state
END State;
PROCEDURE HandleTimeout;
BEGIN {EXCLUSIVE}
INCL( flags, Timeout )
END HandleTimeout;
PROCEDURE AwaitState*( good, bad: SET; ms: LONGINT; VAR res: LONGINT );
BEGIN {EXCLUSIVE}
IF ~(state IN (good + bad)) THEN
IF ms # -1 THEN
IF timeout = NIL THEN NEW( timeout ) END;
Objects.SetTimeout( timeout, SELF.HandleTimeout, ms )
END;
EXCL( flags, Timeout );
AWAIT( (state IN (good + bad)) OR (Timeout IN flags) );
IF ms # -1 THEN Objects.CancelTimeout( timeout ) END
END;
IF state IN good THEN res := Ok
ELSIF state IN bad THEN res := NotConnected
ELSE res := TimedOut
END
END AwaitState;
PROCEDURE Close*;
VAR res: LONGINT; closetimer: Objects.Timer;
BEGIN {EXCLUSIVE}
IF state = Closed THEN RETURN END;
IF Trace THEN
KernelLog.Enter; KernelLog.String( "TCP.Connection.Close, " ); Report( SELF ); KernelLog.Exit;
END;
IF socket # WSock32.InvalidSocket THEN
IF state = CloseWait THEN
res := WSock32.shutdown( socket, WSock32.SDboth );
state := Closed; res := WSock32.closesocket( socket );
socket := WSock32.InvalidSocket; pool.Remove( SELF );
ELSIF state = Established THEN
res := WSock32.shutdown( socket, WSock32.SDSend );
state := FinWait1;
NEW(closetimer);
Objects.SetTimeout(closetimer,SELF.Close,5000);
ELSIF state IN {FinWait1, FinWait2, Closing, TimeWait} THEN
res := WSock32.shutdown( socket, WSock32.SDboth );
res := WSock32.closesocket( socket );
socket := WSock32.InvalidSocket; pool.Remove( SELF );
state := Closed;
ELSIF state = Listen THEN
res := WSock32.shutdown( socket, WSock32.SDboth );
state := Closed; res := WSock32.closesocket( socket );
socket := WSock32.InvalidSocket; pool.Remove( SELF );
END;
IF Trace THEN KernelLog.Enter; KernelLog.String( "Close done." ); Report( SELF ); KernelLog.Exit; END;
END;
END Close;
PROCEDURE SetPortAndIp;
VAR sockname: WSock32.sockaddrIn; lensockname: LONGINT; res: LONGINT;
BEGIN
lensockname := SYSTEM.SIZEOF( WSock32.sockaddrIn );
res := WSock32.getsockname( socket, sockname, lensockname );
IF res = Ok THEN
lport := WSock32.ntohs( sockname.sinPort );
END;
lensockname := SYSTEM.SIZEOF( WSock32.sockaddrIn );
res := WSock32.getpeername( socket, sockname, lensockname );
IF res = Ok THEN
fip.usedProtocol := IP.IPv4;
fip.ipv4Adr := sockname.sinAddr; fport := WSock32.ntohs( sockname.sinPort );
fip.ipv4Adr := WSock32.ntohl( fip.ipv4Adr );
END;
IF Trace THEN KernelLog.Enter; KernelLog.String( "SetPortAndIp " ); Report( SELF ); KernelLog.Exit; END;
END SetPortAndIp;
PROCEDURE Accept*( VAR client: Connection; VAR res: LONGINT );
VAR s: WSock32.Socket; adr: WSock32.sockaddrIn; adrlen: LONGINT;
str: ARRAY 64 OF CHAR;
BEGIN
IF Trace THEN KernelLog.Enter; KernelLog.String( "Accepting connections" ); Report( SELF ); KernelLog.Exit; END;
ASSERT ( (state = Listen) & (socket # WSock32.InvalidSocket) );
adr.sinFamily := WSock32.PFINet; adrlen := SYSTEM.SIZEOF( WSock32.sockaddrIn );
s := WSock32.accept( socket, adr, adrlen );
BEGIN {EXCLUSIVE}
IF s # WSock32.InvalidSocket THEN
NEW( client ); client.lport := NilPort;
IF (adrlen = SYSTEM.SIZEOF( WSock32.sockaddrIn )) &
(adr.sinFamily = WSock32.PFINet) THEN
client.fip.usedProtocol := IP.IPv4;
client.fip.ipv4Adr := adr.sinAddr;
client.fport := WSock32.ntohs( adr.sinPort )
ELSE client.fip := IP.NilAdr; client.fport := NilPort
END;
client.int := IP.InterfaceByDstIP( client.fip );
pool.Add( client, client.Finalize ); client.socket := s;
client.state := Established; res := Ok; client.SetPortAndIp;
IF Trace THEN
KernelLog.Enter; KernelLog.String( "Accepted connection: client lport=" ); KernelLog.Int( client.lport, 1 );
KernelLog.String( " ,fip=" ); IP.AdrToStr( client.fip, str );
KernelLog.String( str ); KernelLog.String( " ,fport=" );
KernelLog.Int( client.fport, 1 ); Report( SELF ); KernelLog.Exit;
END;
ELSE client := NIL; res := ConnectionRefused
END;
END;
END Accept;
PROCEDURE DelaySend*( enable: BOOLEAN );
BEGIN {EXCLUSIVE}
IF enable THEN EXCL( flags, NoDelay ); ELSE INCL( flags, NoDelay ); END;
END DelaySend;
PROCEDURE KeepAlive*( enable: BOOLEAN );
BEGIN {EXCLUSIVE}
IF enable THEN INCL( flags, DoKeepAlive ); ELSE EXCL( flags, DoKeepAlive ); END;
END KeepAlive;
PROCEDURE Discard*;
BEGIN
Close;
END Discard;
PROCEDURE Requested*( ): BOOLEAN;
BEGIN {EXCLUSIVE}
RETURN FALSE;
END Requested;
PROCEDURE Available*( ): LONGINT;
VAR ret, res: LONGINT; fdset: WSock32.FDSet;
data: ARRAY 256 OF CHAR;
BEGIN
ret := WSock32.ioctlsocket( socket, WSock32.FIONRead, res );
IF ret # 0 THEN KernelLog.String( "TCP.Available " ); WSock32.DispError; END;
IF res = 0 THEN
fdset.fdcount := 1; fdset.socket[0] := socket;
ret := WSock32.select( 0, fdset, NIL , NIL , NIL );
IF ret = 1 THEN
res := WSock32.recv( socket, data, 256, {1} );
IF res = 0 THEN
BEGIN {EXCLUSIVE}
IF state = Established THEN state := CloseWait; res := WSock32.shutdown( socket, WSock32.SDReceive );
ELSIF state IN {FinWait1, FinWait2, Closing} THEN
state := Closed; res := WSock32.shutdown( socket, WSock32.SDboth ); res := WSock32.closesocket( socket );
socket := WSock32.InvalidSocket; pool.Remove( SELF );
END;
END;
IF Trace THEN
KernelLog.Enter; KernelLog.String( "TCP.Connection.Available: graceful shutdown by remote side." ); Report( SELF ); KernelLog.Exit;
END;
ELSIF res < 0 THEN
IF Trace THEN KernelLog.String( "TCP.Receiver.Receive: " ); WSock32.DispError; END;
res := 0;
BEGIN {EXCLUSIVE}
state := CloseWait;
END;
END;
END;
END;
RETURN res;
END Available;
PROCEDURE Finalize( ptr: ANY );
VAR res: LONGINT;
BEGIN {EXCLUSIVE}
IF Trace THEN KernelLog.Enter; KernelLog.String( "TCP.Finalize " ); Report( SELF ); KernelLog.Exit; END;
ASSERT ( ptr = SELF );
IF socket # WSock32.InvalidSocket THEN
res := WSock32.shutdown( socket, WSock32.SDboth );
res := WSock32.closesocket( socket );
socket := WSock32.InvalidSocket;
END;
state := Unused
END Finalize;
END Connection;
VAR
pool*: Kernel.FinalizedCollection;
PROCEDURE Init;
BEGIN
NEW( pool )
END Init;
PROCEDURE Finalize( obj: ANY; VAR cont: BOOLEAN );
BEGIN
obj( Connection ).Finalize( obj ); cont := TRUE
END Finalize;
PROCEDURE Cleanup;
BEGIN
pool.Enumerate( Finalize )
END Cleanup;
PROCEDURE ReportState( state: LONGINT );
BEGIN
KernelLog.String( "State=" );
CASE state OF
Closed:
KernelLog.String( "Closed" )
| Listen:
KernelLog.String( "Listen" );
| SynSent:
KernelLog.String( "SynSent" );
| SynReceived:
KernelLog.String( "SynReceived" );
| Established:
KernelLog.String( "Established" );
| CloseWait:
KernelLog.String( "CloseWait" );
| FinWait1:
KernelLog.String( "FinWait1" );
| FinWait2:
KernelLog.String( "FinWait2" );
| TimeWait:
KernelLog.String( "TimeWait" );
| Unused:
KernelLog.String( "Unused" );
ELSE KernelLog.String( "????" );
END;
END ReportState;
PROCEDURE Report( c: Connection );
VAR str: ARRAY 64 OF CHAR;
BEGIN
KernelLog.String( " [lport=" ); KernelLog.Int( c.lport, 1 ); KernelLog.String( " ,fip=" );
IP.AdrToStr( c.fip, str ); KernelLog.String( str ); KernelLog.String( " ,fport=" );
KernelLog.Int( c.fport, 1 ); KernelLog.String( "," ); ReportState( c.state );
KernelLog.String( "]" );
END Report;
PROCEDURE ReportResult( res: LONGINT );
BEGIN
IF res = Ok THEN KernelLog.String( "Ok" );
ELSIF res = ConnectionRefused THEN KernelLog.String( "ConnectionRefused" )
ELSIF res = NotConnected THEN KernelLog.String( "NotConnected" )
ELSIF res = TimedOut THEN KernelLog.String( "TimedOut" );
ELSIF res = Streams.EOF THEN KernelLog.String( "Streams.EOF" );
ELSE KernelLog.String( "Unknown result code=" ); KernelLog.Int( res, 1 );
END;
END ReportResult;
BEGIN
Init; Modules.InstallTermHandler( Cleanup )
END TCP.
state diagram in this version of TCP very much simplified (rest done by Windows):
either
closed -> Listen -> Established -> CloseWait | FinWait1 -> Closed
or
closed -> Established -> CloseWait | FinWait1 -> Closed