Skip to content

Latest commit

 

History

History
1103 lines (868 loc) · 47.1 KB

STATUS.md

File metadata and controls

1103 lines (868 loc) · 47.1 KB

4ward — Status

This file is maintained exclusively by the project owner. Agents: DO NOT edit this file. No exceptions.

Reverse-chronological log. Add new entries at the top (below this header). See ROADMAP.md for the big picture.

2026-06-01

Delta Total
PRs merged 29 652
Kotlin prod +1.1k 17.1k
Kotlin test +2.9k 35.6k
C++ prod +0.3k 4.8k
C++ test +0.4k 1.4k
Proto +0 1.2k
Web frontend +0.1k 5.3k
Total +4.8k 65.4k

4ward is becoming a production-faithful P4Runtime target: it now speaks the protocol's error model, tolerates real controller transport behavior, makes the C++ embedding API debuggable when subprocess startup fails, validates direct resources against P4Info semantics, and keeps tightening byte-level and BMv2 parity across the simulator and playground.

C++ embedding and dataplane reliability

The C++ embedding API now owns the 4ward subprocess's stdout/stderr as a first-class diagnostic stream. FourwardServer captures both pipes into thread-safe in-memory buffers exposed through Stdout() and Stderr(), while optionally teeing the same output to the parent process with clear [4ward stdout] / [4ward stderr] prefixes. Tests can inspect server logs programmatically without scraping console output.

That same capture path makes startup failures much easier to debug. When 4ward fails before it is ready to serve traffic, the error status includes whatever stdout/stderr the Kotlin subprocess produced, so configuration errors, classpath problems, and early crashes show the actual server log instead of a generic startup failure.

Dataplane result delivery now survives backpressure instead of losing outputs. Unmapped egress ports no longer crash dual-encoding, and result enrichment in DataplaneService was deduplicated.

P4Runtime compliance

Write and Read batch failures now report structured per-item errors instead of collapsing into one opaque status. Atomic failures preserve the real triggering code and mark rolled-back or skipped updates as ABORTED.

StreamChannel failures are now in-band. Unsupported DIGEST_ACK and bad PacketOut translation, metadata packing, ingress extraction, or processing produce StreamError responses without killing the stream. PacketOut byte accounting was tightened so semantic packet bytes and padding are treated explicitly.

Production gRPC compatibility

4ward now uses gRPC defaults that work with real SDN controllers: 10MB metadata, unlimited inbound messages, permissive keepalive without active calls, and zero keepalive permit time. These avoid HTTP/2 resets for large P4Runtime batch errors, RESOURCE_EXHAUSTED on bulk Reads or large pipeline configs, and Netty keepalive strike disconnects.

All four knobs are configurable through FourwardServerOptions, and the Kotlin server plus C++ client channel use the same settings. Invalid CLI values now fail loudly.

Direct resources

Direct counters and meters can now be written through P4Runtime TableEntry payloads, and the implementation is much stricter about when those payloads are legal. Validation now accounts for selected actions, action profiles, direct-resource instance names from P4Info versus IR, and default direct extern cases.

Meter rate behavior is documented as out of scope rather than silently pretending to simulate timing semantics.

Differential testing and parity

The BMv2 differential suite gained PRE multicast coverage and a pinned ValueSetEntry oracle gap. PNA counters are now observable. v1model truncate() is implemented and preserves the shortest requested length across repeated calls, matching BMv2.

The docs now describe the BMv2 P4Runtime diff suite as an active independent oracle, and the compatibility dashboard gives the current support state a single home.

Web playground

The playground now encodes RANGE matches explicitly, rejects unsupported match types loudly, renders range matches in installed entries and traces, supports default action edits, and bounds compilation time so web requests do not hang indefinitely.

Maintenance

Dependency override rationale moved next to the overrides in MODULE.bazel, detekt output is surfaced on lint failures, and docs were updated for current P4Runtime validation, current-packet PNA mirrors, PacketBits byte ownership, and remaining limitations.

2026-05-22

Delta Total
PRs merged 188 623
Kotlin prod +2.4k 16.0k
Kotlin test +7.1k 32.7k
C++ prod +2.9k 4.5k
C++ test -0.3k 1.0k
Proto +0.1k 1.2k
Web frontend +0 5.2k
Total +12.2k 60.6k

4ward is ready for DVaaS. We shipped a C++ embedding API, complete oracle predictions including PacketIn, P4Runtime differential testing against BMv2, and made the whole thing build in google3. 188 PRs in 7 weeks.

C++ embedding API

Starting a 4ward instance is as easy as creating a C++ object. The Kotlin runtime, gRPC channels, subprocess lifecycle — all invisible. You call methods, you get results. A composable gtest matcher library makes assertions equally clean: Forwards(), OnPort(3), PacketsByP4RuntimePort compose naturally, and C++20 concepts keep error messages readable. DVaaS tests read like specifications.

Complete oracle predictions

Inject a packet, get back everything — including what the controller would see. CPU-port outputs now carry a decoded PacketIn, byte-for-byte identical to what the P4Runtime stream delivers. No async coordination, no guessing when you've received all messages — one synchronous response with the complete prediction.

When the oracle disagrees with hardware, GetReproducer distills the failure into a minimal regression test: one pipeline, a handful of table entries, one packet, one expected outcome. No cluster, no topology, no controller.

Simulator correctness

You can trust 4ward on real SAI P4 programs, not just toy examples. We fixed a cluster of interacting clone bugs that made the ACL trap→clone→CPU path — the most common PacketIn mechanism — unreliable. Stale data leaking across clone boundaries, multi-replica sessions dropping replicas, metadata preservation silently losing fields. Each invisible in isolation; together they compounded. We also rewrote parser/deparser serialization at the bit level, fixing silent corruption for non-byte-aligned controller headers.

P4Runtime fidelity

What you write is what you read back — and we can prove it matches BMv2. Canonical bytestring encoding (P4RT §8.3) ensures round-trip consistency across every surface. A new differential testing harness runs the same operations against 4ward and BMv2 side-by-side, catching semantic divergence that self-consistent unit tests miss.

API and build polish

One namespace, one naming convention, builds everywhere. We flattened proto packages, renamed directories to reflect scope, made all oneof dispatch exhaustive (missing branches are compile errors), and committed mechanical formatting. On the build side, 55 PRs made 4ward consumable via the Bazel Central Registry and importable into google3 via Copybara.

Performance

Large test suites finish twice as fast. Lock-free forwarding snapshots, fork-point resume, and systematic allocation reduction doubled parallel throughput.

What's next

  • google3 DVaaS integration — the building blocks are ready; the actual wiring is happening internally at Google
  • P4Runtime differential testing — expand the BMv2 comparison corpus

2026-03-30

Delta Total
PRs merged 16 435
Kotlin prod +1.1k 13.6k
Kotlin test -0.8k 25.6k
C++ prod +0 1.6k
C++ test +0 1.3k
Proto +0.1k 1.1k
Web frontend +0 5.2k
Total +0.4k 48.4k

Auxiliary entries work end-to-end. FourwardPinsSwitch now behaves like a real PINS switch — auxiliary P4 entries are derived and installed transparently before each packet, enabling two previously disabled contract tests. Packet synthesis is wired up. The gap between 4ward and an upstream DVaaS PR is closing fast.

DVaaS integration — auxiliary entries land

The big unlock this cycle: 4ward's new RegisterPrePacketHook RPC (#466) lets a client receive a callback before every dataplane RPC while holding the write lock, respond with P4Runtime updates, and have them applied atomically before packet processing. Two follow-ups (#469, #471) enrich the invocation with the full entity snapshot and P4Info, so hook clients can derive auxiliary entries from forwarding state without a separate Read (which would deadlock under the write lock).

On the sonic-pins side, FourwardPinsSwitch immediately puts the hook to work (#52): before each packet, it derives the complete set of auxiliary entries — PRE clone sessions, ingress_clone_table bridging ACL trap to v1model clone(), VLAN disable entries, VLAN membership mirroring, L3 admit for port RIFs (#54). Delta-based updates keep it efficient. Two contract tests that were previously DISABLED now pass: AclTrapPuntsPacketWithAuxEntries and L3ForwardingWorksWithAuxEntries.

Systematic E2E test gap analysis (#46) mapped all 9 disabled tests to implementation tracks and identified the critical path. Five of those gaps are now resolved (#55).

Packet synthesis + P4 platform guards

Packet synthesis wired up via p4-symbolic (#50) — replaces the UNIMPLEMENTED stub with criteria-based test packet generation using SAI default coverage goals (entry coverage × packet fate).

The SAI P4 platform split cleaned up (#50, #53): v1model_sai.p4 typed ports gated behind #ifdef PLATFORM_4WARD, SAI source files reverted to upstream with minimal #ifdef guards (~9 lines) instead of wholesale forks. This unblocks p4-symbolic, BMv2, and p4testgen backends that previously rejected @p4runtime_translation on extern arguments.

Infrastructure and packaging

4ward published as a bazel_dep for sonic-pins (#452), with release process and version scheme documented (#453). FourwardPinsSwitch renamed to reflect its full PINS scope (#49). PacketBridge routes by gNMI interface name instead of assuming matching port numbers (#48). FakeGnmiService implements Set for port ID changes (#49).

P4Runtime hardening

Fixed a gRPC stream multiplexing issue (#459) — NettyServerBuilder without an explicit executor ran handlers on the Netty event loop, failing when StreamChannel and Read shared one HTTP/2 connection. Follow-up investigation (#462, #463, #465) traced the original sonic-pins symptom to a RETURN_IF_ERROR(status) self-initialization bug in gutil, not Netty threading. The executor kept as gRPC-Java convention; the deadlock claim retracted.

Trace booleans (taken, hit) made visible in text proto output (#455) — proto3 was suppressing false defaults, hiding critical debugging information in SAI P4 traces. Golden error test for wrong-role writes (#468). P4Runtime annotation support documented in userdocs (#461).

What's next

  • Close the remaining 5 E2E test gaps — the test gap doc (#55) has the roadmap
  • Wire FourwardOracle into the DVaaS frontend — the last integration milestone before the upstream PR
  • Upstream PR — the finish line

2026-03-29

Delta Total
PRs merged 28 419
Kotlin prod +1.9k 12.5k
Kotlin test +2.8k 26.4k
C++ prod +0 1.6k
C++ test +0 1.3k
Proto +0 1.0k
Web frontend +0 5.2k
Total +4.7k 48.0k

Track 11 (error quality) reaches 100% coverage, table matching gets 127× faster than baseline, and the nondeterminism model gets a correctness overhaul.

Track 11: error quality — 100% golden coverage

Every error the P4Runtime server can produce now has a golden test — 74 tests across all RPC surfaces (#417–#421). Two bugs caught in the process: ReferenceValidator stripped quotes from @refers_to names, and TypeTranslator formatted hex values inconsistently.

Every "unknown X" error now lists valid options with IDs (#424), making errors actionable without a round-trip to the p4info. Eight follow-up PRs (#425, #428, #430) normalized formatting project-wide: parenthesized valid-options lists, consistent casing, truncation at 10 items.

Error message quality promoted as a first-class feature in the README and userdocs (#446), with a dedicated reference page showing examples and design principles.

Track 10 continued: table matching — 127× from baseline

Three allocation-elimination passes on the table matching hot path, each identified via JFR profiling:

  1. BigInteger caching (#422) — ByteString→BigInteger conversion on every lookup was 42% of heap allocations. Long fast-path for widths ≤63 bits; IdentityHashMap cache for wide fields. +24–56%.
  2. Iterator elimination (#429) — ArrayList$Itr allocations were 33% of remaining heap. mapNotNull → indexed for loops; keyValues.find() → pre-built HashMap. +48–165%.
  3. Array-indexed field lookup (#431) — field IDs are small integers; direct array index replaces HashMap + toString(). +21–142%.

Cumulative on WCMP×16+mirror (10k entries, 16 cores): 41 pps → 5,200 pps (127×). Direct forwarding concurrent: 1,400 → 29,000 pps.

New benchmark additions: 500 ternary ACL entries for realistic worst-case (#427), JSON machine-readable output (#435), ./tools/dev.sh benchmark entry point.

BMv2 head-to-head benchmark

Fair SAI P4 middleblock comparison (#441) — identical workload (10k LPM routes + 500 ternary ACL), BMv2 with -O2, 4ward with trace logging enabled:

  • L3 forwarding: BMv2 4,500 pps vs 4ward 2,500 pps (1 core) / 29,000 pps (16 cores)
  • WCMP ×16: BMv2 4,400 pps vs 4ward 1,400 pps (1 core) / 10,000 pps (16 cores)

BMv2 is faster single-threaded (compiled C++, no trace trees). 4ward's concurrent mode (InjectPackets) pulls ahead by parallelizing across packets and within trace tree forks — 7–11× speedup on 16 cores.

Nondeterminism model: parallel vs alternative semantics

Fixed a correctness bug where a 2-member action selector produced 2 output packets instead of 2 possible worlds of 1 packet each (#433). Root cause: parallel forks (clone, multicast) and alternative forks (selectors) were conflated.

New model: ForkMode.PARALLEL branches combine (Cartesian product); ForkMode.ALTERNATIVE branches produce separate possible worlds. collectPossibleOutcomes() returns List<List<OutputPacket>> — one inner list per world. STF runner passes if any world satisfies expects.

Follow-up (#437) removed all backwards-compatibility shims and made forkModeOf() exhaustive — adding a new ForkReason without handling it is now a compile error. A second follow-up (#439) replaced the exponential cross-packet Cartesian product with greedy per-packet world selection — linear complexity, no silent truncation cap.

Documentation

AGENTS.md trimmed from 267 to 145 lines (#438, #440, #442) — surprises and gotchas only. Userdocs rewritten for prose flow (#447). Added jafingerhut/p4-guide as a reference for v1model semantics (#448).

What's next

  • Track 11 — fix the worst remaining error paths (opaque gRPC UNKNOWN from simulator exceptions)
  • DVaaS integration — all building blocks are in place; the head-to-head benchmark confirms 4ward exceeds BMv2 throughput in concurrent mode

2026-03-28

Delta Total
PRs merged 76 391
Kotlin prod -0.4k 10.6k
Kotlin test +2.0k 23.6k
C++ prod +100 1.6k
C++ test +0 1.3k
Proto +0 1.0k
Web frontend +1.4k 5.2k
Total +3.0k 43.3k

66× dataplane throughput improvement, PNA architecture complete, and Track 11 (error quality) underway.

Track 10: dataplane performance — 66× improvement

Started at 41 packets/sec on the hardest workload (WCMP×16 + mirror, 10k table entries, 32 trace tree branches per packet). Now at 2,700 packets/sec (concurrent, 32 cores) / 780 packets/sec (sequential). The 1k pps target is met across all configurations.

Five optimizations, each building on profiling data:

  1. Table lookup caching (#382) — cache pre-fork lookup results across selector re-executions. The single biggest win.
  2. Parser skip (#392, #397) — snapshot post-parser state, restore instead of re-parsing on fork branches.
  3. Long-lived Interpreter (#400) — split into outer class (config maps, built once) and inner Execution (per-run state, cheap).
  4. Parallel fork branches (#406) — parallelStream() replaces the iterative work stack. The code got simpler (-36 lines).
  5. Concurrent packet processing (#409) — ReadWriteMutex replaces the global Mutex; new InjectPackets streaming RPC for bulk injection. Throughput now scales with available cores.

Also: cache correctness tests (#393), direct counter skip on selector re-executions (counters were inflated by fork branches), and comprehensive benchmark covering direct L3, WCMP×4, WCMP×16, and WCMP×16+mirror at 10k entries.

What didn't help (tried and reverted): defaultValue() caching (negligible), persistent collections for copy-on-write (HAMT overhead cancelled copy savings).

Track 6 Phase 3: PNA — complete

68/68 PNA programs covered (37 STF corpus tests, 31 compile-only). 411 p4testgen-generated tests across 22 programs. All major externs implemented. Pipeline ordering validated against DPDK SoftNIC reference.

Track 11: error quality — started

New track: every error should be clear, actionable, and specific. Phase 1 audit started: golden error tests for 13 error paths in the P4Runtime server, ensuring error messages don't regress.

Track 9: P4Runtime hardening — continued

P4Runtime conformance expanded with multi-table test fixtures, structured error detail verification, and encoding edge cases.

Documentation & docs site

User-facing docs updated with the new InjectPackets RPC, concurrent processing guidance, and the recommended DVaaS pattern (subscribe → wait for active {} → inject → collect). Reference docs explain how to match SubscribeResults to injected packets and how to determine the expected PacketIn count from CPU-port outputs.

Docs site SEO: favicon, Open Graph social cards, meta descriptions on all pages, author/date meta tags, sitemap fix for Google indexing (#384, #405, #407). Tutorial promoted to the docs site (#374).

What's next

  • Track 11 — fix the worst error paths (opaque gRPC UNKNOWN from simulator exceptions)
  • DVaaS integration — all building blocks are in place; the new InjectPackets RPC is designed for DVaaS's bulk packet injection pattern

2026-03-14

Delta Total
PRs merged 36 315
Kotlin prod +2.5k 11.0k
Kotlin test +5.9k 21.6k
C++ prod +0.1k 1.6k
C++ test +0 1.3k
Proto +0.1k 1.0k
Web frontend +0.3k 3.8k
Total +8.9k 40.3k

PSA goes from partial to production-grade, P4Runtime survives a full audit, and the packet I/O design lands — the last major infrastructure piece before DVaaS integration.

PSA: 26/26 corpus + 139 additional tests

Two days ago PSA had 20/26 corpus tests. Now all 26 pass, plus 73 hand-written STF tests from a dedicated coverage blitz and 66 compile-only tests for action selector programs.

The final six corpus tests fell in three PRs: I2E/E2E cloning and recirculate (#301), resubmit (#308), and lookahead<bit<N>> (#311). The coverage blitz (#314) then stress-tested 7 previously-untested code paths, fixing 7 bugs along the way. Action selector fork support (#321) adds trace-tree forking for PSA programs. New language features: value_set (P4 spec §12.14, #324), assert/assume/log_msg (#303).

Track 9: P4Runtime hardening — 141/142 requirements

A systematic audit (#319) found gaps and defined a 4-phase hardening plan. All four phases landed:

  • Phase 1 (#320) — restructured the compliance matrix (120 → 142 requirements)
  • Phase 2 (#322) — closed implementation gaps found by the audit
  • Phase 3 (#323) — deepened test coverage across all RPC surfaces
  • Phase 4 (#326) — coverage-guided tests targeting uncovered code paths

Also: role-based access control per §15 (#304), all SetForwardingPipelineConfig actions per §7.2 (#296), explicit rejection of unsupported features (#300), and SAI P4 translation coverage for all 25 middleblock tables (#313). One gap remains (down from 119/120 → 141/142 by expanding the matrix and filling it simultaneously).

Packet I/O: design + implementation

The packet I/O subsystem went from design to fully wired end-to-end:

  • Design docs (#327) — packet_io.md covering PacketBroker architecture, fan-out model, and subscription-based PacketIn delivery
  • Intrinsic ports (#328) — defaults, layering, and overrides for CPU port, drop port, and recirculate port across architectures
  • PacketBroker (#329, #330, #331) — broker pattern, data-plane listener, Dataplane gRPC service naming cleanup, proto gap-filling
  • Implementation (#332) — PacketBroker with fan-out to multiple subscribers, DataplaneService wiring, subscription-based PacketIn streaming

This is the last major infrastructure piece before DVaaS integration: the simulator now routes packets between P4Runtime (PacketOut/In), the Dataplane gRPC service, and internal forwarding — all through a single broker.

Proto namespace cleanup

Dropped the v1 suffix from all proto package names (#333) — simulator.v1 → simulator, p4_simulator.v1 → p4_simulator. The IR evolves in place with additive changes; versioned packages added ceremony without protection.

Build & infra

  • BCR readiness (#315) — real p4c version, behavioral_model as dev_dependency
  • p4c //p4include migration (#316) — dropped custom //:core_p4 target

What's next

  • DVaaS integration — all building blocks are in place (P4Runtime, Dataplane gRPC with PacketBroker, trace trees, SAI P4 with full middleblock coverage, multi-architecture support)
  • PNA architecture — validates that a third architecture is easy to add

2026-03-12

Delta Total
PRs merged 44 279
Kotlin prod +3.3k 8.6k
Kotlin test +3.0k 15.7k
C++ prod +0.2k 1.5k
C++ test +0.2k 1.3k
Proto +0.1k 0.9k
Web frontend +3.5k 3.5k
Total +10.2k 31.4k

4ward becomes a multi-architecture simulator. PSA goes from zero to 20/26 corpus tests, the architecture boundary refactoring pays off, and an interactive web playground gives 4ward its first visual interface. P4Runtime compliance hits 119/120 spec requirements.

Track 6: PSA architecture — 20/26 corpus tests

PSA didn't exist five days ago. Now it's the second fully functional architecture, covering the core PSA pipeline: separate ingress/egress parsers, controls, and deparsers; send_to_port and drop semantics; multicast group replication with per-replica instance IDs; registers, counters, Hash.get_hash, Meter.execute (stub GREEN), and InternetChecksum (clear/add/subtract/get).

The implementation (#282, #286, #289, #290, #295) follows the pattern established by the Phase 1 refactoring: PSAArchitecture.kt plugs into the shared interpreter via architecture-provided extern handlers, with a PipelineConfig (#292) keeping ingress/egress state cleanly separated. The extern dispatch extraction (#272) that made this possible was a pure refactoring — zero new functionality, 186 v1model tests as safety net.

Six tests remain blocked on I2E/E2E cloning, resubmit, recirculate, and one lookahead type resolution edge case.

Track 8: interactive P4 playground

A browser-based P4 IDE (#263, #271, #279, #297) that makes 4ward tangible. Monaco editor with syntax highlighting, real-time compilation, P4Runtime table management, packet injection, and visual trace tree playback — all in one browser tab.

The playground server (#263) runs HTTP (REST) and gRPC (P4Runtime + Dataplane) in one process, so the same simulator can be driven from both the browser and external controllers. Visual pipeline diagrams (#271, #279) use dagre-based graph layout to show control-flow and parser graphs with interactive zoom, pan, and full-screen mode. Keyboard shortcuts (#297) for compile, send, clear, and reset.

P4Runtime: 119/120 spec compliance

From 85 tested requirements to 119 — 34 gaps closed in a systematic push:

  • Full arbitration state machine (#291) — election_id-based primary selection, backup notification, stream lifecycle per §5
  • Write atomicity (#270) — batch rollback on errors, explicit UNIMPLEMENTED for unknown entity types
  • @refers_to referential integrity (#278) — cross-table reference validation at write time
  • PRE entry semantics (#281) — proper INSERT/MODIFY/DELETE for PacketReplicationEngineEntry
  • Spec audit (#280) — established the 120-requirement matrix from a fresh read of the spec; 4 gaps closed immediately (#288)
  • Also: device_id validation (#284), cookie support (#283), default entries in wildcard reads (#285)

One gap remains: VERIFY action for pipeline config (§7.7). A confidence assessment (#293) documents the remaining blind spots honestly.

SAI P4: production-grade confidence

500 p4testgen tests (#260) + 10 constraint violation tests + metadata preservation for clone/resubmit/recirculate (#266) close the remaining fidelity gaps. BMv2 action profile support in the diff-testing driver (#264) rounds out the testing infrastructure.

Simulator: clean architecture boundary

Three refactorings prepare the simulator for its multi-architecture future:

  • Extern dispatch extraction (#272) — architecture-specific extern handling moves out of the interpreter into pluggable handlers. The interpreter becomes a pure IR walker.
  • ProcessPacket decoupled from gRPC (#294) — the simulator's packet processing API no longer depends on proto wire types, enabling embedding in contexts beyond gRPC.
  • EntityReader extraction (#287) — proto assembly for Read responses factored out of monolithic code into a TableDataReader interface.

What's next

  • PSA: 6 remaining tests — cloning (I2E, E2E), resubmit, recirculate
  • DVaaS integration — all building blocks are in place (P4Runtime, Dataplane gRPC, trace trees, SAI P4, multi-architecture support)
  • Track 6 Phase 3: PNA — validates that a third architecture is easy to add; unlocks p4testgen for deep symbolic path coverage

2026-03-07

Delta Total
PRs merged 38 235
Kotlin prod +0.8k 5.3k
Kotlin test +1.6k 12.7k
C++ prod +0 1.3k
C++ test +10 1.1k
Proto -36 0.8k
Total +2.4k 21.2k

4ward is now a user-facing tool. A standalone CLI (4ward compile / sim / run) lets anyone try 4ward on their own P4 programs without touching Bazel — and the biggest architectural debt (the stdin/stdout IPC subprocess layer) is gone, replaced by direct in-process calls and a proper Dataplane gRPC service.

Track 7: Standalone CLI — done

The CLI landed in a single PR (#236) with three subcommands:

  • 4ward run program.p4 test.stf — compile + simulate in one shot
  • 4ward compile program.p4 — emit pipeline config only
  • 4ward sim pipeline.txtpb test.stf — simulate a pre-compiled pipeline

Human-readable trace output by default; --format=textproto for tooling. A 6-part cram tutorial (examples/tutorial.t) doubles as documentation and a regression test. Two example P4 programs ship alongside.

Simulator: IPC → in-process library (#244)

The biggest architectural simplification since day one. The simulator is no longer a subprocess speaking length-delimited protobufs — it's a direct in-process library with typed methods (loadPipeline, processPacket, writeEntry, readEntries). A new Dataplane gRPC service (DataplaneService.kt) exposes ProcessPacket and ProcessPacketWithTraceTree RPCs alongside the existing P4Runtime service, sharing a mutex to prevent control-plane/data-plane races.

Track 5: Architecture customization — done

standard_metadata field widths (port width, drop port) are now derived from the IR at pipeline load time instead of hardcoded bit<9> (#220). A wide-port E2E test verifies 16-bit ports: forwarding to port 1000, mark_to_drop() computing all-ones (65535), and correct drop detection.

P4Runtime: 85/92 spec compliance

Closed 6 untested compliance gaps (#237): table capacity enforcement, group max_size limits, write batch ordering, multi-controller arbitration. Added indirect counter and meter read/write (#243), const table and default entry reads (#215), and tightened write validation — ternary masked bits, LPM trailing bits, duplicate fields (#211).

Testing & quality

  • BMv2 diff: 186/186 pass (#233) — all corpus tests match BMv2 bit-for-bit. Fixed multicast PRE type and exact→LPM promotion.
  • Trace trees: structural events (#231) — PacketIngress and PipelineStage (enter/exit) events give every trace a full pipeline skeleton. 13 golden files updated.
  • STF runner (#242) — smart output validation only rejects unexpected packets when the STF file has expect directives. Eliminates false positives.
  • Meter support (#228) — execute_meter() and direct_meter.read() return GREEN. Rate limiting isn't meaningful in STF tests.

What's next

  • DVaaS integration: 4ward now has all the building blocks (P4Runtime, Dataplane gRPC, trace trees, SAI P4 support). Next step is wiring it into DVaaS as a BMv2 replacement.
  • Track 5 continued: PSA architecture (26 blocked corpus tests)
  • Track 1B: expand p4testgen coverage

2026-03-06 (morning)

Delta Total
PRs merged 20 197
Kotlin prod +0.7k 4.5k
Kotlin test +2.6k 11.1k
C++ prod +0 1.3k
C++ test +0.7k 1.1k
Proto +58 0.9k
Total +4.1k 18.9k

The north star is reached. SAI P4 works end-to-end through 4ward's P4Runtime stack — pipeline load, Write with sdn_string type translation and p4-constraints validation, Read round-trips, Delete. Track 4 went from "4B–4E remain" this morning to all subtracks complete by evening.

Track 4: P4Runtime — done

All five subtracks landed in a single day:

  • 4B: p4-constraints (#196) — @entry_restriction validation at Write time via C++ subprocess wrapping p4lang/p4-constraints. 12 tests.
  • 4C: string translation (#192, #187) — sdn_string support for match fields, action params, and PacketIO metadata. SAI P4's @p4runtime_translation("", string) pattern works.
  • 4D: write validation (#197) — WriteValidator enforces P4Runtime §9.1: table/action IDs, param counts and widths, match field kinds, priority rules. 18 unit tests + 6 E2E tests.
  • 4E: SAI P4 E2E (#199) — the capstone. Real SAI P4 middleblock from sonic-pins compiled, loaded, and exercised via P4Runtime with full string translation. 8 E2E tests.
  • Also: register read/write (#195), action profile members and groups (#185), spec compliance matrix (#202).

Testing infrastructure

  • p4testgen JVM batching (#198) — 155 tests in one JVM instead of 155 separate processes. Tagged heavy, skipped on PR CI.
  • BMv2 diff testing expanded (#188) — 5 → 183 programs.
  • CI heavy test handling (#201, #203, #204) — heavy tests run as a conditional step on main pushes only; PRs stay snappy.

What's next

  • Track 4E gaps: packet forwarding through SAI P4 (needs full L3 path setup), @p4runtime_translation_mappings (VRF's "" → 0 explicit mapping)
  • Track 1B: expand p4testgen beyond 155 programs
  • Track 1C: verify BMv2 diff test results at scale
  • Track 5: PSA architecture (26 blocked corpus tests)

2026-03-06

Delta Total
PRs merged 45 177
Kotlin prod +1.0k 3.8k
Kotlin test +2.5k 8.5k
C++ prod +75 1.3k
C++ test +417 0.4k
Proto +61 0.8k
Total +4.1k 14.8k

Two tracks saw major progress: P4Runtime is now a real server (not a skeleton) and all three testing oracles are operational.

P4Runtime: from skeleton to spec-compliant (Track 4)

The P4Runtime server went from basic Write/Read to a near-complete implementation in one day (PRs #170, #175, #176, #178, #179):

  • Spec-compliant Write errors — ALREADY_EXISTS, NOT_FOUND, FAILED_PRECONDITION per P4Runtime §9.1, not generic INVALID_ARGUMENT (#170)
  • Real @p4runtime_translation — bidirectional SDN↔data-plane mapping tables with explicit, auto-allocate, and hybrid modes. Both sdn_bitwidth and sdn_string translations supported. 14 new unit tests (#176)
  • GetForwardingPipelineConfig, Capabilities, per-entry Read filtering — all three Read levels (wildcard, per-table, per-entry with match key) now work (#175, #178)
  • P4Runtime tests in CI — conformance tests now run in the default suite (#179)
  • Testing strategy documented (Track 4A) — what the P4Runtime equivalent of STF corpus / p4testgen / BMv2 diff testing should look like (#177)

28 P4Runtime conformance tests across 3 test suites. Track 4A (testing strategy) complete; 4B–4E remain.

Three testing oracles, all operational

  • Track 1A complete — 186/186 v1model STF tests pass. Lookahead/advance implemented (#155), checksum varbit bugs fixed (#162).
  • Track 1B complete — p4testgen expanded from 10 → 150 programs with full path exploration (#164, #166, #167, #169, #172). All tests unpinned.
  • Track 1C landed — BMv2 differential testing infrastructure (#173): C++ driver binary, Kotlin test harness, 5 programs passing. CI build fixed (#183).

Docs & housekeeping

  • Testing strategy documented across ARCHITECTURE.md, new TESTING_STRATEGY.md (#154)
  • README rewritten: "Why Kotlin?", trust section, quick-start reordering (#153, #156, #157, #158, #159)
  • AGENTS.md is now the single source of truth — CLAUDE.md just points there (#184)
  • AI_WORKFLOW.md: new "ask what's next" principle (#182)

What's next

  • Track 4B: p4-constraints JNI binding
  • Track 4C: @p4runtime_translation("", string) for SAI P4 port IDs
  • Track 4E: SAI P4 end-to-end
  • Track 1C: expand BMv2 diff testing beyond the initial 5 programs

2026-03-05

Delta Total
PRs merged 48 132
Kotlin prod +0.9k 2.8k
Kotlin test +2.6k 5.9k
C++ prod +49 1.2k
C++ test +0 0
Proto +96 0.8k
Total +3.7k 10.7k

Three big milestones: v1model is essentially complete, trace trees are fully operational, and the project has a clear north star with a parallelizable roadmap. The simulator now passes 94% of v1model corpus tests (up from 58% yesterday) and produces forking trace trees at every non-deterministic choice point — the feature that makes 4ward fundamentally better than BMv2 for dataplane validation.

v1model: 176/187 passing (~94%)

Went from 124 to 176 passing corpus tests in a single sprint. The remaining 11 are mostly lookahead/advance (6 tests needing p4c backend work) plus 5 scattered gaps. The v1model interpreter now handles the full breadth of P4 features: all extern types (registers, counters, checksums, hashes), header unions and stacks with full push/pop/extract semantics, parser error recovery, and enum promotion through a new midend pass.

Trace trees: from schema to shipping

Trace trees went from a proto definition to a complete implementation in one day. The simulator now forks execution at action selectors, clone sessions, and multicast groups — producing a tree of all possible packet outcomes in a single pass. Every trace event carries source location info back to the P4 source. All 7 golden tests pass (zero-fork, 3 selector variants, clone, clone+selector, multicast). This is the core differentiator for DVaaS integration: where BMv2 requires brittle hacks to extract non-deterministic behaviors, 4ward returns them natively.

Clear north star, parallelizable roadmap

Defined the project's end state — replace BMv2 in DVaaS and fully support SAI P4 — and broke the path there into 5 independent tracks (ROADMAP.md). Tracks 1 (v1model), 3 (trace trees), and 4 (P4Runtime) can all proceed in parallel with no cross-dependencies. This matters because it means multiple agents can work simultaneously on different tracks without stepping on each other. Supporting docs: LIMITATIONS.md, REFACTORING.md, AI_WORKFLOW.md.

Also: fixed a coverage regression, added unit tests for core simulator files, documented BMv2 simple_switch as the v1model reference spec.

What's next

  • Finish v1model corpus (1A) — 11 tests to go. Lookahead/advance (6) is the biggest cluster, needs p4c backend support for lookahead() return values.
  • Expand p4testgen (1B) — all 10 programs unpinned and passing full path exploration (#164). Next: expand to more corpus programs.
  • BMv2 diff testing (1C) — not started yet.
  • P4Runtime server — plan finalized (Kotlin, in-process, grpc-java stubs), implementation starting. First milestone: gRPC skeleton + SetForwardingPipelineConfig.

2026-03-04

Delta Total
PRs merged 49 84
Kotlin prod +213 1.9k
Kotlin test +91 3.3k
C++ prod +152 1.1k
C++ test +0 0
Proto +3 0.7k
Total +0.5k 7.0k

Corpus expansion. Classified every remaining p4c STF test (PRs #39–#86).

What landed

  • Tested and categorized all 77 previously-untracked p4c STF tests.
  • 8 new tests pass (101 total in CI).
  • 116 failing tests sorted into manual suites with inline error annotations.

Corpus snapshot: 217 tests total

Suite Count Status
v1model (CI) 101 passing
Skeleton includes 16 manual — missing Bazel targets
PSA architecture 25 manual — unsupported arch
Lookahead/advance 6 manual — p4c backend limitation
Other failures 66 manual — various simulator gaps
Slow compile 1 manual — 10+ min compile
Unsupported arch 1 manual — non-v1model/PSA

Top failure categories

Missing feature Blocked tests
Header unions (UnitVal) 14
Header stack indexing 11
Const table entries 9
Lookahead/advance 6
verify/verify_checksum 5
Payload mismatches 6
STF parser edge cases 3
Integer literal w/o bit type 3
Expression kind: type 3
Register read/write 2

Where are we? Where are we going?

217 p4c STF tests tracked, 101 passing in CI (~47%).

The passing tests cover the core v1model pipeline well: table lookups (exact/LPM/ternary), header manipulation, control flow (exit, switch, nested tables), type casting, side effects, and mux operations. Plus 6 hand-written feature tests (passthrough, basic_table, lpm_routing, ternary_acl, multi_table, switch_action_run).

Failing tests (116) break down into clear feature clusters:

Missing feature Blocked tests Effort
PSA architecture 25 Large (new architecture impl)
Skeleton .p4 Bazel targets 16 Build plumbing, not simulator work
Header union support 14 UnitVal field access + casting
Header stack indexing 11 Field access on HeaderStackVal
Const table entries 9 Populating tables from IR at load time
Lookahead / advance 6 p4c backend limitation
verify/verify_checksum externs 5 Extern implementation
Payload mismatches 6 Various correctness bugs
STF parser issues 3 NumberFormatException on edge-case STF syntax
Integer literal w/o bit type 3 IR/interpreter gap
Unhandled expression kinds 3 type expressions
Register read/write 2 Extern implementation
Other ~13 Assorted (unknown tables, missing packets, etc.)

If the goal is to maximize passing corpus tests with least effort:

  1. Const table entries (9 tests) — probably straightforward: populate table entries from the IR's const_default_action/entries fields during LoadPipeline
  2. Header stack indexing (11 tests) — handle array-index access on HeaderStackVal in the interpreter
  3. Header unions (14 tests) — support union types in the simulator's value model
  4. verify/verify_checksum externs (5 tests) — implement the extern calls
  5. Register read/write (2 tests) — implement register extern methods

Items 1–5 would potentially move 41 tests from manual to passing, bringing the pass rate from ~47% to ~66%.

The bigger lifts (PSA architecture, skeleton Bazel targets, lookahead support) are either architectural or toolchain work and probably belong on a separate track.

Corpus fix-up session (branch: fix-inlined-table-lookup)

Worked through the top failure categories from the morning classification. 23 tests promoted to the passing suite (101 → 124). 12 commits, not yet merged.

Fixes implemented:

  • Header stack support — field access, array indexing, and emission for HeaderStackVal. Promoted 8 gauntlet_index tests + opassign2 + array-copy.
  • Header union support — backend emits HeaderUnionDecl types; simulator creates StructVal for unions with per-member validity tracking (P4 spec §8.20). isValid()/setInvalid() handle union semantics. Promoted header-bool, issue561-1/2.
  • STF queue-based output matching — runner collects all outputs into a FIFO queue, then matches expects by port. Fixes batched STF files where all packets precede all expects (BMv2-compatible behavior).
  • Don't-care extract — P4's p.extract<T>(_) compiles to extract(arg) with undeclared arg; simulator now creates a throw-away header to consume bytes. Promoted issue774-4.
  • Parser error handling — simulator continues pipeline on parser errors instead of dropping the packet. Promoted parser_error + 2 others.
  • Backend fixes — inlined table lookup, InfInt literals, range/mask parser select keysets, multi-key select (ListExpression), value-based switch lowering.
  • STF parser — handle unquoted named action params in add directives.

Corpus snapshot: 213 tests total

Suite Count Status
v1model (CI) 124 passing
Skeleton includes 15 manual — missing Bazel targets
PSA architecture 25 manual — unsupported arch
Lookahead/advance 6 manual — p4c backend limitation
Header stack ops 1 manual — push/pop/comparison
Other failures 40 manual — various simulator gaps
Slow compile 1 manual — 10+ min compile
Unsupported arch 1 manual — non-v1model/PSA

Remaining failure categories

Missing feature Blocked tests
Const table entries 8
Header unions (remaining) 10
verify/verify_checksum 6
Payload mismatches 5
Header stack edge cases 2
Integer literal w/o bit type 2
Register read/write 2
Counter extern 1
Inlined function locals 1
NumberFormatException 2
Missing output packets 1

Where are we going?

124/213 tests passing (~58%), or 124/143 v1model-capable (~87%).

Excluding structurally blocked tests (PSA, skeleton includes, lookahead, unsupported arch = 47 tests), only 19 v1model tests remain failing.

Next highest-impact items:

  1. Const table entries (8 tests) — populate table entries from IR at load time
  2. Header union completions (up to 10 tests) — union-* tests may pass now with the backend/simulator changes; issue561-3 needs one-valid-at-a-time enforcement; issue561-4–7 need HeaderStackVal to accept union elements
  3. verify/verify_checksum externs (6 tests) — implement the extern calls
  4. Register read/write + counter (3 tests) — implement extern methods

2026-03-03

Delta Total
PRs merged 35 35
Kotlin prod +1.7k 1.7k
Kotlin test +3.2k 3.2k
C++ prod +1.0k 1.0k
C++ test +0 0
Proto +0.7k 0.7k
Total +6.5k 6.5k

Day one. Stood up the project end-to-end: proto IR, p4c backend, Kotlin simulator, STF test runner, CI pipeline, coverage reporting.

What landed (PRs #1–#38)

  • Simulator core: interpreter with v1model architecture support — tables (exact, LPM, ternary), header manipulation, control flow (exit, switch, nested tables), type casting, side effects, mux operator, table.apply().hit/.miss/.action_run.
  • p4c backend: C++ plugin emitting proto IR from P4 source. Three quick-wins (#27) jumped corpus pass rate from 26 to 90 tests: ** wildcard match, standard_metadata_t initialization, and IR::ArrayIndex.
  • STF test infrastructure: parameterized corpus harness batching 90+ tests into a single JVM (#30), p4testgen integration for automated path-covering tests (#36).
  • CI/CD: GitHub Actions with formatting, clang-tidy, build+test (Ubuntu + macOS), JaCoCo coverage with differential reports on PRs, BuildBuddy remote cache, self-hosted GitHub Pages coverage reports.
  • Developer tooling: format.sh, lint.sh, coverage.sh, diff-coverage.sh, dev.sh runner, parallel-worktree ~/.bazelrc docs.

Where are we? Where are we going?

90 corpus tests passing out of ~140 tracked. 6 hand-written feature tests (passthrough, basic_table, lpm_routing, ternary_acl, multi_table, switch_action_run). ~50 tests remain unclassified — need to test each one and sort into passing or failing.