ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
telnet.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file telnet.h
6 * @brief Layer 6/7 - minimal RFC 854 Telnet server (PROTOCORE_ENABLE_TELNET).
7 *
8 * A zero-heap line-oriented Telnet console dispatched from the session layer's
9 * ProtoConn::PROTO_TELNET arms (the same way SSH is dispatched to ssh_conn). On connect it
10 * negotiates server-side echo + suppress-go-ahead (so the client runs in
11 * character mode and the server draws the line), accumulates a line, echoes
12 * keystrokes (with backspace handling), and hands each completed line to a
13 * command callback. Output can be pushed to all connected clients.
14 *
15 * Telnet is plaintext - no authentication or encryption. Use it only on a
16 * trusted network; prefer SSH or the WebSocket terminal otherwise.
17 *
18 * Usage:
19 * @code
20 * server.listen(23, ProtoConn::PROTO_TELNET); // open the Telnet port
21 * protocore_telnet_on_command(my_cmd_handler); // void(const char *line, uint8_t id)
22 * @endcode
23 */
24
25#ifndef PROTOCORE_TELNET_H
26#define PROTOCORE_TELNET_H
27
28#include "protocore_config.h"
29
31
32// Only ever pointed at from here, so the tags are enough and the engine's header stays out of
33// every translation unit that includes this one.
34struct protocore_field;
35struct protocore_fval;
36
37#if PROTOCORE_ENABLE_TELNET
38
39/** @brief Called with each completed input line (NUL-terminated, no CR/LF) and its client id. */
40typedef void (*TelnetCommandCb)(const char *line, uint8_t conn_id);
41
42struct ProtoHandler;
43
44/** @brief RFC 854 NVT data: what a write puts on the terminal, as a line or as a field set. */
45typedef struct
46{
47 const char *text; ///< a line of NVT ASCII
48 const struct protocore_field *spec; ///< the field layout a frame is built from
49 const struct protocore_fval *val; ///< the values that fill it
50 size_t nv; ///< how many
51} TelnetOutArgs;
52
53/**
54 * @brief The console an application drives, and the three arms the session layer turns.
55 *
56 * The first five are the application's; the three after them are called for a
57 * ProtoConn::PROTO_TELNET slot and are not an application's business.
58 *
59 * A caller sets the members a call takes, invokes it through ::Telnet, and reads the outcome off
60 * the same handle.
61 *
62 * @var TelnetNs::slot the connection a call acts on
63 * @var TelnetNs::cb the per-line command handler an install registers
64 * @var TelnetNs::out what a write puts on the NVT: a line, or a field set
65 * @var TelnetNs::u8 a call's 8-bit outcome
66 * @var TelnetNs::handler the ProtoHandler a lookup reports
67 * @var TelnetNs::on_command register the per-line command handler
68 * @var TelnetNs::print text to every connected client, no trailing newline added
69 * @var TelnetNs::println text + CRLF to every connected client
70 * @var TelnetNs::frame build @c out.spec and broadcast it. The shape is a `static const
71 * protocore_field[]` the caller declares, so a console line costs a table
72 * walk rather than a format-string parse, and one longer than
73 * TELNET_BUF_SIZE is dropped rather than clipped mid-word
74 * @var TelnetNs::client_count connected clients
75 * @var TelnetNs::accept a connection was accepted on TCP slot @c slot
76 * @var TelnetNs::rx process the received bytes for the connection on @c slot
77 * @var TelnetNs::close the connection on @c slot closed; release its state
78 * @var TelnetNs::proto_handler the ProtoHandler the builtins list installs, which is what keeps
79 * this module free of a dependency on the session layer
80 *
81 * @code
82 * static const protocore_field HEAP[] = {{PROTOCORE_FK_LIT, 0, 11, "free heap: "}, PROTOCORE_U32,
83 * {PROTOCORE_FK_LIT, 0, 8, " bytes\r\n"}, PROTOCORE_END};
84 * Telnet.out.spec = HEAP;
85 * Telnet.out.val = (const protocore_fval[]){PROTOCORE_VU32(free_heap_bytes())};
86 * Telnet.out.nv = 1;
87 * Telnet.frame(Telnet.internal);
88 * @endcode
89 */
90
91typedef struct
92{
93 uint8_t slot; ///< the NVT every call names
94 TelnetCommandCb cb; ///< what a received command line is dispatched to
95 TelnetOutArgs out; ///< what a write puts on the NVT
96 uint8_t u8;
97 const struct ProtoHandler *handler;
98} TelnetVars;
99
100/** @brief The operands and the outcome. */
101extern TelnetVars TelnetV;
102
103/** @brief The entries. */
104typedef struct
105{
106 void (*const on_command)(uint8_t *work);
107 void (*const print)(uint8_t *work);
108 void (*const println)(uint8_t *work);
109 void (*const frame)(uint8_t *work);
110 void (*const client_count)(uint8_t *work);
111 void (*const accept)(uint8_t *work);
112 void (*const rx)(uint8_t *work);
113 void (*const close)(uint8_t *work);
114 void (*const proto_handler)(uint8_t *work);
115} TelnetNs;
116
117// What the table binds, defined once in the .c and taking one parameter each: everything
118// else an entry needs is an operand in TelnetV or a region of the borrow at a fixed offset.
119void protocore_telnet_on_command(uint8_t *work);
120void protocore_telnet_print(uint8_t *work);
121void protocore_telnet_println(uint8_t *work);
122void protocore_telnet_frame(uint8_t *work);
123void protocore_telnet_client_count(uint8_t *work);
124void protocore_telnet_accept(uint8_t *work);
125void protocore_telnet_rx(uint8_t *work);
126void protocore_telnet_close(uint8_t *work);
127void protocore_telnet_proto_handler(uint8_t *work);
128
129// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
130// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
131// `Telnet.on_command(work)` resolves to a named function and becomes a DIRECT call. An extern table
132// leaves the call indirect and the symbol live at every level, -O2 -flto included.
133static const TelnetNs Telnet __attribute__((unused)) = {
134 .on_command = protocore_telnet_on_command,
135 .print = protocore_telnet_print,
136 .println = protocore_telnet_println,
137 .frame = protocore_telnet_frame,
138 .client_count = protocore_telnet_client_count,
139 .accept = protocore_telnet_accept,
140 .rx = protocore_telnet_rx,
141 .close = protocore_telnet_close,
142 .proto_handler = protocore_telnet_proto_handler,
143};
144
145/**
146 * @brief The PROTOCORE_TELNET_BORROW bytes this module's state lives in.
147 *
148 * Stated beside the namespace rather than on it: an entry takes a borrow, and this is where
149 * that borrow comes from. Taken once from the end of the pool, which no mark and no release
150 * walks, so the state lasts the life of the program.
151 *
152 * @return the span.
153 */
154uint8_t *protocore_telnet_span(void);
155
156#endif // PROTOCORE_ENABLE_TELNET
157
159
160#endif // PROTOCORE_TELNET_H
Per-protocol connection event/poll callbacks (the server's dispatch vtable).
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
#define PROTOCORE_END_DECLS
Definition types.h:97