P3 Sliding Window
P3 implements a sliding window, which is used to control the flow of packets. This page documents how the P3 sliding window is implemented in the America Online clients.
Note
I would like to personally thank Randell Jesup, the person who invented the P3 protocol for his guidance, and explanations about some of the finer points of P3 internals.
Sliding Window Overview
Technically, a sliding window is a strategy used to control the flow of packets between two P3 endpoints. The basic idea behind sliding windows is to use a range of sequence numbers that determine when packets can be transmitted. The sliding part of a sliding window, is the idea that the range itself shifts as packets are acknowledged and new ones are sent.
Example
Before looking at how sliding windows are implemented in P3, let's take a look
at a generic example. Assume we have a 3-bit sequence number with a maximum
window size of 4. This means that (for this example) the valid sequence numbers
are 0
through 8
inclusive.
Initially there are no packets in the window.
┌─┬─┬─┬─┬─┬─┬─┬─┐ │0│1│2│3│4│5│6│7│ └─┴─┴─┴─┴─┴─┴─┴─┘
After the first DATA
packet is transmitted using seqence number 0
the window has one packet.
Client: DATA (tx_seq = 0) ┌─┐ ├─┼─┬─┬─┬─┬─┬─┬─┐ │0│1│2│3│4│5│6│7│ ├─┼─┴─┴─┴─┴─┴─┴─┘ └─┘
The next DATA
packet transmitted has the sequence number 1
and the
window now has two packets.
Client: DATA (tx_seq = 1) ┌───┐ ├─┬─┼─┬─┬─┬─┬─┬─┐ │0│1│2│3│4│5│6│7│ ├─┴─┼─┴─┴─┴─┴─┴─┘ └───┘
Assume the client continues transmitting, and sends two more DATA
packets
with the sequence numbers 2
and 3
.
Client: DATA (tx_seq = 2) ┌─────┐ ├─┬─┬─┼─┬─┬─┬─┬─┐ │0│1│2│3│4│5│6│7│ ├─┴─┴─┼─┴─┴─┴─┴─┘ └─────┘ Client: DATA (tx_seq = 3) ┌───────┐ ├─┬─┬─┬─┼─┬─┬─┬─┐ │0│1│2│3│4│5│6│7│ ├─┴─┴─┴─┼─┴─┴─┴─┘ └───────┘
At this point, the sliding window has reached it's maximum size (4
in
our example). That is, the window is full. At this point the client can no
longer transmit any DATA
packets until the host acknowledges the sent
packets. This is how a sliding window controls the flow of packets.
At some later point the host acknowledges the first packet sent by the client
(the one with sequence number 0
).
Host: ACK (rx_seq = 0) ┌─────┐ ┌─┼─┬─┬─┼─┬─┬─┬─┐ │0│1│2│3│4│5│6│7│ └─┼─┴─┴─┼─┴─┴─┴─┘ └─────┘
The window has shrunk slightly, and now contains three unacknowledged packets.
Technically the client could send another DATA
packet, but we will assume
instead the host sends another acknowledgement of sequence number 1
.
Host: ACK (rx_seq = 1) ┌───┐ ┌─┬─┼─┬─┼─┬─┬─┬─┐ │0│1│2│3│4│5│6│7│ └─┴─┼─┴─┼─┴─┴─┴─┘ └───┘
The window size is now 2
since the client transmitted four DATA
packets, and the host acknowledged two packets.
Since the window is available, the client can now transmit additional DATA
packets. For example if the client transmitted a single DATA
packet with the
sequence number 4
, the window would look like:
Client: DATA (tx_seq = 4) ┌─────┐ ┌─┬─┼─┬─┬─┼─┬─┬─┐ │0│1│2│3│4│5│6│7│ └─┴─┼─┴─┴─┼─┴─┴─┘ └─────┘
Notice how the window moves along from left to right? That's the "sliding" part of a sliding window.
Boundary Crossing
A point worth considering is what happens when sequence numbers run out? To
continue the example, assume that the window currently has two packets, for
sequence numbers 6
and 7
. Since the maximum size of the
window in this example is 4
, the client is allowed to transmit DATA
packets. However, what should the sequence numbers be, and what will the window
look like?
Client: DATA (tx_seq = ???) ┌───┐ ┌─┬─┬─┬─┬─┬─┼─┬─┤ │0│1│2│3│4│5│6│7│ ??? └─┴─┴─┴─┴─┴─┼─┴─┤ └───┘
The answer is actually simple. Just wrap around and start the sequence numbers
over again. Thus the sequence number of the next transmitted packet will be
0
.
Client: DATA (tx_seq = 0) ┌─────┐ ┌─┬─┬─┬─┬─┬─┼─┬─┬─┼─┬─┬─┬─┬─┬─┬─┐ │0│1│2│3│4│5│6│7│0│1│2│3│4│5│6│7│ ... └─┴─┴─┴─┴─┴─┼─┴─┴─┼─┴─┴─┴─┴─┴─┴─┘ └─────┘
Sliding Window Implementation
This section discusses details of how a sliding window is implemented by the P3 handler portion of the client.
Sender Window
The P3 handler implements a sliding window for sending packets. That is, the window controls the packets being sent, not the packets being received. While it is generally possible to have one sliding window for sending, and another one for receiving, P3 does not do this.
The tx_seq
Field
The tx_seq
field refers to a packet's sequence number. It is incremented by
1
for each DATA
packet that is transmitted. The other packet types
(called control or "unnumbered" packets) do not increment the tx_seq
value.
Instead they reuse the value of the last tx_seq
that was sent.
The rx_seq
Field
The rx_seq
field contains the acknowledgement information. It is used to
acknowledge all correctly received packets up to and including the one
described by the rx_seq
field. This is called an implicit acknowledgement.
For example, assume the client transmits four DATA
packets to the host
starting with 0x11
as the first tx_seq
value. For each transmitted
packet the window size grows by one.
sequenceDiagram
participant Client
participant Host
Client ->> Host: DATA, tx_seq = 0x11, rx_seq = 0x20
Note over Client: Window = {0x11}
Client ->> Host: DATA, tx_seq = 0x12, rx_seq = 0x20
Note over Client: Window = {0x11, 0x12}
Client ->> Host: DATA, tx_seq = 0x13, rx_seq = 0x20
Note over Client: Window = {0x11, 0x12, 0x13}
Client ->> Host: DATA, tx_seq = 0x14, rx_seq = 0x20
Note over Client: Window = {0x11, 0x12, 0x13, 0x14}
At this point assume the host sends an ACK
packet with the rx_seq
field set
to 0x13
. This acknowledges the correct receipt of packets
0x11
, 0x12
, and 0x13
. The client's window has now
shrunk to only containing the reference to the packet with tx_seq
value
0x14
.
sequenceDiagram
participant Client
participant Host
Host ->> Client: ACK, tx_seq = 0x20, rx_seq = 0x13
Note over Client: Window = {0x14}
Window Parameters
Since the tx_seq
and rx_seq
fields are one byte each, the theoretical range
of values is 0
to 0xFF
(0
- 255
). However
in practice the range of values used is 0x10
to 0x7F
. The
client uses the value 0x7F
as the initial value for both the tx_seq
and rx_seq
fields.
The maximum size of client's window is 0x10
(16
decimal). In
practice the window rarely gets this full due to
preemptive acknowledgments.
Transmitting Acknowledgements
The ACK
packet type is one way to convey acknowledgement information. However
since every P3 packet contains an rx_seq
field, any packet type excluding
INIT
can be used to transmit acknowledgement information. In effect rather
than wasting bandwidth by sending separate ACK
packets, the acknowledgement
information can be sent in the next outbound packet. This is called
piggybacking.
In practice, because of the high volume of data transmitted back and forth, you
typically see acknowledgement information in DATA
packets (though for
example it could also come in the form of a NAK
packet.)
Timeouts
P3 uses a mechanism called heartbeats to handle packet timeouts. Despite the
name, it is not a mechanism to keep the connection alive. Instead heartbeats
are used for eliciting acknowledgement information from the other side after
a timeout period. This timeout period has been observed to be 12
seconds.
If an INIT
packet has been unacknowledged, the client retransmits the packet
INIT
packet. Graphically it looks like:
sequenceDiagram
participant Client
participant Host
Client ->> Host: INIT, tx_seq = 0x7F, rx_seq = 0x7F
Note over Client,Host: 12 seconds later
Client ->> Host: INIT, tx_seq = 0x7F, rx_seq = 0x7F
However if a DATA
packet has been unknowledged, the sender transmits a
HEARTBEAT
packet. The receiver of the HEARTBEAT
then transmits an ACK
packet in response.
sequenceDiagram
participant Client
participant Host
Client ->> Host: DATA, tx_seq = 0x10, rx_seq = 0x7F
Note over Client,Host: 12 seconds later
Client ->> Host: HEARTBEAT, tx_seq = 0x10, rx_seq = 0x7F
Host ->> Client: ACK, tx_seq = 0x7F, rx_seq = 0x10
The client will attempt to transmit timed-out packets (INIT
or DATA
) up to
10 times. After that it disconnects and presents the user with an error message.
Preemptive Acknowledgement
Sliding windows can run into a delay-related problem when transmitting enough data to fill up the sliding window. For example, assume the client needs to transmit 20 packets worth of data, and all of the packets must be received before the host can process the incoming data.
Graphically, the situation looks like:
sequenceDiagram
participant Client
participant Host
Client ->> Host: DATA, tx_seq = 0x20, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x21, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x22, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x23, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x24, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x25, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x26, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x27, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x28, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x29, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x2A, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x2B, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x2C, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x2D, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x2E, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x2F, rx_seq = 0x55
Note over Client,Host: Sender window full
Note over Client: Data still pending
Note over Host: Needs pending data to continue
At this point, unless there is traffic from the host, P3 will pause sending
until a timeout occurs (12
seconds in practice) and then send a
HEARTBEAT
, which will elicit an ACK
and close the window.
To avoid this delay, P3 implements a preemptive acknowledgement. A preemptive
acknowledgement is when a receiver sends an ACK
packet after it has
received a specific number of unacknowledged DATA
packets. In practice, the
preemptive acknowledgement threshold has been observed to be 8
.
This means if the receiving side of a P3 connection receives 8
DATA
packets, and has not yet acknowledged them it sends an ACK
packet.
This looks like:
sequenceDiagram
participant Client
participant Host
Client ->> Host: DATA, tx_seq = 0x20, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x21, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x22, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x23, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x24, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x25, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x26, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x27, rx_seq = 0x55
Host ->> Client: ACK, tx_seq = 0x55, rx_seq = 0x27
Note over Client: Window is now empty
Client ->> Host: DATA, tx_seq = 0x28, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x29, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x2A, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x2B, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x2C, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x2D, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x2E, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x2F, rx_seq = 0x55
Host ->> Client: ACK, tx_seq = 0x55, rx_seq = 0x2F
Note over Client: Window is now empty
Client ->> Host: DATA, tx_seq = 0x30, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x31, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x32, rx_seq = 0x55
Client ->> Host: DATA, tx_seq = 0x33, rx_seq = 0x55