# Transmission Control Protocol

NOTE: This unit is a WIP, this page doesn't describe what the unit actually
      does yet.

## TCP handler

This unit registers a handler with [com/ip4]. It also creates a [lib/coop] event
ID IP4TCPRECV, which has one argument: arg1=tcpcon. The receiving logic is as
follow:

1. Look in the tcpcon list for the incoming addr/port pair.
2. If found, process the packet (update the TCPCon in whatever way is necessary
   w.r.t. the incoming packet) then dispatch IP4TCPRECV with arg1 being the
   tcpcon. We're done.
3. If not found, if it's not a SYN packet, discard. Otherwise dispatch
   IP4TCPRECV with arg1=0

At the moment the event is dispatched, the Active Frame [com/link] is set which
means that TCPMessage is valid.

## Interacting with this unit

When comes the time to play with TCP connections, there are multiple options.
Whatever you choose, however, must play nice with [lib/coop], which means it
should probably be an application.

First, one could imagine an application that initiate TCP connections with
remote servers. In that case, you could do away with no IP4TCPRECV handler at
all. You just open your connection using the API described here, hold references
to them and poll them regularly, do your thing, then close them.

This polling could take place in IDLE, but it would be more efficient to do so
in IP4TCPRECV. This event is triggered for all applications for any TCP
connection, which means that you might receive spurious event dispatches, but
it's still better timing than IDLE.

Second, you might be listening for incoming connections. In this case, you need
to have a IP4TCPRECV handler with specific logic for when arg1=0, where you
possibly open a new connection using the API described here. When you do, set
arg1 to your new connection in order to indicate to whoever else is listening to
IP4TCPRECV that this new SYN packet has been handled.

Instead of keeping track of TCPCon instances, something that this unit already
does globally, you might want to simplify your life and set the "tag" value to
something unique to your application in order to remember that this is one of
"your" connections.

You can look at [bench/tcp] for simple examples.

## Transmission Control Block

TCP connections need an elaborate structure to manage incoming and outgoing
data. The central concept of this structure is that of the sliding window. TCP
specs call this structure the TCB, Transmission Control Block.

TCBs are covered in a separate unit, [com/tcb].

## Incoming ACK timers

One of the efficiencies built into TCP is this idea that ACKs can be merged.
It's not every little segment that needs a receipt. Moreover, because a reply
often follows a request directly, it's even possible that an ACK be made by that
reply.

We implement this with an ACK timer. First, we define a threshold with
MAXINACKMS. Then, we have a background job [lib/coop] that, on IDLE, goes
through all TCBs needing ACK and check if the threshold has been reached. If
yes, craft a ACK and send it.

In parallel, whenever we send a packet for whatever purpose (for example
outgoing contents), we check if there's a need for a ACK. If there is, we
include that ACK right here, which means that the IDLE timer won't ever have a
chance to reach the threshold. We saved a packet!

## Outgoing ACK timers

When sending contents, we need to set a threshold at which point we resend the
contents. This threshold is determined by MAXOUTACKMS.

Moreover, the contents we have to send might exceed MSS (Maximum Segment Size,
which is smaller than the window size), which means we have to send our contents
in chunks.

So, first of all, writing to a TCB writes to the outgoing buffer but doesn't
actually send anything. The TCP background job does it.

The background job goes through all TCBs that have outgoing contents to send.
For each of them, if there's contents to send and that the window allows it,
send a packet. If the timer isn't already running, set it, but otherwise, leave
it untouched.

In parallel of that, whenever we receive an "effective" ACK (that is, a ACK that
is for a range that actually makes our sliding window move), we reset the timer
if there's further outgoing contents to ACK, or disable it if we're 100% synced.

In parallel of all that, we check for outgoing timeouts. Whenever the threshold
is reached for a TCB, we consider all outgoing packets lost and start sending
again, resetting the timer.

TCP specs speak of multiple timers, but I think it's not worth bothering. This
single timer implementation will, I think, do an optimal job in a good majority
of the cases.

## Use of timers when establishing a connection

It's a guiding principle of this unit that packets are only sent during the TCP
"work" routine, whose main purpose is to flush outgoing data from the buffers.

This principle allows us not to sweep the Active Frame [com/link] from under
our feet when executing incoming logic.

This means, for example, that the SYN+ACK that we need to send when accepting
a new connection isn't sent directly in "acceptcon". "acceptcon" changes the
status of the connection and only in the work routine is this SYN+ACK sent.

The status enum corresponds to the status from the specs. For example, upon
accepting a connection, status will be SynReceived. When in that status, we're
supposed to have sent a SYN+ACK. But when encountering a connection with that
status, how does the work routine discriminates between those that sent their
SYN+ACK and those that haven't? The outgoing timer. When it's 0, it means that
it hasn't been sent yet. It's the same principle for all statuses.

## PSH behavior

In the design described above, we already push received data as soon as it's
acknowledged. We can therefore ignore the PSH flag on the incoming side.

On the outgoing side, TCBs have a TFlags field which is ORed into outgoing
packets, so you can set the PSH bit in there if needed.

## URG behavior

Urgent data is out-of-band data. When an URG packet is received, a TCPURGENT
event is triggered and whoever listens to it can act on it.

To send an urgent packet, one needs to call "sendurgentpacket". Its contents
can't be larger than MSS.

TODO: theoretically, we should also be implementing a logic where urgent data
is mixed in regular in outgoing packets for efficiency, but is it really worth
the trouble? What is URG actually used for?

## Behavior at limits

There are four tricky limits with our design:

1. Outgoing buffer is full
2. Incoming buffer is full
3. There are no more available buffers for either outgoing or incoming data
4. We're out of connections

When writing outgoing data to a TCB that already has an outgoing buffer, it's
possible that this buffer is full of unacknowledged data. In this case, writing
to it will yield "0 bytes written". This means that whenever writing to a TCP
connection, one has to provide logic for the possibility that not all data has
been written.

When writing outgoing data to a TCB that doesn't have a buffer yet, and if there
is no available buffer, the result is similar: 0 bytes written.

When there's incoming data to a TCB that already has an incoming buffer, as long
as the system consumes the incoming data fast enough, the window system makes it
impossible not to find a place for it. However, if for whatever reason the
incoming data isn't consumed fast enough, we need to drop incoming data.

Incoming window size *can* overlap unconsumed data in the rolling buffer, but
when writing incoming packets, we only write and acknowledge what fits from the
incoming packet, dropping the rest.

However, when there's incoming data for a TCB that doesn't have a buffer, then
we need to drop that data. We never acknowledge it, so it's going to be resent
at some point, hopefully at a moment when we have available buffers.

Lastly, there's the case of a new incoming SYN packet at a time where all TCBs
are used up, we refuse the connection and answer with a RST packet.

## Connections

This unit maintains an array of TCPCon structures which are basically a pair of
incoming and outgoing TCBs along with a few supporting fields. There's a fixed
number of them and when we're out of them, we can't open a new connection unless
we close one.

Connections are Streams [io/stream]. Writing to it writes to the outgoing TCB,
and reading from it reads from the incoming TCB. The sending and receiving of
the packets that fill/consume those TCBs happen asynchronously.

For the outgoing side, there's a background task that takes care of emptying
the outgoing TCBs onto the network on idling.

For the incoming side, this unit it hooked into [com/ip4]'s incoming handlers
and that's where incoming packets are mapped to open connections and that from
there, incoming TCBs are filled.

Calling "close" on a TCPCon initialize the TCP closing sequence and then frees
the structure for a new connection.

## API

### Constants

TCPPROTO    6. The protocol ID for TCP
IP4TCPRECV  [lib/coop] event upon receiving a TCP packet.

### Structures

struct TCPMessage {
  beshort SourcePort DestPort ;
  beint SeqNum AckNum ;
  beshort Flags Window Checksum UrgentPtr ;
  [void,0] Options ;
}
ip4payload TCPMessage bindstruct

Fields above exactly map to the TCP message structure from the standard.

extends Stream struct TCPCon {
  uint  addr port status ints outts tag ;
  TCBIn in ;
  TCBOut out ;
}

addr   Remote address of the connection. 0 means that the structure is free.
port   Remote port of the connection.
status One of the status enum described below
ints   Timestamp of the oldest unacknowledged piece of data we've received.
       0 means that everything we have has been acknowledged.
outts  Timestamp of the oldest outgoing packet we're waiting an ACK for.
       0 means we're not waiting on anything.
tag    A value that initialized to 0 when opening but otherwise stays untouched
       by this unit.
in     Incoming TCB (inlined struct, not a pointer)
out    Outgoing TCB (inlined struct, not a pointer)

enum Closed SynSent SynReceived Established
enumrev statusrev

Closed      Connection is closed, nothing is happening
SynSent     We've initiated a connection with a SYN and are waiting for a
            SYN+ACK
SynReceived We've received a SYN and sent a SYN+ACK. We're waiting for an ACK
Established The connection is established and working normally.

### Words

findcon ( addr port -- con-or-0 )
  Yield the connection corresponding to the specified remote addr/port pair or
  0 if not found.

acceptcon ( -- con-or-0 )
  Assuming that current TCPMessage is an incoming SYN, open a new TCPCon. If
  successful, yield a pointer to that connection. If there is no available
  connection, yield 0.

  It's not possible to open the same addr:port twice. If you call acceptcon for
  a pair that already has an open connection, that connection will be yielded,
  but that won't be a freshly opened connection.

tcpwork ( -- )
  Go through all active connections and for each of them, send the outgoing
  packets that need to be sent. This is the only word in the unit that sends
  packets and thus the only word that changes the Active Frame [com/link].

.tcpcon ( con -- )
  Print vital statistics on the specified connection.

.tcpcons ( -- )
  Print vital statistics for all used connections.
