Releases: zhh2001/jp4
Release list
jp4 v1.5.0
[1.5.0] — 2026-05-16
v1.5 is a SemVer-minor addition over v1.4; the v1.4 public surface
is unchanged. See docs/migration-1.4-to-1.5.md
for usage examples of each new method. The v1.5 release completes
the per-entity-type read surface: the two packet replication engine
entities — multicast groups and clone sessions — are now readable
through name-less typed query builders that take controller-assigned
numeric ids only, the program-agnostic counterpart to the v1.4
table-driven reads.
Added
P4Switch.readMulticastGroup()+MulticastGroupReadQuery
interface +MulticastGroupEntryrecord (commitb893a53) —
name-less typed read of one multicast group programmed on the
device's packet replication engine. The query builder offers a
groupId(long)server-side filter, a non-default
where(Predicate)client-side filter, and the five terminals
(all/one/stream/allAsync/oneAsync). The entry
method takes noStringargument — the packet replication engine
is program-agnostic and multicast groups are addressed by
controller-assigned numeric id only.MulticastGroupEntryis a
three-field record (multicastGroupId, an ordered list of
Replicafan-out slots, opaque controller-definedBytes metadata); the metadata field was added in P4Runtime 1.4.0 and
surfaces as empty bytes on older devices.- Shared
ReplicaandBackupReplicarecords (commit
b893a53) — entity-package records used by both multicast groups
and the forthcoming clone sessions.Replicacarries a nullable
Bytes port, the per-cloneinstanceid, and an ordered list of
BackupReplicafallback ports. Theportfield is nullable in
two cases that v1.5 treats identically: when the P4Runtime
port_kindoneof is unset, and when the deprecated
egress_portint32 field is set instead — the same flat-nullable
shapeWeightedMember.watchPortuses for the action-profile-group
watch_kindoneof shipped in v1.4.BackupReplicais a record
corresponding to a P4Runtime 1.5.0 spec addition, the first jp4
release that surfaces a v1.5-spec-level type; older devices return
emptybackup_replicaslists. P4Switch.readCloneSession()+CloneSessionReadQuery
interface +CloneSessionEntryrecord (commitf709b5a) —
name-less typed read of one clone session, mirroring the
multicast-group shape withsessionId(long)as the server-side
filter.CloneSessionEntryis a four-field record carrying the
sessionId, an ordered list ofReplicafan-out slots, a
classOfServicewidened from the proto'suint32to a Java
long(the same widening conventiongroupId,memberId,
multicastGroupId, andsessionIditself already follow on
existing entity records), and apacketLengthBytestruncation
control (mapped to plainintbecause the proto declares it as
int32rather thanuint32). ApacketLengthBytesof0means
"do not truncate"; a positive value means "truncate cloned
payload to this many bytes".- BMv2 end-to-end integration tests for the new PRE read
surfaces (commit8032783) — a dedicated
PacketReplicationEngineIntegrationTestclass drives a real
simple_switch_grpcinstance through wire-level Write + Read
round-trips for one multicast group and one clone session. Local
verification againstsimple_switch_grpc1.15.1 found that BMv2
fully implements bothMulticastGroupEntryand
CloneSessionEntryRead RPCs — the opposite of the
RegisterEntrycase from v1.4 where the same BMv2 returns
UNIMPLEMENTED. The new tests retain a defensive
Assumptions.assumeTrueskip pattern onUNIMPLEMENTEDeven so,
for forward-compatibility against older BMv2 builds or alternate
spec-compliant servers that might refuse the read. The fixture
counters_meters_registers_groups.p4from v1.4 serves these
tests as-is — the packet replication engine carries no
P4-program declarations, so no fixture extension is needed.
jp4 v1.4.0
[1.4.0] — 2026-05-16
v1.4 is a SemVer-minor addition over v1.3; the v1.3 public surface
is unchanged. See docs/migration-1.3-to-1.4.md
for usage examples of each new method. The v1.4 release completes
the per-entity-type read surface: counter, meter, register,
action-profile member, and action-profile group cells are now
readable through name-based, typed query builders that mirror the
shape ReadQuery already uses for table reads.
Added
- P4Info index extension for the four new entity types (commit
a280a1e) —P4Infonow indexes counters, meters, registers, and
action profiles in the same parse-loop pass that already indexes
tables and actions, with paired forward (counter(String),
meter(String),register(String),actionProfile(String)),
reverse-id (counterById(int),meterById(int),
registerById(int),actionProfileById(int)), and name-listing
(counterNames(),meterNames(),registerNames(),
actionProfileNames()) accessors. The forward accessors throw
P4PipelineExceptionwith a known-list hint on miss; the reverse
accessors returnnull, matching the convention shared with
tableInfoById/actionInfoById. - Four pipeline value types (commit
a280a1e) —
CounterInfo(name + id + unit + size),MeterInfo(name + id- unit + size; the spec's
MeterSpec.Unitexposes only
UNSPECIFIED/BYTES/PACKETS, not theBOTHshape
counters have),RegisterInfo(name + id + size — per-cell
P4DataTypeSpecis intentionally not exposed, matching the
conventionDigestEvent.datafollows forP4Data), and
ActionProfileInfo(name + id + size + max-group-size + the
table-id set the profile is referenced from).
- unit + size; the spec's
- **
P4Switch.readCounter(String)+CounterReadQueryinterfaceCounterEntryrecord** (commitde3e031) — name-based typed
read of one counter array. The query builder offers an
index(long)server-side filter, a non-default
where(Predicate)client-side filter, and the five terminals
(all/one/stream/allAsync/oneAsync).
CounterEntryis a flat record carrying the resolved counter
name, the cell index, and bothpacketCountandbyteCount;
which value is meaningful is determined by the counter's unit
throughP4Info.counter(name).unit(). The implementation
reuses the existingoutboundExecutor,awaitRead,
mapReadFailure, andreadabilityExceptioninfrastructure
verbatim, establishing the per-entity read template the v1.4
surface expands.
P4Switch.readMeter(String)+MeterReadQueryinterface +
MeterEntry/MeterConfig/MeterCounterData/
CounterDatarecords (commite29bd3e) — name-based typed
read of one meter array.MeterEntryis a nested record
(meter name, index,MeterConfig,MeterCounterData); the
shape mirrors the P4RuntimeMeterEntryproto directly. The
nestedMeterConfigcarriescir,cburst,pir,pburst,
andeburst— the last was added in P4Runtime 1.4.0 and is
only used by srTCM meters; for trTCM meters or for devices that
predate the addition it surfaces as zero.MeterCounterData
groups the per-color cumulative counters (green / yellow / red)
defined by RFC 2697 / RFC 2698; the message itself was added in
P4Runtime 1.4.0.CounterDatais a small two-field nested
helper (packetCount,byteCount) used only inside
MeterCounterData;CounterEntrydeliberately does not share
it because a counter cell carries a single counter datum
whereas a meter cell carries three.P4Switch.readRegister(String)+RegisterReadQuery
interface +RegisterEntryrecord (commitdd152cf) —
name-based typed read of one register array.RegisterEntry
is a three-field record (register name, index,Bytes data)
whosedatais the serialised bytes of the wirep4.v1.P4Data
proto — that is, whatproto.getData().toByteArray()returns,
not whatproto.getData().getBitstring()returns. This matches
the conventionDigestEvent.dataalready follows and the
contractRegisterInfo's javadoc promised ina280a1e.
Consumers decode via
P4Data.parseFrom(entry.data().toByteArray())and access the
appropriate oneof variant; for the commonbit<W>/int<W>
register the field is.getBitstring(). Typed P4Data
unwrapping is held for a future v1.x release.- **
P4Switch.readActionProfileMember(String)and
readActionProfileGroup(String)+ their
ActionProfileMemberReadQueryand
ActionProfileGroupReadQueryinterfaces + three records
(ActionProfileMember,ActionProfileGroup,WeightedMember)- new
ActionInstance.of(String, Map<String, Bytes>)factory**
(commit6e43a4b) — name-based typed read of both halves of
the action-profile entity family. Each query builder offers a
server-side id filter (memberId(long)andgroupId(long)
respectively), a non-defaultwhere(Predicate), and the five
terminals.ActionProfileMemberreuses the existing
ActionInstancevalue type for its action; the new public
staticActionInstance.offactory is the smallest surface
addition that lets the read path build the record without
going through theTableEntryBuilderchain.WeightedMember
carriesmemberId,weight, and a nullableBytes watchPort
whose null surface covers both an unsetwatch_kindoneof and
the deprecatedwatchint32 path; a controller that needs the
deprecated path can parse the wire proto directly.
- new
- BMv2 integration tests for the new read surfaces (commit
1199952) — a dedicatedcounters_meters_registers_groups.p4
fixture declares all four indirect features (counter, meter,
register, action_selector) in one pipeline so a single BMv2
spawn amortises across the five read API families.
CountersMetersRegistersGroupsIntegrationTestreads counter,
meter, register, action-profile member, and action-profile
group cells against a realsimple_switch_grpcinstance.
Two BMv2-shape findings shape the test code: first,
simple_switch_grpc1.15.1 returnsUNIMPLEMENTEDfor the
RegisterEntryread RPC, so the register integration test
catches that status code and skips through
Assumptions.assumeTrueuntil BMv2 adds support — the unit
test inP4SwitchReadRegisterTestalready covers the jp4
read path end-to-end against the gRPC fake; second, P4Runtime
1.3+ canonical-bytestring encoding strips leading zero bytes,
so a 9-bit port value of 5 sent to BMv2 as{0x00, 0x05}
round-trips as a single-byte{0x05}on the read side, and
the action-profile tests send the canonical form to match.
v1.3.0
[1.3.0] — 2026-05-14
v1.3 is a SemVer-minor addition over v1.2; the v1.2 public surface
is unchanged. See docs/migration-1.2-to-1.3.md
for usage examples of each new method. The v1.3 release completes the
stream-message dispatch family: a P4Runtime device's inbound
DigestList and IdleTimeoutNotification arms now have typed event
records, listener APIs, and the corresponding control-plane enable
surface, making both feature families end-to-end usable against a
real BMv2 device.
Added
DigestEventrecord (commitc5ed6b1) — typed value carrying
the resolved digest name, ack-protocol list id, an immutable list
of raw per-entry payload bytes, a wall-clock timestamp, and the
numeric digest id. Delivered by the new
P4Switch.onDigest(Consumer<DigestEvent>)listener; each entry in
thedatalist is the serialised form of onep4.v1.P4Data
message that consumers decode throughP4Data.parseFromif they
want a typed view.IdleTimeoutEventrecord (commitc5ed6b1) — typed value
carrying an immutable list ofTableEntrys that idled out and a
wall-clock timestamp. The list preserves the wire shape directly,
so a single notification can span multiple tables and the
library does not regroup bytable_id.P4Info.digestNameById(int)anddigestIdByName(String)
accessors (commits252990b,617f5bb) — paired forward and
reverse index of digest extern declarations from P4Info, populated
in one parse-loop alongside the existing
tableInfoById/actionInfoById/packetInFieldByIdfamily.
Both return null when the name or id is unknown, matching the
lookup-fail-equals-null convention shared across the existing
reverse-id accessors.P4Switch.onDigest(Consumer<DigestEvent>)(commit3d4e91e)
— single replaceable listener for inboundDigestListstream
messages, ack-first by design: the dispatch path issues a
DigestListAckfor every received list unconditionally — before
any drop check or listener delivery — so the device's spec-defined
ack_timeout_nssuppression window is never entered when the
pipeline is unbound, when no listener is registered, or when
P4Info has no digest with the received id. Eagerly requires a
bound pipeline at registration so the silent-drop trap that would
otherwise apply is caught at the call site.DigestConfigrecord +P4Switch.enableDigest(String, DigestConfig)/enableDigestAsync(commit617f5bb, refined
in85c8744) — the control-plane enable surface for digest
emission. The record carries the three knobs the P4Runtime
DigestEntry.Configdefines:max_timeout_ns,max_list_size,
andack_timeout_ns; the method writes aDigestEntryupdate
through the same outbound dispatch path that backs
insert / modify. Without this call the device emits no
DigestListeven ifonDigestis registered; the pair is
required for the dispatch family to actually surface traffic.P4Switch.onIdleTimeout(Consumer<IdleTimeoutEvent>)(commit
e7a54a9) — single replaceable listener for
IdleTimeoutNotification. The dispatch path reverse-parses each
wireTableEntrythroughEntryProto.fromProto, the same parser
the Read RPC uses. An entry on an action-profile or selector
table is rejected by that parser today; the dispatch helper
catches the exception, WARN-logs the drop, and swallows the whole
notification rather than delivering a partial event — matching
the per-message fail-open posturedispatchPacketInuses for
unparseable inbound packets. UnlikeonDigest, no outbound ack
is sent:IdleTimeoutNotificationhas no corresponding
StreamMessageRequestarm per the P4Runtime spec.TableEntry.idleTimeoutNs()accessor +
TableEntryBuilder.idleTimeoutNs(long)setter +
ActionBuilder.idleTimeoutNs(long)passthrough (commit
5d5bd28) — the control-plane enable surface for idle expiration.
Setting the value to a positive number opts a table entry into
idle-timeout aging on devices whose P4 program declared the
containing table with idle-timeout support; the device fires an
IdleTimeoutNotificationfor the entry once it has not been hit
within the configured window. The default0matches the
v1.0 / v1.1 / v1.2 behaviour and the protobuf wire encoding then
omits the field.EntryProto.toProtowrites the field
conditionally on> 0, mirroring the existingsetPriority
convention;EntryProto.fromProtoround-trips it back onto
the builder so a Read response carries the value forward.- BMv2 integration tests for the new dispatch and enable
surfaces (commit85c8744) — a dedicateddigest_idle.p4
fixture declares one digest extern and one exact-match table with
idle-timeout support;DigestIntegrationTestand
IdleTimeoutIntegrationTestdrive a real BMv2 instance through
the full enable + dispatch loop and assert the device emits
DigestListandIdleTimeoutNotificationfor the configured
paths. The same commit also corrects two implementation details
surfaced by the integration tests:enableDigestAsyncissues
theDigestEntrywrite withUpdate.Type.INSERTrather than
MODIFY(BMv2 rejectsMODIFYfor a digest that has not yet
been INSERTed), and the new P4 fixture uses the standalone
support_timeout = truetable property rather than the
@support_timeout(true)annotation so p4c emits
idle_timeout_behavior = NOTIFY_CONTROL_PLANEin the P4Info
(BMv2 reads the typed field rather than the annotation).
v1.2.0
[1.2.0] — 2026-05-11
v1.2 is a SemVer-minor addition over v1.1; the v1.1 public surface
is unchanged. See docs/migration-1.1-to-1.2.md
for usage examples of each new method. The v1.2 release introduces
the packet-ingestion control surface: a typed drop event, a listener
to observe drops, and a pre-fan-out filter to reject inbound packets
before any sink sees them.
Added
DropEventrecord +Reasonenum (commit4e88813) — a
typed value carrying the dispatch-site reason
(SUBSCRIBER_LAG,QUEUE_FULL,FILTERED), wall-clock
timestamp, parsedPacketIn, and a free-form human-readable
message useful for log correlation without machine-parsing the
reason enum. The record is the data shape the new
P4Switch.onPacketDroppedlistener delivers.P4Switch.onPacketDropped(Consumer<DropEvent>)(commit
8ca1a5b) — single replaceable listener for inbound PacketIn
drops detected by the dispatch path. ObservesSUBSCRIBER_LAG
(aFlow.Publishersubscriber offer drop) andQUEUE_FULL
(the poll-style deque at capacity); a futureFILTEREDreason
fires from theConnector.packetInFilterdispatch site. Mirrors
onMastershipChangeshape: volatile field, NPE on null,
last-write-wins, runs on the shared single-threaded callback
executor. Existing WARN logs on each drop site fire
synchronously before the listener is scheduled — log and
listener are independent surfaces (operator-grep vs
application-handle).Connector.packetInFilter(Predicate<? super PacketIn>)
(commitc35eb5a) — pre-fan-out filter that runs in
P4Switch.dispatchPacketInafterPacketProto.parseInand
before the callback / Publisher / deque fan-out. A packet for
which the filter returns false is dropped (no sink sees it) and
aFILTEREDDropEventfires through the
P4Switch.onPacketDroppedlistener with the message
"rejected by packetInFilter". A filter that throws is treated
as a drop (safe default), logs at WARN, and fires aFILTERED
DropEventwhose message names the thrown exception's simple
class name. Default null = pass all; v1.0 / v1.1 callers see no
behaviour change. ThePredicate<? super PacketIn>shape
matchesReadQuery.where's PECS convention from 1.1.0.
v1.1.0
[1.1.0] — 2026-05-09
v1.1 is a SemVer-minor addition over v1.0; the v1.0 public surface
is unchanged. See docs/migration-1.0-to-1.1.md
for usage examples of each new method.
Added
ReadQuery.where(Predicate<? super TableEntry>)(commit
6277b18) — client-side filtering on the read result. Each call
appends a predicate; entries that fail any predicate are excluded
from the terminal call (.all()/.one()/.stream()and async
variants). The method is a default on theReadQueryinterface
that throwsUnsupportedOperationException, naming1.1.0and
directing the caller to update; the built-in implementation
returned byP4Switch.read(...)overrides it. SemVer-safe for any
external implementer ofReadQuery.Connector.preserveRoleOnReconnect(boolean)(commit
53e4e8e) — opts a.asPrimary()connector into fail-fast on
reconnect role downgrade. When enabled and a reconnect yields a
non-Primary role (because another client arbitrated higher during
the disconnect window), the switch closes itself and stores the
resultingP4ArbitrationLostas the close cause; subsequent user
calls throw that cause via the existing writability and readability
gates instead of the generic"switch is closed". Defaultfalse
preserves the v1.0 silent-Secondary behaviour.Mac.ZERO(commitd1bf319) — publicstatic final Mac
constant for the all-zero MAC address (00:00:00:00:00:00),
useful as a sentinel for invalid or default-initialised source
addresses.
Documentation
- AI-tool and bot attribution rules in
CONTRIBUTING.md
(commit3fb8354) — the commit-message section now enumerates the
rejected forms (AI-namingCo-Authored-By:trailers, footer
banners citing automated assistance, bot emails matching
noreply@*, robot or AI-tool emoji), and preserves the existing
carve-out forCo-Authored-By:lines naming real human
collaborators. - Post-1.0 source-reference cleanup (commit
fc70d0d) — the
build version was bumped from1.0.0to1.1.0-SNAPSHOT,
opening the v1.1 development cycle, and 17 production / docs /
example references tov0.1andv0.2were updated to reflect
post-1.0 reality (descriptive comments namingv0.1ship-set
updated tov1.0; forward-lookingv0.2 will...notes updated to
v1.x will...).
v1.0.0
[1.0.0] — 2026-05-08
v1.0 locks the public API surface; the surface is stable across v1.x
patches. See docs/migration-0.1-to-1.0.md
for v0.1 → v1.0 surface changes with before/after examples.
Added
ActionInstance.paramInt(String)andparamLong(String)
(commitb544caa) — convenience accessors mirroring
PacketIn.metadataInt(String). Extract a primitive integer
parameter without manually wrapping
new BigInteger(1, b.toByteArray()). Throws
IllegalStateExceptionon absent parameter or value too wide for
the target primitive.PacketIn.metadataLong(String)(commitb544caa) —
long-shaped sister ofmetadataInt(String), closing the asymmetry
withparamInt/paramLongonActionInstance.- Symmetric
fromBytes(byte[])factories (commit42e3cfa) —
Ip4.fromBytes(byte[]),Ip6.fromBytes(byte[]),
Mac.fromBytes(byte[]),DeviceConfig.Bmv2.fromBytes(byte[]),
DeviceConfig.Raw.fromBytes(byte[]). The naming family mirrors
the pre-existingP4Info.fromBytes(byte[]). Defensive copy on
construction.
Changed
MastershipStatus.toString()format (commit23481ce) —
replaced the default record rendering with a compact, grep-friendly
form:Acquired(primary=10),Lost(prev=null, primary=10),
Lost(prev=5, primary=10). The unifiedprimary=Nfield name lets
one grep pattern catch both states. Practically disruptive for log
parsers; the underlyingElectionId.toString()is unchanged.DeviceConfig.{Bmv2,Raw}.fromFile(commit2bb1acd) — wraps
IOExceptionasjava.io.UncheckedIOExceptioninstead of plain
RuntimeException. Strictly more specific (subclass of
RuntimeException), so existingcatch (RuntimeException)
continues to work unchanged.- Accessor methods reject null parameter (commit
ff71c89) —
ActionInstance.param(String),PacketIn.metadata(String), and
TableEntry.match(String)now throwNullPointerExceptionfor a
null name, aligning with the project-wide null-rejection convention
documented in eachpackage-info.java. Unknown but non-null names
still returnnullas before. MastershipStatus.Acquired(null)rejected (commitff71c89) —
theAcquiredrecord's canonical constructor now rejects a null
ourElectionIdwithNullPointerException. TheLostrecord's
twoElectionIdfields remain nullable on purpose.- Actionable error messages on width-overflow (commit
b544caa) —
theIllegalStateExceptionthrown bymetadataInt/metadataLong
/paramInt/paramLongwhen a value exceeds the target
primitive's range now names the recommended alternative ("use
metadataLong or metadata(String) directly").
Documentation
- Thread-safety contracts (commit
9c58ee6) — explicit
thread-safety paragraphs on 11 user-facing classes (the four
builders,ReadQuery, three sealed-type interfaces, and three
schema-family final classes). Builders are not safe for concurrent
use; sealed-type record variants and schema metadata are safe to
share across threads. - Null contract (commit
ff71c89) — explicit project-wide
convention statement in all fourpackage-info.javafiles: public
methods reject null arguments withNullPointerExceptionunless
documented otherwise. Methods that accept null on purpose document
the null semantics in their@param. - Production readiness (commit
b106cfa) — README
## Production readinesssection with three subsections:
validation status (what the CI matrix exercises), production-ready
scope, known limitations and v1.x roadmap. - Migration guide (commit
8f20b03) — new
docs/migration-0.1-to-1.0.md
with before/after examples and per-change severity tags for
callers updating from v0.1.
Maven Central: io.github.zhh2001:jp4:1.0.0
Migration from v0.1: see docs/migration-0.1-to-1.0.md.
Full diff vs v0.1.0: v0.1.0...v1.0.0
jp4 v0.1.0
[0.1.0] — 2026-05-07
First release line. Public API surface is locked across v0.1.x patches.
Added
P4Switch— single-device controller. Synchronous and asynchronous
forms ofinsert/modify/delete/send;readquery builder;
onPacketIn/packetInStream/pollPacketIn;onMastershipChange;
bindPipeline/loadPipeline;asPrimary/asSecondaryarbitration;
auto-reconnect viaReconnectPolicy.AutoCloseable; idempotentclose().Connector— fluent builder forP4Switch. Configures device id,
election id, reconnect policy, packet-in queue size before the connect.TableEntry+BatchBuilder— fluent name-based table-entry
construction. P4Info-driven validation at switch-op time with
known-list error messages on misspelled tables / fields / actions /
params. Single-update and batched Write RPCs.ReadQuery— name-based table read..match(...)filters;.all()
/.one()/.stream()terminals (the latterAutoCloseable,
cancels the gRPC iterator on close);.allAsync()/.oneAsync()
variants returningCompletableFuture.PacketIn/PacketOut— three styles for receiving inbound
packets (callback /Flow.Publisher/ blocking poll, fan-out
semantics) and synchronous + async send.controller_packet_metadata
fields surface as a name-keyed map;metadataInt(...)convenience
for ≤31-bit values.P4Info— text and binary protobuf parsers; auto-detect format;
forward (name → entity) and reverse (id → entity) indexes for tables,
actions, match fields, action params, andcontroller_packet_metadata.Match— sealed type covering all five P4Runtime match kinds
(Exact,Lpm,Ternary,Range,Optional). Exhaustive switch at
compile time.- Strong types —
Bytes,Mac,Ip4,Ip6,ElectionId. Each is
a value type with deterministicequals/hashCode/toString. Every
match / param / metadata setter accepts both the strong type and
byte[]as an escape hatch. - Exception hierarchy —
P4RuntimeExceptionparent; three concrete
subclasses (P4ConnectionException,P4PipelineException,
P4OperationException);P4ArbitrationLostspecialises connection
failure for primary-denied scenarios. - Examples — three runnable Gradle modules under
examples/:
simple-l2-switch(learning switch),simple-loadbalancer
(LPM-based router with batch + read + modify),network-monitor
(secondary controller subscribing topacketInStream). - Docker BMv2 image pinning — test infrastructure pins
p4lang/behavioral-modelto a specific manifest digest for
reproducible CI runs. SeeNOTES.md.
Compatibility
- JDK 21+ — compiled to release 21; CI tests JDK 21 and JDK 25.
- P4Runtime spec — proto sources vendored from
p4lang/p4runtime; supports
v1.4 through v1.6 features. - Tested:
p4lang/behavioral-model(Docker image digest pinned to
sha256:7f28ab029368a17…). - Not tested but expected to work: other P4Runtime-spec-compliant
targets like Tofino and Stratum. jp4's API is target-agnostic; CI
only exercises BMv2.
Known issues
- Top-level-only partial-failure shape (non-BMv2 targets). When a
device returns a Write failure without per-update detail entries,
WriteResult.failures()is empty andallSucceeded()returns true.
BMv2 always returns details, so this is dormant against the verified
target. Targets that surface only top-level status will need a
deviceFailedmarker onWriteResult; tracked for v0.2. - JDK 24+ Netty
Unsafewarnings. gRPC's shaded Netty triggers
Unsafe-deprecation warnings on JDK 24+. The jp4 test JVM passes
--enable-native-access=ALL-UNNAMEDto suppress; downstream users
on JDK 24+ should do the same. SeeNOTES.md. - BMv2 Docker mastership-transition quirk. Under Docker BMv2,
back-to-back primary handovers occasionally surface the new client's
first arbitration as a stream error; jp4's connector layer does not
work around this — production controllers running against Docker
BMv2 should add explicit retry aroundP4Switch.connect. See
NOTES.md.
Roadmap (v0.2 candidates)
These were noticed during v0.1 development and are tracked but not
committed for any specific date:
- Multi-switch coordination (a
P4Controllerwith deliberate fan-out
/ parallelism / error-aggregation semantics). - Other entity-type reads — counters, meters, registers, action
profiles, multicast groups, packet replication. ReadQuery.where(Predicate<TableEntry>)andReadQuery.fields(...)
for client-side filtering and projection.DeviceConfig.Tofinovariant alongsideBmv2andRaw.sw.onPacketDropped(Consumer<DropEvent>)hook for backpressure
observability.- Symmetric
fromBytesfactories onDeviceConfig.Bmv2 / .Raw,
Ip4/Mac/Ip6.ActionInstance.paramInt(...)/paramLong(...)
to mirrorPacketIn.metadataInt(...). ReconnectPolicy.preserveRoleOnReconnect()so primary clients
re-arbitrate automatically after an auto-reconnect.Match.lpm(String cidr)CIDR-string factory (e.g.
Match.lpm("10.0.1.0/24")) so common LPM construction is one call
instead of three. Scope (IPv4-only / IPv6-only / both via
prefix-detection) decided at v0.2 design time.MastershipStatus.Lost/AcquiredtoString()simplification.
Current rendering is verbose
(Lost[previousElectionId=null, currentPrimaryElectionId=ElectionId(10)]);
a compact form likeLost(prev=null, primary=10)reads better
in pasted log output and downstream documentation.- Digest and IdleTimeout stream-message handlers (P4Runtime spec
§7 / §11.4) — currently ignored inInboundStreamHandlerwith a
// v0.2 workcomment. v0.2 will add typed subscription APIs
matching the existingonPacketIn/onMastershipChangeshape. - Examples-CI assertion strengthening — current
examples-l2/
examples-lb/examples-monitorjobs grep a small set of
distinctive lines from each example's stdout. v0.2 should diff the
full captured output against each example's README "Expected
output" block, so docs-vs-actual mismatches (like the missing
[MON] stream completedtrailer caught at v0.1 acceptance) fail
CI rather than waiting for a manual review pass. - GitHub Actions runtime upgrade — bump
actions/checkout/
actions/setup-java/gradle/actions/setup-gradlefrom@v4to
@v5(or current non-Node-20 majors). GitHub Actions Node 20
deprecation enforcement: 2026-06-02. Tracked inNOTES.md
item 2.