Skip to content

Releases: syntax1269/SNMP_Embedded

SNMP_Embedded v3.4.4 — Zero-Heap Packet Path · Hardened Parser · Bounded-Memory Proof

Choose a tag to compare

@syntax1269 syntax1269 released this 08 Sep 19:18

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

Choose a tag to compare

@syntax1269 syntax1269 released this 07 Sep 21:36

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 SNMPTrap class, 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=1 combined with
    SNMP_TRAP_VB_RESERVE>0 is 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

Choose a tag to compare

@syntax1269 syntax1269 released this 07 Sep 03:33

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_RESERVE for sketches that attach varbinds to
    traps (3 pool slots each). Escape hatch: SNMP_ZERO_COPY=0 restores 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

Choose a tag to compare

@syntax1269 syntax1269 released this 06 Sep 01:23

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 uninstall

Semantics

  • 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 with errorStatus = 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 SNMPAgent instance 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

Choose a tag to compare

@syntax1269 syntax1269 released this 06 Sep 01:23

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 answer WRONG_LENGTH, covered by a new
    host test).
  • SysBuf accepts real arrays or RFC1213_SKIP — and not a bare char*.
    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 (and SNMP_Sensor now shows them together).

Under the hood

  • New StringBufCallback binds the char[] 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

Choose a tag to compare

@syntax1269 syntax1269 released this 06 Sep 01:15

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 malloc on 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_assert guards fail the build if any container outgrows its slot.
  • Loud, RFC-correct failure modes. Over-cap GETBULK and over-budget responses answer tooBig instead 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:
    lib_deps =
        https://github.com/syntax1269/SNMP_Embedded.git#v3.3.3
    Pin the tag (#v3.3.3) for reproducible builds; use the bare URL to track main (not recommended for production).

Verify your build

git clone https://github.com/syntax1269/SNMP_Embedded
cd SNMP_Embedded/tests && make ci-test

The 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.