Skip to content

(8) feat(dpdk): test EAL harness (with_eal macro + const fn)#1572

Merged
daniel-noland merged 6 commits into
mainfrom
pr/daniel-noland/dpdk-test-eal
Jun 5, 2026
Merged

(8) feat(dpdk): test EAL harness (with_eal macro + const fn)#1572
daniel-noland merged 6 commits into
mainfrom
pr/daniel-noland/dpdk-test-eal

Conversation

@daniel-noland

@daniel-noland daniel-noland commented May 31, 2026

Copy link
Copy Markdown
Collaborator

Stack (8). Base: pr/daniel-noland/threading-rewrite.

DPDK test plumbing that the rest of the ACL stack relies on, kept separate so it
reviews on its own:

  • refactor(dpdk/acl): make compute_min_input_size a const fn.
  • feat(dpdk): test_support::start_eal + #[with_eal] attribute macro
    (dpdk-test-macros), so EAL-dependent tests can opt in declaratively.

No new abstractions, no behavior change to shipping code.

Review stack (merge bottom -> top):

Summary

This PR adds DPDK test harnessing and supporting workspace crates, plus a refactor to make an ACL helper const-evaluable. Key changes:

  • refactor(dpdk/acl): make compute_min_input_size const

    • Converted AclBuildConfig::compute_min_input_size to pub const fn (rewritten to be const-friendly).
    • Preserved runtime call-path and FieldExtentOverflow validation.
    • Added a unit test that evaluates the function in a const context and verifies parity with the runtime result.
  • feat(dpdk): EAL test harness and #[with_eal] macro

    • New proc-macro crate dataplane-dpdk-test-macros providing #[with_eal] which injects a call to crate::test_support::start_eal() at the start of test functions.
    • Added dpdk::test_support::start_eal() using a OnceLock-backed cached Eal. It configures EAL with --no-huge, --no-pci, --in-memory, derives --lcores from the process CPU affinity, and generates a unique --file-prefix. Designed to be once-per-process and safe to use with nextest.
    • dpdk crate now conditionally exposes test_support and re-exports with_eal behind a new test feature; dpdk tests were updated to use #[with_eal] (removing inline per-test start_eal and module-scoped OnceLock).
  • workspace & crate additions / Cargo wiring

    • Added workspace members: dpdk-test-macros, fixed-size, lookup, match-action, match-action-derive.
    • Added workspace dependencies and workspace metadata entries for the new crates.
    • dpdk/Cargo.toml: added a test feature that wires optional deps (id, nix with sched feature, dpdk-test-macros) and replaced prior dev-only id/nix usage with a self-referencing dev-dependency using the new test feature so test-only helpers/macros are available to dpdk tests.
  • New crates and notable APIs

    • fixed-size: new no_std crate providing a FixedSize trait (const SIZE and write_be) with impls for unsigned integer primitives and Ipv4/Ipv6.
    • lookup: new crate with Projection and Lookup traits and impls for BTreeMap and HashMap (unit-tested).
    • match-action and match-action-derive:
      • match-action: new crate defining FieldKind, FieldSpec, MatchKey trait, predicate and rule modules, and (behind feature flags) a generator module for bolero-based value generation.
      • match-action-derive: proc-macro derive(MatchKey) that generates FIELD_SPECS, MatchKey impls, a parallel Rule struct, and helper methods; supports attributes (prefix/mask/range/exact) and generic bounds on FixedSize.
      • match-action modules added: predicate (Exact/Prefix/Mask/Range predicates, matching helpers), rule (ExactSpec/PrefixSpec/MaskSpec/RangeSpec, RuleField traits, Accepts/IsUniversal), generator (bolero-based hits/misses generators) and tests including a derive_roundtrip integration test.
    • net: added fixed_size bridge implementations (TcpPort, UdpPort, UnicastIpv4Addr, Vni) and depends on fixed-size.
  • Tests and usage

    • dpdk ACL integration tests now use #[with_eal] and type-inferred AclContext::new(...).
    • New unit and integration tests were added across the new crates (const-eval test for dpdk/acl, lookup tests, fixed-size tests, match-action derive/unit tests, predicate/generator tests).

Scope & compatibility

  • No production runtime behavior changes beyond test-only helpers and the addition of new workspace crates and public traits/types used by those crates.
  • The ACL helper change is backward compatible at runtime; it exposes a const-capable API to callers that may now compute buffer sizes at compile time.
  • This PR is part of a stacked review (merge order noted in the PR description).

Review notes

  • Reviewer qmonnet: "Sign-off tag missing" (needs address before merge).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds shared DPDK EAL initialization support for tests (via a start_eal() helper and a #[with_eal] proc-macro), and refactors an ACL helper to be usable in const contexts.

Changes:

  • Introduces dpdk::test_support::start_eal() (OnceLock-backed) to initialize EAL once per process with test-friendly flags.
  • Adds dataplane-dpdk-test-macros providing #[with_eal], and wires it into dataplane-dpdk behind a test feature.
  • Refactors AclBuildConfig::compute_min_input_size to a const fn and adds a const-context unit test; updates ACL tests to use the macro and simplifies some call sites.

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
dpdk/src/test_support.rs New shared EAL init helper for tests.
dpdk/src/lib.rs Exposes test_support in tests / test feature; re-exports #[with_eal] under feature = "test".
dpdk/src/acl/mod.rs Updates ACL unit tests to use #[with_eal]; minor doc/example tweak.
dpdk/src/acl/config.rs Makes compute_min_input_size const-friendly and adds a const-context test.
dpdk/Cargo.toml Adds test feature and optional deps/macro wiring for test harness support.
dpdk-test-macros/src/lib.rs Implements the #[with_eal] attribute macro.
dpdk-test-macros/Cargo.toml New proc-macro crate manifest.
Cargo.toml Adds new workspace member + workspace dependency entry.
Cargo.lock Locks new proc-macro crate and updated dependency graph.

Comment thread dpdk/src/acl/config.rs
@qmonnet

qmonnet commented Jun 2, 2026

Copy link
Copy Markdown
Member

Sign-off tag missing

@daniel-noland daniel-noland force-pushed the pr/daniel-noland/threading-rewrite branch from 2021414 to b7b5ece Compare June 3, 2026 19:50
@daniel-noland daniel-noland force-pushed the pr/daniel-noland/dpdk-test-eal branch from 9da6fad to 14adb95 Compare June 3, 2026 19:50
Base automatically changed from pr/daniel-noland/threading-rewrite to main June 3, 2026 21:45
@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds five new workspace crates implementing a match-action packet matching system with fixed-size field serialization, generic lookup abstractions, procedural-macro-driven key derivation, and DPDK test infrastructure.

Changes

Match-action matching infrastructure with fixed-size serialization and DPDK test support

Layer / File(s) Summary
Workspace membership, dependencies, and manifests
Cargo.toml, dpdk-test-macros/Cargo.toml, fixed-size/Cargo.toml, lookup/Cargo.toml, match-action-derive/Cargo.toml, match-action/Cargo.toml
Five new workspace crates are registered as members and path dependencies; workspace metadata entries enable miri and wasm testing for new crates.
DPDK test infrastructure and EAL initialization
dpdk-test-macros/src/lib.rs, dpdk/Cargo.toml, dpdk/src/lib.rs, dpdk/src/test_support.rs, dpdk/src/acl/config.rs, dpdk/src/acl/mod.rs
A test feature and dpdk-test-macros proc-macro are added; #[with_eal] attribute injects EAL singleton initialization into test functions; test_support computes CPU affinity and unique file prefixes; ACL tests are refactored to use the macro and type-inferred AclContext::new, with compute_min_input_size promoted to a const-capable function.
FixedSize trait and network type serializers
fixed-size/src/lib.rs, net/src/fixed_size.rs, net/src/lib.rs, net/Cargo.toml
A no_std FixedSize trait defines SIZE and big-endian write_be for byte serialization; unsigned integers, IPv4/IPv6 addresses are implemented; net adds FixedSize for TcpPort, UdpPort, UnicastIpv4Addr, and Vni with validation tests.
Projection and lookup classification
lookup/src/lib.rs
Projection trait extracts keys from inputs; Lookup trait pairs keys with associated values; classify and classify_opt methods support projection-based and optional-projection lookups; BTreeMap and HashMap implementations are provided with tests.
Rule specification types and field kinds
match-action/src/rule.rs
ExactSpec, PrefixSpec, MaskSpec, and RangeSpec are parameterized over FixedSize types; PrefixSpec::new validates prefix length against computed bit width; RangeSpec supports inclusive range conversion; Backend, IntoBackendField, Accepts, and IsUniversal traits define matching semantics.
Field predicates and matching logic
match-action/src/predicate.rs
Exact, Prefix, Mask, and Range predicate types implement byte-width-limited comparisons; FieldPredicate enum and matches method dispatch to the correct predicate variant; helper routines support masked equality, prefix masking, and big-endian range handling.
Public MatchKey trait and field metadata
match-action/src/lib.rs, match-action/src/field.rs
FieldKind enum categorizes match types (Prefix/Mask/Range/Exact); FieldSpec struct describes field name, kind, size, and byte offset; MatchKey trait requires field_specs and as_key_into for key serialization; conditional modules and re-exports support derive and bolero features.
MatchKey derive macro implementation
match-action-derive/src/lib.rs
The derive macro validates struct constraints, injects FixedSize bounds into generics, computes field byte sizes and offsets, generates FIELD_SPECS constants, implements MatchKey with buffer writing, emits a companion Rule struct with into_backend_fields/accepts/is_universal, and parses per-field attributes (prefix/mask/range/exact) to determine match kinds.
Property generation and derive roundtrip tests
match-action/src/generator.rs, match-action/tests/derive_roundtrip.rs
The generator module (bolero-gated) provides FieldHit and FieldMiss traits for spec-based value generation and predicate-level byte generators; derive_roundtrip.rs validates key byte layout, rule struct generation, field metadata, attribute defaults, generic instantiation, and Range/Option conversions across representative key types.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 A match-action system springs to life,
With fields serialized, rules precise and bright,
Fixed-size traits compose the network flow,
Derive macros weave the bytes below!
Test harness hops with EAL's graceful dance.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: introducing a test EAL harness with the with_eal macro and const fn capability for DPDK testing.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pr/daniel-noland/dpdk-test-eal

Comment @coderabbitai help to get the list of available commands and usage tips.

@daniel-noland daniel-noland force-pushed the pr/daniel-noland/dpdk-test-eal branch 5 times, most recently from 9808e5a to 0e83d83 Compare June 5, 2026 06:33
@daniel-noland daniel-noland enabled auto-merge June 5, 2026 06:39
Lets callers derive the rte_acl input-buffer size requirement from a
const FIELD_DEFS array at compile time -- useful for feeding into a
const generic (e.g. the STRIDE on a DPDK-backed Lookup type alias)
rather than rediscovering it at runtime.

The runtime path through AclBuildConfig::new still calls into the
same helper and caches the result; only the surface broadens from
fn to const fn (with the for-loop rewritten as a while-let-i index
walk that const-fn-eval can chew on).  Existing FieldExtentOverflow
validation is preserved; in a const context overflow becomes a
compile error instead of UB.

Adds one test exercising the const path: const MIN_INPUT_SIZE =
AclBuildConfig::compute_min_input_size(&DEFS); plus the assertion
that the runtime path returns the same value.

just fmt; cargo check -p dataplane-dpdk --all-targets passes.

Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Introduces shared EAL test scaffolding for downstream crates that
need to exercise rte_acl, mempools, or any other DPDK runtime under
`#[test]`.  Two pieces, both behind the new dpdk `test` feature so
production builds remain unchanged:

- dpdk-test-macros: proc-macro crate exposing #[with_eal], which
  injects `let _eal = <dpdk_crate>::test_support::start_eal();` at
  the top of a #[test] function.  Resolves the dpdk crate's path via
  proc-macro-crate so the macro works in-tree (`crate`), under the
  workspace alias (`::dpdk`), or with the canonical name
  (`::dataplane_dpdk`).
- dpdk::test_support: hosts a shared OnceLock<Eal> initialized with
  --no-huge / --no-pci / --in-memory and the cpu-affinity-aware
  --lcores derivation that was previously inline in acl/mod.rs.
  Once-per-process by construction; safe under nextest's per-test
  forking and single-process runners alike.

dpdk/src/acl/mod.rs picks up the new macro: every test in the module
loses its `let _eal = start_eal();` prologue and gains a #[with_eal]
attribute above #[test].  The inline start_eal helper and the
module-scoped OnceLock<Eal> static go away.

dpdk/Cargo.toml grows the `test` feature (turns on dpdk-test-macros
plus the id and nix runtime helpers that test_support needs) and a
self-referencing dev-dep `dataplane-dpdk = { path = ".", features =
["test"] }` so dpdk's own tests see the macro surface.

just fmt; cargo check --workspace --all-targets passes.

Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Introduces a no_std FixedSize trait carrying a known byte width plus
a big-endian write.  Lives in its own crate so value-providing
crates (`net`) and downstream key-packing frameworks (a future
`match-action` PR) can both depend on it without depending on each
other -- `net` implements FixedSize on its own newtypes without an
orphan-rule violation, and the framework doesn't need to know about
any particular value supplier.

fixed-size/src/lib.rs:

- pub trait FixedSize: Copy, with const SIZE: usize and fn
  write_be(&self, out: &mut [u8]).
- Blanket impls for the network-order primitives the framework cares
  about: u8 / u16 / u32 / u64 / u128 / Ipv4Addr / Ipv6Addr.

net/src/fixed_size.rs is the consumer bridge: FixedSize impls for
TcpPort, UdpPort, UnicastIpv4Addr, and Vni.  Each delegates write_be
to the underlying primitive's impl, so wire bytes match what writing
the raw integer would produce.  Vni is a 24-bit value written into a
4-byte field (high byte zero) because backends model fields in 1/2/4
widths, not 3.

net/Cargo.toml grows the fixed-size workspace dep; net/src/lib.rs
adds the module behind no cfg gates (it has no further dependencies
of its own).

just fmt; cargo check --workspace --all-targets passes.

Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Adds the read-only key/action lookup vocabulary downstream match-action
backends will implement.  Tiny crate by design (one trait pair, two
collection impls, inline tests) so consumer crates depend on this
without depending on each other.

lookup/src/lib.rs:

- pub trait Projection<T>: extracts a key of type T from self.
  Implemented on `&'a Source` so the lifetime threads into T when T
  borrows; for owned T the lifetime is unused.  The same source can
  implement Projection<T> for many T -- the call site picks one by
  inference from a Lookup backend's key type.
- impl<K> Projection<Option<K>> for Option<K>: identity, so
  classify_opt accepts a pre-computed Option<K> directly.  Scoped to
  Option<K> (not a general impl<T> Projection<T> for T) so a backend
  that implements Lookup<K, A> for every K can't make classify
  ambiguous.
- pub trait Lookup<K, A>: lookup(&K) -> Option<&A>, plus default
  methods classify (S: Projection<K>) and classify_opt (S:
  Projection<Option<K>>) that project and look up in one call.  Two
  trait type parameters, so one backend can serve many (K, A) pairs.
- Blanket Lookup impls for BTreeMap<K, V> and HashMap<K, V, S> so
  tests / simple consumers get a working backend out of the box.

Inline tests exercise: projection-by-table-type inference at v4
2-tuple and 4-tuple widths, lifetime threading through borrowed
projections, miss returning None, classify_opt short-circuit on None
projection, the identity Projection<Option<K>> for Option<K>, and
HashMap parity with BTreeMap.

just fmt; cargo check --workspace --all-targets passes.

Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Lands the match-action key vocabulary plus its derive.  Backends in
downstream PRs consume the result via a structural FIELD_SPECS view
and a byte-packing as_key_into(); the bolero property-test
generators land next as their own feature gate.

match-action/src/:

- lib.rs publishes the FieldKind enum (Prefix / Mask / Range /
  Exact), the FieldSpec layout record (name / kind / size / offset),
  and the MatchKey trait (N, KEY_SIZE, field_specs(),
  as_key_into()).  Trait methods take slices rather than
  Self::N-sized arrays because Self::N in a trait fn needs unstable
  generic_const_exprs; the derive emits sized inherent helpers on
  concrete types instead.
- field.rs is a thin re-export of FixedSize from the fixed-size
  crate, so consumers can refer to it via match_action::FixedSize.
- predicate.rs holds the type-erased FieldPredicate form (Prefix,
  Mask, Range, Exact variants over FieldBytes) plus the Erased
  backend marker used by the reference oracle.
- rule.rs defines the four kind-typed *Spec wrappers (PrefixSpec,
  MaskSpec, RangeSpec, ExactSpec), the Backend / Accepts /
  IntoBackendField / IsUniversal traits, and the RuleField envelope
  carrying name / spec.

match-action-derive (proc-macro crate) emits, from a struct annotated
with #[prefix] / #[mask] / #[range] / #[exact]:

- the MatchKey impl,
- a parallel <Name>Rule struct holding the typed *Spec data,
- an inherent FIELD_SPECS const for compile-time inspection by
  downstream backends, and
- (for non-generic structs) an inherent as_key() -> [u8; KEY_SIZE]
  for ergonomic byte packing.

tests/derive_roundtrip.rs exercises a 5-tuple-shaped key covering
all four predicate kinds: asserts N, KEY_SIZE, the field_specs()
content, and that as_key_into / as_key produce the expected
big-endian byte layout (with declared field ordering preserved).

just fmt; cargo check --workspace --all-targets passes.

Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Adds property-test generators over the rule wrappers, behind a new
`bolero` feature gate so the bolero dep stays out of the default
build graph.  Downstream consumers (acl property tests, future
cascade Upsert-laws harnesses) enable the feature in their
dev-deps and draw random matching / non-matching packets for any
single rule.

match-action/src/generator.rs:

- FieldHit / FieldMiss TypeGenerators yielding bytes that satisfy /
  violate a given field predicate.  Cover all four predicate kinds:
  Prefix (any address sharing the rule's high-order bits / one with
  flipped bits in that prefix), Mask (value bits match / a flipped
  in-mask bit), Range (uniform draw in [min, max] / outside the
  bounds), Exact (the value / a different value).
- Miss generators skip universal predicates (range covering all
  values, mask of all zeros, prefix length zero, etc.); the
  derive-emitted IsUniversal check on <Name>Rule lets callers detect
  whole rules that have an empty miss set.

match-action/src/lib.rs picks up #[cfg(feature = "bolero")] gates on
the generator mod + its FieldHit / FieldMiss re-exports.

match-action/Cargo.toml grows the bolero optional dep and the
`bolero` feature, which also turns on the matching forwarded feature
on match-action-derive (a no-op today, kept for future emit changes
in the derive).  match-action-derive/Cargo.toml mirrors the
`bolero` feature declaration so cargo can forward through.

just fmt; cargo check --workspace --all-targets and
cargo check -p dataplane-match-action --features bolero pass.

Signed-off-by: Daniel Noland <daniel@githedgehog.com>
@daniel-noland daniel-noland force-pushed the pr/daniel-noland/dpdk-test-eal branch from df2ad93 to 3741795 Compare June 5, 2026 06:45

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
match-action/src/rule.rs (1)

73-77: 💤 Low value

Consider adding a debug assertion for min <= max.

If a caller passes min > max, the resulting range will never match any value. Adding a debug_assert! would catch accidental inversions during development without affecting release performance.

🛠️ Suggested defensive check
 impl<T: FixedSize> RangeSpec<T> {
     #[must_use]
     pub const fn new(min: T, max: T) -> Self {
+        // Note: Can't use debug_assert! in const fn, but consider
+        // a non-const variant or runtime check if ordering matters.
         Self { min, max }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@match-action/src/rule.rs` around lines 73 - 77, Add a debug assertion in
RangeSpec::new to catch inverted bounds: when constructing RangeSpec<T> (impl<T:
FixedSize> RangeSpec<T>::new), insert a debug_assert!(min <= max) so accidental
calls with min > max are detected in debug builds without affecting release
performance.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@match-action/src/generator.rs`:
- Around line 265-300: PredicateHitsBytes::generate assumes range bounds fit in
4 bytes via be_to_u32/u32_to_be; add an explicit guard in the range branch
(inside PredicateHitsBytes::generate where you call self.pred.as_range()) to
check min.len() and max.len() (or just min.len()) <= 4 and return None if they
are larger, or alternatively handle wider sizes via a larger integer conversion
path; update predicate_hits_bytes/PredicateHitsBytes generate to perform this
length check before calling be_to_u32/u32_to_be so the method fails safely
instead of panicking.

---

Nitpick comments:
In `@match-action/src/rule.rs`:
- Around line 73-77: Add a debug assertion in RangeSpec::new to catch inverted
bounds: when constructing RangeSpec<T> (impl<T: FixedSize> RangeSpec<T>::new),
insert a debug_assert!(min <= max) so accidental calls with min > max are
detected in debug builds without affecting release performance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3c038aac-439e-433e-89f9-2d45b1181310

📥 Commits

Reviewing files that changed from the base of the PR and between 9808e5a and 0e83d83.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (24)
  • Cargo.toml
  • dpdk-test-macros/Cargo.toml
  • dpdk-test-macros/src/lib.rs
  • dpdk/Cargo.toml
  • dpdk/src/acl/config.rs
  • dpdk/src/acl/mod.rs
  • dpdk/src/lib.rs
  • dpdk/src/test_support.rs
  • fixed-size/Cargo.toml
  • fixed-size/src/lib.rs
  • lookup/Cargo.toml
  • lookup/src/lib.rs
  • match-action-derive/Cargo.toml
  • match-action-derive/src/lib.rs
  • match-action/Cargo.toml
  • match-action/src/field.rs
  • match-action/src/generator.rs
  • match-action/src/lib.rs
  • match-action/src/predicate.rs
  • match-action/src/rule.rs
  • match-action/tests/derive_roundtrip.rs
  • net/Cargo.toml
  • net/src/fixed_size.rs
  • net/src/lib.rs
✅ Files skipped from review due to trivial changes (4)
  • match-action-derive/Cargo.toml
  • match-action/Cargo.toml
  • match-action/tests/derive_roundtrip.rs
  • net/Cargo.toml
🚧 Files skipped from review as they are similar to previous changes (13)
  • net/src/lib.rs
  • dpdk/src/lib.rs
  • fixed-size/Cargo.toml
  • dpdk-test-macros/Cargo.toml
  • lookup/Cargo.toml
  • fixed-size/src/lib.rs
  • dpdk-test-macros/src/lib.rs
  • dpdk/Cargo.toml
  • net/src/fixed_size.rs
  • dpdk/src/test_support.rs
  • dpdk/src/acl/config.rs
  • lookup/src/lib.rs
  • dpdk/src/acl/mod.rs

Comment on lines +265 to +300
#[must_use]
pub fn predicate_hits_bytes(pred: FieldPredicate) -> PredicateHitsBytes {
PredicateHitsBytes { pred }
}
#[must_use]
pub fn predicate_misses_bytes(pred: FieldPredicate) -> PredicateMissesBytes {
let universal = predicate_is_universal(&pred);
PredicateMissesBytes { pred, universal }
}

pub struct PredicateHitsBytes {
pred: FieldPredicate,
}

impl ValueGenerator for PredicateHitsBytes {
type Output = Vec<u8>;
fn generate<D: Driver>(&self, d: &mut D) -> Option<Vec<u8>> {
if let Some(value) = self.pred.as_exact() {
Some(value.to_vec())
} else if let Some((value, prefix_len)) = self.pred.as_prefix() {
let mut buf = draw_bytes(d, value.len())?;
splat_prefix(&mut buf, value, prefix_len);
Some(buf)
} else if let Some((value, mask)) = self.pred.as_mask() {
let mut buf = draw_bytes(d, value.len())?;
splat_under_mask(&mut buf, value, mask);
Some(buf)
} else if let Some((min, max)) = self.pred.as_range() {
let lo = be_to_u32(min);
let hi = be_to_u32(max);
let v = (lo..=hi).generate(d)?;
Some(u32_to_be(v, min.len()))
} else {
None
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if RangeSpec is ever used with types larger than 4 bytes
rg -n 'RangeSpec<.*(u64|u128|Ipv6)' --type rust

Repository: githedgehog/dataplane

Length of output: 47


Document/guard the 4-byte assumption for range predicate byte generation.

PredicateHitsBytes converts range bounds via be_to_u32/u32_to_be, so FieldPredicate::Range must not carry >4-byte widths (or generate() will blow up instead of returning None). A repo search for RangeSpec<...u64|u128|Ipv6> found no matches, suggesting wider ranges aren’t currently used, but it’s still worth documenting the constraint or adding an explicit guard.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@match-action/src/generator.rs` around lines 265 - 300,
PredicateHitsBytes::generate assumes range bounds fit in 4 bytes via
be_to_u32/u32_to_be; add an explicit guard in the range branch (inside
PredicateHitsBytes::generate where you call self.pred.as_range()) to check
min.len() and max.len() (or just min.len()) <= 4 and return None if they are
larger, or alternatively handle wider sizes via a larger integer conversion
path; update predicate_hits_bytes/PredicateHitsBytes generate to perform this
length check before calling be_to_u32/u32_to_be so the method fails safely
instead of panicking.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@fixed-size/src/lib.rs`:
- Around line 15-18: Document that FixedSize::write_be requires out.len() >=
Self::SIZE and update the trait doc comment to state this precondition; then
either add debug checks in implementations (insert debug_assert!(out.len() >=
Self::SIZE) at the top of each write_be impl) or change the trait signature (fn
write_be(&self, out: &mut [u8]) -> Result<(), BufferTooShortError>) and update
all implementations and callers accordingly so buffer-short conditions are
handled without violating #![deny(clippy::panic)].
- Around line 20-53: The write_be implementations for the FixedSize trait (for
u8, u16, u32, u64, u128 and the Ipv4Addr/Ipv6Addr impls) lack bounds checks and
will panic when out.len() < Self::SIZE; add a defensive check at the start of
each impl's write_be method such as a debug_assert!(out.len() >= Self::SIZE) (or
equivalent non-panicking guard) to validate the buffer length before writing,
ensuring the implementations of FixedSize::write_be perform the length
verification.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: abe2ddf8-84e7-4559-8da9-f75fdbd4a789

📥 Commits

Reviewing files that changed from the base of the PR and between 0e83d83 and 3741795.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (24)
  • Cargo.toml
  • dpdk-test-macros/Cargo.toml
  • dpdk-test-macros/src/lib.rs
  • dpdk/Cargo.toml
  • dpdk/src/acl/config.rs
  • dpdk/src/acl/mod.rs
  • dpdk/src/lib.rs
  • dpdk/src/test_support.rs
  • fixed-size/Cargo.toml
  • fixed-size/src/lib.rs
  • lookup/Cargo.toml
  • lookup/src/lib.rs
  • match-action-derive/Cargo.toml
  • match-action-derive/src/lib.rs
  • match-action/Cargo.toml
  • match-action/src/field.rs
  • match-action/src/generator.rs
  • match-action/src/lib.rs
  • match-action/src/predicate.rs
  • match-action/src/rule.rs
  • match-action/tests/derive_roundtrip.rs
  • net/Cargo.toml
  • net/src/fixed_size.rs
  • net/src/lib.rs
✅ Files skipped from review due to trivial changes (4)
  • lookup/Cargo.toml
  • fixed-size/Cargo.toml
  • net/Cargo.toml
  • match-action-derive/Cargo.toml
🚧 Files skipped from review as they are similar to previous changes (18)
  • match-action/Cargo.toml
  • dpdk/src/lib.rs
  • dpdk-test-macros/Cargo.toml
  • net/src/fixed_size.rs
  • lookup/src/lib.rs
  • match-action/src/field.rs
  • net/src/lib.rs
  • dpdk/src/acl/config.rs
  • dpdk/Cargo.toml
  • dpdk-test-macros/src/lib.rs
  • Cargo.toml
  • match-action/tests/derive_roundtrip.rs
  • dpdk/src/test_support.rs
  • match-action/src/lib.rs
  • match-action/src/predicate.rs
  • match-action/src/generator.rs
  • match-action-derive/src/lib.rs
  • match-action/src/rule.rs

Comment thread fixed-size/src/lib.rs
Comment on lines +15 to +18
pub trait FixedSize: Copy {
const SIZE: usize;
fn write_be(&self, out: &mut [u8]);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Document the buffer size precondition and consider adding safety checks.

The trait lacks documentation explaining that write_be requires out.len() >= Self::SIZE. All implementations will panic if this precondition is violated, which contradicts the #![deny(clippy::panic)] lint on line 11.

Consider:

  1. Adding comprehensive trait documentation explaining the write_be precondition
  2. Adding debug_assert!(out.len() >= Self::SIZE) in each implementation (panics only in debug builds)
  3. Or changing the signature to return Result<(), BufferTooShortError> for graceful handling
📋 Proposed trait documentation
+/// A trait for types that can be serialized to a fixed-size big-endian byte representation.
+///
+/// # Contract
+///
+/// - `SIZE` specifies the exact number of bytes required for serialization
+/// - `write_be` writes exactly `SIZE` bytes in big-endian (network) byte order
+/// - The caller **must** ensure `out.len() >= Self::SIZE` before calling `write_be`
+///
+/// # Panics
+///
+/// Implementations will panic if `out.len() < Self::SIZE`.
 pub trait FixedSize: Copy {
     const SIZE: usize;
+    /// Serialize `self` into the first `Self::SIZE` bytes of `out` in big-endian order.
+    ///
+    /// # Panics
+    ///
+    /// Panics if `out.len() < Self::SIZE`.
     fn write_be(&self, out: &mut [u8]);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fixed-size/src/lib.rs` around lines 15 - 18, Document that
FixedSize::write_be requires out.len() >= Self::SIZE and update the trait doc
comment to state this precondition; then either add debug checks in
implementations (insert debug_assert!(out.len() >= Self::SIZE) at the top of
each write_be impl) or change the trait signature (fn write_be(&self, out: &mut
[u8]) -> Result<(), BufferTooShortError>) and update all implementations and
callers accordingly so buffer-short conditions are handled without violating
#![deny(clippy::panic)].

Comment thread fixed-size/src/lib.rs
Comment on lines +20 to +53
impl FixedSize for u8 {
const SIZE: usize = 1;
fn write_be(&self, out: &mut [u8]) {
out[0] = *self;
}
}

impl FixedSize for u16 {
const SIZE: usize = 2;
fn write_be(&self, out: &mut [u8]) {
out[..Self::SIZE].copy_from_slice(&self.to_be_bytes());
}
}

impl FixedSize for u32 {
const SIZE: usize = 4;
fn write_be(&self, out: &mut [u8]) {
out[..Self::SIZE].copy_from_slice(&self.to_be_bytes());
}
}

impl FixedSize for u64 {
const SIZE: usize = 8;
fn write_be(&self, out: &mut [u8]) {
out[..Self::SIZE].copy_from_slice(&self.to_be_bytes());
}
}

impl FixedSize for u128 {
const SIZE: usize = 16;
fn write_be(&self, out: &mut [u8]) {
out[..Self::SIZE].copy_from_slice(&self.to_be_bytes());
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Add defensive bounds checking to prevent panics on invalid input.

Each implementation will panic if out.len() < Self::SIZE, but there's no validation. This violates the #![deny(clippy::panic)] directive at line 11.

🛡️ Proposed fix with debug assertions

Example pattern for all implementations:

 impl FixedSize for u8 {
     const SIZE: usize = 1;
     fn write_be(&self, out: &mut [u8]) {
+        debug_assert!(
+            out.len() >= Self::SIZE,
+            "write_be: buffer too short (need {}, got {})",
+            Self::SIZE,
+            out.len()
+        );
         out[0] = *self;
     }
 }

 impl FixedSize for u16 {
     const SIZE: usize = 2;
     fn write_be(&self, out: &mut [u8]) {
+        debug_assert!(
+            out.len() >= Self::SIZE,
+            "write_be: buffer too short (need {}, got {})",
+            Self::SIZE,
+            out.len()
+        );
         out[..Self::SIZE].copy_from_slice(&self.to_be_bytes());
     }
 }

Apply the same pattern to u32, u64, u128, Ipv4Addr, and Ipv6Addr.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fixed-size/src/lib.rs` around lines 20 - 53, The write_be implementations for
the FixedSize trait (for u8, u16, u32, u64, u128 and the Ipv4Addr/Ipv6Addr
impls) lack bounds checks and will panic when out.len() < Self::SIZE; add a
defensive check at the start of each impl's write_be method such as a
debug_assert!(out.len() >= Self::SIZE) (or equivalent non-panicking guard) to
validate the buffer length before writing, ensuring the implementations of
FixedSize::write_be perform the length verification.

@daniel-noland daniel-noland added this pull request to the merge queue Jun 5, 2026
Merged via the queue into main with commit b35b0d0 Jun 5, 2026
35 checks passed
@daniel-noland daniel-noland deleted the pr/daniel-noland/dpdk-test-eal branch June 5, 2026 07:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants