ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
sftp.h
Go to the documentation of this file.
1// 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 (PC_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 server/ssh_sftp.
12 *
13 * Everything is big-endian (SSH wire order). A "string" is a `uint32 length || bytes` field (not
14 * NUL-terminated). Version 3 is the de-facto standard (the OpenSSH sftp client's default).
15 *
16 * @author Douglas Quigg (dstroy0)
17 * @date 2026
18 */
19
20#ifndef PROTOCORE_SFTP_H
21#define PROTOCORE_SFTP_H
22
23#include "protocore_config.h"
24
25#if PC_ENABLE_SSH_SFTP
26
27#include <stddef.h>
28#include <stdint.h>
29
30#define PC_SFTP_VERSION 3
31
32// --- request message types (client -> server) ---
33#define PC_SSH_FXP_INIT 1
34#define PC_SSH_FXP_OPEN 3
35#define PC_SSH_FXP_CLOSE 4
36#define PC_SSH_FXP_READ 5
37#define PC_SSH_FXP_WRITE 6
38#define PC_SSH_FXP_LSTAT 7
39#define PC_SSH_FXP_FSTAT 8
40#define PC_SSH_FXP_SETSTAT 9
41#define PC_SSH_FXP_FSETSTAT 10
42#define PC_SSH_FXP_OPENDIR 11
43#define PC_SSH_FXP_READDIR 12
44#define PC_SSH_FXP_REMOVE 13
45#define PC_SSH_FXP_MKDIR 14
46#define PC_SSH_FXP_RMDIR 15
47#define PC_SSH_FXP_REALPATH 16
48#define PC_SSH_FXP_STAT 17
49#define PC_SSH_FXP_RENAME 18
50
51// --- response message types (server -> client) ---
52#define PC_SSH_FXP_VERSION 2
53#define PC_SSH_FXP_STATUS 101
54#define PC_SSH_FXP_HANDLE 102
55#define PC_SSH_FXP_DATA 103
56#define PC_SSH_FXP_NAME 104
57#define PC_SSH_FXP_ATTRS 105
58
59// --- status / error codes (PC_SSH_FXP_STATUS) ---
60#define PC_SSH_FX_OK 0
61#define PC_SSH_FX_EOF 1
62#define PC_SSH_FX_NO_SUCH_FILE 2
63#define PC_SSH_FX_PERMISSION_DENIED 3
64#define PC_SSH_FX_FAILURE 4
65#define PC_SSH_FX_BAD_MESSAGE 5
66#define PC_SSH_FX_OP_UNSUPPORTED 8
67
68// --- PC_SSH_FXP_OPEN pflags ---
69#define PC_SSH_FXF_READ 0x00000001
70#define PC_SSH_FXF_WRITE 0x00000002
71#define PC_SSH_FXF_APPEND 0x00000004
72#define PC_SSH_FXF_CREAT 0x00000008
73#define PC_SSH_FXF_TRUNC 0x00000010
74#define PC_SSH_FXF_EXCL 0x00000020
75
76// --- ATTRS flag word ---
77#define PC_SSH_FILEXFER_ATTR_SIZE 0x00000001
78#define PC_SSH_FILEXFER_ATTR_UIDGID 0x00000002
79#define PC_SSH_FILEXFER_ATTR_PERMS 0x00000004
80#define PC_SSH_FILEXFER_ATTR_ACMODTIME 0x00000008
81#define PC_SSH_FILEXFER_ATTR_EXTENDED 0x80000000
82
83// POSIX mode bits used in the permissions attr / longname (S_IFDIR / S_IFREG + rwx).
84#define PC_SFTP_S_IFDIR 0040000
85#define PC_SFTP_S_IFREG 0100000
86
87/** @brief A decoded/encoded ATTRS blob (only the v3 fields the server sets/reads). */
88struct SftpAttrs
89{
90 uint32_t flags; ///< which fields below are present (SSH_FILEXFER_ATTR_*)
91 uint64_t size; ///< file size (ATTR_SIZE)
92 uint32_t permissions; ///< POSIX mode incl. S_IFDIR/S_IFREG (ATTR_PERMISSIONS)
93 uint32_t atime; ///< access time, unix epoch (ATTR_ACMODTIME)
94 uint32_t mtime; ///< modify time, unix epoch (ATTR_ACMODTIME)
95};
96
97// --- reader: a bounds-checked cursor over a packet payload (the bytes after the 4-byte length prefix) ---
98struct SftpReader
99{
100 const uint8_t *p;
101 size_t len;
102 size_t off;
103 bool ok; ///< false once any read ran past the end (all further reads are no-ops)
104};
105
106void pc_sftp_rd_init(SftpReader *r, const uint8_t *payload, size_t len);
107uint8_t pc_sftp_rd_u8(SftpReader *r);
108uint32_t pc_sftp_rd_u32(SftpReader *r);
109uint64_t pc_sftp_rd_u64(SftpReader *r);
110/** @brief Read a `uint32 len || bytes` string as a pointer into the payload (no copy). @return r->ok. */
111bool pc_sftp_rd_string(SftpReader *r, const uint8_t **out, uint32_t *out_len);
112/** @brief Parse an ATTRS blob (only known fields kept; unknown/extended fields skipped). @return r->ok. */
113bool pc_sftp_rd_attrs(SftpReader *r, SftpAttrs *a);
114
115// --- writer: build a packet into a caller buffer; reserves the 4-byte length prefix, backfilled by finish ---
116struct SftpWriter
117{
118 uint8_t *p;
119 size_t cap;
120 size_t off; ///< current write position (starts at 4, past the reserved length prefix)
121 bool ovf; ///< set once a write would exceed cap
122};
123
124void pc_sftp_wr_init(SftpWriter *w, uint8_t *out, size_t cap);
125void pc_sftp_wr_u8(SftpWriter *w, uint8_t v);
126void pc_sftp_wr_u32(SftpWriter *w, uint32_t v);
127void pc_sftp_wr_u64(SftpWriter *w, uint64_t v);
128void pc_sftp_wr_bytes(SftpWriter *w, const void *b, size_t n);
129void pc_sftp_wr_string(SftpWriter *w, const void *s, uint32_t n); ///< uint32 len + bytes
130void pc_sftp_wr_attrs(SftpWriter *w, const SftpAttrs *a);
131/** @brief Backfill the length prefix (= off-4). @return the total packet length, or 0 on overflow. */
132size_t pc_sftp_wr_finish(SftpWriter *w);
133/** @brief Position where the next byte will be written (used to remember a patch point, e.g. a NAME count). */
134size_t pc_sftp_wr_pos(const SftpWriter *w);
135/** @brief Overwrite a big-endian uint32 already written at @p at (for backfilling a count). */
136void pc_sftp_wr_patch_u32(SftpWriter *w, size_t at, uint32_t v);
137
138// --- framing ---
139/**
140 * @brief The full length of the leading packet in @p buf (4-byte prefix + payload), or 0 if fewer than 4 bytes
141 * are present (need more) or the declared length exceeds @p max (caller drops the connection).
142 */
143size_t pc_sftp_frame_len(const uint8_t *buf, size_t have, size_t max);
144
145// --- response builders (return the total packet length written, or 0 on overflow) ---
146size_t pc_sftp_build_version(uint8_t *out, size_t cap);
147size_t pc_sftp_build_status(uint32_t id, uint32_t code, const char *msg, uint8_t *out, size_t cap);
148size_t pc_sftp_build_handle(uint32_t id, const void *handle, uint32_t hlen, uint8_t *out, size_t cap);
149size_t pc_sftp_build_attrs(uint32_t id, const SftpAttrs *a, uint8_t *out, size_t cap);
150/** @brief PC_SSH_FXP_DATA carrying @p data[0..dlen). */
151size_t pc_sftp_build_data(uint32_t id, const void *data, uint32_t dlen, uint8_t *out, size_t cap);
152/** @brief PC_SSH_FXP_NAME with one entry (filename + longname + attrs) - used by REALPATH. */
153size_t pc_sftp_build_name1(uint32_t id, const char *name, const char *longname, const SftpAttrs *a, uint8_t *out,
154 size_t cap);
155
156/**
157 * @brief Format a Unix `ls -l`-style longname for a NAME entry, e.g. "-rw-r--r-- 1 0 0 1234 Jan 1 2026 name".
158 * @return the string length written (excluding NUL), clamped to @p cap-1.
159 */
160size_t pc_sftp_format_longname(bool is_dir, uint32_t perms, uint64_t size, uint32_t mtime, const char *name, char *out,
161 size_t cap);
162
163#endif // PC_ENABLE_SSH_SFTP
164
165#endif // PROTOCORE_SFTP_H
User-facing configuration for ProtoCore.