ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
phase_machine.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 phase_machine.h
6 * @brief The handshake phase machine, RFC 4253 sec 4.2 through sec 10.
7 *
8 * One connection walks the transport layer's own progression, then hands over to RFC 4252:
9 *
10 * IDENT (sec 4.2) -> KEXINIT (sec 7.1) -> DH_INIT (sec 8) -> NEWKEYS (sec 7.3)
11 * -> SERVICE (sec 10) -> AUTH (RFC 4252) -> OPEN (RFC 4254)
12 *
13 * and sec 9 sends an established connection back to KEXINIT for a re-exchange. The advance
14 * functions are named for the RFC event that causes them and are the only writers of the phase;
15 * the admits functions are the questions the dispatch asks before acting on a message.
16 */
17
18#ifndef PROTOCORE_TRANSPORT_PHASE_MACHINE_H
19#define PROTOCORE_TRANSPORT_PHASE_MACHINE_H
20
21#include "protocore_config.h" // the entry point: protocore_types.h for the widths
22
23#if PROTOCORE_ENABLE_SSH || PROTOCORE_ENABLE_SSH_CLIENT
24
26
27// This module holds nothing between calls, so it carves no borrow and states none. An entry
28// takes one all the same, and never reads it, so every namespace in the tree is invoked the
29// same way.
30
31/** @brief SSH connection lifecycle phase. */
32typedef enum PROTO_ENUM_PACKED
33{
34 SSH_PHASE_IDENT, ///< Awaiting the peer identification string.
35 SSH_PHASE_KEXINIT, ///< Awaiting the peer KEXINIT.
36 SSH_PHASE_DH_INIT, ///< Awaiting SSH_MSG_KEXDH_INIT.
37 SSH_PHASE_NEWKEYS, ///< Awaiting SSH_MSG_NEWKEYS.
38 SSH_PHASE_SERVICE, ///< Awaiting SERVICE_REQUEST ("ssh-userauth").
39 SSH_PHASE_AUTH, ///< User authentication in progress (RFC 4252).
40 SSH_PHASE_OPEN ///< Authenticated; connection/channel protocol active.
41} SshPhase;
42
43/** @brief What get takes: i. */
44typedef struct
45{
46 uint8_t i;
47} PhaseMachineGetArgs;
48
49/** @brief What is takes: i, p. */
50typedef struct
51{
52 uint8_t i;
53 SshPhase p;
54} PhaseMachineIsArgs;
55
56/** @brief What reset takes: i. */
57typedef struct
58{
59 uint8_t i;
60} PhaseMachineResetArgs;
61
62/** @brief What ident_done takes: i. */
63typedef struct
64{
65 uint8_t i;
66} PhaseMachineIdentDoneArgs;
67
68/** @brief What kexinit_done takes: i. */
69typedef struct
70{
71 uint8_t i;
72} PhaseMachineKexinitDoneArgs;
73
74/** @brief What kex_done takes: i. */
75typedef struct
76{
77 uint8_t i;
78} PhaseMachineKexDoneArgs;
79
80/** @brief What newkeys_done takes: i. */
81typedef struct
82{
83 uint8_t i;
84} PhaseMachineNewkeysDoneArgs;
85
86/** @brief What service_done takes: i. */
87typedef struct
88{
89 uint8_t i;
90} PhaseMachineServiceDoneArgs;
91
92/** @brief What auth_done takes: i. */
93typedef struct
94{
95 uint8_t i;
96} PhaseMachineAuthDoneArgs;
97
98/** @brief What rekey_begin takes: i. */
99typedef struct
100{
101 uint8_t i;
102} PhaseMachineRekeyBeginArgs;
103
104/** @brief What admits_ident takes: i. */
105typedef struct
106{
107 uint8_t i;
108} PhaseMachineAdmitsIdentArgs;
109
110/** @brief What admits_kexinit takes: i. */
111typedef struct
112{
113 uint8_t i;
114} PhaseMachineAdmitsKexinitArgs;
115
116/** @brief What kexinit_needs_reply takes: i. */
117typedef struct
118{
119 uint8_t i;
120} PhaseMachineKexinitNeedsReplyArgs;
121
122/** @brief What admits_kexdh_init takes: i. */
123typedef struct
124{
125 uint8_t i;
126} PhaseMachineAdmitsKexdhInitArgs;
127
128/** @brief What admits_newkeys takes: i. */
129typedef struct
130{
131 uint8_t i;
132} PhaseMachineAdmitsNewkeysArgs;
133
134/** @brief What admits_service_request takes: i. */
135typedef struct
136{
137 uint8_t i;
138} PhaseMachineAdmitsServiceRequestArgs;
139
140/** @brief What admits_userauth takes: i. */
141typedef struct
142{
143 uint8_t i;
144} PhaseMachineAdmitsUserauthArgs;
145
146/** @brief What auth_complete takes: i. */
147typedef struct
148{
149 uint8_t i;
150} PhaseMachineAuthCompleteArgs;
151
152/** @brief What admits_rekey takes: i. */
153typedef struct
154{
155 uint8_t i;
156} PhaseMachineAdmitsRekeyArgs;
157
158/** @brief What is_open takes: i. */
159typedef struct
160{
161 uint8_t i;
162} PhaseMachineIsOpenArgs;
163
164/**
165 * @brief The handshake phase machine, RFC 4253 sec 4.2 through sec 10.
166 *
167 * A caller sets the members a call takes, invokes it through ::PhaseMachine with the bytes it runs
168 * out of, and reads the outcome off the same handle.
169 *
170 * PhaseMachine.get_args.i = ...;
171 * PhaseMachine.get(work);
172 * // PhaseMachine.value is what the call reports
173 *
174 * @var PhaseMachineNs::get_args what get takes: i
175 * @var PhaseMachineNs::is_args what is takes: i, p
176 * @var PhaseMachineNs::reset_args what reset takes: i
177 * @var PhaseMachineNs::ident_done_args what ident_done takes: i
178 * @var PhaseMachineNs::kexinit_done_args what kexinit_done takes: i
179 * @var PhaseMachineNs::kex_done_args what kex_done takes: i
180 * @var PhaseMachineNs::newkeys_done_args what newkeys_done takes: i
181 * @var PhaseMachineNs::service_done_args what service_done takes: i
182 * @var PhaseMachineNs::auth_done_args what auth_done takes: i
183 * @var PhaseMachineNs::rekey_begin_args what rekey_begin takes: i
184 * @var PhaseMachineNs::admits_ident_args what admits_ident takes: i
185 * @var PhaseMachineNs::admits_kexinit_args what admits_kexinit takes: i
186 * @var PhaseMachineNs::kexinit_needs_reply_args what kexinit_needs_reply takes: i
187 * @var PhaseMachineNs::admits_kexdh_init_args what admits_kexdh_init takes: i
188 * @var PhaseMachineNs::admits_newkeys_args what admits_newkeys takes: i
189 * @var PhaseMachineNs::admits_service_request_args what admits_service_request takes: i
190 * @var PhaseMachineNs::admits_userauth_args what admits_userauth takes: i
191 * @var PhaseMachineNs::auth_complete_args what auth_complete takes: i
192 * @var PhaseMachineNs::admits_rekey_args what admits_rekey takes: i
193 * @var PhaseMachineNs::is_open_args what is_open takes: i
194 * @var PhaseMachineNs::ok a call's true/false outcome
195 * @var PhaseMachineNs::value the value a call reports
196 * @var PhaseMachineNs::get the phase slot i is in
197 * @var PhaseMachineNs::is true when slot i is in phase p
198 * @var PhaseMachineNs::reset start a connection at the identification exchange (RFC 4253 sec 4.2)
199 * @var PhaseMachineNs::ident_done the peer identification string is whole: sec 4.2 is done, sec 7.1 ...
200 * @var PhaseMachineNs::kexinit_done algorithms are negotiated (sec 7.1): await the exchange of sec 8
201 * @var PhaseMachineNs::kex_done the exchange produced K and H (sec 8): await NEWKEYS
202 * @var PhaseMachineNs::newkeys_done NEWKEYS ended the exchange (sec 7.3): the keys are in use. A first ...
203 * @var PhaseMachineNs::service_done the service request named "ssh-userauth" and was accepted (sec 10)
204 * @var PhaseMachineNs::auth_done authentication completed (RFC 4252 sec 5.1 SUCCESS): the channel ...
205 * @var PhaseMachineNs::rekey_begin A key re-exchange begins on an established connection (sec 9)
206 * @var PhaseMachineNs::admits_ident RFC 4253 sec 4.2: identification bytes are only read before the ...
207 * @var PhaseMachineNs::admits_kexinit RFC 4253 sec 7.1: a KEXINIT starts an exchange, and one is refused ...
208 * @var PhaseMachineNs::kexinit_needs_reply RFC 4253 sec 7.1: does an inbound KEXINIT still need our own in ...
209 * @var PhaseMachineNs::admits_kexdh_init RFC 4253 sec 8: KEXDH_INIT belongs to an exchange that has been ...
210 * @var PhaseMachineNs::admits_newkeys RFC 4253 sec 7.3: "NEWKEYS ends a key exchange", so one outside an ...
211 * @var PhaseMachineNs::admits_service_request RFC 4253 sec 10: a service request is only valid once the exchange ...
212 * @var PhaseMachineNs::admits_userauth RFC 4252: a userauth request belongs to the authentication phase
213 * @var PhaseMachineNs::auth_complete RFC 4252 sec 5.1: a request arriving after SUCCESS is ignored ...
214 * @var PhaseMachineNs::admits_rekey RFC 4253 sec 9: a re-exchange starts only on an established ...
215 * @var PhaseMachineNs::is_open RFC 4254: the channel protocol runs once the connection is open
216 *
217 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
218 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
219 * a caller drives every namespace the same way.
220 */
221typedef struct
222{
223 PhaseMachineGetArgs get_args;
224 PhaseMachineIsArgs is_args;
225 PhaseMachineResetArgs reset_args;
226 PhaseMachineIdentDoneArgs ident_done_args;
227 PhaseMachineKexinitDoneArgs kexinit_done_args;
228 PhaseMachineKexDoneArgs kex_done_args;
229 PhaseMachineNewkeysDoneArgs newkeys_done_args;
230 PhaseMachineServiceDoneArgs service_done_args;
231 PhaseMachineAuthDoneArgs auth_done_args;
232 PhaseMachineRekeyBeginArgs rekey_begin_args;
233 PhaseMachineAdmitsIdentArgs admits_ident_args;
234 PhaseMachineAdmitsKexinitArgs admits_kexinit_args;
235 PhaseMachineKexinitNeedsReplyArgs kexinit_needs_reply_args;
236 PhaseMachineAdmitsKexdhInitArgs admits_kexdh_init_args;
237 PhaseMachineAdmitsNewkeysArgs admits_newkeys_args;
238 PhaseMachineAdmitsServiceRequestArgs admits_service_request_args;
239 PhaseMachineAdmitsUserauthArgs admits_userauth_args;
240 PhaseMachineAuthCompleteArgs auth_complete_args;
241 PhaseMachineAdmitsRekeyArgs admits_rekey_args;
242 PhaseMachineIsOpenArgs is_open_args;
243 proto_bool ok;
244 SshPhase value;
245} PhaseMachineVars;
246
247/** @brief The operands and the outcome. */
248extern PhaseMachineVars PhaseMachineV;
249
250/** @brief The entries. */
251typedef struct
252{
253 void (*const get)(uint8_t *work);
254 void (*const is)(uint8_t *work);
255 void (*const reset)(uint8_t *work);
256 void (*const ident_done)(uint8_t *work);
257 void (*const kexinit_done)(uint8_t *work);
258 void (*const kex_done)(uint8_t *work);
259 void (*const newkeys_done)(uint8_t *work);
260 void (*const service_done)(uint8_t *work);
261 void (*const auth_done)(uint8_t *work);
262 void (*const rekey_begin)(uint8_t *work);
263 void (*const admits_ident)(uint8_t *work);
264 void (*const admits_kexinit)(uint8_t *work);
265 void (*const kexinit_needs_reply)(uint8_t *work);
266 void (*const admits_kexdh_init)(uint8_t *work);
267 void (*const admits_newkeys)(uint8_t *work);
268 void (*const admits_service_request)(uint8_t *work);
269 void (*const admits_userauth)(uint8_t *work);
270 void (*const auth_complete)(uint8_t *work);
271 void (*const admits_rekey)(uint8_t *work);
272 void (*const is_open)(uint8_t *work);
273} PhaseMachineNs;
274
275// What the table binds, defined once in the .c and taking one parameter each: everything
276// else an entry needs is an operand in PhaseMachineV or a region of the borrow at a fixed offset.
277void protocore_phase_machine_get(uint8_t *work);
278void protocore_phase_machine_is(uint8_t *work);
279void protocore_phase_machine_reset(uint8_t *work);
280void protocore_phase_machine_ident_done(uint8_t *work);
281void protocore_phase_machine_kexinit_done(uint8_t *work);
282void protocore_phase_machine_kex_done(uint8_t *work);
283void protocore_phase_machine_newkeys_done(uint8_t *work);
284void protocore_phase_machine_service_done(uint8_t *work);
285void protocore_phase_machine_auth_done(uint8_t *work);
286void protocore_phase_machine_rekey_begin(uint8_t *work);
287void protocore_phase_machine_admits_ident(uint8_t *work);
288void protocore_phase_machine_admits_kexinit(uint8_t *work);
289void protocore_phase_machine_kexinit_needs_reply(uint8_t *work);
290void protocore_phase_machine_admits_kexdh_init(uint8_t *work);
291void protocore_phase_machine_admits_newkeys(uint8_t *work);
292void protocore_phase_machine_admits_service_request(uint8_t *work);
293void protocore_phase_machine_admits_userauth(uint8_t *work);
294void protocore_phase_machine_auth_complete(uint8_t *work);
295void protocore_phase_machine_admits_rekey(uint8_t *work);
296void protocore_phase_machine_is_open(uint8_t *work);
297
298// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
299// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
300// `PhaseMachine.get(work)` resolves to a named function and becomes a DIRECT call. An extern table
301// leaves the call indirect and the symbol live at every level, -O2 -flto included.
302static const PhaseMachineNs PhaseMachine __attribute__((unused)) = {
303 .get = protocore_phase_machine_get,
304 .is = protocore_phase_machine_is,
305 .reset = protocore_phase_machine_reset,
306 .ident_done = protocore_phase_machine_ident_done,
307 .kexinit_done = protocore_phase_machine_kexinit_done,
308 .kex_done = protocore_phase_machine_kex_done,
309 .newkeys_done = protocore_phase_machine_newkeys_done,
310 .service_done = protocore_phase_machine_service_done,
311 .auth_done = protocore_phase_machine_auth_done,
312 .rekey_begin = protocore_phase_machine_rekey_begin,
313 .admits_ident = protocore_phase_machine_admits_ident,
314 .admits_kexinit = protocore_phase_machine_admits_kexinit,
315 .kexinit_needs_reply = protocore_phase_machine_kexinit_needs_reply,
316 .admits_kexdh_init = protocore_phase_machine_admits_kexdh_init,
317 .admits_newkeys = protocore_phase_machine_admits_newkeys,
318 .admits_service_request = protocore_phase_machine_admits_service_request,
319 .admits_userauth = protocore_phase_machine_admits_userauth,
320 .auth_complete = protocore_phase_machine_auth_complete,
321 .admits_rekey = protocore_phase_machine_admits_rekey,
322 .is_open = protocore_phase_machine_is_open,
323};
324
326
327#endif // PROTOCORE_ENABLE_SSH || PROTOCORE_ENABLE_SSH_CLIENT
328
329#endif // PROTOCORE_TRANSPORT_PHASE_MACHINE_H
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97