ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
snmp_ber.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 snmp_ber.h
6 * @brief The SNMP serialization: ASN.1 Basic Encoding Rules over a caller buffer.
7 *
8 * RFC 3417 sec 3.1: "Each instance of a message is serialized (i.e., encoded according to the
9 * convention of [BER]) onto a single UDP over IPv4 datagram, using the algorithm specified in
10 * Section 8." The encoding rules themselves are not an IETF RFC: they are ITU-T Recommendation
11 * X.690 (ISO/IEC 8825-1), which RFC 3417 cites as [BER].
12 *
13 * RFC 3417 sec 8 states the two limits this codec is built to. Item (1): "When encoding the
14 * length field, only the definite form is used; use of the indefinite form encoding is
15 * prohibited. Note that when using the definite-long form, it is permissible to use more than the
16 * minimum number of length octets necessary to encode the length field." A constructed type
17 * therefore opens with a reserved 3-octet definite-long length that is back-patched at close, so
18 * no value is buffered and no octet is shifted. Item (2): simple types are encoded primitive, and
19 * the constructed form is used only for SEQUENCE.
20 *
21 * The tags cover exactly what SNMP puts on the wire: the ASN.1 simple types, the SMIv2
22 * application-wide types (RFC 2578 sec 7.1.5 through 7.1.10), the variable-binding exception
23 * markers and the context-specific PDU tags of the RFC 3416 sec 3 ASN.1.
24 *
25 * Encoder and decoder both run over caller-provided fixed buffers, so the module holds nothing
26 * between calls and is unit-testable with no network stack under it.
27 *
28 * @author Douglas Quigg (dstroy0)
29 * @date 2026
30 */
31
32#ifndef PROTOCORE_SNMP_BER_H
33#define PROTOCORE_SNMP_BER_H
34
35#include "protocore_config.h" // the entry point: protocore_types.h for the widths
36
37#if PROTOCORE_ENABLE_SNMP
38
40
41/** @brief The identifier octets SNMP puts on the wire. */
42typedef enum PROTO_ENUM_PACKED
43{
44 // ASN.1 simple types, encoded primitive (RFC 3417 sec 8 item 2).
45 SNMP_TAG_BER_INTEGER = 0x02,
46 SNMP_TAG_BER_OCTET_STRING = 0x04,
47 SNMP_TAG_BER_NULL = 0x05,
48 SNMP_TAG_BER_OID = 0x06,
49 SNMP_TAG_BER_SEQUENCE = 0x30,
50 // SMIv2 application-wide types (RFC 2578 sec 7.1.5 through 7.1.10).
51 SNMP_TAG_SNMP_IPADDRESS = 0x40,
52 SNMP_TAG_SNMP_COUNTER32 = 0x41,
53 SNMP_TAG_SNMP_GAUGE32 = 0x42,
54 SNMP_TAG_SNMP_TIMETICKS = 0x43,
55 SNMP_TAG_SNMP_OPAQUE = 0x44,
56 SNMP_TAG_SNMP_COUNTER64 = 0x46,
57 // VarBind CHOICE exception markers (RFC 3416 sec 3; used per sec 4.2.1 and sec 4.2.2).
58 SNMP_TAG_SNMP_NO_SUCH_OBJECT = 0x80,
59 SNMP_TAG_SNMP_NO_SUCH_INSTANCE = 0x81,
60 SNMP_TAG_SNMP_END_OF_MIB_VIEW = 0x82,
61 // PDU tags, context-specific constructed (RFC 3416 sec 3).
62 SNMP_TAG_SNMP_PDU_GET = 0xA0, ///< GetRequest-PDU ::= [0] IMPLICIT PDU
63 SNMP_TAG_SNMP_PDU_GETNEXT = 0xA1, ///< GetNextRequest-PDU ::= [1] IMPLICIT PDU
64 SNMP_TAG_SNMP_PDU_RESPONSE = 0xA2, ///< Response-PDU ::= [2] IMPLICIT PDU
65 SNMP_TAG_SNMP_PDU_SET = 0xA3, ///< SetRequest-PDU ::= [3] IMPLICIT PDU
66 SNMP_TAG_SNMP_PDU_GETBULK = 0xA5, ///< GetBulkRequest-PDU ::= [5] IMPLICIT BulkPDU
67 SNMP_TAG_SNMP_PDU_INFORM = 0xA6, ///< InformRequest-PDU ::= [6] IMPLICIT PDU
68 SNMP_TAG_SNMP_PDU_TRAPV2 = 0xA7, ///< SNMPv2-Trap-PDU ::= [7] IMPLICIT PDU
69 SNMP_TAG_SNMP_PDU_REPORT = 0xA8, ///< Report-PDU ::= [8] IMPLICIT PDU
70} SnmpTag;
71
72/**
73 * @brief An encoder's cursor: the caller buffer, how far it is written, and whether it still fits.
74 *
75 * The cursor is the caller's, so several encodings can be open at once (a PDU into one buffer
76 * while the message that will carry it is framed in another).
77 */
78typedef struct BerEnc
79{
80 uint8_t *buf; ///< the buffer octets are written into
81 size_t cap; ///< how many it holds
82 size_t len; ///< how many are written
83 proto_bool ok; ///< no write has run past cap
84} BerEnc;
85
86/** @brief A decoder's cursor: the octets being read, and how far the read has walked. */
87typedef struct
88{
89 const uint8_t *buf; ///< the octets being read
90 size_t len; ///< how many
91 size_t pos; ///< the next octet a read takes
92 proto_bool ok; ///< no read has run past len
93} BerDec;
94
95/** @brief The caller buffer a codec runs over. */
96typedef struct
97{
98 uint8_t *out; ///< where an encoder writes its octets
99 const uint8_t *in; ///< the octets a decoder reads
100 size_t cap; ///< octets available at @c out, or held by @c in
101} SnmpBerBufArgs;
102
103/** @brief The TLV a write carries: its identifier octet and the value under it. */
104typedef struct
105{
106 uint8_t tag; ///< the identifier octet the write emits
107 long ival; ///< the INTEGER value
108 uint32_t uval; ///< the non-negative application-type value (RFC 2578 sec 7.1.6 through 7.1.8)
109 const uint8_t *bytes; ///< OCTET STRING octets, or pre-encoded octets a raw append copies
110 size_t len; ///< how many
111 const uint32_t *arcs; ///< the OBJECT IDENTIFIER subidentifiers
112 size_t arc_count; ///< how many, at least 2
113 size_t token; ///< in: the constructed type a close back-patches; out: the one an open reserved
114} SnmpBerTlvArgs;
115
116/** @brief Where a read lands what it took. */
117typedef struct
118{
119 uint32_t *arc_out; ///< where an OBJECT IDENTIFIER read lands its subidentifiers
120 size_t arc_cap; ///< how many that holds, at least 2
121 size_t skip; ///< value octets a skip steps over
122} SnmpBerReadArgs;
123
124/**
125 * @brief The SNMP serialization (RFC 3417 sec 8, over ITU-T X.690).
126 *
127 * A caller binds a cursor with an init, sets the members a call takes, invokes it through
128 * ::SnmpBer, and reads the outcome off the same handle. Nesting is explicit: an open reports the
129 * token its close needs, so the caller holds it while the inner types are written.
130 *
131 * No storage member: both cursors are the caller's and every call reads or writes only through
132 * them, so the module keeps nothing between calls.
133 *
134 * @var SnmpBerNs::enc the encoder cursor a write acts on
135 * @var SnmpBerNs::dec the decoder cursor a read acts on
136 * @var SnmpBerNs::buf the caller buffer an init binds a cursor to
137 * @var SnmpBerNs::tlv the identifier octet and value a write carries
138 * @var SnmpBerNs::read_args where a read lands what it took
139 * @var SnmpBerNs::ok a call's true/false outcome: the cursor still fits its buffer
140 * @var SnmpBerNs::tag the identifier octet a header read took
141 * @var SnmpBerNs::vlen the value length that header states, in octets
142 * @var SnmpBerNs::ival the value an INTEGER read took
143 * @var SnmpBerNs::n subidentifiers an OBJECT IDENTIFIER read landed
144 * @var SnmpBerNs::enc_init bind an encoder cursor to @c buf.out for @c buf.cap octets
145 * @var SnmpBerNs::put_integer write an INTEGER, two's complement, minimal
146 * @var SnmpBerNs::put_uint write a non-negative value under @c tlv.tag, big-endian, minimal
147 * @var SnmpBerNs::put_octet_string write @c tlv.bytes under @c tlv.tag
148 * @var SnmpBerNs::put_null write NULL, the value a GetRequest-PDU binding carries
149 * @var SnmpBerNs::put_oid write an OBJECT IDENTIFIER from @c tlv.arcs
150 * @var SnmpBerNs::put_tlv write one primitive TLV verbatim
151 * @var SnmpBerNs::put_raw append already-encoded octets, no header of their own
152 * @var SnmpBerNs::seq_begin open a constructed type, reserving its definite-long length
153 * @var SnmpBerNs::seq_end close it, back-patching that length (RFC 3417 sec 8 item 1)
154 * @var SnmpBerNs::dec_init bind a decoder cursor to @c buf.in for @c buf.cap octets
155 * @var SnmpBerNs::read_header take a tag and length, leaving the cursor at the value
156 * @var SnmpBerNs::read_integer take an INTEGER, sign-extended from its first octet
157 * @var SnmpBerNs::read_oid take an OBJECT IDENTIFIER into @c read.arc_out
158 * @var SnmpBerNs::skip step the cursor past @c read.skip value octets
159 */
160typedef struct
161{
162 BerEnc *enc; ///< the encoder cursor every write names
163 BerDec *dec; ///< the decoder cursor every read names
164 SnmpBerBufArgs buf; ///< the caller buffer an init binds
165 SnmpBerTlvArgs tlv; ///< what a write carries
166 SnmpBerReadArgs read_args; ///< where a read lands
167 proto_bool ok;
168 uint8_t tag;
169 size_t vlen;
170 long ival;
171 size_t n;
172} SnmpBerVars;
173
174/** @brief The operands and the outcome. */
175extern SnmpBerVars SnmpBerV;
176
177/** @brief The entries. */
178typedef struct
179{
180 void (*const enc_init)(uint8_t *work);
181 void (*const put_integer)(uint8_t *work);
182 void (*const put_uint)(uint8_t *work);
183 void (*const put_octet_string)(uint8_t *work);
184 void (*const put_null)(uint8_t *work);
185 void (*const put_oid)(uint8_t *work);
186 void (*const put_tlv)(uint8_t *work);
187 void (*const put_raw)(uint8_t *work);
188 void (*const seq_begin)(uint8_t *work);
189 void (*const seq_end)(uint8_t *work);
190 void (*const dec_init)(uint8_t *work);
191 void (*const read_header)(uint8_t *work);
192 void (*const read_integer)(uint8_t *work);
193 void (*const read_oid)(uint8_t *work);
194 void (*const skip)(uint8_t *work);
195} SnmpBerNs;
196
197// What the table binds, defined once in the .c and taking one parameter each: everything
198// else an entry needs is an operand in SnmpBerV or a region of the borrow at a fixed offset.
199void protocore_snmp_ber_enc_init(uint8_t *work);
200void protocore_snmp_ber_put_integer(uint8_t *work);
201void protocore_snmp_ber_put_uint(uint8_t *work);
202void protocore_snmp_ber_put_octet_string(uint8_t *work);
203void protocore_snmp_ber_put_null(uint8_t *work);
204void protocore_snmp_ber_put_oid(uint8_t *work);
205void protocore_snmp_ber_put_raw(uint8_t *work);
206void protocore_snmp_ber_seq_begin(uint8_t *work);
207void protocore_snmp_ber_seq_end(uint8_t *work);
208void protocore_snmp_ber_dec_init(uint8_t *work);
209void protocore_snmp_ber_read_header(uint8_t *work);
210void protocore_snmp_ber_read_integer(uint8_t *work);
211void protocore_snmp_ber_read_oid(uint8_t *work);
212void protocore_snmp_ber_skip(uint8_t *work);
213
214// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
215// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
216// `SnmpBer.enc_init(work)` resolves to a named function and becomes a DIRECT call. An extern table
217// leaves the call indirect and the symbol live at every level, -O2 -flto included.
218static const SnmpBerNs SnmpBer __attribute__((unused)) = {
219 .enc_init = protocore_snmp_ber_enc_init,
220 .put_integer = protocore_snmp_ber_put_integer,
221 .put_uint = protocore_snmp_ber_put_uint,
222 .put_octet_string = protocore_snmp_ber_put_octet_string,
223 .put_null = protocore_snmp_ber_put_null,
224 .put_oid = protocore_snmp_ber_put_oid,
225 .put_tlv = protocore_snmp_ber_put_octet_string,
226 .put_raw = protocore_snmp_ber_put_raw,
227 .seq_begin = protocore_snmp_ber_seq_begin,
228 .seq_end = protocore_snmp_ber_seq_end,
229 .dec_init = protocore_snmp_ber_dec_init,
230 .read_header = protocore_snmp_ber_read_header,
231 .read_integer = protocore_snmp_ber_read_integer,
232 .read_oid = protocore_snmp_ber_read_oid,
233 .skip = protocore_snmp_ber_skip,
234};
235
237
238#endif // PROTOCORE_ENABLE_SNMP
239
240#endif // PROTOCORE_SNMP_BER_H
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
#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