22 static constexpr uint8_t T_SE = 240;
23 static constexpr uint8_t T_SB = 250;
24 static constexpr uint8_t T_WILL = 251;
25 static constexpr uint8_t T_WONT = 252;
26 static constexpr uint8_t T_DO = 253;
27 static constexpr uint8_t T_DONT = 254;
28 static constexpr uint8_t T_IAC = 255;
29 static constexpr uint8_t OPT_ECHO = 1;
30 static constexpr uint8_t OPT_SGA = 3;
34enum class TelnetState : uint8_t
57 TelnetCommandCb cmd_cb =
nullptr;
59static TelnetCtx s_telnet;
61static TelnetConn *find_conn(uint8_t slot)
65 if (s_telnet.tn[i].used && s_telnet.tn[i].slot == slot)
67 return &s_telnet.tn[i];
73static void raw_send(uint8_t slot,
const void *data,
size_t n)
75 if (!pc_conn_active(slot) ||
91static void send_escaped(uint8_t slot,
const void *data,
size_t n)
93 if (!pc_conn_active(slot) || n == 0)
97 const uint8_t *b = (
const uint8_t *)data;
99 for (
size_t i = 0; i < n; i++)
122void pc_telnet_accept(uint8_t slot)
124 TelnetConn *t =
nullptr;
127 if (!s_telnet.tn[i].used)
139 memset(t, 0,
sizeof(*t));
142 t->st = TelnetState::TN_NORMAL;
145 static const uint8_t neg[] = {TelnetByte::T_IAC, TelnetByte::T_WILL, TelnetByte::OPT_ECHO,
146 TelnetByte::T_IAC, TelnetByte::T_WILL, TelnetByte::OPT_SGA};
147 raw_send(slot, neg,
sizeof(neg));
148 raw_send(slot,
"PC Telnet ready\r\n> ", 22);
151void pc_telnet_close(uint8_t slot)
153 TelnetConn *t = find_conn(slot);
161static void handle_data(uint8_t slot, TelnetConn *t, uint8_t b)
169 t->line[t->len] =
'\0';
170 raw_send(slot,
"\r\n", 2);
173 s_telnet.cmd_cb(t->line, (uint8_t)(t - s_telnet.tn));
176 raw_send(slot,
"> ", 2);
179 if (b == 0x08 || b == 0x7F)
184 raw_send(slot,
"\b \b", 3);
192 if (t->len <
sizeof(t->line) - 1)
194 t->line[t->len++] = (char)b;
195 send_escaped(slot, &b, 1);
199void pc_telnet_rx(uint8_t slot)
201 TelnetConn *t = find_conn(slot);
208 while (pc_conn_read_byte(slot, &b))
214 case TelnetState::TN_NORMAL:
215 if (b == TelnetByte::T_IAC)
217 t->st = TelnetState::TN_IAC;
221 handle_data(slot, t, b);
224 case TelnetState::TN_IAC:
225 if (b == TelnetByte::T_SB)
227 t->st = TelnetState::TN_SB;
229 else if (b == TelnetByte::T_WILL || b == TelnetByte::T_WONT || b == TelnetByte::T_DO ||
230 b == TelnetByte::T_DONT)
233 t->st = TelnetState::TN_OPT;
235 else if (b == TelnetByte::T_IAC)
237 handle_data(slot, t, 0xFF);
238 t->st = TelnetState::TN_NORMAL;
242 t->st = TelnetState::TN_NORMAL;
245 case TelnetState::TN_OPT: {
249 if (t->cmd == TelnetByte::T_DO && b != TelnetByte::OPT_ECHO && b != TelnetByte::OPT_SGA)
251 reply = TelnetByte::T_WONT;
253 else if (t->cmd == TelnetByte::T_WILL)
255 reply = TelnetByte::T_DONT;
259 uint8_t resp[3] = {TelnetByte::T_IAC, reply, b};
260 raw_send(slot, resp, 3);
262 t->st = TelnetState::TN_NORMAL;
265 case TelnetState::TN_SB:
266 if (b == TelnetByte::T_SE)
268 t->st = TelnetState::TN_NORMAL;
279void pc_telnet_on_command(TelnetCommandCb cb)
281 s_telnet.cmd_cb = cb;
284static void broadcast(
const char *s,
size_t n)
288 if (s_telnet.tn[i].used)
290 send_escaped(s_telnet.tn[i].slot, s, n);
295void pc_telnet_print(
const char *s)
303void pc_telnet_println(
const char *s)
309 broadcast(
"\r\n", 2);
312void pc_telnet_frame(
const pc_field *spec, ...)
325uint8_t pc_telnet_client_count()
330 if (s_telnet.tn[i].used)
340static const ProtoHandler s_telnet_handler = {pc_telnet_accept, pc_telnet_rx, pc_telnet_close,
nullptr};
343 return &s_telnet_handler;
PC_OPTIMIZE_O2 size_t pc_frame_vbuild(char *out, size_t cap, const pc_field *spec, va_list ap)
va_list form, for a caller that already has one.
Layer 5 (Session) - per-protocol connection handler dispatch table.
#define MAX_TELNET_CONNS
Maximum simultaneous Telnet connections.
#define TELNET_BUF_SIZE
Stack buffer for one Telnet I/O chunk.
Per-protocol connection event/poll callbacks (Layer 5 dispatch vtable).
One field of a frame. Frames are static const pc_field[], so they live in rodata.
void pc_conn_flush(uint8_t slot)
Flush queued bytes / finish the send on slot (TLS-aware).
void pc_conn_close(uint8_t slot)
Close connection slot gracefully (tcp_close), aborting if the FIN cannot be queued....
bool pc_conn_send(uint8_t slot, const void *data, u16_t len)
Send len bytes on connection slot (copies data; TLS-aware).
Layer 4 (Transport) - TCP connection pool, ring buffers, and lwIP integration.
Layer 6/7 - minimal RFC 854 Telnet server (PC_ENABLE_TELNET).