RFC 689

Tenex NCP finite state machine for connections

πŸ“… published May 1975, from ietf

TENEX NCP Finite State Machine for connections ✨

Okay bestie, let's get the title block sorted first πŸŽ€

  • RFC 689, NIC #32656, May 1975 πŸ¦‹
  • Title: TENEX NCP Finite State Machine for connections
  • TENEX Memo Number 155
  • Updates: the memo of 27 August 1971, TENEX memo number 113 🌸
  • Obsoletes: nothing listed in this header
  • No authors or category show up in this excerpt's header.

The Big Picture πŸ’–

So the attached figure is the finite state machine that TENEX version 1.33 uses to run the ARPANET Host to Host protocol. This memo updates the 27 August 1971 memo (TENEX memo 113) specifically for the finite state machine part. Other bits of that memo already got folded into the JSYS manual and other docs, so you don't have to hunt for them 🧚

An FSM is made of three things: States, Events, and Actions. Here's the full cheat sheet:

States Events Actions

01 CLZD 00 RRFC 00 ANOP
02 PNDO 01 CLSR 01 AFNY
03 LSNG 02 CLSS 02 ACLS
04 RFCR 03 CLZR 03 ARFC
05 CLW1 04 CLZS 04 AOPB
06 RFCS 05 ACPT 05 AOPL
07 OPND 06 CONN 06 ACLL
10 CLSW 07 LISN 07 ACLO
11 DATW 10 RRFN 10 AEOR
12 RFN1 11 TIME 11 AEOS
13 CLZW 12 RRFB 12 AES1
16 FREE 13 AABT
14 ACKA

Quick heads-up on the closing drama πŸ’…: there are two kinds of close. One is at the JSYS level (CLOSF) and the other is at the host-to-host protocol level (CLS). Anything with "CLS" in the name is about host-to-host CLS, and anything with "CLZ" is about CLOSF. Keep those straight and the rest gets way easier πŸ™ˆ

Each state gets a quick description below, along with the events that can happen while a connection sits in it and the actions that fire as the state advances.


Ground Rules for the FSM 🌈

A few overall notes, bestie:

  • Actions are written on the state diagram after a "/". Any transition with no action shown gets "ANOP", which is a null action (basically nothing 😌).
  • "AFNY" means a "funny" event happened, aka one that wasn't expected in this state (probably a bug). The result is an IMPBUG error printout.
  • Any event that is not shown on the diagram makes the state loop back to itself and generate an AFNY. These aren't drawn out on the diagram, so keep that in mind.
  • Another "funny" event is when a user program runs an "accept" MTOPR while the state isn't RFCR. An errant program can do that on its own, though, and it shouldn't cause an IMPBUG. So most states loop to self with no action (an "ANOP") when ACPT happens. Iconic behaviour, tbh πŸ‘‘
  • The event "TIME" (also called "HUNG") just means the socket hasn't changed state for a set amount of time, so it might need a little nudge. Right now that time is two minutes. Connections in error states get stepped faster so they clear out quicker ⚑

State 16 - FREE - Free connection πŸ“

A connection in this state either never existed or is almost fully deleted. The only events we really expect here are a program CLOSF (events CLZR or CLZS), or an erroneous Accept. The Accept sets an error status bit (AABT, which means abort). A connection leaves FREE when a socket gets created, either from a user OPENF or from an incoming RFC. That moves the connection into the CLZD state.

State 01 - CLZD - Closed 🫧

This state is super transitory. Whatever event creates the socket also immediately moves the state along to one of three places: PNDG (if the event was an incoming RFC), LSNG (if it was a Listen JSYS, i.e., an OPENF with a null extension), or RFCS (if it was an OPENF with a foreign socket specified). In that last case an RFC gets sent to the foreign socket (ARFC).

State 02 - PNDG - Pending πŸŽ€

Here an RFC has come in, but no local program has shown any interest in it yet. Things that can happen here:

  • LISN: a program decides to listen on the socket. Since an RFC already arrived, the FSM ends up where it would have if LISN and RRFC had happened in the other order, which is RFCR.
  • CONN: a program decides to connect to the foreign socket. This moves the FSM to OPND and triggers AOPB (open the link and send the matching RFC).
  • TIME: the RFC, which was unsolicited, has sat around for two minutes with no local program bothering to act on it. To clear out the tables it gets refused (ACLS, meaning a close is sent) and the next state is CLSW, waiting for the matching CLS.
  • CLSR or CLSS: the site that sent the RFC changed its mind and sent a CLS. So we send the matching CLS back and return the socket to FREE πŸ¦‹

State 10 - CLSW - Wait for CLS 🌸

We land here when nothing else needs to happen on a connection except receiving a CLS from the far end. The CLS has already been sent to the far end. In some cases the file system CLOSF hasn't happened yet, so CLZR and CLZS just loop back into CLSW. The expected events are CLSR or CLSS, which is the CLS arriving from the far end, and those step the connection to FREE. And if two minutes pass without the CLS, the assumption is that the foreign site lost its CLS response, so TIME does the same action.

State 03 - LSNG - Listening for RFC πŸ‘‚

No network activity has happened in this state. The local program is just waiting for someone to try to connect. This state doesn't time out (TIME loops right back into LSNG). The program might stop listening (for example, it could just log out of TENEX), so CLZR and CLZS move the connection to FREE. The event we actually want is an RFC arriving for this socket. When an RFC shows up for a socket in any state other than CLZD, its byte size gets checked against what the user program declared. If it matches, the event is RRFC. If not, it's RRFB. RRFB sends a LSNG socket to CLSW and a CLS goes out, refusing the connection. In the normal case RRFC steps the connection to RFCR πŸ’–

State 06 - RFCS - An RFC has been sent. πŸš€

This state gets entered when a program does a Connect (an OPENF with a specific foreign socket). The RFC has been sent and we're waiting for the matching RFC back. If the foreign site refuses the connection by sending a CLS, then CLSR and CLSS cause the matching CLS to go out (ACLS) and the socket steps to FREE. If the user program gives up waiting for the connection to be accepted (events CLZS or CLZR), a timeout happens (TIME), or an RFC arrives with the wrong byte size (RRFB), the socket steps to CLSW and a CLS is sent (ACLS). When the connection is properly accepted (event RRFC), the link tables get opened (AOPL) and the connection state goes to OPND ✨

State 04 - RFCR - An RFC has been received. πŸ’Œ

This state is reached when the user has done a Listen and an RFC with a matching byte size has come in. This state and PNDG together make up the queueing mechanism for received RFCs. Queued RFCs sitting in PNDG belong to sockets that aren't listening (though another connection with the same socket number might be listening, just not the first one), so they time out after two minutes. In RFCR, though, a program has done a listen, so timeouts are suppressed to give the accept time to happen. The event TIME loops to RFCR.

If the foreign site gets tired of waiting for the accept, it can send a CLS (events CLSR or CLSS). In that case the matching CLS goes out (ACLS) and the socket moves to FREE.

Also, while in this state, the program can look at the foreign host and socket and decide to refuse the connection by doing a CLOSF (events CLZR and CLZS). That sends a CLS (action ACLS) and the socket steps to CLSW to wait for the matching CLS.

If the program likes the connection request, it accepts it with an MTOPR JSYS (event ACPT). That triggers AOPB (sending the matching RFC and opening the link tables), and the socket steps to OPND πŸ‘‘

State 07 - OPND - opened. 🌷

This is the state where data actually gets transferred. Both RFCs have been sent. Allocation and RFNM activity aren't covered in this state diagram. Until one end or the other tries to close the connection, that activity happens at a different level of the code. The exception is TIME. After two minutes of inactivity on the socket, action ACKA (Check allocation) happens. That action makes allocate resynchronizing occur if the foreign host is known to understand that extension to the host-host protocol.

Everything else in this state is about getting a connection properly closed and freed, and honestly it gets a bit complicated 😭 There are four starting events: closes done locally and closes from the remote end, and each of these could be for a sending or a receiving connection. They are CLZR, CLZS, CLSR and CLSS.

  • CLZR: the local program closes a connection that has been receiving. A CLS gets sent (ACLS) and the state moves to CLW1, which is like CLSW except the link tables have to be closed when a CLS comes back.
  • CLZS: the local program closes a connection that has been sending. The state steps to DATW and action AEOS runs. AEOS (action end of send) sets a done flag that lower-level routines use to tell the FSM when the last data has been sent and acknowledged by RFNM.
  • CLSR: a CLS arrives on a receive connection. This is the normal "end of file" case. Action AEOR (end of receive) happens and the state moves to CLZW. AEOR sets the same flags as AEOS, and in addition (if this connection is hooked to an NVT) it performs an NVT DETACH.
  • CLSS: a CLS arrives on a send connection before we (the sending end) closed it. Action AESl happens, which is the same as AEOS except messages queued to go out get discarded. The state moves to RFN2 to wait for the final RFNM.

State 05 - CLW1 - Close wait sub 1. 🧦

This is basically CLSW, except one link table entry still needs clearing out. A CLS has been sent and we're waiting for the matching CLS. When it arrives, or when two minutes go by, the state steps to FREE and action ACLL (close link) runs.

State 11 - DATW - Final data wait. ⏳

This state is NOT the normal waiting-for-data state on an open connection. It only happens while the last of the data is being sent on a send connection that's been locally closed but hasn't had all its data accepted by the far end yet.

  • RRFN: the final RFNM arrives. This is the event we want. Now all the data has been sent, so we send a CLS (ACLS) and go to CLW1 to wait for the matching CLS.
  • TIME can also happen. If it does, we pretend we saw the final RFNM and act as we would for RRFN. This timeout can happen either because an RFNM got lost by the IMP or the subnet, or because of unresponsiveness on the foreign host's part. The second one could happen if the amount of data still to send when the CLOSF is done is bigger than the available allocation at that time. If the foreign host doesn't send an allocation, or disagrees with us and thinks allocation is still outstanding, the timeout frees the socket.
  • CLSS may happen. If it does, the far end hasn't accepted all the data but wants to abort the connection. This gets treated about the same as it was when it happened from OPND, namely the AES1 action, but it has to go to a different next state, RFN1, to show that a local CLOSF has already happened.

State 14 - RFN2 - Final RFNM wait sub 2. 🫢

This state means an unexpected close showed up while we were happily sending data. We now wait for the final RFNM for any outstanding data messages. When that arrives, or if there were no outstanding messages, the state moves to CLZW. ACLO runs, sending a CLS (matching the unexpected CLS that came in during OPND) and closing the link.

If two minutes go by and no final RFNM arrives, we also go to CLZW so we don't get stuck waiting on an unresponsive foreign host or an IMP/subnet failure, and ACLO runs too. This is analogous to the timeout from DATW above.

While waiting for the RFNM, the local program might try to CLOSF the connection (CLZR, CLZS). If it does, we go to RFN1 and keep waiting for the final RFNM πŸ¦‹

State 12 - RFN1 - Final RFNM wait sub 1. 🌈

In this state it's all over but the final RFNM. When it arrives (RRFN), or when two minutes go by,

When this happens, or if there were no outstanding messages to begin with πŸ’…, the state moves to CLZW. Then we do ACLO, sending a CLS (matching the unexpected CLS that showed up during OPND) and closing the link πŸ”—.

If two minutes go by and no final RFNM arrives, we also just go to CLZW to avoid getting stuck waiting on an unresponsive foreign host or an IMP/subnet failure πŸ™ˆ, and we perform ACLO too. This is basically the same move as the timeout from state DATW above, so it should feel familiar bestie πŸ¦‹.

While we're waiting for the RFNM, the local program might try to CLOSF the connection (CLZR, CLZS). If it does, we head to state RFN1 and keep waiting for that final RFNM πŸŽ€.

State 12 - RFN1 - Final RFNM wait sub 1 ✨

This state is giving "it's all over but the final RFNM" 😭. When it arrives (RRFN) or two minutes go by (TIME), we close the link tables and send a CLS to the foreign site (ACLO). Since the CLS has already been received, the connection is now gone, so we step it to FREE 🌈.


State 13 - CLZW - Wait for program to close file 🌸

This is the normal state after an end of file when receiving, before the CLOSF (CLZR) has happened. It's also the state of a send connection that got aborted (CLS) by the receiver (foreign host) once all the sent messages have been RFNM'ed, but the local CLOSF (CLZS) hasn't happened yet πŸ’….

So we just wait for the local program to do the CLOSF 🧚. When it does, we close the link table if this is a receive connection, and either way we step the connection to the FREE state πŸ’–.

πŸ“¦ where this page comes from