RFC 916
Reliable Asynchronous Transfer Protocol (RATP)
RFC 916: Reliable Asynchronous Transfer Protocol (RATP) โจ
Okay bestie, welcome to RFC 916! ๐ This one is from October 1984, written by G. Finn from ISI ๐ . It's an RFC (Request for Comments) that's basically a "hey everyone, here's a proposal, tell me what you think" kind of doc, so it's not a finished standard. It doesn't obsolete or update anything, so it's a totally fresh start ๐ธ. The whole point is a protocol called RATP that lets two programs talk reliably over a link, so the stuff you send comes out the other end intact and unaltered ๐ซถ.
This is part 1 of 9, so there's more coming! ๐ฆ
Status of This Memo ๐
Status of This Memo โจ
Okay so the vibe here is: this RFC is a proposed protocol for the ARPA-Internet community ๐, and it's basically begging for discussion and suggestions for improvements ๐. Distribution is totally unlimited, so share it with everyone ๐.
Here's the tea ๐: this paper proposes and specifies a protocol that lets two programs reliably communicate over a communication link โจ. It makes sure whatever goes in one end, if received, arrives at the other end intact and unaltered ๐. The protocol is called RATP, and it's designed for a full duplex point-to-point connection ๐ฅ. It also has some features that are tailored to the RS-232 links that were super common back then ๐ .
Introduction ๐ธ
We are literally witnessing an explosion in the small or personal computer market ๐. These inexpensive computers usually aren't hooked up to a network, so they're mostly stand-alone devices ๐ฅบ. BUT almost all of them have an RS-232 interface, and they usually have a modem too ๐ป. That means they can talk over the phone to any other similarly equipped computer โ๏ธโจ.
The phone system is a pervasive network, but the big catch is how unpredictable the circuit quality is ๐. Phone circuits were designed for voice, not data ๐๏ธ. Voice can handle way more "noise" than a data circuit can, so a voice circuit tolerates a much higher level of noise than a data circuit does ๐. Which means it's not unusual for a byte to get noise inserted into it, and for spurious data bytes to get added to the stream too ๐ญ.
So we really need a way to reliably transfer data over an RS-232 point-to-point link, and the need is severe ๐ฅ. As more powerful personal computers show up, they need to talk to each other more and more ๐. The new markets and services these computers will let people use will lean heavily on the phone system ๐. Think electronic mail, electronic banking, ordering merchandise from home with a PC, and so on ๐๏ธ. As the information revolution keeps going, data itself becomes a commodity ๐. All of that needs the data sent or received to be accurate ๐ซถ.
1. Philosopy of Design ๐ฆ
Okay, so there were a LOT of tradeoffs made here ๐ง . The top priority was ensuring reliability, and then favoring simplicity of implementation ๐. The hope is that this protocol is simple enough for not only small computers but also stand-alone devices with microcomputers that accept commands over RS-232 lines ๐ค. Fancy stuff like dynamic window management [TCP 81] got left out just for simplicity's sake ๐ . Having several packets outstanding at once was also cut for the same reason, and data queued to send when a connection gets closed remotely is discarded ๐๏ธ. That removes two states from the protocol implementation, which is honestly iconic ๐.
Someone might ask why we even need this protocol, since there are already RS-232 transport protocols out there ๐ค. True, but some of them lack one or more features that are vitally important, or they're just too complex ๐ค. See Appendix II for a quick survey ๐.
- A protocol that can only transfer data in one direction can't use a single RS-232 link for a full-duplex connection ๐ โโ๏ธ. So it can't act as a bridge between most computer networks ๐. It also can't support any app that needs two-way data exchange ๐. It's not really a platform for building most higher level applications either ๐๏ธ. One-way data flow is fine for a weak version of file transfer, but not enough for remote terminal service, transaction oriented processing, and things like that ๐.
- Some existing RS-232 transport protocols only allow fixed size packets, or they don't let the receiver put a limit on the sender's packets ๐ฆ. If that block size is too large for the receiving end concentrator, the concentrator will probably immediately invoke flow control ๐ง. That leads to a ton of dropped and damaged packets ๐ฉ. The receiver MUST be able to tell the sender, at connection initiation, the maximum packet size it's prepared to receive ๐.
Wait, I need to pause on that. The original says "must" there, so I'm keeping it as a MUST, not softening it. Receivers have to inform the sender of the maximum packet size at connection initiation, no exceptions ๐.
- Some protocols have a bunch of features that may or may not be implemented at each site ๐ฒ. Examples are several checksumming algorithms, different data transmission restrictions, sometimes 8-bit data, sometimes restricted ASCII subsets, and so on ๐. Then every site is supposed to implement all the various features, and that's rarely actually met ๐.
Finally, this document is kinda huge ๐. It tries to fully specify how the protocol behaves ๐. A careful explanation of how it behaves in all circumstances is needed so any questions an implementor has can be answered, and so the protocol can be verified ๐งช. The size of this spec should not be read as a sign that it's hard to implement ๐โโ๏ธ.
1.1. The Host Environment ๐
This protocol is designed to run on any point-to-point communication link that can transmit and receive data ๐ก. The link doesn't have to be asynchronous ๐. Neither end controls when the other decides to transmit, so the link SHOULD be full duplex ๐. In the vast majority of cases, an asynchronous full-duplex RS-232 link is what gets used ๐.
In practice, this protocol could live anywhere from the RS-232 driver software on a microcomputer in a concentrator all the way up to the user software level ๐งฉ. Ideally it lives inside the host operating system or concentrator ๐ . It should be an option tied to the communication link, selectable by the user program ๐๏ธ. If reliable data transmission matters a lot, the software would pick that option ๐ฏ. Once the option is chosen, the initial connection handshaking begins ๐ค.
There are plenty of cases where this protocol won't live in a host operating system (at first, it always won't) ๐ . There's also tons of stand-alone equipment that accepts commands over an RS-232 link, like a plotter ๐จ๏ธ. Having a several hour plot ruined by noise on an unreliable line happens way too often ๐ญ. So the sending and receiving sides should be as simple as possible, so applications and stand-alone devices can use the protocol without spending much time or space ๐ง.
1.2. Relation to Other Protocols ๐
The "layering" concept has become the accepted way to design communication protocols ๐ฐ. Since this protocol operates in a point-to-point environment, it covers both the datagram and reliable connection layers ๐. No multi-network capability is implied ๐ . When a link using this protocol bridges different networks, other protocols like TCP are expected to have their packets fragmented and encapsulated inside this protocol's packets ๐.
2. Packet Specification ๐ฆ
RATP sends data over a full-duplex communication link โจ. Data can go in both directions over the link ๐. A stream of data is communicated by being broken into 8-bit pieces called octets ๐ง. Those octets are serially accumulated to form a packet ๐. The packet is the unit of data communicated over the link ๐. The protocol virtually guarantees that data sent at one end, if received, arrives unaltered and intact at the other end ๐ซถ.
Within an octet, all eight bits contain data 8๏ธโฃ. All eight bits MUST be preserved by the link interface and the associated device driver ๐. In many operating systems this is ensured by putting the connection into RAW or BINARY data mode ๐ ๏ธ. During normal operation, packets are transmitted and acknowledged one at a time over the link in each direction ๐. Each packet is made of a HEADER followed by a DATA portion ๐งฑ. The DATA portion MAY be empty ๐ซง.
NOTE: Some older operating systems and devices don't allow 8-bit communication over an RS-232 link โ ๏ธ. Most of them allow restricted 7-bit communication instead ๐. RATP can automatically detect this during connection initiation, and it uses a special packing strategy when full 8-bit communication isn't possible ๐งฉ. This is totally transparent to any client software ๐ซถ. See Appendix I for a discussion of this case ๐.
2.1. Header Format ๐งพ
Byte No.
+-------------------------------+
| |
1 | Synch Leader | Hex 01
| |
+-------------------------------+
| S | A | F | R | S | A | E | S |
2 | Y | C | I | S | N | N | O | O | Control
| N | K | N | T | | | R | |
+-------------------------------+
| |
3 | Data length (0-255) |
| |
+-------------------------------+
| |
4 | Header Checksum |
| |
+-------------------------------+
Header Portion of a Packet
2.1.1. Synch Leader ๐ฏ
RS-232 gives us a self-clocking communications medium ๐ฐ๏ธ. The wires data flows over are often in "noisy" environments, where the noise can show up as unwanted added data ๐ข. That's why the beginning of a packet is marked by a one octet SYNCH pattern ๐ฆ. This lets the receiver throw away noise that shows up on the connection before a packet arrives ๐๏ธ. The SYNCH pattern is defined to be the one octet hex 01, the ASCII Start Of Header character <SOH> ๐.
The SYNCH pattern SHOULD ideally be unlikely to occur as the result of noise ๐ค. Different modems and such respond to noise differently, so this is hard to achieve ๐ต. The chosen pattern is thought to be a good compromise, since many modems show noise by setting the high order bits ๐ญ. Situations will happen where the receiver is scanning for the start of a packet and sees a spurious SYNCH pattern ๐. To catch that kind of situation, a header checksum is provided (see below) ๐ก๏ธ.
2.1.2. Control Bits ๐๏ธ
The first octet after the SYNCH pattern holds a 5-bit field of control flags plus two 1-bit sequence number fields ๐ข. The last bit is reserved and MUST be zero ๐ โโ๏ธ.
2.1.2.1. SYN - Synchronize Flag ๐ค
Synchronize the connection ๐. No data MAY be sent in a packet that has the SYN flag set ๐ซ.
2.1.2.2. ACK - Acknowledge Flag โ
The acknowledge number is significant ๐. Data MAY accompany a packet that has this flag set, as long as neither SYN, RST, nor FIN is also set ๐. Once a connection has been established, this is always set ๐.
That's a wrap on part 1! ๐ Part 2 is coming next, bestie, so stay tuned ๐ฆโจ
2.1.2.3. RST - Reset Flag ๐จ
Okay bestie, RST is the panic button. It's how one end of a connection can reset the other when something anomalous (aka weird) happens. ๐ญ And no data may be sent in a packet that has the RST flag set. No exceptions, no cap ๐
2.1.2.4. FIN - Finishing Flag ๐ฆ
FIN basically says "I'm done." It means no more data will be sent to the other end of the connection, and no more data will be accepted either. Same rule as RST, though: no data may be sent in a packet that has the FIN flag set. Gone, but make it clean โจ
2.1.2.5. SN - Sequence Number ๐ข
This is the Sequence Number attached to this packet. Simple, iconic, no drama ๐
2.1.2.6. AN - Acknowledge Number ๐ซถ
If the ACK control flag is set, this is the next Sequence Number the sender of the packet is expecting to receive. It's giving "here's what I want next" energy ๐ธ
2.1.2.7. EOR - End of Record ๐
The EOR bit is a little helper for higher level protocols that might need to fragment their packets. Like, the Internet protocol often uses packets as big as 576 octets, and a packet that size would need fragmenting when it's carried over this protocol. ๐ If EOR is set, it's just telling the higher level that a record ends in this packet. It's information only, so the higher level is the one responsible for setting or clearing it when it builds packets to send. Also, the interface to the protocol has to give a way to read, set, and clear this bit ๐
2.1.2.8. SO - Single Octet โจ๏ธ
Okay, so one use case that's considered super important is single character transmission. Picture a user typing on a personal computer keyboard and sending each key to another computer over an unreliable link. Rapid interactive response is the goal, so lots of typed characters are expected to go out one at a time. To keep the overhead low for this special case, the SO control flag exists ๐
The SO flag means nothing if the SYN, RST, or FIN flags are set. Assuming none of those are set, SO set means this packet carries a single octet of data. Since the data is always one octet, the LENGTH field is superfluous and actually holds the data octet itself. The data portion of the packet isn't transmitted at all ๐ง
This is the glow-up part ๐ Without SO, a packet would need seven octets. With it, only four are needed, so transmission efficiency improves by 40 percent. And the header checksum still protects that single octet of data ๐
2.1.3. Length ๐
The second octet after the SYNCH pattern holds length info. If the SYN bit is present, this field is the maximum number of data octets the receiver is allowed to transmit to the sender in any single packet. That quantity is called the MDL. A sender may say it doesn't want to accept any data octets by setting an MDL of zero. In that case, presumably all the data is moving from sender to receiver. Obviously, if data is going to be transmitted, both sides of a connection can't have an MDL of zero ๐
โโ๏ธ
If none of SYN, RST, or FIN are set, this is an 8-bit field called LENGTH. In that case, if the SO bit is set, LENGTH contains a single octet of data. Otherwise, it holds the count of data octets in the packet. A single packet can carry anywhere from zero (0) up to MDL octets of data. And MDL is capped at a maximum of 255 ๐ฅ
2.1.4. Header Checksum ๐งฎ
The header checksum algorithm is the 8-bit version of the 16-bit data checksum we'll get to below. It's built and processed in a similar way, just eight bits wide instead of sixteen. When sending, the header checksum octet starts out cleared. Then an 8-bit sum of the control, length, and header checksum octets is formed using end-around carry. That sum gets complemented and stored in the header checksum octet. On receipt, the receiver forms the same 8-bit end-around carry sum of those same three octets. If the sum is octal 377, the header is presumed valid. In all other cases, the header is assumed to be invalid โ
Why bother giving the header its own protection? That's covered in the chapter on error handling. The header checksum covers the control and data length octets, and it does not include the SYNCH pattern ๐ฆ
2.2. Data Format ๐
The data portion of a packet comes right after the header if the SO flag isn't set and LENGTH > 0. It's made of LENGTH data octets, immediately followed by two data checksum octets. If it's present, the data portion has LENGTH+2 octets total ๐
Data Byte No.
+-------------------------------+
1 | | High order \
+-- --+ > Word
2 | | Low order /
+-- --+
. | Data | High order \
+-- --+ > Word
. | | Low order /
+-- --+
LENGTH | | High order \
+-------------------------------+ > Word
| Imaginary padding octet 0 | Low order /
+-------------------------------+
LENGTH+1 | | High order \
+-- Data Checksum --+ > Word
LENGTH+2 | | Low order /
+-------------------------------+
Data Portion of a Packet
2.2.1. Data Checksum ๐ธ
The last two octets of the data portion are a data checksum. This is a 16-bit checksum that catches incorrectly transmitted data. It's been shown to be a reliable way to detect most kinds of bit drop-out and bit insertion. It doesn't guarantee that every such error gets caught, though. The chance that one slips through undetected is on the order of 2**(-16) ๐
The checksum octets come after the data so the sender can compute the checksum while it's transmitting, and the receiver can compute it while it's receiving. That means neither side has to store the whole packet and then go back for a separate checksumming pass. Efficient, bestie โจ
Order of Transmission
How 8-bit octets get assembled into 16-bit words is a big deal. You have to decide which octet is the low order one and which is the high order one, and that has to be rigidly specified if you want to compute 16-bit checksums. This protocol uses big endian ordering, as shown in the diagram above [Cohen 81] ๐ง
Checksum Algorithm
The checksum algorithm is similar to the one used by the IP and TCP protocols [IP 81] [TCP 81]. It's been shown to be reliable and relatively easy to compute. If you want a deeper dive into its properties, the interested reader can check out [TCP Checksum 78] ๐
The checksum algorithm works like this:
SENDER ๐
The unsigned sum of the 16-bit words in the data portion of the packet is formed. Any overflow gets added into the lowest order bit. This sum doesn't include the header portion of the packet. When building a packet for transmission, the two octet checksum field is zero. The sum is then bit complemented and inserted into the checksum field before the packet is sent.
If the total number of data octets is odd, the last octet is padded on the right (low order) with zeros so it forms a 16-bit word for checksum purposes. That pad octet is not transmitted as part of the packet ๐ซง
RECEIVER ๐จ
The sum is computed the same way, but this time it includes the values received in the checksum field. If the 16-bit sum is octal 177777, the data is presumed valid. In all other cases, the data is presumed invalid ๐ โโ๏ธ
This unsigned 16-bit sum adds 16-bit quantities, with any overflow bit added into the lowest order bit of the sum. That's called 'end around carry', and it gives a couple of really nice properties: 1) it's fully commutative, so summing in any order gives the same result, and 2) if you apply a given rotation to each quantity before addition, then apply the inverse rotation to the final total, the result is equivalent to any other rotation you pick. That second one is the real win, because it lets little endian machines like the PDP-11 grab 16-bit quantities and add them in byte swapped order ๐
Here's the PDP-11 code for calculating the checksum:
CLR R0 ; R0 will get the checksum
; R2 contains LENGTH count
LOOP: ADD (R1)+,R0 ; Add the next 16-bit byte
ADC R0 ; Make any carry be end around
SOB R2,LOOP ; Loop over entire packet
COM R0 ; Bit complement result
2.3. Sequence Numbers ๐
Sequence numbers team up with acknowledge numbers to do two jobs. They tell the sender that its last data packet was received, and they tell the receiver which sequence number the next data packet should carry. When the ACK flag is set in a packet, the AN field holds the sequence number of the next data packet the sender is expected to send. The sender looks at the AN field and, by implication, knows the packet it just sent should have had this sequence number:
<AN received-1 modulo 2>
If it did have that number, the packet is considered acknowledged ๐
The receiver works the same way in reverse. It expects the next data packet it sees to have an SN field value equal to the AN field of the last acknowledge message it sent. If that's not the case, the receiver assumes it's getting a duplicate of a data packet it already acknowledged. That suggests the packet carrying the acknowledgment never arrived, so the packet with the acknowledgment should be retransmitted. The duplicate data packet is discarded ๐๏ธ
Only packets that contain status flags (SYN, RST, FIN, or SO) or data need acknowledgment. A packet that's only an acknowledgment, i.e. <AN=n><CTL=ACK>, doesn't require a response, since it has no status flags or data in it ๐
Both the AN and SN fields are a single bit wide ๐ฆ. Since at most one packet is being sent or acknowledged in a particular direction at any one time, a single bit is enough for duplicate packet detection and for removing a packet from the retransmission queue โจ. The arithmetic to advance these numbers is modulo 2, so once a data packet is acknowledged, the sender's next sequence number is the current one plus one, modulo 2:
<SN = SN + 1 modulo 2>
Okay, so, the individual acknowledgment of each data packet can make you think side A can't send data to side B until it hears a packet from B. Like, A has to wait for B's packet, acknowledge it, and then tuck some of its own data into that acknowledgment. ๐ โโ๏ธ That's not how it works, bestie.
As long as its last packet that needed a response has been acknowledged, each side of a connection is free to send a data packet whenever it wants ๐ . And if one side is sending data and also needs to acknowledge a data packet from the other side, it's most efficient to combine both jobs into one packet. Iconic efficiency ๐ฅ.
2.4. Maximum Packet Size
The maximum packet size is:
SYNCH + HEADER + Data Checksum + 255 = 261 octets
So there's no need to allocate more storage than that for any received packet ๐ง.
3. The Opening and Closing of a Connection
3.1. Opening a Connection
Okay, the "three-way handshake" is the procedure used to set up a connection ๐ค. It's normally started by one end and answered by the other. It still works if both sides start it at the same time โจ. Experience has shown that opening a connection this way keeps the chance of false connections down to an acceptably low level ๐.
Here's the simplest version of the three-way handshake. Time goes top to bottom, line by line, and some lines are numbered so we can point at them ๐. User events go in brackets, like [OPEN]. An arrow (-->) shows which way a packet is flowing, and an ellipsis (...) means a packet is in transit ๐. Side A and side B are the two ends of the connection. An "XXX" means the packet got lost or rejected ๐. The contents of the packet are shown in the middle of each line. The state of both connections is whatever the departure or arrival of the packet on that line caused ๐ธ. The data portion of a packet is left out to keep things clean.
Side A Side B
1. CLOSED LISTEN
2. [OPEN request]
SYN-SENT -> <SN=0><CTL=SYN><MDL=n> ...
3. --> SYN-RECEIVED
... <SN=0><AN=1><CTL=SYN,ACK><MDL=m> <--
4. ESTABLISHED <--
--> <SN=1><AN=1><CTL=ACK><DATA> ...
5. --> ESTABLISHED
On line 2, the user on side A has asked for a connection to be opened ๐. Side A tries to open it by sending a SYN packet to side B, which is in the LISTEN state. The SYN gives its initial sequence number, which is zero here. It also puts the largest number of data octets it can handle in any one packet into the LENGTH field of the header, which is the MDL. The MDL is normally positive. Sending this packet puts A into the SYN-SENT state.
On line 3, side B has just received the SYN from A, so B moves to SYN-RECEIVED ๐. Now B sends a SYN back to A, acknowledging the SYN it just got. Notice the AN field says B is now expecting to hear SN=1, which acknowledges the SYN from A that used SN=0 ๐
. B also puts the largest number of data octets it's ready to handle in its LENGTH field.
On line 4, side A receives the SYN from B, which acknowledges A's original SYN. That puts A into ESTABLISHED ๐. Side A can now be confident that B expects to receive more packets from A.
A is now free to send B the first DATA packet. On line 5, when side B receives that packet, B moves into ESTABLISHED too ๐. DATA can't be sent until the sender is in ESTABLISHED, because the LENGTH field is used to set the MDL when the connection is opened ๐ง.
3.2. Recovering from a Simultaneous Active OPEN
Honestly, it's totally possible for both ends to choose an active OPEN at the same time ๐ญ. In that case, neither end is in LISTEN, and both send SYN packets. A reliable bidirectional protocol has to recover from this situation ๐ซถ. It should recover in a way that lets the connection be successfully opened.
Side A Side B
1. CLOSED CLOSED
2. [OPEN request]
SYN-SENT --> <SN=0><CTL=SYN><MDL=n> ...
3. ... [OPEN request]
<SN=0><CTL=SYN><MDL=m> <-- SYN-SENT
4. --> SYN-RECEIVED
... <SN=0><AN=1><CTL=SYN,ACK><MDL=m> <--
5. (packet finally arrives)
SYN-RECEIVED <-- <SN=0><CTL=SYN><MDL=m>
--> <SN=0><AN=1><CTL=SYN,ACK><MDL=n> --> ESTABLISHED
... <SN=1><AN=1><CTL=ACK> <--
6. (packet finally arrives)
ESTABLISHED <-- <SN=0><AN=1><CTL=SYN,ACK><MDL=m>
--> <SN=1><AN=1><CTL=ACK> ...
During a simultaneous connection, both sides of the connection go from CLOSED through SYN-SENT to SYN-RECEIVED, and then finally to ESTABLISHED ๐ฆโจ.
3.3. Detecting a Half-Open Connection
Any computer can crash after a connection has been set up ๐ฅ. After it recovers, it might try to open a brand-new connection. The other end has to be able to spot this and treat it as an error ๐จ.
Side A Side
1. ESTABLISHED ESTABLISHED
--> <SN=0><AN=1><CTL=ACK><DATA> ...
-->
(crashes)
2. XXX <SN=1><AN=1><CTL=ACK><DATA> <--
3. (attempts to open new connection )
--> <SN=0><CTL=SYN><MDL=m> -->
... <SN=0><AN=1><CTL=RST,ACK> <-- (abort)
CLOSED
4. <--
(connection refused)
CLOSED
3.4. Closing a Connection
Either side can choose to close an established connection ๐. You do it by sending a packet with the FIN control bit set. A FIN packet can't carry any data ๐ โโ๏ธ. The other end responds by shutting down its own end of the connection and sending back a FIN, ACK ๐ .
Side A Side B
1. ESTABLISHED ESTABLISHED
2. [CLOSE request from user]
FIN-WAIT --> <SN=0><AN=1><CTL=FIN> ...
3. --> LAST-ACK
... <SN=1><AN=1><CTL=FIN,ACK> <--
4. TIME-WAIT <--
--> <SN=1><AN=0><CTL=ACK> ...
5. --> CLOSED
6. (after 2*SRTT time passes)
CLOSED
On line 2, the user on side A of the fully open connection decides to close it down with a CLOSE call ๐ฅบ. No more data gets accepted for sending. If data is still unsent, the message "Warning: Unsent data remains." is passed to the user โ ๏ธ. No more data will be received either. A packet containing a FIN and no data is built and sent. Side A goes into FIN-WAIT.
Heads up, bestie: if there's still unsent data when you CLOSE, you'll get the "Warning: Unsent data remains." message ๐จ.
Side B sees the FIN and immediately builds a FIN, ACK in response, then goes into LAST-ACK ๐ซ . When side A gets that FIN, ACK, it immediately sends back an ACK and moves to TIME-WAIT. On line 5, side B gets the final acknowledgment of its FIN, ACK and goes to CLOSED ๐. On line 6, after waiting long enough to be sure its last acknowledgment arrived, side A goes to CLOSED too โจ. (SRTT is the Smoothed Round Trip Time, and it's defined in section 6.3.1 โญ.)
4. Packet Reception
Receiving a packet is pretty straightforward, but there are a few points worth talking about ๐ง . This chapter goes through packet reception one stage at a time, in time order ๐.
Synch Detection
The first stage in receiving a packet is finding the SYNCH pattern ๐. Octets are read nonstop and thrown away until the SYNCH pattern shows up. Once SYNCH has been seen, move on to the Header Reception stage ๐.
Header Reception
The rest of the header is three octets long ๐. Nothing else can happen until the whole header has been read. Once it's read, the header checksum test runs ๐งฎ. If that test fails, it's assumed the current SYNCH pattern came from a data error ๐ฌ. Since the correct SYNCH could show up right after the current one, go back to the Synch Detection stage, but treat the three octets of the header that followed the bad SYNCH as new input ๐.
If the header checksum test succeeds, move on to the Data Reception stage ๐.
Data Reception
Next, we figure out how much of the packet is left to read ๐. If any of the SYN, RST, SO, or FIN flags are set, then legally the whole packet has already been read, and it's considered to have 'arrived' ๐. A packet with one of those flags set has no data portion. Otherwise, the LENGTH field says how much data is still left to read. In that case, if LENGTH is zero, the packet has no data portion and is considered to have arrived ๐ฆ.
Now we assume a data portion is present and LENGTH was non-zero. The data checksum is LENGTH+2 octets, so those must be read next ๐ง. Once they're read, the data checksum test runs. If that test fails, the entire packet is thrown out and we go back to the Synch Detection stage ๐. If it passes, the packet is considered to have arrived โ .
Once a packet has arrived, it's handed up to the upper level protocol software ๐. In a multiprocess implementation, packet reception starts over at the Synch Detection stage ๐.
5. Functional Specification
Okay, here's the big picture ๐. A handy way to talk about and build protocols is as a state machine ๐งฉ. A connection can be thought of as passing through a bunch of states, with possible error conditions, from the moment it starts until it closes ๐ฌ. In this model, each state is a known point in a connection's history ๐. The connection moves from state to state in response to events ๐ซ. Those events come from user calls to the protocol interface (like a request to open or close a connection, or data to send), from incoming packets ๐ฆ, and from timeouts โฐ.
Okay so info about a connection has to be kept at both ends of it ๐ Following the lingo from [TCP 81], all the stuff a connection needs to work properly is called the Transmission Control Block, or TCB ๐. The user requests to the protocol interface are OPEN, SEND, RECEIVE, ABORT, STATUS, and CLOSE โจ.
This chapter is split into three parts ๐ง. First we get a quick rundown of each state, then a closer look at which transitions can happen between states, and last a deep dive into what each state actually does ๐ฅ.
5.1. Protocol States
The states used to describe this protocol are:
- LISTEN ๐ Waiting for a connection from the other end of the link.
- SYN-SENT ๐ค Waiting for a matching connection request, after you've already sent a connection request.
- SYN-RECEIVED ๐ฅ Waiting for a confirming connection request acknowledgment, after you've both received and sent a connection request.
- ESTABLISHED ๐ The connection is fully opened at both ends. This is the normal state for data transfer, and it's giving main character energy.
- FIN-WAIT ๐ You're waiting for a connection termination request from the other end, and also for an acknowledgment of a termination request you sent earlier.
- LAST-ACK ๐ซถ This end has seen and acknowledged a termination request from the other end. You've responded with a termination request of your own and are now waiting for an acknowledgment of that one.
- CLOSING ๐ค Waiting for an acknowledgment of a connection termination request.
- TIME-WAIT โณ Waiting for enough time to pass so you can be sure the other end received the acknowledgment of its termination request.
- CLOSED ๐ A fictional state (not a real place you can hang out, lol). It represents a completely terminated connection. If either end of a connection is in this state, it will neither send nor receive data or control packets. Iykyk ๐
5.2. State Transitions
This section covers the events that make the protocol leave its current state. Each state gets a quick mention, followed by a list of the events that make it depart and which state it moves to as a result. Departures caused by the presence of an RST flag are not shown ๐ธ
5.2.1. LISTEN
This is a request to listen for any connection from the other end of the link. In this state, no packets are sent. The connection may be thought of as half-open. A STATUS request will return this information to the caller ๐
It's arrived at from the CLOSED state in response to a passive OPEN. In a passive OPEN, no packets are sent, and the interface is just waiting for the other end of the link to start a connection. This state can also be reached in certain cases in response to an RST connection reset request ๐
Departures
- A CLOSE request is made by the user. Delete the half-open TCB and go to the CLOSED state.
- A packet arrives with the SYN flag set. Retrieve the sender's MDL that they placed into the LENGTH field. Set AN to be received SN+1 modulo 2. Build a response packet with SYN, ACK set. Choose your MDL and place it into the LENGTH octet. Choose your initial SN and place it in AN. Send this packet and go to the SYN-RECEIVED state ๐
5.2.2. SYN-SENT
It's arrived at from the CLOSED state in response to a user's active OPEN request ๐
Departures
- A CLOSE request is made by the user. Delete the TCB and go to the CLOSED state.
- A packet arrives with the SYN flag set. Retrieve the sender's MDL that they placed into the LENGTH field. Set AN to be received SN+1 modulo 2. Build a response packet with ACK set, and place AN in it. Send this packet and go to the SYN-RECEIVED state.
- A packet arrives with the SYN, ACK flags set. Retrieve the sender's MDL that they placed into the LENGTH field. Set AN to be received SN+1 modulo 2. Build a response packet with ACK set. Set SN to be SN+1 modulo 2, then place SN and AN into the header. Remembering the other end's MDL, build the data portion of the packet. Send this packet and go to the ESTABLISHED state ๐ฅณ
5.2.3. SYN-RECEIVED
It's arrived at from the LISTEN and SYN-SENT states in response to an arriving SYN packet ๐ฉ
Departures
- A CLOSE request is made by the user. Create a packet with FIN set. Send it and go to the FIN-WAIT state.
- A packet arrives with the ACK flag set. This packet acknowledges a previous SYN packet. Go to the ESTABLISHED state. The TCB should now note that the connection is fully opened โจ
- A packet arrives with the FIN flag set. The other end has decided to close the connection. Create a packet with FIN, ACK set. Send it and go to the LAST-ACK state.
5.2.4. ESTABLISHED
This is the normal state for a connection ๐ Data packets may be exchanged in both directions (as long as the MDL allows it). It's arrived at from the SYN-RECEIVED and SYN-SENT states in response to the completion of connection initiation ๐
Departures
- In response to a CLOSE request from the user. Set AN to be the most recently received SN+1 modulo 2. Build a packet with FIN set. Set SN to be SN+1 modulo 2, place SN and AN into the header, and send the packet. Go to the FIN-WAIT state.
- A packet containing a FIN is received. Set AN to be received SN+1 modulo 2. Build a response packet with both FIN and ACK set. Set SN to be SN+1 modulo 2, then place SN and AN into the header. No data portion is built. Send this packet and go to the LAST-ACK state ๐ญ
5.2.5. FIN-WAIT
It's arrived at from either the SYN-RECEIVED state or the ESTABLISHED state. In both cases, the user has requested a CLOSE of the connection and a packet with a FIN was sent ๐ซ
Departures
- A FIN, ACK packet is received that acknowledges the FIN just sent. Go to the TIME-WAIT state.
- A FIN packet is received, which means the other end of the connection has simultaneously decided to close. Set AN=received SN+1 modulo 2, and SN=SN+1 modulo 2. Send a response packet with the ACK set. Go to the CLOSING state.
5.2.6. LAST-ACK
It's arrived at from the ESTABLISHED and SYN-RECEIVED states ๐
Departures
- An ACK is received for the last packet sent, which was a FIN. Delete the TCB and go to the CLOSED state.
5.2.7. CLOSING
It's arrived at from the FIN-WAIT state ๐ซง
Departures
- An ACK is received for the last packet sent, which was a FIN. Go to the TIME-WAIT state.
5.2.8. TIME-WAIT
It's arrived at from the FIN-WAIT and CLOSING states ๐ฐ๏ธ
Departures
- This state waits until 2*SRTT time has passed. It then deletes the TCB associated with the connection and goes to the CLOSED state.
5.2.9. CLOSED
This state can be reached for a number of reasons:
- While in the LISTEN state, the user requests a CLOSE.
- While in the SYN-SENT state, the user requests a CLOSE.
- While in the TIME-WAIT state, the 2*SRTT time period has elapsed.
- While in the LAST-ACK state, an arriving packet has an ACK of the previously sent FIN packet.
In this state, no data is read or sent over the link. To leave this state, an outside request to open a new connection is required ๐
Departures
- The user requests an active OPEN. Create a packet with SYN set. Choose your MDL and place it into the LENGTH octet. Choose your initial SN. AN is immaterial. Send this packet and go to the SYN-SENT state. The TCB for this connection is created. The connection may be thought of as half-open. A STATUS request will return this information to the caller ๐ฆ
- The user requests a passive OPEN. The TCB for this connection is created. The connection may be thought of as half-open. A STATUS request will return this information to the caller. Go to the LISTEN state ๐
5.3. State Behavior
This section discusses in detail how each state behaves when a packet arrives. In what follows, a packet is not considered to have arrived until it has passed a number of tests (see the chapter entitled: Packet Reception) ๐
The method chosen to describe state behavior is tabular. Each state is listed opposite a sequence of named procedures to execute whenever a packet has arrived ๐
STATE BEHAVIOR
=============+========================
LISTEN | A
-------------+------------------------
SYN-SENT | B
-------------+------------------------
SYN-RECEIVED | C1 D1 E F1 H1
-------------+------------------------
ESTABLISHED | C2 D2 E F2 H2 I1
-------------+------------------------
FIN-WAIT | C2 D2 E F3 H3
-------------+------------------------
LAST-ACK | C2 D3 E F3 H4
-------------+------------------------
CLOSING | C2 D3 E F3 H5
-------------+------------------------
TIME-WAIT | D3 E F3 H6
-------------+------------------------
CLOSED | G
-------------+------------------------
For example, in the ESTABLISHED state, when a packet arrives, procedure C2 is executed, then D2, then E, F2, H2, and finally I1. Any procedure may end the processing that's happening or cause a state change. Note that these procedures run in sequence, first C2, then D2, and so on. The time ordering cannot be mixed ๐งฉ
The particular actions associated with each procedure are now described ๐
Procedure A
This procedure details the behavior of the LISTEN state. First, check the packet for the RST flag. If it's set, the packet is discarded and ignored, and you return and continue the processing associated with this state ๐ โโ๏ธ
We'll assume now that the RST flag was not set. Check the packet for the ACK flag. If it's set, that's an illegal condition, since no connection has been opened yet. Send an RST packet with the correct response SN value:
<SN=received AN><CTL=RST>
Return to the current state without any further processing ๐ซถ
We're still in the LISTEN state, bestie ๐ and now we're assuming neither the RST nor the ACK flag got set. ๐ซฃ
Peep for a SYN flag. If it's there, someone is trying to open a connection, so make a TCB for it ๐. The sender dropped its MDL in the LENGTH field, and their starting SN is in there too. Grab both and stash them in the TCB ๐พ. The SO flag gets ignored here, because it means nothing when SYN, RST, or FIN is set ๐.
Then send a SYN packet that acknowledges the SYN you got. Pick your own starting SN and your own MDL for this end of the connection ๐:
<SN=0><AN=received SN+1 modulo 2><CTL=SYN, ACK><LENGTH=MDL>
Then go to the SYN-RECEIVED state, no extra processing needed โจ.
Anything that fails all of those checks gets discarded and ignored ๐๏ธ. Stay in your current state, no extra processing. Bye bye ๐
Procedure B ๐ฆ
This is the SYN-SENT state, and we land here when this end decides to do an active OPEN ๐.
First, check the packet for the ACK flag. If ACK is set, check whether the AN value was what you expected. If it was, keep going below. If it wasn't, the AN value is unexpected ๐ฌ. If RST was set, discard the packet and stay in the current state with no extra processing. If RST wasn't set, send a reset:
<SN=received AN><CTL=RST>
Then discard the packet and stay in the current state with no extra processing.
At this point, either ACK was set and AN was what you expected, or ACK wasn't set at all. Second, check the RST flag. If RST is set, there are two cases ๐ :
- If ACK is set, discard the packet, flush the retransmission queue, tell the user "Error: Connection refused", delete the TCB, and go to the CLOSED state without any further processing.
- If ACK wasn't set, discard the packet and stay in this state with no extra processing.
At this point we assume the packet had an ACK that was Ok, or no ACK at all, and no RST. Now check the packet for the SYN flag. If ACK is set, then our SYN got acknowledged ๐ฅฐ. Store the MDL you received in the TCB. We're technically in the ESTABLISHED state now ๐. Send an acknowledgment packet, plus any initial data that's queued to send:
<SN=received AN><AN=received SN+1 modulo 2><CTL=ACK><DATA>
Then go to the ESTABLISHED state with no extra processing.
If SYN is set but ACK isn't, the other end also did an active open ๐. Acknowledge the SYN, pick your MDL, and send:
<SN=0><AN=received SN+1 modulo 2><CTL=SYN, ACK><LENGTH=MDL>
Then go to the SYN-RECEIVED state with no extra processing.
Anything that doesn't meet the tests above gets discarded and ignored ๐ซ . Stay in the current state with no extra processing.
Procedure C1 ๐ธ
Check the received SN field. If the SN value was expected, return and keep going with the processing for this state โ .
If it wasn't what we expected, then if RST or FIN was set, discard the packet and stay in the current state with no extra processing.
If neither RST nor FIN was set, we assume this packet is a duplicate of one we already got ๐ฏ. Send an ACK back:
<SN=received AN><AN=received SN+1 modulo 2><CTL=ACK>
Then discard the duplicate and stay in the current state with no extra processing.
Procedure C2 ๐ง
Check the received SN field. If the SN value was expected, return and keep going with the processing for this state โจ.
If it wasn't, then if RST or FIN was set, discard the packet and stay in the current state with no extra processing.
If SYN was set, we assume the other end crashed and is trying to open a brand new connection ๐. We respond by sending a legal reset:
<SN=received AN><AN=received SN+1 modulo 2><CTL=RST, ACK>
This makes the other end, which is still in SYN-SENT, close ๐. Flush the retransmission queue, tell the user "Error: Connection reset", discard the packet, delete the TCB, and go to the CLOSED state with no extra processing.
If none of RST, FIN, or SYN was set, we assume this packet is a duplicate of one we already got ๐. Send an ACK back:
<SN=received AN><AN=received SN+1 modulo 2><CTL=ACK>
Then discard the duplicate and stay in the current state with no extra processing.
Procedure D1 ๐
Check the packet for an RST flag. If RST isn't set, return and keep going with the processing for this state ๐.
RST is now assumed to be set ๐ฑ. If the connection started from the LISTEN state (it was passively opened), flush the retransmission queue, discard the packet, and go to the LISTEN state with no extra processing.
If the connection was started actively instead (it came from SYN-SENT), flush the retransmission queue, tell the user "Error: Connection refused", discard the packet, delete the TCB, and go to the CLOSED state with no extra processing.
Procedure D2 ๐
Check the packet for an RST flag. If RST isn't set, return and keep going with the processing for this state โ .
RST is now assumed to be set ๐. Flush any data that's still waiting to be sent. Flush the retransmission queue, tell the user "Error: Connection reset.", discard the packet, delete the TCB, and go to the CLOSED state with no extra processing.
Procedure D3 ๐
Check the packet for an RST flag. If RST isn't set, return and keep going with the processing for this state ๐ธ.
RST is now assumed to be set. Discard the packet, delete the TCB, and go to the CLOSED state with no extra processing ๐ซก.
Procedure E ๐ฅ
Check for the SYN flag. If SYN isn't set, return and keep going with the processing for this state โ .
Now we assume SYN was set. A SYN showing up here is an error ๐จ. Flush the retransmission queue and send a legal RST packet.
If ACK was set, send:
<SN=received AN><CTL=RST>
If ACK wasn't set, send:
<SN=0><CTL=RST>
The user should receive the message "Error: Connection reset.", then the TCB gets deleted and we go to the CLOSED state with no extra processing ๐ .
Procedure F1 ๐ฆ
Check for the ACK flag. If ACK isn't set, discard the packet and return with no extra processing ๐ .
Now we assume ACK was set. If the AN field value was what we expected, return and keep going with the processing for this state โจ.
Now we assume ACK was set but the AN field value was unexpected ๐ฌ. If the connection started from the LISTEN state (it was passively opened), flush the retransmission queue, discard the packet, and send a legal RST packet:
<SN=received AN><CTL=RST>
Then delete the TCB and go to the LISTEN state with no extra processing.
Otherwise, the connection was started actively (it came from SYN-SENT). Tell the user "Error: Connection refused", flush the retransmission queue, discard the packet, and send a legal RST packet:
<SN=received AN><CTL=RST>
Then delete the TCB and go to the CLOSED state with no extra processing.
Procedure F2 ๐ท
Check for the ACK flag. If ACK isn't set, discard the packet and return with no extra processing ๐ .
Now we assume ACK was set. If the AN field value was what we expected, flush the retransmission queue and tell the user "Ok" if a buffer has been fully acknowledged ๐. Another packet with data can go out now. Return and keep going with the processing for this state.
Now we assume ACK was set but the AN field value was unexpected. This is treated as a duplicate acknowledgment ๐. It gets ignored, and we return and keep going with the processing for this state.
Procedure F3 ๐
Check for the ACK flag. If ACK isn't set, discard the packet and return with no extra processing ๐ .
Now we assume ACK was set. If the AN field value was what we expected, keep going with the processing for this state ๐ซ.
Now we assume ACK was set but the AN field value was unexpected. This gets ignored. Return and keep going with the processing for this state ๐ซถ.
Procedure G ๐ง
This is the CLOSED state of a connection ๐. Every incoming packet gets discarded. If the packet had RST set, take no action. Otherwise we have to build an RST packet, because this end is closed and the other end has wrong info about the state of the connection, so it needs to hear about it ๐ฃ.
If ACK was set, send:
<SN=received AN><CTL=RST>
If ACK wasn't set, send:
<SN=0><AN=received SN+1 modulo 2><CTL=RST, ACK>
After sending the reset packet, return to the current state with no extra processing ๐ฉท.
Procedure H1 ๐
Our SYN got acknowledged ๐. We're technically in the ESTABLISHED state now. Send any initial data that's queued to go:
<SN=received AN><AN=received SN+1 modulo 2><CTL=ACK><DATA>
Go to the ESTABLISHED state and run procedure I1 to process any data in this packet ๐.
Anything that doesn't meet the tests above gets discarded and ignored ๐๏ธ. Stay in the current state with no extra processing.
Procedure H2 ๐
Okay bestie, we're picking up right where the FIN check lives ๐
Check the presence of the FIN flag. If FIN is not set, then just keep going with the processing for this state ๐
So now we're assuming FIN is set ๐ That means the other end has decided to close the connection ๐
Flush the retransmission queue. If any data is still waiting to go out, tell the user Warning: Data left unsent. The user MUST also be informed Connection closing. โจ An acknowledgment for the FIN MUST be sent too, and it also says this end is closing too ๐ซถ:
<SN=received AN><AN=received SN + 1 modulo 2><CTL=FIN, ACK>
Then go to the LAST-ACK state without any further processing. Bye bye, no extra steps ๐ฆ
H3
This state is the final behavior of the FIN-WAIT state ๐ธ
If the packet did not contain a FIN, it's a duplicate ๐ That means the other end hasn't seen the FIN we sent earlier. Don't panic, we just rely on retransmission of our earlier FIN packet to tell the other end we want to close. Discard the packet and return without any further processing.
Now we have a packet that should contain a FIN. By the rules of this protocol, an ACK of a FIN requires a FIN, ACK in response and no data ๐ If the packet has data in it, that's an illegal condition ๐ Send a reset:
<SN=received AN><AN=received SN+1 modulo 2><CTL=RST, ACK>
Then discard the packet, flush the retransmission queue, tell the user Error: Connection reset., delete the TCB, and go to the CLOSED state without any further processing. Rough day, but it's giving clean exit ๐ง
We now assume the FIN flag was set and no data came along with it. If the AN field value was expected, then this packet is acknowledging a FIN we already sent ๐ The other end is assumed to be closing too, and it's waiting on an acknowledgment. So send an acknowledgment of the FIN:
<SN=received AN><AN=received SN+1 modulo 2><CTL=ACK>
Start the 2*SRTT timer associated with the TIME-WAIT state, discard the packet, and go to the TIME-WAIT state without any further processing ๐
Otherwise, the AN field value was unexpected. That means both sides are closing at the same time, which is a whole main character moment ๐ Send an acknowledgment of the FIN:
<SN=received AN><AN=received SN+1 modulo 2><CTL=ACK>
Discard the packet and go to the CLOSING state without any further processing โญ
H4
This state is the final behavior of the LAST-ACK state ๐ฅน
If the AN field value is expected, then this ACK is responding to the FIN, ACK we recently sent ๐ This is the final acknowledging message, and it means both sides agree to close the connection. Discard the packet, flush all queues, delete the TCB, and go to the CLOSED state without any further processing. Iconic finish ๐
Otherwise, the AN field value was unexpected. Discard the packet and stay in the current state without any further processing ๐ซ
H5
This state is the final behavior of the CLOSING state ๐ท
If the AN field value was expected, then this packet acknowledges the FIN we recently sent ๐ This is the final acknowledging message, and it means both sides agree to close the connection. Start the 2*SRTT timer associated with the TIME-WAIT state, discard the packet, and go to the TIME-WAIT state without any further processing โจ
Otherwise, the AN field value was unexpected. Discard the packet and stay in the current state without any further processing ๐
H6
This state is the behavior of the TIME-WAIT state ๐ฐ๏ธ Check the presence of the ACK flag. If ACK is not set, discard the packet and return without any further processing.
Check the presence of the FIN flag. If FIN is not set, discard the packet and return without any further processing.
We now assume the FIN flag was set. This situation means the last acknowledgment of the FIN packet sent by the other end didn't arrive ๐ฅบ So resend the acknowledgment:
<SN=received AN><AN=received SN+1 modulo 2><CTL=ACK>
Restart the 2*SRTT timer, discard the packet, and stay in the current state without any further processing ๐งธ
I1
This is the stage of processing in the ESTABLISHED state where all the flag bits have been handled and only data might be left ๐ฆ Check whether the packet contains data. If it doesn't, discard the packet and return to the current state without any further processing.
We assume the packet contained data, meaning either the SO flag was set or LENGTH is positive ๐ That data goes into the user's receive buffers. As those fill up, the user should be told Receive buffer full. Then an acknowledgment gets sent:
<SN=received AN><AN=received SN+1 modulo 2><CTL=ACK>
If data is queued to send, it's most efficient to piggyback this acknowledgment on that data packet ๐
Discard the packet and return to the ESTABLISHED state without any further processing ๐ธ
5.4. Timers โฐ
There are three timers associated with this protocol ๐ซง Here's a quick look at what each one does and what happens when it expires. How these timeouts actually behave and get set up is up to the protocol implementation ๐ ๏ธ
5.4.1. User Timeout
Real talk, implementations need a user-controllable timeout for successfully opening a connection, successfully acknowledging data, and successfully closing a connection ๐ Picture a connection that's so noisy nothing gets through, or one that's physically cut. Without an overriding timeout, those situations would retransmit forever ๐ญ
When this timeout expires, the user is told Error: Connection aborted due to user timeout., all queues are flushed, the TCB is deleted, and the CLOSED state is entered ๐ช
5.4.2. Retransmission Timeout
This timer makes sure any packet with a significant SN gets acknowledged ๐ฌ When one is sent, it goes into a retransmission queue and the retransmission timer starts. If no acknowledgment shows up before the timer runs out, the packet is retransmitted and the timer restarts. If the acknowledgment arrives in time, the timer stops and the packet leaves the retransmission queue. Then the next packet with a significant SN can go out ๐
This timeout is meant to work together with a counter that tracks how many times a packet has been retransmitted ๐ข Normally, there's an upper limit on retransmissions. If that limit gets exceeded, the connection is aborted, kind of like the user timeout. The user is told Error: Connection aborted due to retransmission failure, all queues are flushed, the TCB is deleted, and the CLOSED state is entered ๐
5.4.3. TIME-WAIT Timeout
This timeout catches any FIN packets that might be retransmitted from the other end when an acknowledgment packet got dropped ๐ต๏ธโโ๏ธ The timeout period SHOULD be at least as long as 2*SRTT. Once it expires, the other end is assumed to be closed, the TCB is deleted, and this end enters the CLOSED state too ๐
6. Data Error Handling ๐จ
This chapter goes deep on the data errors an established connection can run into ๐ These are different from the protocol errors we covered above. In the order we'll discuss them, they are:
- Framing Errors
- Missing SYNCH pattern
- Unacknowledged packets
- Bad packets
- Duplicate packets
- Outside flow control
- Packets that are too large
- Packets that are too small
6.1. Framing Errors
The RS-232 specification only gives framing for an individual octet ๐งต Link level protocols for computer networking usually frame each packet. The SYNCH pattern marks the beginning of a packet ๐ No similar pattern was picked to mark the end and fully frame the packet ๐คทโโ๏ธ
Any bit pattern can show up in the data portion of a packet ๐ณ For any particular pattern to reliably mark the end of a packet, that terminating pattern can't be allowed to appear in the data. This is usually handled by the sender changing any occurrence of the terminating pattern in the data so it's no longer recognizable as that pattern, and it can be restored on receipt. Both the sender and the receiver are REQUIRED by this technique to examine all the data ๐ Without a protocol chip to handle this, it's a source of some overhead ๐ข
6.1.1. Synthetic Framing
Without framing, the end of the packet MUST be determined synthetically ๐งฉ The start of a packet is indicated by the SYNCH pattern. The expected end can only be figured out by reading the LENGTH octet in the header. It's important to know whether LENGTH can be trusted ๐ฅธ This is handled by using a one-octet header checksum that covers the first two octets after the SYNCH pattern. If the header passes the checksum and none of the SYN, FIN, RST, or SO flag bits are set, then LENGTH is trusted and the number of octets expected beyond the header is LENGTH+2. (For packets where any of those flag bits are set, the packet length is fixed and only includes a header portion ๐)
If the header fails the checksum, we're in some trouble ๐ฌ The length is wrong, so it could be too small or too large. To recover, do the following. Starting right after the SYNCH pattern, rescan for the next SYNCH pattern. Throw away all octets until a SYNCH is seen, then try to reinterpret it as a packet. The sender's retransmission timeout guarantees a new copy of the packet will be sent, so after the initial SYNCH pattern is discarded, the SYNCH pattern at the beginning of the retransmitted packet will eventually be seen ๐โจ
6.1.2. Costs of Synthetic Framing
This framing strategy causes no overhead unless data errors happen in the packet ๐ That's presumed to be pretty unlikely ๐ฒ It also removes the overhead of both sender and receiver having to scan through the data to handle any termination pattern that might show up in it ๐
The worst case is kinda brutal ๐: a packet header fails its checksum, a fresh SYNCH pattern shows up in the next few octets, that header fails its checksum too, and so on, until the SYNCH pattern of the retransmitted packet is finally spotted. Consistently bad behavior like that means an extremely noisy communications link ๐ก
6.2. Missing SYNCH Pattern ๐
Any valid packet must begin with the SYNCH pattern. Any receiver must discard all input octets until the SYNCH pattern is seen. The data which immediately follows a SYNCH pattern is interpreted as a packet. The header checksum test is applied, then LENGTH+2 octets are read, the data checksum test is applied, etc. ๐ง
6.3. Unacknowledged Packets โฐ
If an ACK for a packet is not obtained within the retransmission timeout interval, that packet is retransmitted. Because significant variability in response can be expected from either end of a connection, it is best to dynamically calculate the retransmission timeout interval. An example of such a calculation is provided below. The protocol will operate successfully, although not with as high an effective transmission rate, if a realistic upper bound time is used instead. ๐
A realistic upper bound time depends upon the packet size and line speed. If the baud rate of the connection is 300 or above, let B be the baud rate (for clarity, assume it is the same in both directions), let L be the MDL of the receiver, and let P be the packet processing time of the receiver. Then an Upper Bound for the Reception Time (UBRT) is:
UBRT = L/(B/10) seconds + P seconds
and a realistic upper bound time is 2*UBRT seconds. โจ
6.3.1. Calculation of Retransmission Timeout Interval ๐งฎ
For the purpose of detecting retransmission time out, the protocol must have access to a clock which provides at least single second resolution. One technique for calculating the round trip time is:
Measure the elapsed time between sending a packet with a particular SN and receiving an ACK with an AN which covers that SN. The measured elapsed time is the Round Trip Time (RTT). Next, a Smoothed Round Trip Time (SRTT) is calculated as:
SRTT = (ALPHA * SRTT) + ((1- ALPHA) * RTT)
and based upon this, you compute the Retransmission Time Out (RTO) as:
RTO = min[UBOUND, max[LBOUND, (BETA * SRTT)]]
where UBOUND is an upper bound on the timeout (e.g., 1 minute), LBOUND is a lower bound on the timeout (e.g., 1 second), ALPHA is a smoothing factor (e.g., .8 to .9), and BETA is a delay variance factor (e.g., 1.3 to 2.0). ๐
6.4. Bad Packets ๐ โโ๏ธ
A bad packet is received when it fails either the header or data checksum tests. When this happens, the sender will retransmit the packet after the retransmission timeout interval. ๐
6.5. Duplicate Packets ๐ฏโโ๏ธ
A duplicate packet is a packet which passes the checksum tests but for which the SN received is significant but not the expected value. This is normally caused when the sender did not get the ACK last sent by the receiver. This situation is diagrammed below. ๐
Side A Side B
ESTABLISHED ESTABLISHED
1. --> <SN=1><AN=0><CTL=ACK><DATA> ...
-->
2. XXX <SN=0><AN=0><CTL=ACK><OTHER-DATA> <--
3. (after SRTT)
--> <SN=1><AN=0><CTL=ACK><DATA> ...
4. -->
... <SN=0><AN=0><CTL=ACK><OTHER-DATA> <--
5. <--
In line 2, B's packet was lost in transit. It may have failed its checksum tests when it reached A, or its initial SYNCH pattern was smashed, etc.. In line 3, side A comes to the decision that its packet from line 1 was not received after SRTT time passes, and retransmits that packet. ๐ฌ
In line 4, side B receives the packet. It detects a duplicate because it already sent a packet acknowledging A's SN=1 (although that packet was lost). B now discards the duplicate and immediately retransmits its last packet to A. Side A finally receives the retransmitted packet in line 5. ๐ฅน
6.6. Outside Flow Control ๐ฆ
There are many large computer systems which make use of flow control to regulate their input side of an RS-232 link. Flow control based upon two special characters, such as <Ctrl-S> (ASCII DC3) and <Ctrl-Q> (ASCII DC1), is almost universally in use today. So it becomes important for the protocol to be able to either:
- Recognize and obey the flow control of the host computer(s), or
- Ignore the flow control but still guarantee reliable data reception.
It is the latter approach which this protocol takes. This decision was made because the number of differing flow control characters in use would make it difficult to obey them all. ๐
There is a particular type of flow control with which this protocol will not operate. The ENQUIRE, ACKNOWLEDGE method of flow control requires that the receiver of an inquiry respond with an acknowledge before any more data will be sent to it. This type of flow control also usually prohibits unrestricted 8-bit data transmission, because the inquiry character is forbidden as a data byte. ๐ซ
For the other class of flow control methods, a proof is required that data may still be reliably transmitted and received if flow control is ignored. For the purposes of this discussion, assume <Ctrl-S> is sent when the receiving end of the connection wishes the sender to stop transmitting. A <Ctrl-Q> is sent when the receiver wishes the sender to resume. The choice of these particular two characters is arbitrary. If the sender does not immediately cease transmission upon receipt of the <Ctrl-S>, characters may be discarded. Since this protocol chooses to ignore the flow control characters, any part of a packet may be discarded. ๐ซ
More precisely stated, consider X to be the receiver and Y to be the sender. The packet sent is represented by the string abc, where a, b, and c are data segments of unspecified size. X may receive one of:
1. abc
2. ab
3. ac
4. bc
For case [1], the correct data is received and no special action need be taken. โ
For cases [2], [3], and [4], we have a situation identical to data dropped during transmission. This is handled by the same checksum, time-out and retransmission strategy already described. ๐
Assume Y is not now in the act of receiving a packet. Then Y sees the two characters <Ctrl-S> and <Ctrl-Q> appear as input in that order. Y is waiting for a message to appear and so expects to see a SYNCH pattern. If the two characters "<Ctrl-S><Ctrl-Q>" are not part of a SYNCH pattern, then they will be immediately discarded. If Y is receiving a packet, then the <Ctrl-S> and <Ctrl-Q> are seen to be added noise characters and would be detected by the checksum tests. The packet being received would require retransmission. ๐ฆ
The question of which character to pick for the SYNCH pattern is slightly muddied by the above observation. To the author's knowledge, <SOH> is rarely, if ever, picked for flow control. This is part of the motivation in using it as the SYNCH pattern. ๐
How does one guarantee that any data will actually arrive successfully? The initial choice of maximum data counts during connection establishment is very important. Some knowledge of one's own operating system must be assumed. If it is known, for example, that streams of data in excess of a certain length will often trigger flow control at the connection baud rate, then the maximum data count should be chosen sufficiently lower that flow control rarely will be employed. An intelligent choice of the maximum data count will guarantee that some packets will arrive without encountering flow control. ๐ง ๐
6.7. Packets that are too Large ๐จ
Assume a packet arrives which passes its header checksum test but whose LENGTH is larger than the MDL of the receiver. In such a case, the sender has violated the protocol, or a packet has a data error in the LENGTH octet and has passed the header checksum test. The latter is unlikely, so we assume the former. The receiver will abort his connection. The sender must inform the user Error: Connection aborted due to MDL error, and go to the CLOSED state. ๐
When the MDL is exceeded, the receiver will transmit a legal reset:
<SN=received AN><CTL=RST>
6.8. Packets that are too Small ๐ชถ
Assume that a packet has passed its header checksum test, but some of the data octets have been dropped by the link. In such a case, the receiver's routine which reads data and builds packets is expecting octets which do not arrive. After SRTT, the sender will retransmit this packet to the receiver. The receiver will now have enough data to complete the packet. Almost certainly, however, it will fail the data checksum test. As with any bad packet, the receiver will rescan from the octet immediately following the SYNCH pattern for the next SYNCH pattern. In this manner, the receiver will eventually see the SYNCH pattern of the retransmitted packet. ๐ฆ
I. Inability to Transmit/Receive 8-bit Data ๐
okay so, some older operating systems and devices literally can't do 8-bit communication over an RS-232 link ๐ญ. most of them can only do restricted 7-bit communication. if that's unavoidable, both ends of the connection must have a protocol layer underneath this protocol ๐ซถ. that lower layer unpacks the packets it sends over the RS-232 link and repacks the packets it receives over it. RATP will automatically figure out whether full 8-bit or restricted 7-bit communication is being used (more on that below) โจ.
the strategy for restricted 7-bit communication is called 4/8 packing ๐. each octet gets chopped into two 4-bit nibbles. the high order four bits go out first, followed by the low order bits. on the receiving side, each octet gets repacked by the inverse function: the high order nibble arrives first, then the low order nibble, and those two get glued back together into one octet ๐งฉ.
I.1. Encoding for Transmission ๐ธ
some systems can't do 8-bit over RS-232 at all, and on top of that some operating systems put special restrictions on the non-printable ASCII characters ๐. so the encoding for 4/8 packing should restrict itself to transmitting data only in the printable 7-bit ASCII range ๐ .
I.2. Framing an Octet ๐ฆ
the seventh and highest order bit of a transmitted 7-bit ASCII byte is a flag. it tells you whether the high or low order nibble of an octet is inside this character ๐. if the flag is set, it means a new octet is coming in, and this printable ASCII character holds the high order nibble in its four low order bits. it also means the next ASCII character received should not have its highest order bit set.
this high order flag bit gets set by adding the ASCII character "@" (octal 100) to a data byte. so the first nibble of an octet always goes out with "@" added to its value. the high order nibble becomes the characters "@" through letter "O" ๐ฅ.
the lower order nibble goes out with zero "0" added to its value, so it becomes the characters "0" through "?". when receiving 4/8 packed data, any characters not within the range "0" through letter "O" get discarded ๐๏ธ.
here's an octet whose octal value is 45, sent as two 7-bit printable ASCII characters:
+-------------+
High order |1|0|0|0|1|0|0| First transmitted ("@" + data) = D
+-------------+
Low order |0|1|1|0|1|0|1| Second transmitted ("0" + data) = 5
+-------------+
okay now this part is super important ๐ค. since data bytes can get dropped or added at any time, you always need to know which portion of an octet is expected, and you should only deliver complete octets to the higher protocol level. if a single 7-bit character got dropped without anyone noticing, the data stream handed up to the higher level could be shifted by an odd multiple of four bits. in the worst case that could stick around forever, and the higher level would never receive an octet correctly. then no packets would ever be received correctly, and the connection would be totally unusable ๐.
to avoid this, octets get assembled using a state machine that's driven by the presence of the high order flag bit. that bit in a 7-bit printable character marks the beginning of a new octet โจ. the two-state machine that assembles octets is described below. a byte received with the high order flag bit set is called "HIGH", and the byte without it is called "LOW".
State 0
[Start state] Read a byte from the legal restricted set. This is determined by seeing if the byte is in the legal range "@" to the letter "O". If it was not, discard the byte and return to this state.
A HIGH byte was read. Place the four low order bits of the byte into the four high order bits of the assembled octet and go to state 1. Otherwise, discard the byte and return to this state.
State 1
Read a byte from the legal restricted set. This is determined by seeing if the byte is in the legal range zero "0" to the letter "O". If it was not, discard the byte and return to this state.
If a LOW byte was read, subtract zero "0" from the byte, placing the four low order bits of the result into the four low order bits of the assembled octet. A full octet has now been assembled. Pass the octet to the higher level and go to state 0.
Otherwise, a HIGH byte was read. Place the four low order bits of the byte into the four high order bits of the assembled octet and return to this state.
using this state machine to receive 4/8 packed data means the data stream delivered to the higher level won't stay shifted by an odd multiple of four bits forever ๐. the restriction on which bytes can be read throws out obviously bad data, and in some cases it can handle uncontrolled padding or blocking insertion.
I.3. Automatic Detection of 8-bit or 4/8 Packed Data ๐ง
it's an unavoidable problem that some machines can't handle unrestricted 8-bit data. since that's a given, it's desirable to automatically detect whether unrestricted 8-bit or restricted 4/8 packing is being used to send data on a connection. for this discussion, machines that can transmit and receive both unrestricted 8-bit and 4/8 packed data are called smart. machines are called dumb if they can only transmit and receive 4/8 packed data ๐ค.
when initiating a connection there are four possible machine configurations:
- A (smart) opens a connection to B (smart).
- A (dumb) opens a connection to B (smart).
- A (dumb) opens a connection to B (dumb).
- A (smart) opens a connection to B (dumb).
each case gets examined, and extensions to the behavior for the LISTEN and SYN-SENT states are provided, which allow both types of machines to initiate or receive a connection ๐ฆ.
Cases 1 and 2: LISTEN Behavior for a Smart Machine
in these cases machine A initiates a connection to B, who is assumed to be in the LISTEN state. B has to be able to passively detect whether 8-bit or 4/8 packing is being used and respond accordingly. the method B uses relies on detecting a valid first packet. in the LISTEN state, B tries to treat the incoming data as if it were both unrestricted 8-bit and 4/8 packed at the same time ๐.
the incoming data is in effect fed to two different receiving algorithms. the detection of a valid header will happen in one of these algorithms before the other. if the first valid header was read assuming unrestricted 8-bit data, then any resulting connection is assumed to use unrestricted 8-bit data for the life of the connection. if the first valid header assumed 4/8 packing, then the resulting connection is assumed to use 4/8 packing for the life of the connection. if an illegal condition is detected in the LISTEN state, the protocol will reply with a RST packet in kind ๐ซ.
Case 3: LISTEN Behavior for a Dumb Machine
in this case machine B is the recipient of a connection request and can only handle 4/8 packed data. the LISTEN behavior for machine B assumes that all connections are 4/8 packed, and it never deals with unrestricted 8-bit data. as a result, it will refuse to open a connection request from a smart machine (see case 4 below) ๐
โโ๏ธ.
Case 4: SYN-SENT Behavior for a Smart Machine
in this case machine A tries to open a connection to machine B. however, A has no knowledge of B's capabilities ๐คทโโ๏ธ. A will send its connection request assuming B is smart and using unrestricted 8-bit transmission. it will wait for a reply, assuming the response will also be unrestricted 8-bit. if B is in fact dumb, it won't return a SYN-ACK, because of the restriction imposed by case 3 above. if no connection is made with B using 8-bit data, the entire connection initiation is restarted, assuming B is dumb: 4/8 packing is used, and the response is assumed to be 4/8 packed as well ๐.
the cost of this approach is a longer time to determine whether it's possible to open a connection to B. it's twice as long. but the advantages of being able to automatically adjust to either unrestricted 8-bit or 4/8 packed data outweigh this disadvantage ๐. RATP will not exhibit the schizophrenic behavior of many other asynchronous protocols when dealing with both classes of machines ๐โจ
II. A Brief Survey of Some Asynchronous Link Protocols ๐ธ
II.1. DDCMP ๐
okay so DDCMP, Copyright (c) 1978 Digital Equipment Corporation [DDCMP 78], is a reliable point-to-point AND multi-point transmission protocol, and it's used by a lot of that manufacturer's computers. it really does give you reliable asynchronous two-way data transmission, so it's not a mess ๐
some of its choices come from being built for multi-point data links ๐ which means the headers end up way longer than you'd need for plain two-way point-to-point stuff. bestie, that's a lot of extra baggage ๐
DDCMP lets up to 255 outstanding unacknowledged messages hang out at once โจ it also specifically says that one end of a connection can choose to limit its send queue to just one outstanding unacknowledged message. it's also allowed to send a stream of outstanding unacknowledged packets. here's the tea: unless every RS-232 implementation of DDCMP was capped at one outstanding packet, the clash with existing flow control rules could tank the thruput into the ground ๐ (quick side note: DDCMP is assumed to be in control of the link driver, so dealing with all the different flow control mechanisms isn't on the table.)
DDCMP uses a CRC polynomial for data protection, and it's annoyingly hard to calculate on lots of machines without special hardware [TCP Checksum 78]. the good news is that plenty of Digital Equipment computers already have that hardware ๐ซถ
DDCMP does not give the receiver a way to restrict incoming packet size ๐ yes, higher level protocols built on top of DDCMP could negotiate packet size on their own. but then that job gets pushed away from the link level, which is where it properly belongs. not vibing.
honestly? a full implementation of DDCMP is too complex to even consider ๐ญ and if you tried to implement only part of it, you'd run into compatibility problems with the implementations that already exist on other computers.
II.2. MODEM Protocol ๐ฆ
this one is super popular amongst microcomputers, so it's kind of iconic ๐ the description here comes from:
MODEM/XMODEM Protocol Explained by Kelly Smith, CP/M-Net "SYSOP" January 8,1980
so the basic idea is that data gets sent in 128-byte sequentially numbered blocks, and every block gets one single checksum byte tacked on at the end ๐ the receiving computer does its own checksum as the data comes in. once a block is finished, it compares its result with the sending computer's checksum. if they match, the receiver sends back an ACK (ASCII protocol character for ACKNOWLEDGE, 06 Hex, Control-F). the ACK basically means "all's well on this end, send some more..." ๐
the sender starts with an "initial NAK" (ASCII protocol character for NEGATIVE ACKNOWLEDGE, 15 Hex, Control-U). the NAK means "that wasn't quite right, please send again." because the first hookup between the two computers is asynchronous, the receiver will time out while waiting for data and send that NAK as the cue for the sender to start transmitting. the sender knows the receiver is going to time out, and it uses that to "get in sync"... literally clever ๐ง
when the sender gets that initial NAK, it replies with a SOH (ASCII protocol character for START OF HEADING, 01 Hex, Control-A). then it sends the first block number, then the 1's complement of the block number, then 128 bytes of 8 bit data, and finally a checksum. the checksum is calculated by summing the SOH, the block number, the block number 1's complement, and the 128 bytes of data ๐งฎ
here's how the exchange looks:
---/NAK/------------------------/ACK/------------------
15H 06H
---/SOH/BLK#/BLK#/DATA/CSUM/---/SOH/BLK#/BLK#/DATA/etc.
01H 01H FEH 8bit 8bit 01H 02H FDH 8bit ....
this keeps going with the next 128 bytes. if the receiver ACKed the block, the sender moves on to the next sequential block number and its 1's complement, and so on ๐ซ
okay but here's the catch ๐ as you can see from this partial description, the MODEM protocol is unidirectional. data can only go from the sender to the receiver, as one stream. if you wanted data flowing the other way at the same time, you'd need a second connection over another RS-232 line. not ideal, bestie.
on top of that, this protocol is locked to a fixed 128 octet packet size. a lot of front-end concentrators just can't handle packets that big ๐ฉ it's been observed many times that the concentrator of a busy DECsystem-20 can invoke flow control on input at 1200 baud for packets as small as 64 characters. yikes.
II.3. KERMIT System ๐ง
the KERMIT system, Copyright (c) 1981 Columbia University, is a file transfer environment that was developed fairly recently โจ it has implementations running on DECsystem-20, IBM 370 VM/CMS, 8080 CP/M based systems, the IBM PC, and more.
KERMIT smooshes reliable transfer and file transfer together into one package ๐ extending it to other applications and higher level protocols would be possible, but the line between the reliable transfer part and the application layer is super blurry. it violates the layering design strategy the Internet uses, and that's a big no from me ๐ โโ๏ธ
for certain computers there's a limitation to the restricted printable ASCII set, but other computers don't have that limit. this leads to confusion ๐ KERMIT allows both restricted ASCII and 8-bit transmission, so at least it's flexible.
the KERMIT protocol does have a way to set the MDL when a connection starts ๐ the catch is that the max packet size is smaller: 96 octets instead of 261 octets.
KERMIT originally used a checksumming algorithm limited to six bits. this is considered too weak at error detection for data packets ๐ฅฒ KERMIT now allows two other checksumming algorithms on top of the original one. and there must be a negotiation between sender and receiver about which algorithm to use.
now the simultaneous open situation ๐ the KERMIT protocol doesn't seem to make any provision for both sides of a connection attempting an active open at the same time. one side has to be the initial "sending Kermit" and the other has to be the "receiving Kermit". the code published as a KERMIT implementation guide can't recover from simultaneous active opens, and it immediately ABORTs. this shows a bias towards unidirectional data flow, which is a whole vibe ๐ค
the KERMIT packet type (similar to RATP control flags) specifies whether an ACK/NAK is in the packet, or data, and so on. these are mutually exclusive, so piggybacking an ACK on a data packet isn't possible, and that can be a source of overhead ๐ฆ also, KERMIT restricts the sender to a single outstanding unacknowledged packet, just like RATP does. and it gives an entire byte to the sequence number, which is unnecessary ๐ค
now for error recovery ๐จ the size of a packet is stored in the second byte of the packet, and it isn't protected by a header checksum. if the length field gets messed up by noise on the link, it could end up longer than the correct packet size. the code published as the KERMIT implementation guide relies on detecting the <SOH> character anywhere in a packet to mark the beginning of a packet header, and it re-SYNCHs using this technique. that only works if binary data in a packet is quoted. if full eight bit data is transmitted, it doesn't look like the KERMIT protocol rescans for a new MARK (SYNCH) character within the bad packet data it just consumed. in those circumstances it will throw away the retransmitted packet, or portions of it. re-SYNCHing under those conditions is problematic ๐
References ๐
[Cohen 81]
Cohen, D. On Holy Wars and a Plea for Peace. IEEE Computer, October, 1981.
[DDCMP 78]
DDCMP AA-D599A-TC edition, Digital Equipment Corporation, 1978. Version 4.0.
[IP 81]
Postel, J. DOD Standard Internet Protocol [RFC-791] Defense Advanced Research Projects Agency, 1981.
[TCP 81]
Postel, J. Transmission Control Protocol [RFC-793] Defense Advanced Research Projects Agency, 1981.
[TCP Checksum 78]
Plummer, W. W. TCP Checksum Function Design. Technical Report, Bolt Beranek and Newman, Inc., 1978.
Editors Notes ๐
This memo was prepared in essentially this form in June 1983, and set aside. Distribution at this time is prompted by the the "Thinwire" proposal described in RFC-914.
--jon postel ๐ซถ
๐ฆ where this page comes from
- ietf: rfc916 1984-10
- ๐ girlified by claude-haiku-5-5 on : a language model's rewrite of the original, under its terms