P4Runtime Specification
version 1.0.0
 The P4.org API Working Group
2019-03-28

Abstract. P4 is a language for programming the data plane of network devices. The P4Runtime API is a control plane specification for controlling the data plane elements of a device defined or described by a P4 program. This document provides a precise definition of the P4Runtime API. The target audience for this document includes developers who want to write controller applications for P4 devices or switches.

## 1. Introduction and Scope

This document is published by the P4.org API Working Group, which was chartered [16] to design and standardize vendor-independent, protocol-independent runtime APIs for P4-defined or P4-described data planes. This document specifies one such API, called P4Runtime. It is meant to disambiguate and augment the programmatic API definition expressed in Protobuf format and available at https://github.com/p4lang/p4runtime/tree/v1.0.0/proto.

### 1.1. P4 Language Version Applicability

P4Runtime is designed to be implemented in conjunction with the P416 language version or later. P414 programs should be translated into P416 to be made compatible with P4Runtime. This version of P4Runtime utilizes features which are not in P416 1.0, but were introduced in P416 1.1.0 [1].

### 1.2. In Scope

This specification document defines the semantics of P4Runtime messages, whose syntax is defined in Protobuf format. The following are in scope of P4Runtime:

• Runtime control of P4 built-in objects (tables and Value Sets) and Portable Switch Architecture (PSA) [18] externs (e.g. Counters, Meters, Action Profiles, ). We recommend that this version of P4Runtime be used with targets that are compliant with PSA version 1.1.0.
• Runtime control of architecture-specific (non-PSA) externs, through an extension mechanism.
• Basic session management for Software-Defined Networking (SDN) use-cases, including support for controller replication to enable control plane redundancy.
• Partition of the P4 forwarding elements into different roles, which can be assigned to different control entities.
• Packet I/O to enable streaming packets to & from the control plane.
• Batching support, with different atomicity guarantees.
• In-the-field device-reconfiguration with a new P4 data plane.

The following are in the scope of this specification document:

• Rationale for the P4Runtime design.
• Reference architecture and use-cases for deploying a P4Runtime service.
• Detailed description of the API semantics.
• Requirements for conformant implementations of the API.

### 1.3. Not In Scope

The following are not in scope of P4Runtime:

• Runtime control of elements outside the P4 language. For example, architecture-dependent elements such as ports, traffic management, etc. are outside of the P4 language and are thus not covered by P4Runtime. Efforts are underway to standardize the control of these via gNMI and gNOI APIs, using description models defined and maintained by the OpenConfig project [32]. An open source implementation of these APIs is also in progress as part of the Stratum project [34].
• Protobuf message definitions for runtime control of non-PSA externs. While P4Runtime includes an extension mechanism to support additional P4 architectures, it does not define the syntax or semantics of any additional control message for externs introduced by non-PSA architectures.

The following are not in scope of this specification document:

• Description of the P4 programming language; it is assumed that the reader is already familiar with P416 [1].
• Descriptions of gRPC and Protobuf files in general.
• Controller role definition (for partition of P4 entities); the P4.org API Working Group may publish a companion document in the future describing one possible role definition scheme.

## 2. Terms and Definitions

arbitration
Refers to the process through which P4Runtime ensures that at any given time, there is a single master (i.e. a client with write access) for a given role. Also referred to as “master-slave arbitration”.
client
The gRPC client is the software entity which controls the P4 target or device by communicating with the gRPC agent or server. The client may be local (within the device) or remote (for example, an SDN controller).
COS
Class of Service.
device
Synonymous with target, although device usually connotes a physical appliance or other hardware, whereas target can signify hardware or software.
entity
An instantiated P4 program object such as a table or an extern (from PSA or any other architecture).
gRPC
gRPC Remote Procedure Calls, an open-source client-server RPC framework. See [9].
HA
High-Availability. Refers to a redundancy architecture.
Instrumentation
The part of the P4Runtime server which implements the calls to the device or target native “SDK” or backend.
IPC
Inter-Process Communication.
P4 Blob
A more colloquial term for P4 Device Config (Blob = Binary Large Object).
P4 Device Config
The output of the P4 compiler backend, which is included in the Forwarding Pipeline Config. This is opaque, architecture- and target-specific binary data which can be loaded onto the device to change its “program.”
P4Info
Metadata which specifies the P4 entities which can be accessed via P4Runtime. These entities have a one-for-one correspondence with instantiated objects in the P4 source code.
P4RT
Abbreviation for P4Runtime.
Protobuf (Protocol Buffers)
The wire serialization format for P4Runtime. Protobuf version 3 (proto3) is used to define the P4Runtime interface. See [19].
PSA
Portable Switch Architecture [18]; a target architecture that describes common capabilities of network switch devices that process and forward packets across multiple interface ports.
RPC
Remote Procedure Call.
RTT
Round-trip time.
SDN
Software-Defined Networking, an approach to networking that advocates the separation of the control and forwarding planes, as well as the abstraction of the networking infrastructure, in order to promote programmability of the network control. SDN is often associated with OpenFlow, a communications protocol that enables remote control of the network infrastructure through a programmable, centralized network controller.
SDN port
A 32-bit port number defined by a remote Software-Defined Network (SDN) controller. The SDN port number maps to a unique device port id, which may be in a different number space.
server
The gRPC server which accepts P4Runtime requests on the device or target. It uses instrumentation to translate P4Runtime API calls into target-specific actions.
stream
Refers to a gRPC Stream, which is a RPC on which several messages can be sent and received. P4Runtime defines one Stream RPC (StreamChannel), which is a bidirectional stream (both the client and the server can send messages) which is used for packet I/O and master-slave arbitration, among other things.
switch config
Refers to the non-forwarding config (different from the P4 Forwarding Pipeline Config) that is delivered to the switch via a different interface. For example, the switch config may be captured using OpenConfig models and delivered through a gNMI interface.
target
The hardware or software entity which “executes” the P4 pipeline and hosts the P4Runtime Service; often used interchangeably with “device”.
URI
Uniform Resource Identifier; a string of characters designed for unambiguous identification of resources.

## 3. Reference Architecture

Figure 1 represents the P4Runtime Reference Architecture. The device or target to be controlled is at the bottom, and one or more controllers is shown at the top. A multi-master protocol allows more than one controller to participate, and a role-based arbitration scheme ensures only one controller has write access to each read/write entity, or the pipeline config itself. Any controller may perform read access to any entity or the pipeline config. Later sections describe this in detail. For the sake of brevity, the term controller may refer to one or more controllers.

The P4Runtime API defines the messages and semantics of the interface between the client(s) and the server. The API is specified by the p4runtime.proto Protobuf file, which is available on GitHub as part of the standard [14]. It may be compiled via protoc the Protobuf compiler to produce both client and server implementation stubs in a variety of languages. It is the responsibility of target implementers to instrument the server.

Reference implementations of P4 targets supporting P4Runtime, as well as sample clients, may be available on the p4lang/PI GitHub repository [15]. A future goal may be to produce a reference gRPC server which can be instrumented in a generic way, e.g. via callbacks, thus reducing the burden of implementing P4Runtime.

The controller can access the P4 entities which are declared in the P4Info metadata. The P4Info structure is defined by p4info.proto, another Protobuf file available as part of the standard.

The controller can also set the ForwardingPipelineConfig, which amounts to installing and running the compiled P4 program output, which is included in the p4_device_config Protobuf message field, and installing the associated P4Info metadata. Furthermore, the controller can query the target for the ForwardingPipelineConfig to retrieve the device config and the P4Info.

### 3.1. Idealized Workflow

In the idealized workflow, a P4 source program is compiled to produce both a P4 device config and P4Info metadata. These comprise the ForwardingPipelineConfig message. A P4Runtime controller chooses a configuration appropriate to a particular target and installs it via a SetForwardingPipelineConfig RPC. Metadata in the P4Info describes both the overall program itself (PkgInfo) as well as all entity instances derived from the P4 program tables and extern instances. Each entity instance has an associated numeric ID assigned by the P4 compiler which serves as a concise “handle” used in API calls.

In this workflow, P4 compiler backends are developed for each unique type of target and produce P4Info and a target-specific device config. The P4Info schema is designed to be target and architecture-independent, although the specific contents are likely to be architecture-dependent. The compiler ensures the code is compatible with the specific target and rejects code which is incompatible.

In some use cases, it is expected that a controller will store a collection of multiple P4 “packages”, where each package consists of the P4 device config and P4Info, and install them at will onto the target. A controller can also query the ForwardingPipelineConfig from the target via the GetForwardingPipelineRequest RPC. This can be useful to obtain the pipeline configuration from a running device to synchronize the controller to its current state.

### 3.2. P4 as a Behavioral Description Language

P4 can be considered a behavioral description of a switching device which may or may not execute “P4” natively. There is no requirement that a P4 compiler be used in the production of either the P4 device config or the P4Info. There is no absolute requirement that the target accept a SetForwardingPipelineRequest to change its pipeline “program”, as some devices may be fixed in function, or configured via means other than P4 programs. Furthermore, a controller can run without a P4 source program, since the P4Info file provides all of the information necessary to describe the P4Runtime API messages needed to configure such a device.

While a P4 program does provide a precise description of the data plane behavior, and this can prove invaluable in writing correct control plane software, in some cases it is enough for a control plane software developer to have the control plane API, plus good documentation of the data plane behavior. Some device vendors may wish to keep their P4 source code private. The minimum requirement for the controller and device to communicate properly is a P4Info file that can be loaded by a controller in order to render the correct P4Runtime API.

In such scenarios, it is crucial to have detailed documentation, perhaps included in the P4Info file itself, specifically the metadata in the PkgInfo message as well as the embedded doc fields. Nevertheless, a P4 program which describes the pipeline is ideally available. The contents of the P4Info file will be described in later sections.

### 3.3. Alternative Workflows

Given the notions above concerning P4 code as behavioral description and P4Info as API metadata, some other workflows are possible. The scenarios below are just examples and actual situations may vary.

#### 3.3.1. P4 Source Available, Compiled into P4Info but not Compiled into P4 Device Config

In this situation, P4 source code is available mainly as a behavioral model and compiled to produce P4Info, but it is not compiled to produce the p4_device_config. The device's configuration might be derived via some other means to implement the P4 source code's intentions. The P4 code, if available, can be studied to understand the pipeline, and the P4Info can be used to implement the control plane.

#### 3.3.2. No P4 Source Available, P4Info Available

In this situation, P4Info is available but no P4 source is available for any number of reasons, the most likely of which are:

1. The vendor or organization does not wish to divulge the P4 source code, to protect intellectual property or maintain security.

2. The target was not implemented using P4 code to begin with, although it still obeys the control plane API specified in the P4Info.

As discussed in Section 3.2, in the absence of a P4 program describing the data plane behavior, the detailed knowledge required to write correct control plane code must come from other sources, e.g. documentation.

#### 3.3.3. Partial P4Info and P4 Source are Available

In this situation, a subset of the target's pipeline configuration is exposed as P4 source code and P4Info. The complete device behavior might be expressed as a larger P4 program and P4Info, but these are not exposed to everybody. This limits API access to only certain functions and behaviors. The hidden functions and APIs might be available to select users who would have access to the complete P4Info and possibly P4 source code.

#### 3.3.4. P4Info Role-Based Subsets

In this situation, P4Info is selectively packaged into role-based subsets to allow some controllers access to just the functionality required. For example, a controller may only need read access to statistics counters and nothing more.

## 4. Controller Use-cases

P4Runtime allows for more than one controller. The mechanisms and semantics are described in a later section. Here we present a number of use-cases. Each use-case highlights a particular aspect of P4Runtime's flexibility and is not intended to be exhaustive. Real-world use-cases may combine various techniques and be more complex.

### 4.1. Single Embedded Controller

Figure 2 shows perhaps the simplest use-case. A device or target has an embedded controller which communicates to an on-board switch via P4Runtime. This might be appropriate for an embedded appliance which is not intended for SDN use-cases.

P4Runtime was designed to be a viable embedded API. Complex controller architectures typically feature multiple processes communicating with some sort of IPC (Inter-Process Communications). P4Runtime is thus both an ideal RPC and an IPC.

### 4.2. Single Remote Controller

Figure 3 shows a single remote Controller in charge of the P4 target. In this use-case, the device has no control of the pipeline, it just hosts the server. While this is possible, it is probably more practical to have a hybrid use-case as described in subsequent sections.

### 4.3. Embedded+ Single Remote Controller

Figure 4 illustrates the use-case of an embedded controller plus a single remote controller. Both controllers are clients of the single server. The embedded controller is in charge of one set of P4 entities plus the pipeline configuration. The remote controller is in charge of the remainder of the P4 entities. An equally-valid, alternative use-case, could assign the pipeline configuration to the remote controller.

For example, to minimize round-trip times (RTT) it might make sense for the embedded controller to manage the contents of a fast-failover table. The remote controller might manage the contents of routing tables.

### 4.4. Embedded+ Two Remote Controllers

Figure 5 illustrates the case of an embedded controller similar to the previous use-case, and two remote controllers. One of the remote controllers is responsible for some entities, e.g. routing tables, and the other remote controller is responsible for other entities, perhaps statistics tables. Role-based access divides the ownership.

### 4.5. Embedded Controller+ Two High-Availability Remote Controllers

Figure 6 illustrates a single embedded controller plus two remote controllers in an active-standby HA (High-Availability) configuration. Controller #1 is the active controller and is in charge of some entities. If it fails, Controller #2 takes over and manages the tables formerly owned by Controller #1. The mechanics of HA architectures are beyond the scope of this document, but the P4Runtime multi-master arbitration scheme supports it.

## 5. Master-Slave Arbitration and Controller Replication

The P4Runtime interface allows multiple controllers to be connected to the P4Runtime server running on the device at the same time for the following reasons:

1. Partitioning of the control plane: Multiple controllers may have orthogonal, non-overlapping, “roles” (or “realms”) and should be able to push forwarding entities simultaneously. The control plane can be partitioned into multiple roles and each role will have a set of controllers, one of which is the master and the rest are slaves. Role definition, i.e. how P4 entities get assigned to each role, is out-of-scope of this document.

2. Redundancy and fault tolerance: Supporting multiple controllers allows having one or more standby slave controllers, which take over controlling the devices in case the master controller goes offline.

To support multiple controllers, P4Runtime uses the streaming channel (available via StreamChannel RPC) for session management. The workflow is described as follows:

• Each controller instance (e.g. a controller process) can participate in one or more roles. For each (device_id, role_id), the controller receives an election_id. This election_id can be the same for different roles and/or devices, as long as the tuple (device_id, role_id, election_id) is unique. For each (device_id, role_id) that the controller wishes to control, it establishes a StreamChannel with the P4Runtime server responsible for that device, and sends a MasterArbitrationUpdate message containing that tuple of (device_id, role_id, election_id) values. The P4Runtime server selects a master independently for each (device_id, role_id) pair. The master is the client that has the highest election_id among all active StreamChannel connections with the same (device_id, role_id) values. A connection between a controller instance and a device id which involves a persistent StreamChannel can be referred to as a P4Runtime client.

Note that the P4Runtime server does not assign a role_id or election_id to any controller. It is up to an arbitration mechanism outside of the server to decide on the controller roles, and the election_id values used for each StreamChannel. The P4Runtime server only keeps track of the (device_id, role_id, election_id) of each StreamChannel that has sent a successful MasterArbitrationUpdate message, and maintains the invariant that all such 3-tuples are unique. A server must use all three of these values from a WriteRequest message to identify which client is making the WriteRequest, not only the election_id. This enables controllers to re-use the same numeric election_id values across different (device_id, role_id) pairs. P4Runtime does not require election_id values be reused across such different (device_id, role_id) pairs; it allows it.

• To start a controller session, a controller first opens a bidirectional stream channel to the server via the StreamChannel RPC for each device. This is the first thing the controller does to identify itself to the P4Runtime server on the device. This stream will be used for two purposes:

• Session management: As soon as the controller opens the stream channel, it sends a StreamMessageRequest message to the switch. The controller populates the MasterArbitrationUpdate field in this message using its role_id and election_id. Note that the status field in the MasterArbitrationUpdate is not populated by the controller. This field is populated by the P4Runtime server when it sends a response back to the client, as explained below.

• Streaming of notifications (e.g. digests) and packet I/O: The same streaming channel will be used for streaming notifications, as well as for packet-in and packet-out messages. Note that unless specified otherwise by the role definitions, only the master controller can participate in packet I/O. This feature is explained in more details in the Packet I/O section.

• Note that the stream is opened per device. In case a switching platform has multiple devices (e.g. multi-ASIC line card) which are all controlled via the same P4Runtime server, it is possible to have different masters for different devices. In this case, it is the responsibility of the P4Runtime server to keep track of the master for each device (and role). More specifically, the P4Runtime server will know which stream corresponds to the master controller for each pair of (device_id, role_id) at any point of time.

• The streaming channel between the controller and the server defines the liveness of the controller session. The controller is considered “offline” or “dead” as soon as its corresponding stream channel to the switch is broken, in which case the P4Runtime server quickly sets one of the slave controllers with the highest election_id as master.

• The mechanism via which the controller receives the P4Runtime server details which includes the device_id, ip and port, as well as the mechanism via which it receives the Forwarding Pipeline Config, are implementation specific and beyond the scope of this specification. Similarly, the mechanism via which the P4Runtime server receives its switch config (which notably includes the device_id) is beyond the scope of this specification. Nevertheless, if the server details or switch config are transferred via the network, it is recommended to use TLS or similar encryption and authentication mechanisms to prevent eavesdropping attacks.

• After the controller sends a StreamMessageRequest message to the P4Runtime server, the server sends a StreamMessageResponse message back to the controller, in which it populates the MasterArbitrationUpdate. The controller must populate the device_id, role, and election_id fields. The election_id field is set to the highest value, i.e. the value for the current master. The server also populates the status field in the MasterArbitrationUpdate (note that this field is not populated in the MasterArbitrationUpdate received by the controller). The value of the status message is one of the following:

• OK (with status.code set to google.rpc.OK) when the controller is determined to be the master for a given (device_id, role_id).
• Non-OK (with status.code set to google.rpc.ALREADY_EXISTS) when the controller is determined to be a slave for a given (device_id, role_id).

gRPC enables the server to identify which client originated each message in the StreamChannel stream. For example, the C++ gRPC library [10] in synchronous mode enables a server process to cause a function to be called when a new client creates a StreamChannel stream. This function should not return until the stream is closed and the server has done any cleanup required when a StreamChannel is closed normally (or broken, e.g. because a client process unexpectedly terminated). Thus the server can easily associate all StreamChannel messages received from the same client, because they are processed within the context of the same function call.

A P4Runtime implementation need not rely on the gRPC library providing information with unary RPC messages that identify which client they came from. Unary RPC messages include requests to write table entries in the data plane, or read state from the data plane, among others described later. P4Runtime relies on clients identifying themselves in every write request, by including the values device_id, role_id, and election_id in all write requests. The server trusts clients not to use a triple of values other than their own in their write requests. gRPC provides authentication methods [8] that should be deployed to prevent untrusted clients from creating channels, and thus from making changes or even reading the state of the server.

### 5.1. Default Role

A controller can omit the role message in MasterArbitrationUpdate. This implies the “default role”, which corresponds to “full pipeline access”. This also implies that a default role has a role.id of 0 (default). If using a default role, all RPCs from the controller (e.g. Write) must set the role_id to 0.

### 5.2. Role Config

The role.config field in the MasterArbitrationUpdate message sent by the controller describes the role configuration, i.e. which operations are in the scope of a given role. In particular, the definition of a role may include the following:

• A list of P4 entities for which the controller may issue Write updates and receive notification messages (e.g. DigestList and IdleTimeoutNotification).
• Whether the controller is able to receive PacketIn messages, along with a filtering mechanism based on the values of the PacketMetadata fields to select which PacketIn messages should be sent to the controller.
• Whether the controller is able to send PacketOut messages, along with a filtering mechanism based on the values of the PacketMetadata fields to select which PacketOut messages are allowed to be sent by the controller.

An unset role.config implies “full pipeline access” (similar to the default role explained above). In order to support different role definition schemes, role.config is defined as an Any Protobuf message [28]. Such schemes are out-of-scope of this document. When partitioning of the control plane is desired, the P4Runtime client(s) and server need to agree on a role definition scheme in an out-of-band fashion.

### 5.3. Rules for Handling MasterArbitrationUpdate Messages Received from Controllers

1. If the MasterArbitrationUpdate message is received for the first time (for a newly connected controller):

1. If device_id does not match any of the devices known to the P4Runtime server, the server shall terminate the stream by returning a NOT_FOUND error.

2. If the election_id is already used by another controller for the same (device_id, role_id), the P4Runtime server shall terminate the stream by returning an INVALID_ARGUMENT error.

3. If the max number of clients for the given (device_id, role_id) exceeds the supported limit, the P4Runtime server shall terminate the stream by returning a RESOURCE_EXHAUSTED error.

4. Otherwise, the controller is added to a list of connected controllers for the given (device_id, role_id) and the controller is notified by sending a StreamMessageResponse message back to it, as explained earlier.

2. If the MasterArbitrationUpdate message is received from an already connected controller:

1. If the device_id does not match the one already assigned to this stream, the P4Runtime server shall terminate the stream by returning a FAILED_PRECONDITION error.

2. Otherwise, if the role.id matches the current role_id assigned to this stream:

1. If the election_id also matches the one assigned to this stream, the server will accept the (new) role.config only if this controller is the current master. If the controller is not a master, the operation is a no-op.

2. If the election_id is already assigned to another controller stream for the same (device_id, role_id), the P4Runtime server shall terminate the stream by returning an INVALID_ARGUMENT error.

3. Otherwise, the P4Runtime server updates the election_id for this controller. If this makes the client the new master, the server will also accept the given role.config and follow the “mastership change rules” described in the following section.

3. Otherwise (i.e. role.id is different from current role_id assigned to this stream), the P4Runtime server moves the controller to the new role. This controller will then be treated as a new controller for the new (device_id, role_id). The server accepts the given role.config only if the client becomes master, in which case the server also follows the “mastership change rules” described in the following section.

If role.config does not match the “out-of-band” scheme previously agreed upon, the server must return an INVALID_ARGUMENT error.

### 5.4. Mastership Change

“Mastership change” refers to either one of these cases:

1. A new MasterArbitrationUpdate is received from an already connected controller for a given (device_id, role_id), which changes the controller mastership status (the controller becomes master or slave).

2. A streaming channel for a given master controller breaks, forcing a new master to be elected.

In case of a mastership change, the P4Runtime server shall send the election_id of the master to all the connected controllers for a given (device_id, role_id). The StreamMessageResponse sent back to all the connected controllers has a MasterArbitrationUpdate message populated with the device_id, role_id, and election_id of the master, as well as an OK status for the master and non-OK status (with ALREADY_EXISTS error code) for slaves.

## 6. The P4Info Message

The purpose of P4Info was described under Reference Architecture. Here we describe the various components.

### 6.1. Common Messages

These messages appear nested within many other messages.

#### 6.1.1. Documentation Message

Documentation is used to carry both brief and long descriptions of something. Good content within a documentation field is extremely helpful to P4Runtime application developers.

message Documentation {
// A brief description of something, e.g. one sentence
string brief = 1;
// A more verbose description of something.
// Multiline is accepted. Markup format (if any) is TBD.
string description = 2;
}

#### 6.1.2. Preamble Message

The preamble serves as the “descriptor” for each entity and contains the unique instance ID, name, alias, annotations and documentation.

message Preamble {
// ids share the same number-space; e.g. table ids cannot overlap with counter
// ids. Even though this is irrelevant to this proto definition, the ids are
// allocated in such a way that it is possible based on an id to deduce the
// resource type (e.g. table, action, counter, ...). This means that code
// using these ids can detect if the wrong resource type is used
// somewhere. This also means that ids of different types can be mixed
// (e.g. direct resource list for a table) without ambiguity. Note that id 0
// is reserved and means "invalid id".
uint32 id = 1;
// fully qualified name of the P4 object, e.g. c1.c2.ipv4_lpm
string name = 2;
// an alias (alternative name) for the P4 object, probably shorter than its
// fully qualified name. The only constraint is for it to be unique with
// respect to other P4 objects of the same type. By default, the compiler uses
// the shortest suffix of the name that uniquely identifies the object. For
// example if the P4 program contains two tables with names s.c1.t and s.c2.t,
// the default aliases will respectively be c1.t and c2.t. In the future, the
// P4 programmer may also be able to override the default alias for any P4
// object (TBD).
string alias = 3;
repeated string annotations = 4;
// Documentation of the entity
Documentation doc = 5;
}

#### 6.1.3. Annotating P4 Entities with Documentation

P4 entities may be annotated using the following annotations:

@brief(string...)
@description(string...)

Attaching either or both of these annotations to an entity will generate a P4Info Documentation Message, which in turn will appear in the Preamble Message for the entity.

The P4 compiler should not emit annotation messages in the P4Info for these specific cases; instead, it should generate the Documentation messages as described.

The following example shows documentation annotations for a table entity:

@brief("Match IPv4 addresses to next-hop MAC and port")
@description("Match IPv4 addresses to next-hop MAC and port. \
Uses LPM match type.")
table my_ipv4_lkup {
...
}

### 6.2. PkgInfo Message

The PkgInfo message contains package-level metadata which describes the overall P4 program itself, as opposed to P4 entities. PkgInfo can be extracted and used to facilitate “browsing” of available P4 programs from a library. Although all fields are technically “optional,” every implementation should include as a minimum the name, version, doc and arch fields. The other fields are recommended to be included.

// Can be used to manage multiple P4 packages.
message PkgInfo {
// a definitive name for this configuration, e.g. switch.p4_v1.0
string name = 1;
// configuration version, free-format string
string version = 2;
// brief and detailed descriptions
Documentation doc = 3;
// Miscellaneous metadata, free-form; a way to extend PkgInfo
repeated string annotations = 4;
// the target architecture, e.g. "psa"
string arch = 5;
// organization which produced the configuration, e.g. "p4.org"
string organization = 6;
// contact info for support,e.g. "tech-support@acme.org"
string contact = 7;
string url = 8;
}

#### 6.2.1. Annotating P4 code with PkgInfo

A P4 progam's PkgInfo may be declared using one or more of the following annotations, attached to the main block only:

@pkginfo(key=value)
@pkginfo(key=value[,key=value,...])
@brief("A brief description")
@description("A longer\
description")
@custom_annotation(...)
@another_custom_annotation(...)

Above we see several different types of annotations:

• @pkginfo - This is used to populate a specific field within the PkgInfo message. Multiple @pkginfo annotations are allowed. For compactness, multiple key-value pairs can appear in a single @pkginfo annotation, separated by commas. Each key must only appear once and the compiler must reject the program if one appears multiple times. The keys must be from among the message fields inside PkgInfo, for example, name, version, etc. Each key-value pair assigns a value to the corresponding field inside the single PkgInfo message for the program's P4Info. One exception is that the Documentation field of PkgInfo must be expressed as individual @description and @brief annotations, see next bullets. The key arch will be ignored (with a warning) by the compiler. The value for this should come from the compiler itself.

• @brief - This will populate the PkgInfo.doc.brief message field.

• @description - This will populate the PkgInfo.doc.description message field

• @<anything else> - This will create a PkgInfo.annotation entry

Declaring one or more of these annotations on main will generate a single corresponding PkgInfo message in the P4Info as described in PkgInfo Message.

The following example shows @pkginfo annotations using a mixture of single and multiple key-value pairs. It also shows @brief and @description annotations, plus some additional custom annotations. The well-known annotations will produce corresponding fields inside the PkgInfo message. The custom annotations will be appended to the PkgInfo.annotations list.

@pkginfo(name="switch.p4",version="2")
@pkginfo(organization="p4.org")
@pkginfo(contact="info@p4.org")
@pkginfo(url="www.p4.org")
@brief("L2/L3 switch")
@description("L2/L3 switch.\
Built for data-center profile.")
@my_annotation1(...) // Not well-known, this will appear in PkgInfo annotations
@my_annotation2(...) // Not well-known, this will appear in PkgInfo annotations
PSA_Switch(IgPipeline, PacketReplicationEngine(), EgPipeline,
BufferingQueueingEngine()) main;

### 6.3. ID Allocation for P4Info Objects

P4Info objects receive a unique ID, which is used to identify the object in P4Runtime messages. IDs are 32-bit unsigned integers which are assigned by the compiler during the P4Info generation process. IDs are assigned in such a way that it is possible based on the ID value alone to deduce the type of the object (e.g. table, action, counter, ). The most significant 8 bits of the ID encodes the object type (as per Table 1). The p4info.proto file includes a mapping from object type to 8-bit prefix value, encoded as an enum definition (p4.config.v1.P4Ids.Prefix). These values must be used (e.g. by the compiler) when allocating IDs. The remaining 24 bits must be generated in such a way that the resulting IDs must be globally unique in the scope of the P4Info message. Table 2 shows the ID layout.

It is possible to statically set the least-significant 24 bits of the ID in the P4 program source by annotating the object with @id (see Table 3). The compiler must honor the @id annotations when generating the P4Info message and must fail the compilation if statically-assigned ID suffixes lead to non-unique IDs (i.e. if the P4 programmer tries to assign the same ID suffix to two different P4 objects of the same type by annotating them with the same @id value). Note that it is not possible for the P4 programmer to change the value of the 8-bit ID prefix, which encodes the object type.

### 6.4. P4Info Objects

#### 6.4.1. Table

Table messages are used to specify all possible match-action tables exposed to a control plane. This message contains the following fields:

• preamble, a Preamble message with the ID, name, and alias of this table.

• match_fields, a repeated field of type MatchField representing the data to be used to construct the lookup key matched in this table. Each MatchField message is defined with the following fields:

• id, the uint32 identifier of this MatchField, unique in the scope of this table. No rules are prescribed on the way MatchField IDs should be allocated, as long as two MatchField of the same table do not have the same ID. Nonetheless, we recommend that the IDs be assigned incrementally, starting from 1, in the same order as in the P4 key declaration.

• name, the string representing the name of this MatchField.

• annotations, a repeated field of strings, each one representing a P4 annotation associated to this match field.

• bitwidth, an int32 value set to the size in bits of this match field.

• match, a oneof describing the match behavior for this field; it can be either:

• match_type, an enum field of type MatchType, which includes all possible PSA match kinds.
• other_match_type, a string field which can be used to encode any architecture-specific match type.
• doc, a Documentation message describing this match field.

• type_name, which indicates whether the match field has a user-defined type; this is useful for translation.

• action_refs, a repeated ActionRef field representing the set of possible actions for this table. The ActionRef message is used to reference an action specified in the same P4Info message and it includes the following fields:

• id, the uint32 identifier of the action.
• scope, an enum value which can take one of three values: TABLE_AND_DEFAULT, TABLE_ONLY and DEFAULT_ONLY. The scope of the action is determined by the use of the P4 standard annotations @tableonly and @defaultonly [17]. TABLE_ONLY (@tableonly annotation) means that the action can only appear within the table, and never as the default action. DEFAULT_ONLY (@defaultonly annotation) means that the action can only be used as the default action. TABLE_AND_DEFAULT is the default value for the enum and means that neither annotation was used in P4 and that the action can be used both within the table and as the default action.
• annotations, a repeated string field, each one representing a P4 annotation associated to the action reference in this table.
• const_default_action_id, if this table has a constant default action, this field will carry the uint32 identifier of that action, otherwise its value will be 0. A default action is executed when a matching table entry is not found for a given packet. Being constant means that the control plane cannot set a different default action at runtime or change the default action's arguments.

• implementation_id, the uint32 identifier of the “implementation” of this table. 0 (default value) means that the table is a regular (direct) match table. Otherwise, this field will carry the ID of an extern instance specified in the same P4Info message (e.g. a PSA ActionProfile or ActionSelector instance). The table is then referred to as an indirect match table.

• direct_resource_ids, repeated uint32 identifiers for all the direct resources attached to this table, such as DirectMeter and DirectCounter instances, specified in the same P4Info message. In this version of the P4Runtime specification only one direct resource of each type can be associated to a table, hence for PSA programs this field is expected to have a maximum size of 2.

• size, an int64 describing the desired number of table entries that the target should support for the table. See the “Size” subsection within the “Table Properties” section of the P416 language specification for details [27].

• idle_timeout_behavior, which describes the behavior of the data plane when the idle timeout of a table entry expires (see Idle-Timeout section). Value can be any of the IdleTimeoutBehavior enum:

• NO_TIMEOUT (default value), which means that idle timeout is not supported for this table.
• NOTIFY_CONTROL, which means that the control plane should be notified of the expiration of a table entry by means of a notification (see section on Table Idle Timeout Notifications).
• is_const_table, a boolean flag indicating that the table is filled with static entries and cannot be modified by the control plane at runtime.

• other_properties, an Any Protobuf message [28] to embed architecture-specific table properties [27] which are not part of the core P4 language or of the PSA architecture.

#### 6.4.2. Action

Action messages are used to specify all possible actions of all match-action tables.

The Action message defines the following fields:

• preamble, a Preamble message with the ID, name, and alias of this action

• params, a repeated field of Param messages representing the set of runtime parameters that should be provided by the control plane when inserting or modifying a table entry with this action. Each Param message contains the following fields:

• id, the uint32 identifier of this parameter. No rules are prescribed on the way Param IDs should be allocated, as long as two Param of the same action do not have the same ID. Nonetheless, we recommend that the IDs be assigned incrementally, starting from 1, in the same order as in the P4 action declaration.
• name, the string representing the name of this parameter.
• annotations, a repeated field of strings, each one representing a P4 annotation associated to this parameter.
• bitwidth, an int32 value set to the size in bits of this parameter.
• doc, which describes this parameter using a Documentation message.
• type_name, which indicates whether the action parameter has a user-defined type; this is useful for translation.

#### 6.4.3. ActionProfile

ActionProfile messages are used to specify all available instances of Action Profile and Action Selector PSA externs.

PSA Action Profiles are used to describe implementations of match-action tables where multiple table entries can share the same action instance. Indeed, differently from a regular match-action table where each entry contains the action specification, when using Action Profile-based tables, the control plane can insert entries pointing to an Action Profile member, where each member then points to an action instance. The control plane is responsible for creating, modifying, or deleting members at runtime.

PSA Action Selectors extend Action Profiles with the capability of bundling together multiple members into groups. Match-action table entries can point to a member or group. When processing a packet, if the table entry points to a group, a dynamic selection algorithm is used to select a member from the group and apply the corresponding action to the packet. The dynamic selection algorithm is typically specified in the P4 program when instantiating the Action Selector, however it is not specified in the P4Info. The control plane is responsible for creating, modifying, or deleting both members and groups at runtime.

While PSA defines Action Profile and Action Selector as two different externs, P4Info uses the same ActionProfile message to describe both.

The ActionProfile message includes the following fields:

• preamble, a Preamble message with the ID, name, and alias of this Action Profile or Selector.

• table_ids, a repeated field of uint32 identifiers used to reference tables whose implementation uses this Action Profile or Selector.

• with_selector, a boolean flag indicating whether this message describes an instance of a PSA Action Selector extern.

• size, an int64 representing the maximum number of member entries that the Action Profile can hold, or, in the case of an Action Selector, the maximum sum of all member weights across all selector groups.

• max_group_size, an int32 representing the maximum sum of all member weights within any given selector group, in the case of an Action Selector, or 0 for an Action Profile. PSA programs can use the @max_group_size annotation to provide this value for Action Selectors. If the annotation is omitted, the P4Info field will default to 0.

#### 6.4.4. Counter&DirectCounter

Counter and DirectCounter messages are used to specify all possible instances of Counter and Direct Counter PSA externs respectively. Both externs are used to represent data plane counters that keep statistics such as the number of packets or bytes. The main difference between (indexed) counters and direct counters is:

• Indexed counters provide a fixed number of independent counter values, also called cells. Each cell can be read by the control plane using an integer index.

• Direct counters are associated a given match-action table, providing as many cells as the number of entries in the table.

Both Counter and DirectCounter messages share the following fields:

• preamble, a Preamble message with the ID, name, and alias of this counter extern instance.

• spec, a message of of type CounterSpec used to describe the compile-time configuration of this counter. Currently, the CounterSpec message is used to carry only the counter unit, which can be any of the CounterSpec.Unit enum values:

• UNSPECIFIED: reserved value.
• BYTES: byte counter.
• PACKETS: packet counter.
• BOTH: combination of both byte and packet counter.

For indexed counters, the Counter message contains also a size field, an int64 representing the maximum number of independent values that can be held by this counter array. Conversely, the DirectCounter message contains a direct_table_id field that carries the unit32 identifier of the table to which this direct counter is attached.

For indexed counters, the Counter message contains also an index_type_name field, which indicates whether the index has a user-defined type. This is useful for translation. The underlying built-in type must be a fixed-width unsigned bitstring (bit<W>).

#### 6.4.5. Meter&DirectMeter

Meter and DirectMeter messages are used to specify all possible instances of Meter and Direct Meter PSA externs. Both externs provide mechanism to keep data plane statistics typically used to mark or drop packets that exceed a given packet or bit rate. Similarly to counters, the main difference between (indexed) meters and direct meters is:

• Indexed meters provide a fixed number of independent meter values, also called cells. Each cell can be accessed by the control plane using an integer index, e.g. to set the rate threshold.

• Direct meters are associated to match-action tables, providing as many cells as the number of entries in the table.

Both Meter and DirectMeter messages share the following fields:

• preamble, a Preamble message with the ID, name, and alias of this meter extern instance.

• spec, a message of type MeterSpec used to describe the capabilities of this meter extern instance. Currently, the MeterSpec message is used to carry only the meter unit, which can be any of the MeterSpec.Unit enum values:

• UNSPECIFIED: reserved value.
• BYTES, which signifies that this meter can be configured with rates expressed in bytes/second.
• PACKETS, for rates expressed in packets/second.

For indexed meters, the Meter message contains also a size field, an int64 representing the maximum number of independent cells that can be held by this meter. Conversely, the DirectMeter message contains a direct_table_id field that carries the uint32 identifier of the table to which this direct meter is attached.

For indexed meters, the Meter message contains also an index_type_name field, which indicates whether the index has a user-defined type. This is useful for translation. The underlying built-in type must be a fixed-width unsigned bitstring (bit<W>).

#### 6.4.6. ControllerPacketMetadata

ControllerPacketMetadata messages are used to describe any metadata associated with controller packet-in and packet-out. A packet-in is defined as a data plane packet that is sent by the P4Runtime server to the control plane for further inspection. Similarly, a packet-out is defined as a data packet generated by the control plane and injected in the data plane via the P4Runtime server.

When inspecting a packet-in, the control plane might need to have access to additional information such as the original data plane port where the packet was received, the timestamp when the packet was received, if the packet is a clone, etc. Similarly, when sending a packet-out, the control plane might need to specify additional information used by the device to process the data packet.

Such additional information for packet-in and packet-out can be expressed by means of P4 headers carrying P4 standard annotations @controller_header("packet_in") and @controller_header("packet_out"), respectively. ControllerPacketMetadata messages capture the information contained within these special headers and are needed by the P4Runtime server to process packet-in and packet-out stream messages (see section on Packet I/O stream messages).

A P4Info message can contain at most two ControllerPacketMetadata messages, one describing the packet-in header, and the other the packet-out header. Each message contains the following fields:

• preamble, a Preamble message where preamble.name is set to "packet_in" and "packet_out" for packet-in and packet-out metadata, respectively.

• metadata, a repeated field of type Metadata, where each Metadata message includes the following fields:

• id, a uint32 identifier of this metadata. No rules are prescribed on the way metadata IDs should be allocated, as long as two Metadata of the same ControllerPacketMetadata message do not have the same ID. Nonetheless, if the P4Info message was generated from a P4 compiler, we recommend that the IDs be assigned incrementally, starting from 1, in the same order as the fields in the P4 header declaration.
• name, a string representation of the name of this metadata. If the P4Info message was generated from a P4 compiler, then this field is expected to be set to the name of the P4 controller header field (see example below).
• annotations, a repeated field of strings, each one representing a P4 annotation associated to this metadata.
• bitwidth, an int32 representing the size in bits of this metadata.
• type_name, which indicates whether the metadata field has a user-defined type; this is useful for translation.

As an example, consider the following snippet of a P4 program where controller headers are specified and we show the corresponding ControllerPacketMetadata messages.

@controller_header("packet_out")
bit<9> egress_port; /* suggested port where the packet
should be sent */
bit<8> queue_id;    /* suggested queue ID */
}

bit<9> ingress_port; /* data plane port ID where
the original packet was received */
bit<1> is_clone;     /* 1 if this is a clone of the
original packet */
}
controller_packet_metadata {
preamble {
id: 2868916615
name: "packet_out"
}
id: 1
name: "egress_port"
bitwidth: 9
}
id: 2
name: "queue_id"
bitwidth: 8
}
}

preamble {
id: 2868941301
name: "packet_in"
}
id: 1
name: "ingress_port"
bitwidth: 9
}
id: 2
name: "is_clone"
bitwidth: 1
}
}

Note that the use of @controller_header is optional for Packet I/O. The P4 program may define controller headers without this annotation and use them to encapsulate controller packets. However, in this case the client will be responsible for extracting the metadata from the serialized header in packet-in messages and for serializing the metadata when generating packet-out messages.

#### 6.4.7. ValueSet

ValueSet messages are used to specify all possible P4 Parser Value Sets. Parser Value Sets can be used by the control plane to specify runtime matches used by the P4 parser to determine transitions from one state to another. For more information on Parser Value Sets, refer to the P416 specification [35].

The ValueSet message defines the following fields:

• preamble, a Preamble message with the ID, name, and alias of this Value Set.

• match, a repeated field of MatchField messages, representing the list of matches performed when looking up an expression in a Value Set. This determines the format of the members which can be inserted into the Value Set by the control plane, similarly to the match_fields repeated field in the Table message.

• size, an int32 representing the maximum number of entries (values) in the Value Set. It corresponds to the value of the size argument of the P4 value_set constructor call.

According to the P4 specification, the type parameter of a Value Set, which defines the type of the expression that can be matched against the Value Set in a parser transition, and therefore determines the format of the members that can be inserted into the Value Set by the control plane, must be one of bit<W>, tuple, or struct [24]. The rest of this section looks at all 3 of these cases and gives an example ValueSet message when appropriate.

1. If the type parameter is bit<W>, match will include exactly one MatchField message, with the following fields (if a field is omitted here, it means the default Protobuf value should be used):

• id: set to 1
• bitwidth: set to the value of W
• match_type: set to EXACT
@id(1) value_set<bit<8> >(4) pvs;
select (hdr.f8) { /* ... */ }
value_sets {
preamble {
id: 0x03000001
name: "pvs"
}
match {
id: 1
bitwidth: 8
match_type: EXACT
}
size: 4
}
1. If the type parameter is a tuple, this version of P4Runtime does not support runtime programming of the Value Set. If the P4Info message is generated by a compiler, and the P4 program includes such a Value Set, the compiler must reject the program.

2. If the type parameter is a struct, this version of P4Runtime requires that all the fields of the struct be of type bit<W> (where W can be different for each field). Otherwise, if the P4Info message is generated by a compiler, the compiler must reject the program. If the Value Set is supported, the match field will include one MatchField message for each field in the struct, with the following fields:

• id: must be unique with respect to the other match entries. If the P4Info message was generated from a P4 compiler, we recommend that the IDs be assigned incrementally, starting from 1, in the same order as the fields in the P4 struct declaration.
• name: set to the name of the corresponding struct field.
• annotations: set to the list of P4 annotations associated with the struct field, except for the @match annotation, if present (see the match field below).
• bitwidth: set to the value of W for the corresponding struct field.
• type_name, which indicates whether the struct field has a user-defined type; this is useful for translation.
• match: by default match_type is set to EXACT; the P4 programmer can specify a different match type by using the @match annotation [24].
• doc: documentation associated with the struct field.
struct match_t {
bit<8> f8;
@match(ternary) bit<16> f16;
@match(custom) bit<32> f32;
}
@id(1) value_set<match_t>(4) pvs;
select ({ hdr.f8, hdr.f16, hdr.f32 }) { /* ... */ }
value_sets {
preamble {
id: 0x03000001
name: "pvs"
}
match {
id: 1
name: "f8"
bitwidth: 8
match_type: EXACT
}
match {
id: 2
name: "f16"
bitwidth: 16
match_type: TERNARY
}
match {
id: 3
name: "f32"
bitwidth: 32
other_match_type: "custom"
}
size: 4
}

Although not mentioned in the P4 specification, P4Runtime also supports the cases where the Value Set type parameter is a user-defined type that resolves to a bit<W>, or a struct where one or more fields is a user-defined type that resolves to a bit<W>. For each MatchField that corresponds to a user-defined type, the type_name field must be set to the appropriate value (i.e. the name of the type).

#### 6.4.8. Register

Register messages are used to specify all possible instances of Register PSA externs.

Registers are stateful memories that can be read and written by data plane during packet forwarding. The control plane can also access registers at runtime.

The Register message defines the following fields:

• preamble, a Preamble message with the ID, name, and alias of this register instance.

• type_spec, which specifies the data type stored by this register, expressed using a P4DataTypeSpec message (see section on Representation of Arbitrary P4 Types).

• size, an int32 value representing the total number of independent register cells available.

• index_type_name, which indicates whether the register index has a user-defined type. This is useful for translation. The underlying built-in type must be a fixed-width unsigned bitstring (bit<W>).

#### 6.4.9. Digest

Digest messages are used to specify all possible instances of Packet Digest PSA externs.

A packet digest is a mechanism to efficiently send notifications from the data plane to the control plane. This mechanism differs from packet-in which is generally used to send entire packets (headers plus payload), each one as a separate P4Runtime stream message. A digest for a packet has a size typically much smaller than the packet itself, as it can be used to send only a subset of the headers or P4 metadata associated with the packet. To reduce the rate of messages sent to the control plane, a P4Runtime server can combine digests for multiple packets into larger messages.

The Digest message defines the following fields:

• preamble, a Preamble message with the ID, name, and alias of this digest instance.

• type_spec, which specifies the data type of an individual digest notification using a P4DataTypeSpec message (see section on Representation of Arbitrary P4 Types).

#### 6.4.10. Extern

Extern messages are used to specify all extern instances across all extern types for a non-PSA architecture. This is useful when extending P4Runtime to support a new architecture. Each architecture-specific extern type corresponds to at most one Extern message instance in P4Info. The Extern message defines the following fields:

• extern_type_id, a 32-bit unsigned integer which uniquely identifies the extern type in the context of the architecture. It must be in the reserved range [0x81, 0xfe]. Note that this value does not need to be unique across all architectures from all organizations, since at any given time every device managed by a P4Runtime server maps to a single P4Info message and a single architecture.

• extern_type_name, which specifies the fully-qualified P4 name of the extern type.

• instances, a repeated field of ExternInstance Protobuf messages, with each entry corresponding to a separate P4 instance of the extern. The ExternInstance in turn defines the following fields:

• preamble, a Preamble message with the ID, name, and alias of this digest instance.
• info, an Any Protobuf message [28] which is used to embed arbitrary information specific to the extern instance. Note that the underlying Protobuf message type for info should be the same for all instances of this extern type. That Protobuf message should be defined in a separate architecture-specific Protobuf file. See section on Extending P4Runtime for non-PSA Architectures for more information.

If the P4 program does not include any instance of a given extern type, the Extern message instance for that type should be omitted from the P4Info.

### 6.5. Support for Arbitrary P4 Types with P4TypeInfo

See section on Representation of Arbitrary P4 Types.

## 7. P4 Forwarding-Pipeline Configuration

The ForwardingPipelineConfig captures data needed to realize a P4 forwarding-pipeline and map various IDs passed in P4Runtime entity messages. It is formally called the “Device Configuration” and sometimes also referred to as the “P4 Blob”. It is defined as:

message ForwardingPipelineConfig {
config.P4Info p4info = 1;
bytes p4_device_config = 2;
}
}

The p4info field captures the P4 program metadata as described by the P4Info. This message is the output of the P4 compiler and is target-agnostic.

The p4_device_config is opaque binary data which contains the target-specific configuration to realize the P4 program. The P4 program running on a target is changed by loading a new ForwardingPipelineConfig on that target.

The cookie field is opaque data which may be used by a control plane to uniquely identify a forwarding-pipeline configuration among others managed by the same control plane. For example, a controller can compute its value using a hash function over the P4Info and/or target-specific binary data. However, there are no restrictions on how such value is computed, or where this is stored on the target, as long as it is returned with a GetForwardingPipelineConfig RPC. When writing the config via a SetForwardingPipelineConfig RPC, the cookie field is optional. For this reason, the actual value is wrapped in its own message to clearly identify cases where a cookie is not present.

## 8. General Principles for Message Formatting

### 8.1. Set / Unset Protobuf Field

In Protobuf version 3 (proto3), the default value for a message field is “unset” [4]. An application, such as the P4Runtime client or server, is able to distinguish between an unset message field and a message field set to its default value. We often use this distinction in P4Runtime and the meaning of a message can vary based on which of its message fields are set. For example, when reading values from an indirect PSA counter using the CounterEntry message, an “unset” index field means that all entries in the counter array should be read and returned to the P4Runtime client (we refer to this as a wildcard read). On the other hand, if the index message field is set, a single entry will be read.

Let's look at the counter example in more details. Based on this specification document, the C++ server code which processes CounterEntry messages may look like this:

auto *counter_entry = ...
if (counter_entry->has_index()) {
auto index = counter_entry->index().index();
} else {
}
1. Reading a single counter entry at index 0 in the counter array with id <id>:

• Here is the C++ client code:
p4::v1::CounterEntry entry;
entry.set_counter_id(<id>);
entry.mutable_index();
// The above line sets the index field; it is equivalent to:
// auto *index = entry.mutable_index();
// index->set_index(0);
• Here is the corresponding Protobuf message in text format:
counter_id: <id>
index {}
• Expected behavior: Counter entry at index 0 is read. Notice that the index subfield is missing under the index field message of CounterEntry in the text dump of the message. This is because the subfield is a scalar numeric type and 0 is therefore its default value. Scalar fields with default values are omitted from the textual representation of Protobuf messages.
2. Reading all counter entries by leaving the index field unset

• Here is the C++ client code:
p4::v1::CounterEntry entry;
entry.set_counter_id(<id>);
• Here is the corresponding Protobuf message in text format:
counter_id: <id>
• Expected behavior: All counter entries for the provided counter instance are read. Notice that the index message field is unset (default value) and is therefore omitted from the textual representation of the message.

The reads and writes a client issues towards a server should be symmetrical and unambiguous. More specifically, if a client writes a P4 entity and then reads it back then the client should expect that the message it wrote and the message it read should match if the RPCs finished successfully. Consider the following pseudocode as an example:

intended_value = value

status = server.write(intended_value, p4_entity)

assert(intended_value == observed_value)

To ensure read-write symmetry, the rest of this document tries to offer canonical representations for various data types, but this principle should be thought of where it falls short. Ensuring this will allow client software to recover programmatically from failures that can affect the switch stack software, communication channel, or the client replicas. If Read RPC returns a semantically-same but syntactically-different response then the client would have to canonicalize the read values to check its internal state, which only pushes the protocol's complexities to the client implementations.

In order to avoid placing too much burden on the P4Runtime server implementation, we do not in general mandate that the order of values in a Protobuf repeated field be preserved. For example, the server is not required to preserve the order of the match fields in a TableEntry message. If there is a specific case for which the order is significant and / or needs to be preserved, it will be explicitly stated in this document. The MessageDifferencer class [33] included in the Protobuf C++ API supports comparing messages while treating repeated fields as sets, so that different orderings of the same elements are considered equal. This method of comparing Protobuf messages may come at a cost in performance.

### 8.3. Zero as Reserved Value

p4runtime.proto uses proto3 syntax, and so it does not allow not specifying a scalar data type, such as a uint32. Therefore, we usually reserve value 0 for those fields to mean unset. In particular, 0 is not a valid P4 object ID and it is an error to specify 0 for any P4 object ID in a non-read request towards the switch, such as in a WriteRequest or a SetForwardingPipelineConfigRequest.

### 8.4. Bytestrings

P4Runtime integer values may be too large to fit in Protobuf primitive data types (32-bit and 64-bit words). The P4 language does not put any limit on the size of integer values, whether unsigned (bit<W>) or signed (int<W>), and it is up to the P4 programmer to choose the appropriate sizes. Because of this flexibility, P4Runtime represents P4 integer values as binary strings, using the bytes Protobuf type. The correct bitwidth as per the P4 program of each integer variable exposed through P4Runtime is specified in the P4Info message.

The canonical binary string representation uses the shortest string that fits the encoded integer value. This representation achieves three goals:

• It ensures that a properly encoded binary string's integer value conforms to the P4Info-specified bitwidth.

• It helps facilitate non-disruptive P4 program updates.

In particular, the kinds of P4 program updates that this representation facilitates are those where a P4Runtime server and client can continue to transmit P4Runtime messages between them when one has a P4Info file for version A of a P4 program, at the same time that the other has a P4Info file for version B of a P4 program, and those P4 programs differ in the bitwidths of some values of type bit<W> and/or int<W>.

Note that this representation does not make it possible to seamlessly change the type of a value from signed to unsigned, or vice versa. If you attempt to do so, this mechanism can quietly change negative signed values to positive unsigned values, or vice versa. It also limits the magnitude of the values transmitted to those that fit within the smaller of the bitwidths supported by either end of the message transmission. If a message sender attempts to send a value larger than the receiver expects, the receiver will detect it as out of range.

In the P4Runtime API version 1.0, values of table key fields, action parameters, and fields in packet-in and packet-out headers between a device and the controller (see 6.4.6), may not be of type int<W>. The rules for encoding signed values thus only apply to messages of type P4Data (see 8.5.3).

For a value of type bit<W>, the fewest number of bits required to represent the integer value is the smallest integer such that .

For a value of type int<W>, the fewest number of bits required to represent the integer value in 2's complement form is the smallest integer such that .

As a special case, define that the value requires at least bit to represent, regardless of whether it is signed or unsigned.

The shortest possible binary string for an integer that needs bits to represent it is computed as:

minimum_string_size = floor((A + 7) / 8)

Binary strings with the byte length computed as minimum_string_size promote P4Runtime read-write symmetry in both client-to-server requests and server-to-client replies.

Any additional bits in the bytes sent for an unsigned integer value (type bit<W>) must be 0. If additional bytes are transmitted above the minimum_string_size minimum required, they must be filled with 0.

Any additional bits in the bytes sent for a signed integer value (type int<W>) must be copies of the sign bit, i.e. 0 for non-negative values, or 1 for negative values. If additional bytes are transmitted above the minimum_string_size minimum required, they must be filled with copies of the sign bit, i.e. 0 for non-negative values, or 0xff for negative values. In 2's complement representation, this is called “sign extension”, and leaves the numeric value represented unchanged.

Upon receiving a binary string, the P4Runtime receiver (whether the server or the client) does not impose any restrictions on the length of the string itself. Instead, the receiver verifies that the value encoded by the string fits within the expected type (signed or unsigned) and P4Info-specified bitwidth for the P4 object value.

For a received bitstring expected to fit within a bit<W> type, the value it represents is in range if, after removing all most significant 0 bits, the remaining bitstring's width is W bits or less.

For a received bitstring expected to fit within an int<W> type, the value it represents is in range if, after “undoing sign extension”, the remaining bit string's width is W bits or less. To undo sign extension, start by eliminating the most significant bit, but only if it is equal to the bit that follows it (otherwise removing the most significant bit would change the sign of the value). Repeat that process until either only a single bit remains, or until the two most significant bits are different from each other.

If the string's byte length is zero, the server always rejects the string.

When the server rejects a binary string due to any of the previous criteria, it returns an OUT_OF_RANGE error.

For all binary strings, P4Runtime uses big-endian (i.e. network) byte-order. For signed integer values (int<W> P4 type), P4Runtime uses the same two's complement bitwise representation as P4. Table 4 shows various examples of integer values that the server accepts as valid P4Runtime binary strings according to the criteria in the list above.

Table 5 shows some examples of invalid P4Runtime binary strings:

As the preceding examples illustrate, a P4Runtime server must accept a wide assortment of possible binary string encodings for the same integer value. This requirement addresses P4 program upgrade scenarios where binary string widths can expand or contract. In some P4Runtime environments, the changes cannot be deployed simultaneously to all P4Runtime clients and servers. Given a hypothetical match field type change from bit<8> to bit<9>, a server running the bit<9> version of the P4 program will accept requests from clients that remain on the bit<8> P4Runtime version.

Despite the server's binary string flexibility for P4 program update support, the client and server must both remain aware of the read-write symmetry requirements. As described earlier, read-write symmetry requires that the encoder of a P4Runtime request or reply uses the shortest strings that fit the encoded integer values.

Representation of variable-length integer values (varbit<W> P4 type) is similar to the representation of fixed-width unsigned integers. We use a binary string, whose length is the dynamic-length of the expression. When the value is provided by the P4Runtime client, the server must verify that the length of the binary string is less than the maximum length specified in the P4 program, and return an OUT_OF_RANGE error code otherwise.

### 8.5. Representation of Arbitrary P4 Types

#### 8.5.1. Problem Statement

The P416 language includes more complex types than just binary strings [3]. Most of these complex data types can be exposed to the control plane through table key expressions, Value Set lookup expressions, Register (PSA extern type) value types, etc. Not supporting these more complex types can be very limiting. Table 6 shows the different P416 types and how they are allowed to be used, as per the P416 specification.

For example, the following P416 objects involve complex types that need to be exposed in P4Runtime in order to support runtime operations on these objects.

Digest<tuple<bit<4>, bit<8> > >() digest_complex;
digest_complex.pack({ hdr.ipv4.version, hdr.ipv4.protocol });
// ...
ipv4_t ipv4;
ipv6_t ipv6;
}
Register<ip_t, bit<32> >(128) register_ip;

One solution would be to use only binary string (bytes type) in p4runtime.proto and to define a custom serialization format for complex P416 types. The serialization would maybe be trivial for header types but would require some work for header unions, header stacks, etc. For example, in the case of a PSA Register storing header unions, a client reading from that Register would need to receive information about which member header is valid, in addition to the binary contents of this header. Rather than coming-up with a serialization format from scratch, we decided to use a Protobuf representation for all P416 types.

#### 8.5.2. P4 Type Specifications in p4info.proto

In order for the P4Runtime client to generate correctly-formatted messages and for the P4Runtime service implementation to validate them, P4Info needs to specify the type of each P4 expression which is exposed to the control plane. In the Register example above, client and server need to know that each element of the register has type ip_t, which is a header union with 2 possible headers: ipv4 with type ipv4_t and ipv6 with type ipv6_t. Similarly, they need to know the field layout for both of these header types.

To achieve this we introduce 2 main Protobuf messages: P4TypeInfo and P4DataTypeSpec.

P4TypeInfo is a top-level member of P4Info and includes Protobuf maps storing the type specification for all the named types in the P416 program. These named types are struct, header, header_union, enum and serializable_enum; for each of these we have a type specification message, respectively P4StructTypeSpec, P4HeaderTypeSpec, P4HeaderUnionTypeSpec, P4EnumTypeSpec and P4SerializableEnumTypeSpec. We preserve P4 annotations for named types, which is useful to identify well-known headers, such as IPv4 or IPv6. P4TypeInfo also includes the list of parser errors for the program, as a P4ErrorTypeSpec message.

P4DataTypeSpec is meant to be used in P4Info, to specify the expected format of the P4-dependent values being exchanged between the P4Runtime client and server. Each P4DataTypeSpec message corresponds to a compile-time type in the original P416 program (e.g. the type parameter of an extern). This compile-time type is represented as a Protobuf oneof, which can be:

• a string representing the name of the type in case of a named type (struct, header, header_union, enum, serializable_enum or user-defined “new” type),

• an empty Protobuf message for bool and error, or

• a Protobuf message for other anonymous types (bit<W>, int<W>, varbit<W>, tuple or stack). The “binary string” types (bit<W>, int<W>, and varbit<W>) are grouped together in the P4BitstringLikeTypeSpec message, since they are the only sub-types allowed in headers and values with one of these types are represented similarly in P4Runtime (with the Protobuf bytes type).

For all P416 compound types (tuple, struct, header, and header_union), the order of members in the repeated field of the Protobuf type specification is guaranteed to be the same as the order of the members in the corresponding P416 declaration. The same goes for the order of members of an enum (serializable or not) or members of error.

#### 8.5.3. P4Data in p4runtime.proto

P4Runtime uses the P4Data message to represent values of arbitrary types. The P4Runtime client must generate correct P4Data messages based on the type specification information included in P4Info. The P4Data message was designed to introduce little overhead compared to using binary strings in the most common case (P416 bit<W> type).

Just like its P4Info counterpart - P4DataTypeSpec -, P4Data uses a Protobuf oneof to represent all possible values.

We define a canonical representation for P4Data messages therefore guaranteeing read-write symmetry by introducing the following requirements:

• The order of members in P4StructLike and the order of bitstrings in P4Header must match the order in the corresponding p4info.proto type specification and hence the order in the corresponding P416 type declaration.

• An invalid header is represented by a P4Header message where the is_valid field is false and the bitstrings repeated field is empty.

• An invalid header union (i.e. all headers in the union are invalid) is represented by a P4HeaderUnion message where the valid_header_name is the empty string (default value for the field) and the valid_header is unset.

• The order of entries in P4HeaderStack and P4HeaderUnionStack is from element at index 0 of the stack to last element of the stack, in ascending order of index. The length of the entries field must always be equal to the compile-time size of the corresponding P4 stack declaration. This size is included in the P4Info, in the corresponding P4HeaderStackTypeSpec or P4HeaderUnionStackTypeSpec message.

#### 8.5.4. Example

Let's look at the Register example again:

header_union ip_t {
ipv4_t ipv4;
ipv6_t ipv6;
}
Register<ip_t, bit<32> >(128) register_ip;

Here's the corresponding entry in the P4Info message:

registers {
preamble {
id: 369119267
name: "register_ip"
alias: "register_ip"
}
type_spec {
name: "ip_t"
}
}
size: 128
}
type_info {
key: "ipv4_t"
value {
members {
name: "version"
type_spec {

bit {
bitwidth: 4
}
}
} # ...
key: "ipv6_t"
value {
members {
name: "version"
type_spec {
bit {
bitwidth: 4
}
}
} # ...
key: "ip_t"
value {
members {
name: "ipv4"
name: "ipv4_t"
}
}
members {
name: "ipv6"
name: "ipv6_t"
}
}
}
}
}

Here's a p4.WriteRequest to set the value of register_ip[12]:

update {
type: INSERT
entity {
register_entry {
register_id: 369119267
index {
index: 12
}
data {
is_valid: true
bitstrings: "\x04"
bitstrings: # ...
}
}
}
}
}
}

#### 8.5.5. enum, serializable enum and error

P416 supports 2 different classes of enumeration types: without underlying type (safe enum) and with underlying type (serializable enum or “unsafe” enum) [5]. For enum types with no underlying type as well as error there is no integer value associated with each symbolic member entry (whether assigned automatically by the compiler or directly in the P4 source). We therefore use a human-readable string in P4Data to represent enum and error values.

Serializable enum types have an underlying fixed-width unsigned integer representation (bit<W>). All named enum members must be assigned an integer value by the P4 programmer, but not all valid numeric values for the underlying type need to have a corresponding name. P4TypeInfo includes the mapping between entry name and entry value. When providing serializable enum values through P4Data, one must use the assigned integer value (enum_value bytestring field). P4Runtime does not provide a way for the client to use the name even when the enum member has one instead of the value, as it makes it easier for the server to respect the read-write symmetry principle.

#### 8.5.6. User-defined types

P416 enables programmers to introduce new types [11]. While similar to typedef, this mechanism introduces in fact a new type, which is not a strict synonym of the original type. It is important to preserve this distinction in the P4Info message, in particular for the purposes of translation. When introducing a new type, the declaration can be annotated with @p4runtime_translation to indicate that the type exposed to the P4Runtime client is different from the original P4 type. One important use-case is for port numbers, whose underlying data plane representation may vary on different targets, but for which it may be convenient to present a unified representation and numbering scheme to the control plane. The @p4runtime_translation annotation can only be used if the underlying P4 built-in type is a fixed-width unsigned bitstring type (bit<W>) and the type exposed to the control plane will also be a fixed-width unsigned bitstring, with a potentially different bitwidth. It takes two parameters: a URI (Uniform Resource Identifier) which uniquely identifies the translation being performed on entities of the new type to the P4Runtime server and the bitwidth of the bitstring type exposed to the control plane. It is recommended that the URI includes at least the P4 architecture name and the type name.

User-defined types are specified using the P4NewTypeSpec message, which has the following fields:

• representation, a Protobuf oneof specifying how values of this type are exchanged between client and server; it can be either:

• original_type, if and only if no @p4runtime_translation annotation is present. It specifies the underlying built-in P4 type for the user-defined type. If the underlying type used in the P4 type declaration is itself a user-defined type, original_type is obtained by “walking” the chain of type declarations recursively until a built-in type (e.g bit<W>) is found.

• translated_type, if and only if the P4 type declaration was annotated with @p4runtime_translation. It is of type P4NewTypeTranslation, which itself has two fields uri and sdn_bitwidth , which map to the two input parameters to the annotation.

• annotations, a repeated field of strings, each one representing a P4 annotation associated to the type declaration.

For example, an architecture in this case PSA may introduce a new type for port numbers:

@p4runtime_translation("p4.org/psa/v1/PortId_t", 32)
type bit<9> PortId_t;

In this case, the P4Info message would include the following P4TypeInfo message:

type_info {
new_types {
key: "PortId_t"
value {  # P4NewTypeSpec
translated_type {  # P4NewTypeTranslation
uri: "p4.org/psa/v1/PortId_t"
sdn_bitwidth: 32
}
}
}
}

Note that a P4 compiler may provide a mechanism external to the language to specify if and how a user-defined type is to be translated (e.g. through some configuration file passed on the command-line when invoking the compiler). This mechanism should take precedence over @p4runtime_translation to enable users to overwrite annotations included as part of the P4 architecture definition.

#### 8.5.7. Trade-off for v1.0 Release

For the v1.0 release of P4Runtime, it was decided not to replace occurrences of bytes with P4Data in the p4.v1.FieldMatch message, which is used to represent table and Value Set entries. This is to avoid breaking pre-release implementations of P4Runtime. Similarly it has been decided to keep using bytes to provide action parameter values. However P4Data is used whenever appropriate for PSA externs and we encourage the use of P4Data in architecture-specific extensions.

In order to support translation for action parameters and match fields, we include a type_name field in p4.config.v1.MatchField and p4.config.v1.Action.Param.

## 9. P4 Entity Messages

P4Runtime covers P4 entities that are either part of the P416 language, or defined as PSA externs. The sections below describe the messages for each supported entity.

### 9.1. TableEntry

The match-action table is the core packet-processing construct of the P4 language. It consists of a collection of table entries, or flow rules, each mapping a key value to a P4 action along with input values for the action's parameters. Packets are looked-up in the table by matching them against the flow rules. In case of a match, the corresponding action is applied on the packet, otherwise, a default action is applied. The exact behavior of P4 tables is described in the P4 specification.

P4Runtime supports inserting, modifying, deleting and reading table entries with the TableEntry entity, which has the following fields:

• table_id, which identifies the table instance; the table_id is determined by the P4Info message.

• match, a repeated field of FieldMatch messages. Each element in the repeated field is used to provide a value for the corresponding element in the key property of the P4 table declaration.

• action, which indicates which of the table's actions to execute in case of match and with which argument values.

• priority, a 32-bit integer used to order entries when the table's match key includes a ternary or range match.

• controller_metadata, a 64-bit cookie value which is opaque to the target. There is no requirement of where this is stored, but it must be returned by the server along with the rest of the entry when the client performs a read on the entry.

• meter_config, which is used to read and write the configuration for the direct meter entry attached to this table entry, if any. See Direct resources section for more information.

• counter_data, which is used to read and write the value for the direct counter entry attached to this table entry, if any. See Direct resources section for more information.

• is_default_action, a boolean flag which indicates whether the table entry is the default entry for the table. See Default entry section for more information.

• idle_timeout_ns and time_since_last_hit, which are two fields used to implement idle-timeout support for the table, if applicable. See Idle-timeout section for more information.

The priority field must be set to a non-zero value if the match key includes a ternary match (i.e. in the case of PSA if the P4Info entry for the table indicates that one or more of its match fields has a TERNARY or RANGE match type) or to zero otherwise. A higher priority number indicates that the entry must be given higher priority when performing a table lookup. Clients must allow multiple entries to be added with the same priority value. If a packet can match multiple entries with the same priority, it is not deterministic in the data plane which entry a packet will match. If a client wishes to make the matching behavior deterministic, it must use different priority values for any pair of table entries that the same packet matches.

The match and priority fields are used to uniquely identify an entry within a table. Therefore, these fields cannot be modified after the entry has been inserted and must be provided for MODIFY and DELETE updates. When deleting an entry, these key fields (along with is_default_action) are the only fields considered by the server. All other fields must be ignored, even if they have nonsensical values (such as an invalid action field). In the case of a keyless table (the table has an empty match key), the server must reject all attempts to INSERT a match entry and return an INVALID_ARGUMENT error.

The number of match entries that a table should support is indicated in P4Info (size field of Table message). The guarantees provided to the P4Runtime client are the same as the ones described in the P416 specification for the size property [27]. In particular, some implementations may not be able to always accommodate an arbitrary set of entries up to the requested size, and other implementations may provide the P4Runtime client with more entries than requested. The P4Runtime server must return RESOURCE_EXHAUSTED when a table entry cannot be inserted because of a size limitation. It is recommended that, for the sake of portability, P4Runtime clients do not try to insert additional entries once the size indicated in P4Info has been reached.

#### 9.1.1. Match Format

The bytes fields in the FieldMatch message follow the format described in Bytestrings.

For “don't care” matches, the P4Runtime client must omit the field's entire FieldMatch entry when building the match repeated field of the TableEntry message. This requirement leads to smaller Protobuf messages overall, while enabling a canonical representation for “don't care” matches, which is needed to ensure read-write symmetry. For PSA match types, a “don't care” match for a specific match key field is defined as follows:

• For a TERNARY match, it is logically equivalent to a mask of zeros.

• For an LPM match, it is logically equivalent to a prefix_len of zero.

• For a RANGE match, it is logically equivalent to a range which includes all possible values for the field.

Note that there is no “don't care” value for EXACT matches and therefore exact match fields can never be omitted from the TableEntry message.

The following example shows a P4Runtime message that treats a TERNARY field as a “don't care” match. The P4 program defines table t with TERNARY and EXACT fields in its match key:

table t {
key = {
hdr.ipv4.dip: ternary;
istd.ingress_port: exact;
}
actions = {
drop;
}
}

In this P4Runtime request, the client omits the table's TERNARY field from the repeated match field to indicate a “don't care” match. As shown below, the match specifies only the EXACT field given by field_id: 2.

device_id: 3
entities {
table_entry {
table_id: 33554439  # Table t's ID.
match {
# field_id 1 is not present to use the don't care ternary value.
field_id: 2
exact {
value: "\x20"
}
}
action {
# Action selection goes here.
}
}
}

For every member of the TableEntry repeated match field, field_id must be a valid id for the table, as per the P4Info, and one of the fields in field_match_type must be set. We summarize additional constraints which depend on the match-type in the following list. If any one of them is violated, the P4Runtime server must return an INVALID_ARGUMENT error code.

• EXACT match
• The binary string encoding of the value must conform to the Bytestrings requirements.
assert(BytestringValid(match.exact().value()))
• LPM match
• The binary string encoding of the value (when present) must conform to the Bytestrings requirements.
• “Don't care” match must be omitted.
• “Don't care” bits must be 0 in value.
assert(BytestringValid(match.lpm().value()))

pLen = match.lpm().prefix_len()
assert(pLen > 0)

trailing_zeros = countTrailingZeros(match.lpm().value())
assert(trailing_zeros >= field_bits - pLen)
• TERNARY match
• The binary string encoding of the value (when present) and mask (when present) must conform to the Bytestrings requirements.
• “Don't care” match must be omitted.
• Masked bits must be 0 in value. This constraint taken together with the Bytestrings requirements means that the value's binary string is never longer than the mask's binary string. When the value's string is shorter than the mask string, the most-significant value bits need zero-padding before any logical operations with the mask.
assert(BytestringValid(match.ternary().value()))

value = parseInteger(match.ternary().value())

assert(value & mask == value)
• RANGE match
• The binary string encoding of the low bound (when present) and high bound (when present) must conform to the Bytestrings requirements.
• Low bound must be less than or equal to the high bound.
• “Don't care” match must be omitted.
assert(BytestringValid(match.range().low()))
assert(BytestringValid(match.range().high()))

low = parseInteger(match.range().low())
high = parseInteger(match.range().high())

assert(low <= high)

assert(low != min_field_value || high != max_field_value)

#### 9.1.2. Action Specification

The TableEntry action field must be set for every INSERT and MODIFY update, except when resetting the default entry. Based on the implementation property value of the P4 table, the oneof in the TableAction message will either be:

• an Action specification for direct tables (with no P4 implementation property)

• an action profile member id for indirect tables for which the implementation property is an action profile with no selector.

• an action profile member id or group id for indirect tables for which the implementation property is an action profile with selector.

• an ActionProfileActionSet specification for indirect tables for which the implementation property is an action profile with selector. This usage is described in One Shot Action Selector Programming

If the action field is not set (and if is_default_action is false) or if the oneof does not match the table description in the P4Info (e.g. the oneof is action_profile_member_id for a direct table), the server must return an INVALID_ARGUMENT error code.

The Action Protobuf message has the following fields:

• action_id, which identifies the action instance; the action_id is determined by the P4Info message and must match one of the possible action choices for the table, or the server must return an INVALID_ARGUMENT error code. If the client uses a valid action_id for the table but does not respect the action scope specified in P4Info (e.g. tries to set a TABLE_ONLY action as the default action), the server must return a PERMISSION_DENIED error code.

• params: a repeated Protobuf field of action parameter values, each encoded as a Param message. For each parameter, param_id must be valid for the action (as per the P4Info) and value must follow the format described in Bytestrings. The P4Runtime client must provide a valid value for each parameter of the P4 action; we do not support default values for action parameters. The server must return an INVALID_ARGUMENT error code if a parameter id is missing, if an extra parameter id not found in the P4Info was provided by the client, if a parameter value is missing, or if the value provided for one of the parameters does not conform to the Bytestrings format.

For indirect tables, if the P4Runtime client provides a member or group id which has not been inserted in the corresponding action profile instance yet, the P4Runtime server must return a NOT_FOUND error code.

#### 9.1.3. Default Entry

According to the P4 specification, the default entry for a table is always set. It can be set at compile-time by the P4 programmer or defaults to NoAction (which is a no-op) otherwise and assuming it is not declared as const, can be modified by the P4Runtime client. Because the default entry is always set, we do not allow INSERT and DELETE updates on the default entry and the P4Runtime server must return an INVALID_ARGUMENT error code if the client attempts one.

The default entry is identified by setting the is_default_action boolean field to true. When this flag is set to true, the repeated match field must be empty and the priority field must be set to zero, otherwise the P4Runtime server must return an INVALID_ARGUMENT error code. When performing a MODIFY update on the default entry, the client can either provide a valid action for the table or leave the action field unset, in which case the default entry will be reset to its original value, as defined in the P4 program. When resetting the default entry, its controller_metadata value as well as the configurations for its direct resources will be reset to their defaults. If the default entry is constant (as indicated by the P4 program and the P4Info message), the server must return a PERMISSION_DENIED error code if the client attempts to modify it.

Apart from the above restrictions, the default entry is treated like a regular entry, including with regards to direct resources.

In this P4Runtime release, we have decided to restrict the default entry for indirect tables tables with an ActionProfile or ActionSelector implementation property to a constant NoAction action entry, with the hope that it would simplify the implementation of the P4Runtime service.

#### 9.1.4. Constant Tables

Constant tables are defined as tables whose match entries are immutable. They are identified by the is_const_table flag in P4Info. The only write updates which are allowed for constant tables are MODIFY operations on the default action, assuming the default action itself is not constant. If the P4Runtime client attempts to perform any other kind of write update on a constant table, the server must return a PERMISSION_DENIED error. However, the contents of such tables can be queried by the client through a ReadRequest. When reading static (immutable) entries from a constant table, the following fields and only these fields , must be set by the server: table_id, match, action, priority and is_default_action. In particular, we assume that constant tables cannot be assigned direct resources and idle timeout is not supported for static entries. If the table requires a priority value for entries, the server must populate the priority field appropriately, starting at 1 for the lowest priority entry and incrementing the value by 1 for each successive entry. Note that P416 does not support assigning explicit priorities to static entries. When a priority value is required (e.g. for tables including RANGE and / or TERNARY matches in the case of PSA), it is inferred based on the order in which entries appear in the table declaration.

When performing a ReadRequest, the P4Runtime client can select all entries from one or all tables on the target and use several of the TableEntry fields to filter the results, much like when performing a SQL request. For each field that can be used to filter the result, the client may use the default value for the field to act as a wildcard. This default value is zero for scalar fields such as priority and “unset” for message fields such as match. The following fields may be used to select and filter results:

• table_id: If default (0), entries from all tables including constant tables will be selected and no other filter can be used. Otherwise only the specified table will be considered.
• match: If default (unset), all entries from the specified table will be considered. Otherwise, results will be filtered based on the provided match key, which must be a valid match key for the table. The match will be exact, which means at most one entry will be returned.
• action: If default (unset), all entries from the specified table will be considered. Otherwise, the client can provide an action_id (for direct tables), which will be use to filter table entries. For this P4Runtime release, this is the only kind of action-based filtering we support: the client cannot filter based on action parameter values and cannot filter indirect table entries based on action profile member id / action profile group id.
• priority: If default (0), all entries from the specified table will be considered. Otherwise, results will be filtered based on the provided priority value.
• controller_metadata: If default (0), all entries from the specified table will be considered. Otherwise, results will be filtered based on the provided controller_metadata value.
• is_default_action: If default (false), all non-default entries from the specified table will be considered. Otherwise, only the default entry will be considered.

For example, in order to read all entries from all tables from device 3, the client can use the following ReadRequest message.

device_id: 3
entities {
table_entry {
table_id: 0
priority: 0
}
}

In order to read all entries with priority 11 from a specific table (with id 0x0212ab34) from device 3, the client can use the following ReadRequest message:

device_id: 3
entities {
table_entry {
table_id: 0x0212ab34
priority: 11
}
}

The canonical representation of “don't care” matches, combined with the ability to do a wildcard read on all table entries by leaving the match field unset, means that there exists a specific ambiguous case in which the same message could be used to either read a single “don't care” entry or to do a wildcard read. If a table has no fields with match kind EXACT, it is possible via P4Runtime to add an entry that is “don't care” for all fields (i.e. has an empty match field) but is not the default entry (i.e. is_default_action is false). When reading this entry from the table, there is no way to read only that entry from the table, because it would require providing an unset match field in the request, which in turn indicates that the client wishes to perform a wildcard read on all non-default entries. Consider the following example which uses a table with a single LPM match:

table t {
key = {
hdr.ipv4.dip: lpm;
}
actions = {
drop; fwd;
}
}

The following WriteRequest message can be used to add 2 entries:

device_id: 3
entities {
table_entry { # don't care entry
table_id: 0x0212ab34
# ...
}
table entry {
table_id: 0x0212ab34
match {
field_id: 1
lpm {
value: 0x0a000000
prefix_len: 8
}
# ...
}
}

The first entry is a “don't care” entry, while the second one matches all 10.0.0.0/8 addresses. The second entry has higher priority than the first one.

The following ReadRequest message will return all entries in the table, not just the “don't care” entry.

device_id: 3
entities {
table_entry {
table_id: 0x0212ab34
}
}

This issue also exists for tables with TERNARY and / or RANGE matches. However, in this case the priority is also taken into account for wildcard reads, and because a priority of 0 is not valid, in practice only the entries with the same priority as the “don't care” entry will be returned to the client. If the client uses distinct priority values for all entries which is strongly recommended to achieve deterministic behavior , then there is no ambiguity because the wildcard read will actually return a single entry (the “don't care” entry) as long as the priority field is set to the correct value.

#### 9.1.6. Direct Resources

In addition to the DirectCounterEntry and DirectMeterEntry entities, P4Runtime support reading and writing direct resources as part of the TableEntry message. This is convenient for two reasons:

• A table entry and its direct resources can be read with a single entity when doing a Read RPC call

• The initial configuration for an entry's direct resources can be specified when the entry is inserted. This may enable the target to add the table entry and configure the direct resources in an atomic fashion if supported. When the table has a direct meter, this may help guarantee that the lifetime of the meter entry is the same as the lifetime of the table entry, and that there is no time gap during which data plane traffic can “hit” the table entry without executing the appropriate meter entry.

Once the table entry has been inserted, the P4Runtime client is free to use the DirectCounterEntry and DirectMeterEntry messages for read and write operations on DirectCounter and DirectMeter instances. For example, it is usually more convenient as well as more efficient to use DirectCounterEntry to query a counter entry value rather than use TableEntry, assuming the client is not interested in reading other table entry properties as well, such as the controller metadata cookie or the action entry.

The PSA specification states that when a table is assigned a direct resource (meter or counter), this direct resource does not need to be “executed” in every action bound to the table. It is an error to provide a direct resource configuration in a TableEntry message when programming an action that does not execute the direct resource, and the server must return an INVALID_ARGUMENT error code.

We leverage Protobuf's ability to differentiate between set and unset fields to give the P4Runtime client fined-grained control over how direct resources are read and written through the TableEntry message. The list below describes how the server must handle the meter_config and counter_data fields for read and write requests, based on whether the fields are set or not. We do not cover error cases in the list, i.e. we assume that we are dealing with a table which is assigned a direct counter / a direct meter, and that the action being used for the table entry “executes” the direct resource appropriately.

• meter_config field

• WriteRequest (INSERT)
• if unset: The initial configuration for the meter entry is the default (meter returns GREEN for all packets).
• if set: The initial configuration for the meter entry is the one provided by the client.
• WriteRequest (MODIFY)
• if unset: The meter entry's configuration is reset to the default (meter returns GREEN for all packets).
• if set: The value provided by the client is used to re-configure the meter entry.
• ReadRequest
• if unset: The response does not include the meter entry's configuration (meter_config is unset in the response).
• if set: If the meter entry's configuration is the default configuration, meter_config is unset in the response. Otherwise, the response includes the meter entry's configuration that was written by the client earlier. This respects the “read-write symmetry” principle.
• counter_data field

• WriteRequest (INSERT)
• if unset: The initial value for the counter entry is the default (0).
• if set: The initial value for the counter entry is the one provided by the client.
• WriteRequest (MODIFY)
• if unset: The counter entry's value is not changed.
• if set: The value provided by the client is written to the counter entry.
• ReadRequest
• if unset: The response does not include the counter entry's value (counter_data is unset in the response).
• if set: The response includes the counter entry's value read from the target.

In its default configuration, a meter returns the GREEN color for every packet when it is executed. This default configuration can be achieved by leaving the meter_config field unset when inserting or modifying a table entry. When modifying a table entry, if the P4Runtime client wishes to maintain the same meter configuration, it needs to be provided again in the TableEntry message (i.e. the meter_config field must be set to match the existing configuration).

#### 9.1.7. Idle-timeout

P4Runtime supports idle timeout for table entries. When adding a table entry, the client can specify a Time-To-Live (TTL) value. If at any time during its lifetime, the data plane entry is not “hit” (i.e. not selected by any packet lookup) for a lapse of time greater or equal to its TTL, the P4Runtime should, with best effort, generate a stream notification using the IdleTimeoutNotification message to the master client, which can then take action, such as remove the idle table entry.

Two fields of the TableEntry Protobuf message are used to implement idle timeout:

• idle_timeout_ns: the configured TTL for the table entry in nanoseconds. A value of 0 means that the entry never expires, i.e. no IdleTimeoutNotification message will ever be generated for this entry. When a client reads a TableEntry, this field will be included in the response and the value must match exactly the one set by the client when inserting or modifying the entry.

• time_since_last_hit: a Protobuf message with a single field (elapsed_ns) used to indicate the time in nanoseconds elapsed since the last time the data plane entry was hit. The time_since_last_hit field must be unset for a TableEntry write. When reading a table entry, time_since_last_hit must be set in the response if and only if it was set (to an empty message) in the request. If the field is set in the request, it must be set to the correct value in the response even if the TTL value for the entry is 0.

These fields can only be set if idle timeout is supported for the table, as per the P4Info message. If idle timeout is not supported by the table, the P4Runtime server must return an INVALID_ARGUMENT error code if at least one of these conditions is met:

• idle_timeout_ns is set to a non-zero value, or
• time_since_last_hit is set

The target should do its best to approximate the idle_timeout_ns value provided by the client. For example, most targets may not be able to accommodate arbitrarily small values of TTL, in which case they should use the smallest value they can support, rather than reject the TableEntry write with an error code. Similarly, each target should do its best to provide reasonably-accurate values for time_since_last_hit.

P4Runtime does not support idle timeout for default entries. When the is_default_action flag is set in a TableEntry message, idle_timeout_ns must be set to 0 (default) and time_since_last_hit must be unset. If the server receives a TableEntry message which violates this, it must return an INVALID_ARGUMENT error.

For more information about idle timeout, in particular regarding IdleTimeoutNotification, please refer to the Table idle timeout notifications section.

### 9.2. ActionProfileMember&ActionProfileGroup

P4Runtime defines an API for programming a PSA ActionProfile extern using ActionProfileMember messages. A PSA ActionSelector extern can be programmed using both ActionProfileMember and ActionProfileGroup messages. PSA supports tables that can be implemented with an action profile or selector instance. Such tables are referred to as indirect tables, in contrast to direct tables, whose entries are directly bound to an action instance. The following P4 snippet illustrates an indirect table t for L3 routing, implemented with an action selector as.

ActionSelector(HashAlgorithm.crc32,
/*size = */ 32w1024,
/*output_width = */ 32w10) as;

istd.egress_port = p;
hdr.ethernet.smac = smac;
hdr.ethernet.dmac = dmac;
}

table t {
key = {
hdr.ipv4.dip: lpm;  // LPM on destination IP address
}
actions = {
set_nhop;
}
implementation = as;
}

When programming table t in the example above, a P4Runtime client should specify the TableAction in the TableEntry to be a reference to either an action profile member or group. The reference is a uint32 identifier that uniquely identifies a member or group programmed in the action selector as.

If a table entry in an indirect table with an ActionProfile implementation is hit, then the corresponding table action gives a member id. The member table is looked up with the member id, and the corresponding action specification is used to modify the packet or its metadata.

If a table entry in an indirect table with an ActionSelector implementation is hit, then the corresponding table action gives either a member id or a group id. For a member id, the member table in the selector is looked up, and the corresponding action specification is used to modify the packet or its metadata. For a group id, a hash algorithm, defined in the P4 ActionSelector specification is used to obtain a member id from the set of members in the group. For example, the hash algorithm in the P4 example above is 32-bit CRC. The obtained member id is used to look up the member table in the selector and obtain the action specification, which is then used to modify the packet or its metadata.

#### 9.2.1. Action Profile Member Programming

Action profile members are entries in the ActionProfile or ActionSelector and are referenced by a uint32 identifier that is bound to an action specification. An action profile member for an ActionProfile or ActionSelector extern instance may be bound only to the actions that appear in the actions attribute of the table implemented using the extern instance. If multiple table implementations share an extern instance, then the actions attributes of the tables must have an identical list of P4 actions. The IDs of the tables implemented with a selector will appear in P4Info as part of the ActionProfile message for the selector.

An ActionProfileMember entity update message has the following fields:

• action_profile_id is the uint32 identifier of the PSA ActionProfile or ActionSelector extern instance, as defined in P4Info.

• member_id is the uint32 identifier of the action profile member entry being updated.

• action is the specification of the P4 action instance bound to the action profile member entry.

An action profile member may be inserted, modified or deleted as per the following semantics.

• INSERT: Add a new member entry bound to an eligible P4 action specification. The member id must be different from ids of already programmed entries for that extern, or the server must return an ALREADY_EXISTS error code. The action specification must be provided, or the server must return INVALID_ARGUMENT. The total number of members should not exceed the maximum specified in the P4 extern specification as a result of this insertion, or the server should return RESOURCE_EXHAUSTED.
• MODIFY: Modify the action specification of an existing member entry. An entry with the member id must exist, or the server must return NOT_FOUND, and the action specification must be provided, or the server must return INVALID_ARGUMENT.
• DELETE: Delete the member entry and deallocate the member id. If the member id is not valid the server must return a NOT_FOUND error code. The member must not be part of an action profile group, or the server must return FAILED_PRECONDITION. If needed, the action profile group should first be modified to remove the member from the group. The member must not be referenced in the table action of any table entry, or the server must also return FAILED_PRECONDITION. member_id is the only field which is considered when performing a DELETE and every other field will be ignored.

#### 9.2.2. Action Profile Group Programming

Action profile groups are entries in an ActionSelector and are referenced by a uint32 identifier that is bound to a set of action profile members already programmed in the selector. The action profile members in a group must be bound to actions of the same type.

An ActionProfileGroup entity update message has the following fields:

• action_profile_id is the uint32 identifier of the PSA ActionSelector extern instance, as defined in P4Info.

• group_id is the uint32 identifier of the action profile group entry being updated.

• members is a repeated field defining the set of members that are part of the group. For each member in a group, the controller must define the following fields:

• member_id for looking up the member table in the selector.
• weight specifying the probability of the member's selection at runtime. 0 is not a valid weight value and the server must return INVALID_ARGUMENT if the client attempts to use it.
• watch is the controller-defined 32-bit port number that the member's liveness depends on. At runtime, the member must be excluded from selection if the watch port is down.
• max_size is the maximum sum of all member weights for the group. This field is defined when the group is inserted, and must not be changed in a MODIFY update. See the subsection below for the rules on setting max_size.

An action profile group may be inserted, modified or deleted as per the following semantics.

• INSERT: Add a new group entry bound to a set of existing action profile members. The group_id must be different from ids of already programmed groups for that selector, or the server must return an ALREADY_EXISTS error code. All members specified in the group must exist in the selector, or the server must return NOT_FOUND. P4Runtime does not explicitly limit the number of groups, however, such limits may be imposed out-of-band by the target.
• MODIFY: Modify the member set specification of an existing group entry. An entry with the group_id must exist, or the server must return NOT_FOUND. All members specified in the group entry must exist in the selector, or the server must return NOT_FOUND. The value of max_size must be identical to the value used when inserting the group, otherwise an INVALID_ARGUMENT error is returned.
• DELETE: Delete the group entry and deallocate the group_id. The group must not be referenced in the table action of any table entry, or the server must return a FAILED_PRECONDITION error code. If the group_id is invalid, the server must return NOT_FOUND. group_id is the only field which is considered when performing a DELETE and every other field will be ignored.

When setting the group membership with INSERT or MODIFY, the members repeated field must not include duplicates, i.e. members with the same member_id. The weight field is used instead to logically “repeat” the member inside the group.

It is explicitly allowed for the same member to be present in multiple groups at the same time. If, as a result, an implementation “stores” the action id and parameters in the target in multiple locations, the server must update all of those locations when a request to modify such a member is made.

##### 9.2.2.1. Rules on Setting max_size

The valid values for max_size depend on the static max_group_size included in the P4Info message:

• If max_group_size is greater than 0, then max_size must be greater than 0, and less than or equal to max_group_size. We assume that the target can support selector groups for which the sum of all member weights is up to max_group_size, or the P4Runtime server would have rejected the Forwarding Pipeline Config. If max_size is greater than max_group_size, the server must return INVALID_ARGUMENT.

• Otherwise (i.e. if max_group_size is 0), the P4Runtime client can set max_size to any value greater than or equal to 0.

• A max_size of 0 indicates that the client is not able to specify a maximum size at group-creation time, and the target should use the maximum value it can support. If the maximum value supported by the target is exceeded during a write update (INSERT or MODIFY), the target must return a RESOURCE_EXHAUSTED error.

• If max_size is greater than 0 and the value is not supported by the target, the server must return a RESOURCE_EXHAUSTED error at group-creation time.

#### 9.2.3. One Shot Action Selector Programming

P4Runtime supports syntactic sugar to program a table, which is implemented with an action selector, in one shot. One shot means that a table entry, an action profile group, and a set of action profile members can be programmed with a single update message. Using one shots has the advantage that the controller does not need to keep track of group ids and member ids.

One shots are programmed by choosing the ActionProfileActionSet message as the TableAction. The ActionProfileActionSet message consists of a set of ActionProfileAction messages, which in turn have the following fields:

• action is one of the actions specified by the table that is being programmed.

• weight specifying the probability of the action's selection at runtime. 0 is not a valid weight value and the server must return INVALID_ARGUMENT if the client attempts to use it. The sum of all weights across all ActionProfileAction messages for that ActionProfileActionSet message must not exceed the max_group_size specificed in the P4Info (if greater than 0), or the server must return INVALID_ARGUMENT.

• watch is the controller-defined 32-bit port number that the action's liveness depends on. At runtime, the action must be excluded from selection if the watch port is down.

Semantically, one shots are equivalent to programming the table entry, group, and members individually; with the necessary group id and member ids bound to unused ids. An implementation is free to implement one shots in other ways, as long as the implementation matches the above semantics.

To preserve read-write symmetry, an implementation must answer ReadRequests with the original one shot messages. It may not return a desugared version of the one shot message.

For example, consider the action selector table defined here. This table could be programmed with the following one shot update:

table_entry {
table_id: 0x0212ab34
match { /* lpm match */ }
action {
action_profile_action_set {
action_profile_actions {
action { /* set nexthop 1 */ }
weight: 1
watch: 1
}
action_profile_actions {
action { /* set nexthop 2 */ }
weight: 2
watch: 2
}
action_profile_actions {
action { /* set nexthop 3 */ }
weight: 3
watch: 3
}
}
}
}

Which would be equivalent to the following updates, where GROUP_ID, MEMBER_ID_1, MEMBER_ID_2, and MEMBER_ID_3 are unused ids:

action_profile_member {
action_profile_id: 1
member_id: MEMBER_ID_1
action { /* set nexthop 1 */ }
}
action_profile_member {
action_profile_id: 1
member_id: MEMBER_ID_2
action { /* set nexthop 2 */ }
}
action_profile_member {
action_profile_id: 1
member_id: MEMBER_ID_3
action {  /* set nexthop 3 */ }
}
action_profile_group {
action_profile_id: 0x11ab12cd
group_id: GROUP_ID
members {
member_id: MEMBER_ID_1
weight: 1
watch: 1
}
members {
member_id: MEMBER_ID_2
weight: 2
watch: 2
}
members {
member_id: MEMBER_ID_3
weight: 3
watch: 3
}
}
table_entry {
table_id: 0x0212ab34
match { /* lpm match */ }
action { action_profile_group_id: GROUP_ID }
}

Note that when using the above method (members and groups), the client also needs to use multiple messages to ensure correct ordering between the dependent updates. Members need to be inserted first, then the group needs to be created, and finally the match entry can be inserted. Therefore, 3 distinct WriteRequest batches are required.

It is possible to include several ActionProfileAction messages with the same exact action specification in one ActionProfileActionSet message. However, the P4Runtime client is encouraged not to do so, as the same can be achieved by using the weight field. Note that to preserve read-write symmetry, the server must not coalesce multiple ActionProfileAction messages with the same action specification into one.

All the tables associated with an action selector may either be programmed exclusively with one shots, or exclusively with ActionProfileMember and ActionProfileGroup messages. Programming some entries with one shots, and other entries with ActionProfileMember and ActionProfileGroup messages is not allowed, and the server must return the error code INVALID_ARGUMENT in that case.

A P4Runtime server must support the one shot style of programming tables with an action selector implementation. Support for the ActionProfileMember and ActionProfileGroup style is optional. If ActionProfileMember and ActionProfileGroup are not supported by a server, it must return an UNIMPLEMENTED error for every ActionProfileMember or ActionProfileGroup message that it receives.

#### 9.2.4. Constraints on action selector programming

The PSA specification states that the following features are optional in action selector implementations [20]:

1. Support for non-empty groups where in the same group, different members are bound to different actions.
2. Predictable data plane behavior when a matched table entry points to an empty group.

For 1., if a client tries to INSERT or MODIFY a group with members bound to different actions, the server should return UNIMPLEMENTED if not supported by the target. This applies to the one shot style of programming as well. We recommend that control plane implementations take into account this possible limitation and be designed so as not to rely on this feature for the sake of portability. A target with this restriction is also not expected to support modifying the action function of a member which is part of one or more groups and should return UNIMPLEMENTED (modifying the action parameter values must be supported, however).

PSA 1.1 introduces the psa_empty_group_action table property in order to enable the P4 programmer to specify the action to perform on the packet when the matched table entry points to an empty action selector group. This action may be different from the default action, which is performed in case of table miss. psa_empty_group_action is one possible way to achieve property 2. in the list above. We recommend that all P4Runtime implementations support this property. Note that this version of P4Runtime does not provide any mechanism to modify the value of psa_empty_group_action at runtime, so the value will be constant and will either be provided by the P4 programmer or will default to NoAction. Even when psa_empty_group_action is not implemented by the target, P4Runtime does not require the server to return an error code when the client performs an operation which results in an empty group, despite the possibility for undeterministic or target-specific behavior. It is likely that future PSA versions will make the implementation of psa_empty_group_action mandatory and that future P4Runtime versions will provide a mechanism to change the property value dynamically. Note that the discussion above also applies to the one shot style of programming.

The PSA specification includes a discussion on how to implement psa_empty_group_action in software in the P4Runtime server [23].

### 9.3. CounterEntry&DirectCounterEntry

PSA defines Counters as a mechanism for keeping statistics of bytes and packets. Statistics may be updated as a result of an action associated with a table entry, or a direct invocation such as from a P4 control. The CounterData P4Runtime message can be used for all three types of PSA counters PACKETS, BYTES and PACKETS_AND_BYTES and consists of the following fields:

• byte_count is an int64, corresponding to the number of octets.
• packet_count is an int64, corresponding to the number of packets.
message CounterData {
int64 byte_count = 1;
int64 packet_count = 2;
}

P4Runtime does not distinguish between the different PSA counter types, and allows for simultaneous updates of byte_count and packet_count fields, which is equivalent to specifying the counter type PACKETS_AND_BYTES. Counters may be defined as direct or indirect (indexed) instances.

#### 9.3.1. DirectCounterEntry

A direct counter is a direct resource associated with a TableEntry (see Direct Resources). The counter_data field of the TableEntry message can be used to initialize the counter value at the same time as the table entry is inserted. Once the table entry has been created, the P4Runtime client may modify the associated direct counter entry using the DirectCounterEntry message. Once the table entry is deleted the associated direct counter entry can no longer be accessed.

message DirectCounterEntry {
TableEntry table_entry = 1;
CounterData data = 2;
}

A WriteRequest may only include an Update message of type MODIFY with a DirectCounterEntry, whose fields are specified by the client as follows:

• the table_entry.match field must match TableEntry.match of the table entry to which this direct counter entry is associated. If a matching TableEntry is not found, the server returns the error code NOT_FOUND.

• data is used to set the counter value to the value specified by the client. Note that if this Protobuf field is not set, the counter value is not modified.

Specifying DirectCounterEntry in an Update message of type INSERT or DELETE is not allowed, and the server must return the error code INVALID_ARGUMENT in that case.

A client may use ReadRequest in two ways to read the contents of a DirectCounter:

• As a direct resource associated with a table entry, request the server to return the counter value in the counter_data field of the TableEntry message (see Direct resources).

• Explicitly request the counter value by including the DirectCounterEntry in the ReadRequest. The table_entry.match field must match the TableEntry whose counter is being read. If no such entry is found, the server returns the error code NOT_FOUND.

#### 9.3.2. CounterEntry

An indirect or indexed counter is not associated with a specific TableEntry and may be updated independently of any action. It may be read or written using the P4Runtime CounterEntry message whose fields are defined as follows:

• counter_id is a uint32, the unique identifier for the counter.

• index is a Protobuf message that encapsulates an int64, used to index into the counter array.

• data is a Protobuf message of type CounterData, which represents the counter value.

message CounterEntry {
uint32 counter_id = 1;
Index index = 2;
CounterData data = 3;
}

The CounterEntry can only be used in a WriteRequest with the MODIFY update type. The P4Runtime server must return an INVALID_ARGUMENT error code for update types INSERT and DELETE. By default all the counter entries in the array have default value 0.

• INSERT: Server returns the error code INVALID_ARGUMENT.
• MODIFY: Modify an indirect counter instance whose unique id is counter_id and array index is specified by index. The counter value is set to the value specified by the client in the data field. Note that the counter value is not modified if this Protobuf field is not set. If index is omitted all counter values in the array will be set to the value provided by the client. The server must return INVALID_ARGUMENT for a negative index value and OUT_OF_RANGE if the index value exceeds the size of the counter array.
• DELETE: Server returns the error code INVALID_ARGUMENT.

A P4Runtime client may request to read the counter values of one or more indirect counter instances with a ReadRequest by including a CounterEntry entity for each of the instances, specifying the counter_id and index. Wildcard reads are also supported as follows.

• If the counter_id field is set to 0 (default), the server returns the counter values for all indirect counter instances in the ReadResponse.

• If the index field is not set, the server returns the counter values for all indirect counters in the array identified by the unique id counter_id.

### 9.4. MeterEntry&DirectMeterEntry

Meters are an advanced mechanism for keeping statistics, involving stateful “marking” and usually “throttling” of packets based on configured rates of traffic. The PSA metering function is based on the Two Rate Three Color Marker (trTCM) defined in RFC 2698 [2]. The trTCM meters an arbitrary packet stream using two configured rates the Peak Information Rate (PIR) and Committed Information Rate (CIR), and their associated burst sizes and “marks” its packets as GREEN, YELLOW or RED based on the observed rate.

A meter may be configured as a direct or indirect instance, similar to a counter. The MeterConfig P4Runtime message represents meter configuration.

message MeterConfig {
int64 cir = 1;  // Committed Information Rate
int64 cburst = 2;  // Committed Burst Size
int64 pir = 3;  // Peak Information Rate
int64 pburst = 4;  // Peak Burst Size
}

#### 9.4.1. DirectMeterEntry

A direct meter is a direct resource associated with a TableEntry (see Direct resources). The meter_config field of the TableEntry message can be used to initialize the meter configuration at the same time as the table entry is inserted. Once the table entry has been created, the P4Runtime client may modify the associated direct meter entry using the DirectMeterEntry message. Once the table entry is deleted the associated direct meter entry can no longer be accessed.

message DirectMeterEntry {
TableEntry table_entry = 1;
MeterConfig config = 2;
}

A WriteRequest may only include an Update message of type MODIFY with a DirectMeterEntry, whose fields are specified by the client as follows:

• the table_entry.match field must match the match key of the TableEntry message used to insert the entry and the associated direct meter entry. The action field is ignored in this case. If a matching TableEntry is not found, the server returns the error code NOT_FOUND.

• config is used to set the configuration for the meter entry to the value specified by the client. Note that if this Protobuf field is not set, the meter config is set to execute the default behavior (GREEN for all packets).

Specifying DirectMeterEntry in an Update message of type INSERT or DELETE is not allowed, and the server must return the error code INVALID_ARGUMENT in that case.

A client may use ReadRequest in two ways to read a DirectMeter config.

• As a direct resource associated with a table entry, request the server to return the meter config in the meter_config field of the TableEntry message (see Direct resources).

• Explicitly request the meter configuration by including the DirectMeterEntry in the ReadRequest. The table_entry.match field must match the TableEntry whose meter config is being read. If no such entry is found, the server returns the error code NOT_FOUND.

#### 9.4.2. MeterEntry

An indirect or indexed meter is not associated with a specific TableEntry and may be executed independently of any action. Its configuration may be read or written using the P4Runtime MeterEntry message whose fields are defined as follows:

• meter_id is a uint32, the unique identifier for the meter.

• index is a Protobuf message that encapsulates an int64, used to index into a meter array.

• config is a Protobuf message of type MeterConfig, which represents the meter configuration.

message MeterEntry {
uint32 meter_id = 1;
Index index = 2;
MeterConfig config = 3;
}

The MeterEntry can only be used in a WriteRequest with the MODIFY update type. The P4Runtime server must return an INVALID_ARGUMENT error code for update types INSERT and DELETE. By default all the meter entries in the array have a default configuration (GREEN for all packets).

• INSERT: Server returns the error code INVALID_ARGUMENT.
• MODIFY: Modify an indirect meter instance whose unique id is meter_id and array index is specified by index. The meter is reconfigured using the config field specified by the client. Note that the meter configuration is set to the default behavior (GREEN for all packets) if this Protobuf field is not set. If the index field is omitted all meter configurations in the array will be set to the value provided by the client (or reset to the default value if config is unset). The server must return INVALID_ARGUMENT for a negative index value and OUT_OF_RANGE if the index value exceeds the size of the meter array.
• DELETE: Server returns the error code INVALID_ARGUMENT.

A P4Runtime client may request to read the configuration of one or more indirect meter instances with a ReadRequest by including a MeterEntry entity for each of the instances, specifying the meter_id and index. Wildcard reads are also supported as follows:

• If the meter_id field is set to 0 (default), the server returns the configuration for all indirect meter instances in the ReadResponse.

• If the index field is not set, the server returns the configuration for all indirect meters in the array identified by the unique id meter_id.

### 9.5. PacketReplicationEngineEntry

The PSA Packet Replication Engine (PRE) is an extern that is implicitly instantiated in all PSA programs. The PRE is responsible for implementing multicasting and cloning functionality in the data plane. P4Runtime defines an API to program the PRE with multicast groups and clone sessions to allow replication of data plane packets.

#### 9.5.1. MulticastGroupEntry

Multicasting is achieved in PSA programs by setting the multicast_group ingress output metadata to a non-zero identifier. The number of replicas and their egress ports for the multicast group is programmed at runtime by the client using the MulticastGroupEntry API in P4Runtime. The following P4 program illustrates a possible data plane behavior of multicasting ARP packets in the ingress. Note that the data plane type of the multicast group metadata is 10 bits on the PSA device in this example.

control arp_multicast(inout H hdr, inout M smeta) {
apply {
if (hdr.ethernet.isValid() &&
hdr.ethernet.eth_type == ETH_TYPE_ARP) {
smeta.multicast_group = (MulticastGroup_t) 1;
}
}
}

At runtime, the client writes the following update in the target (shown in Protobuf text format).

type: INSERT
entity {
packet_replication_engine_entry {
multicast_group_entry {
multicast_group_id: 1
replicas { egress_port: 5 instance: 1 }
replicas { egress_port: 12 instance: 2 }
replicas { egress_port: 18 instance: 3 }
replicas { egress_port: 24 instance: 4 }
}
}
}

As a result of the above P4Runtime programming, the target device will create four replicas of an ARP packet. These replicas will appear in the egress pipeline as independent packets with egress port set to PSA device port numbers corresponding to SDN port numbers 5, 12, 18 and 24. For more discussion on the translation between SDN ports and PSA device ports, refer to the PSA Metadata Translation section.

The egress packets may be distinguished for further processing in the egress using the instance metadata. Note that a packet may not be both unicast and multicast; if the multicast group is set, it will override the unicast egress port. If the P4 multicast_group metadata is set to a value that is not programmed in the PRE, then the packet is dropped.

A multicast group may be inserted, modified or deleted as per the following semantics.

• INSERT: Add a new multicast group entry bound to a set of egress ports and replica IDs. The multicast_group_id field is a uint32 and must not exceed the maximum value supported by the target. The PSA specification states that 0 is a special value which indicates that no multicast replication is to be performed for a packet [22]. Therefore multicast_group_id must never be set to 0. If one of these constraints is violated, the P4Runtime server must return an INVALID_ARGUMENT error. The replica instance ID is also a uint32, and its value may not exceed the maximum allowed by the target for the EgressInstance_t type (0 is allowed), or the server must return an INVALID_ARGUMENT