# Messing With Telnet

Telnet is a nice simple protocol for remote text based interaction.
I wrote an interactive TUI canvas with it.  Try it out here:

$ telnet jott.live 8000

You can use arrow keys to move around, type keys to change your character,
and hit space to "stamp" the character.
(Source code [here](https://jott.live/code/telnet.py).)

**HackerNews may be slowing this down a bit, discussion
I've spawned up ports 8000, 8001 and 8002**

## How I Built It

In my head I roughly envisioned users interacting with a shared
terminal environment.

Telnet seemed like the best protocol to use,
so I began by skimming the telnet [spec](https://tools.ietf.org/html/rfc854)
and gradually implementing what seemed to be required.

Telnet is run atop TCP, so I started with `import sockets` and began poking
and prodding at it.  I chose this method over thoroughly reading the spec.

The telnet protocol begins with an option negotitation stage that is easily skipped.
I ran through the telnet option
and guessed that the code for "End of subnegotiation parameters" (SE)
might be all the server needs to look for.
Once that code is found, I assumed, the rest of the data will be raw user input.

Turns out that this guess almost works.  Empirically the SE was
followed by IAC DONT ECHO.  After reskimming the spec, I concluded that
IAC seemed reasonably necessary.  Not sure about the "DONT ECHO" part, but my
brittle code assumes it will always be present.

Unfortunately, I couldn't get away with *no* option negotiation.
Telnet by default (at least on my machine) requires line breaks
before sending user input to the server.  To get around this I began by manually
hitting the escape key from the client side, typing `mode character` and hitting

$ telnet localhost 8000
telnet> mode character

which made for quite a poor experience.
Googling around I found that the *server* can actually
[request character mode](https://stackoverflow.com/questions/273261/force-telnet-client-into-character-mode).
Sending those codes immediately after ignoring
the client's codes ended up working.

I now had the ability to process every key a user typed after they
telnetted into the server.
I tested out some ANSI escape codes and found that it was quite easy to make
the experience immersive.  The two most important ANSI sequences ended up being:
HIDE_CURSOR = b"\033[?25l"
MOVE_CURSOR = lambda x,y : "\033[{};{}H".format(y,x).encode('ascii')
which basically allow you to recreate all of ncurses, provided you know the size
of the user's terminal windows.

It is not immediately apparent how best to query the user's terminal size from
telnet.  My telnet client wasn't sending [NAWS](https://tools.ietf.org/html/rfc1073),
and I couldn't be bothered to induce that with a proper negotiation procedure.

However, I found a nice little [trick](https://stackoverflow.com/a/53267802) that
involves moving the cursor to some very large size and sending a "request cursor"
ANSI code.
The result is the cursor moves to the bottom edge of the terminal window
and returns a sequence that looks like `'\x1b[25;80R'` (a terminal
size of 25x80).  This only took a few lines to get working.

From this point forward I was quite happy with the APIs I'd discovered and the
project became a basic exercise in program design.  I hope you enjoy it :)