# DEC MOP Remote Console (console carrier) — protocol notes and VT1200 DWT results Status: **the protocol works against the MAME VT1200**. `HELP` and `SHOW` were answered by the firmware; `TEST` and `INIT` were accepted but do not return a usable console reply (details in §6). The client is `dwt/mop_console.py`. Everything below is either read from a cited source or was observed on the emulator. Where the two disagree, the observation is called out as such. --- ## 1. Sources | source | what it establishes | |---|---| | DEC, *DECnet Digital Network Architecture — Maintenance Operations Protocol Functional Specification*, **v3.0.0**, Dec 1983, order AA-X436A-TK. PDF: (text extracted locally with `pdftotext -layout`) | MOP message codes and bodies, §5.3 “Remote Console” pp. 79–87; Ethernet protocol types and multicast addresses, Appendix B.3 pp. B-1/B-2 | | DEC, *MOP Functional Specification*, **v4.0.0**, Jun 1992, EK-DNA11-FS-001. PDF: | later revision of the same protocol (linked from the revision table at ) | | Mats O Jansson, `mopd` 2.5.3, `common/mopdef.h` and `common/rc.c`: | independent constants: `MOP_K_PROTO_RC 0x6002`; codes 5/7/9/11/13/15/17/19; multicast `ab:00:00:02:00:00` | | Christine Caulfield, `latd`'s `moprc.cc` / `moprc.h`: | a working console **requester**: the on-wire header (2-byte little-endian length, then code), the Reserve + Request ID handshake, the System ID function-bit check, CCP/CRA flag handling, Release | | libpcap `ethertype.h`: | all four ethertypes: `MOPDL 0x6001`, `MOPRC 0x6002`, `DN 0x6003`, `LAT 0x6004` | | VSI, *DECnet-Plus for OpenVMS Network Management Guide*, §10.2.2.1 and Appendix F: | the 8-byte Reserve-Console **verification** value is the MOP service password; byte order for NCL vs NCP; `ccr` is the DECnet-Plus console client | | `docs/EK-V1000-SV-002_service-guide.txt` §2.2 (this repo) | the VT1200 side: NCP `CONNECT VIA PHYSICAL ADDRESS `, `DWT>>>` prompt, `INIT`/`SHOW`/`TEST`/`HELP`, `TEST` parameters and P3 mode bits | Ethertypes were **not** taken on trust: * `0x6001` MOP Dump/Load — libpcap; also `60-01` in MOP §B.3. * `0x6002` MOP Remote Console — libpcap, `mopdef.h`, MOP §B.3 (“60-02 Remote Console”), and observed on the wire. * `0x6003` DECnet (DNA Routing) — libpcap; the MOP spec does not claim it. * `0x6004` LAT — libpcap. Only `0x6002` is used here. --- ## 2. Frame layout Ethernet II, protocol type **0x6002**, destination = the terminal’s unicast MAC (or the protocol’s multicast `ab:00:00:02:00:00`; the VT1200 answers unicast, which is all we needed): ``` offset size field 0 6 destination MAC 6 6 source MAC 12 2 ethertype 0x6002 (big-endian on the wire) 14 2 MOP length, little-endian 16 1 MOP function code 17 ... code-specific body end ... zero padding to a 46-byte Ethernet payload minimum ``` The **length counts the bytes after the length field** (code + body), and excludes the length itself and any padding. `moprc.cc` builds every message that way (`buf[0] = len+2; buf[1] = 0; buf[2] = code; …`) and the VT1200 does the same: a `SHOW` reply is a 527-byte frame with length `0x01FF` = 511 = 1 code + 1 flags + 509 data. **The length field is authoritative, not the frame size.** This is not theoretical: when polled for a second response the VT1200 emits a 60-byte frame whose length field is `2` but whose bytes after the CRA flags are **stale text from the previous response buffer** (observed: `SHOW` leftovers after the `TEST` reply). A client that trusts the frame size will print garbage. `dwt/evidence/mop-script3.pcap` contains both cases. There is **no version byte, no session id and no checksum** on this path. The MOP spec’s Remote Console messages are additions to MOP 2.1 (§5.3); the fields above are the whole header. ### Function codes used From MOP §5.3 and `mopdef.h`: | code | message | body | |---|---|---| | 5 | Request ID | reserved (1, = 0) + receipt number (2, LE) | | 6 | Boot | verification + processor/control/software id (not used) | | 7 | System ID | reserved (1, = 0) + receipt (2, LE) + TLVs | | 9 | Request Counters | receipt (2) | | 11 | Counters | receipt (2) + counter block | | **13** | **Reserve Console** | **verification (8)** — the MOP service password | | **15** | **Release Console** | empty | | **17** | **Console Command and Poll** | control flags (1) + console data (*) | | **19** | **Console Response and Acknowledge** | control flags (1) + console data (*) | Codes 16 and 18 are explicitly reserved by MOP §5. ### System ID TLVs Body after the reserved byte and receipt is a sequence of `type (2, LE) | length (1) | value (length)` entries. The VT1200’s reply (`dwt/evidence/mop-hello.pcap`) decodes as: ``` 37 00 length = 55 07 code = System ID 00 reserved 00 00 receipt 01 00 03 03 00 00 maintenance version 3.0.0 02 00 02 e1 00 functions = 0x00E1 03 00 06 c6 fc cc 16 f5 66 console user = host MAC 04 00 02 0c 00 reservation timer = 12 s 05 00 02 20 00 console command buffer = 32 bytes 06 00 02 58 02 console response buffer = 600 bytes 07 00 06 08 00 2b 16 4b 5a hardware address 64 00 01 72 communication device = 0x72 00 00 00 padding (parsed as an empty type-0 entry) ``` Function bitmap (**type 2, bit 5** = console carrier, 0x20): ``` bit 0 loop, 1 dump, 2 primary loader, 3 multi-block loader, 4 boot, bit 5 console carrier, 6 data link counters, 7 console reservation ``` `0xE1` therefore means loop + console carrier + counters + reservation. `moprc` refuses to proceed unless `functions & 0x20` (and prints “target does not support remote console” otherwise). --- ## 3. Session and sequence handling `moprc`’s sequence is the reference implementation and was followed exactly: 1. **Reserve Console (13)** with an 8-byte verification value. 2. **Request ID (5)** with reserved `0` and a 2-byte receipt number. 3. Wait for **System ID (7)**. This is the “connected” condition; check function bit 5. 4. **Console Command and Poll (17)** carries console input. The control-flags byte has: * bit 0 — **message number**, a one-bit sequence number toggled for each command; * bit 1 — **break**, prepend a serial break to the command data. 5. **Console Response and Acknowledge (19)** carries console output. Its flags: * bit 0 — message number being acknowledged; * bit 1 — command data lost (resend); * bit 2 — response data lost (overrun). 6. **Release Console (15)** with no body. There is no connection id beyond the 48-bit MAC pair, and no per-message acknowledgement other than the poll/response alternation. The protocol is half-duplex and polled: the terminal buffers console output and sends it in a CRA when it is polled. **Do not keep polling after data arrives** unless you want the response repeated — the VT1200 re-sends its whole response buffer on every poll, and an empty response is a CRA with length 2 whose frame may still carry stale bytes (§2). `mop_console.py` polls only until the first data, then drains, treating an exact repeat as a duplicate and a superset as a buffer that grew. Data is a raw byte stream. A command line is the ASCII text plus `\r` (the DWT parser and the real terminal use CR; `moprc` translates LF to CR). The terminal echoes the input and answers with CRLF text and the `DWT>>> ` prompt. --- ## 4. Password / verification `Reserve Console`’s 8-byte VERIFICATION is the **MOP service password** (MOP §5.3.6: “a verification code that must match before the receiving system can honor the request”). VSI §10.2.2.1 says a server that requires a password **ignores the request** if it is wrong — there is no negative reply, so the symptom is exactly “Target does not respond”. The default is `%X0000000000000000`. Byte order (VSI §10.2.2.1): * **NCL / DECnet-Plus**: the value is an octet string; the **leftmost** hex pair is the first byte on the wire. * **NCP (Phase IV)**: the value is an unsigned integer; the **rightmost** digits are the low byte, so bytes go out reversed. NCP `123456789ABCD` becomes NCL `%XCDAB896745230100`, transmitted `CD AB 89 67 45 23 01 00`. The client supports both: `--password HEX` (NCL order, zero-padded to 8) and `--password-int HEX` (NCP integer order, little-endian). **The VT1200 did not challenge us.** Reserve with eight zero bytes works, and Reserve with the arbitrary non-zero value `0102030405060708` also works (`dwt/evidence/wrong-password.log`): the firmware accepted the reservation and answered `HELP`. So in this configuration the verification field is not validated (or no password is configured). There is no challenge/response handshake on the MOP path. The ROM string `dwt_prf: challenge failed - size error (expecting %d, got %d)` is **not** this path. Per `dwt/notes/rdebug-static.md` §6, `dwt_prf` (`0x07EB3FB8`) is the 16-byte challenge of the **TCP remote-debug listener** (`DEBUG TCP Port`, 771), and that listener is separately gated closed by the configuration word at bit `0xAECD2`. MOP never calls it. --- ## 5. What the emulator actually replied Setup: `VT1200_SKIP_LEVEL1=1`, `-cfg_directory cfg-net -networkprovider taptun`, private NVRAM, OS booted to the Terminal Manager. Capture: `dwt/evidence/mop-hello.pcap` (tcpdump filter `ether proto 0x6002`), log: `dwt/evidence/hello.log`. Sequence (timestamps from the pcap): ``` 19.715674 host -> terminal Reserve Console payload 0900 0d00 0000000000000000 19.715796 host -> terminal Request ID payload 0400 0500 0000 19.716371 term -> host System ID 71-byte frame, length 0x37 19.716620 host -> terminal CCP "HELP\r" payload 0700 1100 48454c500d 19.716674 host -> terminal CCP poll (flags 1) 19.719929 term -> host CRA 443-byte frame, length 0x01AB = 427 19.720308 term -> host CRA (empty ack, stale bytes, length 2) ... 20.822062 term -> host CRA SHOW 527-byte frame, length 0x01FF = 511 ``` The first CRA data is the whole 425-byte HELP response in **one frame**: ``` ab01 1300 48454c50 0d0a 0d0a 2020 2044 4543 7769 6e64 6f77 7320 5465 726d 696e 616c ... ... 0d0a 0a0a 0d44 5754 3e3e 3e20 ``` i.e. `HELP\r\n\r\n DECwindows Terminal Remote Console\r\n…DWT>>> `. `SHOW` is 509 bytes (`dwt/evidence/dwt-show.txt`): ``` SHOW DECwindows Terminal System Configuration Configuration Parameter Parameter Value -------------------------------------------------------- Firmware Revision V2.2 Option Memory Present SIMM 1 installed SIMM 2 installed SIMM 3 installed Image Option Present Display Type Monochrome Monitor Type VR150 DWT>>> ``` `HELP` (`dwt/evidence/dwt-help.txt`) lists exactly the four documented commands and their syntax; it matches the ROM strings and the static analysis (`rdebug-static.md` §9). --- ## 6. `TEST` and `INIT` The service guide says remote diagnostics requires a reset and **no active work (no open windows)**. DWT lives inside the running session manager, so with the Terminal Manager open we could not give it that state. The observed behaviour is consistent with that precondition, but note what is and is not established: **`TEST 0 0` and `TEST 12`** (two different tests, two fresh boots, private NVRAM): the terminal accepts the line and replies ``` TEST 0 0 Testing... ``` (22 bytes, CRA length 24; `dwt/evidence/test00b.log`, `test12.log`), and then **no further console output is produced**, even with continued polling for 10 s. Worse, the MOP server then stops answering: a following `Reserve` gets no System ID (`help-after-test.log`, `help-after-test12.log`). We tried a longer wait (test 0: more than a minute after the command) with the same result. So the documented `TEST` is reachable and parses, but does not return the `OK` / `DWT>>>` the service guide shows in this OS-up configuration. We cannot distinguish “test is running and has taken over the machine” from “the session manager was stopped by the test” from the console alone. This is an honest gap, not a protocol failure: the frame carrying `Testing...` has correct length, code and flags. **`INIT`**: accepted, **no reply at all**, and the terminal stops answering MOP for >90 s afterwards (`dwt/evidence/init.log`, `help-after-init2.log`); consistent with a reset/reboot. Do not run this against the real terminal. **Why not run `TEST` in the pre-OS menu?** DWT does not exist there — see §7. --- ## 7. Does DWT answer before the OS boots? No. Two configurations were tested. **OS-up** (`VT1200_SKIP_LEVEL1=1`, `-cfg_directory cfg-net`, private NVRAM): System ID in 0.5 ms, HELP/SHOW as above. **Bare boot** (no `VT1200_SKIP_LEVEL1`), which is the repo’s level-0 diagnostics-menu baseline `PC=3FDCE400` (`harness/mame/README.md`). A Lua probe printed the PC and a client sent Reserve/RequestID at the same time: ``` PC_PROBE frame=100 PC=3FDCE400 PC_PROBE frame=200 PC=3FDCE400 PC_PROBE frame=400 PC=3FDCE400 PC_PROBE frame=600 PC=3FDCE400 PC_PROBE frame=1200 PC=3FDCE400 PC_PROBE frame=2400 PC=3FDCE400 PC_PROBE frame=3600 PC=3FDCE400 ``` while **8 Reserve/RequestID pairs over 8 s got zero frames back** (`dwt/evidence/noos-attempt3.log`, `mop-noos.pcap`, `mame-noos-probe.log`). Earlier without the probe, two 8 s attempts (16 pairs) were also silent (`noos-attempt1.log`, `noos-attempt2.log`). So the DWT console is part of the booted OS/session manager, not the power-on diagnostics code. That matches the ROM string `Session Manager : Cannot create MOP-Server` and the static analysis showing the TEXT parser and the four-command table live in the OS image. Caveat: the OS-up runs use `VT1200_SKIP_LEVEL1=1`, a **deliberate harness divergence** (modelled board input; the real machine reads IP3 low). These results are evidence about the OS/DWT path, not about the real terminal’s POST. --- ## 8. Client `dwt/mop_console.py`, stdlib only (`socket.AF_PACKET`, `select`, `struct`, `fcntl`). No scapy. * `--iface` (default `vt1200`), `--mac` (default `08:00:2b:16:4b:5a`), `--src-mac` (default: the interface MAC). * `connect` = Reserve + Request ID; prints the System ID decode; requires function bit 5. * one command: `… HELP` or `… --raw HEX`. * `--script [CMDS]` defaults to `HELP,SHOW,TEST 0 0`; `--script HELP,SHOW` runs a subset. Every TX/RX frame is hexdumped and decoded. * `--raw HEX` sends arbitrary **console data** inside a CCP. `--raw-frame HEX` sends a complete MOP payload (length + code + body) for protocol experiments. * `--password HEX` / `--password-int HEX`, `--ethertype` (default 0x6002), `--dst` (unicast/broadcast/multicast override), `--no-connect`, `--no-release`, `--quiet`, `--connect-timeout`, `--dry-run`. * Timeout diagnosis prints the phase, the last frames sent (full hex and payload-only hex), whether anything was received, and the common causes (wrong password = silent ignore; pre-OS menu; wrong ethertype). * `--dry-run` needs no socket and prints the exact frames. **Privileges:** `AF_PACKET` send needs `CAP_NET_RAW`. Run with `sudo`, or grant the interpreter the capability once: ```sh sudo setcap cap_net_raw+ep "$(readlink -f "$(which python3)")" ``` `tcpdump` on this host works for user `arti` via the `wireshark` group; the client itself does not. ### Reproduce ```sh # once per boot of the host ./harness/net-setup.sh # terminal: OS-up headless run (private NVRAM, ~20 s wall to the Terminal Manager) cd harness/mame VT1200_SKIP_LEVEL1=1 /home/arti/code/k-space/mame0289/vt1200 vt1200 \ -rompath roms -sound none -skip_gameinfo -lightgun -lightgun_device mouse \ -cfg_directory cfg-net -networkprovider taptun \ -video none -nothrottle -seconds_to_run 600 \ -nvram_directory /home/arti/code/k-space/vt1200/.vt1200-scratch/dwt-nv # capture (second shell) sudo tcpdump -i vt1200 -U -w dwt/evidence/mop-hello.pcap 'ether proto 0x6002' # client (third shell, after the Terminal Manager is up) sudo python3 dwt/mop_console.py --iface vt1200 --mac 08:00:2b:16:4b:5a \ --script HELP,SHOW ``` Expected: 425 bytes for HELP, 509 for SHOW, each in one CRA frame. For the pre-OS check, drop `VT1200_SKIP_LEVEL1=1` (and add `-debug -debugger none -autoboot_script dwt/evidence/pc_probe.lua` if you want the `PC=3FDCE400` confirmation). For `TEST`/`INIT`: ```sh sudo python3 dwt/mop_console.py --iface vt1200 --mac 08:00:2b:16:4b:5a \ --quiet 10 --raw "$(printf 'TEST 0 0\r' | xxd -p | tr -d '\n')" ``` `--dry-run` version of the whole script (no privileges): ```sh python3 dwt/mop_console.py --dry-run --script HELP,SHOW ``` --- ## 9. What changed between attempts * The first attempt used the `moprc` framing verbatim and worked on the first OS-up try; no ethertype or addressing fallback was needed. (`0x6001` is Dump/Load and was not tried because `0x6002` answered.) * The first collector polled continuously. The VT1200 answers every poll by re-sending its buffer, so the first HELP capture had **1235 frames** for one HELP (not kept; the behaviour is visible in the duplicate 60-byte CRAs in `mop-script3.pcap`). The client now stops polling once data has arrived, and treats a repeated chunk as a duplicate and a superset as a grown buffer. The clean run is `mop-hello.pcap` — 135 packets because the terminal emits an empty CRA for every poll, but only three carry data (`System ID`, HELP, SHOW). * A 0.8–1.0 s quiet window cut `TEST` off after `Testing...`; extending it to 10 s and then polling for over a minute confirmed there is no further data, so the `TEST` result is not a client timeout. * The “no DWT before OS” claim was first one silent attempt (not enough), then repeated 16 silent handshakes, then tied to `PC=3FDCE400` with a frame probe. ## 10. Open points * Whether `TEST` would complete if the terminal were reset with no active windows (the service guide’s precondition). DWT lives in the session manager, so “no session manager” and “DWT reachable” appear mutually exclusive in this firmware; a real terminal may behave differently. * Whether the VT1200 has a non-empty maintenance password setting in NVR that would make the 8-byte verification meaningful. Our fresh private NVR is all-zero and the firmware ignored even a wrong non-zero value. * `dwt_prf` / TCP port 771 is the other agent’s path, not this one.