paleotechnica
Member
- Joined
- Oct 20, 2025
- Messages
- 29
As a related side-project to the VT420 emulator, I started digging into the virtually undocumented DEC SSU protocol. The patent is somewhat useful for understanding the overall structure, but it was nowhere near enough to build an implementation.
I ended up using a real VT420, some ROM disassembly, and a lot of experimentation to basically build a working implementation from scratch.
Along the way I found a few behaviours that aren’t mentioned in the patent at all.
So why would you bother with SSU instead of a raw serial connection? Mainly because the early VT terminals don’t have hardware flow control and rely entirely on XON/XOFF. SSU uses transmission credits, which makes it much harder for the sender to overrun the receiver’s buffers. It also frees up the Comm2 port for a printer while still allowing two sessions, if that sort of thing appeals to you.
SSU also gives you “session framing.” You can open and close sessions, and the terminal can even prompt you for a session name - which hypothetically lets you provide something like an SSH hostname without writing a custom “jump box” layer.
Here are the results from my experiments. The low-level details are in the GitHub repo at: https://github.com/mmastrac/blaze/blob/main/architecture/SSU.md
The SSU server implementation is basically ready for people to play with. Given how little data it pushes around, it should run fine on a very low-end Raspberry Pi. If anyone wants to try it out in their own setups, I’d love to get extra eyes on it and see how it behaves on different hardware. I'll probably need to walk the first few people through the setup until I've ironed out some of the kinks.
Flow Control
SSU uses a simple flow control mechanism based on credits. Each side is allocated a number of credits to use for sending data, and the strategy for dispensing them is left to the implementation. Generally, the credits will reflect the available buffer space for the session.
By default, each side of the session has no credits and must be granted credits before sending data.
When a side runs out of credits on a given channel, its credits have been explicitly zeroed/reset, or if it has never been granted credits, it must not send any more data until it explicitly receives more credits.
Each side should preemptively add more credits as it detects the peer is running low. If the remote side continues sending data after running out of credits, the local side can send a `ZERO` message to force it to zero out its credit balance until there's enough buffer space to receive data from it again.
The entire balance of credit does not need to be dispensed at once. The sender can dispense different levels of credits as it sees fit, for example to reflect different buffer watermarks.
The entire SSU channel between peers may be controlled with XON/XOFF in cases where one side needs time to catch up on control message processing or otherwise needs to pause the total flow of data. This pauses ALL data processing for ALL sessions.
Handshake
The handshake can be initiated from either side and is as follows. Certain `REPORT` responses have been omitted for brevity:
1. The local side sends a `PROBE` message (`!@AB`) to indicate that it is in the disabled state.
2. The remote side responds with `!AAB` (enabled, no sessions) or `!BAB` (enabled, with existing sessions).
3. The local side sends a `REPORT` message confirming activation: `=!a@`.
4. If the remote side sent `!BAB`, the local side should send `;` to request a session restore.
- The remote side sends a `RESTORE_START` message.
- For each open session, the remote side sends a `OPEN_SESSION` message.
- The remote side sends a `RESTORE_END` message.
5. If the remote side did not send `!BAB`, it may still send `OPEN_SESSION` messages to open sessions.
6. If the remote side did not open sessions, the local side may also request named or unnamed sessions be opened via `OPEN_SESSION` messages.
7. Either side may send a `SELECT_SESSION` message and start sending data once it receives an `ADD_CREDITS` message granting credits.
Message Format
The stream is in "data mode" by default and all messages are directed towards the selected session. To send a command to the remote side, send the intro byte
(`0x14`, a.k.a. `DC4`) followed by the opcode, parameters, and the term byte (`0x1C`).
If a raw `0x14` is supposed to be sent, it is encoded as `0x14` `T` instead. XON and XOFF to be sent to a session are similarily encoded as `0x14` `Q` and `0x14`
`S` respectively.
Parameters are encoded with an offset of 0x40, meaning that each character is encoded as a six-bit value, with zero being `@`, one being `A`, etc.
Session IDs are encoded as 1-based indices, meaning that `A` is 1 and `B` is 2.
In certain cases, such as `RESET`, a session ID of 0 is used to indicate all sessions.
The following opcodes are supported. Unrecognized opcodes should be ignored:
I ended up using a real VT420, some ROM disassembly, and a lot of experimentation to basically build a working implementation from scratch.
So why would you bother with SSU instead of a raw serial connection? Mainly because the early VT terminals don’t have hardware flow control and rely entirely on XON/XOFF. SSU uses transmission credits, which makes it much harder for the sender to overrun the receiver’s buffers. It also frees up the Comm2 port for a printer while still allowing two sessions, if that sort of thing appeals to you.
SSU also gives you “session framing.” You can open and close sessions, and the terminal can even prompt you for a session name - which hypothetically lets you provide something like an SSH hostname without writing a custom “jump box” layer.
Here are the results from my experiments. The low-level details are in the GitHub repo at: https://github.com/mmastrac/blaze/blob/main/architecture/SSU.md
The SSU server implementation is basically ready for people to play with. Given how little data it pushes around, it should run fine on a very low-end Raspberry Pi. If anyone wants to try it out in their own setups, I’d love to get extra eyes on it and see how it behaves on different hardware. I'll probably need to walk the first few people through the setup until I've ironed out some of the kinks.
Flow Control
SSU uses a simple flow control mechanism based on credits. Each side is allocated a number of credits to use for sending data, and the strategy for dispensing them is left to the implementation. Generally, the credits will reflect the available buffer space for the session.
By default, each side of the session has no credits and must be granted credits before sending data.
When a side runs out of credits on a given channel, its credits have been explicitly zeroed/reset, or if it has never been granted credits, it must not send any more data until it explicitly receives more credits.
Each side should preemptively add more credits as it detects the peer is running low. If the remote side continues sending data after running out of credits, the local side can send a `ZERO` message to force it to zero out its credit balance until there's enough buffer space to receive data from it again.
The entire balance of credit does not need to be dispensed at once. The sender can dispense different levels of credits as it sees fit, for example to reflect different buffer watermarks.
The entire SSU channel between peers may be controlled with XON/XOFF in cases where one side needs time to catch up on control message processing or otherwise needs to pause the total flow of data. This pauses ALL data processing for ALL sessions.
Handshake
The handshake can be initiated from either side and is as follows. Certain `REPORT` responses have been omitted for brevity:
1. The local side sends a `PROBE` message (`!@AB`) to indicate that it is in the disabled state.
2. The remote side responds with `!AAB` (enabled, no sessions) or `!BAB` (enabled, with existing sessions).
3. The local side sends a `REPORT` message confirming activation: `=!a@`.
4. If the remote side sent `!BAB`, the local side should send `;` to request a session restore.
- The remote side sends a `RESTORE_START` message.
- For each open session, the remote side sends a `OPEN_SESSION` message.
- The remote side sends a `RESTORE_END` message.
5. If the remote side did not send `!BAB`, it may still send `OPEN_SESSION` messages to open sessions.
6. If the remote side did not open sessions, the local side may also request named or unnamed sessions be opened via `OPEN_SESSION` messages.
7. Either side may send a `SELECT_SESSION` message and start sending data once it receives an `ADD_CREDITS` message granting credits.
Message Format
The stream is in "data mode" by default and all messages are directed towards the selected session. To send a command to the remote side, send the intro byte
(`0x14`, a.k.a. `DC4`) followed by the opcode, parameters, and the term byte (`0x1C`).
If a raw `0x14` is supposed to be sent, it is encoded as `0x14` `T` instead. XON and XOFF to be sent to a session are similarily encoded as `0x14` `Q` and `0x14`
`S` respectively.
Parameters are encoded with an offset of 0x40, meaning that each character is encoded as a six-bit value, with zero being `@`, one being `A`, etc.
Session IDs are encoded as 1-based indices, meaning that `A` is 1 and `B` is 2.
In certain cases, such as `RESET`, a session ID of 0 is used to indicate all sessions.
The following opcodes are supported. Unrecognized opcodes should be ignored:
| Opcode | ASCII | Opcode Name | Description |
! | 0x21 | PROBE | Probe/Enable |
" | 0x22 | OPEN_SESSION | Open session |
# | 0x23 | SELECT_SESSION | Select session |
| ... | ... | ... | ... |
* | 0x2A | RESET | Reset |
+ | 0x2B | ADD_CREDITS | Add credits |
, | 0x2C | UNUSED | (unused opcode) |
- | 0x2D | VERIFY_CREDITS | Verify credits |
. | 0x2E | CLOSE_SESSION | Close session |
/ | 0x2F | DISABLE | Disable |
0 | 0x30 | ZERO_CREDITS | Zero credits |
| ... | ... | ... | ... |
: | 0x3A | SEND_BREAK | Send break |
; | 0x3B | REQUEST_RESTORE | Request restore |
< | 0x3C | RESTORE | Restore |
= | 0x3D | REPORT | Report/Ack |
> | 0x3E | RESTORE_END | Restore end |
? | 0x3F | QUERY_SESSION | Query session |