You will find the books TCP/IP Illustrated, Volume 1 (Stevens) and Volume 2 (Wright & Stevens) useful when exploring the PowerNet TCP code, especially for details of sequence and ack numbers.
The configuration below can now be found in PNconfig.fth.
0 equ TCPDEBUG \ -- n
Set this flag non-zero to generate debug messages.
#1460 equ TCPDATASIZE \ -- n ; EGS009
Transmit buffer size of pbuf less tcp hdr(ish).
#1460 2 * equ TCPTXBUFFSIZE \ -- n
Size of the retransmission buffer for a TCP socket.
One buffer of this size is allocated from the heap
when a TCP socket is created.
#1460 equ TCPWINDOWSIZE \ -- n ; EGS002
TCP window size.
#100 equ TXDELAYTIME \ -- ms
timer for delayed transmit (mSecs), default is 100 milliseconds.
#10 equ ACKDELAYTIME \ -- ms
timer for delayed ack (mSecs), default is 10 milliseconds.
#5000 equ TXRETRYTIME \ -- ms
timer for transmit retries (mSecs), default is 5 seconds.
#30000 equ TCPCONNECTTIME \ -- ms
timer for incoming connections to complete (mSecs), default is 30 seconds.
#30000 equ TCPMSLTIME \ -- ms
timer for maximum segment lifetime (mSecs, default is 30 secs).
#7200000 equ TCPIDLETIME \ -- ms
timer for idle disconnection (mSecs, default is 2 hours).
#12 equ TCPMAXRETRIES \ -- n
Maximum number of transmission retries before returning
SOCKET_ERROR, default is 12.
If a receiving socket fails to accept new data, the total
timeout will be TXRETRYTIME*TCPMAXRETRIES, the default being
60 seconds.
create MSSopt \ -- addr
Maximum Segment Size option.
variable NoTcpHandle \ -- addr
Holds the handle of a socket used to handle unknown
TCP requests.
: Init-NoTcp-Socket \ -- res|SOCKET_ERROR
Initialise a socket structure to handle unknown TCP requests.
SOCKET_ERROR
is returned if the socket cannot be initialised.
Most equates are not documented.
STRUCT tcp_hdr \ -- size ; of tcp header structure
TCP header structure.
STRUCT tcpcb \ -- size
TCP control block structure is defined in structs.fth.
: TcpState@ \ *sk -- state
Get socket state.
: .tcpstate \ n --
Display tcp socket state. Only compiled if DIAGS? is non-zero.
: ShowTcpState \ *sk --
Display the state for the connection
: initTcpTxBuff \ buff *cb --
Use buff to initialise or reset the TCP transmit buffer.
: -TcpTxBuff \ *cb --
FREE
the TCP transmit buffer and reset the transmit
pointers.
: goCLOSED \ *sk --
Place socket in closed state.
: (CloseSocket) \ *sk --
Prepare a socket for closure. Final closure and memory recovery
is performed in the TCP idle action to avoid race hazards.
: ChkTcpTxBuff \ *cb -- ior
If the TCPCB does not have a transmit buffer, create it.
Return 0 if the buffer exists or has been created. We assume
that the TCPCB is valid.
: GetTCPHdrLen \ *tcp -- len
Extract length of TCP header (varies with options).
: GetTCPPktLen \ *ip -- len ; SFP005
Extract length of TCP packet
: >TCPData \ *tcp -- *tcpdata
Get pointer to TCP packet data.
: GetTCPDataLen \ *ip -- len ; SFP005
Extract length of TCP data packet.
: GetMSS \ *tcp defmss -- mss ; SFP045
If the TCP header in a SYN or SYN/ACK packet contains an
MSS option return it, otherwise return the default value.
the output is limited to the range 0..defMSS
.
: GetMSSsyn \ *tcp -- mss ; SFP045
If the TCP header in a SYN packet contains an MSS option
return it, otherwise return the default value of 536.
Used by servers to read the incoming MSS in a SYN packet.
: GetMSSsyn/ack \ *tcp -- mss ; SFP045
If the TCP header in a SYN packet contains an MSS option
return it, otherwise return the default value of 536.
Used by clients to read the incoming MSS in a SYN/ACK packet.
: TcpCksum \ *tcp tcplen iprem iploc -- cksum
A generic TCP checksum function. The parameters supplied
are used to form a 'pseudoheader' from which the actual
checksum is generated.
: GenTcpCksum \ *sk *tcp len --
Generate the TCP checksum using a prepared header and
socket data for the IP addresses. Len is the
length of the full TCP packet.
The words in this section are the defaults for transmitting the TCP receive window size to the remote end.
PowerNet queues input packets in the socket structure before
passing them to the BSD layer. Usually, ACKs are delayed.
There are three ways to implement receive window handling,
selected by the equate genWinSize?
in the PowerNet
configuration file.
genWinSize?
=0. The receive window is always set
to TCPWINDOWSIZE
.genWinSize?
=1.
By default, we assume that if all the input packets have
not been consumed by the time the ACK is sent, the system
is under heavy load and we do not want any more input for
the moment. This is indicated by setting the window size
to zero. Otherwise, the receive window is set to
TCPWINDOWSIZE
. Although this "bang bang" approach is
very crude, it works well unless many packets are being sent
from PowerNet, as can happen when web pages are served from
memory.genWinSize?
=2. The word genWindowSize ( *sk *tcp -- )
is provided by you to suit your application. See tcp.fth
for the examples.
: genWindowSize \ *sk *tcp --
Given a socket *sk and a TCP header at *tcp,
calculate the TCP window size and place it in the TCP
header. This version is used when genWinSize?
=0,
and implements the previous constant window size.
: skMoreIp? \ *sk -- flag
Return true if the socket has an input packet waiting.
: NextWinSize \ *sk -- size
Return the TCP Window size to be used for the packet send.
: genWindowSize \ *sk *tcp --
Given a socket *sk and a TCP header at *tcp,
calculate the TCP window size and place it in the TCP
header. This version is used when genWinSize?
=1,
and implements the "bang bang" control mechanism.
: #unsent \ *cb -- n
Number of bytes waiting to be sent.
If no transmit buffer has been allocated, this returns 0.
: #unacked \ *cb -- n ; SBD011
Returns the number of sent bytes awaiting acknowledgement.
If no transmit buffer has been allocated, this returns 0.
: setAckDelay \ *cb --
Set the ACK delay timer.
: checkRxWin \ *sk --
If the receive window is set to 0 and the delayed ACK timer
is not running, start the delayed ACK timer.
: checkTxUnsent \ *cb --
The TCP transmit primitive *\fo{SendTcpPkt) always clears
the delayed ACK timer. If there is no more data to send,
clear the delayed transmit flag and set the delayed ACK
timer if if the receive window is zero.
: MakeTCPhdr \ fl ol *sk *tcp --
Take data from the socket *sk and create a TCP header
at *tcp. The TCP header checksum is set to 0.
: SendTcpPkt { *sk fl *opt ol *dat dl | *pb *tcp pl -- res }
Transmit a TCP packet directly through the IP layer.
*sk |
points to the socket struct to be used |
fl |
the flag bits to use |
*opt,ol |
are the option pointer/length (if non-zero) |
*dat, |
dl are the data pointer/length (if non-zero) |
res |
= SOCKET_ERROR on failure, or no. of bytes senton success |
: SendTcpOpts \ *sk fl *opt ol -- ; SFP005
Send a packet that consists of the flags
and options without data.
: SendTcpFlags \ *sk fl -- ; SFP005
Send a packet that just consists of the flags,
without options or data.
: ForceTCPReset \ *sk -- ; SFP012
Force a socket reset, and then close the socket.
: sent<txbuff \ *cb len --
Bump the transmit buffer after len bytes sent from txbuffer.
: SendBuff>TCP { *cb *sk | nb -- }
Send any buffered data out. No action is taken if
there is nothing to send. Use this only in the TCP
state machine.
: SendAck>TCP { *cb *sk | nb -- }
Send any buffered data out with an ACK.
: skValid? \ *sk -- flag
Returns true if the socket structure is a valid TCP socket.
: skCanSend? \ *sk -- flag
Returns true if the socket structure is valid, the socket is
in TCPS_ESTABLISHED state and has not exceeded the maximum
number of retry attempts.
: sendtoTCP { hsocket *bf len flags | bufflen *sk *cb nb -- res }
Move user data into the socket's transmit buffer, waiting
in the word until all data has been transferred to the
buffer. This may involve packet transmission while waiting
for the buffer to empty. Because the socket may be closed
during transmission, the socket must be checked before each
buffer fill operation.
Note that parts of this code must not be pre-empted by
the scheduler.
: isTCP_Fin \ *tcp -- T|F
Set if FIN flag bit is set.
: isTCP_Syn \ *tcp -- T|F ; set if flag bit is set
Set if SYN flag bit is set.
: isTCP_Rst \ *tcp -- T|F ; set if flag bit is set
Set if RST flag bit is set.
: isTCP_Psh \ *tcp -- T|F ; set if flag bit is set
Set if PSH flag bit is set.
: isTCP_Ack \ *tcp -- T|F ; set if flag bit is set
Set if ACK flag bit is set.
: isTCP_Urg \ *tcp -- T|F ; set if flag bit is set
Set if URG flag bit is set.
: isTCP_SYN/ACK \ *tcp -- flag ; SFP005
Set if SYN and ACK flags are both set.
: SendNoSocket { *pb *tcp | *ip *sk *cb -- }
Handles sending the necessary RST replies when a connection
request is made for a TCP socket we don't know about.
Go send an RST message. The packet buffer is freed.
: set2MSL \ *cb --
Start the 2xMSL timer for disconnection.
: setIdle \ *cb --
Start the disconnect timer with the IDLE period.
: setConn \ *cb --
Start the connect timer with the TCPCONNECTTIME period.
: goSYN_REC \ *sk --
Put the given socket into TCPS_SYN_RECEIVED state.
This transition is performed by a server.
: goESTABLISHED \ *sk --
Move the socket into ESTABLISHED state.
: goLAST_ACK \ *sk --
Move the socket into LAST_ACK state from CLOSE_WAIT.
: goTIME_WAIT \ *sk --
Move the socket into TIME_WAIT state.
: goCLOSING \ *sk --
Move the socket into CLOSING state from FIN_WAIT1
: goFIN_WAIT_1 \ *sk --
Move socket into FIN_WAIT_1 state from SYN_RECVD or ESTABLISHED.
: goFIN_WAIT_2 \ *sk --
Move the socket into FIN_WAIT_2 state.
: goCLOSE_WAIT \ *sk --
Move the socket into CLOSE_WAIT state after receiving a FIN
in ESTABLISHED.
: goSYN_SENT \ *sk --
Move the socket into SYN_SENT state from CLOSED.
From PowerNet v4.8 onwards, listening sockets save connection attempts (receipt of SYN) on a linked list of connection items. When the application, e.g. a Telnet client, wants to use a connection, it inspects the queue, and transfers the next item on the list to a new socket in SYN_RECEIVED state. The new socket waits until the socket gets to ESTABLISHED state at which point the new connection can be used to transfer data.
Compared to the previous design, listening sockets use less RAM and, because connection attempts are stored, the design is kinder to single-threaded servers.
struct /ConnItem \ -- len
Holds unprocessed connection information after a SYN packet
has been received by a listening socket. All entries are in
native order.
: .ci \ *ci --
Display the connection queue item.
: .cq \ hs --
Display the socket's connection queue.
: TCPConnQ? \ *sk -- flag
Return true if there is a connection on the queue.
: #TCPConnQ \ *sk -- n
Return the number of connections on the queue.
: ciSame? \ *ci1 *ci2 -- flag
Return true if the two connection items are for the same
connection, i.e. a retry has has occurred.
: onConnQ? \ *ci *sk -- flag
Return true if the connection is already on the socket's
connection queue.
: >TCPConnQ \ *tcp *sk --
Add an entry to a listening socket's connection queue.
On failure no action is taken and we assume that the
connecting socket will retry.
: TCPConnQ> \ *skl *ska --
Take a connection from the listening socket *skl and
transfer it to the accepting socket *ska which enters
TCPS_SYN_RECEIVED state.
: DoTcpClosed { *pb *sk | *ip *tcp *cb -- }
Handle incoming packet while in CLOSED state.
: DoTcpListen { *pb *sk | *tcp *cb -- }
Handle incoming packet while in listening state (server).
: DoTcpSynReceived { *pb *sk | *tcp *cb -- }
Handle incoming packet while in SYN received state (server).
: DoTcpSynSent { *pb *sk | *tcp *cb -- }
Handle incoming packet while in SYN sent state (client).
: acktxbytes \ *cb nb -- nb' ; SBD011
Removes n bytes from the transmit buffer.
: DoTcpAcknowledge { *tcp *cb *sk | nb -- }
Handles incoming ACK. See Wright & Stevens about ack numbers.
: DoTcpEstablished { *pb *sk | *ip *tcp *cb tdlen -- }
Handle incoming packet while in established state.
: DoTcpLastAck { *pb *sk | *tcp *cb -- }
Handle incoming packet while in last ACK state.
: DoTcpFinWait1 { *pb *sk | *ip *tcp *cb -- }
Handle incoming packet while in FIN_WAIT1 state.
: DoTcpFinWait2 { *pb *sk | *ip *tcp *cb -- }
Handle incoming packet while in FIN_WAIT2 state.
: DoTcpClosing { *pb *sk | *tcp *cb -- }
Handle incoming packet while in closing state.
: DoTcpCloseWait \ *pb *sk --
Handle incoming packet while in CLOSE_WAIT state.
: DoTcpTimeWait { *pb *sk | *tcp *cb -- }
Handle incoming packet while in timed wait state.
: IncTcpPacket { *pb hs | *sk *cb -- }
Process an incoming packet destined for hs.
: RxTCPPacket { *pb | *ip *tcp hs *q bf -- }
Process an incoming TCP packet.
: DoTcpRetry? \ *cb *sk -- ; SFP021
Send data if tx retry timer expiration.
: DoTcpDelayedData? \ *cb *sk -- ; SFP021
Send data if the transmit delay timer has expired.
: DoTcpDelayedAck? \ *cb *sk -- ; SFP021
Send ack/data if ack delay timer expiration.
: DoTcpDisconDelay \ *cb *sk -- ; SFP021
Handle disconnect delay timer expiration.
: DoTcpConnectTimeout \ *cb *sk -- ; SFP021
Handles timeout for failed connections.
#5 tick-ms max equ TcpIdleMs \ -- ms ; SFP030
Interval in miliseconds between TCP idle checks. Using this
timer mechanism reduces the CPU load at the expense of
transmission performance on local networks. Setting the
value of TcpIdleMs
to zero turns the mechanism off.
The value set here in tcp.fth is only used if
TcpIdleMs
has not been defined in your PNconfig.fth
file.
0 value TcpIdleTimer \ -- time ; SFP030
Time for next TCPIDLE check.
: TcpIdle { | *sk *cb -- }
Handles TCP idle time processing.
: Tcp_Open { *sk | *cb res -- res }
Allows an application to open the TCP connection.
N.B. This is a blocking function. It will not return
until connected or timed out.
: Tcp_Close { *sk | *cb res -- res }
Allows an application to close the TCP connection cleanly.
This is the TCP primitive for the BSD disconnect
function.
: TcpInit \ --
Initialise the TCP layer.