Parses MTTS bitfield values from telnetlib3 ttype3 into a ClientCaps dataclass. Includes color_depth property that returns the best available color mode (truecolor, 256, or 16) based on client capabilities.
86 lines
3.2 KiB
Text
86 lines
3.2 KiB
Text
charset negotiation vs mtts: why tintin++ says WILL then REJECTED
|
|
=================================================================
|
|
|
|
the problem
|
|
-----------
|
|
|
|
telnetlib3 defaults to connect_maxwait=4.0, waiting for CHARSET encoding
|
|
negotiation to resolve. MUD clients like tintin++ reject CHARSET but advertise
|
|
encoding support through MTTS instead. result: 4 seconds of dead air on every
|
|
connection.
|
|
|
|
the fix (our side)
|
|
------------------
|
|
|
|
pass connect_maxwait=0.5 to create_server(). the actual option negotiation
|
|
finishes in ~500ms. CHARSET gets rejected almost immediately but telnetlib3
|
|
doesn't have a fallback path — it just waits for the timeout. 0.5s is plenty.
|
|
|
|
server = await telnetlib3.create_server(
|
|
host="127.0.0.1", port=PORT, shell=shell, connect_maxwait=0.5
|
|
)
|
|
|
|
the upstream fix (telnetlib3)
|
|
-----------------------------
|
|
|
|
when CHARSET is rejected, telnetlib3 could check MTTS bit 3 for UTF-8 support
|
|
and resolve encoding immediately instead of running out the clock. this would
|
|
be a good contribution. the data is already there:
|
|
|
|
MTTS 2825 → bit 3 set → client supports UTF-8
|
|
|
|
what happens on the wire
|
|
------------------------
|
|
|
|
telnetlib3 sends DO CHARSET. tintin++ responds WILL CHARSET ("i know what that
|
|
is") then immediately REJECTED ("but i don't want to use it"). this is normal
|
|
for MUD clients — they prefer MTTS.
|
|
|
|
+0.000 server send DO CHARSET
|
|
+0.100 client recv WILL CHARSET
|
|
+0.100 server send SB CHARSET REQUEST UTF-8 ... US-ASCII
|
|
+0.200 client recv SB CHARSET REJECTED
|
|
|
|
meanwhile, in the same negotiation, tintin++ already told us via MTTS:
|
|
|
|
ttype3: MTTS 2825 = 0b101100001001
|
|
bit 0 (1) = ANSI
|
|
bit 1 (2) = VT100
|
|
bit 2 (4) = UTF-8
|
|
bit 3 (8) = 256 COLORS [SET]
|
|
bit 4 (16) = MOUSE TRACKING
|
|
bit 5 (32) = OSC COLOR PALETTE
|
|
bit 6 (64) = SCREEN READER
|
|
bit 7 (128) = PROXY
|
|
bit 8 (256) = TRUECOLOR [SET]
|
|
bit 9 (512) = MNES [SET]
|
|
bit 10 (1024) = MSLP
|
|
bit 11 (2048) = SSL [SET]
|
|
|
|
for MTTS 2825: bits 0, 3, 8, 9, 11 are set
|
|
|
|
NOTE: The original version of this doc had incorrect MTTS bit mappings (copied
|
|
from an unreliable source). This was corrected on 2026-02-07 to match the
|
|
actual MTTS spec from tintin.mudhalla.net/protocols/mtts/. The wrong values
|
|
caused a bug in caps.py that misinterpreted client capabilities.
|
|
|
|
two protocols, one answer. MTTS (via TTYPE round 3) is what the MUD ecosystem
|
|
uses. RFC 2066 CHARSET is technically correct but practically ignored.
|
|
|
|
will echo and mud clients
|
|
-------------------------
|
|
|
|
related discovery: telnetlib3 intentionally skips WILL ECHO for detected MUD
|
|
clients. tintin++ is in the MUD_TERMINALS fingerprint list. MUD clients
|
|
interpret WILL ECHO as "password mode" and mask input, so the server avoids
|
|
sending it. the client handles its own local echo.
|
|
|
|
this means will_echo is always False for tintin++, and that's correct. don't
|
|
try to fight it or wait for it.
|
|
|
|
see also
|
|
--------
|
|
|
|
- MTTS spec: https://tintin.mudhalla.net/protocols/mtts/
|
|
- RFC 2066: CHARSET option
|
|
- telnetlib3 source: server.py _negotiate_echo(), fingerprinting.py _is_maybe_mud()
|