ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
scp.cpp
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 scp.cpp
6 * @brief SCP (RCP) protocol wire codec - implementation. See scp.h.
7 */
8
10#include "shared_primitives/strbuf.h" // pc_sb frame builder
11
12#if PC_ENABLE_SSH_SCP
13
14#include <stdio.h>
15#include <string.h>
16
17namespace
18{
19// Apply one scp flag token (e.g. "-t", "-rf"): -t selects the sink role, -f the source; other letters
20// (-v/-r/-p/-d and combinations) are accepted and ignored.
21void apply_scp_flags(const char *tok, size_t tlen, ScpMode *mode)
22{
23 for (size_t k = 1; k < tlen; k++)
24 {
25 if (tok[k] == 't')
26 {
27 *mode = ScpMode::SINK;
28 }
29 else if (tok[k] == 'f')
30 {
31 *mode = ScpMode::SOURCE;
32 }
33 }
34}
35} // namespace
36
37ScpMode pc_scp_parse_cmd(const char *cmd, size_t cmd_len, char *path_out, size_t path_cap)
38{
39 if (!cmd || !path_out || path_cap == 0)
40 {
41 return ScpMode::INVALID;
42 }
43 ScpMode mode = ScpMode::INVALID;
44 const char *last_tok = nullptr; // the last non-flag token is the target path
45 size_t last_len = 0;
46 size_t i = 0;
47 while (i < cmd_len)
48 {
49 while (i < cmd_len && cmd[i] == ' ')
50 {
51 i++;
52 }
53 if (i >= cmd_len)
54 {
55 break;
56 }
57 size_t start = i;
58 while (i < cmd_len && cmd[i] != ' ')
59 {
60 i++;
61 }
62 size_t tlen = i - start;
63 if (tlen >= 2 && cmd[start] == '-')
64 {
65 apply_scp_flags(cmd + start, tlen, &mode);
66 }
67 else
68 {
69 last_tok = cmd + start; // "scp" then the path; the last one wins
70 last_len = tlen;
71 }
72 }
73 // The last_len == 0 arm below can never be true, so this line is branch-excluded. The scan above
74 // only assigns last_tok/last_len after skipping spaces and confirming i < cmd_len, then advances
75 // at least one byte before the token ends - so a recorded token is always >= 1 byte long. The
76 // other three conditions here are covered.
77 if (mode == ScpMode::INVALID || !last_tok || last_len == 0 || last_len >= path_cap) // GCOVR_EXCL_LINE
78 {
79 return ScpMode::INVALID;
80 }
81 memcpy(path_out, last_tok, last_len);
82 path_out[last_len] = '\0';
83 return mode;
84}
85
86bool pc_scp_parse_cline(const char *line, size_t len, uint32_t *mode_out, uint64_t *size_out, char *name_out,
87 size_t name_cap)
88{
89 if (!line || len < 1 || line[0] != 'C') // only plain file records (not D/E directory records)
90 {
91 return false;
92 }
93 size_t i = 1;
94
95 uint32_t mode = 0;
96 size_t ms = i;
97 while (i < len && line[i] >= '0' && line[i] <= '7')
98 {
99 mode = mode * 8 + (uint32_t)(line[i] - '0');
100 i++;
101 }
102 if (i == ms || i >= len || line[i] != ' ')
103 {
104 return false;
105 }
106 i++;
107
108 uint64_t size = 0;
109 size_t ss = i;
110 while (i < len && line[i] >= '0' && line[i] <= '9')
111 {
112 size = size * 10 + (uint64_t)(line[i] - '0');
113 i++;
114 }
115 if (i == ss || i >= len || line[i] != ' ')
116 {
117 return false;
118 }
119 i++;
120
121 size_t ns = i;
122 while (i < len && line[i] != '\n' && line[i] != '\0')
123 {
124 i++;
125 }
126 size_t nlen = i - ns;
127 if (nlen == 0 || nlen >= name_cap)
128 {
129 return false;
130 }
131 memcpy(name_out, line + ns, nlen);
132 name_out[nlen] = '\0';
133
134 if (mode_out)
135 {
136 *mode_out = mode;
137 }
138 if (size_out)
139 {
140 *size_out = size;
141 }
142 return true;
143}
144
145size_t pc_scp_build_cline(uint32_t mode, uint64_t size, const char *name, char *out, size_t cap)
146{
147 pc_sb sb_out = {out, cap, 0, true};
148 pc_sb_put(&sb_out, "C");
149 pc_sb_uint(&sb_out, (uint64_t)((unsigned)(mode & 07777)), 8, 4);
150 pc_sb_put(&sb_out, " ");
151 pc_sb_u64(&sb_out, (uint64_t)((unsigned long long)size));
152 pc_sb_put(&sb_out, " ");
153 pc_sb_put(&sb_out, name);
154 pc_sb_put(&sb_out, "\n");
155 int n = (int)pc_sb_finish(&sb_out);
156 // The n <= 0 arm can never be true, so this line is branch-excluded: snprintf returns the length
157 // it WOULD have written (never negative here - no encoding can fail on this format), and that
158 // format always emits at least "C0000 0 \n". Only the truncation arm is reachable, and it is
159 // covered. The guard stays as defense against a non-conforming libc.
160 if (n <= 0 || (size_t)n >= cap) // GCOVR_EXCL_LINE
161 {
162 return 0;
163 }
164 return (size_t)n;
165}
166
167#endif // PC_ENABLE_SSH_SCP
SCP (RCP) protocol wire codec - the pure, host-testable half of the SCP-over-SSH server (PC_ENABLE_SS...
Bounded no-heap string builder that fails closed on overflow (one shared copy).
size_t pc_sb_finish(pc_sb *b)
NUL-terminate and return the built length, or 0 if the build overflowed.
Definition strbuf.h:648
void pc_sb_uint(pc_sb *b, uint64_t v, unsigned base, unsigned min_digits)
Append v in base (10 or 16), left-padded with '0' to at least min_digits.
Definition strbuf.h:207
void pc_sb_u64(pc_sb *b, uint64_t v)
Append v as decimal (64-bit).
Definition strbuf.h:309
void pc_sb_put(pc_sb *b, const char *s)
Append NUL-terminated s; leaves the buffer untouched and clears ok if it would not fit.
Definition strbuf.h:60
Bump-append target; ok latches false once an append would overflow cap.
Definition strbuf.h:30