ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
snmp_ber.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 pc_snmp_ber.cpp
6 * @brief ASN.1 BER codec implementation for the SNMP agent.
7 */
8
10
11#if PC_ENABLE_SNMP
12
13#include <string.h>
14
15// ---------------------------------------------------------------------------
16// Encoder
17// ---------------------------------------------------------------------------
18
19void pc_ber_enc_init(BerEnc *e, uint8_t *buf, size_t cap)
20{
21 e->buf = buf;
22 e->cap = cap;
23 e->len = 0;
24 e->ok = (buf != nullptr && cap > 0);
25}
26
27static void enc_byte(BerEnc *e, uint8_t b)
28{
29 if (!e->ok)
30 {
31 return;
32 }
33 if (e->len >= e->cap)
34 {
35 e->ok = false;
36 return;
37 }
38 e->buf[e->len++] = b;
39}
40
41static void enc_bytes(BerEnc *e, const uint8_t *p, size_t n)
42{
43 for (size_t i = 0; i < n; i++)
44 {
45 enc_byte(e, p[i]);
46 }
47}
48
49// Definite length in minimal form (short < 128, else long form).
50static void enc_len(BerEnc *e, size_t length)
51{
52 if (length < 0x80)
53 {
54 enc_byte(e, (uint8_t)length);
55 return;
56 }
57 uint8_t tmp[4];
58 int k = 0;
59 size_t v = length;
60 while (v)
61 {
62 tmp[k++] = (uint8_t)(v & 0xFF);
63 v >>= 8;
64 }
65 enc_byte(e, (uint8_t)(0x80 | k));
66 for (int i = k - 1; i >= 0; i--)
67 {
68 enc_byte(e, tmp[i]);
69 }
70}
71
72bool pc_ber_put_tlv(BerEnc *e, uint8_t tag, const uint8_t *val, size_t n)
73{
74 enc_byte(e, tag);
75 enc_len(e, n);
76 enc_bytes(e, val, n);
77 return e->ok;
78}
79
80bool pc_ber_put_raw(BerEnc *e, const uint8_t *bytes, size_t n)
81{
82 enc_bytes(e, bytes, n);
83 return e->ok;
84}
85
86bool pc_ber_put_integer(BerEnc *e, long v)
87{
88 // Minimal two's-complement encoding.
89 uint8_t tmp[8];
90 int k = 0;
91 // Always emit at least one byte; strip redundant leading 0x00 / 0xFF while
92 // preserving sign.
93 long val = v;
94 do
95 {
96 tmp[k++] = (uint8_t)(val & 0xFF);
97 val >>= 8;
98 // GCOVR_EXCL_START the k < sizeof(tmp) bound can never fail: after sizeof(long) shifts val is 0 (v >= 0,
99 // so the top byte's sign bit is clear) or -1 (v < 0, sign bit set), and either way the first operand has
100 // already ended the loop. It is a defensive bound on the tmp[] write, not a reachable exit.
101 } while (!((val == 0 && !(tmp[k - 1] & 0x80)) || (val == -1 && (tmp[k - 1] & 0x80))) && k < (int)sizeof(tmp));
102 // GCOVR_EXCL_STOP
103
104 enc_byte(e, (uint8_t)SnmpTag::BER_INTEGER);
105 enc_len(e, (size_t)k);
106 for (int i = k - 1; i >= 0; i--)
107 {
108 enc_byte(e, tmp[i]);
109 }
110 return e->ok;
111}
112
113bool pc_ber_put_uint(BerEnc *e, uint8_t tag, uint32_t v)
114{
115 // Non-negative integer: big-endian, minimal, with a leading 0x00 if the top
116 // bit would otherwise make it look negative.
117 uint8_t tmp[5];
118 int k = 0;
119 uint32_t val = v;
120 do
121 {
122 tmp[k++] = (uint8_t)(val & 0xFF);
123 val >>= 8;
124 // val is uint32_t, so after 4 shifts it is 0 and `val` has already ended the loop: the k < 4
125 // bound is a defensive cap on the tmp[] write, never a reachable exit.
126 } while (val && k < 4); // GCOVR_EXCL_LINE k < 4 can never be false (see above)
127 if (tmp[k - 1] & 0x80)
128 {
129 tmp[k++] = 0x00; // keep it positive
130 }
131
132 enc_byte(e, tag);
133 enc_len(e, (size_t)k);
134 for (int i = k - 1; i >= 0; i--)
135 {
136 enc_byte(e, tmp[i]);
137 }
138 return e->ok;
139}
140
141bool pc_ber_put_octet_string(BerEnc *e, uint8_t tag, const uint8_t *d, size_t n)
142{
143 return pc_ber_put_tlv(e, tag, d, n);
144}
145
146bool pc_ber_put_null(BerEnc *e)
147{
148 enc_byte(e, (uint8_t)SnmpTag::BER_NULL);
149 enc_byte(e, 0x00);
150 return e->ok;
151}
152
153// Emit @p v as base-128, big-endian, high bit set on all but the last byte, into tmp[*t] bounded by cap.
154static void oid_emit_arc(uint8_t *tmp, size_t cap, size_t *t, uint32_t v, BerEnc *e)
155{
156 uint8_t b[5];
157 int k = 0;
158 do
159 {
160 b[k++] = (uint8_t)(v & 0x7F);
161 v >>= 7;
162 } while (v);
163 for (int i = k - 1; i >= 0; i--)
164 {
165 uint8_t byte = b[i];
166 if (i != 0)
167 {
168 byte |= 0x80;
169 }
170 if (*t < cap)
171 {
172 tmp[(*t)++] = byte;
173 }
174 else
175 {
176 e->ok = false;
177 }
178 }
179}
180
181bool pc_ber_put_oid(BerEnc *e, const uint32_t *arcs, size_t n)
182{
183 if (n < 2)
184 {
185 e->ok = false;
186 return false;
187 }
188 uint8_t tmp[SNMP_MAX_OID_LEN * 5];
189 size_t t = 0;
190 // First two arcs combine into one byte/value: 40*arc0 + arc1.
191 uint32_t first = 40u * arcs[0] + arcs[1];
192 oid_emit_arc(tmp, sizeof(tmp), &t, first, e);
193 for (size_t i = 2; i < n; i++)
194 {
195 oid_emit_arc(tmp, sizeof(tmp), &t, arcs[i], e);
196 }
197 return pc_ber_put_tlv(e, (uint8_t)SnmpTag::BER_OID, tmp, t);
198}
199
200size_t pc_ber_seq_begin(BerEnc *e, uint8_t tag)
201{
202 enc_byte(e, tag);
203 size_t token = e->len; // position of the (reserved) length field
204 enc_byte(e, 0x82); // long-form, 2 length bytes - back-patched at close
205 enc_byte(e, 0x00);
206 enc_byte(e, 0x00);
207 return token;
208}
209
210void pc_ber_seq_end(BerEnc *e, size_t token)
211{
212 if (!e->ok)
213 {
214 return;
215 }
216 size_t content = e->len - (token + 3);
217 if (content > 0xFFFF)
218 {
219 e->ok = false;
220 return;
221 }
222 e->buf[token] = 0x82;
223 e->buf[token + 1] = (uint8_t)((content >> 8) & 0xFF);
224 e->buf[token + 2] = (uint8_t)(content & 0xFF);
225}
226
227// ---------------------------------------------------------------------------
228// Decoder
229// ---------------------------------------------------------------------------
230
231void pc_ber_dec_init(BerDec *d, const uint8_t *buf, size_t len)
232{
233 d->buf = buf;
234 d->len = len;
235 d->pos = 0;
236 d->ok = (buf != nullptr);
237}
238
239bool pc_ber_read_header(BerDec *d, uint8_t *tag, size_t *length)
240{
241 if (!d->ok || d->pos >= d->len)
242 {
243 d->ok = false;
244 return false;
245 }
246 *tag = d->buf[d->pos++];
247
248 if (d->pos >= d->len)
249 {
250 d->ok = false;
251 return false;
252 }
253 uint8_t l0 = d->buf[d->pos++];
254 size_t length_val;
255 if (l0 < 0x80)
256 {
257 length_val = l0;
258 }
259 else
260 {
261 int k = l0 & 0x7F;
262 if (k == 0 || k > 4 || d->pos + (size_t)k > d->len)
263 {
264 d->ok = false;
265 return false;
266 }
267 length_val = 0;
268 for (int i = 0; i < k; i++)
269 {
270 length_val = (length_val << 8) | d->buf[d->pos++];
271 }
272 }
273 // Wrap-safe bound: a 4-octet long-form length can be up to 0xFFFFFFFF, so on a 32-bit
274 // target d->pos + length_val would overflow and slip under d->len, letting an
275 // attacker-supplied length past the check. Compare against the remaining capacity
276 // instead (d->pos <= d->len holds here: the count-byte check above bounded d->pos).
277 if (length_val > d->len - d->pos)
278 {
279 d->ok = false;
280 return false;
281 }
282 *length = length_val;
283 return true;
284}
285
286bool pc_ber_read_integer(BerDec *d, long *out)
287{
288 uint8_t tag;
289 size_t len;
290 if (!pc_ber_read_header(d, &tag, &len) || tag != (uint8_t)SnmpTag::BER_INTEGER || len == 0 || len > 8)
291 {
292 d->ok = false;
293 return false;
294 }
295 // Sign-extend from the first byte. Accumulate in unsigned: left-shifting a negative signed
296 // value is undefined behavior; the final reinterpret yields the two's-complement result.
297 uint64_t uv = (d->buf[d->pos] & 0x80) ? ~(uint64_t)0 : 0;
298 for (size_t i = 0; i < len; i++)
299 {
300 uv = (uv << 8) | d->buf[d->pos + i];
301 }
302 d->pos += len;
303 *out = (long)uv;
304 return true;
305}
306
307bool pc_ber_read_oid(BerDec *d, uint32_t *arcs, size_t max, size_t *n)
308{
309 uint8_t tag;
310 size_t len;
311 if (!pc_ber_read_header(d, &tag, &len) || tag != (uint8_t)SnmpTag::BER_OID || len == 0 || max < 2)
312 {
313 d->ok = false;
314 return false;
315 }
316 const uint8_t *p = d->buf + d->pos;
317 size_t count = 0;
318 uint32_t acc = 0;
319 bool first_done = false;
320 // X.690 8.19: each subidentifier is base-128 (high bit = continuation). The FIRST
321 // subidentifier is itself multi-octet-capable and encodes 40*arc0 + arc1.
322 for (size_t i = 0; i < len; i++)
323 {
324 acc = (acc << 7) | (uint32_t)(p[i] & 0x7F);
325 if (p[i] & 0x80)
326 {
327 continue; // more octets in this subidentifier
328 }
329 if (!first_done)
330 {
331 uint32_t arc0 = acc / 40u;
332 if (arc0 > 2u)
333 {
334 arc0 = 2u; // arc0 is 0..2; the remainder goes to arc1 (may exceed 39)
335 }
336 arcs[count++] = arc0; // count==0 here, max>=2 guaranteed above
337 arcs[count++] = acc - 40u * arc0;
338 first_done = true;
339 }
340 else
341 {
342 if (count >= max)
343 {
344 d->ok = false;
345 return false;
346 }
347 arcs[count++] = acc;
348 }
349 acc = 0;
350 }
351 d->pos += len;
352 *n = count;
353 return true;
354}
355
356bool pc_ber_skip(BerDec *d, size_t length)
357{
358 if (!d->ok || d->pos > d->len || length > d->len - d->pos) // wrap-safe (see pc_ber_read_header)
359 {
360 d->ok = false;
361 return false;
362 }
363 d->pos += length;
364 return true;
365}
366
367#endif // PC_ENABLE_SNMP
#define SNMP_MAX_OID_LEN
Maximum sub-identifiers (arcs) in an SNMP object identifier.