Hello Malcom,
if you look at our telnet negotiations, I probably should add some pointers...
First, look also at
https://github.com/MorgenGrauen/mg-mudlib/blob/master/secure/telnetneg-structs.c
for the struct in use and especially this is important:
telopt_s->lo_wishes->localside: the state we want to be in (WILL/WONT)
telopt_s->lo_wishes->remoteside: the state we want the other side to
be in (DO/DONT)
telopt_s->re_wishes->localside: the state the other side wants US to be in
(DO/DONT)
telopt_s->re_wishes->remoteside: the state the other side wants to be in
(WILL/WONT)
telopt_s->state: the currently effective state of the option on the two
sides.
[explanation:
The side that sends WILL wants to use the option on their side. The other can
respond with DO (yes, use it on your side) or DONT (no, don't use it on your
side).
The side that sends DO wants the OTHER side to use the option and the other
side can respond with WILL (yes, I will use it) or WONT (no, I won't use it).
Often, both direction channels can be negotiated independently, e.g. it would
be possible to negotiate BINARY transmission only for the direction mud->client.
]
The basic telnet option handler works by registering closures for handling
remote and local sides for each supported option.
If a remotehandler exists, the client is allowed to switch on the option on
their side (and usually we send DO) and the remote handler gets all sideband
(SB) data that the client sends us plus status changes on the client side
(activation, deactivation).
If a localhandler exists, we try to switch on the option on our side (we send
WILL) and the handler will receive any status changes of the option
(activation, deactivation) on the mud side.
A new option is supported by adding and registering the needed handlers and
the basic one delegates to them.
The basic handler keeps track of the wishes of both sides during negotiation
(keep in mind, that most telnet options are *symmetrical*, i.e. mud and client
can use or not use them independently) and switches the active state only when
negotiation is concluded for the respective option.
To complicate matters, the handler is inherited by the login object
(/secure/login) and the basic player object (/std/player/base), because both
have to negotiate with the client (not only during login). And when the
connection is switched to the final player object, the state has to be
transferred as well.
Also, some options are already negotiated during login (e.g. EOR), but some
are only available once the player object took over the connection after login
(e.g. CHARSET and GMCP).