Skip to content

Releases: Zious11/wirerust

v0.11.0

29 Jun 12:48
3072e82

Choose a tag to compare

[0.11.0] - 2026-06-29

Added

  • EtherNet/IP (ENIP) + CIP protocol analyzer — the headline feature of this release
    (Feature #316, STORY-130..139, PRs #317#334, ADR-010). wirerust now analyzes TCP/44818
    flows using the ODVA EtherNet/IP + Common Industrial Protocol (CIP) stack. The analyzer
    is enabled with --enip (also covered by --all) and requires stream reassembly.

    Protocol coverage:

    • Parses the 24-byte ENIP encapsulation header (all fields, little-endian per ODVA
      specification): command, length, session_handle, status, sender_context, options.
      [STORY-130, PR #317, BC-2.17.001/002]
    • Classifies all 65,536 possible u16 command values into the 9 ODVA known commands
      (ListServices, ListIdentity, ListInterfaces, RegisterSession, UnRegisterSession,
      SendRRData, SendUnitData, IndicateStatus, Cancel) plus an Unknown catch-all.
      [STORY-130, PR #317, BC-2.17.004]
    • Parses Common Packet Format (CPF) item lists from SendRRData (0x006F) and
      SendUnitData (0x0070) payloads: bounded item-count walk, type_id recognition for
      Null Address (0x0000), Connected Address (0x00A1), Connected Data (0x00B1), and
      Unconnected Data (0x00B2) items. CIP service extraction and request-path segment
      parse apply to Unconnected Data Items (0x00B2) only in this release.
      [STORY-132, PR #319, BC-2.17.005/006/007/009]
    • Dispatched as Rule 7 in the StreamDispatcher — port-44818 fallback after the
      existing TLS, HTTP, Modbus (port 502), and DNP3 (port 20000) rules. Content-signature
      rules (TLS record, HTTP prefix) take priority. [STORY-131, PR #318, ADR-010 Decision 1]
    • Per-flow state (EnipFlowState) with a 600-byte per-direction carry buffer
      (carry_c2s / carry_s2c), frame-walk loop, and session summary folded at
      capture end. [STORY-136/137/138, PRs #326#329, BC-2.17.016/017/021]

    CLI flags:

    • --enip — enable EtherNet/IP TCP analysis (default-off; included by --all)
    • --enip-write-burst-threshold N — T0836 write-burst threshold: fires when more than
      N CIP write-class service requests (SetAttributesAll, SetAttributeList,
      SetAttributeSingle) are observed in any 1-second window per flow (default: 50)
    • --enip-error-burst-threshold M — T0888 error-burst threshold: fires when more than
      M CIP error responses (non-zero general_status) are observed in any 10-second window
      per flow (default: 5; strict > semantics)

    MITRE ATT&CK for ICS detections (ics-attack-19.1):

    • T0846 Remote System Discovery — emitted per flow on the first ENIP ListIdentity
      (command 0x0063) frame; one-shot guard per flow. [STORY-134, PR #323, BC-2.17.010]
    • T0888 Remote System Information Discovery — two detection patterns:
      Pattern A: CIP GetAttribute{All,List,Single} request targeting Identity Object
      (Class 0x01) in the request path; Pattern B: CIP error-response burst exceeding
      --enip-error-burst-threshold within a 10-second window. [STORY-134/135, PRs #323/#324,
      BC-2.17.014]
    • T0858 Change Operating Mode — emitted per CIP Stop service (service code 0x07)
      request, indicating a controller run-to-stop transition command. [STORY-135, PR #324,
      BC-2.17.011]
    • T0816 Device Restart/Shutdown — emitted per CIP Reset service (service code 0x05)
      request. [STORY-135, PR #324, BC-2.17.013]
    • T0836 Modify Parameter — emitted when CIP write-class services (SetAttributesAll
      0x02, SetAttributeList 0x04, SetAttributeSingle 0x10) exceed
      --enip-write-burst-threshold within a 1-second window per flow. [STORY-135, PR #324,
      BC-2.17.012]
    • T0814 Denial of Service — malformed-frame anomaly; fires when 3 or more
      structurally invalid ENIP frames accumulate in a 300-second window per flow. Shared
      technique ID with the DNP3/Modbus analyzers. [STORY-137, PR #327, BC-2.17.018]

    New MitreTactic::IcsExecution enum variant added (TA0104) for T0858 "Change Operating
    Mode". MITRE catalog grew from 25 to 28 seeded technique IDs; emitted count grew from
    17 to 20 (T0858, T0816, T0846 added to the emitted set). T0846 promoted from
    seeded-only to emitted for the first time via ENIP ListIdentity detection.
    [STORY-133, PR #320, BC-2.10.008, VP-007]

    Session summary (enip_summary): summarize() produces a 7-key JSON object —
    command_distribution, total_pdu_count, parse_errors, write_count,
    error_count, flows_analyzed, dropped_findings — folding both closed and
    still-open flows at call time. [STORY-138, PR #329, BC-2.17.021]

    Formal verification and quality assurance:

    • VP-032 Kani proof harnesses Sub-A through Sub-D: parse_enip_header all-input
      safety, classify_enip_command totality, is_valid_enip_frame biconditional,
      classify_cip_service totality. [STORY-130/132, BC-2.17.001–004/007]
    • fuzz_enip_cip_parse cargo-fuzz harness covering parse_cpf_items,
      parse_cip_header, parse_cip_request_path, and parse_enip_header — F-P9-002
      obligation discharged. [PR #332]
    • Full-pipeline E2E tests against real ENIP/CIP pcaps: holdout scenarios HS-110
      through HS-122 verified (6 test cases, real-world captures). [PR #333]

Changed

  • ENIP session summary wire format cleaned up. The enip_summary JSON output uses
    the canonical key name "parse_errors" (not "total_parse_errors") from day one,
    consistent with the lesson learned from the DNP3 rename in v0.10.0. The summary wire
    format was further cleaned up to ensure consistent field ordering and null-safety.
    [PR #331, BC-2.17.021 Invariant 1]

  • Green-doc-tense CI gate added. A new CI job (green-doc-tense-gate) runs
    bin/check-green-doc-tense on all tracked source and test files, failing if any
    doc-comment or changelog entry uses aspirational tense markers ("will", "planned",
    "future") in contexts that assert current behavior. The gate includes a self-test
    (bin/test_check_green_doc_tense.py) that verifies 10 known-bad and 14 known-good
    patterns. [PR #321, b9b2e93]

Fixed

  • ENIP source-IP attribution corrected. The per-direction source-IP resolution
    in on_data was incorrect: it used a port-44818 heuristic that misidentified the
    client when the FlowKey's lower port was 44818. Replaced with direction-based
    attribution (Direction::ClientToServer maps to the TCP initiator; ServerToClient
    maps to the TCP responder), mirroring the Modbus pattern. Finding source_ip fields
    now correctly reflect the sending endpoint. [PR #328, AC-139-002]

  • ENIP summarize() includes still-open flows. summarize() previously reported
    only counters accumulated from closed flows, silently undercounting total_pdu_count,
    flows_analyzed, parse_errors, and command_distribution whenever flows were still
    open at capture end. The summary now folds all still-open EnipFlowState entries into
    the aggregate at call time (RULING-W61-001). [PR #330, BC-2.17.021 Postcondition 1]

  • Modbus EC-X1: per-direction carry buffer split (carry_c2s / carry_s2c).
    The Modbus analyzer previously used a single shared carry buffer for both directions, allowing
    a response packet's trailing bytes to be spliced into the next request's reassembly window
    (cross-direction carry-buffer contamination). The carry buffer is now split into two
    independent fields keyed by direction, eliminating the splice. [STORY-141, PR #336,
    BC-2.14.EC-X1]

  • Modbus EC-X2: saturating_sub for clock-backwards window reset.
    A non-monotonic timestamp (e.g. packet re-ordering or NTP step) caused the time-delta
    computation in the Modbus window-reset path to underflow (wrapping subtraction on an unsigned
    value). The subtraction now uses saturating_sub, preventing the underflow and keeping the
    window-reset logic correct when clocks move backwards. [STORY-141, PR #336, BC-2.14.EC-X2]

  • DNP3 EC-X1: per-direction carry buffer split (carry_c2s / carry_s2c).
    Same cross-direction carry-buffer splice fix applied to the DNP3 analyzer. [STORY-140,
    PR #335, BC-2.15.EC-X1]

  • DNP3 EC-X2: saturating_sub for clock-backwards window reset.
    Same saturating subtraction fix applied to the DNP3 window-reset path. [STORY-140, PR #335,
    BC-2.15.EC-X2]

  • DNP3 desync-latch: complete-predicate gated on frame_count == 0.
    The DNP3 desync-latch complete-predicate fired unconditionally, which could produce a spurious
    desync event on the very first frame of a session before any real desync had occurred. The
    predicate is now gated on frame_count == 0 so it only triggers after at least one valid
    frame has been observed. [STORY-142, PR #336, BC-2.15.DESYNC]

  • ENIP EC-X1: per-direction carry buffer split (carry_c2s / carry_s2c).
    Same cross-direction carry-buffer splice fix applied to the EtherNet/IP analyzer. [STORY-139,
    PR #334, BC-2.17.EC-X1]

  • ENIP EC-X2: saturating_sub for clock-backwards window reset.
    Same saturating subtraction fix applied to the ENIP window-reset path. [STORY-139, PR #334,
    BC-2.17.EC-X2]

v0.10.0

24 Jun 15:23
0cbe922

Choose a tag to compare

Breaking Changes

  • DNP3 analyzer output: renamed summary key total_parse_errorsparse_errors.
    The detail map produced by the DNP3 analyzer now uses the key "parse_errors" instead of
    "total_parse_errors", aligning DNP3 with sibling analyzers (HTTP, TLS, Modbus) that already
    use "parse_errors". JSON consumers reading DNP3 summary output must migrate the key name.
    [PC-014, BC-2.15.020 v1.4, STORY-108 AC-010]

    Migration: Replace any lookup of detail["total_parse_errors"] with
    detail["parse_errors"] in your consumer. For jq users:
    jq '.[] | .detail.total_parse_errors'jq '.[] | .detail.parse_errors'.

v0.9.4

23 Jun 20:03
96b49e8

Choose a tag to compare

Added

  • Per-finding mitre_attack JSON array for SIEM consumers (issue #64). Each finding in JSON
    output now carries a mitre_attack array. Every element is an object with the fields id,
    name, tactic_id, tactic_name, and reference, resolved from the static MITRE catalog at
    report time. Downstream SIEM ingestion pipelines can consume structured technique metadata
    directly without maintaining a separate ID-to-name lookup.

Fixed

  • ICS-matrix tactic IDs corrected for ICS techniques. ICS techniques previously emitted
    Enterprise-matrix tactic IDs; they now emit the correct ICS-matrix tactic IDs. Three new ICS
    tactic variants were added: IcsDiscovery (TA0102), IcsCollection (TA0100), and
    IcsCommandAndControl (TA0101). Two technique-to-tactic mappings were corrected:
    • T0830 Adversary-in-the-Middle reclassified from its previous tactic to Collection
      (TA0100)
      .
    • T0831 Manipulation of Control reclassified from its previous tactic to Impact
      (TA0105)
      .

Docs

  • Corrected the ARP tactic column in README to reflect the updated ICS-matrix tactic assignments.
  • Superseded the stale MITRE mapping design doc; current behavior is authoritative.

v0.9.3

22 Jun 21:18
2dbf461

Choose a tag to compare

Added

  • pcapng capture-format reader. wirerust now reads pcapng files in addition to classic
    pcap. Format is detected by a magic-byte probe on the first four bytes of the file
    (pcapng SHB magic 0x0A0D0D0A), so pcapng files are accepted regardless of file
    extension — including when passing a directory, where the file list is now built by
    magic-byte detection rather than by extension filter alone (.pcapng files were
    previously excluded from directory expansion).

    The reader parses four block types:

    • SHB (Section Header Block) — both big- and little-endian byte orders.
    • IDB (Interface Description Block) — up to 65,535 interfaces per file; all
      interfaces in a single file must share the same link type. The if_tsresol IDB
      option (code 9) is parsed to determine timestamp resolution; nanosecond captures
      (e.g. if_tsresol = 0x09) are converted correctly to microseconds for analysis.
    • EPB (Enhanced Packet Block) — packet data, interface ID lookup, and per-packet
      timestamp reconstruction using the interface's if_tsresol.
    • SPB (Simple Packet Block) — parsed and yielded as packets with no timestamp
      (SPB carries no timestamp field).

    The following block types are silently skipped: NRB (Name Resolution Block), ISB
    (Interface Statistics Block), DSB (Decryption Secrets Block), OPB (Obsolete Packet
    Block), and any unrecognized block type. Multi-section files (a second SHB) are
    rejected — use mergecap or editcap to re-save as a single-section file.

    The same five link types supported for classic pcap (Ethernet 1, Raw IP 101, Linux
    Cooked/SLL 113, IPv4 228, IPv6 229) are supported for pcapng.

    A 4 GiB per-file size cap (E-INP-014) is enforced via fstat on the already-open
    file descriptor before the full file is loaded into memory.

  • PcapSource::is_pcapng discriminant field. The PcapSource struct now carries
    a public is_pcapng: bool field that is true when the file was identified as pcapng
    by magic-byte detection. Used internally for the zero-packet notice wording
    ("pcapng file" vs. "pcap file").

  • Per-file error isolation for batch analysis. When analyzing a directory, a parse
    error or read failure on one file is reported to stderr and skipped; remaining files
    in the batch continue to be processed. Files that parse successfully but contain zero
    packets emit a notice to stderr: "notice: <path>: 0 packets read from <pcap|pcapng>
    file", with the OPB-clause appended when the file contained Obsolete Packet Blocks
    that were skipped.

  • New input-validation error codes (pcapng-specific guards):

    Code Condition
    E-INP-010 pcapng block framing rejection — crate-level framing error (btl misaligned, EOF mid-block, zero-advance forward-progress stall) or EPB interface ID out of range on a non-empty interface table.
    E-INP-011 Multi-IDB link-type conflict — a subsequent Interface Description Block declares a link type that differs from the first interface's link type.
    E-INP-012 Second Section Header Block — multi-section pcapng files are not supported.
    E-INP-013 IDB after first packet block — an Interface Description Block appears after the first EPB or SPB has already been emitted, an ordering not supported by wirerust.
    E-INP-014 File too large — pcapng file exceeds the 4 GiB in-memory limit; message instructs the user to split the capture or use a streaming tool.
    E-INP-015 Interface table cap exceeded — pcapng file declares more than 65,535 Interface Description Blocks.

    (Codes E-INP-008 and E-INP-009 — SHB/IDB/EPB body-too-short and empty interface
    table, respectively — were also introduced in this delta as part of the pcapng reader
    but do not appear in the above table as they describe internal structural failures
    rather than user-actionable input constraints.)

Fixed

  • TCP reassembly CWE-407 null-eviction storm (PR #298). When the flow table reached
    max_flows and a new flow arrived, the eviction loop's break condition (<= max_flows)
    fired immediately on the first iteration, causing an O(F log F) sort with zero flows
    actually evicted. On captures with frozen or duplicate timestamps — where the
    time-based idle expiry never fires — every new flow beyond the cap triggered a full
    sort with no eviction, producing quadratic behavior. On a 120,000-flow
    frozen-timestamp capture the wall time was ~75 s before this fix.

    Three mitigations were applied:

    • R1 (CWE-401 zombie segments): Segments whose end offset lies strictly below the
      reassembly flush cursor are now rejected instead of being inserted into the gap map,
      preventing unbounded zombie segment accumulation.
    • R2 (null-eviction storm fix): The break condition changed from <= max_flows to
      < max_flows, ensuring at least one flow is evicted on each eviction call.
    • R3 (batch eviction to headroom): max_flows-triggered eviction now evicts down
      to 90% of max_flows in one call (headroom target = max(1, max_flows * 9 / 10)),
      amortizing the O(F log F) sort across the next ~10% of new-flow admissions. The same
      120,000-flow frozen-timestamp scenario completes in ~0.76 s after these fixes.
  • R4 packet-index cadence expiry (defense-in-depth for frozen timestamps). A
    packet-index sweep runs every N packets (expiry_sweep_interval, configurable) and
    expires flows idle for more than idle_packet_threshold packets, independent of
    capture timestamps. This ensures idle flows are reclaimed even on captures where all
    packet timestamps are identical or otherwise frozen.

  • read_magic short-read race eliminated. The magic-byte probe used by directory
    expansion previously called read() and accepted a short read as a valid result, meaning
    a file with exactly 4 bytes might not return all four bytes on a single read() call.
    Changed to read_exact(), which either fills the buffer or returns an error, so files
    shorter than 4 bytes correctly return None and files of exactly 4 bytes are read
    reliably.

  • pcapng block-walk forward-progress guard (CWE-835). The block-walk loop now
    checks that the parser advances after each block; a zero-advance result is treated as a
    framing anomaly (E-INP-010) rather than looping indefinitely.

  • pcapng file-size gate uses fstat on the open fd (CWE-367 advisory). The size
    check now calls metadata() on the already-open file descriptor rather than a second
    path-based stat() call, closing the TOCTOU window between magic-byte detection and
    size enforcement.

  • pcapng IDB options TLV parsed with section endianness. The parse_idb_options
    function previously read option TLV fields as fixed little-endian. It now uses the
    section endianness (big or little) detected from the SHB byte-order magic, so
    if_tsresol and other IDB options are decoded correctly from big-endian pcapng files.

Security

  • CWE-407 + CWE-401 mitigated in the TCP reassembly engine (see Fixed — PR #298).
  • CWE-835 forward-progress guard added to the pcapng block-walk loop.
  • CWE-367 TOCTOU window for pcapng file-size gate closed by switching to fstat on
    the open file descriptor.
  • Block sequence counter in the pcapng block-walk uses saturating_add to prevent
    wraparound (SEC-005).

v0.9.2

19 Jun 23:16
b73b242

Choose a tag to compare

Fixed

  • DNP3 control_operation_counts was non-deterministic across process runs.
    Dnp3Analyzer::summarize() previously called self.flows.values().enumerate()
    over a HashMap<FlowKey, Dnp3FlowState>. Because HashMap uses a per-process
    random seed (HashBrown), the iteration order changed each run, causing the
    flow index assigned by enumerate() to map to a different flow on every
    invocation. The BTreeMap key-sort masked the issue at the key level (keys
    "0".."N-1" were always sorted), but the VALUE at each key was
    non-deterministic. Running wirerust analyze <dnp3-capture> --all twice on the
    same file produced different control_operation_counts output (confirmed on a
    real 26K-packet DNP3 capture in post-release e2e testing).

    Fix: derive Ord + PartialOrd on FlowKey (lexicographic order on
    (lower_ip, lower_port, upper_ip, upper_port); IpAddr and u16 both
    implement Ord). In summarize(), sort flows.iter() by FlowKey before
    enumerate(), so index→value assignment is stable across all process runs.
    JSON schema is unchanged — keys remain "0".."N-1" strings in a BTreeMap.
    Traces to BC-2.15.020 postcondition 1.

v0.9.1

19 Jun 22:44
ad4eec8

Choose a tag to compare

Fixed

  • --no-collapse help text and README referenced non-existent flags
    --output json / --output csv.
    There is no --output flag in wirerust;
    the real flags are --json <FILE>, --csv <FILE>, and
    --output-format <fmt>. The doc-comment in src/cli.rs and the corresponding
    line in README.md both said "Has no effect on --output json or --output csv."
    Corrected to "Has no effect on --json, --csv, or --output-format json|csv
    output." Behavior is unchanged — JSON and CSV output were already
    collapse-invariant; only the help text wording was wrong.

v0.9.0

19 Jun 21:48
986e148

Choose a tag to compare

Changed (BREAKING)

  • TerminalReporter findings-render mode: two bools → FindingsRender enum → FindingsRender
    struct of two orthogonal enums (STORY-120 PR #266, STORY-122/A PR #268).

    This entry supersedes the three-variant enum description that shipped in an earlier 0.9.0
    pre-release entry.

    Phase 1 (STORY-120, PR #266): The show_mitre_grouping: bool and collapse_findings: bool
    public fields on TerminalReporter were removed and replaced by a single render: FindingsRender
    field typed as a three-variant enum (Grouped, FlatCollapsed, FlatExpanded).

    Phase 2 (STORY-122/A, PR #268): FindingsRender was reshaped from a three-variant enum into
    a struct of two orthogonal enums: { grouping: Grouping, collapse: Collapse }. The
    Grouping enum has variants Grouped and Flat; the Collapse enum has variants Collapsed
    and Expanded. All four combinations are valid. The three named enum variants (Grouped,
    FlatCollapsed, FlatExpanded) no longer exist. Per RFC 1105 this is an additional breaking
    change: any code that matched or constructed the three-variant enum must migrate to the
    two-field struct. The 0.8.x → 0.9.0 minor bump covers both phases.

    Forward-compatibility (F7-R2): Grouping, Collapse, and FindingsRender (in
    wirerust::reporter::terminal) are now marked #[non_exhaustive], allowing future
    variants or fields to be added without a semver-breaking change. Because FindingsRender
    is #[non_exhaustive], external crates must construct it via the new
    FindingsRender::new(grouping, collapse) constructor rather than a struct literal
    (struct-literal construction of a #[non_exhaustive] struct is rejected by the compiler
    outside the defining crate).

Changed

  • --mitre now collapses identical findings within each MITRE tactic bucket by default
    (STORY-119/B, PR #269).
    When --mitre is passed, wirerust analyze routes output through
    the new render_findings_grouped_collapsed path, which groups identical findings (same category,
    verdict, confidence, summary) within each tactic bucket into a single line with a (xN) count
    suffix and up to K=3 representative evidence samples. Singletons render without a count suffix.
    Terminal output for --mitre is no longer byte-identical to the pre-0.9.0 grouped output.
    JSON and CSV output are unaffected.

  • --no-collapse is now dual-scope (STORY-119/B, PR #269). Previously --no-collapse
    suppressed collapse only in flat (non---mitre) mode. It now suppresses collapse in both flat
    and grouped (--mitre) modes. Passing --no-collapse restores one-line-per-finding output
    regardless of whether --mitre is also passed.

v0.8.0

18 Jun 02:29
73034da

Choose a tag to compare

Added

  • --no-collapse flag for wirerust analyze to opt out of terminal finding-collapse (closes
    #259, STORY-118). Pass --no-collapse to restore the pre-v0.8.0 one-line-per-finding output.

Changed

  • Terminal analyze output now collapses repeated findings by default. Findings that share
    the same (category, verdict, confidence, summary) are collapsed into a single line with a
    (xN) count suffix and up to 3 representative evidence samples (K=3). This is a
    display-layer-only behavioral change: JSON and CSV output are unaffected, and
    --mitre-grouped mode is also unchanged (grouped-mode collapse is deferred to a future
    release). Pass --no-collapse to disable. Governed by ADR-0003 Display-Layer Aggregation.

v0.7.1

17 Jun 14:03
b98a72f

Choose a tag to compare

Added

  • Regression test coverage for VLAN / QinQ (802.1ad double-tag) / MACsec link-extension ARP
    offset handling — 10 tests across tests/bc_2_16_qinq_macsec_offset_tests.rs and
    tests/bc_2_16_e17_macsec_offset_tests.rs (issue #253, STORY-116/117). Includes an
    off-by-8 SCI-accounting guard for MACsec-tagged ARP.

Notes

  • No runtime behavior change: the VLAN/QinQ/MACsec offset handling itself shipped in 0.7.0;
    this release adds regression guards. MACsec-over-ARP offset correctness is proven by
    etherparse source + upstream proptests + synthetic tests and is documented as an
    evidence-backed limitation (no public on-wire MACsec+ARP capture exists).

v0.7.0

16 Jun 20:28
dd8e142

Choose a tag to compare

Added

  • ARP Security Analyzer (issue #9, epic E-16) for link-layer and OT network forensics.
    Detects five threat classes with MITRE ATT&CK attribution:

    • D1 ARP spoofing — binding-conflict detection with MEDIUM→HIGH severity escalation
      (configurable --arp-spoof-threshold, default 3 conflicts). Attributed to T0830
      Adversary-in-the-Middle
      and T1557.002 ARP Cache Poisoning.
    • D2 Gratuitous ARP (GARP) — unsolicited GARP frames flagged as Possible; binding-conflict
      GARP (GARP where the announced MAC differs from the established binding) escalated to Likely.
    • D3 ARP storms — high-rate ARP flood detection (configurable --arp-storm-rate, default
      50 frames/window). Attributed to T0830.
    • D11 Malformed ARP frames — strict + lax/snaplen-truncated ARP parsing; frames that fail
      both passes are flagged as malformed-protocol anomalies.
    • D12 L2/L3 MAC mismatch — Ethernet source MAC vs. ARP sender hardware address mismatch
      detection, flagging potential header spoofing.

    New CLI flags: --arp (enable; also included in -a/--all), --arp-spoof-threshold N,
    --arp-storm-rate N. Binding-table LRU cap: 65 536 entries; storm-counter LRU cap: 4 096
    entries.

    Implemented across STORY-111..115 (PRs #236, #238, #239, #240, #241) with formal hardening
    in PRs #242#251.

Changed

  • Migrated the packet decoder from etherparse 0.16 to 0.20 (DecodedFrame{Ip,Arp} model).
    Strict and lax/snaplen-truncated ARP parsing added; VLAN/QinQ/MACsec link-extension offset
    handling included.
  • Bumped chrono 0.4.44 → 0.4.45 (#237).

Verified

  • VP-024 ARP parse-safety and binding-cap formally verified: 5 Kani proof harnesses proven
    correct, cargo-fuzz 16.2 M executions / 0 crashes, cargo-mutants 98.9 % kill rate on the
    ARP delta.