Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Spec compliance

ndn-rs’s compliance with the NDN Packet Specification and the NDNCERT specification is tracked by live witness scripts under testbed/tests/audit/ that exit non-zero when a compliance claim regresses. This page summarises which areas are covered.

The live source of truth is testbed/EXPECTED_FAILURES.md. This page is a reader-facing map to the witnesses, not a substitute for the tracker. A feature is release-ready only when the corresponding witness passes or the tracker explains why the remaining work is out of scope.

Coverage areas

AreaSpec sourceAudit sectionWitness prefix
Name and component typesPacket spec §2 (Name)A.01 – A.04a*_blake3_*, a19_a20_uri_*
Interest TLVPacket spec §3A.05 – A.08a05_a18_tlv_strictness
Data TLVPacket spec §4A.09 – A.16a10_databuilder_build_sig
Signature typesPacket spec §5A.16, A.17, BLAKE3a16_signature_value_length, a17_blake3_registered
KeyLocator rulesPacket spec §5.5A.15a15_keylocator_rules
LP TLV (NDNLPv2)LP specA.11, A.12a11_nack_reason_documented, a12_nack_lp_only
LP IncomingFaceId / LocalFieldsNDNLPv2 §local fieldsX.02x02_incoming_face_id_local_fields
Nonce lengthPacket spec §3A.13a13_nonce_length_rejected
FinalBlockId / UriComponentNaming conventionA.19, A.20a19_a20_uri_finalblockid
Signed InterestsPacket spec §3 (signed)A.09a09_signed_interest_verify
Persistent-state InterestPersistent Interest design(interop)persistent_interest_*
NDNCERT issued certNDNCERT specC.07, C.08, C.18, N.13acme_dns01.sh, cert_*
Architectural cleanupPhase 2 ARCH-1..20(ARCH-N)arch* (per-item witnesses)
Tiered API surfacePhase 3 §3tier docsphase3_*

The testbed/tests/audit/*.sh scripts are the runnable witnesses; each exits non-zero when the claim it tracks regresses.

As of the 2026-05-28 release-readiness pass, the tracker-driven audit harness runs every script named by testbed/EXPECTED_FAILURES.md and reports 54 PASS / 0 FAIL / 0 SKIP. The report for that local pass was captured in testbed/tests/audit/transcripts/release_audit_run_all_after.txt; the important release signal is zero divergences from the tracker.

Grep-only checks are not release-quality proof for protocol behavior. They may guard documentation wording, removed APIs, or source inventory, but packet, security, forwarding, and management claims need a RUST-UNIT, RUST-INTEG, INTEROP-SCRIPT, or WIRE-CAPTURE witness before this page should describe them as complete.

Reading the witnesses

Each witness is a shell script with exit-code semantics:

  • 0 — finding passes / claim holds.
  • 1 — finding fails / claim regressed; the script prints the exact diagnostic.
  • 2 — live interop precondition is missing; Rust/local witnesses may have passed, but the Docker leg did not run.
# Run a single witness:
bash testbed/tests/audit/a17_blake3_registered.sh ; echo exit=$?

# Run every release-tracked audit witness:
RESULTS_DIR=/tmp/ndn-audit-results bash testbed/tests/audit/run_all.sh

The audit harness scaffold is testbed/tests/audit/_template.sh; new findings follow the same shape (project memory feedback_witness_first_compliance).

Cross-impl on-disk references

Per project memory feedback_cross_reference_standard, every audit finding cites the source implementation it tracks against the upstream NDN reference implementations cloned on disk. The references live alongside each witness script’s # Finding: header comment.

Recently closed blockers

These audit rows were release blockers in the pre-v0.1 tracker and now have passing behavioral or live witnesses:

FindingWitnessResolution
A.09a09_signed_interest_verify.shSigned Interest signer bytes are checked against the decoded final-wire Interest::signed_region(), and KeyChain-signed Interests verify against that region.
A.15a15_keylocator_rules.shKeyLocator presence/absence is enforced by SignatureType and surfaced by outer packet decoders.
C.01c01_rsa_ecdsa_verifiers.shRSA-SHA256 and ECDSA-SHA256 now have behavioral verifier witnesses for valid signature, wrong signature, malformed key, and validator dispatch.
C.09c09_safebag_ndnsec_interop.shSafeBag portability is now witnessed through reference ndnsec: ndn-rs exports an ECDSA-P256 SafeBag, ndnsec imports/re-exports it, and ndn-rs decrypts and verifies the returned SafeBag. SafeBag encryption now uses the ndn-cxx-compatible PBES2/PBKDF2-HMAC-SHA256/AES-256-CBC profile.
C.12c12_mgmt_sign_digest.sh, c12_mgmt_sign_key.sh, c12_mgmt_dataset_fresh.shMgmtClient command Interests are decoded and checked for DigestSha256 over the spec signed region; the key-backed script now registers a signed route against Docker NFD and verifies it with nfdc route list. Dataset queries now set CanBePrefix+MustBeFresh, and the follow-up witness proves ndn-ctl route rib-list sees a freshly registered NFD route instead of stale cached dataset Data.
C.13c13_ndncert_challenge_tlv.sh, c13_ndncert_live_interop.shNDNCERT CHALLENGE parameters use TLV ParameterKey/ParameterValue, and the live witness enrolls against upstream ndncert-ca-server, completes the PIN challenge, fetches the issued Certificate v2 Data, decodes it with ndn-rs, and checks the issuer chain prefix.
Validator configvalidator_config_behavior.shThe configuration-validator release claim is now behavioral: ordered first-match rules, no-match denial, exact KeyLocator-prefix checking, and hierarchical checking all have Rust witnesses. Full ndn-cxx validator.conf parsing is not advertised.
C.16c16_lvs_user_fn_failsafe.shLVS binary schemas with unsupported user functions fail closed: the parser flags the function call, trust-schema import rejects enforcement, and direct policy evaluation denies a fixture that would match if the constraint were ignored open.
D.01d01_hoplimit_decrement.shHopLimit is decremented on the incoming pipeline; the Docker witness proves HopLimit=2 still reaches an NFD producer while HopLimit=1 is dropped after decrementing to zero.
D.04d04_pit_aggregation_selectors.shPIT entries aggregate by Name/ForwardingHint with per-in-record selectors, and CS lookup rejects stale cached Data for MustBeFresh Interests at both store and engine stage level.
D.02 / I.11d02_localhop_scope.sh/localhop scope is covered by Rust unit behavior and live interop: remote TCP drops, local Unix face passes.
E.04e04_dataset_segmentation.shManagement datasets are returned as versioned segmented Data with FinalBlockId; the Docker witness verifies this for /localhost/nfd/faces/list.
E.05e05_notification_streams.shNotificationStream<T> has unit coverage for publisher/subscriber delivery and semantic face events; the Docker live witness mutates strategy-choice and fetches the resulting /localhost/nfd/strategy-choice/notifications/seq=<n> event Data with ndn-mgmt-notification-fetch.
Management FaceStatusnfdc_interop_face_list.shThe Docker interop image ships reference NFD nfdc; nfdc face list decodes ndn-fwd FaceStatus Data, including required Flags, without tripping NFD’s strict dataset decoder.
N.12n12_mgmt_response_signing.shndn-fwd testbed boots with a persistent ECDSA-P256 identity; reference nfdc status decodes live management Data, and ndn-mgmt-response-verify verifies /localhost/nfd/cs/config ControlResponse Data against the shared PIB trust anchor.
G.03g03_psync_interop.shThe Docker interop image builds upstream C++ PSync plus a deterministic FullProducer fixture; the witness runs it against ndn-rs ndn-psync-consumer through NFD and requires five distinct expected Sync update prefixes.
G.04g04_nlsr_interop.shndn-rs NLSR now has a live C++ NLSR sidecar witness. The Docker test requires bidirectional route convergence: ndn-fwd-nlsr installs /test/r1/data, and C++ NLSR/NFD installs /test/r2/data with origin=nlsr; a pcap is saved with the transcript.
G.06g06_swim_vs_autoconfig.shSWIM artifacts are absent, Rust AutoConfig hub-discovery witnesses pass, and upstream ndn-autoconfig succeeds through an NDN-FCH fixture by creating a hub face and registering / plus /localhop/nfd routes on ndn-fwd.
G.09g09_prefix_announcement_consume.shPrefixAnnouncement is now covered through decode, validation, route installation, and live forwarding-path use: a validated announcement installs /learned, and a later /learned/item Interest reaches the announcing face. Tampered or untrusted announcements install no route.
Management statustestbed/tests/compliance/mgmt_protocol.shndn-ctl status renders the NFD-compatible status/version/startTime/uptime signal expected by the live compliance suite.
N.05n05_nack_no_reason.shA Nack header without NackReason decodes as None, not Other(0).
N.06n06_dead_nonce_list.shThe engine inserts retiring PIT nonces into DeadNonceList on satisfaction/expiry and consults it before PIT aggregation, so repeated (Name, Nonce) after PIT erasure drops as a loop.
N.08n08_unsolicited_data_policy.shUnsolicitedDataPolicy is now engine-witnessed for all four NFD-compatible modes: drop-all, admit-all, admit-local, and admit-network. Admitted unsolicited Data is cache-only and still must pass the verified-Data CS gate.
N.09n09_multiaccess_nack_policy.shNacks are treated as point-to-point feedback: generated Nacks are suppressed on shared-medium ingress, incoming shared-medium Nacks are ignored, and propagation skips multi-access/ad-hoc downstream faces. A live UDP fixture now injects a real socket-originated Nack on the shared-medium face and proves it is not propagated.
N.10n10_command_replay.shSigned management commands enforce the SignatureTime window and per-signer strictly-increasing replay rule, and ndn-fwd mounts management with the replay cache wired by default.
N.14n14_trust_anchor_validity.shExpired and not-yet-valid trust anchors are rejected before entering the anchor set or cert cache; valid anchors still insert.
N.02n02_lp_reassembly_collision.shLP reassembly is keyed by sender endpoint as well as fragment sequence: packet-level tests cover overlapping sequences, engine tests prove FaceAddr-derived UDP/MAC endpoint IDs reach TlvDecodeStage, and a live UDP fixture reassembles colliding fragment sequences from two real socket source addresses.

The remaining shared-medium depth work is now optional transport breadth: Ethernet/BLE captures can extend the same invariants beyond the live UDP socket fixtures when those environments are available.

Intentional extensions (downstream-relied-upon divergences)

A few ndn-rs behaviors deliberately extend or diverge from stock NFD/ndn-cxx. They are intentional, not findings, and downstream stacks (notably NDF) build on them, so each is pinned by a passing regression-guard witness in testbed/tests/audit/ and tracked in testbed/EXPECTED_FAILURES.md under “NDF-relied-upon extensions”. They are listed here so the divergence is part of the documented compliance surface rather than an untracked delta.

ExtensionWhat divergesWitness
Persistent Interest / SubscriptionRequest (TLV 0x230)A subscription Interest creates a persistent PIT entry with per-InRecord persistence state and a data-count budget, distinct from a classical one-shot entry at the same name. Stock NFD has no such TLV. The value optionally carries a trailing SubscriptionId (stable correlation handle) so a subscription can survive a face flap: its surviving budget is parked on face-down and spliced back when re-expressed with the same id (F15 B1), and on an upstream face-down the stale out-record is pruned so a re-expressed subscription re-establishes the upstream leg instead of aggregate-suppressing (F15 B2).f16i_subscription_persistence.sh, f15_subscription_survival.sh
ReplayGuard monotonic=false shared-key modeSigned-Interest replay protection uses AND-semantics (a replay only when every shared anti-replay field agrees), and supports a non-monotonic mode for a key shared across devices, where an out-of-order seq is legitimate but an exact in-window repeat is still rejected.f16ii_replay_guard_shared_key.sh
ContentHashTarget::InnerTlvType delegated hashingA face may be configured to hash a specific inner TLV type (e.g. 364) rather than the whole Content, so a delegating consumer can name/verify by that inner digest.f16iii_inner_tlv_hashing.sh
Implicit-digest fetch (<auth>/blocks/<hash>)Content-Store lookup and CanBePrefix resolution by ImplicitSha256DigestComponent, supporting content-hash-addressed retrieval. (Standard NDN component, but pinned here as a relied-upon retrieval path.)f14_implicit_digest_fetch.sh

The SignedTrustContext wire object also carries an optional non-critical provenance hint (SOURCE_BUNDLE_HASH, TLV 0x041A) recording the SHA-256 digest of the source trust bundle a context was projected from; old nodes skip it per the NDN evolvability rule.

Strictness divergences (stricter than ndn-cxx/NFD)

Areandn-rs behaviorReferences
Non-minimal VAR-NUMBERread_varu64 rejects a TLV-TYPE/TLV-LENGTH written in a longer-than-minimal form (TlvError::NonMinimalVarNumber).ndn-cxx / NFD / NDNts decode such forms leniently.
NonNegativeInteger widths (LP/SVS)The core Interest/Data and management decoders enforce the NDN {1,2,4,8}-octet rule, but the LP (decode_be_u64) and SVS (decode_nni) integer decoders are currently lenient — they fold an off-width value into a u64 (truncating >8 bytes) rather than rejecting it. NFD rejects off-width LP integer fields.NFD rejects; ndn-rs core/mgmt reject; ndn-rs LP/SVS accept.

The LP/SVS NNI-width leniency is a known internal inconsistency (audit I-2): the same rule is enforced in some decoders and not others. It is tracked to be made uniformly strict as part of consolidating the duplicated TLV/NNI codecs onto one ndn-tlv API (audit R-1).

This is intentional: a minimal-length VAR-NUMBER is the canonical wire form, and rejecting non-minimal encodings avoids two distinct wire representations of the same logical packet. The NDN Packet Format recommends minimal encoding, and the reference implementations emit minimal forms, so in practice interop is not affected — a conforming peer never sends a non-minimal length. ndn-rs likewise encodes minimally. The only one-directional risk is a hand-crafted or deliberately non-minimal encoder; a differential test against python-ndn / NDNts is tracked to confirm the references never emit non-minimal lengths in the wild. (Audit finding I-1.)

TLV codepoint allocations

ndn-rs’s TLV allocations split into three classes:

  • IANA / registry codes ndn-rs implements (forwarding).
  • ndn-rs-internal codes used only on in-process or shared-memory faces (no wire reach).
  • Codes reserved for v0.1.x.

Releasing under v0.1.0

Release-readiness is gated on every critical-severity audit finding being closed, the cross-implementation interop suite running in the interop image, and the release notes matching the live tracker. The 2026-05-28 Docker baseline for testbed/tests/interop/run_all.sh passes all eight ndn-cxx and NDNts packet-exchange scenarios. The same pass now also includes live G.03 PSync, G.04 NLSR, G.06 AutoConfig, C.09 SafeBag, N.12 management-response signing, and E.05 notification-stream witnesses. Remaining work is mostly deeper optional fixture coverage and smaller semantic policy rows, not a blocked generic interop harness. For heavy AutoConfig/testbed rebuilds on Docker Desktop, prefer NDN_TESTBED_BUILD_JOBS=2 testbed/tools/up-g06-low-memory.sh so the reference C++ and Rust tool images build sequentially.

See also

  • testbed/tests/audit/ — runnable witness scripts.
  • v0.1.0 release boundary — what is in scope once the release is tagged.