MODULE TCP;
IMPORT Out := KernelLog, IP, Streams, Glue, Unix, Sockets, Objects;
CONST
NilPort* = 0;
Ok* = 0;
ConnectionRefused* = 3701;
ConnectionReset* = 3702;
WrongInterface* = 3703;
TimedOut* = 3704;
NotConnected* = 3705;
NoInterface* = 3706;
InterfaceClosed* = 3707;
NumStates* = 4;
Closed* = 0;
Listen* = 1;
Established* = 2;
Unused* = 4;
OpenStates* = {Listen, Established};
ClosedStates* = {Unused, Closed};
HalfClosedStates* = ClosedStates + {};
FinStates* = {Unused, Closed};
VAR
trace: BOOLEAN;
hex: ARRAY 17 OF CHAR;
TYPE
Connection* = OBJECT (Streams.Connection)
VAR
int- : IP.Interface;
lport- : LONGINT;
fip- : IP.Adr;
fport- : LONGINT;
state* : SHORTINT;
socket : LONGINT; localAdr, foreignAdr: Sockets.SocketAdr;
irs-: LONGINT;
rcvnxt-: LONGINT;
iss-: LONGINT;
sndnxt-: LONGINT;
PROCEDURE & Init*;
BEGIN
state := Unused;
irs := 0; iss := 0; rcvnxt := 0; sndnxt := 0
END Init;
PROCEDURE Open*( lport: LONGINT; fip: IP.Adr; fport: LONGINT; VAR res: LONGINT );
VAR ignore: BOOLEAN;
BEGIN {EXCLUSIVE}
ASSERT( (state = Unused) & (lport >= 0) & (lport < 10000H) & (fport >= 0) & (fport < 10000H) );
IF trace THEN Out.String( "Open connection " ) END;
socket := Sockets.Socket( Unix.AFINET, Unix.SockStream, Unix.IpProtoTCP );
IF socket # 0 THEN
IF (~IP.IsNilAdr( fip )) & (fport # NilPort) THEN
IF trace THEN Out.String( "(inout) " ) END;
foreignAdr := Sockets.NewSocketAdr( fip, fport );
IF Sockets.Connect( socket, foreignAdr ) THEN
ignore := Sockets.SetLinger( socket );
SELF.fip := fip; SELF.fport := fport;
localAdr := Sockets.GetSockName( socket );
SELF.lport := Sockets.GetPortNumber( localAdr );
state := Established; res := Ok
ELSE
Out.String( "connect failed" ); Out.Ln;
Sockets.Close( socket ); res := ConnectionRefused
END
ELSE
IF trace THEN Out.String( "(listen) " ) END;
ASSERT( (fport = NilPort) & (IP.IsNilAdr( fip )) );
localAdr := Sockets.NewSocketAdr( IP.NilAdr, lport );
IF Sockets.Bind( socket, localAdr ) THEN
localAdr := Sockets.GetSockName( socket );
SELF.lport := Sockets.GetPortNumber( localAdr );
IF Sockets.Listen( socket ) THEN
ignore := Sockets.SetLinger( socket );
SELF.fip := IP.NilAdr;
state := Listen; res := Ok
ELSE Sockets.Close( socket ); res := NotConnected
END
ELSE Sockets.Close( socket ); res := NotConnected
END
END
ELSE
Out.String( "open socket failed" ); Out.Ln;
res := NotConnected
END;
IF res = Ok THEN
NEW( int, Sockets.SockAdrToIPAdr( localAdr ) )
END;
IF trace THEN
IF res = Ok THEN
Out.String( "socket=" ); Out.Int( socket, 0 );
Out.String( ", locport=" ); Out.Int( SELF.lport, 0 );
Out.String( " done." )
ELSE
Out.String( " failed." )
END;
Out.Ln
END;
END Open;
PROCEDURE Send*( CONST data: ARRAY OF CHAR; ofs, len: LONGINT; propagate: BOOLEAN; VAR res: LONGINT );
VAR n: LONGINT;
BEGIN {EXCLUSIVE}
IF trace THEN Out.String( "Send: socket=" ); Out.Int( socket, 0 ); ShowBuffer( data, ofs, len ) END;
IF state = Established THEN
res := Ok;
WHILE len > 0 DO
n := len;
IF Sockets.Send( socket, data, ofs, n ) THEN
DEC( len, n ); INC( ofs, n )
ELSE
res := ConnectionReset; len := 0
END
END
ELSE
res := NotConnected
END;
INC( sndnxt )
END Send;
PROCEDURE Receive*( VAR data: ARRAY OF CHAR; ofs, size, min: LONGINT; VAR len, res: LONGINT );
VAR p, x: LONGINT;
BEGIN {EXCLUSIVE}
ASSERT( (ofs >= 0) & (ofs + size <= LEN( data )) & (min <= size) );
IF trace THEN
Out.String( "Receive: socket=" ); Out.Int( socket, 0 );
Out.String( " min=" ); Out.Int( min, 0 );
p := ofs
END;
len := 0; res := Ok;
IF size = 0 THEN RETURN END;
IF state IN {Listen, Established} THEN
LOOP
x := size;
IF Sockets.Recv( socket, data, ofs, x, 0 ) THEN
IF x > 0 THEN
DEC( size, x ); INC( len, x ); INC( ofs, x );
IF len >= min THEN
IF trace THEN ShowBuffer( data, p, len ) END;
INC( rcvnxt );
RETURN
END
ELSE
Sockets.Close( socket ); state := Closed;
res := NotConnected; RETURN
END
ELSE
Sockets.Close( socket ); state := Closed;
res := NotConnected; RETURN
END
END;
ELSE
res := NotConnected
END;
INC( rcvnxt )
END Receive;
PROCEDURE DelaySend*( enable: BOOLEAN );
VAR ignore: BOOLEAN;
BEGIN {EXCLUSIVE}
ignore := Sockets.NoDelay( socket, ~enable )
END DelaySend;
PROCEDURE KeepAlive*( enable: BOOLEAN );
VAR ignore: BOOLEAN;
BEGIN {EXCLUSIVE}
ignore := Sockets.KeepAlive( socket, enable )
END KeepAlive;
PROCEDURE Available*( ): LONGINT;
VAR available: LONGINT;
BEGIN {EXCLUSIVE}
IF state IN {Established, Listen} THEN
IF Sockets.Requested( socket ) THEN
available := Sockets.Available( socket );
IF available >= 0 THEN
RETURN available
END;
END
END;
RETURN 0
END Available;
PROCEDURE State*( ): LONGINT;
BEGIN
RETURN state
END State;
PROCEDURE AwaitState*( good, bad: SET; ms: LONGINT; VAR res: LONGINT );
BEGIN
WHILE (ms > 0) & ~(state IN (good+bad)) DO Objects.Sleep( 10 ); DEC( ms, 10 ) END;
IF state IN good THEN
res := Ok
ELSIF state IN bad THEN
res := NotConnected
ELSE
res := TimedOut
END
END AwaitState;
PROCEDURE Close*;
BEGIN
Sockets.Close( socket ); state := Closed;
END Close;
PROCEDURE Discard*;
BEGIN
Sockets.Close( socket ); state := Closed;
END Discard;
PROCEDURE Accept*( VAR client: Connection; VAR res: LONGINT );
VAR newsocket: LONGINT; peerAdr: Sockets.SocketAdr;
BEGIN {EXCLUSIVE}
IF trace THEN
Out.String( "Accept: socket=" ); Out.Int( socket, 0 ); Out.String( " ... " )
END;
IF state = Listen THEN
newsocket := Sockets.Accept( socket );
IF newsocket > 0 THEN
peerAdr := Sockets.GetPeerName( newsocket );
NEW( client );
client.int := int;
client.socket := newsocket;
client.state := Established;
client.fip := Sockets.SockAdrToIPAdr( peerAdr );
client.fport := Sockets.GetPortNumber( peerAdr );
IF trace THEN
Out.String( "Accept done, client socket=" ); Out.Int( newsocket, 0 ); Out.Ln
END;
res := Ok
ELSE
res := NotConnected ;
IF trace THEN Out.String( "Accept failed." ); Out.Ln END
END;
ELSE
res := NotConnected ;
IF trace THEN Out.String( "Accept failed (state # Listen)." ); Out.Ln END
END;
END Accept;
PROCEDURE Requested*( ): BOOLEAN;
BEGIN {EXCLUSIVE}
RETURN (state = Listen) & Sockets.Requested( socket )
END Requested;
END Connection;
PROCEDURE ShowBuffer( CONST buf: ARRAY OF CHAR; p, l: LONGINT );
VAR i, pe: LONGINT; c: CHAR; x: LONGINT;
BEGIN
Out.Ln; Out.Char( '[' );
pe := p + l - 1; i := 0;
WHILE (p < LEN( buf )) & (p <= pe) DO
c := buf[p]; INC( p );
IF (c >= ' ') & (c <= '~') THEN Out.Char( c )
ELSE
x := ORD( c );
CASE x OF
|10: Out.String( "\n" )
|11: Out.String( "\t" )
|13: Out.String( "\c" )
ELSE
Out.Char( '\' ); Out.Char( hex[x DIV 16] ); Out.Char( hex[x MOD 16] );
END
END;
INC( i );
IF (p < pe) & ((i >= 80) OR (c = 0AX)) THEN Out.Ln; i := 0 END
END;
Out.Char( ']' ); Out.Ln
END ShowBuffer;
PROCEDURE DisplayErrors*( par: ANY ): ANY;
BEGIN
RETURN NIL;
END DisplayErrors;
PROCEDURE DiscardAll*( par: ANY ): ANY;
BEGIN
RETURN NIL;
END DiscardAll;
PROCEDURE ToggleTrace*;
BEGIN
trace := ~trace;
Out.Enter;
Out.String( "TCP trace " );
IF trace THEN Out.String( "on" ) ELSE Out.String( "off" ) END;
Out.Exit
END ToggleTrace;
BEGIN
hex := "0123456789ABCDEF";
trace := 7 IN Glue.debug;
END TCP.