RFC 60

A Simplified NCP Protocol

πŸ“… published July 1970, from ietf

Abstract ✨

Okay bestie, so this is RFC 60, "A Simplified NCP Protocol" πŸ’– by R. Kalin from MIT, dated 13 July 1970. It's part of the Network Working Group series, and the header doesn't list anything it obsoletes or updates. Let's get into it πŸŽ€

This RFC defines a brand-new NCP protocol that is literally simple enough to run on a very small computer, but it can still be extended for efficient operation on big timesharing machines πŸ–₯️ Since worst-case storage requirements can be predicted, a conservative implementation gets to skip all the complicated resource allocation and storage control procedures. It also defines a general error recovery procedure 🫢

Overview and Rational πŸ¦‹

The central premise here is a firm insistence that all user-to-user connections be bi-directional. If you know communication theory, this is giving very reasonable energy. All communication needs a cyclical flow of information πŸ”„ If you refuse to have a simple link between a message and its reply, the protocol gets way more complicated than it needs to be, and simple flow control mechanisms turn into total nightmares 😭

The proposal is that a bi-directional connection, or duplex link, gets identified by a pair of socket numbers, one for each end. That's half the number currently required, which is iconic πŸ‘‘ Attached to the connection are some number of "crates" or message containers πŸ“¦ These crates travel back and forth over the link carrying network messages from one side to the other. Buffers get allocated at each end of the link to hold the crates and the messages they carry. The worst-case buffer requirement equals the number of crates in circulation, which is the "capacity" of the link πŸ’…

Details 🌸

A message buffer has four states that follow one another cyclically, and it's obsessed with the loop πŸ” They are:

  1. empty,
  2. filled with a message-laden crate to be unloaded,
  3. filled with an empty crate, and
  4. filled with a message-laden crate to be sent.

Normally, state transitions line up with message arrival, message removal, message insertion and message transmission 🧚

For a process to be an NCP it MUST:

  1. be able to make initial contact with foreign hosts via the control link and, if necessary, delete user-to-user links left over from the previous system incarnation.
  2. be able to create user-to-user links.
  3. be able to interface users with these links.
  4. be able to delete user-to-user links.

The first of the four functions SHALL NOT be discussed here, except to point out that it has critical races that can't be resolved without making assumptions about maximum message propagation delays 😬 Since within the ARPA network there are no bounds on message turnaround time, the approach chosen MUST necessarily be tender πŸ₯Ή The other three functions get discussed first from the viewpoint of someone implementing a minimal NCP. After that, extensions and improvements suitable for larger machines are proposed πŸ“

Any NCP MUST be capable of creating a duplex link between a local user process and a remote one πŸ’œ The current protocol does this by queuing a potentially unbounded number of RFC's and waiting for the user to check the queue and figure out who they want to talk to. But there's no guarantee the user will ever look at the queue, and there's no way to limit its size. The suggested overflow error message also fails here, because it admits the RFC will only be sent again πŸ™ˆ The picture doesn't have to be this bleak! The following network conversation shows how connections can be made without using queues or relying on the user process paying attention ✨

Suppose a local user process and a remote user process want to set up a new connection. The remote process asks its NCP to listen for a connection request, and gives it the socket identifier for its end. Optionally, it can give both socket identifiers. The user process at the local end asks its NCP to send a request for a duplex link (RFDL), and it specifies both socket identifiers of the proposed link. The local NCP sends an RFDL over the control link with this format:

RFDL <my socket> <your socket> <max number buffers> <spare>

The third argument is normally supplied by the local NCP and says the maximum number of buffers the NCP will consider allocating to this duplex link. If buffers are in user storage, the count MAY be given by the user in a call made to the NCP πŸŽ€

The RFDL arrives at the remote host, and the remote NCP compares <my socket> and <your socket> against the socket identifiers supplied by unmatched listens issued to it. For listens where just a single identifier was given, only <your socket> has to match. If both socket identifiers were given, they BOTH MUST match. If a match is found, an acknowledgement message with this format gets sent back by the NCP:

ACDL <your socket> <my socket> <number buffers> <spare>

The <number buffers> parameter equals the smaller of the <max number buffers> from the RFDL and the number of message buffers the remote NCP agrees to. If no match is found, the error message returned is an ACDL where <number buffers> equals zero πŸ™…β€β™€οΈ Note that the RFDL mechanism is kind of similar to an RFC mechanism where the bound on queue size is one, and connection acceptance is done entirely by the NCP πŸ’…

The two kinds of listen match two modes of channel operation. The single-parameter kind, like a LOGIN process, is for programs that will "talk with anyone who happens to dial their number" πŸ“ž Screening contacts for appropriateness is left to the user process. The double-parameter listen is for user programs that know who they'll communicate with and don't want to be bothered by random RFDL's from other sources. Given how the socket name space is partitioned, it's impossible to get a matching RFDL from any process except the intended one πŸ”’

Message buffers for the connection get allocated in the remote host before it sends the ACDL, and in the local host at the moment the ACDL is received. The number of buffers at each end equals the <number buffers> parameter in the ACDL. The state of all remote buffers is "empty," and the state of all local buffers is "filled with empty crate." Once buffers are allocated, the local user process is notified that it can start sending messages πŸš€

The type of interface the NCP presents between the user process and the newly created duplex link is a decision local to that host. A simple but complete interface would provide two calls to the NCP. GETMESSAGE would return the next message from the link, complete with marking, text and padding. PUTMESSAGE would take a message, with marking and text only, and buffer it for transmission. The obvious logical errors would be reported 🧁

We suggest that message alignment be left to the user. On most machines it's a simple but time-consuming operation. If the NCP does it, there's no guarantee the user won't have to readjust it themselves anyway 😀 It's usually not possible to know a priori whether the text portion should be right-adjusted to a word boundary, left-adjusted to a word boundary, aligned to the end of the last message, or fragmented in some exotic way πŸŒ€

Within this protocol, message boundaries are used to provide storage allocation information. If the user doesn't REQUIRE this information, it can be forgotten, and the user interface can be made to appear as a bit stream. Purists will love that, but such a strategy MAY produce complications when you try to synchronize both ends of a link πŸ€“

Links are deleted by removing empty crates from them and reclaiming the buffers allocated to the crates that were removed. Only buffers with crates in them can be reclaimed; empty buffers MUST remain available to receive messages that MAY arrive. When no crates are left, no buffers remain, and the socket identifiers can be forgotten. When empty crates are removed, a decrement size message is sent to the foreign NCP so it can reduce its buffer allocation:

DEC <my socket> <your socket> <number of buffers dropped>

A reply is solicited from the foreign NCP, either to affirm the deletions or to complain about an error. Possible errors include "no such link" and "impossible number of buffers dropped" πŸ™ƒ

The option to close a link can be given to a user process by providing either of two system calls. NOMOREOUTPUT declares that no more messages will be sent by the local user process. All local buffers for the link that contain empty crates get reclaimed by the NCP, and DEC messages are sent to the foreign NCP. As crates are emptied via GETMESSAGE calls, their buffers get reclaimed too 🧹 As an alternative, the call KILLMESSAGE can be implemented. This call can be used in place of a PUTMESSAGE. Instead of filling an empty crate with a message to send, KILLMESSAGE causes the crate to be reclaimed and a DEC control message to be sent πŸ’€

In situations where the user process has died, or for some other reason can't close the link, more drastic measures MUST be taken 😱 For these situations, the ABEND control message is defined:

ABEND <my socket> <your socket>

After sending an ABEND, the issuing NCP starts to close the link. All buffers containing input are destroyed. A DEC is issued for these and for the previously empty buffers. If messages arrive on the link, they are destroyed and a DEC is issued. Any ABEND received for the link is ignored πŸ›‘

When the remote NCP receives the ABEND, it stops sending messages over the link and refuses new messages from the user process at its end. Empty buffers are reclaimed. Pending output messages are destroyed and their buffers reclaimed. Input messages are fed to the user process for as long as it will accept them, and buffers are reclaimed as input is accepted. DEC's are issued to cover all reclaimed buffers. When the user process won't take any more input, input messages are destroyed and their buffers reclaimed. Eventually all buffers get reclaimed at both ends of the link. At that point the connection can be considered closed, and the socket numbers used can be reassigned without ambiguity 🌈

Under this proposed protocol, the four functions above are all that must be part of a network NCP πŸ§šβ€β™€οΈ If buffers are allocated only when free ones exist, there can be no "overflow" errors, and there's no need to place further constraints on message flow. Buffer room is guaranteed for any user message that arrives. All control messages can be processed without requiring additional storage to be allocated. Attempts by a user process to issue too many listens can be thwarted by local control procedures πŸ›‘οΈ

Storage gets inefficient when the number of outstanding connections gets large. One price of coding simplicity is a fifty percent utilization of buffer space πŸ₯² On large hosts, it MAY prove advantageous to implement some of the following NCP extensions. With more complicated flow control procedures, it becomes possible for an NCP to allocate more buffer space than actually exists and still not get into trouble. Other extensions provide message compression, improved throughput and user-transparent error recovery ⭐

Okay so bestie, the INQ thing is next πŸ’… Some extensions need the foreign host to cooperate, and they assume it's implemented more than the bare-minimum NCP. So we send an inquiry control message (INQ) to find out what extensions the foreign host actually has πŸ” The reply is a control message that defines a host profile πŸŽ€ If the foreign host sends back an "undefined error" message instead, we assume it only has a minimal NCP. No cap, that's the main character move πŸ¦‹

Next up is a simple extension that replaces user RFNM's 🌸 A user RFNM is a null text message, like the reply you'd send when a file gets transferred over a duplex link. They're lowkey inefficient because each one ties up an entry in the IMP's link assignment table and drags down network throughput πŸ’€ The better move is to send a special message over the control link. That way one short message can stand in for several user messages ✨

URFNM <my socket> <your socket> <number of user RFNM's>

Now the tea πŸ«– The control link runs at the same time as the return side of the user link, so URFNM's can't be swapped in for user RFNM's when there are other messages going out on the return link. If you did, ordering would get lost, and so would user transparency 😭 Keep the order intact, babe.

Throughput can also go up with a way to add more crates on a duplex link πŸš€ That could be the user asking for it, or the NCP deciding on its own. The request looks like this:

INC <my socket> <your socket> <number buffers desired>

The foreign host answers an increase request by sending back an INCR:

INCR <my socket> <your socket> <number buffers to be added>

Here's the catch πŸ‘€ If the foreign NCP can't cover the extra buffer demand, then <number buffers to be added> will come back smaller than <number buffers desired>, and it might even be zero πŸ’” As for starting states, every local buffer that gets added begins as "filled with empty crate", and every foreign buffer begins as "empty" 🧚

The spare argument in the RFDL and ACDL could be used to declare the biggest message that will actually be sent in that direction πŸ“ A perceptive NCP could watch that info and allocate smaller buffers. A lesser NCP could ignore it and always assume maximum length messages. For example, if the field is zero, then only user RFNM's will be sent. A smart NCP would allocate no storage at all 🧠✨

If the NCP keeps a copy of each user message it sends over the network until a reply comes back, it can set up an automatic error recovery procedure πŸ›Ÿ And since the capacity of the link is always known, an NCP can figure out whether any messages are still in transit. To do that, it first sends a STOP message to the foreign NCP:

STOP <my socket> <your socket>

The STOP message tells the foreign NCP to temporarily stop sending messages over the selected link. Unlike CEASE-ON-LINK, there's no guarantee about how many messages will still get sent before the STOP takes effect πŸ™ˆ Then the local NCP sends a link inquiry message:

LINQ <my socket> <your socket>

The reply tells you how many crates are at the foreign end of the link πŸ“¦ Keep sending LINQ until that number plus the number of local crates equals the capacity of the link. At that point no messages are in transit, and both ends of the link are synchronized 🫢 Now messages can be identified relative to that sync point. So the local NCP can send a control message asking, for example, that the third to last message be retransmitted, and the foreign NCP can figure out exactly which message that is and resend it 🎯 Once all errors are recovered, a START control message gets sent to the foreign NCP. It's similar in format to the STOP, and then normal operation continues 🌈 The whole recovery procedure can be totally transparent to both user processes. Iconic. πŸ‘‘

Real talk about storage: the larger hosts won't strictly follow the worst case storage allocation requirements πŸ‹οΈ Instead, they'll allocate more buffers than they actually have and rely on statistics to keep them out of trouble most of the time. That behaviour is perfectly fine, as long as it stays transparent to foreign hosts. The protocol lets an NCP lie about its storage allocation, as long as it doesn't get caught 🀫 If detection looks imminent, some of the following control mechanisms will need to be applied. They're listed in increasing order of power, so the last one is the nuclear option πŸ’₯

a) Do not send out any user RFNM's or other short messages. There's a good chance they'd be replaced by longer messages, which would strain buffer capacity even more 😬

b) Try not to accept any new messages from the IMP. Block local processes that are trying to issue messages 🚫

c) Issue DEC's to free up buffer space. Do not allocate more than one buffer to RFDL's, and refuse INC's 🧹

d) Fake errors in messages that are waiting for local user action. Only do this if the host that sent the message has implemented error recovery. This frees buffer space and lets you recover later. It's admittedly a last resort, but it should be powerful enough to control any emergency 🚨

The author really hopes this protocol is an attractive alternative to the one proposed by RFC 54 and its additions 🫠 It shows up kinda late, but it shouldn't be more than a minor jolt to implementation efforts. It's simple enough to implement quickly. If it gets adopted, most of the sites that exist right now could be talking intelligently with one another by the end of the summer β˜€οΈπŸ‰ Literally so iconic.

References

[1] Crocker, S.D., Postel, J., Newkirk, J. and Kraley, M., "Official protocol proffering", RFC 54, June 1970.

Author's Address

Richard Kalin MIT Lincoln Laboratory

         [ This RFC was put into machine readable form for entry ]
           [ into the online RFC archives by Ian Redfern 4/97 ]
πŸ“¦ where this page comes from