Skip to content

Core: connection layer (telnet, GMCP, MCCP2) - #2

Closed
MHohlios wants to merge 5 commits into
wiffletree/phase-0-rust-workspace-and-gpui-spike-virtualizefrom
wiffletree/core-connection-layer-telnet-gmcp-mccp2
Closed

MHohlios wants to merge 5 commits into
wiffletree/phase-0-rust-workspace-and-gpui-spike-virtualizefrom
wiffletree/core-connection-layer-telnet-gmcp-mccp2

Conversation

@MHohlios

Copy link
Copy Markdown
Contributor

Adds MUDling's connection layer to mudling-core, as two modules:

  • telnet: a sans-IO telnet parser, so a replay can drive it
  • net: a tokio TCP layer around it

Linear: VYT-190 (Phase 1: Core, VYT-186). Stacked on #1 (Phase 0).

What's in it

  • Telnet:
    • IAC escaping in both directions
    • option negotiation that replies only when an option's state changes
    • ECHO, EOR, GMCP, MSSP (NAME only) and MCCP2
    • every other option refused, NAWS and TTYPE included
  • Text: memchr slices text between IAC sequences with no copying (PRD §5 Core: default plugins (prompt learning, HUD fields, chaining, pagers, game detection) #8). A GMCP message costs time linear in its length.
  • MCCP2: decompression starts right after IAC SB COMPRESS2 IAC SE, and plain parsing resumes at stream end. It uses flate2 with the pure-Rust zlib-rs backend, which is about 90× faster than the default miniz_oxide.
  • Subnegotiation cap: subnegotiations are capped at 8 MiB. Past the cap, the rest is dropped with a SubnegotiationDropped event, and parsing carries on.
  • net:
    • connect() returns at once, and sending never blocks.
    • The connect timeout defaults to 10 s, and close or drop aborts a pending connect.
    • The read buffer is reused between reads.
    • A login hook answers prompts in the server's encoding.
  • Events: Connected, Data(Bytes), PromptEnd (GA/EOR), Gmcp { package, data }, GmcpEnabled, Echo(bool) (false means a password prompt), Mssp(name), SubnegotiationDropped, Disconnected(reason).

Numbers (release, M3 Pro, mudling-bench-telnet --check)

Throughput p99 per 4 KB chunk
Plain ~1.9 GB/s 5.1 µs
MCCP2 1.2 GB/s of telnet 50–70 µs

GMCP costs 0.4 ns per byte, the same at 32 KB as at 1 MB.

Tests

  • A synthetic fixture stream, and an MCCP2 fixture, are each split at every byte offset and also fed one byte at a time.
  • The rest cover zero-copy slicing, an oversize subnegotiation split across chunks, and corrupt zlib data.
  • Loopback TCP tests cover negotiation, the GMCP hello, the login hook, close, refusal, and the connect timeout or abort.
  • The example cargo run -p mudling-core --example connect -- host:port has not been run against a real server.

Follow-ups (not in this PR)

  • Auto-login should match on the line pipeline's lines instead of raw Data, which belongs to session wiring.
  • A user cancel during connect shows as Failed.
  • The subnegotiation buffer keeps its peak size after a large GMCP message.

mhohlios added 3 commits October 29, 2026 13:01
A sans-IO telnet parser slices server text between IAC bytes with memchr
into zero-copy Bytes, negotiates ECHO, EOR, GMCP, MSSP and MCCP2, and
refuses every other option, NAWS included. Subnegotiation spans chunks,
MCCP2 starts mid-chunk and ends cleanly, and MSSP surfaces only NAME.
The net module wraps it in a tokio task with a non-blocking handle,
sends Core.Hello and Core.Supports.Set when GMCP is agreed, and offers a
login hook for plugins. mudling-bench-telnet measures throughput and the
cost per 4 KB chunk; examples/connect prints a server's events.
A longer subnegotiation is dropped up to IAC SE, reported once as
Event::SubnegotiationDropped, and parsing carries on, so a broken server
can't grow memory without limit.
…answers

- Reuse the read buffer, reserving only when under 4 KB remains.
- Time the connect out (ConnectOptions::connect_timeout, 10 s default)
  and abort it on close or when every handle drops.
- LoginHook::answer returns bytes in the server's encoding.
- Ignore the wall-clock linear-time test; the bench measures it.
- The telnet bench uses the core's GMCP framing and constants, and
  names its decompressed totals telnet_*.
- Say why MCCP2 output blocks are 16 KB.
mhohlios added 2 commits October 29, 2026 19:52
…lize' into wiffletree/core-connection-layer-telnet-gmcp-mccp2
The telnet bench is a second binary, so cargo run -p mudling-bench needs
to know which one to run.
@MHohlios

Copy link
Copy Markdown
Contributor Author

Landed on main through #15.

@MHohlios MHohlios closed this Oct 31, 2026
@MHohlios
MHohlios deleted the wiffletree/core-connection-layer-telnet-gmcp-mccp2 branch November 1, 2026 17:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant