ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
sftp.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 sftp.h
6 * @brief SFTP protocol v3 wire codec (SSH_FXP_*, draft-ietf-secsh-filexfer-02) - the pure, host-testable
7 * half of the SSH SFTP subsystem (PROTOCORE_ENABLE_SSH_SFTP).
8 *
9 * SFTP runs as an SSH "subsystem" over a session channel: length-prefixed packets, each `uint32 length ||
10 * byte type || …`. This file parses request packets and builds response packets into caller buffers - no
11 * filesystem, no SSH, no Arduino, zero heap. The fs::FS binding + the channel glue live in
12 * network_drivers/application/sftp/ssh_sftp.
13 *
14 * Everything is big-endian (SSH wire order). A "string" is a `uint32 length || bytes` field (not
15 * NUL-terminated). Version 3 is the de-facto standard (the OpenSSH sftp client's default).
16 *
17 * @author Douglas Quigg (dstroy0)
18 * @date 2026
19 */
20
21#ifndef PROTOCORE_SFTP_H
22#define PROTOCORE_SFTP_H
23
24#include "protocore_config.h" // the entry point: protocore_types.h for the widths
25
26#if PROTOCORE_ENABLE_SSH_SFTP
27
29
30// This module holds nothing between calls, so it carves no borrow and states none. An entry
31// takes one all the same, and never reads it, so every namespace in the tree is invoked the
32// same way.
33
34#define PROTOCORE_SFTP_VERSION 3
35
36// --- request message types (client -> server) ---
37#define PROTOCORE_SSH_FXP_INIT 1
38#define PROTOCORE_SSH_FXP_OPEN 3
39#define PROTOCORE_SSH_FXP_CLOSE 4
40#define PROTOCORE_SSH_FXP_READ 5
41#define PROTOCORE_SSH_FXP_WRITE 6
42#define PROTOCORE_SSH_FXP_LSTAT 7
43#define PROTOCORE_SSH_FXP_FSTAT 8
44#define PROTOCORE_SSH_FXP_SETSTAT 9
45#define PROTOCORE_SSH_FXP_FSETSTAT 10
46#define PROTOCORE_SSH_FXP_OPENDIR 11
47#define PROTOCORE_SSH_FXP_READDIR 12
48#define PROTOCORE_SSH_FXP_REMOVE 13
49#define PROTOCORE_SSH_FXP_MKDIR 14
50#define PROTOCORE_SSH_FXP_RMDIR 15
51#define PROTOCORE_SSH_FXP_REALPATH 16
52#define PROTOCORE_SSH_FXP_STAT 17
53#define PROTOCORE_SSH_FXP_RENAME 18
54
55// --- response message types (server -> client) ---
56#define PROTOCORE_SSH_FXP_VERSION 2
57#define PROTOCORE_SSH_FXP_STATUS 101
58#define PROTOCORE_SSH_FXP_HANDLE 102
59#define PROTOCORE_SSH_FXP_DATA 103
60#define PROTOCORE_SSH_FXP_NAME 104
61#define PROTOCORE_SSH_FXP_ATTRS 105
62
63// --- status / error codes (PROTOCORE_SSH_FXP_STATUS) ---
64#define PROTOCORE_SSH_FX_OK 0
65#define PROTOCORE_SSH_FX_EOF 1
66#define PROTOCORE_SSH_FX_NO_SUCH_FILE 2
67#define PROTOCORE_SSH_FX_PERMISSION_DENIED 3
68#define PROTOCORE_SSH_FX_FAILURE 4
69#define PROTOCORE_SSH_FX_BAD_MESSAGE 5
70#define PROTOCORE_SSH_FX_OP_UNSUPPORTED 8
71
72// --- PROTOCORE_SSH_FXP_OPEN pflags ---
73#define PROTOCORE_SSH_FXF_READ 0x00000001
74#define PROTOCORE_SSH_FXF_WRITE 0x00000002
75#define PROTOCORE_SSH_FXF_APPEND 0x00000004
76#define PROTOCORE_SSH_FXF_CREAT 0x00000008
77#define PROTOCORE_SSH_FXF_TRUNC 0x00000010
78#define PROTOCORE_SSH_FXF_EXCL 0x00000020
79
80// --- ATTRS flag word ---
81#define PROTOCORE_SSH_FILEXFER_ATTR_SIZE 0x00000001
82#define PROTOCORE_SSH_FILEXFER_ATTR_UIDGID 0x00000002
83#define PROTOCORE_SSH_FILEXFER_ATTR_PERMS 0x00000004
84#define PROTOCORE_SSH_FILEXFER_ATTR_ACMODTIME 0x00000008
85#define PROTOCORE_SSH_FILEXFER_ATTR_EXTENDED 0x80000000
86
87// POSIX mode bits used in the permissions attr / longname (S_IFDIR / S_IFREG + rwx).
88#define PROTOCORE_SFTP_S_IFDIR 0040000
89#define PROTOCORE_SFTP_S_IFREG 0100000
90
91/** @brief A decoded/encoded ATTRS blob (only the v3 fields the server sets/reads). */
92typedef struct
93{
94 uint32_t flags; ///< which fields below are present (SSH_FILEXFER_ATTR_*)
95 uint64_t size; ///< file size (ATTR_SIZE)
96 uint32_t permissions; ///< POSIX mode incl. S_IFDIR/S_IFREG (ATTR_PERMISSIONS)
97 uint32_t atime; ///< access time, unix epoch (ATTR_ACMODTIME)
98 uint32_t mtime; ///< modify time, unix epoch (ATTR_ACMODTIME)
99} SftpAttrs;
100
101typedef struct
102{
103 const uint8_t *p;
104 size_t len;
105 size_t off;
106 proto_bool ok; ///< false once any read ran past the end (all further reads are no-ops)
107} SftpReader;
108
109typedef struct
110{
111 uint8_t *p;
112 size_t cap;
113 size_t off; ///< current write position (starts at 4, past the reserved length prefix)
114 proto_bool ovf; ///< set once a write would exceed cap
115} SftpWriter;
116
117/** @brief What rd_init takes: r, payload, len. */
118typedef struct
119{
120 SftpReader *r;
121 const uint8_t *payload;
122 size_t len;
123} SftpRdInitArgs;
124
125/** @brief What rd_u8 takes: r. */
126typedef struct
127{
128 SftpReader *r;
129} SftpRdU8Args;
130
131/** @brief What rd_u32 takes: r. */
132typedef struct
133{
134 SftpReader *r;
135} SftpRdU32Args;
136
137/** @brief What rd_u64 takes: r. */
138typedef struct
139{
140 SftpReader *r;
141} SftpRdU64Args;
142
143/** @brief What rd_string takes: r, out, out_len. */
144typedef struct
145{
146 SftpReader *r;
147 const uint8_t **out;
148 uint32_t *out_len;
149} SftpRdStringArgs;
150
151/** @brief What rd_attrs takes: r, a. */
152typedef struct
153{
154 SftpReader *r;
155 SftpAttrs *a;
156} SftpRdAttrsArgs;
157
158/** @brief What wr_init takes: w, out, cap. */
159typedef struct
160{
161 SftpWriter *w;
162 uint8_t *out;
163 size_t cap;
164} SftpWrInitArgs;
165
166/** @brief What wr_u8 takes: w, v. */
167typedef struct
168{
169 SftpWriter *w;
170 uint8_t v;
171} SftpWrU8Args;
172
173/** @brief What wr_u32 takes: w, v. */
174typedef struct
175{
176 SftpWriter *w;
177 uint32_t v;
178} SftpWrU32Args;
179
180/** @brief What wr_u64 takes: w, v. */
181typedef struct
182{
183 SftpWriter *w;
184 uint64_t v;
185} SftpWrU64Args;
186
187/** @brief What wr_bytes takes: w, b, n. */
188typedef struct
189{
190 SftpWriter *w;
191 const void *b;
192 size_t n;
193} SftpWrBytesArgs;
194
195/** @brief What wr_string takes: w, s, n. */
196typedef struct
197{
198 SftpWriter *w;
199 const void *s;
200 uint32_t n;
201} SftpWrStringArgs;
202
203/** @brief What wr_attrs takes: w, a. */
204typedef struct
205{
206 SftpWriter *w;
207 const SftpAttrs *a;
208} SftpWrAttrsArgs;
209
210/** @brief What wr_finish takes: w. */
211typedef struct
212{
213 SftpWriter *w;
214} SftpWrFinishArgs;
215
216/** @brief What wr_pos takes: w. */
217typedef struct
218{
219 const SftpWriter *w;
220} SftpWrPosArgs;
221
222/** @brief What wr_patch_u32 takes: w, at, v. */
223typedef struct
224{
225 SftpWriter *w;
226 size_t at;
227 uint32_t v;
228} SftpWrPatchU32Args;
229
230/** @brief What frame_len takes: buf, have, max. */
231typedef struct
232{
233 const uint8_t *buf;
234 size_t have;
235 size_t max;
236} SftpFrameLenArgs;
237
238/** @brief What build_version takes: out, cap. */
239typedef struct
240{
241 uint8_t *out;
242 size_t cap;
243} SftpBuildVersionArgs;
244
245/** @brief What build_status takes: id, code, msg, out, cap. */
246typedef struct
247{
248 uint32_t id;
249 uint32_t code;
250 const char *msg;
251 uint8_t *out;
252 size_t cap;
253} SftpBuildStatusArgs;
254
255/** @brief What build_handle takes: id, handle, hlen, out, cap. */
256typedef struct
257{
258 uint32_t id;
259 const void *handle;
260 uint32_t hlen;
261 uint8_t *out;
262 size_t cap;
263} SftpBuildHandleArgs;
264
265/** @brief What build_attrs takes: id, a, out, cap. */
266typedef struct
267{
268 uint32_t id;
269 const SftpAttrs *a;
270 uint8_t *out;
271 size_t cap;
272} SftpBuildAttrsArgs;
273
274/** @brief What build_data takes: id, data, dlen, out, cap. */
275typedef struct
276{
277 uint32_t id;
278 const void *data;
279 uint32_t dlen;
280 uint8_t *out;
281 size_t cap;
282} SftpBuildDataArgs;
283
284/** @brief What build_name1 takes: id, name, longname, a, out, cap. */
285typedef struct
286{
287 uint32_t id;
288 const char *name;
289 const char *longname;
290 const SftpAttrs *a;
291 uint8_t *out;
292 size_t cap;
293} SftpBuildName1Args;
294
295/** @brief What format_longname takes: is_dir, perms, size, mtime, ... */
296typedef struct
297{
298 proto_bool is_dir;
299 uint32_t perms;
300 uint64_t size;
301 uint32_t mtime;
302 const char *name;
303 char *out;
304 size_t cap;
305} SftpFormatLongnameArgs;
306
307/**
308 * @brief SFTP protocol v3 wire codec (SSH_FXP_*, draft-ietf-secsh-filexfer-02) - the pure, host-testable half of the
309 * SSH SFTP subsystem (PROTOCORE_ENABLE_SSH_SFTP).
310 *
311 * A caller sets the members a call takes, invokes it through ::Sftp with the bytes it runs
312 * out of, and reads the outcome off the same handle.
313 *
314 * Sftp.rd_init_args.r = ...;
315 * Sftp.rd_init_args.payload = ...;
316 * Sftp.rd_init_args.len = ...;
317 * Sftp.rd_init(work);
318 *
319 * @var SftpNs::rd_init_args what rd_init takes: r, payload, len
320 * @var SftpNs::rd_u8_args what rd_u8 takes: r
321 * @var SftpNs::rd_u32_args what rd_u32 takes: r
322 * @var SftpNs::rd_u64_args what rd_u64 takes: r
323 * @var SftpNs::rd_string_args what rd_string takes: r, out, out_len
324 * @var SftpNs::rd_attrs_args what rd_attrs takes: r, a
325 * @var SftpNs::wr_init_args what wr_init takes: w, out, cap
326 * @var SftpNs::wr_u8_args what wr_u8 takes: w, v
327 * @var SftpNs::wr_u32_args what wr_u32 takes: w, v
328 * @var SftpNs::wr_u64_args what wr_u64 takes: w, v
329 * @var SftpNs::wr_bytes_args what wr_bytes takes: w, b, n
330 * @var SftpNs::wr_string_args what wr_string takes: w, s, n
331 * @var SftpNs::wr_attrs_args what wr_attrs takes: w, a
332 * @var SftpNs::wr_finish_args what wr_finish takes: w
333 * @var SftpNs::wr_pos_args what wr_pos takes: w
334 * @var SftpNs::wr_patch_u32_args what wr_patch_u32 takes: w, at, v
335 * @var SftpNs::frame_len_args what frame_len takes: buf, have, max
336 * @var SftpNs::build_version_args what build_version takes: out, cap
337 * @var SftpNs::build_status_args what build_status takes: id, code, msg, out, cap
338 * @var SftpNs::build_handle_args what build_handle takes: id, handle, hlen, out, cap
339 * @var SftpNs::build_attrs_args what build_attrs takes: id, a, out, cap
340 * @var SftpNs::build_data_args what build_data takes: id, data, dlen, out, cap
341 * @var SftpNs::build_name1_args what build_name1 takes: id, name, longname, a, out, cap
342 * @var SftpNs::format_longname_args what format_longname takes: is_dir, perms, size, mtime,
343 * @var SftpNs::ok a call's true/false outcome
344 * @var SftpNs::value the value a call reports
345 * @var SftpNs::u32 what a call reports
346 * @var SftpNs::u64 what a call reports
347 * @var SftpNs::n the string length written (excluding NUL), clamped to cap-1
348 * @var SftpNs::rd_init rd_init
349 * @var SftpNs::rd_u8 rd_u8
350 * @var SftpNs::rd_u32 rd_u32
351 * @var SftpNs::rd_u64 rd_u64
352 * @var SftpNs::rd_string read a `uint32 len || bytes` string as a pointer into the payload ...
353 * @var SftpNs::rd_attrs parse an ATTRS blob (only known fields kept; unknown/extended ...
354 * @var SftpNs::wr_init wr_init
355 * @var SftpNs::wr_u8 wr_u8
356 * @var SftpNs::wr_u32 wr_u32
357 * @var SftpNs::wr_u64 wr_u64
358 * @var SftpNs::wr_bytes wr_bytes
359 * @var SftpNs::wr_string wr_string
360 * @var SftpNs::wr_attrs wr_attrs
361 * @var SftpNs::wr_finish backfill the length prefix (= off-4). the total packet length, or 0 ...
362 * @var SftpNs::wr_pos position where the next byte will be written (used to remember a ...
363 * @var SftpNs::wr_patch_u32 overwrite a big-endian uint32 already written at at (for ...
364 * @var SftpNs::frame_len the full length of the leading packet in buf (4-byte prefix + ...
365 * @var SftpNs::build_version build_version
366 * @var SftpNs::build_status build_status
367 * @var SftpNs::build_handle build_handle
368 * @var SftpNs::build_attrs build_attrs
369 * @var SftpNs::build_data PROTOCORE_SSH_FXP_DATA carrying data[0..dlen)
370 * @var SftpNs::build_name1 PROTOCORE_SSH_FXP_NAME with one entry (filename + longname + attrs) ...
371 * @var SftpNs::format_longname format a Unix `ls -l`-style longname for a NAME entry, e.g. ...
372 *
373 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
374 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
375 * a caller drives every namespace the same way.
376 */
377typedef struct
378{
379 SftpRdInitArgs rd_init_args;
380 SftpRdU8Args rd_u8_args;
381 SftpRdU32Args rd_u32_args;
382 SftpRdU64Args rd_u64_args;
383 SftpRdStringArgs rd_string_args;
384 SftpRdAttrsArgs rd_attrs_args;
385 SftpWrInitArgs wr_init_args;
386 SftpWrU8Args wr_u8_args;
387 SftpWrU32Args wr_u32_args;
388 SftpWrU64Args wr_u64_args;
389 SftpWrBytesArgs wr_bytes_args;
390 SftpWrStringArgs wr_string_args;
391 SftpWrAttrsArgs wr_attrs_args;
392 SftpWrFinishArgs wr_finish_args;
393 SftpWrPosArgs wr_pos_args;
394 SftpWrPatchU32Args wr_patch_u32_args;
395 SftpFrameLenArgs frame_len_args;
396 SftpBuildVersionArgs build_version_args;
397 SftpBuildStatusArgs build_status_args;
398 SftpBuildHandleArgs build_handle_args;
399 SftpBuildAttrsArgs build_attrs_args;
400 SftpBuildDataArgs build_data_args;
401 SftpBuildName1Args build_name1_args;
402 SftpFormatLongnameArgs format_longname_args;
403 proto_bool ok;
404 uint8_t value;
405 uint32_t u32;
406 uint64_t u64;
407 size_t n;
408} SftpVars;
409
410/** @brief The operands and the outcome. */
411extern SftpVars SftpV;
412
413/** @brief The entries. */
414typedef struct
415{
416 void (*const rd_init)(uint8_t *work);
417 void (*const rd_u8)(uint8_t *work);
418 void (*const rd_u32)(uint8_t *work);
419 void (*const rd_u64)(uint8_t *work);
420 void (*const rd_string)(uint8_t *work);
421 void (*const rd_attrs)(uint8_t *work);
422 void (*const wr_init)(uint8_t *work);
423 void (*const wr_u8)(uint8_t *work);
424 void (*const wr_u32)(uint8_t *work);
425 void (*const wr_u64)(uint8_t *work);
426 void (*const wr_bytes)(uint8_t *work);
427 void (*const wr_string)(uint8_t *work);
428 void (*const wr_attrs)(uint8_t *work);
429 void (*const wr_finish)(uint8_t *work);
430 void (*const wr_pos)(uint8_t *work);
431 void (*const wr_patch_u32)(uint8_t *work);
432 void (*const frame_len)(uint8_t *work);
433 void (*const build_version)(uint8_t *work);
434 void (*const build_status)(uint8_t *work);
435 void (*const build_handle)(uint8_t *work);
436 void (*const build_attrs)(uint8_t *work);
437 void (*const build_data)(uint8_t *work);
438 void (*const build_name1)(uint8_t *work);
439 void (*const format_longname)(uint8_t *work);
440} SftpNs;
441
442// What the table binds, defined once in the .c and taking one parameter each: everything
443// else an entry needs is an operand in SftpV or a region of the borrow at a fixed offset.
444void protocore_sftp_rd_init(uint8_t *work);
445void protocore_sftp_rd_u8(uint8_t *work);
446void protocore_sftp_rd_u32(uint8_t *work);
447void protocore_sftp_rd_u64(uint8_t *work);
448void protocore_sftp_rd_string(uint8_t *work);
449void protocore_sftp_rd_attrs(uint8_t *work);
450void protocore_sftp_wr_init(uint8_t *work);
451void protocore_sftp_wr_u8(uint8_t *work);
452void protocore_sftp_wr_u32(uint8_t *work);
453void protocore_sftp_wr_u64(uint8_t *work);
454void protocore_sftp_wr_bytes(uint8_t *work);
455void protocore_sftp_wr_string(uint8_t *work);
456void protocore_sftp_wr_attrs(uint8_t *work);
457void protocore_sftp_wr_finish(uint8_t *work);
458void protocore_sftp_wr_pos(uint8_t *work);
459void protocore_sftp_wr_patch_u32(uint8_t *work);
460void protocore_sftp_frame_len(uint8_t *work);
461void protocore_sftp_build_version(uint8_t *work);
462void protocore_sftp_build_status(uint8_t *work);
463void protocore_sftp_build_handle(uint8_t *work);
464void protocore_sftp_build_attrs(uint8_t *work);
465void protocore_sftp_build_data(uint8_t *work);
466void protocore_sftp_build_name1(uint8_t *work);
467void protocore_sftp_format_longname(uint8_t *work);
468
469// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
470// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
471// `Sftp.rd_init(work)` resolves to a named function and becomes a DIRECT call. An extern table
472// leaves the call indirect and the symbol live at every level, -O2 -flto included.
473static const SftpNs Sftp __attribute__((unused)) = {
474 .rd_init = protocore_sftp_rd_init,
475 .rd_u8 = protocore_sftp_rd_u8,
476 .rd_u32 = protocore_sftp_rd_u32,
477 .rd_u64 = protocore_sftp_rd_u64,
478 .rd_string = protocore_sftp_rd_string,
479 .rd_attrs = protocore_sftp_rd_attrs,
480 .wr_init = protocore_sftp_wr_init,
481 .wr_u8 = protocore_sftp_wr_u8,
482 .wr_u32 = protocore_sftp_wr_u32,
483 .wr_u64 = protocore_sftp_wr_u64,
484 .wr_bytes = protocore_sftp_wr_bytes,
485 .wr_string = protocore_sftp_wr_string,
486 .wr_attrs = protocore_sftp_wr_attrs,
487 .wr_finish = protocore_sftp_wr_finish,
488 .wr_pos = protocore_sftp_wr_pos,
489 .wr_patch_u32 = protocore_sftp_wr_patch_u32,
490 .frame_len = protocore_sftp_frame_len,
491 .build_version = protocore_sftp_build_version,
492 .build_status = protocore_sftp_build_status,
493 .build_handle = protocore_sftp_build_handle,
494 .build_attrs = protocore_sftp_build_attrs,
495 .build_data = protocore_sftp_build_data,
496 .build_name1 = protocore_sftp_build_name1,
497 .format_longname = protocore_sftp_format_longname,
498};
499
501
502#endif // PROTOCORE_ENABLE_SSH_SFTP
503
504#endif // PROTOCORE_SFTP_H
#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