Repository navigation
Releases: syntax1269/SNMP_Embedded
Release list
SNMP_Embedded v3.4.4 — Zero-Heap Packet Path · Hardened Parser · Bounded-Memory Proof
SNMP_Embedded v3.4.4 brings deterministic memory behavior, hardened packet
validation, and visible runtime resource statistics together in one release.
It is designed for embedded devices that must answer SNMP requests reliably for
long periods without progressive heap fragmentation caused by packet traffic.
1. True zero-heap packet path
The v3.4.0 zero-copy baseline removed packet copies but still constructed
values through std::shared_ptr, which could allocate a heap control block for
each value. v3.4.4 replaces the built-in packet-path ownership with the
move-only, pool-owning AsnPtr<T>. The pool itself carries the ownership state.
The corrected comparison is against the v3.4.0 baseline, not v3.4.1:
| Packet path | GET | SET | GETNEXT | GETBULK |
|---|---|---|---|---|
| zero-copy, v3.4.0 baseline | 11 | 9 | 9 | 2 |
| classic compatibility path, v3.4.0 baseline | 94 | 87 | 94 | 42 |
| v3.4.4 current, both paths | 0 | 0 | 0 | 0 |
The built-in callback classes use the optimized pool-owning path. The
deprecated callback bridge remains available for source compatibility with
external subclasses; new callback implementations should use the optimized
raw-value factory.
2. Hardened parser
Network data is treated as untrusted input. The decoder now rejects malformed
BER length fields that extend beyond the supplied buffer, use unsupported
forms, or exceed the supported length width. Hostile SNMP version integers are
validated as raw values before conversion to the protocol enum.
The fixes cover both the owning compatibility path and the in-place packet
path, with permanent regression coverage for truncated packets, invalid BER
lengths, deep nesting, malformed OIDs, oversized integers, malformed SET and
GETBULK requests, hostile version fields, and random packet contents.
3. Runtime statistics and bounded memory
A sketch can inspect the agent without allocating a reporting object:
SNMP_RuntimeStats stats;
snmp.getRuntimeStats(&stats);
Serial.printf("rx=%u malformed=%u rejected=%u tooBig=%u pool=%u/%u peak=%u\n",
(unsigned)stats.packets_received,
(unsigned)stats.malformed_packets,
(unsigned)stats.packets_rejected,
(unsigned)stats.too_big_responses,
(unsigned)stats.pool_used,
(unsigned)stats.pool_cap,
(unsigned)stats.pool_high_water);The snapshot provides current pool use, pool high-water, pool capacity, packet
receipts, malformed packets, community rejections, tooBig responses, pool
allocation failures, and double-release alarms.
SNMP_ENGINE_MAX_RAM_BYTES is a compile-time constant for the configured
engine footprint. It accounts for the ASN pool arena, packet buffer, and
worst-case VarBind array.
Hardware verification
Reference platform: ESP8266 ESP-01, 80 MHz, 768-byte packet profile.
- One-minute flood soak: 181 operations, 0 failures.
- Five-minute flood soak: 928 operations, 0 failures on the clean run.
- Pool high-water: 27 of 33 slots.
- Heap diagnostic drift: zero net drift in the clean soak measurements.
- Alarms, crashes, and reboots: 0, 0, and 0.
Network-hardness verification
Malformed and hostile packet shapes were replayed against the live agent,
including truncated and overrun lengths, indefinite forms, deep nesting,
malformed OIDs, oversized integers, malformed SET and GETBULK requests,
hostile version fields, all-zero and all-0xff data, random garbage, and
prefix truncations.
Across three campaigns:
- 16,387 hostile datagrams were transmitted.
- 615 of 615 liveness probes were answered.
- The pool remained at 27/33 high-water occupancy.
- There were 0 alarms, 0 crashes, and 0 reboots.
- Malformed packets were rejected without destabilizing the agent.
Compatibility
The normal agent lifecycle and sketch-facing handler API remain compatible.
The default in-place packet path is selected by SNMP_ZERO_COPY=1; the owning
compatibility path remains available with SNMP_ZERO_COPY=0. The SNMP_NO_TRAPS
and SNMP_NO_BUILTIN_SYSUPTIME footprint flags remain available for builds
that do not need those features.
Validation summary
- Built-in packet paths: zero heap allocations per GET, SET, GETNEXT, and
GETBULK in the v3.4.4 allocation census. - Malformed BER and hostile-version regression cases: passing.
- ESP8266 flood soaks: passing with stable pool occupancy and no alarms.
- ESP8266 hostile network campaigns: passing with full liveness recovery.
See README.md for the progressive overview and
SNMP_Embedded_Technical_Manual.md for
complete API and configuration details.
SNMP_Embedded v3.4.1 — SNMP_NO_TRAPS: a leaner agent for endpoints that never trap
SNMP_NO_TRAPS — one flag, for the huge class of endpoints that only ever
answer requests: sensors, actuators, monitors. Set it as a global build flag
and the entire trap/inform subsystem is compiled out of your firmware:
- Smaller: the
SNMPTrapclass, the inform retry queue, and the
inform-ack machinery do not exist in the binary. - Leaner pool: the exact-pricing formula drops the 16-slot trap-tree
term —pool = 2*SNMP_MAX_VARBINDS + 4 + SNMP_MAX_CALLBACKS_PER_AGENT
(the 13-handler 768-B profile derives 25 slots instead of 33). - Loud, never silent: an accidental trap call fails at compile time with
a message naming the flag and the remedy.SNMP_NO_TRAPS=1combined with
SNMP_TRAP_VB_RESERVE>0is a build-time error.
Flag off (the default) is bit-identical to v3.4.0: the host matrix is
green with identical counts (2,125 assertions / 28 cases; zero-copy-off
262 / 23; no-builtin-uptime 2,057 / 23), and the standard ESP8266 flood
soaks re-ran clean — pool 27/33, 0 alarms, 0 crashes, 0 reboots.
New test-notraps host profile (with static_assert proof of the pool
derivation) joins the CI matrix, and a latent tests-Makefile object-dir
collision affecting local back-to-back profile builds is fixed.
Compatibility: public API unchanged when the flag is off (default).
With the flag on, trap/inform API removal is by design and loudly announced
at compile time. Library Manager users: no action needed — the flag is opt-in.
Requires an Ethernet/WiFi UDP stack per the bundled examples; sizing tables
and the full memory model are in the README.
SNMP_Embedded v3.4.0 — Zero-Copy Packet Path, Exact-Pricing Pool, +64% Free Heap
SNMP_Embedded v3.4.0 — the zero-copy release.
What changed
Inbound SNMP packets are now parsed in place over the UDP receive buffer —
no per-varbind container copies, no dotted-string renders on the hot path.
Responses are written directly into the outgoing UDP buffer, with a
measured response-size fit check: tooBig is answered only when genuinely
true. Handler dispatch matches the request's raw encoded-OID bytes, which are
exactly the bytes that define SNMP's sort order — walk semantics are exact.
The pool formula is now exactly priced: every slot is charged to a named,
source-verified consumer (request tick = 2·VB value containers; trap tick = the
16-slot minimal v2c trap tree; + an explicit 4-slot idle margin; + permanent
handlers). Queued informs hold zero pool slots — proven on hardware with the
inform queue saturated under flood.
Measured on hardware (ESP8266 ESP-01, standardized flood harness)
| Metric | v3.3.6 | v3.4.0 | Delta |
|---|---|---|---|
| Free heap at boot, 768-B profile | 28.6 KB | 35.7 KB | +7.1 KB (+25%) |
| Free heap at boot, 1400-B profile | 21.4 KB | 35.0 KB | +13.6 KB (+64%) |
| Pool size (13-handler profile) | 57 / 79 slots | 33 / 33 | −42% / −58% |
| Sustained request throughput | 2.35 ops/s | 2.91–3.03 ops/s | +24–28% |
| Flash (768-B profile) | 307.4 KB | 301.9 KB | −5.4 KB |
| Pool alarms / crashes / reboots | 0 | 0 | parity |
| SET round-trip integrity | 100% | 100% | parity |
Validated per profile with 1-minute, 5-minute, and 15-minute flood soaks
(bulkwalk, wide-walk tooBig, GETNEXT, SET, sysDescr mix) plus an
inform-queue-saturation stress profile — zero pool alarms, zero crashes,
zero reboots across the entire campaign.
Compatibility
- No API changes. Existing sketches compile and behave identically.
- Wire behavior is byte-verified identical to v3.3.6 (2,125-assertion host
suite, byte-for-byte response comparison across GET / GETNEXT / GETBULK /
SET / error paths). SNMPPacket::parseFrom()keeps its owning-container contract.- New opt-in flag
SNMP_TRAP_VB_RESERVEfor sketches that attach varbinds to
traps (3 pool slots each). Escape hatch:SNMP_ZERO_COPY=0restores the
classic container path.
Upgrade
No sketch changes required. Users who size pools manually via
SNMP_POOL_ASN_OBJECTS overrides keep full control; everyone else gets the
tighter derived arena automatically.
SNMP_Embedded v3.3.6 — Inform delivery confirmation: know when the manager received your inform
SNMP informs exist to be acknowledged: per RFC 3416, the receiving manager
answers an InformRequest with a Response PDU. The library has always matched
that response — by request ID — against its pending-inform queue, retried on
timeout, and retired the item on success. What it never had was a way for
your sketch to see that moment. v3.3.6 exposes it:
void onInformAck(unsigned long requestID, bool success) {
if (success) {
// the manager processed the inform with noError — confirmed delivery
} else {
// the manager responded with an error status — rejected, not lost
}
}
snmp.setInformAckCallback(onInformAck); // in setup(); nullptr to uninstallSemantics
- Fires from inside
snmp.loop()— never from an ISR, no threading
concerns, safe to touch serial, LEDs, or your own state. success = true— the manager decoded and processed the inform
(Response PDU witherrorStatus = noError).success = false— the manager answered with an error status. Your
inform was rejected, not lost: the distinction matters for alarm pipelines.- No phantom confirmations — the callback only fires for request IDs that
actually have a pending inform. Unsolicited or stale Response PDUs are
ignored, exactly as before. - Timeout is not an ack — an inform that exhausts its retries with no
response does not fire the callback (nothing arrived to acknowledge); the
queue item is retired when the retry budget runs out, unchanged. - Per-agent — each
SNMPAgentinstance carries its own hook, so
multi-agent sketches wire one per instance.
Compatibility
Additive only. The informAckCB typedef (void (*)(unsigned long, bool)) is
new in SNMPParser.h; existing sketches compile and behave identically with
or without a callback installed.
Verification
- Host suite: 262 assertions / 23 test cases (default) · 194 / 18 with
-DSNMP_NO_BUILTIN_SYSUPTIME— new cases cover ack delivery, outcome
propagation, phantom suppression, uninstall, and queue consumption. - Compile matrix: all examples and demos on ESP8266 + ESP32.
SNMP_Embedded v3.3.5 — Five-input system-group call: the library deduces your buffer sizes
v3.3.4 introduced the one-call RFC 1213 system-group helper, but the read-write
strings needed an explicit sizeof() per buffer — safe, yet noisy. v3.3.5
listens to how the API actually reads:
// Before (v3.3.4): 8 arguments, three sizeof() calls
snmp.addRFC1213SystemGroup(sysDescr,
&sysContact, sizeof(sysContactBuf),
&sysName, sizeof(sysNameBuf),
&sysLocation,sizeof(sysLocBuf), &sysServices);
// Now (v3.3.5): 5 arguments, zero sizeof()
snmp.addRFC1213SystemGroup(sysDescr, sysContactBuf, sysNameBuf, sysLocBuf, &sysServices);Skip an OID in one self-documenting token — RFC1213_SKIP replaces the
nullptr, 0 pair:
// only sysName + sysLocation exist on this agent:
snmp.addRFC1213SystemGroup(sysDescr, RFC1213_SKIP, sysNameBuf, sysLocBuf, nullptr);Safety didn't move — it got stricter
- The capacity is deduced from the array itself, so a SET can still never
overflow your storage (over-long SETs answerWRONG_LENGTH, covered by a new
host test). SysBufaccepts real arrays orRFC1213_SKIP— and not a barechar*.
A pointer has no deducible capacity, so that mistake fails to compile
instead of silently guessing.- Heap or runtime-sized storage? The v3.3.4 pointer+len overload remains; both
forms work side by side (andSNMP_Sensornow shows them together).
Under the hood
- New
StringBufCallbackbinds thechar[]directly — same semantics as the
pointer form, zero allocation. - All five sketches/examples updated to teach the auto-size form.
Verification
- Host suite: 243 assertions / 22 test cases (default) · 175 / 17 with
-DSNMP_NO_BUILTIN_SYSUPTIME— both exit clean. - Compile matrix: 8/8 (2 examples × ESP8266 + ESP32, 2 CLI demos,
PlatformIO minimal × 2 envs). - Hardware (ESP8266): clean boot, 1-minute flood soak at ~3 ops/s — 0 pool
alarms, 0 crashes, heap floor 23,504 B.
SNMP_Embedded v3.3.3 — Initial Release
SNMP_Embedded is a memory-safe SNMPv2c agent library for Arduino on ESP32 and ESP8266, built for long-term unattended operation on small MCUs. This is the initial baseline release.
Highlights
- Deterministic memory, zero runtime heap. All packet memory lives in a fixed-slot ASN.1 pool that is allocated once at boot — no
mallocon the request path, no fragmentation, no transient spikes. - Derived sizing, not magic numbers. The varbind cap and pool size are computed at compile time from the packet budget (default 1400 B packets → 8-varbind cap, 142 slots × 312 B);
static_assertguards fail the build if any container outgrows its slot. - Loud, RFC-correct failure modes. Over-cap GETBULK and over-budget responses answer
tooBiginstead of silently truncating; double-release and pool exhaustion are detected and logged, never swallowed. - Hardware-proven. Flood soaks on a 1 MB ESP8266 board (200 ms between operations, 30+ minutes sustained) finished with zero crashes and zero pool alarms; heap stays flat by construction.
- CI-enforced. Every push runs arduino-lint, a 175-assertion host test suite, and an ESP8266 + ESP32 example compile matrix.
What's included
- SNMPv2c agent: GET, GETNEXT, GETBULK, SET, traps, and informs
- RFC1213 system group plus user-defined custom OID handlers
- Two examples (
ESP32_SNMP,SNMP_Sensor) that compile out of the box - Full API and memory-model documentation in the README
Measured footprint (examples as shipped)
| Target | Example | Result |
|---|---|---|
| ESP32 | ESP32_SNMP |
70% program / 15% RAM |
| ESP8266 (d1_mini) | both examples | compile clean |
Install
- Arduino IDE (Library Manager): Tools → Manage Libraries… → search SNMP_Embedded → Install (indexed as of the acceptance of registry PR arduino/library-registry#9032).
- Manual: Code → Download ZIP, then Sketch → Include Library → Add .ZIP Library…
- PlatformIO: add to
lib_deps:Pin the tag (lib_deps = https://github.com/syntax1269/SNMP_Embedded.git#v3.3.3
#v3.3.3) for reproducible builds; use the bare URL to trackmain(not recommended for production).
Verify your build
git clone https://github.com/syntax1269/SNMP_Embedded
cd SNMP_Embedded/tests && make ci-testThe suite prints a color-coded summary in a terminal (green pass / yellow warnings / red failures); piped or CI output stays plain text for clean logs and diffs.
Version numbering
v3.3.3 continues the version lineage of the codebase this library was derived from; it is the first SNMP_Embedded release.
Provenance
Begins as a fork of Arduino_SNMP v2.1.0 by Aidan Cyr (MIT). The majority of the engine has since been rewritten — pool allocator, derived sizing, trap/inform lifecycle, and the entire verification infrastructure. Attribution is retained in the LICENSE per the MIT license.