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.
| 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.
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.
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.
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 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.
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.
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.
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.
| 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.
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.
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.
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.
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.
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.
Large test suites finish twice as fast. Lock-free forwarding snapshots, fork-point resume, and systematic allocation reduction doubled parallel throughput.
- google3 DVaaS integration — the building blocks are ready; the actual wiring is happening internally at Google
- P4Runtime differential testing — expand the BMv2 comparison corpus
| 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.
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 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.
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).
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).
- Close the remaining 5 E2E test gaps — the test gap doc (#55) has the roadmap
- Wire
FourwardOracleinto the DVaaS frontend — the last integration milestone before the upstream PR - Upstream PR — the finish line
| 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.
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.
Three allocation-elimination passes on the table matching hot path, each identified via JFR profiling:
- BigInteger caching (#422) —
ByteString→BigIntegerconversion on every lookup was 42% of heap allocations.Longfast-path for widths ≤63 bits;IdentityHashMapcache for wide fields. +24–56%. - Iterator elimination (#429) —
ArrayList$Itrallocations were 33% of remaining heap.mapNotNull→ indexed for loops;keyValues.find()→ pre-builtHashMap. +48–165%. - 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.
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.
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.
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).
- Track 11 — fix the worst remaining error paths (opaque gRPC
UNKNOWNfrom simulator exceptions) - DVaaS integration — all building blocks are in place; the head-to-head benchmark confirms 4ward exceeds BMv2 throughput in concurrent mode
| 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.
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:
- Table lookup caching (#382) — cache pre-fork lookup results across selector re-executions. The single biggest win.
- Parser skip (#392, #397) — snapshot post-parser state, restore instead of re-parsing on fork branches.
- Long-lived Interpreter (#400) — split into outer class (config
maps, built once) and inner
Execution(per-run state, cheap). - Parallel fork branches (#406) —
parallelStream()replaces the iterative work stack. The code got simpler (-36 lines). - Concurrent packet processing (#409) —
ReadWriteMutexreplaces the globalMutex; newInjectPacketsstreaming 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).
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.
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.
P4Runtime conformance expanded with multi-table test fixtures, structured error detail verification, and encoding edge cases.
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).
- Track 11 — fix the worst error paths (opaque gRPC
UNKNOWNfrom simulator exceptions) - DVaaS integration — all building blocks are in place; the new
InjectPacketsRPC is designed for DVaaS's bulk packet injection pattern
| 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.
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).
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).
The packet I/O subsystem went from design to fully wired end-to-end:
- Design docs (#327) —
packet_io.mdcovering 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) —
PacketBrokerwith fan-out to multiple subscribers,DataplaneServicewiring, subscription-basedPacketInstreaming
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.
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.
- BCR readiness (#315) — real p4c version, behavioral_model as dev_dependency
- p4c
//p4includemigration (#316) — dropped custom//:core_p4target
- 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
| 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.
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.
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.
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
UNIMPLEMENTEDfor unknown entity types @refers_toreferential 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.
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.
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
TableDataReaderinterface.
- 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
| 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.
The CLI landed in a single PR (#236) with three subcommands:
4ward run program.p4 test.stf— compile + simulate in one shot4ward compile program.p4— emit pipeline config only4ward 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.
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.
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.
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).
- 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) —
PacketIngressandPipelineStage(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
expectdirectives. Eliminates false positives. - Meter support (#228) —
execute_meter()anddirect_meter.read()return GREEN. Rate limiting isn't meaningful in STF tests.
- 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
| 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.
All five subtracks landed in a single day:
- 4B: p4-constraints (#196) —
@entry_restrictionvalidation at Write time via C++ subprocess wrapping p4lang/p4-constraints. 12 tests. - 4C: string translation (#192, #187) —
sdn_stringsupport for match fields, action params, and PacketIO metadata. SAI P4's@p4runtime_translation("", string)pattern works. - 4D: write validation (#197) —
WriteValidatorenforces 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).
- 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.
- Track 4E gaps: packet forwarding through SAI P4 (needs full L3 path
setup),
@p4runtime_translation_mappings(VRF's"" → 0explicit 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)
| 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.
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_PRECONDITIONper P4Runtime §9.1, not genericINVALID_ARGUMENT(#170) - Real
@p4runtime_translation— bidirectional SDN↔data-plane mapping tables with explicit, auto-allocate, and hybrid modes. Bothsdn_bitwidthandsdn_stringtranslations 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.
- 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).
- 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)
- 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
| 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.
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 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.
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.
- 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.
| 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).
- 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.
| 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 |
| 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 |
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:
- Const table entries (9 tests) — probably straightforward: populate table
entries from the IR's
const_default_action/entriesfields during LoadPipeline - Header stack indexing (11 tests) — handle array-index access on HeaderStackVal in the interpreter
- Header unions (14 tests) — support union types in the simulator's value model
- verify/verify_checksum externs (5 tests) — implement the extern calls
- 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.
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
HeaderUnionDecltypes; simulator createsStructValfor 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 toextract(arg)with undeclaredarg; 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
adddirectives.
| 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 |
| 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 |
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:
- Const table entries (8 tests) — populate table entries from IR at load time
- 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
HeaderStackValto accept union elements - verify/verify_checksum externs (6 tests) — implement the extern calls
- Register read/write + counter (3 tests) — implement extern methods
| 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.
- 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_tinitialization, andIR::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.shrunner, parallel-worktree~/.bazelrcdocs.
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.