RFC 984
PCMAIL: A distributed mail system for personal computers
1. Status of this Document โจ
RFC 984: PCMAIL: A Distributed Mail System for Personal Computers ๐
Authors: David D. Clark and Mark L. Lambert ๐ Affiliation: M. I. T. Laboratory for Computer Science ๐ฆ Date: May 1986 ๐ธ Obsoletes / Updates: The original header doesn't list any RFCs that this one obsoletes or updates. ๐ Network Working Group ๐
okay so bestie, first things first: this doc is a preliminary discussion of how to design a mail system built on personal computers ๐ปโจ. It's published so people can discuss it and comment, and it is not a standard ๐ โโ๏ธ. The proposal may change, so implementing it is not advised ๐ฌ. Distribution of this memo is unlimited ๐.
2. Introduction ๐ง
Okay, so PCMAIL is a distributed mail system that gives mail service to an arbitrary number of users ๐ . Each user owns one or more personal computers (PCs) ๐ป. The system is split into two halves, and it's giving very clean architecture ๐ญ๐ฅ.
The first half is a single entity called the "repository" ๐๏ธ. The repository is the storage center for incoming mail ๐ฅ. Mail for a PCMAIL user can come from outside, from the Internet, or from other repository users ๐. The repository also keeps a stable copy of each user's mail state, which the doc calls the user's "global mail state" ๐. Because of that, the repository is typically a computer with a huge amount of disk storage ๐พ๐.
The second half is one or more "clients" ๐ฑ. Each PCMAIL user can have any number of clients, and they're usually PCs ๐ฅ๏ธ. Clients give the user a friendly way to reach their global mail state over a network ๐ซถ. To make things faster between the repository and a user's clients, each client keeps a local copy of that user's global mail state, called the "local mail state" ๐. But here's the catch: clients are PCs, so they won't always have network access, which means the local and global states won't always match ๐. So synchronizing them is necessary ๐โจ.
Clients talk to the repository using the Distributed Mail System Protocol (DMSP) ๐ฆ. The spec for DMSP is in appendix A ๐. So the repository is a DMSP server, and it's also a mail end-site and storage facility ๐. DMSP gives you a full set of mail operations, like "send a message", "delete a message", "print a message", and so on ๐. It also has special operations for syncing a user's global mail state with their clients' local mail states ๐. The designers paid really close attention to how DMSP operations act on a user's mail state ๐ง๐ . Every DMSP operation is atomic, meaning it either succeeds completely or fails completely ๐ฏ. So a client can get abruptly disconnected from the repository without leaving the mail state inconsistent or damaged ๐ก๏ธ๐ญ.
PCMAIL is built specifically for PCs, so the design is heavily shaped by three PC traits ๐. First, PCs are relatively inexpensive ๐ธ. That means people might own more than one, maybe one at the office and one at home ๐ข๐ . Second, PCs are portable ๐. Most can be packed up and moved in the back seat of a car, and a few are truly portable, about the size of a briefcase and battery-powered ๐งณ๐. Third, PCs are resource-poor ๐ฅบ. A typical PC has a small amount of main memory (typically less than one megabyte), and very little mass storage, like floppy-disk drives that can access maybe 360 kilobytes of data ๐พ๐.
Since PCs are cheap and people may own several, PCMAIL lets users have multiple access points to their mail state ๐โจ. Each user can have several client PCs, and each one can reach the user's mail by talking to the repository over a network ๐. All the client PCs keep local copies of the user's global mail state, and they sync the local and global states using DMSP ๐๐.
Here's the thing: many PCs will only be connected to a network every once in a while, so they can't always reach the repository ๐ด๐ฌ. So PCMAIL allows two communication modes between repository and client ๐ค. Interactive mode is for clients that are always connected to the network ๐ข. Any change to the client's local mail state is immediately made to the repository's global mail state, and any incoming mail is immediately sent from the repository to the client โก. Batch mode is for clients that only access the repository now and then ๐๏ธ. Users change the client's local mail state, and those changes get queued up as "actions" ๐. When the client next connects to the repository, the actions get sent over, and the local mail state is synced with the global one ๐๐.
Last but not least, PCMAIL is designed so that using a resource-poor PC as a client doesn't hurt too much ๐ฅน๐ซถ. Mail messages are split into two parts: a "descriptor" and a "body" ๐ฆ. The descriptor is a capsule summary of the message, about 100 bytes long, and its length doesn't depend on how long the actual message is ๐ . The body is the actual message text, including an RFC-822 standard message header ๐. Even if the client doesn't have room for every message, it can always hold every descriptor, so the user at least gets a summary of their whole mail state ๐โจ. Message bodies can then be pulled down from the repository whenever the client has storage space free ๐ฅ๐ธ.
The rest of the document is organized like this ๐บ๏ธ. First, there's a detailed description of the repository architecture ๐๏ธ. Next comes DMSP: its operations and why it was designed the way it was ๐ฆ. The third section covers client architecture ๐ฑ. After that, there's a walkthrough of a typical DMSP session between the repository and a client ๐ฌ. The final section talks about the current PCMAIL implementation ๐ ๏ธ๐.
Up next: the repository architecture ๐๏ธ๐ more to come, bestie!
3. Repository Architecture โจ
okay so the repository is basically the big boss machine ๐ฅ๏ธ๐ . it's usually a medium-to-large computer with a huge pile of disk storage. it also has to be a permanent network site, bestie, because client PCs talk to it over the network and they rely on it being available at any time ๐ซถ
the repository has a few jobs. the most important one is that it has to efficiently manage a potentially large number of users and their mail states ๐. mail has to be stored reliably, in a way that makes it easy for multiple clients to reach the global mail state and sync their local mail states with it ๐. second, the repository has to talk to its clients efficiently ๐ก. the protocol between repository and client must be reliable and must offer operations that (1) allow typical mail manipulation, and (2) support Pcmail's distributed nature by allowing efficient synchronization between local and global mail states. it's giving teamwork ๐ค. third, the repository must be able to process mail from sources outside its own user community (a primary outside source is the Internet ๐). Internet mail arrives with a NIC RFC-822 standard message header, and the recipient names in the message must be properly translated from the RFC-822 namespace into the repository's namespace. this is lowkey the trickiest bit ๐
3.1. Management of user mail state
Pcmail splits the world into a community of users ๐ธ. each user is referred to by a user object. a user object has a unique name, a password (which the user's clients use to authenticate themselves to the repository before they manipulate a global mail state ๐), a list of "client objects" describing the clients that belong to the user, and a list of "mailbox objects".
a client object has a unique name and a status. a user has one client object for every client they own, and a client cannot communicate with the repository unless it has a matching client object in a user's client list. so client objects are basically the repository's guest list ๐ and they're how it identifies valid clients. client objects also help the repository manage local and global mail state synchronization. for every global state change, the repository keeps a list of client objects for those clients that haven't recorded the global change locally ๐ง
a client's status is either "active" or "inactive". the repository defines inactive clients as clients that haven't connected to it within a set time period (one week in the current Pcmail implementation) ๐๏ธ. when an inactive client does connect, the repository tells it that it has been "reset" ๐. the repository resets a client by marking all messages in the user's mail state as having changed since that client last logged in. then, when the client next syncs with the repository, it gets a complete copy of the repository's global mail state. this forced reset is based on the assumption that enough global state changes happen in a week that the client would spend way too much time doing an ordinary local state-global state synchronization ๐ญ
messages live in mailboxes ๐ฌ. users can have an arbitrary number of mailboxes, and they serve both to store and to categorize messages. since there can be any number of mailboxes, messages can be categorized to an arbitrarily fine degree ๐ . a mailbox object both names a mailbox and describes its contents. mailboxes are identified by a unique name, and their contents are described by three numeric values. the first is the total number of messages in the mailbox. the second is the total number of unseen messages in the mailbox (messages that have never been seen by the user via any client). the third is the next available message unique identifier (UID). this info goes in the mailbox object so clients can get a summary of a mailbox's contents without having to read every message inside it ๐ฆ
associated with each mailbox is an arbitrary number of message objects. each message is split into two parts: a "descriptor", which holds a summary of useful info about the message, and a "body", which is the message text itself, including the NIC RFC-822 message header ๐. each message gets a monotonically increasing UID based on the owning mailbox's next available UID. every mailbox has its own set of UIDs which, together with the mailbox name and user name, uniquely identify the message within the repository ๐
a descriptor holds the following info: the message UID, the message size in bytes and lines, four "useful" message header fields (the date:, to:, from:, and subject: fields), and two groups of eight flags each. the first group of flags is system defined. these flags mark whether the message has never been seen, whether it has been deleted, whether it is a forwarded message, and whether the message has been expunged. the remaining four flags are reserved for future use ๐ฎ. the second group of flags is user defined. the repository never looks at these flags internally. instead, application programs running on the clients can use them ๐จ. descriptors are an efficient way for clients to get message info without wasting time pulling the whole message from the repository โจ
3.2. Repository-to-RFC-822 name translation
"Address objects" give the repository a way to translate the RFC-822-style mail addresses in Internet messages into repository names ๐. the repository has its own namespace for message identification. any message is uniquely identified by the triple (user-name, mailbox-name, message-UID). any mailbox is uniquely identified by the pair (user-name, mailbox-name). so, to send a message between two repository users, a user addresses the message to (user-name, mailbox-name). the repository delivers the message to the named user and mailbox, and gives it a UID based on the requested mailbox's next available UID ๐
to translate between RFC-822-style mail addresses and repository names, the repository keeps a list of address objects. each address object is an association between an RFC-822-style address and a (user-name, mailbox-name) pair. when mail arrives from the Internet, the repository can use the address object list to translate the recipients into (user-name, mailbox-name) pairs and route the message correctly ๐
4. Communication Between Repository and Client: DMSP ๐ฌ
the Distributed Mail System Protocol (DMSP) is a block-stream protocol that defines and manipulates the objects from the previous section ๐งฉ. it was designed for Pcmail's single-repository/multiple-client model of the world. on top of typical mail manipulation functions, DMSP also has functions that make it easy to sync global and local mail states ๐ซถ
DMSP is built on top of the Unified Stream Protocol (USP), which is specified in MIT-LCS Technical Memo 255. USP gives you a reliable virtual circuit block-stream connection between two machines ๐. it defines a basic set of data types ("strings", "integers", "booleans", etc.). instances of these data types are grouped in an application-defined order to form USP blocks. each USP block is defined by a numeric "block type", so a USP application can interpret a block's contents based on knowing its type ๐ง . DMSP is a set of operations, and each operation is made up of one or more different USP blocks that get sent between repository and client ๐
here's how a DMSP session goes: a client starts the session by opening a USP connection to the repository's machine. then the client authenticates both itself and its user to the repository with a "login" operation ๐. if authentication succeeds, the user does an arbitrary number of DMSP operations and then ends the session with a "logout" operation (at which point the repository closes the connection) ๐
because DMSP can manipulate a pair of mail states (local and global) at once, it is extremely important that all DMSP operations are atomic ๐ญ. a failure in any DMSP operation must leave both states in a consistent, known state. for this reason, a DMSP operation counts as failed unless the operation initiator receives an explicit acknowledgement. this acknowledgement comes in one of two basic forms, based on the two broad categories all DMSP operations fall into ๐๏ธ. first, an operation can be a request to modify mail state. in that case the repository acknowledges with either an "ok" or a "failure" (and if it's a failure, the reason is returned too). second, an operation can be a request for information. then the repository acknowledges by handing the client the information ๐ฆ. things like "delete a message" are in the first category, and things like "send a list of mailboxes" are in the second ๐
here's a list of DMSP operations by object type, their block types and arguments, and their expected acknowledgement block types ๐. each DMSP block has a different number. the first digit of each block type tells you which object is being manipulated: operations numbered 5xx are general, operations numbered 6xx are user operations, operations numbered 7xx are client operations, operations numbered 8xx are mailbox and address operations, and operations numbered 11xx are message operations ๐
blocks marked "=>" flow from client to repository, and blocks marked "<=" flow from repository to client. if more than one block can be sent, the choices are separated by "or" ("|") characters ๐ฆ
for clarity, each block type is written in a human-understandable form. the block number is followed by an operation name, and this name is never sent as part of a USP block ๐. block arguments are identified by name and type, and enclosed in square brackets. "Record" data types are described by a list of "field-name:field-type" pairs in square brackets. "Choice" data types are described by a list of "tag:tag-name" pairs in square brackets. USP data types get abbreviated like this:
Primitive data types:
- string: str
- cardinal: card
- long-cardinal: Lcard
- integer: int
- long-integer: Lint
- boolean: bool
Compound data types:
- sequence: SEQ
- array: AR
- record: REC
- choice: CH
4.1. General operations
the first group of DMSP operations does general stuff that doesn't work on any one particular class of object ๐. DMSP has six general operations, which provide the following services:
if either a client or the repository thinks the other is malfunctioning, it can send an "abort-request" ๐จ. an abort-request is never acknowledged. after the request is sent, the sender immediately closes the USP connection and hands control back to its application ๐ค
=> 503 (abort-request) [why:str]
DMSP has a lowkey limited remote debugging situation ๐ through the "start-debug" and "end-debug" ops. When a client sends start-debug, the repo switches on its own version of remote debugging โจ. What remote debugging actually is depends on the implementation, and the current repo just writes debug info to a special file ๐. end-debug turns it back off ๐ .
=> 504 (start-debug) []
<= 500 (ok) [] |
501 (failure) [why:str]
or
=> 505 (end-debug) []
<= 500 (ok) []
Next up is version skew, which is a real vibe killer ๐ญ. To stop clients and the repo from running different DMSP versions, there's a "send-version" op. The client sends its DMSP version number, and it only succeeds if that matches the repo's DMSP version number. No match, no success ๐ โโ๏ธ.
=> 506 (send-version) [version-number:card]
<= 500 (ok) [] |
501 (failure) [why:str]
DMSP also lets clients send an arbitrary text message to the repo ๐ฌ. The "log-message" op takes a string of any length, the repo accepts it, and what happens to it after that is up to the implementation ๐คทโโ๏ธ.
=> 507 log-message[message:str]
<= 500 (ok) [] |
501 (failure) [why:str]
And finally, users can send mail to each other with the "send-message" op ๐ฌ. The message has to come with an Internet-style header as defined by NIC RFC-822. The repo takes the message and delivers it to the mailboxes listed in the "to:", "cc:", and "bcc:" fields of that header. If any of those mailboxes live outside the repo's user community, the repo is responsible for handing the message to a local SMTP server ๐ซถ.
An OK block only gets sent back if the entire message was successfully transmitted ๐. For messages going out to the Internet, send-message counts as a success once the message has made it to the local SMTP server.
=> 508 (send-message) [message:SEQ[str]]
<= 500 (ok) [] |
501 (failure) [why:str]
4.2. User operations
Next up are the user object ops ๐. The big ones are "login" and "logout". A client has to do a login before it can touch a user's mail state ๐. A DMSP login block has five items: (1) the user's name, (2) the user's password, (3) the name of the client doing the login, (4) a flag telling the repo to create a client object for that client if one doesn't already exist, and (5) a flag that's TRUE if the client wants "batch mode" or FALSE if it wants "interactive" mode. That flag lets the repo tune its internal settings for whichever mode is in play ๐๏ธ.
The repo can answer with an OK block (auth worked โ ), a FAILURE block (auth didn't work โ), or a FORCE-RESET block. FORCE-RESET shows up when the repo has marked the client as "inactive", which happens to clients that haven't connected in over a week ๐ณ. It tells the client to wipe its local mail state and pull down a full copy of the repo's mail state. The reasoning is that so many changes would have piled up in a week that a normal sync would be super inefficient ๐ฅฒ.
=> 600 (login) [user:str, password:str, client:str,
create-client-object?:bool,
batch-mode-flag:bool]
<= 500 (ok) [] |
501 (failure) [why:str] |
705 (force-client-reset) []
When a client is done talking to the repo, it sends a logout ๐. That gives the repo a chance to do any cleanup it needs before it closes the USP connection.
=> 601 (logout) []
<= 500 (ok) []
DMSP also has "add-user" and "remove-user" ops, which let system admins add new users to the repo and remove them remotely ๐งโ๐ป. These ops are privileged, so the repo checks who's asking and authenticates them before doing either one ๐. Both need the name of the user being added or removed, and add-user also needs a default password to give the new user.
=> 602 (add-user) [user:str, password:str]
<= 500 (ok) [] |
501 (failure) [why:str]
=> 603 (remove-user) [user:str]
<= 500 (ok) [] |
501 (failure) [why:str]
Users can change their own password with the "set-password" op ๐. It works pretty much like the UNIX change-password command, and it takes the user's current password and the new one they want. If the current password you send matches the user's actual current password, it gets swapped for the new one ๐ .
=> 604 (set-password) [old-password:str,
new-password:str]
<= 500 (ok) [] |
501 (failure) [why:str]
4.3. Client operations
DMSP has four ops for messing with client objects ๐ง. The first one, "list-clients", tells the repo to send the user's client list back to the client that asked. The list is a series of (name, status) pairs ๐.
=> 700 (list-clients) []
<= 701 (client-list) [client-list:SEQ[
REC[name:str, status:card]]]
"add-client" lets a user add a client object to their client list โ. Login can also do this through the "create-this-client?" flag, but add-client is handy when you want to make a bunch of new client objects while already logged in through an existing client ๐. It needs the name of the client to add.
=> 702 (add-client) [client:str]
<= 500 (ok) [] |
501 (failure) [why:str]
The most common way this one fails is trying to add a client that already exists ๐.
"remove-client" takes an existing client object off a user's client list ๐๏ธ. The client being removed can even be the one making the request ๐. It needs the name of the client to remove.
=> 703 (remove-client) [client:str]
<= 500 (ok) [] |
501 (failure) [why:str]
The most common failure here is trying to remove a client that doesn't exist ๐. That's the usual failure for any DMSP op that works on a named object.
The last client op is "reset-client" ๐. It makes the repo mark every message in the user's mail state as changed since the client last logged in. The next time the client syncs, it'll get a full copy of the repo's global mail state. This is useful for two reasons. First, a client's local mail state can easily get lost or damaged, especially if it lives on a floppy disk ๐พ. Second, if the repo has marked a client as inactive, reset-client is a quick way to resync when there are so many differences between local and global mail state that a normal sync would take forever ๐ข.
=> 704 (reset-client) [client:str]
<= 500 (ok) [] |
501 (failure) [why:str]
4.4. Mailbox operations
DMSP supports five ops that work on mailbox objects ๐ซ. First, "list-mailboxes" has the repo send each mailbox's info to the client that asked. That info is the mailbox name, the total message count, the unseen message count, and the "next available UID" ๐ข. It's super useful for syncing local and global mail state, since a client can compare the user's global mailbox list against its own local one ๐ฏ. It also gives you a quick summary of each mailbox without actually sending the contents โจ.
=> 800 (list-mailboxes) []
<= 801 (mailbox-list) [mailbox-list:SEQ[
REC[mailbox:str,
next-UID:Lcard,
num-msgs:card,
num-unseen-msgs:card]]]
"add-mailbox" has the repo create a new mailbox and attach it to the user's mailbox list ๐ฅ. An address object gets made automatically to bind the (user-name, mailbox-name) pair to an RFC-822-style address, and it goes into the repo's address object list. That's what lets mail from the Internet find its way to the new mailbox ๐.
=> 802 (add-mailbox) [mailbox:str]
<= 500 (ok) [] |
501 (failure) [why:str]
"Remove-mailbox" takes a mailbox off the user's list ๐งน. Every message inside it gets deleted and permanently removed from the system ๐ฑ. Any address objects tying that mailbox name to RFC-822-style addresses get removed too.
=> 803 (remove-mailbox) [mailbox:str]
<= 500 (ok) [] |
501 (failure) [why:str]
DMSP also has an "expunge-mailbox" op ๐๏ธ. Messages can be marked "deleted" and "undeleted" whenever you want, but the deletions only become permanent once you run expunge-mailbox. That makes the repo go through a named mailbox and remove any messages marked "deleted" from the system ๐ฅ.
=> 808 expunge-mailbox[mailbox:str]
<= 500 (ok) [] |
501 (failure) [why:str]
Last but not least, "reset-mailbox" makes the repo mark every message in a named mailbox as changed since the current client last logged in ๐. When the client syncs next, it gets a full copy of that mailbox's mail state. It's basically a more specific version of reset-client, which pulls down the user's entire global mail state. Its main job is fixing mailboxes whose contents got accidentally destroyed locally ๐.
=> 809 (reset-mailbox) [mailbox:str]
<= 500 (ok) [] |
501 (failure) [why:str]
4.5. Address operations
DMSP has three ops that let users manipulate address objects ๐ฎ. First up, "list-address" returns a list of address objects tied to a particular (user-name, mailbox-name) pair ๐.
=> 804 (list-addresses) [mailbox:str]
<= 501 (failure) [why:str] |
805 (address-list) [address-list:SEQ[str]]
"add-address" adds a new address object that links a (user-name, mailbox-name) pair to a given RFC-822-style mailbox address ๐.
=> 806 (add-address) [mailbox:str,
RFC-822-mail-address:str]
okay so the remove-address response is the last bit of that section ๐ โจ
<= 500 (ok) [] |
501 (failure) [why:str]
4. Communication Between Repository and Client: DMSP โจ
4.6. Message operations ๐ฆ
Okay bestie, messages are the stuff clients touch the most, so DMSP gives them special message ops to make syncing fast ๐ฅ and a bunch of normal message-manipulation moves too ๐. Heads up: in this part, "message" and "descriptor" mean the exact same thing, so don't get confused when I switch between them ๐
To check a message's flag values, a client uses the get-descriptor-flags operation ๐. The repository sends back an array of booleans. Eight of those are system defined, and eight are user defined, which the repository just ignores ๐ง
=> 1100 (get-descriptor-flags) [mailbox:str,
uid:Lcard]
<= 1101 (descriptor-flags) [flags:SEQ[bool]] |
501 (failure) [why:str]
Next up is get-descriptors ๐. A user asks for a whole series of descriptors by giving a pair of message UIDs, one for the lower bound and one for the upper bound. UIDs are defined to keep going up monotonically, so that pair is enough to pin down the exact series, no cap ๐. The repository replies with a sequence of "choices". Each element is either a descriptor, and then the choice is tagged as a descriptor, or it's a notice that the requested message got expunged after the client's last connection to the repository ๐
=> 1102 (get-descriptors) [mailbox:str,
low-UID:Lcard,
high-UID:Lcard]
<= 501 (failure) [why:str] |
1103 (descriptor-list) [descriptor-list:SEQ[ CH[
expunged[uid:Lcard]
descriptor[REC[UID:Lcard,
flags:SEQ[bool],
from-field:str,
to-field:str,
date-field:str,
subject-field:str,
num-bytes:Lcard,
num-lines:Lcard]
]]]]
Then there's get-changed-descriptors ๐ธ, which is meant for state synchronization. Whenever a descriptor changes state, like getting deleted for example, the repository keeps track of which clients haven't recorded that change locally yet ๐. Get-changed-descriptors makes the repository send over a given number of descriptors that changed since the client's last synchronization. The list starts with the earliest-changed descriptor, so it's giving chronological vibes โญ
=> 1105 (get-changed-descriptors) [mailbox:str,
max-to-send:card]
<= 501 (failure) why:str] |
1103 (descriptor-list) [descriptor-list:SEQ[
CH[
expunged[uid:Lcard]
descriptor[REC[UID:Lcard,
flags:SEQ[bool],
from-field:str,
to-field:str,
date-field:str,
subject-field:str,
num-bytes:Lcard,
num-lines:Lcard]
]]]]
Once the user has looked at the changed descriptors, they'll want to tell the repository that the current client has recorded the change locally ๐ซถ. That's what reset-changed-descriptors is for ๐. It marks a given number of changed descriptors as "seen by current client", starting with the changed descriptor that has the lowest UID โจ
=> 1106 (reset-changed-descriptors) [
mailbox:str,
number-to-reset:card]
<= 500 (ok) [] |
501 (failure) [why:str]
Message bodies come over from repository to user through get-message-text ๐ฉ. Splitting "get-descriptors" and "get-message-text" into separate ops is lowkey genius, because clients with tiny amounts of disk storage can grab a small message summary (through "get-descriptors" or "get-changed-descriptors") without pulling the entire message ๐งโโ๏ธ ๐
=> 1107 (get-message-text)[mailbox:str,
uid:Lcard]
<= 501 (failure) [why:str] |
1110 (message) [message:SEQ[str]]
Sometimes a message is just too big for some clients to store locally ๐ญ. Users can still look at the contents with print-message ๐จ๏ธ. This one has the repository send a copy of the message to a named printer. The printer name only has to mean something to that specific repository implementation, and DMSP just sends the name as a way of identifying it ๐ฆ
=> 1108 (print-message) [mailbox:str,
uid:Lcard,
printer-name:str]
<= 500 (ok) [] |
501 (failure) [why:str]
The user can set and clear any of the 16 descriptor flags using set-flag ๐ฉ. The flag gets set or cleared depending on the operation arguments, and that's literally all there is to it ๐
=> 1109 (set-flag) [mailbox:str,
uid:Lcard,
flag-number:card,
flag-setting:bool]
<= 500 (ok) [] |
501 (failure) [why:str]
And last but definitely not least, copying a message into another mailbox is done with copy-message ๐โจ
=> 1111 (copy-message) [source-mailbox:str,
target-mailbox:str,
source-uid:Lcard]
<= 500 (ok) [] |
501 (failure) [why:str]
5. Client Architecture ๐
Okay bestie, clients are usually PCs, so Pcmail's architecture has to vibe with three PC traits ๐ป. One, PCs are cheap, so a user might own more than one ๐ธ. Two, they're portable, so they aren't expected to stay plugged into a network ๐. Three, they're resource-poor, so they can't be expected to store a ton of state locally ๐ฅฒ. The subsections below show how Pcmail handles all three ๐ .
5.1. Multiple clients ๐ฏโโ๏ธ
Pcmail users can own more than one PC, and that's the whole reason Pcmail uses a multiple client model โจ. A user might have a PC client at home, another at the office, and maybe even a third portable PC ๐งณ. Each client keeps its own separate copy of the user's mail state, which is why Pcmail is distributed in nature ๐. Having separate clients means users can get to their mail state from several different locations, and that's honestly iconic ๐ฆ.
5.2. Synchronization ๐
PCs are pretty portable, so a PC being always connected to a network is relatively unlikely ๐. That's another reason each client keeps a local copy of the user's mail state ๐. The user can then change the local mail state while they're not connected to the network (or the repository). Obviously this brings up the problem of keeping the local and global mail states in sync, and ngl that's the tricky part ๐ญ.
The repository is always in a position to receive global mail state updates, whether that's incoming mail or changes from other clients ๐ฌ. A client that isn't always connected can't receive those global changes right away ๐ฌ. On top of that, the client's user can make their own changes to the local mail state ๐.
Pcmail's architecture allows efficient synchronization between each client's local mail state and the repository's global mail state ๐ซถ. Every client is identified in the repository by a client object attached to the user, and that object is the basis for synchronization between local and global mail states ๐ฅ. Some of the less common state changes are adding and deleting user mailboxes, and adding and deleting address objects ๐๏ธ. These changes get synced through DMSP list operations, which let clients compare their local versions of the mailbox and address object lists with the repository's global version and make any appropriate changes ๐. Most possible changes to a user's mail state come in the form of changed descriptors ๐. Since most users have a large number of messages, and message states change pretty often, message synchronization needs special attention ๐.
An existing descriptor can change in one of two ways. First, one of its sixteen flags can change (this covers reading an unseen message, deleting a message, and expunging a message) โ . Second, a descriptor changes when incoming mail arrives or when a message gets copied from one mailbox to another ๐ฅ. Both of those result in a new message being added to a mailbox ๐.
In both of those cases, the repository and every client that hasn't previously noted a change need to sync up ๐. To keep track of which clients have noticed a global mail state change and updated their local states, each descriptor has a (potentially empty) "update list" of client objects attached to it ๐ง. That list identifies the clients that haven't recorded a change to that descriptor's state yet ๐.
When a client connects to the repository, it runs the DMSP "get-changed-descriptors" operation ๐ซ. This makes the repository return a list of all descriptor objects that have the requesting client on their update list ๐. As the client receives the changed descriptors, it can store them locally, which updates the local mail state ๐พ. Once a changed descriptor has been recorded, the client uses the DMSP "reset-descriptors" operation to remove itself from that descriptor's update list ๐งน. After that, the descriptor won't be sent to the client again unless (1) it's explicitly requested, or (2) it changes again ๐ โโ๏ธ.
So a client can run through its user's mailboxes, grab all the changed descriptors, fold them into the local mail state, and mark each change as recorded ๐.
5.3. Batch operation versus interactive operation ๐ฆ
Because PCs are so portable, they may not always be connected to the repository ๐งณ. Since each client keeps a local mail state, Pcmail users can manipulate that local state while they aren't connected ๐ . This is called "batch" operation, because all changes get recorded by the client and then made to the repository's global state as one batch, the next time the client connects ๐ฎ. Interactive operation happens when a client is always connected to the repository ๐ถ. In interactive mode, changes to the local mail state get propagated to the global state right away through DMSP operations โก.
In batch mode, the client connects to the repository and sends over all the changes the user made to the local mail state ๐ค. The repository updates its global mail state to match ๐๏ธ. Once all the changes have been processed, the client starts synchronization so it can pull in newly arrived mail and the mail state changes made by other clients ๐ธ.
In interactive mode, local changes go straight to the repository, so the first part of batch-type operation is gone ๐. The synchronization process changes too, because interactive clients can periodically poll the repository for a list of changes and sync a small amount at a time ๐ฐ๏ธ.
5.4. Message summaries ๐
PCs are assumed to have very little disk storage, so a given client may never have enough room for a complete local copy of the user's global mail state ๐ฅบ. That means Pcmail's client architecture has to let users get a clear picture of their mail state without having all their messages on hand ๐ง .
Descriptors do exactly that, giving message information without taking up big chunks of storage ๐. Each descriptor contains a summary of information about one message โจ. That information includes the message UID, its length in bytes and lines, and its status (encoded in the eight system-defined and eight user-defined flags) ๐ท๏ธ. It also includes portions of the message's RFC-822 header (the "to:", "from:", "subject:" and "date:" fields) ๐. All of this fits into a small data structure (around 100 bytes) whose length doesn't depend on the size of the message it describes ๐ฆ.
Any client should be able to store a complete list of message descriptors without much trouble ๐. That lets a user see their whole mail state without having every message stored locally ๐. Short messages can live on the client alongside the descriptors ๐ฑ. Long messages can either be printed through the DMSP "print-message" operation, or pulled down on their own with the "fetch-message-text" operation ๐จ๏ธ๐ .
6. Typical Client-Repository Interaction โจ
okay so this example walks through a typical chat session between the repository and a client ๐ The client is one of three belonging to user "Fred" ๐ง. It's called "office-client", and since Fred uses it regularly to check his mail, it's marked as "active" ๐ธ. Fred has two mailboxes. "main" is where all his current mail lives, and "archive" is where messages of lasting importance get kept ๐. The example runs through a simple synchronization and then a bunch of typical mail state manipulations ๐ . Usually the sync is handled by an application program that connects to the repository, logs in, syncs, and logs out ๐ซถ.
For the example, all DMSP operations are shown in a user-readable format ๐. In real life they'd be sent as a stream of USP blocks, each one a block-type number followed by a stream of bytes for the block's arguments ๐ค. The block name and its number are both included just for convenience โจ.
To get at his global mail state, the client software has to authenticate Fred to the repository. It does this with the DMSP login operation ๐:
600 (login) ["fred", "ajyr63ywg", "office-client",
FALSE, FALSE]
This tells the repository that Fred is logging in via "office-client", and that "office-client" is identified by an existing client object attached to Fred's user object ๐. The second login argument is an encrypted version of Fred's password ๐. The final argument tells the repository that Fred's client isn't running in batch mode but in interactive mode ๐.
Fred's authentication checks out, so the repository logs him in and acknowledges the login request with an OK block โ
Now that Fred is logged in, he wants to bring "office-client"'s local mail state up to date ๐ฆ. To do that, the client program asks for an up-to-date list of mailboxes:
800 (list-mailboxes) []
The repository replies with:
801 (mailbox-list) [["main", 10, 1, 253],
["archive", 100, 0, 101]]
This tells the client there are two mailboxes, "main" and "archive" ๐ฌ. "Main" has 10 messages, one of which is unseen, and the next incoming message will be assigned a UID of 253 ๐. "Archive", on the other hand, has 100 messages, none of which are unseen, and the next message sent to "archive" will get the UID 101 ๐. There are no new mailboxes in the list. (If there were, the client program would create them. On the other hand, if some mailboxes in the client's local list weren't in the repository's list, the program would assume another client deleted them and delete them locally as well ๐ซ .)
To sync, the client only needs to look at each mailbox's contents to see if (1) any new mail has arrived, or (2) Fred changed any messages on one of his other two clients after "office-client"'s last connection to the repository ๐.
The client asks for any changed descriptors with the "get-changed-descriptors" operation. It requests at most ten of them, since storage is super tight on "office-client" ๐ฅบ
1105 (get-changed-descriptors) ["main", 10]
The repository responds with:
1103 (descriptor-list) [[descriptor[
6,
[T T F F F F F F F F F F F F F F],
"Fred@borax",
"Joe@fab",
"Wed, 23 Jan 86 11:11 EST",
"tomorrow's meeting",
621,
10]]
[descriptor[
10,
[F T F F F F F F F F F F F F F F],
"Fred",
"Freds-secretary",
"Fri, 25 Jan 86 11:11 EST",
"Monthly progress report",
13211,
350]]
]
The first descriptor is a message Fred deleted on another client yesterday, so "office-client" marks its local copy as deleted ๐๏ธ. The second one is a brand new message, so "office-client" adds the descriptor to its local list โจ. Both changes are now recorded locally, which means the descriptors can be reset:
1106 (reset-descriptors) ["main", 2]
The repository clears each descriptor's update vector bit that corresponds to "office-client"'s client object ๐งน. "Main" is now synced! ๐ The client then turns to Fred's "archive" mailbox and asks for the first ten changed descriptors:
1105 (get-changed-descriptors) ["archive", 10]
The repository responds with:
1103 (descriptor-list) []
An empty list tells "office-client" that nothing in "archive" has changed since its last sync, so no new sync is needed for that mailbox ๐
Fred's client is now ready to pull down the new message so Fred can read it ๐. The message is 320 lines long, and there might not be enough storage on "office-client" to hold it. The client tries anyway:
1107 (fetch-message-text) ["main", 10]
The repository starts transmitting the message:
1110 (message) ["From: Fred's-secretary",
"To: Fred",
"Subject: Monthly progress report",
"Date: Fri, 25 Jan 86 11:11 EST",
"",
"Dear Fred,",
"Here is this month's progress report",
...
]
Halfway through, "office-client" runs out of disk space ๐ฑ Because all DMSP operations are defined to be atomic, the part of the message that already arrived is thrown away locally and the operation fails ๐. "Office-client" lets Fred know the message can't be pulled over because there isn't enough disk space. The sync is now finished, so Fred's client logs out:
601 (logout) []
The repository does any housecleaning it needs, acknowledges the logout request, and closes the USP connection ๐
7. A Current Pcmail Implementation ๐
Next up is a quick look at a real Pcmail implementation that serves a small community of users ๐. The Pcmail repository runs under UNIX on a DEC VAX-750 that's connected to the Internet ๐ฅ๏ธ. The clients are IBM PCs, XTs, and ATs ๐พ. The network software that talks to the repository only allows "batch-mode" operation. Users make local state changes, which get queued until the client connects to the repository. Then the changes are performed and the local and global states are synchronized, and the client disconnects ๐.
Users access and modify their local mail state through a user interface program ๐ช. The program uses windows and a full-screen mode, and gives users a rich variety of commands for working on individual messages as well as mailboxes ๐ฆ. The interface lets you use any text editor to compose messages, and it adds some features of its own to make RFC-822-style header composition easier โ๏ธ.
Synchronization and processing of queued changes is handled by a separate program that the user runs whenever they want ๐โโ๏ธ. It takes any actions queued while using the user interface and converts them into DMSP operations. All queued changes are made before any synchronization happens ๐.
Limiting the client to batch mode was a deliberate choice, and there are two big reasons ๐. First, the implementation is geared toward portable computers as clients. These machines are rarely connected to the network, so interactive mode isn't really needed ๐ป. Clients that are constantly connected run a bit less efficiently than they could, since users have to make changes locally and then run the action-processing/synchronization program, instead of just making changes interactively ๐ข.
The second big reason is that batch mode allows a very simple locking scheme to prevent problems from concurrent state updates ๐. A user may have several clients, so the repository could easily end up in all kinds of inconsistent states as different clients try to change its global mail state at the same time ๐ตโ๐ซ. To prevent that, a user's mail state is locked as soon as a client connects to the repository, and the lock is released when that client disconnects ๐. This scheme is simple to implement, but it makes interactive-mode operation really cumbersome. If a user stays constantly connected (that is, in interactive mode), the repository would be unavailable to the user's other clients for an unacceptable length of time ๐ โโ๏ธ.
8. Conclusions ๐
Pcmail is now used by a small community of people at the MIT Laboratory for Computer Science ๐ซ. The repository design works well, giving a fairly efficient way to store and maintain mail state for several users ๐ฆ. Members of another research group at LCS are working on a replicated, scaleable version of the repository, built to support a very large community of users with high availability ๐. That repository also uses DMSP and has successfully communicated with clients that use the current repository implementation ๐ค. So DMSP seems to be useable over several flavors of repository design ๐ฆ.
The clients are PCs, and they're unfortunately very limited in resources, which makes local mail state manipulation hard at times ๐ฉ. Synchronization is also pretty time consuming because the PCs perform slowly ๐. Batch-mode client operation is super useful for portable computers that spend a big chunk of their time unplugged and away from a network ๐. It's somewhat less useful for most of the clients, though. Those are always connected to the network and could make really good use of "interactive-mode" state manipulation โจ
I. DMSP Protocol Specification โจ
Okay bestie, this is the final part ๐ Here's the full DMSP block list, sorted by operation type. Same as before: "=>" means a block flowing from the client to the repository, and "<=" means a block flowing from the repository to the client ๐
General operations:
=> or <= 503 (abort-request) [why:str]
(no acknowledgement)
=> 504 (start-debug) []
<= 500 (ok) [] |
501 (failure) [why:str]
=> 505 (end-debug) []
<= 500 (ok) []
=> 506 (send-version) [version:card]
<= 500 (ok) [] |
501 (failure) [why:str]
=> 507 (log-message) [message:str]
<= 500 (ok) [] |
501 (failure) [why:str]
=> 508 (send-message) [message:seq[str]]
<= 500 (ok) [] |
501 (failure) [why:str]
User operations:
=> 600 (login) [name:str, password:str,
client:str, create-client-object?:bool
batch-mode-flag:bool]
<= 500 (ok) [] |
501 (failure) [why:str] |
705 (force-client-reset) []
=> 601 (logout) []
<= 500 (ok) []
=> 602 (add-user) [name:str, password:str]
<= 500 (ok) [] |
501 (failure) [why:str]
=> 603 (remove-user) [user:str]
<= 500 (ok) [] |
501 (failure) [why:str]
=> 604 (set-password) [old:str, new:str]
<= 500 (ok) [] |
501 (failure) [why:str]
Client operations:
=> 700 (list-clients) []
<= 701 (client-list) [client-list:seq[
rec[name:str], status:card]]
=> 702 (add-client) [client:str]
<= 500 (ok) [] |
501 (failure) [why:str]
=> 703 (remove-client) [client:str]
<= 500 (ok) [] |
501 (failure) [why:str]
=> 704 (reset-client) [client:str]
<= 500 (ok) [] |
501 (failure) [why:str]
Mailbox operations:
=> 800 (list-mailboxes) []
<= 801 (mailbox-list) [mailbox-list:seq[
rec[mailbox:str,
next-uid:lcard,
num-msgs:card,
num-unseen-msgs:card]]]
=> 802 (add-mailbox) [mailbox:str]
<= 500 (ok) [] |
501 (failure) [why:str]
=> 803 (remove-mailbox) [mailbox:str]
<= 500 (ok) [] |
501 (failure) [why:str]
=> 808 (expunge-mailbox) [mailbox:str]
<= 500 (ok) [] |
501 (failure) [why:str]
=> 809 (reset-mailbox) [mailbox:str]
<= 500 (ok) [] |
501 (failure) [why:str]
Address operations:
=> 804 (list-addresses) [mailbox:str]
<= 501 (failure) [why:str] |
805 (address-list) [address-list:seq[str]]
=> 806 (add-address) [mailbox:str, address:str]
<= 500 (ok) [] |
501 (failure) [why:str]
=> 807 (remove-address) [mailbox:str, address:str]
<= 500 (ok) [] |
501 (failure) [why:str]
Message operations:
=> 1100 (get-descriptor-flags) [mailbox:str, uid:lcard]
<= 1101 (descriptor-flags) [flags:seq[bool]] |
501 (failure) [why:str]
=> 1102 (get-descriptors) [mailbox:str,
low-uid:lcard,
high-uid:lcard]
<= 501 (failure) [why:str] |
1103 (descriptor-list) [descriptor-list:seq[
ch[
expunged[uid:lcard],
descriptor[rec[uid:lcard,
flags:seq[bool],
from-field:str,
to-field:str,
date-field:str,
subject-field:str,
nun-bytes:lcard,
num-lines:lcard]
]]]]
=> 1105 (get-changed-descriptors) [mailbox:str,
max-to-send:card]
<= 501 (failure) [why:str] |
1103 (descriptor-list) [descriptor-list:seq[
ch[
expunged[uid:lcard],
descriptor[rec[uid:lcard,
flags:seq[bool],
from-field:str,
to-field:str,
date-field:str,
subject-field:str,
num-bytes:lcard,
num-lines:lcard]
]]]]
=> 1106 (reset-changed-descriptors) [
mailbox:str,
start-uid:lcard,
end-uid:lcard]
<= 500 (ok) [] |
501 (failure) [why:str]
=> 1107 (get-message-text) [mailbox:str,
uid:lcard]
<= 501 (failure) [why:str] |
1110 (message) [message:seq[str]]
=> 1108 (print-message) [mailbox:str,
uid:lcard,
printer-name:str]
<= 500 (ok) [] |
501 (failure) [why:str]
=> 1109 (set-flag) [mailbox:str,
uid:lcard,
flag-number:card,
flag-setting:bool]
<= 500 (ok) [] |
501 (failure) [why:str]
=> 1111 copy-message[source-mailbox:str,
target-mailbox:str,
source-uid:lcard]
<= 500 (ok) [] |
501 (failure) [why:str]
DMSP block types by number ๐ข
General block types
ok 500
failure 501
abort-request 503
start-debug 504
end-debug 505
send-version 506
log-message 507
send-message 508
User operation block types
login 600
logout 601
add-user 602
remove-user 603
set-password 604
Client operation block types
list-clients 700
client-list 701
add-clien 702
remove-client 703
reset-client 704
force-client-reset 705
Mailbox operation block types
list-mailboxes 800
mailbox-list 801
add-mailbox 802
remove-mailbox 803
expunge-mailbox 808
reset-mailbox 809
Address operation block types
list-addresses 804
address-list 805
add-address 806
remove-address 807
Message operation block types
get-descriptor-flags 1100
descriptor-flags 1101
get-descriptors 1102
descriptor-list 1103
get-changed-descriptors 1105
reset-changed-descriptors 1106
get-message-text 1107
print-message 1108
set-flag 1109
message 1110
copy-message 1111
That's the whole thing, bestie! ๐๐ You made it through all of RFC 984 ๐ ๐ฆ
๐ฆ where this page comes from
- ietf: rfc984 1986-05
- ๐ girlified by claude-haiku-5-5 on : a language model's rewrite of the original, under its terms