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