diff --git a/.github/workflows/any-branch-uploads.yml b/.github/workflows/any-branch-uploads.yml index 9b1483cf..a41cfc71 100644 --- a/.github/workflows/any-branch-uploads.yml +++ b/.github/workflows/any-branch-uploads.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v3 - name: Build spec run: | - docker run -v `pwd`/docs/v1:/usr/src/p4-spec p4lang/p4rt-madoko:latest make + docker run -v `pwd`/docs/v1:/usr/src/p4-spec p4lang/p4rt-asciidoc :latest make ls docs/v1/build - name: Upload spec to S3 if needed if: ${{ github.actor != 'dependabot[bot]' }} diff --git a/.github/workflows/main-branch-uploads.yml b/.github/workflows/main-branch-uploads.yml index 89748253..7001733e 100644 --- a/.github/workflows/main-branch-uploads.yml +++ b/.github/workflows/main-branch-uploads.yml @@ -16,7 +16,7 @@ jobs: fetch-depth: 0 - name: Build spec run: | - docker run -v `pwd`/docs/v1:/usr/src/p4-spec p4lang/p4rt-madoko:latest make + docker run -v `pwd`/docs/v1:/usr/src/p4-spec p4lang/p4rt-asciidoc :latest make ls docs/v1/build - name: Upload spec to S3 uses: jakejarvis/s3-sync-action@v0.5.1 diff --git a/.github/workflows/spec.yml b/.github/workflows/spec.yml index cc1983ae..579ab847 100644 --- a/.github/workflows/spec.yml +++ b/.github/workflows/spec.yml @@ -11,13 +11,13 @@ on: - '*-dev' jobs: - madoko-lint: + asciidoc-lint: runs-on: [ubuntu-latest] steps: - uses: actions/checkout@v3 - name: Run linter run: | - ./tools/madokolint.py docs/v1/P4Runtime-Spec.mdk + ./tools/asciidoclint.py docs/v1/P4Runtime-Spec.adoc build-spec: runs-on: [ubuntu-latest] @@ -25,5 +25,5 @@ jobs: - uses: actions/checkout@v3 - name: Build spec run: | - docker run -v `pwd`/docs/v1:/usr/src/p4-spec p4lang/p4rt-madoko:latest make + docker run -v `pwd`/docs/v1:/usr/src/p4-spec p4lang/p4rt-asciidoc :latest make build_spec_with_images ls docs/v1/build diff --git a/.github/workflows/tag-uploads.yml b/.github/workflows/tag-uploads.yml index 4495cda0..4efde883 100644 --- a/.github/workflows/tag-uploads.yml +++ b/.github/workflows/tag-uploads.yml @@ -18,7 +18,7 @@ jobs: fetch-depth: 0 - name: Build spec run: | - docker run -v `pwd`/docs/v1:/usr/src/p4-spec p4lang/p4rt-madoko:latest make + docker run -v `pwd`/docs/v1:/usr/src/p4-spec p4lang/p4rt-asciidoc :latest make ls docs/v1/build - name: Upload spec to S3 uses: jakejarvis/s3-sync-action@v0.5.1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9dac0765..b357f9ab 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,10 +4,9 @@ You can fork the repo and submit a pull request in Github. All developers must have signed the [P4.org](http://p4.org) CLA. -### Madoko style checker +### AsciiDoc style checker -The P4Runtime specification is written using -[Madoko](http://madoko.org/reference.html). We provide a lint tool to catch -basic formatting issues and try to keep the spec uniform. The lint tool will be -run as part of CI and patches cannot be merged until it returns success. You can -run the lint tool locally with `./tools/madokolint.py`. +The P4Runtime specification is written using [AsciiDoc](https://docs.asciidoctor.org/). +We provide a lint tool to catch basic formatting issues and try to keep the spec uniform. +The lint tool will be run as part of CI and patches cannot be merged until it returns success. You can +run the lint tool locally with `./tools/asciidoclint.py`. diff --git a/docs/tools/Dockerfile.asciidoc b/docs/tools/Dockerfile.asciidoc new file mode 100644 index 00000000..ce5df55b --- /dev/null +++ b/docs/tools/Dockerfile.asciidoc @@ -0,0 +1,31 @@ +FROM ruby:3.3.5 +LABEL maintainer="P4 API Working Group " +LABEL description="Dockerfile used for building the asciidoc specification" + +RUN apt-get update && \ + apt-get install -y cmake flex bison libglib2.0-dev libcairo2-dev libpango1.0-dev libxml2-dev libwebp-dev libzstd-dev libgdk-pixbuf-2.0-dev time + +RUN apt-get install -y libreoffice && \ + gem install asciidoctor && \ + echo 'gem: --no-document' > /etc/gemrc && \ + gem install nokogiri && \ + gem install rghost && \ + gem install asciidoctor-diagram && \ + gem install asciidoctor-plantuml && \ + gem install asciidoctor-pdf --version 2.3.19 && \ + gem install asciidoctor-pdf-cjk && \ + gem install asciidoctor-lists --version 1.1.2 && \ + gem install coderay pygments.rb thread_safe && \ + gem install slim && \ + gem install concurrent-ruby && \ + gem install haml tilt && \ + gem install asciidoctor-mathematical && \ + gem install asciidoctor-bibtex &&\ + git clone https://github.com/rouge-ruby/rouge &&\ + cd rouge && \ + git log -n 1 | cat && \ + gem build rouge.gemspec && \ + gem install rouge + +VOLUME ["/usr/src/p4-spec"] +WORKDIR /usr/src/p4-spec \ No newline at end of file diff --git a/docs/tools/Dockerfile.madoko b/docs/tools/Dockerfile.madoko deleted file mode 100644 index be73c9e3..00000000 --- a/docs/tools/Dockerfile.madoko +++ /dev/null @@ -1,24 +0,0 @@ -FROM ubuntu:16.04 -LABEL maintainer="P4 API Working Group " -LABEL description="Dockerfile used for building the Madoko specification" - -ENV PKG_DEPS software-properties-common build-essential libreoffice \ - nodejs npm texlive-full fonts-liberation - -RUN apt-get update && \ - apt-get install -y --no-install-recommends $PKG_DEPS && \ - apt-get autoremove --purge -y && \ - rm -rf /var/cache/apt/* /var/lib/apt/lists/* - -RUN npm install madoko -g && \ - ln -s /usr/bin/nodejs /usr/local/bin/node - -VOLUME ["/usr/src/p4-spec"] -WORKDIR /usr/src/p4-spec - -# add extra fonts for P4_14 look-alike -RUN mkdir -p /usr/share/fonts/truetype/UtopiaStd /usr/share/fonts/truetype/LuxiMono -COPY fonts/UtopiaStd-Regular.otf /usr/share/fonts/truetype/UtopiaStd/ -COPY fonts/luximr.ttf /usr/share/fonts/truetype/LuxiMono/ -COPY fonts/fix_helvetica.conf /etc/fonts/local.conf -RUN fc-cache -fv diff --git a/docs/tools/README.md b/docs/tools/README.md index fef843bf..893b2488 100644 --- a/docs/tools/README.md +++ b/docs/tools/README.md @@ -1,4 +1,4 @@ -Dockerfile.madoko is used to build a Docker image which we use to render the +Dockerfile.asciidoc is used to build a Docker image which we use to render the P4Runtime specification (HTML & PDF) in CI. The image can also be used locally to build the specification without having to worry about installing all the dependencies yourself. @@ -9,9 +9,9 @@ simply pull the image from dockerhub and don't have to worry about building the image themselves. If you are a maintainer and you need to upload a new version of the Docker image to to dockerhub, you will need the following commands: ```bash -docker build -t p4rt-madoko -f Dockerfile.madoko . -docker tag p4rt-madoko p4lang/p4rt-madoko:latest -docker push p4lang/p4rt-madoko:latest +docker build -t p4rt-asciidoc -f Dockerfile.asciidoc . +docker tag p4rt-asciidoc p4lang/p4rt-asciidoc:latest +docker push p4lang/p4rt-asciidoc:latest ``` Note that you need to have write permissions to the p4lang dockerhub repository diff --git a/docs/v1/Makefile b/docs/v1/Makefile index 1fe6c52c..3856a62c 100644 --- a/docs/v1/Makefile +++ b/docs/v1/Makefile @@ -1,21 +1,32 @@ - SPEC=P4Runtime-Spec -all: html pdf build +ROUGE_STYLE=github +ROUGE_CSS=style + +all: ${SPEC}.pdf ${SPEC}.html build: - mkdir -p build -# Note: Brute-force image rendering; could use more precise file-by-file make rules -images: build assets/*.odg - soffice --convert-to svg --outdir build assets/*.odg > /dev/null 2>&1 - soffice --convert-to png --outdir build assets/*.odg > /dev/null 2>&1 +${SPEC}.pdf: ${SPEC}.adoc images + time asciidoctor-pdf -v \ + -a pdf-fontsdir=resources/fonts \ + -r asciidoctor-mathematical \ + -r asciidoctor-bibtex \ + -r asciidoctor-lists \ + -a rouge-style=$(ROUGE_STYLE) $< -html: ${SPEC}.mdk p4.json assets/* images - madoko -vv --png --odir=build ${SPEC}.mdk +${SPEC}.html: ${SPEC}.adoc images + time asciidoctor -v \ + -r asciidoctor-mathematical \ + -r asciidoctor-bibtex \ + -r asciidoctor-lists \ + -a rouge-css=$(ROUGE_CSS) $< -pdf: ${SPEC}.mdk p4.json assets/* images - madoko --pdf -vv --png --odir=build $< +images: + soffice --convert-to svg --outdir resources/figs resources/figs/*.odg > /dev/null 2>&1 + soffice --convert-to png --outdir resources/figs resources/figs/*.odg > /dev/null 2>&1 +build_spec_with_images: images all + clean: - ${RM} -rf build + /bin/rm -f ${SPEC}.pdf ${SPEC}.html \ No newline at end of file diff --git a/docs/v1/P4Runtime-Spec.mdk b/docs/v1/P4Runtime-Spec.adoc similarity index 79% rename from docs/v1/P4Runtime-Spec.mdk rename to docs/v1/P4Runtime-Spec.adoc index c1124ed7..7d0ecf45 100755 --- a/docs/v1/P4Runtime-Spec.mdk +++ b/docs/v1/P4Runtime-Spec.adoc @@ -1,262 +1,91 @@ -Title : P4Runtime Specification -Title Note: version 1.5.0-dev -Title Footer: &date; -Author: The P4.org API Working Group -Heading depth: 5 -Pdf Latex: xelatex -Document Class: [11pt]article -Package: [top=1in, bottom=1.25in, left=1in, right=1in]geometry -Package: fancyhdr - -Tex Header: - \setlength{\headheight}{30pt} - \setlength{\emergencystretch}{2em} - -Bib: references.bib -Bib Search Url: - -Script: https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js -Script: bibref_no_title.js - -@if html { -body.madoko { - font-family: utopia-std, serif; -} -title,titlenote,titlefooter,authors,h1,h2,h3,h4,h5 { - font-family: helvetica, sans-serif; - font-weight: bold; -} -pre, code { - font-family: monospace; - font-size: 10pt; -} -} - -@if tex { -body.madoko { - font-family: UtopiaStd-Regular; -} -title,titlenote,titlefooter,authors { - font-family: sans-serif; - font-weight: bold; -} -pre, code { - font-family: LuxiMono; - font-size: 75%; -} -} - -Colorizer: p4 -Colorizer: proto -Colorizer: prototext -Colorizer: cpp - -.token.keyword { - font-weight: bold; -} - -@if html { -p4example { - replace: "~ Begin P4ExampleBlock&nl;\ - ````p4&nl;&source;&nl;````&nl;\ - ~ End P4ExampleBlock"; - padding:6pt; - margin-top: 6pt; - margin-bottom: 6pt; - border: solid; - background-color: #ffffdd; - border-width: 0.5pt; -} -} - -@if tex { -p4example { - replace: "~ Begin P4ExampleBlock&nl;\ - ````p4&nl;&source;&nl;````&nl;\ - ~ End P4ExampleBlock"; - breakable: true; - padding: 6pt; - margin-top: 6pt; - margin-bottom: 6pt; - border: solid; - background-color: #ffffdd; - border-width: 0.5pt; -} -} - -@if html { -pseudo { - replace: "~ Begin PseudoBlock&nl;\ - ````&nl;&source;&nl;````&nl;\ - ~ End PseudoBlock"; - padding: 6pt; - margin-top: 6pt; - margin-bottom: 6pt; - border: solid; - background-color: #e9fce9; - border-width: 0.5pt; -} -} - -@if tex { -pseudo { - replace: "~ Begin PseudoBlock&nl;\ - ````&nl;&source;&nl;````&nl;\ - ~ End PseudoBlock"; - breakable : true; - padding: 6pt; - margin-top: 6pt; - margin-bottom: 6pt; - background-color: #e9fce9; - border: solid; - border-width: 0.5pt; -} -} - -@if html { -cpp { - replace: "~ Begin CPPblock&nl;\ - ````cpp&nl;&source;&nl;````&nl;\ - ~ End CPPblock"; - border: solid; - margin-top: 6pt; - margin-bottom: 6pt; - padding: 6pt; - background-color: #e9fce9; - border-width: 0.5pt; -} -} - -@if tex { -cpp { - replace: "~ Begin CPPblock&nl;\ - ````cpp&nl;&source;&nl;````&nl;\ - ~ End CPPblock"; - breakable: true; - margin-top: 6pt; - margin-bottom: 6pt; - padding: 6pt; - background-color: #e9fce9; - border: solid; - border-width: 0.5pt; -} -} - -@if html { -proto { - replace: "~ Begin Protoblock&nl;\ - ````proto&nl;&source;&nl;````&nl;\ - ~ End Protoblock"; - border: solid; - margin-top: 6pt; - margin-bottom: 6pt; - padding: 6pt; - background-color: #e6ffff; - border-width: 0.5pt; -} -} - -@if tex { -proto { - replace: "~ Begin Protoblock&nl;\ - ````proto&nl;&source;&nl;````&nl;\ - ~ End Protoblock"; - breakable: true; - margin-top: 6pt; - margin-bottom: 6pt; - padding: 6pt; - background-color: #e6ffff; - border: solid; - border-width: 0.5pt; -} -} - -@if html { -prototext { - replace: "~ Begin Prototextblock&nl;\ - ````prototext&nl;&source;&nl;````&nl;\ - ~ End Prototextblock"; - border: solid; - margin-top: 6pt; - margin-bottom: 6pt; - padding: 6pt; - background-color: #e6ffff; - border-width: 0.5pt; -} -} - -@if tex { -prototext { - replace: "~ Begin Prototextblock&nl;\ - ````prototext&nl;&source;&nl;````&nl;\ - ~ End Prototextblock"; - breakable: true; - margin-top: 6pt; - margin-bottom: 6pt; - padding: 6pt; - background-color: #e6ffff; - border: solid; - border-width: 0.5pt; -} -} - -[TITLE] - -~ Begin Abstract += P4Runtime Specification : The P4.org API Working Group +:doctype: book +:revnumber: version 1.5.0-dev +:imagesdir: resources/figs +:font-size: 10 +:sectnums: 5 +:sectnumlevels: 5 +:toc: macro +:toc-title: Contents +:toclevels: 5 +:toc: left +:!chapter-signifier: +:xrefstyle: short +:stem: latexmath +:pdf-themesdir: resources/theme/ +:pdf-theme: p4-theme.yml +:stylesdir: resources/theme/ +:stylesheet: p4-stylesheet.css +:source-highlighter: rouge +:data-uri: +:bibtex-file: resources/theme/references.bib +:bibtex-order: appearance +:bibtex-style: ieee +:listing-caption: Listing +:revdate: {docdate} + + +[abstract] +.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. -~ End Abstract -[TOC] -[TOC=figures] -[TOC=tables] +toc::[] +:sectnums!: +== List of figures +list-of::image[] -# Introduction and Scope +== List of tables +list-of::table[] +:sectnums: + +[#sec-scope] +== Introduction and Scope This document is published by the *P4.org API Working Group*, which was -chartered [@P4APIWGCharter] to design and standardize vendor-independent, +chartered cite:[P4APIWGCharter] 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/main/proto](https://github.com/p4lang/p4runtime/tree/main/proto). +link:https://github.com/p4lang/p4runtime/tree/main/proto[https://github.com/p4lang/p4runtime/tree/main/proto]. -## P4 Language Version Applicability +=== P4 Language Version Applicability P4Runtime is designed to be implemented in conjunction with the P4~16~ language version or later. P4~14~ programs should be translated into P4~16~ to be made compatible with P4Runtime. This version of P4Runtime utilizes features which are -not in P4~16~ 1.0, but were introduced in P4~16~ 1.2.4 [@P4Revisions124]. For -this version of P4Runtime, we recommend using P4~16~ 1.2.4 [@P4Revisions124]. +not in P4~16~ 1.0, but were introduced in P4~16~ 1.2.4 cite:[P4Revisions124]. For +this version of P4Runtime, we recommend using P4~16~ 1.2.4 cite:[P4Revisions124]. This version of the P4Runtime specification does not yet explicitly address compatibility with the following P4~16~ language features introduced in versions 1.2.2 or 1.2.4 of the language specification: -* Added support for generic structures [@P4Revisions122]. -* Added support for additional enumeration types [@P4Revisions122]. -* Added support for 0-width bitstrings and varbits [@P4Revisions122]. +* Added support for generic structures cite:[P4Revisions122]. +* Added support for additional enumeration types cite:[P4Revisions122]. +* Added support for 0-width bitstrings and varbits cite:[P4Revisions122]. * Clarified restrictions for parameters with default values - [@P4Revisions124]. + cite:[P4Revisions124]. * Allow ranges to be specified by serializable enums - [@P4Revisions124]. -* Added `list` type [@P4Revisions124]. + cite:[P4Revisions124]. +* Added `list` type cite:[P4Revisions124]. * Clarified behavior of table with no `key` property, or if its list - of keys is empty [@P4Revisions124]. + of keys is empty cite:[P4Revisions124]. -## In Scope +=== 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) [@PSA] externs (⪚ Counters, Meters, Action + Switch Architecture (PSA) cite:[PSA] 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 @@ -277,7 +106,7 @@ The following are in the scope of this specification document: * Detailed description of the API semantics. * Requirements for conformant implementations of the API. -## Not In Scope +=== Not In Scope The following are not in scope of P4Runtime: @@ -286,8 +115,8 @@ The following are not in scope of P4Runtime: 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 - [@OpenConfig]. An open source implementation of these APIs is also in progress - as part of the Stratum project [@Stratum]. + cite:[OpenConfig]. An open source implementation of these APIs is also in progress + as part of the Stratum project cite:[Stratum]. * 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 @@ -296,17 +125,18 @@ The following are not in scope of P4Runtime: 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 P4~16~ [@P4Spec]. + already familiar with P4~16~ cite:[P4Spec]. * Descriptions of gRPC and Protobuf files in general. -* Controller [role](#sec-arbitration-role-config) definition (for partition of +* Controller xref:sec-arbitration-role-config[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. -# Terms and Definitions +[#sec-terms-definitions] +== Terms and Definitions * arbitration : Refers to the process through which P4Runtime ensures that at any given - time, there is a single primary controller (&ie; a client with write access) + time, there is a single primary controller (i.e. a client with write access) for a given role. Also referred to as "client arbitration". * client : The gRPC client is the software entity which controls the P4 target or @@ -323,7 +153,7 @@ The following are not in scope of this specification document: any other architecture). * gRPC : gRPC Remote Procedure Calls, an open-source client-server RPC framework. See - [@gRPC]. + cite:[gRPC]. * HA : High-Availability. Refers to a redundancy architecture. * Instrumentation @@ -345,9 +175,9 @@ The following are not in scope of this specification document: : Abbreviation for P4Runtime. * Protobuf (Protocol Buffers) : The wire serialization format for P4Runtime. Protobuf version 3 (proto3) is - used to define the P4Runtime interface. See [@Proto]. + used to define the P4Runtime interface. See cite:[Proto]. * PSA - : Portable Switch Architecture [@PSA]; a target architecture that describes + : Portable Switch Architecture cite:[PSA]; a target architecture that describes common capabilities of network switch devices that process and forward packets across multiple interface ports. * RPC @@ -386,9 +216,10 @@ The following are not in scope of this specification document: : Uniform Resource Identifier; a string of characters designed for unambiguous identification of resources. -# Reference Architecture { #sec-reference-architecture} +[#sec-reference-architecture] +== Reference Architecture -Figure [#fig-reference-architecture] represents the P4Runtime Reference +<<#fig-reference-architecture>> 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. P4Runtime only grants write access to a single primary controller for each read/write entity. A role defines a grouping @@ -402,15 +233,15 @@ 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 -[@P4RuntimeRepo]. It may be compiled via protoc --- the Protobuf compiler --- +cite:[P4RuntimeRepo]. 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 [@PIRepo]. A future +clients, may be available on the p4lang/PI GitHub repository cite:[PIRepo]. A future goal may be to produce a reference gRPC server which can be instrumented in a -generic way, ⪚ via callbacks, thus reducing the burden of implementing +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 @@ -423,15 +254,12 @@ installing and running the compiled P4 program output, which is included in the metadata. Furthermore, the controller can query the target for the `ForwardingPipelineConfig` to retrieve the device config and the P4Info. +.P4Runtime Reference Architecture. +[#fig-reference-architecture] +image::reference-architecture.png[width=400,align="center"] -~ Figure { #fig-reference-architecture; \ -caption: "P4Runtime Reference Architecture." } -![reference-architecture] -~ -[reference-architecture]: build/reference-architecture.[svg,png] \ -{ height: 7cm; page-align: here } - -## P4Runtime Service Implementation +<<< +=== P4Runtime Service Implementation The P4Runtime API is implemented by a program that runs a gRPC server which binds an implementation of auto-generated P4Runtime Service interface. This @@ -441,7 +269,8 @@ P4Runtime service. Servers should allow users to override the default port using a configuration file or flag when starting the server. Uses of other port numbers as the default should be discontinued. -### Security concerns +[#sec-security-concerns] +==== Security concerns Appropriate measures and security best practices must be in place to protect the P4Runtime server and client, and the communication channel between the two. @@ -452,14 +281,15 @@ man-in-the-middle attacks between the server and client. Mutual TLS (mTLS) may be used to facilitate the authentication of the client by the server and vice-versa. -## Idealized Workflow + +=== 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 --- +(`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. @@ -478,7 +308,8 @@ controller can also query the `ForwardingPipelineConfig` from the target via the configuration from a running device to synchronize the controller to its current state. -## P4 as a Behavioral Description Language { #sec-p4-as-behavioral-description-language} +[#sec-p4-as-behavioral-description-language] +=== 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 @@ -505,13 +336,14 @@ 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. -## Alternative Workflows +=== 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. -### P4 Source Available, Compiled into P4Info but not Compiled into P4 Device Config +[#sec-p4-source-available] +==== 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 @@ -520,7 +352,8 @@ 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. -### No P4 Source Available, P4Info Available +[#sec-no-p4-source-available] +==== 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: @@ -531,12 +364,13 @@ number of reasons, the most likely of which are: 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 [#sec-p4-as-behavioral-description-language], in the +As discussed in <<#sec-p4-as-behavioral-description-language>>, 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, ⪚ documentation. +sources, e.g. documentation. -### Partial P4Info and P4 Source are Available +[#sec-partial-p4info] +==== 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 @@ -545,13 +379,15 @@ 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. -### P4Info Role-Based Subsets +[#sec-p4info-role] +==== 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. -## P4Runtime State Across Restarts { #sec-restarts } +[#sec-restarts] +=== P4Runtime State Across Restarts All targets support full restarts, where all forwarding state is reset and the P4Runtime server starts with a clean state. Some targets may also support @@ -559,18 +395,18 @@ In-Service Software Upgrade (ISSU), where the software on the target can be restarted while traffic is being forwarded. In this case, the P4Runtime server may have the ability to access information from memory before the upgrade. -# Controller Use-cases +[#sec-controller] +== Controller Use-cases P4Runtime allows for more than one controller. The mechanisms and semantics are -described in a later -[section](#sec-client-arbitration-and-controller-replication). Here we +described in <<#sec-client-arbitration-and-controller-replication>>. 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. -## Single Embedded Controller +=== Single Embedded Controller -Figure [#fig-single-embedded-controller] shows perhaps the simplest use-case. A +<<#fig-single-embedded-controller>> 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. @@ -580,30 +416,27 @@ architectures typically feature multiple processes communicating with some sort of IPC (Inter-Process Communications). P4Runtime is thus both an ideal RPC and an IPC. -~ Figure { #fig-single-embedded-controller; \ -caption: "Use-Case: Single Embedded Controller" } -![single-embedded-controller] -~ -[single-embedded-controller]: build/single-embedded-controller.[svg,png] \ -{ height: 6cm; page-align: forcehere } -## Single Remote Controller +.Use-Case: Single Embedded Controller +[#fig-single-embedded-controller] +image::single-embedded-controller.png[width=400,align="center"] -Figure [#fig-single-remote-controller] shows a single remote Controller in + +=== Single Remote Controller + +<> 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. -~ Figure { #fig-single-remote-controller; \ -caption: "Use-Case: Single Remote Controller" } -![single-remote-controller] -~ -[single-remote-controller]: build/single-remote-controller.[svg,png] \ -{ height: 7cm; page-align: forcehere } +.Use-Case: Single Remote Controller +[#fig-single-remote-controller] +image::single-embedded-controller.png[width=400,align="center"] + -## Embedded + Single Remote Controller +=== Embedded + Single Remote Controller -Figure [#fig-embedded-plus-single-remote-controller] illustrates the use-case of +<> 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 @@ -614,52 +447,40 @@ 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. -~ Figure { #fig-embedded-plus-single-remote-controller; \ -caption: "Use-Case: Embedded Plus Single Remote Controller" } -![embedded-plus-single-remote-controller] -~ -[embedded-plus-single-remote-controller]: \ -build/embedded-plus-single-remote-controller.[svg,png] \ -{ height: 7cm; page-align: forcehere } +.Use-Case: Embedded Plus Single Remote Controller +[#fig-embedded-plus-single-remote-controller] +image::embedded-plus-single-remote-controller.png[width=400,align="center"] -## Embedded + Two Remote Controllers +=== Embedded + Two Remote Controllers -Figure [#fig-embedded-plus-two-remote-controllers] illustrates the case of an +<> 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, -⪚ routing tables, and the other remote controller is responsible for other +e.g. routing tables, and the other remote controller is responsible for other entities, perhaps statistics tables. Role-based access divides the ownership. -~ Figure { #fig-embedded-plus-two-remote-controllers; \ -caption: "Use-Case: Embedded Plus Two Remote Controllers" } -![embedded-plus-two-remote-controllers] -~ -[embedded-plus-two-remote-controllers]: \ -build/embedded-plus-two-remote-controllers.[svg,png] \ -{ height: 7cm; page-align: forcehere } +.Use-Case: Embedded Plus Two Remote Controllers +[#fig-embedded-plus-two-remote-controllers] +image::embedded-plus-two-remote-controllers.png[width=400,align="center"] -## Embedded Controller + Two High-Availability Remote Controllers +=== Embedded Controller + Two High-Availability Remote Controllers -Figure [#fig-embedded-plus-two-remote-ha-controllers] illustrates a single -embedded controller plus two remote controllers in an active-standby (&ie; +<> illustrates a single +embedded controller plus two remote controllers in an active-standby (i.e. primary-backup) 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 role-based client arbitration scheme supports it. -~ Figure { #fig-embedded-plus-two-remote-ha-controllers; \ -caption: "Use-Case: Embedded Plus Two Remote High-Availability Controllers" } -![embedded-plus-two-remote-ha-controllers] -~ -[embedded-plus-two-remote-ha-controllers]: \ -build/embedded-plus-two-remote-ha-controllers.[svg,png] \ -{ height: 7cm; page-align: forcehere } +.Use-Case: Embedded Plus Two Remote High-Availability Controllers +[#fig-embedded-plus-two-remote-ha-controllers] +image::embedded-plus-two-remote-controllers.png[width=400,align="center"] -# Client Arbitration and Controller Replication {\ - #sec-client-arbitration-and-controller-replication} +[#sec-client-arbitration-and-controller-replication] +== Client Arbitration and Controller Replication -The P4Runtime interface allows multiple clients (&ie; controllers) to be +The P4Runtime interface allows multiple clients (i.e. controllers) to be connected to the P4Runtime server running on the device at the same time for the following reasons: @@ -667,7 +488,7 @@ following reasons: 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 - primary and the rest are backups. Role definition, &ie; how P4 entities get + primary and the rest are backups. 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 @@ -680,7 +501,7 @@ 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 (⪚ a controller process) can participate in one or +* Each controller instance (e.g. a controller process) can participate in one or more roles. For each (`device_id`, `role`), 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`, `election_id`) is @@ -692,9 +513,8 @@ follows: primary independently for each (`device_id`, `role`) pair. The primary is the client that has the highest `election_id` that the device has ever received for the same (`device_id`, `role`) values. A connection between a controller - instance and a device id --- which involves a persistent `StreamChannel` --- - can be referred to as a P4Runtime client. - + 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` 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 @@ -720,18 +540,17 @@ follows: 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 (⪚ digests) and packet I/O:** The same + * **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 primary controller can participate in - packet I/O. This feature is explained in more details in the [Packet - I/O](#sec-packet-i_o) section. - - Note that a controller session is only required if the controller wants to do + packet I/O. This feature is explained in more details in the xref:sec-packet-i_o[Packet I/O] section. + + + Note that a controller session is only required if the controller wants to do Packet I/O, or modify the forwarding state. * Note that the stream is opened per device. In case a switching platform has - multiple devices (⪚ multi-ASIC line card) which are all controlled via the + multiple devices (e.g. multi-ASIC line card) which are all controlled via the same P4Runtime server, it is possible to have different primary clients for different devices. In this case, it is the responsibility of the P4Runtime server to keep track of the primary for each device (and role). More @@ -745,12 +564,11 @@ follows: broken. When a primary channel gets broken: 1. An advisory message is sent to all other controllers for that `device_id` - and `role`, as described in a - [later section](#sec-arbitration-notification); and + and `role`, as described in a xref:sec-arbitration-notification[later section]; and 2. The P4Runtime server will be without a primary controller, until a client sends a successful `MasterArbitrationUpdate` (as per the rules in a - [later section](#sec-arbitration-updates)). + xref:sec-arbitration-updates[later section]). * The mechanism through which the controller receives the P4Runtime server details are implementation specific and beyond the scope of this @@ -763,11 +581,11 @@ follows: prevent eavesdropping attacks. gRPC enables the server to identify which client originated each message in the -`StreamChannel` stream. For example, the C++ gRPC library [@gRPCStreamC] in +`StreamChannel` stream. For example, the C++ gRPC library cite:[gRPCStreamC] 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 completed any cleanup required -when a `StreamChannel` is closed normally (or broken, ⪚ because a client +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. @@ -779,27 +597,28 @@ 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`, 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 [@gRPCAuth] that +their write requests. gRPC provides authentication methods cite:[gRPCAuth] that should be deployed to prevent untrusted clients from creating channels, and thus from making changes or even reading the state of the server. -## Default Role +=== 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 `""` (default). -If using a default role, all RPCs from the controller (⪚ `Write`) must +If using a default role, all RPCs from the controller (e.g. `Write`) must leave the `role` unset. -## Role Config { #sec-arbitration-role-config} +[#sec-arbitration-role-config] +=== Role Config The `role.config` field in the `MasterArbitrationUpdate` message sent by the -controller describes the role configuration, &ie; which operations are in 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 (⪚ `DigestList` 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 @@ -810,7 +629,7 @@ following: 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 [@ProtoAny]. Such schemes +`role.config` is defined as an `Any` Protobuf message cite:[ProtoAny]. 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. @@ -818,53 +637,54 @@ scheme in an out-of-band fashion. It is the job of the P4Runtime server to remember the `role.config` for every `device_id` and `role` pair. -## Rules for Handling `MasterArbitrationUpdate` Messages Received from Controllers { #sec-arbitration-updates } +[#sec-arbitration-updates] +=== Rules for Handling `MasterArbitrationUpdate` Messages Received from Controllers -1. If the `MasterArbitrationUpdate` message is received for the first time on - this particular channel (&ie; for a newly connected controller): +. If the `MasterArbitrationUpdate` message is received for the first time on + this particular channel (i.e. for a newly connected controller): - 1. If `device_id` does not match any of the devices known to the P4Runtime + .. 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 set and is already used by another live + .. If the `election_id` is set and is already used by another live controller for the same (`device_id`, `role`), the P4Runtime server shall terminate the stream by returning an `INVALID_ARGUMENT` error. - 3. If `role.config` does not match the "out-of-band" scheme previously + .. If `role.config` does not match the "out-of-band" scheme previously agreed upon, the server must return an `INVALID_ARGUMENT` error. - 4. If the number of open streams for the given (`device_id`, `role`) + .. If the number of open streams for the given (`device_id`, `role`) exceeds the supported limit, the P4Runtime server shall terminate the stream by returning a `RESOURCE_EXHAUSTED` error. - 5. Otherwise, the controller is added to a list of live controllers for + .. Otherwise, the controller is added to a list of live controllers for the given (`device_id`, `role`) and the server remembers the controllers `device_id`, `role` and `election_id` for this gRPC channel. See below for the rules to determine if this controller becomes a primary or backup, and what notifications are sent as a consequence. -2. Otherwise, if the `MasterArbitrationUpdate` message is received from an +. Otherwise, if the `MasterArbitrationUpdate` message is received from an already live controller: - 1. If the `device_id` does not match the one already assigned to this + .. 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. If the `role` does not match the current `role` assigned to this + .. If the `role` does not match the current `role` assigned to this stream, the P4Runtime server shall terminate the stream by returning a `FAILED_PRECONDITION` error. If the controller wishes to change its role, it must close the current stream channel and open a new one. - 3. If `role.config` does not match the "out-of-band" scheme previously + .. If `role.config` does not match the "out-of-band" scheme previously agreed upon, the server must return an `INVALID_ARGUMENT` error. - 4. If the `election_id` is set and is already used by another live + .. If the `election_id` is set and is already used by another live controller (excluding the controller making the request) for the same (`device_id`, `role`), the P4Runtime server shall terminate the stream by returning an `INVALID_ARGUMENT` error. - 5. Otherwise, the server updates the `election_id` it has stored for this + .. Otherwise, the server updates the `election_id` it has stored for this controller. This change might cause a change in the primary client (this controller might become primary, or the controller might have downgraded itself to a backup, see below), as well as notifications being sent to @@ -876,51 +696,52 @@ the primary client. Let `election_id_past` be the highest election ID the server has ever seen for the given `device_id` and `role` (including the one of the current primary if there is one). -1. If `election_id` is greater than or equal to `election_id_past`, then the +. If `election_id` is greater than or equal to `election_id_past`, then the controller becomes, or stays, primary. The server updates the role configuration to `role.config` for the given `role`. Furthermore: - 1. If there was no primary for this `device_id` and `role` before and + .. If there was no primary for this `device_id` and `role` before and there are no `Write` requests still processing from a previous primary, then the server immediately sends an advisory notification to all controllers for this `device_id` and `role`. See the - [following section](#sec-arbitration-notification) for the format of the + xref:sec-arbitration-notification[following section] for the format of the advisory message. - 2. If there was a previous primary, including this controller, or `Write` + .. If there was a previous primary, including this controller, or `Write` requests in flight, then the server carries out the following steps (in this order): - 1. The server stops accepting `Write` requests from the previous primary + ... The server stops accepting `Write` requests from the previous primary (if there is one). At this point, the server will reject all `Write` requests with `PERMISSION_DENIED`. - 2. The server notifies all controllers other than the new primary client + ... The server notifies all controllers other than the new primary client of the change by sending the advisory notification described in - the [following section](#sec-arbitration-notification). + the xref:sec-arbitration-notification[following section]. - 3. The server will finish processing any `Write` requests that have + ... The server will finish processing any `Write` requests that have already started. If there are errors, they are reported as usual to the previous primary. If the previous primary has already disconnected, any possible errors are dropped and not reported. - 4. The server now accepts the current controller as the new primary, + ... The server now accepts the current controller as the new primary, thus accepting `Write` requests from this controller. The server - updates the highest election ID (&ie; `election_id_past`) it has seen + updates the highest election ID (i.e. `election_id_past`) it has seen for this `device_id` and `role` to `election_id`. - 5. The server notifies the new primary by sending the advisory message - described in the [following section](#sec-arbitration-notification). + ... The server notifies the new primary by sending the advisory message + described in the xref:sec-arbitration-notification[following section]. -2. Otherwise, the controller becomes a backup. If the controller was previously +. Otherwise, the controller becomes a backup. If the controller was previously a primary (and downgraded itself), then an advisory message is sent to all controllers for this `device_id` and `role`. Otherwise, the advisory message is only sent to the controller that sent the initial `MasterArbitrationUpdate`. See the - [following section](#sec-arbitration-notification) for the format of the + xref:sec-arbitration-notification[following section] for the format of the advisory message. -## Client Arbitration Notifications { #sec-arbitration-notification} +[#sec-arbitration-notification] +=== Client Arbitration Notifications For any given `device_id` and `role`, any time a new primary is chosen, a primary downgrades its status to a backup, a primary disconnects, or the @@ -935,49 +756,51 @@ primary downgrades its status to a backup, a primary disconnects, or the * `election_id` is populated as follows: - * If there has not been any primary at all, the election_id is left unset. + ** If there has not been any primary at all, the election_id is left unset. - * Otherwise, `election_id` is set to the highest election ID that the server + ** Otherwise, `election_id` is set to the highest election ID that the server has seen for this `device_id` and `role` (which is the `election_id` of the current primary if there is any). * `status` is set differently based on whether the notification is sent to the primary or a backup controller: - * If there is a primary: + ** If there is a primary: - * For the primary, `status` is OK (with `status.code` set to + *** For the primary, `status` is OK (with `status.code` set to `google.rpc.OK`). - * For all backup controllers, `status` is set to non-OK (with + *** For all backup controllers, `status` is set to non-OK (with `status.code` set to `google.rpc.ALREADY_EXISTS`). - * Otherwise, if there is no primary currently, for all backup controllers, + ** Otherwise, if there is no primary currently, for all backup controllers, `status` is set to non-OK (with `status.code` set to `google.rpc.NOT_FOUND`). Note that on primary client changes with outstanding `Write` request, some notifications might be delayed, see the -[previous section](#sec-arbitration-updates) for details. +xref:sec-arbitration-updates[previous section] for details. -# The P4Info Message +== The P4Info Message The purpose of P4Info was described under -[Reference Architecture](#sec-reference-architecture). +xref:sec-reference-architecture[Reference Architecture]. Here we describe the various components. -## Common Messages +=== Common Messages These messages appear nested within many other messages. -### `Documentation` Message +[#sec-documentation-message] +==== `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. -~ Begin Proto +[source,protobuf] +---- message Documentation { // A brief description of something, e.g. one sentence string brief = 1; @@ -985,14 +808,16 @@ message Documentation { // Multiline is accepted. Markup format (if any) is TBD. string description = 2; } -~ End Proto +---- -### `Preamble` Message +[#sec-preamble-message] +==== `Preamble` Message The preamble serves as the "descriptor" for each entity and contains the unique instance ID, name, alias, annotations and documentation. -~ Begin Proto +[source,protobuf] +---- 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 @@ -1022,39 +847,37 @@ message Preamble { Documentation doc = 5; repeated StructuredAnnotation structured_annotations = 6; } -~ End Proto +---- -### Annotating P4 Entities with `Documentation` { #sec-annotating-p4-entities-with-documentation} +[#sec-annotating-p4-entities-with-documentation] +==== Annotating P4 Entities with `Documentation` P4 entities may be annotated using the following annotations: -~ Begin p4example +[source,p4] +---- @brief(string...) @description(string...) -~ End p4example - +---- Attaching either or both of these annotations to an entity will generate a -P4Info [Documentation Message](#sec-documentation-message), which in turn will -appear in the [Preamble Message](#sec-preamble-message) for the entity. - +P4Info xref:sec-documentation-message[Documentation Message], which in turn will +appear in the xref:sec-preamble-message[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: - -~ Begin p4example +[source,p4] +---- @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 { ... } -~ End p4example - -### Structured Annotations +---- +==== Structured Annotations -P4 supports both unstructured and structured annotations [@P4Annotations]. +P4 supports both unstructured and structured annotations cite:[P4Annotations]. Unstructured annotations of the form `MyAnno1` or `MyAnno2(body-content)` can either be empty, or contain free-form content; anything between the pair of matched parentheses is legal. Conversely, structured annotations of the form @@ -1063,19 +886,15 @@ which allows declaring key-value lists or expression lists. Both unstructured and structured annotations may be used simultaneously on a P4 element and P4Info supports this. -The annotations described up to this point, ⪚ `@brief()`, have all been +The annotations described up to this point, e.g. `@brief()`, have all been unstructured annotations, or simply annotations. These are represented in -P4Info as `repeated string annotations` fields in the various `message`s. -Similarly, structured annotations are represented in `repeated -StructuredAnnotation structured_annotations` fields which are siblings to the -unstructured `annotations`. The `structured_annotations` contain parsed -representations of the original annotation source. This parsing includes -expression-evaluation, so the resulting P4Info may contain a simplified -replica of the original structured annotations. +P4Info as `repeated string annotations` fields in the various `messages`. +Similarly, structured annotations are represented in `repeated StructuredAnnotation structured_annotations` fields which are siblings to the unstructured `annotations`. +The `structured_annotations` contain parsed representations of the original annotation source. This parsing includes expression-evaluation, so the resulting P4Info may contain a simplified replica of the original structured annotations. The structured annotation messages are defined in p4types.proto. - -~ Begin Proto +[source,protobuf] +---- message KeyValuePair { string key = 1; Expression value = 2; @@ -1106,7 +925,7 @@ message StructuredAnnotation { // Optional. Location of the '@' symbol of this annotation in the source code. SourceLocation source_location = 4; } -~ End Proto +---- The `StructuredAnnotation` message can represent either a `KeyValuePairList` or an `ExpressionList`. @@ -1122,47 +941,51 @@ conversions. The following invariants hold: -1. For any P4 entity, there are no two `StructuredAnnotation`s that have the +. For any P4 entity, there are no two `StructuredAnnotation`s that have the same name. -2. Within a `KeyValuePairList`, there are no two `KeyValuePair`s that have the +. Within a `KeyValuePairList`, there are no two `KeyValuePair`s that have the same `key.` -#### Structured Annotation Examples +===== Structured Annotation Examples We omit the `source_location` field in the following examples. **Empty Expression List** - -~ Begin P4Example +[source,p4] +---- @Empty[] table t { ... } -~ End P4Example +---- The generated P4Info will contain the following. -~ Begin Proto +[source,protobuf] +---- structured_annotations { name: "Empty" } -~ End Proto +---- **Mixed Expression List** -~ Begin P4Example +[source,p4] +---- #define TEXT_CONST "hello" #define NUM_CONST 6 @MixedExprList[1,TEXT_CONST,true,1==2,5+NUM_CONST] table t { ... } -~ End P4Example +---- The generated P4Info will contain: -~ Begin Proto +[source,protobuf] +[%unbreakable] +---- structured_annotations { name: "MixedExprList" expression_list { @@ -1183,20 +1006,22 @@ structured_annotations { } } } -~ End Proto +---- **kvList of Mixed Expressions** -~ Begin P4Example +[source,p4] +---- @MixedKV[label="text", my_bool=true, int_val=2*3] table t { ... } -~ End P4Example +---- The generated P4Info will contain: -~ Begin Proto +[source,protobuf] +---- structured_annotations { name: "MixedKV" kv_pair_list { @@ -1220,14 +1045,15 @@ structured_annotations { } } } -~ End Proto +---- -### `SourceLocation` Message +==== `SourceLocation` Message A source location describes a location within a *.p4*-source file. The `SourceLocation` message is defined in p4types.proto as follows: -~ Begin Proto +[source,protobuf] +---- // Location of code relative to a given source file. message SourceLocation { // Path to the source file (absolute or relative to the working directory). @@ -1236,7 +1062,7 @@ message SourceLocation { int32 line = 2; int32 column = 3; } -~ End Proto +---- We provide source locations for structured and unstructured annotations. This information may be useful when annotations require further parsing or @@ -1258,8 +1084,8 @@ be found in the following place: an optional field `SourceLocation source_location` holding its source location, if present. - -## `PkgInfo` Message +[#sec-pkginfo-message] +=== `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 @@ -1268,7 +1094,8 @@ 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. -~ Begin Proto +[source,protobuf] +---- // Can be used to manage multiple P4 packages. message PkgInfo { // a definitive name for this configuration, e.g. switch.p4_v1.0 @@ -1297,10 +1124,10 @@ message PkgInfo { // reject the P4Info when used with a SetForwardingPipelineConfigRequest. PlatformProperties platform_properties = 11; } -~ End Proto - +---- where the `PlatformProperties` message looks as follows: -~ Begin Proto +[source,protobuf] +---- // Used to describe the required properties of the underlying platform. message PlatformProperties { // The minimum number of multicast entries (i.e. multicast groups) that the @@ -1314,14 +1141,15 @@ message PlatformProperties { // Must be no larger than `multicast_group_table_total_replicas`. int32 multicast_group_table_max_replicas_per_entry = 3; } -~ End Proto - -### Annotating P4 code with PkgInfo { #sec-annotating-p4-code-with-pkginfo} +---- +[#sec-annotating-p4-code-with-pkginfo] +==== Annotating P4 code with PkgInfo A P4 program's `PkgInfo` may be declared using one or more of the following annotations, attached to the `main` block only: -~ Begin p4example +[source,p4] +---- @pkginfo(key=value) @pkginfo(key=value[,key=value,...]) @brief("A brief description") @@ -1331,7 +1159,7 @@ description") @another_custom_annotation(...) @platform_property(key=value) @platform_property(key=value[,key=value,...]) -~ End p4example +---- Above we see several different types of annotations: @@ -1368,7 +1196,7 @@ Above we see several different types of annotations: Declaring one or more of these annotations on `main` will generate a single corresponding `PkgInfo` message in the P4Info as described in -[PkgInfo Message](#sec-pkginfo-message). +xref:sec-pkginfo-message[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, @@ -1376,7 +1204,8 @@ 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. -~ Begin p4example +[source,p4] +---- @pkginfo(name="switch.p4",version="2") @pkginfo(organization="p4.org") @pkginfo(contact="info@p4.org") @@ -1388,66 +1217,63 @@ Built for data-center profile.") @my_annotation2(...) // Not well-known, this will appear in PkgInfo annotations PSA_Switch(IgPipeline, PacketReplicationEngine(), EgPipeline, BufferingQueueingEngine()) main; -~ End p4example +---- -## ID Allocation for P4Info Objects { #sec-id-allocation} +[#sec-id-allocation] +=== 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 -(⪚ table, action, counter, ...). The most significant 8 bits of the ID -encodes the object type (as per Table [#tab-mapping-p4-obj-ids]). The +(e.g. table, action, counter, ...). The most significant 8 bits of the ID +encodes the object type (as per <<#tab-mapping-p4-obj-ids>>). 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 (⪚ by the compiler) when allocating IDs. The remaining 24 bits 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 [#tab-format-p4-obj-ids] shows the ID +the scope of the P4Info message. <<#tab-format-p4-obj-ids>> shows the ID layout. -~ TableFigure { #tab-mapping-p4-obj-ids; \ - caption: "Mapping of P4Info object type to 8-bit ID prefix value"; \ - page-align: forcehere; } -|--------------------|----------------------------------------------------------------------| -| 8-bit prefix value | P4 object type | -+--------------------+----------------------------------------------------------------------+ -| 0x00 | Reserved (unspecified) | -| 0x01 | Action | -| 0x02 | Table | -| 0x03 | Value-set | -| 0x04 | Controller header (header type with `@controller_header` annotation) | -| 0x05...0x0f | Reserved (for future P4 built-in objects) | -| 0x10 | Reserved (start of PSA extern types) | -| 0x11 | PSA Action profiles / selectors | -| 0x12 | PSA Counter | -| 0x13 | PSA Direct counter | -| 0x14 | PSA Meter | -| 0x15 | PSA Direct meter | -| 0x16 | PSA Register | -| 0x17 | PSA Digest | -| 0x18...0x7f | Reserved (for future PSA extern types) | -| 0x80 | Reserved (start of vendor-specific extern types) | -| 0x81...0xfe | Vendor-specific extern types | -| 0xff | Reserved (max prefix value) | -+--------------------+----------------------------------------------------------------------+ -~ - -~ TableFigure { #tab-format-p4-obj-ids; \ - caption: "Format of P4Info object IDs"; \ - breakable: true; \ - page-align: forcehere; } -|----------------------------|------------------------------------------| -| MSB bit 31 ........ bit 24 | bit 23 ....................... bit 0 LSB | -+:--------------------------:+:----------------------------------------:+ -| Object type prefix | Generated suffix (⪚ by the compiler) | -+----------------------------+------------------------------------------+ -~ +//The row 0x04 as to be fixed as is in the madoko document +.Mapping of P4Info object type to 8-bit ID prefix value +[width=90%, cols="2", grid=cols,align=center, options=header, unbreakable] +[#tab-mapping-p4-obj-ids] +|=== +| 8-bit prefix value | P4 object type +| 0x00 | Reserved (unspecified) +| 0x01 | Action +| 0x02 | Table +| 0x03 | Value-set +| 0x04 | Controller header (header type with `@controller_header` annotation) +| 0x05...0x0f | Reserved (for future P4 built-in objects) +| 0x10 | Reserved (start of PSA extern types) +| 0x11 | PSA Action profiles / selectors +| 0x12 | PSA Counter +| 0x13 | PSA Direct counter +| 0x14 | PSA Meter +| 0x15 | PSA Direct meter +| 0x16 | PSA Register +| 0x17 | PSA Digest +| 0x18...0x7f | Reserved (for future PSA extern types) +| 0x80 | Reserved (start of vendor-specific extern types) +| 0x81...0xfe | Vendor-specific extern types +| 0xff | Reserved (max prefix value) +|=== + +.Format of P4Info object IDs +[.center,cols="2",width=70%, grid=cols,align=center, options=header, unbreakable] +[#tab-format-p4-obj-ids] +|=== +| MSB bit 31 ........ bit 24 | bit 23 ....................... bit 0 LSB +| Object type prefix | Generated suffix (e.g. by the compiler) +|=== 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 -[#tab-exmpl-p4-obj-ids]). The compiler must honor the `@id` annotations when +P4 program source by annotating the object with `@id` (see +<<#tab-exmpl-p4-obj-ids>>). 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 (&ie; if the P4 +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 @@ -1458,21 +1284,18 @@ the 8-bit prefix with a non-zero value, in which case the compiler will give an error if the 8-bit prefix does not contain the correct value, or leave it as is if it is correct. -~ TableFigure { #tab-exmpl-p4-obj-ids; \ - caption: "Example of statically-assigned P4Info object IDs"; \ - page-align: forcehere; } -|--------------------------------|------------------------------------------------------------| -| P4 declaration(s) | Compiler-allocated ID(s) | -+---{width:7cm}------------------+------------------------------------------------------------+ -| `@id(0x12ab34) table tA...` | 0x0212ab34 | -+--------------------------------+------------------------------------------------------------+ -| `@id(0x12ab34) table tA...` | **Error**(same ID suffixes for 2 objects of the same type) | -| `@id(0x12ab34) table tB...` | | -+--------------------------------+------------------------------------------------------------+ -| `@id(0x12ab34) table tA...` | 0x0212ab34 | -| `@id(0x12ab34) action act1...` | 0x0112ab34 | -+--------------------------------+------------------------------------------------------------+ -~ +//The second row as to be fixed as is in the madoko document +.Example of statically-assigned P4Info object IDs +[cols="2",width=80%, align=center, options=header, unbreakable] +[#tab-exmpl-p4-obj-ids] +|=== +| P4 declaration(s) | Compiler-allocated ID(s) +| `@id(0x12ab34) table tA...` | 0x0212ab34 +| `@id(0x12ab34) table tA...` | **Error**(same ID suffixes for 2 objects of the same type) +| `@id(0x12ab34) table tB...` | +| `@id(0x12ab34) table tA...` | 0x0212ab34 +| `@id(0x12ab34) action act1...` | 0x0112ab34 +|=== The `@id` annotation can also be used to choose the ID for match fields, action parameters, and packet metadata. In this case, there is no 8-bit prefix @@ -1480,9 +1303,9 @@ and the programmer is free to choose any 32-bit number. The compiler must fail if the IDs chosen by the programmer are not unique (within a table, action, or header, respectively). -## P4Info Objects +=== P4Info Objects -### `Table` +==== `Table` Table messages are used to specify all possible match-action tables exposed to a control plane. This message contains the following fields: @@ -1493,7 +1316,7 @@ control plane. This message contains the following fields: 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 + ** 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, if the P4Info message was generated from a P4 @@ -1502,41 +1325,39 @@ control plane. This message contains the following fields: can either choose the IDs using the `@id` annotation, or let the compiler choose them. - * `name`, the string representing the name of this `MatchField`. + ** `name`, the string representing the name of this `MatchField`. - * `annotations`, a repeated field of strings, each one representing a P4 + ** `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. + ** `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 + ** `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 + *** `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 + *** `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. + ** `doc`, a `Documentation` message describing this match field. - * `type_name`, which indicates whether the match field has a [user-defined - type](#sec-user-defined-types); this is useful for - [translation](#sec-psa-metadata-translation). + ** `type_name`, which indicates whether the match field has a xref:sec-user-defined-types[user-defined type]; this is useful for xref:sec-psa-metadata-translation[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: + ** `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` [@P4ActionAnnotations]. `TABLE_ONLY` + `@tableonly` and `@defaultonly` cite:[P4ActionAnnotations]. `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 + ** `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 @@ -1550,11 +1371,10 @@ control plane. This message contains the following fields: specified arguments when the table does not match. If no explicit default action is set, the identifier of this field will default to the id of the `NoAction` action. - * `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 (⪚ a PSA `ActionProfile` or `ActionSelector` + 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 @@ -1567,17 +1387,17 @@ control plane. This message contains the following fields: * `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 P4~16~ language specification for details - [@P4TableProperties]. + cite:[P4TableProperties]. * `idle_timeout_behavior`, which describes the behavior of the data plane when the idle timeout of a table entry expires (see - [Idle-Timeout](#sec-idle-timeout) section). Value can be any of the + xref:sec-idle-timeout[Idle-Timeout] section). Value can be any of the `IdleTimeoutBehavior` enum: - * `NO_TIMEOUT` (default value), which means that idle timeout is not + ** `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 + ** `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](#sec-table-idle-timeout-notification)). + xref:sec-table-idle-timeout-notification[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. @@ -1588,11 +1408,11 @@ control plane. This message contains the following fields: `const entries` properties, and there is at least one entry in the list. -* `other_properties`, an `Any` Protobuf message [@ProtoAny] to embed - architecture-specific table properties [@P4TableProperties] which are not part +* `other_properties`, an `Any` Protobuf message cite:[ProtoAny] to embed + architecture-specific table properties cite:[P4TableProperties] which are not part of the core P4 language or of the PSA architecture. -### `Action` +==== `Action` `Action` messages are used to specify all possible actions of all match-action tables. @@ -1605,23 +1425,22 @@ The `Action` message defines the following fields: 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 + ** `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, 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 in the P4 action declaration. The programmer can either choose the IDs using the `@id` annotation, or let the compiler choose them. - * `name`, the string representing the name of this parameter. - * `annotations`, a repeated field of strings, each one representing a P4 + ** `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](#sec-user-defined-types); this is useful for - [translation](#sec-psa-metadata-translation). + ** `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 xref:sec-user-defined-types[user-defined type]; this is useful for xref:sec-psa-metadata-translation[translation]. -### `ActionProfile` { #sec-p4info-action-profile} +[#sec-p4info-action-profile] +==== `ActionProfile` `ActionProfile` messages are used to specify all available instances of Action Profile and Action Selector PSA externs. @@ -1671,10 +1490,10 @@ The `ActionProfile` message includes the following fields: * `selector_size_semantics`, a oneof for Action Selectors that specifies how `size` and `max_group_size` are interpreted. It can be either: - * `sum_of_weights`, indicating that `size` and `max_group_size` represent + ** `sum_of_weights`, indicating that `size` and `max_group_size` represent the maximum sum of weights that can be present across all selector groups and within a single selector group respectively. - * `sum_of_members`, indicating that `size` and `max_group_size` represent + ** `sum_of_members`, indicating that `size` and `max_group_size` represent the maximum number of members that can be present across all selector groups and within a single selector group respectively, irrespective of their weight. The `SumOfMembers` message used to represent this value also @@ -1683,16 +1502,15 @@ The `ActionProfile` message includes the following fields: allowed for weight. - PSA programs can use the `@selector_size_semantics` annotation with one of - `sum_of_weights` or `sum_of_members` to specify this value for Action - Selectors. In the `sum_of_members` case, the `@max_member_weight` annotation - can be used to specify `max_member_weight`. Unless otherwise specified, the - value of `selector_size_semantics` should default to `sum_of_weights`. - However, an unset `selector_size_semantics` should also be treated as - `sum_of_weights` for backwards compatibility in Action Selectors. In Action - Profiles, this value must be unset. +PSA programs can use the `@selector_size_semantics` annotation with one of +`sum_of_weights` or `sum_of_members` to specify this value for Action Selectors. In the `sum_of_members` case, the `@max_member_weight` annotation +can be used to specify `max_member_weight`. Unless otherwise specified, the +value of `selector_size_semantics` should default to `sum_of_weights`. +However, an unset `selector_size_semantics` should also be treated as +`sum_of_weights` for backwards compatibility in Action Selectors. In Action +Profiles, this value must be unset. -### `Counter` & `DirectCounter` +==== `Counter` & `DirectCounter` `Counter` and `DirectCounter` messages are used to specify all possible instances of Counter and Direct Counter PSA externs respectively. Both externs @@ -1716,10 +1534,10 @@ Both `Counter` and `DirectCounter` messages share the following fields: 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. + ** `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 @@ -1728,12 +1546,11 @@ by this counter array. Conversely, the `DirectCounter` message contains a 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](#sec-user-defined-types). This is useful for -[translation](#sec-psa-metadata-translation). The underlying built-in type must -be a fixed-width unsigned bitstring (`bit`). +field, which indicates whether the index has a xref:sec-user-defined-types[user-defined +type]. This is useful for xref:sec-psa-metadata-translation[translation].The underlying built-in type must be a fixed-width unsigned bitstring (`bit`). -### `Meter` & `DirectMeter` +[#sec-meter-directmeter] +==== `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 @@ -1743,7 +1560,7 @@ 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, - ⪚ to set the rate threshold. + 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. @@ -1757,31 +1574,34 @@ Both `Meter` and `DirectMeter` messages share the following fields: this meter extern instance. The `MeterSpec` message is used to describe the meter unit and the meter type. The meter unit can be any of the `MeterSpec.Unit` enum values: - * `UNSPECIFIED`: reserved value. - * `BYTES`, which signifies that this meter can be configured with rates + ** `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. + ** `PACKETS`, for rates expressed in packets/second. - The meter type can be any of the `MeterSpec.Type` enum values: - * `TWO_RATE_THREE_COLOR`: This is the *Two Rate Three Color Marker* (trTCM) - defined in RFC 2698 [@RFC2698]. This is the standard P4Runtime meter type +The meter type can be any of the `MeterSpec.Type` enum values: + + ** `TWO_RATE_THREE_COLOR`: This is the *Two Rate Three Color Marker* (trTCM) + defined in RFC 2698 cite:[RFC2698]. This is the standard P4Runtime meter type and allows meters to use two rates to split packets into three potential colors: GREEN, YELLOW, or RED. This mode is the default, but can also be set explicitly in a P4 program by adding the `@two_rate_three_color` annotation to the meter definition. For example, in a V1Model P4 program, we might define a trTCM direct meter as follows: - ~ Begin P4Example - @two_rate_three_color - direct_meter(MeterType.bytes) my_meter; - ~ End P4Example - * `SINGLE_RATE_THREE_COLOR`: This is the *Single Rate Three Color Marker* - (srTCM) defined in RFC 2697 [@RFC2697]. This allows meters to use one rate ++ +[source,p4] +---- +@two_rate_three_color +direct_meter(MeterType.bytes) my_meter; +---- + ** `SINGLE_RATE_THREE_COLOR`: This is the *Single Rate Three Color Marker* + (srTCM) defined in RFC 2697 cite:[RFC2697]. This allows meters to use one rate and an Excess Burst Size (EBS) to split packets into three potential colors: GREEN, YELLOW, or RED. In a P4 program, this mode can be set by adding the `@single_rate_three_color` annotation to the meter definition. - * `SINGLE_RATE_TWO_COLOR`: This is a simplified version of RFC 2697 - [@RFC2697], and the `SINGLE_RATE_THREE_COLOR` mode above. + ** `SINGLE_RATE_TWO_COLOR`: This is a simplified version of RFC 2697 + cite:[RFC2697], and the `SINGLE_RATE_THREE_COLOR` mode above. `SINGLE_RATE_TWO_COLOR` restricts meters to use only a single rate specified by the Committed Information Rate (CIR) and Committed Burst Size (CBS) to mark packets GREEN or RED. In a P4 program, this mode can be set @@ -1794,12 +1614,11 @@ 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](#sec-user-defined-types). This is useful for -[translation](#sec-psa-metadata-translation). The underlying built-in type must -be a fixed-width unsigned bitstring (`bit`). +field, which indicates whether the index has a xref:sec-user-defined-types[user-defined +type]. This is useful for xref:sec-psa-metadata-translation[translation]. The underlying built-in type must be a fixed-width unsigned bitstring (`bit`). -### `ControllerPacketMetadata` { #sec-controller-packet-meta} +[#sec-controller-packet-meta] +==== `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 @@ -1830,29 +1649,30 @@ message contains the following fields: * `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 + ** `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. 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. The P4 programmer can either choose the IDs using the `@id` annotation, or let the compiler choose them. - * `name`, a string representation of the name of this metadata. If the + ** `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 + ** `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](#sec-user-defined-types); this is useful for - [translation](#sec-psa-metadata-translation). + ** `bitwidth`, an `int32` representing the size in bits of this metadata. + ** `type_name`, which indicates whether the metadata field has a + xref:sec-user-defined-types[user-defined type]; this is useful for + xref:sec-psa-metadata-translation[translation]. As an example, consider the following snippet of a P4 program where controller headers are specified and we show the corresponding `ControllerPacketMetadata` messages. -~ Begin P4Example +[source,p4] +---- @controller_header("packet_out") header PacketOut_t { bit<9> egress_port; /* suggested port where the packet @@ -1867,13 +1687,15 @@ header PacketIn_t { bit<1> is_clone; /* 1 if this is a clone of the original packet */ } -~ End P4Example -~ Begin Prototext +---- +[source,protobuf] +[%unbreakable] +---- controller_packet_metadata { preamble { id: 2868916615 name: "packet_out" - annotations: "@controller_header(\"packet_out\")" + annotations: "@controller_header(\"packet_out"\)" } metadata { id: 1 @@ -1891,7 +1713,7 @@ controller_packet_metadata { preamble { id: 2868941301 name: "packet_in" - annotations: "@controller_header(\"packet_in\")" + annotations: "@controller_header(\"packet_in"\)" } metadata { id: 1 @@ -1904,21 +1726,20 @@ controller_packet_metadata { bitwidth: 1 } } -~End Prototext - +---- 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. -### `ValueSet` +==== `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 P4~16~ -specification [@P4ValueSets]. +specification cite:[P4ValueSets]. The `ValueSet` message defines the following fields: @@ -1939,10 +1760,10 @@ 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`, -`tuple`, or `struct` [@P4SelectExpr]. The rest of this section looks at all 3 of +`tuple`, or `struct` cite:[P4SelectExpr]. 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`, `match` will include exactly one +. If the type parameter is `bit`, `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): @@ -1950,11 +1771,14 @@ these cases and gives an example `ValueSet` message when appropriate. * `bitwidth`: set to the value of `W` * `match_type`: set to `EXACT` -~ Begin P4Example +[source,p4] +---- @id(1) value_set >(4) pvs; select (hdr.f8) { /* ... */ } -~ End P4Example -~ Begin Prototext +---- + +[source,protobuf] +---- value_sets { preamble { id: 0x03000001 @@ -1967,14 +1791,14 @@ value_sets { } size: 4 } -~ End Prototext +---- -2. If the type parameter is a `tuple`, this version of P4Runtime does not +. 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. -3. If the type parameter is a `struct`, this version of P4Runtime requires that +. If the type parameter is a `struct`, this version of P4Runtime requires that all the fields of the struct be of type `bit` (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 @@ -1991,15 +1815,14 @@ value_sets { 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](#sec-user-defined-types); this is useful for - [translation](#sec-psa-metadata-translation). + * `type_name`, which indicates whether the struct field has a xref:sec-user-defined-types[user-defined type]; this is useful for xref:sec-psa-metadata-translation[translation]. * `match`: by default `match_type` is set to `EXACT`; the P4 programmer can specify a different match type by using the `@match` annotation - [@P4SelectExpr]. + cite:[P4SelectExpr]. * `doc`: documentation associated with the struct field. -~ Begin P4Example +[source,p4] +---- struct match_t { @id(1) bit<8> f8; @id(2) @match(ternary) bit<16> f16; @@ -2007,8 +1830,9 @@ struct match_t { } @id(1) value_set(4) pvs; select ({ hdr.f8, hdr.f16, hdr.f32 }) { /* ... */ } -~ End P4Example -~ Begin Prototext +---- +[source,protobuf] +---- value_sets { preamble { id: 0x03000001 @@ -2034,20 +1858,20 @@ value_sets { } size: 4 } -~ End Prototext +---- In the above example, the `@id` annotations on the P4 struct fields are optional. When omitted, the compiler will choose appropriate IDs. Although not mentioned in the P4 specification, P4Runtime also supports the -cases where the Value Set type parameter is a [user-defined -type](#sec-user-defined-types) that resolves to a `bit`, or a `struct` where -one or more fields is a [user-defined type](#sec-user-defined-types) that +cases where the Value Set type parameter is a xref:sec-user-defined-types[user-defined +type]that resolves to a `bit`, or a `struct` where +one or more fields is a xref:sec-user-defined-types[user-defined type] that resolves to a `bit`. For each `MatchField` that corresponds to a user-defined -type, the `type_name` field must be set to the appropriate value (&ie; the name +type, the `type_name` field must be set to the appropriate value (i.e. the name of the type). -### `Register` +==== `Register` `Register` messages are used to specify all possible instances of Register PSA externs. @@ -2062,18 +1886,17 @@ The `Register` message defines the following fields: 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](#sec-representation-of-arbitrary-p4-types)). + using a `P4DataTypeSpec` message (see section on xref:sec-representation-of-arbitrary-p4-types[Representation of ArbitraryP4 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](#sec-user-defined-types). This is useful for - [translation](#sec-psa-metadata-translation). The underlying built-in type + xref:sec-user-defined-types[user-defined type]. This is useful for + xref:sec-psa-metadata-translation[translation]. The underlying built-in type must be a fixed-width unsigned bitstring (`bit`). -### `Digest` +==== `Digest` `Digest` messages are used to specify all possible instances of Packet Digest PSA externs. @@ -2093,10 +1916,10 @@ The `Digest` message defines the following fields: 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](#sec-representation-of-arbitrary-p4-types)). + notification using a `P4DataTypeSpec` message (see section on xref:sec-representation-of-arbitrary-p4-types[Representation of Arbitrary P4 Types]). -### `Extern` { #sec-p4info-extern} +[#sec-p4info-extern] +==== `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 @@ -2105,8 +1928,8 @@ 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](#sec-id-allocation) `[0x81, 0xfe]`. Note that this value does not need + extern type in the context of the architecture. It must be in the + xref:sec-id-allocation[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. @@ -2120,30 +1943,30 @@ the following fields: * `preamble`, a `Preamble` message with the ID, name, and alias of this digest instance. - * `info`, an `Any` Protobuf message [@ProtoAny] which is used to embed + * `info`, an `Any` Protobuf message cite:[ProtoAny] 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](#sec-extending-p4runtime) for more - information. + separate architecture-specific Protobuf file. See section on xref:sec-extending-p4runtime[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. -## Support for Arbitrary P4 Types with P4TypeInfo +=== Support for Arbitrary P4 Types with P4TypeInfo -See section on [Representation of Arbitrary P4 -Types](#sec-representation-of-arbitrary-p4-types). +See section on xref:sec-representation-of-arbitrary-p4-types[Representation of Arbitrary P4 +Types]. -# P4 Forwarding-Pipeline Configuration { #sec-p4-fwd-pipe-config} +[#sec-p4-fwd-pipe-config] +== 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: -~ Begin Proto +[source,protobuf] +---- message ForwardingPipelineConfig { config.P4Info p4info = 1; bytes p4_device_config = 2; @@ -2152,7 +1975,7 @@ message ForwardingPipelineConfig { } Cookie cookie = 3; } -~ End Proto +---- 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. @@ -2171,14 +1994,17 @@ 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. -# General Principles for Message Formatting {#sec-message-formatting-principles} +[#sec-message-formatting-principles] +== General Principles for Message Formatting -## Default-valued Fields {#sec-default-valued-fields} +[#sec-default-valued-fields] +=== Default-valued Fields There is a subtle distinction between the treatment of default-valued scalar fields vs default-valued message fields in P4Runtime. -### Set / Unset Scalar Fields +<<< +==== Set / Unset Scalar Fields In Protobuf version 3 (*proto3*), the default value of scalar fields is `0` for numeric types such as `int32`, and the empty string `""` for string types @@ -2195,10 +2021,10 @@ In contrast to scalar fields, note that for message fields, we often do make a distinction between an unset message field vs a message field set to its default value, see the next section. -### Set / Unset Message Fields +==== Set / Unset Message Fields In Protobuf version 3 (*proto3*), the default value for a message field is -"unset" [@ProtoDefaults]. An application, such as the P4Runtime client or +"unset" cite:[ProtoDefaults]. 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 @@ -2211,7 +2037,9 @@ 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: -~ Begin CPP + +[source,c++] +---- auto *counter_entry = ... if (counter_entry->has_index()) { auto index = counter_entry->index().index(); @@ -2219,24 +2047,29 @@ if (counter_entry->has_index()) { } else { read_all_entries(counter_entry->id()); } -~ End CPP +---- -1. Reading a single counter entry at index 0 in the counter array with id -``: +. Reading a single counter entry at index 0 in the counter array with id ``: * Here is the C++ client code: -~ Begin CPP + +[source,c++] +---- p4::v1::CounterEntry entry; entry.set_counter_id(); entry.mutable_index(); // The above line sets the index field; it is equivalent to: // auto *index = entry.mutable_index(); // index->set_index(0); -~ End CPP +---- + * Here is the corresponding Protobuf message in text format: -~ Begin Prototext -counter_id: + +[source,protobuf] +---- +counter_id: "id_value" index {} -~ End Prototext +---- + * **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 @@ -2244,40 +2077,47 @@ index {} 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 +[start=2] +. Reading all counter entries by leaving the `index` field unset * Here is the C++ client code: -~ Begin CPP + +[source,c++] +---- p4::v1::CounterEntry entry; entry.set_counter_id(); -~ End CPP +---- + * Here is the corresponding Protobuf message in text format: -~ Begin Prototext -counter_id: -~ End Prototext + +[source,protobuf] +---- +counter_id: "id_value" +---- + * **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. -## Read-Write Symmetry { #sec-read-write-symmetry} +[#sec-read-write-symmetry] +=== Read-Write Symmetry 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 (with the exception of parts -of the response known to be data plane volatile, as explained in section on -[Data plane volatile objects](#sec-data-plane-volatile-objects)). -Consider the following pseudocode as an +of the response known to be data plane volatile, as explained in <<#sec-data-plane-volatile-objects>>). Consider the following pseudocode as an example: -~ Begin Pseudo +[source,python] +---- intended_value = value status = server.write(intended_value, p4_entity) observed_value = server.read(p4_entity) assert(intended_value == observed_value) -~ End Pseudo +---- To ensure read-write symmetry, the rest of this document tries to offer canonical representations for various data types, but this principle should be @@ -2294,12 +2134,13 @@ 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 [@ProtoMessageDifferencer] included in the Protobuf +`MessageDifferencer` class cite:[ProtoMessageDifferencer] 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. -### Data plane volatile objects { #sec-data-plane-volatile-objects } +[#sec-data-plane-volatile-objects] +==== Data plane volatile objects An exception to read-write symmetry are objects whose contents or fields can change by the action of the data plane alone, even if no @@ -2312,12 +2153,12 @@ modify objects in the data plane via an `Update` message. For each, a description is given of the parts of that entity that are data plane volatile. -#### ExternEntry +===== ExternEntry Data plane volatility depends upon the definition of the extern and its control plane API. -#### TableEntry +===== TableEntry For a table with a direct counter associated with it, the `counter_data` field of a `TableEntry` can be modified by the data plane when packets @@ -2327,12 +2168,12 @@ For a table with a direct meter associated with it, the `meter_counter_data` field of a `TableEntry` can be modified by the data plane when packets match the entry. -For a PSA [@PSA] table with property `psa_idle_timeout` equal to +For a PSA cite:[PSA] table with property `psa_idle_timeout` equal to `PSA_IdleTimeout_t.NOTIFY_CONTROL`, the data plane can modify the `elapsed_ns` field of a `TableEntry` when _no_ packets match the entry for an implementation-specific amount of time. -For a PNA [@PNA] table with property `pna_idle_timeout` equal to +For a PNA cite:[PNA] table with property `pna_idle_timeout` equal to `PNA_IdleTimeout_t.NOTIFY_CONTROL` or `PNA_IdleTimeout_t.AUTO_DELETE` the data plane can modify the `elapsed_ns` field of a `TableEntry` when _no_ packets cause the extern function `restart_expire_timer` to @@ -2347,74 +2188,75 @@ and the action calls the `set_entry_expire_time` extern function (or any of the other extern functions defined to have an effect similar to calling `set_entry_expire_time`). -For a PNA [@PNA] table with the property `add_on_miss` equal to `true` +For a PNA cite:[PNA] table with the property `add_on_miss` equal to `true` the data plane can insert new entries into the table without any controller's involvement. -For a PNA [@PNA] table with the property `pna_idle_timeout` equal to +For a PNA cite:[PNA] table with the property `pna_idle_timeout` equal to `PNA_IdleTimeout_t.AUTO_DELETE`, the data plane can delete existing entries from the table without any controller's involvement. -#### ActionProfileMember +===== ActionProfileMember Not data plane volatile in any architectures defined by P4.org specifications. -#### ActionProfileGroup +===== ActionProfileGroup Not data plane volatile in any architectures defined by P4.org specifications. The `watch_port` feature does affect how action selectors behave while processing packets, but this feature does not affect what a P4Runtime client sees when it reads the configuration. -#### MeterEntry +===== MeterEntry The field `counter_data` is modified by the data plane when the corresponding meter is updated in the data plane. -#### DirectMeterEntry +===== DirectMeterEntry The field `counter_data` is modified by the data plane when the corresponding meter is updated in the data plane. -#### CounterEntry +===== CounterEntry The field `data` is modified by the data plane when the corresponding counter is updated in the data plane. -#### DirectCounterEntry +===== DirectCounterEntry The field `data` is modified by the data plane when the corresponding counter is updated in the data plane. -#### PacketReplicationEngineEntry +===== PacketReplicationEngineEntry Not data plane volatile in any architectures defined by P4.org specifications. -#### ValueSetEntry +===== ValueSetEntry Not data plane volatile in any architectures defined by P4.org specifications. -#### RegisterEntry +===== RegisterEntry The field `data` can be modified by the data plane when the corresponding register entry is updated in the data plane. -#### DigestEntry +===== DigestEntry Not data plane volatile in any architectures defined by P4.org specifications. -## Bytestrings { #sec-bytestrings} +[#sec-bytestrings] +=== 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`) or signed (`int`), 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 +`bytes` Protobuf type. The correct bitwidth — as per the P4 program — of each integer variable exposed through P4Runtime is specified in the P4Info message. @@ -2424,7 +2266,7 @@ 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 supports [read-write symmetry](#sec-read-write-symmetry). +* It supports xref:sec-read-write-symmetry[read-write symmetry]. * It helps facilitate non-disruptive P4 program updates. @@ -2446,26 +2288,26 @@ range. In the P4Runtime API version 1.0 (including minor version revisions), values of table key fields, action parameters, and fields in packet-in and packet-out -headers between a device and the controller (see [#sec-controller-packet-meta]), +headers between a device and the controller (see <<#sec-controller-packet-meta>>), may not be of type `int`. The rules for encoding signed values thus only -apply to messages of type `P4Data` (see [#sec-p4data-in-p4runtime-proto]). +apply to messages of type `P4Data` (see <<#sec-p4data-in-p4runtime-proto>>). For a value of type `bit`, the fewest number of bits required to represent -the integer value $V > 0$ is the smallest integer $A$ such that $V \leq 2^A - -1$. +the integer value latexmath:[$V > 0$] is the smallest integer latexmath:[$A$] such that latexmath:[$V \leq 2^A - 1$]. For a value of type `int`, the fewest number of bits required to represent -the integer value $V \neq 0$ in 2's complement form is the smallest integer $A$ -such that $-2^{A-1} \leq V \leq 2^{A-1} - 1$. +the integer value latexmath:[$V \neq 0$] in 2's complement form is the smallest integer latexmath:[$A$] +such that latexmath:[$-2^{A-1} \leq V \leq 2^{A-1} - 1$]. -As a special case, define that the value $V=0$ requires at least $A=1$ bit to +As a special case, define that the value latexmath:[$V=0$] requires at least latexmath:[$A=1$] bit to represent, regardless of whether it is signed or unsigned. -The shortest possible binary string for an integer $V$ that needs $A$ bits to +The shortest possible binary string for an integer latexmath:[$V$] that needs latexmath:[$A$] bits to represent it is computed as: -~ Begin CPP +[source,c++] +---- minimum_string_size = floor((A + 7) / 8) -~ End CPP +---- Binary strings with the byte length computed as `minimum_string_size` promote P4Runtime read-write symmetry in both client-to-server requests and @@ -2476,10 +2318,10 @@ Any additional bits in the bytes sent for an unsigned integer value (type `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`) -must be copies of the sign bit, &ie; 0 for non-negative values, or 1 for +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, &ie; 0 for non-negative values, or 0xff for negative values. In 2's +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. @@ -2506,53 +2348,52 @@ 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 (&ie; network) byte-order. +For all binary strings, P4Runtime uses big-endian (i.e. network) byte-order. For signed integer values (`int` P4 type), P4Runtime uses the same two's -complement bitwise representation as P4. Table [#tab-valid-bytestring-encoding] +complement bitwise representation as P4. <<#tab-valid-bytestring-encoding>> shows various examples of integer values that the server accepts as valid P4Runtime binary strings according to the criteria in the list above. -~ TableFigure { #tab-valid-bytestring-encoding; \ - caption: "Examples of Valid Bytestring Encoding"; } -|-----------|----------------|-------------------------|---------------------| -| P4 type | Integer value | P4Runtime binary string | Read-write symmetry | -+-----------+----------------+-------------------------+---------------------+ -| `bit<8>` | 99 (0x63) | `\x63` | yes | -| `bit<16>` | 99 (0x63) | `\x00\x63` | no | -| `bit<16>` | 99 (0x63) | `\x63` | yes | -| `bit<16>` | 12388 (0x3064) | `\x30\x64` | yes | -| `bit<16>` | 12388 (0x3064) | `\x00\x30\x64` | no | -| `bit<12>` | 99 (0x63) | `\x00\x63` | no | -| `bit<12>` | 99 (0x63) | `\x63` | yes | -| `bit<12>` | 99 (0x63) | `\x00\x00\x63` | no | -| `int<8>` | 99 (0x63) | `\x63` | yes | -| `int<8>` | -99 (-0x63) | `\x9d` | yes | -| `int<8>` | -99 (-0x63) | `\xff\x9d` | no | -| `int<12>` | -739 (-0x2e3) | `\xfd\x1d` | yes | -| `int<16>` | 0 (0x0) | `\x00\x00` | no | -| `int<16>` | 0 (0x0) | `\x00` | yes | -|-----------|----------------|-------------------------|---------------------| -~ - -Table [#tab-invalid-bytestring-encoding] shows some examples of invalid +.Examples of Valid Bytestring Encoding +[.center,cols="4",width=70%,align=center, options=header, unbreakable] +[#tab-valid-bytestring-encoding] +|=== +| P4 type | Integer value | P4Runtime binary string | Read-write symmetry +| `bit<8>` | 99 (0x63) | `\x63` | yes +| `bit<16>` | 99 (0x63) | `\x00\x63` | no +| `bit<16>` | 99 (0x63) | `\x63` | yes +| `bit<16>` | 12388 (0x3064) | `\x30\x64` | yes +| `bit<16>` | 12388 (0x3064) | `\x00\x30\x64` | no +| `bit<12>` | 99 (0x63) | `\x00\x63` | no +| `bit<12>` | 99 (0x63) | `\x63` | yes +| `bit<12>` | 99 (0x63) | `\x00\x00\x63` | no +| `int<8>` | 99 (0x63) | `\x63` | yes +| `int<8>` | -99 (-0x63) | `\x9d` | yes +| `int<8>` | -99 (-0x63) | `\xff\x9d` | no +| `int<12>` | -739 (-0x2e3) | `\xfd\x1d` | yes +| `int<16>` | 0 (0x0) | `\x00\x00` | no +| `int<16>` | 0 (0x0) | `\x00` | yes +|=== + +<<#tab-invalid-bytestring-encoding>> shows some examples of invalid P4Runtime binary strings: -~ TableFigure { #tab-invalid-bytestring-encoding; \ - caption: "Examples of Invalid Bytestring Encoding"; } -|-----------|-------------------------| -| P4 type | P4Runtime binary string | -+-----------+-------------------------+ -| `bit<8>` | `\x01\x63` | -| `bit<8>` | `empty string` | -| `bit<16>` | `\x01\x00\x63` | -| `bit<12>` | `\x10\x63` | -| `bit<12>` | `\x01\x00\x63` | -| `bit<12>` | `\x00\x40\x63` | -| `int<8>` | `\x00\x9d` | -| `int<12>` | `\x8d\x1d` | -| `int<16>` | `empty string` | -|-----------|-------------------------| -~ + +.Examples of Invalid Bytestring Encoding +[.center,cols="2",width=70%,align=center, options=header, unbreakable] +[#tab-invalid-bytestring-encoding] +|=== +| P4 type | P4Runtime binary string +| `bit<8>` | `\x01\x63` +| `bit<8>` | `empty string` +| `bit<16>` | `\x01\x00\x63` +| `bit<12>` | `\x10\x63` +| `bit<12>` | `\x01\x00\x63` +| `bit<12>` | `\x00\x40\x63` +| `int<8>` | `\x00\x9d` +| `int<12>` | `\x8d\x1d` +| `int<16>` | `empty string` +|=== As the preceding examples illustrate, a P4Runtime server must accept a wide assortment of possible binary string encodings for the same integer value. @@ -2564,8 +2405,7 @@ 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](#sec-read-write-symmetry) +the client and server must both remain aware of the xref:sec-read-write-symmetry[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. @@ -2577,49 +2417,50 @@ 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. -## Representation of Arbitrary P4 Types { #sec-representation-of-arbitrary-p4-types} +[#sec-representation-of-arbitrary-p4-types] +=== Representation of Arbitrary P4 Types -### Problem Statement +==== Problem Statement The P4~16~ language includes more complex types than just binary strings -[@P4ComplexTypes]. Most of these complex data types can be exposed to the +cite:[P4ComplexTypes]. 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 [#tab-p4-type-usage] shows the different +types can be very limiting. <<#tab-p4-type-usage>> shows the different P4~16~ types and how they are allowed to be used, as per the P4~16~ specification. -~ TableFigure { #tab-p4-type-usage; \ - caption: "P4 Type Usage"; } -|----------------|-----------------------|--------------|-----------------| -| | Container type ||| -| |-----------------------|--------------|-----------------| -| Element type | header | header_union | struct or tuple | -+:---------------+:----------------------+:-------------+:----------------+ -| `bit` | allowed | error | allowed | -| `int` | allowed | error | allowed | -| `varbit` | allowed | error | allowed | -| `int` | error | error | error | -| `void` | error | error | error | -| `error` | error | error | allowed | -| `match_kind` | error | error | error | -| `bool` | error | error | allowed | -| `enum` | allowed[^enum_header] | error | allowed | -| `header` | error | allowed | allowed | -| header stack | error | error | allowed | -| `header_union` | error | error | allowed | -| `struct` | error | error | allowed | -| `tuple` | error | error | allowed | -|----------------|-----------------------|--------------|-----------------| -~ - -[^enum_header]: an `enum` type used as a field in a `header` must specify a +//looking for a better solution because is horrible +.P4 Type Usage +[.center,%autowidth] +[#tab-p4-type-usage] +|=== +| *Element type* 3+^| *Container type* +| | *header* | *header_union* | *struct or tuple* +| `bit` | allowed | error | allowed +| `int` | allowed | error | allowed +| `varbit` | allowed | error | allowed +| `int` | error | error | error +| `void` | error | error | error +| `error` | error | error | allowed +| `match_kind` | error | error | error +| `bool` | error | error | allowed +| `enum` | allowed[1] | error | allowed +| `header` | error | allowed | allowed +| header stack | error | error | allowed +| `header_union` | error | error | allowed +| `struct` | error | error | allowed +| `tuple` | error | error | allowed +|=== + +[1] An `enum` type used as a field in a `header` must specify a underlying type and representation for `enum` elements. For example, the following P4~16~ objects involve complex types that need to be exposed in P4Runtime in order to support runtime operations on these objects. -~ Begin P4Example +[source,p4] +---- Digest, bit<8> > >() digest_complex; digest_complex.pack({ hdr.ipv4.version, hdr.ipv4.protocol }); // ... @@ -2628,7 +2469,7 @@ header_union ip_t { ipv6_t ipv6; } Register >(128) register_ip; -~ End P4Example +---- One solution would be to use only binary string (`bytes` type) in p4runtime.proto and to define a custom serialization format for complex P4~16~ @@ -2640,7 +2481,7 @@ 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 P4~16~ types. -### P4 Type Specifications in p4info.proto +==== 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 @@ -2663,10 +2504,12 @@ 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. +[%unbreakable] +-- `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 P4~16~ program (⪚ the type parameter of an extern). This +original P4~16~ 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`, @@ -2681,14 +2524,15 @@ compile-time type is represented as a Protobuf `oneof`, which can be: 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 P4~16~ 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 P4~16~ declaration. The same goes for the order of members of an `enum` (serializable or not) or members of `error`. -### `P4Data` in p4runtime.proto { #sec-p4data-in-p4runtime-proto } +[#sec-p4data-in-p4runtime-proto] +==== `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 @@ -2699,8 +2543,8 @@ case (P4~16~ `bit` 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: +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 @@ -2710,7 +2554,7 @@ guaranteeing read-write symmetry --- by introducing the following requirements: * 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 (&ie; all headers in the union are invalid) is + * 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. @@ -2721,21 +2565,22 @@ guaranteeing read-write symmetry --- by introducing the following requirements: included in the P4Info, in the corresponding `P4HeaderStackTypeSpec` or `P4HeaderUnionStackTypeSpec` message. -### Example - +==== Example Let's look at the Register example again: -~ Begin P4Example +[source,p4] +---- header_union ip_t { ipv4_t ipv4; ipv6_t ipv6; } Register >(128) register_ip; -~ End P4Example +---- Here's the corresponding entry in the P4Info message: -~ Begin Prototext +[source,protobuf] +---- registers { preamble { id: 369119267 @@ -2761,7 +2606,8 @@ type_info { bitwidth: 4 } } - } # ... + } + ... headers { key: "ipv6_t" value { @@ -2772,7 +2618,8 @@ type_info { bitwidth: 4 } } - } # ... + } + ... header_unions { key: "ip_t" value { @@ -2791,10 +2638,12 @@ type_info { } } } -~ End Prototext +---- + Here's a `p4.WriteRequest` to set the value of `register_ip[12]`: -~ Begin Prototext +[source,protobuf] +---- update { type: INSERT entity { @@ -2809,20 +2658,20 @@ update { valid_header { is_valid: true bitstrings: "\x04" - bitstrings: # ... + bitstrings: ... } } } } } } -~ End Prototext +---- -### `enum`, serializable `enum` and `error` +==== `enum`, serializable `enum` and `error` P4~16~ supports 2 different classes of enumeration types: without underlying type (safe enum) and with underlying type (serializable enum or "unsafe" enum) -[@P4Enums]. For `enum` types with no underlying type --- as well as `error` --- +cite:[P4Enums]. 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 @@ -2835,20 +2684,21 @@ 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](#sec-read-write-symmetry) principle. +name — even when the enum member has one — instead of the value, as it makes +it easier for the server to respect the xref:sec-read-write-symmetry[read-write +symmetry] principle. -### User-defined types { #sec-user-defined-types} +[#sec-user-defined-types] +==== User-defined types -P4~16~ enables programmers to introduce new types [@P4NewTypes]. While similar +P4~16~ enables programmers to introduce new types cite:[P4NewTypes]. 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](#sec-psa-metadata-translation). When introducing a new type, the +xref:sec-psa-metadata-translation[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](#sec-translation-of-port-numbers), +important use-case is for xref:sec-translation-of-port-numbers[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 @@ -2877,25 +2727,27 @@ 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 + ** `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 (⪚ `bit`) is + `type` declarations recursively until a built-in type (e.g. `bit`) is found. - * `translated_type`, if and only if the P4 `type` declaration was annotated + ** `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 either `sdn_string` or - `sdn_bitwidth` ---, which map to the two input parameters to the + which itself has two fields — `uri` and either `sdn_string` or + `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 example, an architecture — in this case PSA — may introduce a new type for port numbers: -~ Begin P4Example + +[source,p4] +---- // Controller refers to ports as a string, e.g. "eth0". @p4runtime_translation("p4.org/psa/v1/PortId_String_t", string) type bit<9> PortId_String_t; @@ -2907,17 +2759,19 @@ type bit<9> PortId_Bit32_t; // Same as above. @p4runtime_translation("p4.org/psa/v1/PortId_32_t", 32) type bit<9> PortId_32_t; -~ End P4Example - +---- +<<< In this case, the P4Info message would include the following `P4TypeInfo` messages: -~ Begin Prototext +[source,protobuf] +[%unbreakable] +---- type_info { new_types { key: "PortId_String_t" - value { # P4NewTypeSpec - translated_type { # P4NewTypeTranslation + value { // P4NewTypeSpec + translated_type { // P4NewTypeTranslation uri: "p4.org/psa/v1/PortId_String_t" sdn_string: {} } @@ -2928,8 +2782,8 @@ type_info { type_info { new_types { key: "PortId_Bit32_t" - value { # P4NewTypeSpec - translated_type { # P4NewTypeTranslation + value { // P4NewTypeSpec + translated_type { // P4NewTypeTranslation uri: "p4.org/psa/v1/PortId_Bit32_t" sdn_bitwidth: 32 } @@ -2940,23 +2794,23 @@ type_info { type_info { new_types { key: "PortId_32_t" - value { # P4NewTypeSpec - translated_type { # P4NewTypeTranslation + value { // P4NewTypeSpec + translated_type { // P4NewTypeTranslation uri: "p4.org/psa/v1/PortId_32_t" sdn_bitwidth: 32 } } } } -~ End Prototext +---- 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 (⪚ through some +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. -### Trade-off for v1.x Releases +==== Trade-off for v1.x Releases For the v1.x release ofs P4Runtime, it was decided not to replace occurrences of `bytes` with `P4Data` in the `p4.v1.FieldMatch` message, which is used to @@ -2966,13 +2820,13 @@ implementations of P4Runtime. Similarly it has been decided to keep using 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](#sec-psa-metadata-translation) for action -parameters and match fields, we include a `type_name` field in -`p4.config.v1.MatchField`, `p4.config.v1.Action.Param` and -`p4.config.v1.ControllerPacketMetadata.Metadata`. In addition, the `bitwidth` -field for all of these message types must abide by the following rule when -`type_name` names a translated user-defined type: +In order to support xref:sec-psa-metadata-translation[translation]for action +parameters and match fields, we include a `type_name` field in `p4.config.v1.MatchField` , +`p4.config.v1.Action.Param` and `p4.config.v1.ControllerPacketMetadata.Metadata`. In addition, +the `bitwidth` field for all of these message types must abide by the following rule when `type_name` names a translated user-defined type: +[%unbreakable] +-- * If the *controller_type* is a fixed-width unsigned bitstring, the `bitwidth` field must be set to the bitwidth of the *controller_type*. This information is redundant with the one included in the `P4TypeInfo` entry for the @@ -2981,13 +2835,15 @@ field for all of these message types must abide by the following rule when believe that using the bitwidth of the underlying type here would be inappropriate as it would make the P4Info message target-dependent. - * Otherwise (&ie;, if the *controller_type* is a string), the `bitwidth` must + * Otherwise (i.e, if the *controller_type* is a string), the `bitwidth` must be "unset", which, in Protobuf version 3, is the same as setting the field to 0. - +-- For example, consider the following P4 snippet, which declares a P4 table matching on two expressions with translated user-defined types: -~ Begin P4Example + +[source,p4] +---- @p4runtime_translation("p4.org/psa/v1/PortId_String_t", string) type bit<9> PortId_String_t; @@ -3004,11 +2860,12 @@ table t { drop; } } -~ End P4Example +---- This table would have the following representation in the generated P4Info message: -~ Begin Prototext +[source,protobuf] +---- tables { preamble { id: 34728461 @@ -3018,7 +2875,7 @@ tables { match_fields { id: 1 name: "meta.port1" - # notice that bitwidth is unset + // notice that bitwidth is unset match_type: EXACT type_name { name: "PortId_String_t" @@ -3033,17 +2890,19 @@ tables { name: "PortId_Bit32_t" } } - # ... + ... } -~ End Prototext +---- -# P4 Entity Messages { #sec-p4-entity-msgs} +[#sec-p4-entity-msgs] +== P4 Entity Messages P4Runtime covers P4 entities that are either part of the P4~16~ language, or defined as PSA externs. The sections below describe the messages for each supported entity. -## `TableEntry` { #sec-table-entry} +[#sec-table-entry] +=== `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 @@ -3081,30 +2940,28 @@ the `TableEntry` entity, which has the following fields: 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](#sec-direct-resources) section for more information. + direct meter entry attached to this table entry, if any. See + xref:sec-direct-resources[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](#sec-direct-resources) section for more information. + counter entry attached to this table entry, if any. See xref:sec-direct-resources[Direct resources] section for more information. * `meter_counter_data`, which is used to read and write the per-color counter values for the direct meter entry attached to this table entry, if any. - See [Direct resources](#sec-direct-resources) section for more information. + See xref:sec-direct-resources[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](#sec-default-entry) + the default entry for the table. See xref:sec-default-entry[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](#sec-idle-timeout) section for more information. + implement idle-timeout support for the table, if applicable. See xref:sec-idle-timeout[Idle-timeout]section for more information. * `is_const`, a boolean value that is `true` if and only if the entry cannot be modified or deleted by the client. The `priority` field must be set to a non-zero value if the match key includes a -ternary match (&ie; in the case of PSA if the P4Info entry for the table +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 an `OPTIONAL`, `TERNARY` or `RANGE` match type) or to zero otherwise. A higher priority number indicates that the entry @@ -3127,7 +2984,7 @@ table (the table has an empty match key), the server must reject all attempts to 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 P4~16~ specification for the -`size` property [@P4TableProperties]. In particular, some implementations may +`size` property cite:[P4TableProperties]. 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 @@ -3140,16 +2997,17 @@ The `is_const` field must be `false` in any `INSERT`, `MODIFY`, or `DELETE` write request of a table entry. If it is true, the server must reject the operation and return an `INVALID_ARGUMENT` error. -### Match Format { #sec-match-format} +[#sec-match-format] +==== Match Format The bytes fields in the `FieldMatch` message follow the format described in -[Bytestrings](#sec-bytestrings). +xref:sec-bytestrings[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](#sec-read-write-symmetry). For PSA match types, +to ensure xref:sec-read-write-symmetry[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. @@ -3168,7 +3026,8 @@ 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: -~ Begin P4Example +[source,p4] +---- table t { key = { hdr.ipv4.dip: ternary; @@ -3178,30 +3037,31 @@ table t { drop; } } -~ End P4Example +---- 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`. -~ Begin Prototext +[source,protobuf] +---- device_id: 3 entities { table_entry { - table_id: 33554439 # Table t's ID. + table_id: 33554439 // Table t's ID. match { - # field_id 1 is not present to use the don't care ternary value. + // 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. + // Action selection goes here. } } } -~ End Prototext +---- 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 @@ -3211,19 +3071,21 @@ P4Runtime server must return an `INVALID_ARGUMENT` error code. * `EXACT` match * The binary string encoding of the value must conform to the - [Bytestrings](#sec-bytestrings) requirements. + xref:sec-bytestrings[Bytestrings] requirements. -~ Begin Pseudo +[source,python] +---- assert(BytestringValid(match.exact().value())) -~ End Pseudo +---- * `LPM` match * The binary string encoding of the value (when present) must conform to the - [Bytestrings](#sec-bytestrings) requirements. + xref:sec-bytestrings[Bytestrings] requirements. * "Don't care" match must be omitted. * "Don't care" bits must be 0 in value. -~ Begin Pseudo +[source,python] +---- assert(BytestringValid(match.lpm().value())) pLen = match.lpm().prefix_len() @@ -3231,20 +3093,21 @@ assert(pLen > 0) trailing_zeros = countTrailingZeros(match.lpm().value()) assert(trailing_zeros >= field_bits - pLen) -~ End Pseudo +---- * `TERNARY` match * The binary string encoding of the value (when present) and mask (when - present) must conform to the [Bytestrings](#sec-bytestrings) requirements. + present) must conform to the xref:sec-bytestrings[Bytestrings] requirements. * "Don't care" match must be omitted. * Masked bits must be 0 in value. This constraint taken together - with the [Bytestrings](#sec-bytestrings) requirements means that the + with the xref:sec-bytestrings[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. -~ Begin Pseudo +[source,python] +---- assert(BytestringValid(match.ternary().value())) assert(BytestringValid(match.ternary().mask())) assert(match.ternary().value().size() <= match.ternary().mask().size()); @@ -3255,16 +3118,17 @@ mask = parseInteger(match.ternary().mask()) assert(mask != 0) assert(value & mask == value) -~ End Pseudo +---- * `RANGE` match * The binary string encoding of the low bound (when present) and high bound - (when present) must conform to the [Bytestrings](#sec-bytestrings) + (when present) must conform to the xref:sec-bytestrings[Bytestrings] requirements. * Low bound must be less than or equal to the high bound. * "Don't care" match must be omitted. -~ Begin Pseudo +[source,python] +---- assert(BytestringValid(match.range().low())) assert(BytestringValid(match.range().high())) @@ -3274,17 +3138,18 @@ high = parseInteger(match.range().high()) assert(low <= high) assert(low != min_field_value || high != max_field_value) -~ End Pseudo +---- * `OPTIONAL` match * The binary string encoding of the value must conform to the - [Bytestrings](#sec-bytestrings) requirements. + xref:sec-bytestrings[Bytestrings] requirements. -~ Begin Pseudo +[source,python] +---- assert(BytestringValid(match.optional().value())) -~ End Pseudo +---- -### Action Specification +==== Action Specification The `TableEntry` `action` field must be set for every `INSERT` update but may be left unset for `MODIFY` updates, in which case the action specification for the @@ -3304,10 +3169,10 @@ the `oneof` in the `TableAction` message will either be: * 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](#sec-oneshot) + selector. This usage is described in xref:sec-oneshot[One Shot Action Selector + Programming]. -If the `oneof` does not match the table description in the P4Info (⪚ the +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. @@ -3317,30 +3182,31 @@ The `Action` Protobuf message has the following fields: 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 (⪚ tries to set a `TABLE_ONLY` + 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](#sec-bytestrings). The P4Runtime client must provide a valid + xref:sec-bytestrings[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 + 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](#sec-bytestrings) format. + xref:sec-bytestrings[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. -### Default Entry { #sec-default-entry} +[#sec-default-entry] +==== 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 +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 @@ -3354,20 +3220,21 @@ 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` and `metadata` value as well as the -configurations for its [direct resources](#sec-direct-resources) will be reset +configurations for its xref:sec-direct-resources[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](#sec-direct-resources). +entry, including with regards to xref:sec-direct-resources[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 +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. -### Constant Tables { #sec-constant-tables } +[#sec-constant-tables] +==== Constant Tables Constant tables are defined as tables whose match entries are immutable. They are identified by the table property `const entries` @@ -3375,8 +3242,7 @@ in the P4~16~ source code. In the P4Info, such tables have `is_const_table` equal to true, and if the list of entries in the source code has at least one entry in it, they also have `has_initial_entries` flag equal to true. For tables declared with -the `entries` property, without `const` before `entries` see Section -[#sec-preinitialized-tables]. +the `entries` property, without `const` before `entries` see <<#sec-preinitialized-tables>>. The only write updates which are allowed for constant tables are `MODIFY` operations on direct resources, and the default action (assuming the default @@ -3395,7 +3261,7 @@ the following fields must be set by the server: `table_id`, `match`, `action`, direct resources that are being queried. Idle timeouts are not supported for static entries. -When a priority value is required (⪚ for tables including `RANGE`, +When a priority value is required (e.g. for tables including `RANGE`, `TERNARY` or `OPTIONAL` matches), it is inferred based on the order in which entries appear in the table declaration. As of August 2023, the open source `p4c` compiler always assigns entry priority values in a @@ -3405,21 +3271,22 @@ language specification does not preclude the P4 developer from explicitly specifying priorities for entries in constant tables, but `p4c` does not yet support this. -### Preinitialized tables { #sec-preinitialized-tables } +[#sec-preinitialized-tables] +==== Preinitialized tables Preinitialized tables are those defined with an `entries` table property in the P4~16~ source code, with no `const` qualifier before `entries`, and at least one entry in that list. In the P4Info, such tables have `has_initial_entries` flag equal to true, but `is_const_table` is false. For tables declared with `const entries`, -see section on [Constant Tables](#sec-constant-tables). +see section on xref:sec-constant-tables[Constant Tables]. Every P4 table falls into one of three categories: * *Normal table*: Neither `entries` nor `const entries` are declared in the source code, and thus `is_const_table` and `has_initial_entries` will both be false. A corner case is that if - it has `entries = { }` with no `const` before `entries`, &ie; an + it has `entries = { }` with no `const` before `entries`, i.e. an empty list of entries, that is also a normal table. * *Constant table*: The table has `const entries` declared, and thus a separate `entries` property is not permitted by the language. Such @@ -3433,12 +3300,13 @@ Every P4 table falls into one of three categories: A preinitialized table is allowed to have a mix of some entries marked `const`, and other entries not marked `const`. - +[%unbreakable] +-- Entries not marked `const` may be modified or deleted, just as a client may do for any entry in a normal table. - +-- Entries marked `const` behave like entries in a constant table, -&ie; only `MODIFY` operations on direct resources are allowed. +i.e. only `MODIFY` operations on direct resources are allowed. Unlike a table with `is_const_table = true`, a client may insert entries into a table with `has_initial_entries = true` and @@ -3447,8 +3315,7 @@ number of entries supported by the target for the table. The contents of preinitialized tables can be queried by the client through a `ReadRequest`. The server fills in the same fields in the -response as it does for constant tables, as described in section on -[Constant Tables](#sec-constant-tables), and with the same restrictions on table +response as it does for constant tables, as described in section on xref:sec-constant-tables[Constant Tables], and with the same restrictions on table features supported. If the table requires a priority value for entries, the priorities of @@ -3459,10 +3326,11 @@ in a normal table can. The contents of all table entries within the `entries` table properties in a P4 program can be written to a separate output file by -the open source `p4c` compiler. See Section [#sec-entries-files] for +the open source `p4c` compiler. See <<#sec-entries-files>> for details. -### Wildcard Reads { #sec-table-wildcard-reads} +[#sec-table-wildcard-reads] +==== Wildcard Reads 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 @@ -3472,8 +3340,8 @@ 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 +* `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 @@ -3504,7 +3372,8 @@ fields may be used to select and filter results: For example, in order to read all entries from all tables from device 3, the client can use the following `ReadRequest` message. -~ Begin Prototext +[source,protobuf] +---- device_id: 3 entities { table_entry { @@ -3514,13 +3383,14 @@ entities { metadata: "" } } -~ End Prototext +---- 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: -~ Begin Prototext +[source,protobuf] +---- device_id: 3 entities { table_entry { @@ -3530,22 +3400,23 @@ entities { metadata: "" } } -~ End Prototext +---- 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 (&ie; has an empty -`match` field) but is not the default entry (&ie; `is_default_action` is +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: -~ Begin P4Example +[source,p4] +---- table t { key = { hdr.ipv4.dip: lpm; @@ -3554,15 +3425,17 @@ table t { drop; fwd; } } -~ End P4Example +---- The following `WriteRequest` message can be used to add 2 entries: -~ Begin Prototext + +[source,protobuf] +---- device_id: 3 entities { - table_entry { # don't care entry + table_entry { // don't care entry table_id: 0x0212ab34 - # ... + ... } table entry { table_id: 0x0212ab34 @@ -3572,36 +3445,38 @@ entities { value: 0x0a000000 prefix_len: 8 } - # ... + ... } } -~ End Prototext +---- 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. -~ Begin Prototext +[source,protobuf] +---- device_id: 3 entities { table_entry { table_id: 0x0212ab34 } } -~ End Prototext +---- This issue also exists for tables with `TERNARY`, `RANGE`, and `OPTIONAL` 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](#sec-table-entry) ---, +client. If the client uses distinct priority values for all entries — which is +strongly recommended to achieve xref:sec-table-entry[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. -### Direct Resources { #sec-direct-resources} +[#sec-direct-resources] +==== Direct Resources In addition to the `DirectCounterEntry` and `DirectMeterEntry` entities, P4Runtime support reading and writing direct resources as part of the @@ -3638,62 +3513,62 @@ 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`, `counter_data` and `meter_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, &ie; we assume +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 + ** `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 + *** 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 + ** `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 + *** 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 + ** `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 + *** 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 + ** `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 + *** 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 + ** `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 + ** `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 + *** if **set**: The response includes the counter entry's value read from the target. * `meter_counter_data` field - * `WriteRequest (INSERT)` - * if **unset**: The initial value for all 3 counter entries is the + ** `WriteRequest (INSERT)` + *** if **unset**: The initial value for all 3 counter entries is the default (0). - * if **set**: The initial value for all 3 counter entries is the + *** if **set**: The initial value for all 3 counter entries is the default (0). Sub-field, if any, can only have zero (0) as its value. `INVALID_ARGUMENT` error is returned for any non-zero sub-field value. - * `WriteRequest (MODIFY)` - * if **unset**: All the 3 counter entries are unchanged. - * if **set**: All the 3 counters are reset to 0. Sub-field, if any, can + ** `WriteRequest (MODIFY)` + *** if **unset**: All the 3 counter entries are unchanged. + *** if **set**: All the 3 counters are reset to 0. Sub-field, if any, can only have zero (0) as its value. `INVALID_ARGUMENT` error is returned for any non-zero sub-field value. - * `ReadRequest` - * if **unset**: The response does not include counter values + ** `ReadRequest` + *** if **unset**: The response does not include counter values (`meter_counter_data` is unset in the response). - * if **set**: The response includes all the 3 counter values read from + *** if **set**: The response includes all the 3 counter values read from the target. In its default configuration, a meter returns the GREEN color for every packet @@ -3701,23 +3576,24 @@ 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 -(&ie; the `meter_config` field must be set to match the existing configuration). +(i.e. the `meter_config` field must be set to match the existing configuration). -### Idle-timeout { #sec-idle-timeout } +[#sec-idle-timeout] +==== 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" (&ie; not selected by any packet +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 primary client, which can then take +with best effort, generate a stream notification — using the +`IdleTimeoutNotification` message — to the primary 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, &ie; no + 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 @@ -3753,10 +3629,10 @@ 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](#sec-table-idle-timeout-notification) section. +`IdleTimeoutNotification`, please refer to the xref:sec-table-idle-timeout-notification[Table idle timeout notifications] section. -## `ActionProfileMember` & `ActionProfileGroup` { #sec-action-profile-member-and-group } +[#sec-action-profile-member-and-group] +=== `ActionProfileMember` & `ActionProfileGroup` P4Runtime defines an API for programming a PSA ActionProfile extern using `ActionProfileMember` messages. A PSA ActionSelector extern can be programmed @@ -3767,7 +3643,8 @@ 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`. -~ Begin P4Example +[source,p4] +---- ActionSelector(HashAlgorithm.crc32, /*size = */ 32w1024, /*output_width = */ 32w10) as; @@ -3787,7 +3664,7 @@ table t { } implementation = as; } -~ End P4Example +---- 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 @@ -3811,7 +3688,7 @@ 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. -### Action Profile Member Programming +==== 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 @@ -3864,7 +3741,8 @@ ActionSelector objects. A message with `action_profile_id` equal to the id of an existing ActionProfile or ActionSelector object, and a `member_id` equal to 0, will read all members of that specified object. -### Action Profile Group Programming { #sec-action-profile-group-programming } +[#sec-action-profile-group-programming] +==== 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 @@ -3893,27 +3771,26 @@ An `ActionProfileGroup` entity update message has the following fields: * `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 + ** `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_port` is the controller-defined port that the member's + ** `watch_port` is the controller-defined port that the member's liveness depends on. At runtime, the member must be excluded from - selection if the watch port is down. See Section - [#action-selector-constraints] for notes on the behavior if all members in + selection if the watch port is down. See <<#action-selector-constraints>> for notes on the behavior if all members in a group are excluded for this reason. If `watch_port` is empty, then the member is always included in the selection, regardless of the status of any port of the device. The value must be empty or the SDN port of an existing port on the device, otherwise the server must return `INVALID_ARGUMENT`. If the target does not support using the SDN port as a - watch port (⪚ on some targets LAGs cannot be used for this purpose), + watch port (e.g. on some targets LAGs cannot be used for this purpose), the server must return `UNIMPLEMENTED`. * `max_size` is the maximum sum of all members or member weights (as per the `selector_size_semantics`) for the group. This field is defined when the group is inserted, and must not be changed in a `MODIFY` update, otherwise an `INVALID_ARGUMENT` error is returned. See the subsection below for the - [rules on setting `max_size`](#sec-max-size-rules). + xref:sec-max-size-rules[rules on setting `max_size`]. An action profile group may be inserted, modified or deleted as per the following semantics. @@ -3938,7 +3815,7 @@ following semantics. field will be ignored. When setting the group membership with `INSERT` or `MODIFY`, the `members` -repeated field must not include duplicates, &ie; members with the same +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. @@ -3953,7 +3830,8 @@ message with `action_profile_id` equal to the id of an existing ActionSelector object, and a `group_id` equal to 0, will read all groups of that one specified object. -#### Rules on Setting `max_size` { #sec-max-size-rules} +[#sec-max-size-rules] +===== Rules on Setting `max_size` The valid values for `max_size` depend on the static `max_group_size` included in the P4Info message: @@ -3965,7 +3843,7 @@ in the P4Info message: would have rejected the Forwarding Pipeline Config. If `max_size` is greater than `max_group_size`, the server must return `INVALID_ARGUMENT`. -* Otherwise (&ie; if `max_group_size` is 0), the P4Runtime client can set +* 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 @@ -3978,7 +3856,8 @@ in the P4Info message: target, the server must return a `RESOURCE_EXHAUSTED` error at group-creation time. -### One Shot Action Selector Programming { #sec-oneshot } +[#sec-oneshot] +==== 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 @@ -4009,7 +3888,7 @@ is `sum_of_members`, or else the server must return `INVALID_ARGUMENT`. Each * `watch_port` is the controller-defined port that the action's liveness depends on. At runtime, the action must be excluded from selection if the watch port - is down. See Section [#sec-action-profile-group-programming] for more details + is down. See <<#sec-action-profile-group-programming>> for more details on the `watch_port` field, which also apply for one shot action selector programming. @@ -4023,11 +3902,11 @@ To preserve read-write symmetry, an implementation must answer `ReadRequest`s 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](#sec-action-profile-member-and-group). This table could be programmed +For example, consider the action selector table defined xref:sec-action-profile-member-and-group[role]. This table could be programmed with the following one shot update: -~ Begin Prototext +[source,protobuf] +---- table_entry { table_id: 0x0212ab34 match { /* lpm match */ } @@ -4051,12 +3930,13 @@ table_entry { } } } -~ End Prototext +---- Which would be equivalent to the following updates, where `GROUP_ID`, `MEMBER_ID_1`, `MEMBER_ID_2`, and `MEMBER_ID_3` are unused ids: -~ Begin Prototext +[source,p4] +---- action_profile_member { action_profile_id: 0x11ab12cd member_id: MEMBER_ID_1 @@ -4096,11 +3976,10 @@ table_entry { match { /* lpm match */ } action { action_profile_group_id: GROUP_ID } } -~ End Prototext +---- 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](#sec-batching-and-ordering-of-updates). Members need to be inserted +needs to use multiple messages to ensure xref:sec-batching-and-ordering-of-updates[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. @@ -4110,7 +3989,7 @@ 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. Additionally, each `action` specification would count as -a separate member for the purposes of ⪚ the `sum_of_members` group size +a separate member for the purposes of e.g. the `sum_of_members` group size calculation for Action Selectors. All the tables associated with an action selector may either be programmed @@ -4127,14 +4006,15 @@ an action selector implementation. Support for the `ActionProfileMember` and `UNIMPLEMENTED` error for every `ActionProfileMember` or `ActionProfileGroup` message that it receives. -### Constraints on action selector programming { #action-selector-constraints } +[#action-selector-constraints] +==== Constraints on action selector programming The PSA specification states that the following features are *optional* in -action selector implementations [@PSAActionSelector]: +action selector implementations cite:[PSAActionSelector]: -1. Support for non-empty groups where in the same group, different members are +. 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 +. 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 @@ -4167,7 +4047,7 @@ style of programming. The PSA specification includes a discussion on how to implement `psa_empty_group_action` in software in the P4Runtime server -[@PSAEmptyGroupActionAppendix]. +cite:[PSAEmptyGroupActionAppendix]. If a P4Runtime implementation does support `psa_empty_group_action`, that action should be executed when an action selector group is selected that uses the watch @@ -4179,45 +4059,47 @@ its behavior when a group effectively becomes empty because the watch ports of all members of a group are down. -## `CounterEntry` & `DirectCounterEntry` +=== `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: +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. -~ Begin Proto +[source,protobuf] +---- message CounterData { int64 byte_count = 1; int64 packet_count = 2; } -~ End Proto +---- 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. -### `DirectCounterEntry` +==== `DirectCounterEntry` A direct counter is a direct resource associated with a `TableEntry` (see -[Direct Resources](#sec-direct-resources)). The `counter_data` field of the +xref:sec-direct-resources[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. -~ Begin Proto +[source,protobuf] +---- message DirectCounterEntry { TableEntry table_entry = 1; CounterData data = 2; } -~ End Proto +---- A `WriteRequest` may only include an `Update` message of type `MODIFY` with a `DirectCounterEntry`, whose fields are specified by the client as follows: @@ -4239,14 +4121,14 @@ A client may use `ReadRequest` in two ways to read the contents of a * 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](#sec-direct-resources)). +(see xref:sec-direct-resources[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`. -### `CounterEntry` +==== `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 @@ -4260,13 +4142,14 @@ the P4Runtime `CounterEntry` message whose fields are defined as follows: * `data` is a Protobuf message of type `CounterData`, which represents the counter value. -~ Begin Proto +[source,p4] +---- message CounterEntry { uint32 counter_id = 1; Index index = 2; CounterData data = 3; } -~ End Proto +---- 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 @@ -4295,30 +4178,30 @@ entity for each of the instances, specifying the `counter_id` and values for all indirect counters in the array identified by the unique id `counter_id`. -## `MeterEntry` & `DirectMeterEntry` { #sec-meter-directmeter } +=== `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 [@RFC2698]. P4Runtime clients may additionally -restrict meter usage on a table to RFC 2697's [@RFC2697] *Single Rate Three +(trTCM) defined in RFC 2698 cite:[RFC2698]. P4Runtime clients may additionally +restrict meter usage on a table to RFC 2697's cite:[RFC2697] *Single Rate Three Color Marker* (srTCM) or a simplified version of it that we call *Single Rate Two Color Marker*. The type of a table's meter is set by the `MeterSpec.Type` as -described in the [Meter & DirectMeter section](#sec-meter-directmeter). +described in the xref:sec-meter-directmeter[Meter & DirectMeter section]. -The trTCM meters an arbitrary packet stream using two configured rates --- +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 +associated burst sizes — and "marks" its packets as GREEN, YELLOW or RED based on the observed rate. -The srTCM meters an arbitrary packet stream using a single configured rate --- -the Committed Information Rate (CIR) and its associated burst size --- as well +The srTCM meters an arbitrary packet stream using a single configured rate — +the Committed Information Rate (CIR) and its associated burst size — as well as the Excess Burst Size (EBS) and "marks" its packets as GREEN, YELLOW or RED based on the observed rate. The *Single Rate Two Color Marker* meters an arbitary packet stream using a -single configured rate --- the Committed Information Rate (CIR) and its -associated burst size --- and "marks" its packets as GREEN or RED based on the +single configured rate — the Committed Information Rate (CIR) and its +associated burst size — and "marks" its packets as GREEN or RED based on the observed rate. `MeterEntry` & `DirectMeterEntry` have an additional field `counter_data` that @@ -4333,7 +4216,8 @@ primary purpose of the color counters is for debugging purposes. A meter may be configured as a direct or indirect instance, similar to a counter. The `MeterConfig` P4Runtime message represents meter configuration. -~ Begin Proto +[source,protobuf] +---- message MeterConfig { int64 cir = 1; // Committed Information Rate int64 cburst = 2; // Committed Burst Size @@ -4341,7 +4225,7 @@ message MeterConfig { int64 pburst = 4; // Peak Burst Size int64 eburst = 5; // Excess burst size (only used for srTCM) } -~ End Proto +---- A MeterConfig for a trTCM typed meter must only be accepted if `eburst` is unset. Otherwise, the server should return an `INVALID_ARGUMENT` error. @@ -4361,23 +4245,23 @@ packets identically in all three cases. Thus, changing the meter type from *Single Rate Two Color Marker* to *Single Rate Three Color Marker* or *Two Rate Three Color Marker* is a non-breaking change. -### `DirectMeterEntry` +==== `DirectMeterEntry` -A direct meter is a direct resource associated with a `TableEntry` (see [Direct -resources](#sec-direct-resources)). The `meter_config` field of the `TableEntry` +A direct meter is a direct resource associated with a `TableEntry` (see xref:sec-direct-resources[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. -~ Begin Proto +[source,protobuf] +---- message DirectMeterEntry { TableEntry table_entry = 1; MeterConfig config = 2; MeterCounterData counter_data = 3; } -~ End Proto +---- A `WriteRequest` may only include an `Update` message of type `MODIFY` with a `DirectMeterEntry`, whose fields are specified by the client as follows: @@ -4406,7 +4290,7 @@ 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](#sec-direct-resources)). + message (see xref:sec-direct-resources[Direct Resources]). * Explicitly request the meter configuration by including the `DirectMeterEntry` in the `ReadRequest`. The `table_entry.match` field must match the @@ -4415,7 +4299,7 @@ A client may use `ReadRequest` in two ways to read a `DirectMeter` config. per color counter data for the meter entry, if supported and specified in the request message. -### `MeterEntry` +==== `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 @@ -4434,14 +4318,15 @@ follows: represents the per color counter values associated with the corresponding meter. -~ Begin Proto +[source,protobuf] +---- message MeterEntry { uint32 meter_id = 1; Index index = 2; MeterConfig config = 3; MeterCounterData counter_data = 4; } -~ End Proto +---- 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 @@ -4472,7 +4357,7 @@ supported as follows: configuration for all indirect meters in the array identified by the unique id `meter_id`. -### `MeterCounterData` +==== `MeterCounterData` The `MeterCounterData` P4Runtime message represents the per color counters associated with a meter entry. Whenever a meter is executed and returns @@ -4483,16 +4368,16 @@ As seen above, these counters can be associated with a `DirectMeterEntry` or `UNIMPLEMENTED` if a `MeterCounterData` field was set in a read or write request. -~ Begin Proto +[source,p4] +---- message MeterCounterData { CounterData green = 1; CounterData yellow = 2; CounterData red = 3; } -~ End Proto - +---- -## `PacketReplicationEngineEntry` +=== `PacketReplicationEngineEntry` The PSA Packet Replication Engine (PRE) is an extern that is implicitly instantiated in all PSA programs. The PRE is responsible for implementing @@ -4500,7 +4385,7 @@ 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. -### `MulticastGroupEntry` +==== `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 @@ -4510,7 +4395,8 @@ 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. -~ Begin P4Example +[source,p4] +---- control arp_multicast(inout H hdr, inout M smeta) { apply { if (hdr.ethernet.isValid() && @@ -4519,12 +4405,13 @@ control arp_multicast(inout H hdr, inout M smeta) { } } } -~ End P4Example +---- At runtime, the client writes the following update in the target (shown in Protobuf text format). -~ Begin Prototext +[source,protobuf] +---- type: INSERT entity { packet_replication_engine_entry { @@ -4555,14 +4442,13 @@ entity { } } } -~ End Prototext +---- 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](#sec-translation-of-port-numbers) section. Each +translation between SDN ports and PSA device ports, refer to the xref:sec-translation-of-port-numbers[PSA Metadata Translation] section. Each replica can optionally have a list of backup replicas used as fallback ports for multicast, where the highest-preference starts with the primary replica followed by its backup replicas in order. When the highest-preferred port of a @@ -4576,10 +4462,10 @@ backups are down for a particular replica, the packet processing side effects can result in one of the two main expected behaviors that the target is recommended to implement: -1. No replica is created so none of the side effects from the +. No replica is created so none of the side effects from the after-multicast-replication processing should occur for this replica. -2. The system sends one replica to the primary port, which gets dropped, +. The system sends one replica to the primary port, which gets dropped, but the system will perform any side effects of the after-multicast-replication processing. @@ -4598,7 +4484,7 @@ 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 be greater - than 0 (see explanation [below](#sec-valid-values-for-mg-id)), or the + than 0 (see explanation xref:sec-valid-values-for-mg-id[below]), or 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 @@ -4627,12 +4513,13 @@ other fields in `MulticastGroupEntry` are ignored. To perform a *wildcard* `Read` on all configured multicast group entries, the `multicast_group_id` field must be set to 0, its default value. -#### Valid Values for `multicast_group_id` { #sec-valid-values-for-mg-id} +[#sec-valid-values-for-mg-id] +===== Valid Values for `multicast_group_id` The PSA specification states that the valid *data plane* values for multicast group ids (`MulticastGroup_t`) range from 1 (0 is a special value that indicates no multicast replication is to be performed for a packet) to the maximum value -supported by the target [@PSATranslation]. This means that, in the absence of +supported by the target cite:[PSATranslation]. This means that, in the absence of translation, the client must set the `multicast_group_id` field to a value in this range when inserting a multicast group. However, because P4Runtime reserves 0 as a special *wildcard* value which is used to read all the multicast groups @@ -4642,7 +4529,7 @@ multicast group ids. In other words, it is not possible to map (using translation) a zero SDN multicast group id value to a non-zero data plane multicast group id value. -### `CloneSessionEntry` +==== `CloneSessionEntry` PSA supports cloning of packets in both the ingress and egress pipeline. Ingress cloning creates a mirror of the packet as seen in the beginning of the ingress @@ -4661,7 +4548,8 @@ type of the clone session metadata is 10 bits on the PSA device in this example. We assume that the `clone_low_ttl` control block is applied in the ingress pipeline to create an ingress-to-egress clone. -~ Begin P4Example +[source,p4] +---- control clone_low_ttl(inout H hdr, inout M smeta) { apply { if (hdr.ipv4.isValid() && @@ -4671,38 +4559,39 @@ control clone_low_ttl(inout H hdr, inout M smeta) { } } } -~ End P4Example +---- At runtime, the client writes the following update in the target (shown in Protobuf text format). -~ Begin Prototext +[source,protobuf] +---- type: INSERT entity { packet_replication_engine_entry { clone_session_entry { session_id: 100 - replicas { port: "\xff\xff\xff\xfd" instance: 1 } # to CPU + replicas { port: "\xff\xff\xff\xfd" instance: 1 } // to CPU class_of_service: 2 packet_length_bytes: 4096 } } } -~ End Prototext +---- As a result of the above P4Runtime programming, the target device will create one replica of a low TTL packet from the ingress to the egress. Note that the clone session ID of the programmed PRE entry is identical to the value used in the data plane in this example (no numerical translation, which is the default -for values of type `CloneSessionId_t` [@PSATranslation]). The clone will be +for values of type `CloneSessionId_t` cite:[PSATranslation]). The clone will be treated for scheduling in the PRE with a class of service value of 2. If the packet is larger than 4096 bytes, it will be truncated to carry at most 4096 bytes. The cloned replica will appear in the egress pipeline as an independent packet with egress port set to CPU (corresponding to SDN port `0xfffffffd`; see -[Translation of Port Numbers](#sec-translation-of-port-numbers)). Note that the +xref:sec-translation-of-port-numbers[Translation of Port Numbers]). Note that the egress port (`port` field) must be an SDN port and must refer to a singleton port. @@ -4714,7 +4603,7 @@ semantics: * `INSERT`: Add a new clone session entry bound to a set of egress ports and replica IDs. The `session_id` is a `uint32` and must be greater than 0 (see - explanation [below](#sec-valid-values-for-session-id)), or the P4Runtime + explanation xref:sec-valid-values-for-session-id[below], or 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 also @@ -4723,13 +4612,12 @@ semantics: service for each clone packet instance will be set to the value programmed in the clone session entry (`class_of_service` field). This value must be a valid value for the PSA `ClassOfService_t` type, which supports runtime translation - by default [@PSATranslation], or the server must return - `INVALID_ARGUMENT`. See [PSA Metadata - Translation](#sec-translation-of-port-numbers) for more information. The - `packet_length_bytes` field must be set to a non-zero value if the clone - packet should be truncated to the given value (in bytes). If the - `packet_length_bytes` field is 0 (default), no truncation on the clone will be - performed. + by default cite:[PSATranslation], or the server must return + `INVALID_ARGUMENT`. See xref:sec-translation-of-port-numbers[PSA Metadata + Translation] for more information. The `packet_length_bytes` field must be + set to a non-zero value if the clone packet should be truncated to the given + value (in bytes). If the`packet_length_bytes` field is 0 (default), + no truncation on the clone will be performed. * `MODIFY`: Modify the attributes of a given clone session entry, indexed by the given `clone_session_id`. Same restrictions as `INSERT` apply here. * `DELETE`: Delete the clone session indexed by the given @@ -4743,24 +4631,24 @@ configured clone session entries, the `session_id` field must be set to 0, its default value. The `session_id` field can never be equal to 0 in a `Write` RPC. If it does, the server must return an `INVALID_ARGUMENT` error. -#### Valid Values for `session_id` { #sec-valid-values-for-session-id} +[#sec-valid-values-for-session-id] +===== Valid Values for `session_id` The PSA specification states that the valid *data plane* values for clone session ids (`CloneSessionId_t`) range from 0 to the maximum value supported by -the target [@PSATranslation]. Note that unlike for [multicast group -ids](#sec-valid-values-for-mg-id), 0 is a valid *data plane* value for clone +the target cite:[PSATranslation]. Note that unlike for xref:sec-valid-values-for-mg-id[multicast group ids], 0 is a valid *data plane* value for clone session ids. However, just like for multicast group ids, P4Runtime reserves 0 as a special *wildcard* value which is used to read all the clone sessions configured in the target. This means that 0 can never be used as a `session_id` value when inserting a clone session, whether or not numeric translation is enabled for clone session ids. If translation is *not* enabled, we effectively "lose" one clone session, assuming the target supports 0 as mandated by the PSA -specification. If this is an issue (⪚ because the target supports a very +specification. If this is an issue (e.g. because the target supports a very limited number of clone sessions), one can enable translation on `CloneSessionId_t` and map any non-zero SDN session id to the data plane clone session with id 0, then insert a clone session with the chosen SDN session id. -## `ValueSetEntry` +=== `ValueSetEntry` Parser Value Set is a construct in P4 that is used to support programmability of parser state transitions. A transition select statement in P4 can use a parser @@ -4768,7 +4656,8 @@ Value Set to define a runtime programmable state transition as shown in the example below. A runtime programmable set of TRILL Ethertypes is used to transition the parser state machine to the `parse_trill_types` state. -~ Begin P4Example +[source,p4] +---- state parse_l2 { @id(1) value_set(MAX_TRILL_TYPES) trill_types; extract(hdr.ethernet); @@ -4779,11 +4668,12 @@ state parse_l2 { _: reject; } } -~ End P4Example +---- The corresponding entry in the P4Info for this Value Set is: -~ Begin Prototext +[source,protobuf] +---- value_sets { preamble { id: 0x03000001 @@ -4791,17 +4681,18 @@ value_sets { } match { id: 1 - bitwidth: + bitwidth: "ETH_TYPE_BITWIDTH_VALUE" match_type: EXACT } - size: + size: "MAX_TRILL_TYPES_VALUE" } -~ End Prototext +---- At runtime, the client writes the following update in the target (shown in Protobuf text format). -~ Begin Prototext +[source,protobuf] +---- type: MODIFY entity { value_set_entry { @@ -4818,7 +4709,7 @@ entity { } } } -~ End Prototext +---- As a result of the above P4Runtime programming, all packets with EtherType values of 0x22f3 and 0x893b will be parsed as per the state machine starting at @@ -4838,7 +4729,7 @@ A `ValueSetEntry` entity update message has the following fields: it matches all its `FieldMatch` messages. This is similar to how a packet matches a table entry if and only if it matches all the components of the match key for this entry. `FieldMatch` messages in a `ValueSetEntry` follow - the [same rules](#sec-match-format) as in a `TableEntry`. + the xref:sec-match-format[same rules] as in a `TableEntry`. A `ValueSetEntry` may only be modified. If the update type is `INSERT` or `DELETE`, the server must return an `INVALID_ARGUMENT` error. If the update type @@ -4846,19 +4737,19 @@ is `MODIFY`, the server writes the members given in the repeated field to the Value Set entry indexed by the given `value_set_id`. The maximum number of matches must not exceed the maximum size given by the `size` field in P4Info of the Value Set, otherwise the server must return a `RESOURCE_EXHAUSTED` error. To -empty a Value Set (&ie; restore it to its initial state), the P4Runtime client +empty a Value Set (i.e. restore it to its initial state), the P4Runtime client can perform a `MODIFY` update with an empty `members` repeated field. -To facilitate [read-write symmetry](#sec-read-write-symmetry), the server must +To facilitate xref:sec-read-write-symmetry[read-write symmetry], the server must return an `ALREADY_EXISTS` error in case of duplicate members. Unlike for match tables, a priority value is not required for ternary, range and optional matches: overlapping entries do not need to be ordered and the parse state transition is determined by whether or not the packet matches at least one entry in the set. -See Appendix [#sec-value-set-example] for a more complex Value Set example. +See Appendix <<#sec-value-set-example>> for a more complex Value Set example. -## `RegisterEntry` +=== `RegisterEntry` The PSA Register extern is a stateful memory array that can be read and written during packet forwarding. The `RegisterEntry` P4Runtime entity is used by the @@ -4885,7 +4776,8 @@ control plane operations. Register entry in the P4Info, or the server must return an `INVALID_ARGUMENT` error. -## `DigestEntry` { #sec-digestentry } +[#sec-digestentry] +=== `DigestEntry` A digest is one mechanism to send a message from the data plane to the control plane. It is traditionally used for MAC address learning: when a packet @@ -4917,12 +4809,12 @@ if they are not duplicate. digest messages are exchanged between server and client for a given `digest_id`; these parameters are: - * `max_timeout_ns`: the maximum server buffering delay in nanoseconds for an + ** `max_timeout_ns`: the maximum server buffering delay in nanoseconds for an outstanding digest message. - * `max_list_size`: the maximum digest list size --- in number of digest - messages --- sent by the server to the client as a single `DigestList` + ** `max_list_size`: the maximum digest list size — in number of digest + messages — sent by the server to the client as a single `DigestList` Protobuf message. - * `ack_timeout_ns`: the timeout in nanoseconds that a server must wait for a + ** `ack_timeout_ns`: the timeout in nanoseconds that a server must wait for a digest list acknowledgement from the client before new digest messages can be generated for the same learned data. @@ -4955,7 +4847,7 @@ digest list and acted on it. The server must keep a "cache" containing the set of all digest messages that have been sent, but not acknowledged yet by the primary client, up-to `ack_timeout_ns` in the past. The server must delete all cache entries for a given digest list when they are at least `ack_timeout_ns` -old or when a matching `DigestListAck` message (&ie; with the same `digest_id` +old or when a matching `DigestListAck` message (i.e. with the same `digest_id` and `list_id` fields as the `DigestList` message) is received. The acknowledgement mechanism described above is not used to implement some sort @@ -4979,7 +4871,8 @@ status change. Here is some pseudo-code implementing the handling of digest messages in the P4Runtime server: -~ Begin CPP +[source,p4] +---- DigestStream stream; DigestCache cache; DigestBuffer buffers; @@ -5023,78 +4916,82 @@ while (true) { } sleep(X); } -~ End CPP +---- -## `ExternEntry` { #sec-extern-entry} +[#sec-extern-entry] +=== `ExternEntry` This is used to support a P4 extern entity that is not part of PSA. It is defined as: -~ Begin Proto +[source,protobuf] +---- message ExternEntry { uint32 extern_type_id = 1; uint32 extern_id = 2; google.protobuf.Any entry = 3; } -~ End Proto +---- Each `ExternEntry` entity maps to an `Extern` message in the -[P4Info](#sec-p4info-extern) and an `ExternInstance` message within that +xref:sec-p4info-extern[P4Info] and an `ExternInstance` message within that message. The `extern_type_id` field must be equal to the one in `ExternEntry`. The `extern_id` field must be equal to the ID included in the `preamble` of the corresponding `ExternInstance` message. -`entry` itself is embedded as an `Any` Protobuf message [@ProtoAny] to keep the +`entry` itself is embedded as an `Any` Protobuf message cite:[ProtoAny] to keep the protocol extensible. It includes the extern-specific parameters required by the P4Runtime server to perform the read or write operation. The underlying Protobuf message should be defined in a separate architecture-specific Protobuf file. See -section on [Extending P4Runtime for non-PSA -Architectures](#sec-extending-p4runtime) for more information. +section on xref:sec-extending-p4runtime[Extending P4Runtime for non-PSA +Architectures] for more information. -# Error Reporting Messages { #sec-error-reporting-messages} +[#sec-error-reporting-messages] +== Error Reporting Messages P4Runtime is based on gRPC and all RPCs return a status to indicate success or failure. gRPC supports multiple language bindings; we use C++ binding below to explain how error reporting works in the failure case. -gRPC uses `grpc::Status` [@gRPCStatus] to represent the status returned by an +gRPC uses `grpc::Status` cite:[gRPCStatus] to represent the status returned by an RPC. It has 3 attributes: -~ Begin CPP +[source,c++] +---- StatusCode code_; grpc::string error_message_; grpc::string binary_error_details_; -~ End CPP +---- -The `code_` represents a canonical error [@gRPCStatusCodes] and describes the +The `code_` represents a canonical error cite:[gRPCStatusCodes] and describes the overall RPC status. The `error_message_` is a developer-facing error message, which should be in English. The `binary_error_details_` carries a serialized -`google.rpc.Status` message [@ProtoStatus] message, which has 3 fields: +`google.rpc.Status` message cite:[ProtoStatus] message, which has 3 fields: -~ Begin Proto +[source,protobuf] +---- int32 code = 1; // see code.proto string message = 2; repeated google.protobuf.Any details = 3; -~ End Proto +---- The code and message fields must be the same as `code_` and `error_message_` fields from `grpc::Status` above. The `details` field is a list that consists of `p4.Error` messages that carry error details for individual elements inside -batch-request RPCs (⪚ `Write` and `Read`). `p4.Error` includes a canonical +batch-request RPCs (e.g. `Write` and `Read`). `p4.Error` includes a canonical error code but also enables different target vendors to additionally express their own error codes in their chosen error-space. This specification document tries to cover all possible generic error cases and to provide the appropriate -value for the canonical error code based on best practices [@gRPCStatusCodes]. +value for the canonical error code based on best practices cite:[gRPCStatusCodes]. -Figure [#fig-error-report] illustrates how these messages fit together. +<<#fig-p4prg>>.. illustrates how these messages fit together. -~ Figure { #fig-error-report; caption: "P4Runtime Error Report Message Format" } -![error-report] -~ -[error-report]: build/error-report.[svg,png] { width: 70%; page-align: here } +.P4Runtime Error Report Message Format. +[#fig-p4prg] +image::error-report.png[width=400,align="center"] gRPC provides utility functions `ExtractErrorDetails()` and `SetErrorDetails()` -[@gRPCErrorDetails] to easily convert between `grpc::Status` and +cite:[gRPCErrorDetails] to easily convert between `grpc::Status` and `google.rpc.Status`. Please see sections on individual P4Runtime RPCs for details on how @@ -5103,10 +5000,10 @@ Please see sections on individual P4Runtime RPCs for details on how Note that P4Runtime also provides a way for the server to asynchronously report errors which occur when processing stream messages from the client. This error-reporting mechanism is orthogonal to the one described in this section, -which applies to unary RPCs. See the section on [Stream Error -Reporting](#sec-stream-error-reporting) for more information. +which applies to unary RPCs. See the section on xref:sec-stream-error-reporting[Stream Error +Reporting] for more information. -# Atomicity of Individual `Write` and `Read` Operations +== Atomicity of Individual `Write` and `Read` Operations Each individual entity in a batch is guaranteed to be read or written atomically relative to packet forwarding. For example, for every table data plane `apply` @@ -5120,16 +5017,17 @@ data plane methods. The atomicity guarantees provided by P4Runtime for individual `Read` and `Write` operations are the same as the guarantees required by PSA and are described in -details in the PSA specification [@PSAAtomicityOfControlPlaneOps]. +details in the PSA specification cite:[PSAAtomicityOfControlPlaneOps]. -The P4~16~ language introduces an `@atomic` annotation [@P4Concurrency], to +The P4~16~ language introduces an `@atomic` annotation cite:[P4Concurrency], to guarantee atomic data plane execution of entire blocks of P4 code. P4Runtime implementations are required to honor the `@atomic` annotation for `Write` -operations, as well as [non-wildcard `Read` operations](#sec-wildcard-reads), +operations, as well as xref:sec-wildcard-reads[non-wildcard `Read` operations], relative to data plane execution. Consider the following P4 example written for PSA: -~ Begin P4Example +[source,p4] +---- control C1 { typedef bit<10> Index_t; typedef bit<32> Value_t; @@ -5144,7 +5042,7 @@ control C1 { } } } -~ End P4Example +---- If a P4Runtime server is processing messages which write to Register `r1` at index `1`, these writes must not happen between the data plane `read` and @@ -5152,7 +5050,8 @@ index `1`, these writes must not happen between the data plane `read` and Now let's consider the following example: -~ Begin P4Example +[source,p4] +---- control C1 { typedef bit<10> Index_t; typedef bit<32> Value_t; @@ -5169,7 +5068,7 @@ control C1 { } } } -~ End P4Example +---- If a P4Runtime client issues a *wildcard* `Read` on Register `r1`, there is no guarantee that `r1[1] == r1[2]` in the response, as the read for `r1[1]` may @@ -5188,12 +5087,15 @@ If the `@atomic` annotation cannot be honored with the above guarantees by the P4Runtime implementation for a P4-programmable target, we expect the P4 compiler to reject the program. -# `Write` RPC { #sec-write-rpc } +[#sec-write-rpc] +== `Write` RPC The `Write` RPC updates one or more P4 entities on the target. The request is defined as follows: -~ Begin Proto +[source,protobuf] +[%unbreakable] +---- message WriteRequest { uint64 device_id = 1; string role = 6; @@ -5206,29 +5108,29 @@ message WriteRequest { } Atomicity atomicity = 5; } -~ End Proto +---- The `device_id` uniquely identifies the target P4 device. The `role` and `election_id` define the client role and election-id as described in the -[Primary-Backup Arbitration and Controller -Replication](#sec-client-arbitration-and-controller-replication) -section. The server is expected to perform the following checks (in this order) +xref:sec-client-arbitration-and-controller-replication[Primary-Backup Arbitration and Controller +Replication] section. The server is expected to perform the following checks (in this order) before processing the `updates` list: -1. If `device_id` does not match any of the devices known to the P4Runtime +. If `device_id` does not match any of the devices known to the P4Runtime server or if `role` does not match any of the roles for the device, the server must return a `NOT_FOUND` error. -2. If the client is not the primary for (`device_id`, `role`) according to +. If the client is not the primary for (`device_id`, `role`) according to the `election_id` value, the server must return a `PERMISSION_DENIED` error. -3. If the `Write` is attempted before a `ForwardingPipelineConfig` has been set, +. If the `Write` is attempted before a `ForwardingPipelineConfig` has been set, the server must return a `FAILED_PRECONDITION` error. The updates field is a list of P4 entity updates to be applied. Each update is defined as: -~ Begin Proto +[source,protobuf] +---- message Update { enum Type { UNSPECIFIED = 0; @@ -5239,13 +5141,13 @@ message Update { Type type = 1; Entity entity = 2; } -~ End Proto +---- This is modeled as performing an update operation on the given entity against -its entity container. The entity container is either a *logical* table (⪚ -`CounterEntry`) or an actual table (⪚ `TableEntry`) in the P4 data +its entity container. The entity container is either a *logical* table (e.g. +`CounterEntry`) or an actual table (e.g. `TableEntry`) in the P4 data plane. Each entity in the container is uniquely identified by its *key*. Please -refer to the [P4 Entity Messages](#sec-p4-entity-msgs) section for details on +refer to the xref:sec-p4-entity-msgs[P4 Entity Messages] section for details on what parts of the entity specification make up the *key* for each P4 entity. An update can be one of the following types: @@ -5255,14 +5157,14 @@ An update can be one of the following types: If the entity already exists, an `ALREADY_EXISTS` error is returned, and the existing entity remains unchanged. If the entity is malformed, an `INVALID_ARGUMENT` error is usually returned (unless a more specific error - code applies [@gRPCStatusCodes]). If the entity cannot be inserted because the + code applies cite:[gRPCStatusCodes]). If the entity cannot be inserted because the container is already full, a `RESOURCE_EXHAUSTED` error is returned. * `MODIFY`: Modifies the P4 entity to its new specified state. This uses - *assign* or *full-snapshot* semantics, &ie; the entity field contains the + *assign* or *full-snapshot* semantics, i.e. the entity field contains the complete new state of the entity, not a diff from its previous state. If the entity is malformed, an `INVALID_ARGUMENT` error is usually returned (unless a - more specific error code applies [@gRPCStatusCodes]). If the entity does not + more specific error code applies cite:[gRPCStatusCodes]). If the entity does not exist, a `NOT_FOUND` error is returned. * `DELETE`: Deletes the specified P4 entity. If the entity does not exist, a @@ -5272,7 +5174,8 @@ An update can be one of the following types: If an update is not allowed under the given controller role, the server must return a `PERMISSION_DENIED` error for this update. -## Batching and Ordering of Updates { #sec-batching-and-ordering-of-updates} +[#sec-batching-and-ordering-of-updates] +=== Batching and Ordering of Updates P4Runtime supports batching of `Write` operations. The list of updates in a `WriteRequest` is referred to as a *batch*. A batch can consist of arbitrary @@ -5281,26 +5184,26 @@ entity or table (in the case of `TableEntry` entities). The P4Runtime server may choose to reorder updates in a batch when processing them, and/or process updates in parallel. Updates across multiple concurrent -`WriteRequest`s can also be processed interleaved and/or in parallel. +``WriteRequest``s can also be processed interleaved and/or in parallel. However, **the processing of requests must be strictly serializable**. That -is, given a history $S$ of `WriteRequest`s including the responses to those -requests, there must exist an order $L$ for all updates in $S$, such that: - -1. All updates from the same write request must appear as a contiguous - subsequence in $L$, but the order within that subsequence can be arbitrary. -2. For two updates $u_1$ and $u_2$, if the write request containing $u_1$ - completed before the write request of $u_2$ was sent, then $u_1$ must appear - before $u_2$ in $L$. -3. Executing all updates in $L$ sequentially must yield the same response for - every update as in $S$. -4. The observable state of the switch after $S$ (⪚, through the `Read` RPC) - is identical to the one obtained by sequentially executing $L$. +is, given a history latexmath:[$S$] of ``WriteRequest``s including the responses to those +requests, there must exist an order latexmath:[$L$] for all updates in latexmath:[$S$], such that: + +. All updates from the same write request must appear as a contiguous + subsequence in latexmath:[$L$], but the order within that subsequence can be arbitrary. +. For two updates latexmath:[$u_1$] and latexmath:[$u_2$], if the write request containing latexmath:[$u_1$] + completed before the write request of latexmath:[$u_2$] was sent, then latexmath:[$u_1$] must appear + before latexmath:[$u_2$] in latexmath:[$L$]. +. Executing all updates in latexmath:[$L$] sequentially must yield the same response for + every update as in latexmath:[$S$]. +. The observable state of the switch after latexmath:[$S$] (e.g., through the `Read` RPC) + is identical to the one obtained by sequentially executing latexmath:[$L$]. The `Write` RPC demarcates the batch boundary, and can be used to ensure ordering between dependent updates. When the `Write` RPC returns, it is required that all operations in the batch have been committed to the P4 data plane, with the exception of any operations that return an error status. If two updates -from the client depend on each other (⪚ inserting an `ActionProfileMember` +from the client depend on each other (e.g. inserting an `ActionProfileMember` followed by pointing a `TableEntry` to it) and the updates are not split into separate batches, then the behavior may be non-deterministic. Similarly, clients can invoke multiple outstanding `Write` RPCs. If the updates across @@ -5312,8 +5215,8 @@ wants to enforce that batches are applied in a specific order, each `Write` call should be sent sequentially, waiting for the previous call to be acknowledged before sending the next one. - -## Batch Atomicity { #sec-batch-atomicity} +[#sec-batch-atomicity] +=== Batch Atomicity A P4Runtime server may arbitrarily reorder messages within a batch. The atomicity semantics of the batch operations are defined by the `Atomicity` @@ -5339,14 +5242,14 @@ enum. A P4Runtime server is required to support only the modes marked as of the rollback mechanism are outside the scope of this specification. One possibility is to create a shadow copy of both the software and hardware state at the start, and restore it upon failure. - - If a P4Runtime server does not support this option at all, an - `UNIMPLEMENTED` error is returned at all times. If a P4Runtime - supports some batches in an rollback way but not others (⪚ it is - more straightforward to implement batches that contain only `INSERT` - operations, vs. those that contain `DELETE` operations), an - `UNIMPLEMENTED` error is returned when the batch cannot be executed - in a data plane-atomic way. ++ +If a P4Runtime server does not support this option at all, an +`UNIMPLEMENTED` error is returned at all times. If a P4Runtime +supports some batches in an rollback way but not others (e.g. it is +more straightforward to implement batches that contain only `INSERT` +operations, vs. those that contain `DELETE` operations), an +`UNIMPLEMENTED` error is returned when the batch cannot be executed +in a data plane-atomic way. * *Optional*: `DATAPLANE_ATOMIC`: This is the strictest requirement where the entire batch must be atomic from a data plane point of view. Every data plane @@ -5360,11 +5263,11 @@ enum. A P4Runtime server is required to support only the modes marked as all operations within the batch. At the end (if there were no errors), a simple pointer-swap like approach can be used to switch to this half of the table. - - If a P4Runtime server does not support this option at all, an `UNIMPLEMENTED` - error is returned at all times. If a P4Runtime supports some batches in an - atomic way but not others, an `UNIMPLEMENTED` error is returned when the batch - cannot be executed in a data plane-atomic way. ++ +If a P4Runtime server does not support this option at all, an `UNIMPLEMENTED` +error is returned at all times. If a P4Runtime supports some batches in an +atomic way but not others, an `UNIMPLEMENTED` error is returned when the batch +cannot be executed in a data plane-atomic way. There is no expectation that a given client must always use the same `Atomicity` enum value. At any given time, the client is free to compose batches and assign @@ -5372,22 +5275,22 @@ atomicity mode as it sees fit. For example, for a set of entities, a client may decide to use `DATAPLANE_ATOMIC` at one time and default behavior (`CONTINUE_ON_ERROR`) at other times. -## Error Reporting +=== Error Reporting -Please see section [Error Reporting Messages](#sec-error-reporting-messages) for +Please see section xref:sec-error-reporting-messages[Error Reporting Messages] for information on error reporting messages and guidelines. P4Runtime server will populate `grpc::Status` as follows: -1. If all batch updates succeeded, set `grpc::Status::code_` to `OK` and do not +. If all batch updates succeeded, set `grpc::Status::code_` to `OK` and do not populate any other field. -2. If an error is encountered before even trying to attempt individual batch +. If an error is encountered before even trying to attempt individual batch updates, set `grpc::Status::code_` that best describes that RPC-wide error. For example, use `UNAVAILABLE` if the P4Runtime service is not yet ready to handle requests. Set `error_message_` to describe the issue. Do not set `error_details` in this case. -3. Otherwise, if one or more updates in the batch (`WriteRequest.updates`) +. Otherwise, if one or more updates in the batch (`WriteRequest.updates`) failed, set `grpc::Status::code_` to `UNKNOWN`. For example, one update in the batch may fail with `RESOURCE_EXHAUSTED` and another with `INVALID_ARGUMENT`. A `p4.Error` message is used to capture the status of @@ -5398,45 +5301,47 @@ populate `grpc::Status` as follows: updates. If some of the updates were successful, the corresponding `p4.Error` should set the code to `OK` and omit other fields. -~ Begin Prototext -# Example of a grpc::Status returned for a Write RPC with a batch of 3 updates. -# The first and third updates encountered an error, while the second update -# succeeded. -code_ = 2 # UNKNOWN +[source,protobuf] +---- +Example of a grpc::Status returned for a Write RPC with a batch of 3 updates. +The first and third updates encountered an error, while the second update +succeeded. +code_ = 2 // UNKNOWN error_message_ = "Write failure." binary_error_details { - code: 2 # UNKNOWN + code: 2 // UNKNOWN message: "Write failure." details { - canonical_code: 8 # RESOURCE_EXHAUSTED + canonical_code: 8 // RESOURCE_EXHAUSTED message: "Table is full." space: "targetX-psa-vendorY" - code: 500 # ERR_TABLE_FULL + code: 500 // ERR_TABLE_FULL } details { - canonical_code: 0 # OK + canonical_code: 0 // OK } details { - canonical_code: 6 # ALREADY_EXISTS + canonical_code: 6 // ALREADY_EXISTS message: "Entity already exists." space: "targetX-psa-vendorY" - code: 600 # ERR_ENTITY_ALREADY_EXISTS + code: 600 // ERR_ENTITY_ALREADY_EXISTS } } -~ End Prototext +---- -# `Read` RPC +== `Read` RPC The `Read` RPC retrieves one or more P4 entities from the P4Runtime server. The request is defined as: -~ Begin Proto +[source,protobuf] +---- message ReadRequest { uint64 device_id = 1; string role = 3; repeated Entity entities = 2; } -~ End Proto +---- The `device_id` uniquely identifies the target P4 device. If it does not match any of the devices known to the P4Runtime server, the server must return a @@ -5455,20 +5360,21 @@ require an `election_id`, and they do not require the presence of an open The `Read `response consists of a sequence of messages (a gRPC `stream`) with each message defined as: -~ Begin Proto +[source,protobuf] +---- message ReadResponse { repeated Entity entities = 1; } -~ End Proto +---- The `entities` repeated field is a list of P4 entities retrieved. The client reads from the returned stream until it is closed by the server when there are no more messages. In case of error, the stream is closed prematurely by the server and the client obtains the error status (in C++ the error status is obtained by calling the `Finish()` method on the stream object -[@gRPCStreamC]). +cite:[gRPCStreamC]). -## Nomenclature +=== Nomenclature * request : An element of the `p4.ReadRequest.entities` repeated field. @@ -5477,14 +5383,15 @@ obtained by calling the `Finish()` method on the stream object Each *request* acts as a query filter for that entity type. If a *request* fully specifies the entity key, the `Read` operation should retrieve a single P4 -entity. Please refer to the [P4 Entity Messages](#sec-p4-entity-msgs) section +entity. Please refer to the xref:sec-p4-entity-msgs[P4 Entity Messages] section for details on what parts of the entity specification make up the entity *key*. -## Wildcard Reads { #sec-wildcard-reads} +[#sec-wildcard-reads] +=== Wildcard Reads P4Runtime allows wildcard read of P4 entities. A *request* may omit or use default values for parts of the entity key to achieve wildcard behavior. Please -refer to the [P4 Entity Messages](#sec-p4-entity-msgs) section for details on +refer to the xref:sec-p4-entity-msgs[P4 Entity Messages] section for details on what parts of the entity can be wildcarded in a given *request*. For example, in a *request* of type `CounterEntry`: @@ -5498,25 +5405,26 @@ For example, in a *request* of type `CounterEntry`: To read the entire forwarding state for a given device, the P4Runtime client can generate the following `ReadRequest`: -~ Begin Prototext -device_id: +[source,protobuf] +---- +device_id: "DEVICE_ID" entities { - extern_entry { } # read all extern instances for all supported extern types - table_entry { } # read all table entries for all tables - action_profile_member { } # read all members for all action profiles - action_profile_group { } # read all groups for all action profiles + extern_entry { } // read all extern instances for all supported extern types + table_entry { } // read all table entries for all tables + action_profile_member { } // read all members for all action profiles + action_profile_group { } // read all groups for all action profiles ... } -~ End Prototext +---- The `entity` oneof field in the `Entity` message must always be set, or the server must return an `INVALID_ARGUMENT` error. In other words, P4Runtime does not support performing a wildcard read on the entire forwarding state by including an empty `Entity` message in the `ReadRequest`. The main reason for this decision is to prevent backwards-compatibility issues if new entity types -are added to the `entity` oneof [@ProtoOneOfBackwardsCompatibility]. +are added to the `entity` oneof cite:[ProtoOneOfBackwardsCompatibility]. -## Batch Processing +=== Batch Processing A P4Runtime server may arbitrarily reorder requests within a batch to maximize performance. There is no requirement that a particular entity type *request* @@ -5524,27 +5432,27 @@ appears only once in the batch. A P4Runtime server will process the batch as follows: -1. Lock state (preventing new writes) and validate each *request* in the batch: +. Lock state (preventing new writes) and validate each *request* in the batch: - 1. If it is a valid *request*, perform the read; + .. If it is a valid *request*, perform the read; - 1. If the read was successful, return the entities read in + ... If the read was successful, return the entities read in `ReadResponse` stream. - 2. If the read failed (exception / critical-error), prepare a `p4.Error` + ... If the read failed (exception / critical-error), prepare a `p4.Error` with code set to `INTERNAL`. - 2. If the *request* is invalid (invalid-argument, not-supported, etc.), + .. If the *request* is invalid (invalid-argument, not-supported, etc.), prepare a `p4.Error` with relevant canonical code to capture the error. -2. Unlock the state (allowing new writes); +. Unlock the state (allowing new writes); -3. Close the `ReadResponse` stream and return a `grpc::Status` as follows: +. Close the `ReadResponse` stream and return a `grpc::Status` as follows: - 1. If no errors were encountered, set code to `OK` and do not populate any + .. If no errors were encountered, set code to `OK` and do not populate any other field. - 2. Otherwise, the overall code should be set to `UNKNOWN`. See section - [Error Reporting Messages](#sec-error-reporting-messages) for information + .. Otherwise, the overall code should be set to `UNKNOWN`. See section + xref:sec-error-reporting-messages[Error Reporting Messages]for information on error reporting messages and guidelines. Assemble a list of `p4.Error` messages (from step 1 above) such that each element reflects the status of the request in the batch at the same location (1:1 @@ -5552,14 +5460,14 @@ A P4Runtime server will process the batch as follows: `google.rpc.Status.details` field. This behavior also matches `Write` RPC. -### Example +==== Example If a client asked to read `{a,b,c,d}` and `b` and `d` *requests* didn't validate, the server will return entities corresponding to `a` and `c`, followed by a status `{p4.Error(OK), p4.Error(xxx), p4.Error(yyy), p4.Error(OK)}` in the `details` field. -The P4Runtime server is not required to perform any optimization (⪚ merge two +The P4Runtime server is not required to perform any optimization (e.g. merge two *requests* in the *batch* if one is a subset of other). As a result of this, it is possible for the `ReadResponse` to contain the same entity more than once. If performance is a concern, the P4Runtime client should handle this merging. @@ -5575,10 +5483,10 @@ This could be from the same or multiple clients. P4Runtime is based on gRPC which provides a concurrent server design. A server implementation that supports concurrent RPC handlers may choose to maximize performance by using a multi-reader lock (also known as multiple-readers/single-writer lock). -Conversely (⪚ in a single-threaded architecture), it may choose to serialize +Conversely (e.g. in a single-threaded architecture), it may choose to serialize `Read` RPC processing. -## Parallelism of Read and Write Requests +=== Parallelism of Read and Write Requests A P4Runtime server may be implemented to serve at most one `ReadRequest` or `WriteRequest` message at a time, sequentially. It @@ -5616,19 +5524,20 @@ meets all of the requirements above. It is possible to meet the requirements of this specification and perform even more requests in parallel than that example -implementation allows, ⪚ if the server somehow determined that two +implementation allows, e.g. if the server somehow determined that two `WriteRequest` messages that inserted entries to the same table could not affect the results of the other, they could also be performed in parallel. It is not required that a P4Runtime server do this, and may be difficult to implement correctly. -# `SetForwardingPipelineConfig` RPC +== `SetForwardingPipelineConfig` RPC A P4Runtime client may configure the P4Runtime target with a new P4 pipeline by invoking the `SetForwardingPipelineConfig RPC`. The request is defined as: -~ Begin Proto +[source,protobuf] +---- message SetForwardingPipelineConfigRequest { enum Action { UNSPECIFIED = 0; @@ -5644,16 +5553,16 @@ message SetForwardingPipelineConfigRequest { Action action = 4; ForwardingPipelineConfig config = 5; } -~ End Proto +---- The server is expected to perform the following checks (in this order) before performing the required `action`: -1. If `device_id` does not match any of the devices known to the P4Runtime +. If `device_id` does not match any of the devices known to the P4Runtime server or if `role` does not match any of the roles for the device, the server must return a `NOT_FOUND` error. -2. If the client is not the primary for (`device_id`, `role`) according to +. If the client is not the primary for (`device_id`, `role`) according to the `election_id` value, the server must return a `PERMISSION_DENIED` error. The action is the type of configuration action requested, it can be one of: @@ -5677,7 +5586,7 @@ The action is the type of configuration action requested, it can be one of: forwarding state in the target is updated by replaying the write requests to the target device since the last config was saved. Config should not be provided for this action type. Returns a `NOT_FOUND` error if no saved config - is found, &ie; if no `VERIFY_AND_SAVE` action preceded this one. Returns an + is found, i.e. if no `VERIFY_AND_SAVE` action preceded this one. Returns an `INVALID_ARGUMENT` error if a config is provided with this message. * `RECONCILE_AND_COMMIT`: verifies, saves and realizes the given config, while @@ -5693,22 +5602,23 @@ The action is the type of configuration action requested, it can be one of: The `config` field is a message of type `ForwardingPipelineConfig` that carries the P4Info, the opaque target-dependent forwarding-pipeline configuration data -(⪚ generated by the P4 compiler for the target), and, optionally, the cookie -to uniquely identify such configuration. See the [Forwarding-Pipeline -Configuration](#sec-p4-fwd-pipe-config) section for details. +(e.g. generated by the P4 compiler for the target), and, optionally, the cookie +to uniquely identify such configuration. See the xref:sec-p4-fwd-pipe-config[Forwarding-Pipeline +Configuration] section for details. A P4Runtime server running on a non-programmable device may not -support `SetForwardingPipelineConfig` (⪚ the forwarding-pipeline +support `SetForwardingPipelineConfig` (e.g. the forwarding-pipeline config is part of the device's software image, or is supplied using a different mechanism). In such cases, the RPC should return an `UNIMPLEMENTED` error. -# `GetForwardingPipelineConfig` RPC +== `GetForwardingPipelineConfig` RPC The forwarding-pipeline configuration of the target can be retrieved by invoking the `GetForwardingPipelineConfig RPC`. The request is defined as: -~ Begin Proto +[source,protobuf] +---- message GetForwardingPipelineConfigRequest { enum ResponseType { ALL = 0; @@ -5719,7 +5629,7 @@ message GetForwardingPipelineConfigRequest { uint64 device_id = 1; ResponseType response_type = 2; } -~ End Proto +---- The `device_id` uniquely identifies the target P4 device. A `NOT_FOUND` error is returned if the `device_id` is not recognized by the P4Runtime server. @@ -5743,11 +5653,12 @@ its value can be one of: The response contains the `ForwardingPipelineConfig` for the specified device: -~ Begin Proto +[source,protobuf] +---- message GetForwardingPipelineConfigResponse { ForwardingPipelineConfig config = 1; } -~ End Proto +---- If a P4Runtime server is in a state where the forwarding-pipeline config is not known, the top-level `config` field will be unset in the response. Examples are @@ -5769,9 +5680,10 @@ If a P4Runtime server supports both `SetForwardingPipelineConfig` as well as returning the `p4_device_config`, there should be read-write symmetry between `SetForwardingPipelineConfig` and `GetForwardingPipelineConfig` RPCs. -# P4Runtime Stream Messages +== P4Runtime Stream Messages -## Packet I/O { #sec-packet-i_o} +[#sec-packet-i_o] +=== Packet I/O P4Runtime supports controller packet-in and packet-out by means of `PacketIn` and `PacketOut` stream messages, respectively. @@ -5781,10 +5693,10 @@ and `PacketOut` stream messages, respectively. message received by the server from a client which is not allowed to send such messages based on its current role definition must be dropped. The server may also generate a `StreamMessageResponse` message with the `error` field set to -report the error to the client. See the section on [Stream Error -Reporting](#sec-stream-error-reporting) for more information on `error`. +report the error to the client. See the section on xref:sec-stream-error-reporting[Stream Error +Reporting]for more information on `error`. -As introduced in the [`ControllerPacketMetadata`](#sec-controller-packet-meta) +As introduced in the xref:sec-controller-packet-meta[`ControllerPacketMetadata`] section, such messages can carry arbitrary metadata specified by means of P4 headers annotated with `@controller_header`. The expected metadata is described in the P4Info using the `ControllerPacketMetadata` messages. @@ -5792,7 +5704,8 @@ in the P4Info using the `ControllerPacketMetadata` messages. Both `PacketIn` and `PacketOut` stream messages share the same fields and are defined as follows: -~ Begin Proto +[source,protobuf] +---- // Packet sent from the controller to the switch. message PacketOut { bytes payload = 1; @@ -5810,7 +5723,7 @@ message PacketMetadata { uint32 metadata_id = 1; bytes value = 2; } -~ End Proto +---- * `payload` is used to carry the full packet content, including the headers. @@ -5824,13 +5737,13 @@ message PacketMetadata { to be consistent with what is specified in the corresponding P4Info `ControllerPacketMetadata.Metadata` message with the same `id`, in the following sense: - + If `ControllerPacketMetadata.Metadata.bitwidth` is set, or if + ** If `ControllerPacketMetadata.Metadata.bitwidth` is set, or if `ControllerPacketMetadata.Metadata.type_name` is set and `P4NewTypeSpec.translated_type.sdn_bitwidth` is set in the `P4NewTypeSpec` for the type name, then `PacketMetadata.value` must be a binary string of the specified bit width conforming to the [Bytestrings](#sec-bytestrings) requirements. - + If `ControllerPacketMetadata.Metadata.type_name` is set and + ** If `ControllerPacketMetadata.Metadata.type_name` is set and `P4NewTypeSpec.translated_type.sdn_string` is set in the `P4NewTypeSpec` message for the specified type name, then `PacketMetadata.value` can be an arbitrary SDN string subject to the @@ -5842,7 +5755,7 @@ message PacketMetadata { the section on [Stream Error Reporting](#sec-stream-error-reporting) for more information on `error`. -## Client Arbitration Update +=== Client Arbitration Update P4Runtime's client arbitration mechanism ensures that only the current primary can modify state on the switch, and that the `election_id` is monotonically @@ -5858,9 +5771,7 @@ controller first opens a bidirectional stream channel to the server via `StreamChannel` for each device and sends a `StreamMessageRequest` message. The controller populates the `MasterArbitrationUpdate` field in this message using its `role` and `election_id` and the `device_id` of the device, as explained -in detail in the [Client Arbitration and Controller -Replication](#sec-client-arbitration-and-controller-replication) -section. For any given `(device_id, role)`, the P4Runtime server keeps track +in detail in the xref:sec-client-arbitration-and-controller-replication[Client Arbitration and Controller Replication] section. For any given `(device_id, role)`, the P4Runtime server keeps track of the highest `election_id` that it has ever received. If a controller's `election_id` is equal to the highest `election_id` that the server has ever received, that controller is the primary. All other controllers are backups. @@ -5870,12 +5781,13 @@ primary. There can be at most one primary, because for any given This invariant must be maintained across in-service software upgrades, and the P4Runtime server must remember the highest `election_id` after such a restart. -However, across a [full restart](#sec-restarts), the `election_id` must be +However, across a xref:sec-restarts[full restart], the `election_id` must be reset. In fact, a full restart is the only way to reset the `election_id`. The `MasterArbitrationUpdate` message is defined as follows: -~ Begin Proto +[source,protobuf] +---- message MasterArbitrationUpdate { uint64 device_id = 1; // The role for which the primary client is being arbitrated. For use-cases @@ -5903,7 +5815,7 @@ message Role { // out-of-scope of P4Runtime. .google.protobuf.Any config = 2; } -~ End Proto +---- Note that the `status` field in the `MasterArbitrationUpdate` message is not populated by the controller. This field is populated by the P4Runtime server @@ -5911,9 +5823,9 @@ when it sends a `StreamMessageResponse` message back to the controller, in which it populates the `MasterArbitrationUpdate` message using the `device_id`, `role`, and `election_id` it previously received from the controller. The server also populates the `status` field in the `MasterArbitrationUpdate` according to -the rules in an [earlier section](#sec-arbitration-updates). +the rules in an xref:sec-arbitration-updates[earlier section]. -### Unset Election ID +==== Unset Election ID The sender need not specify an `election_id`. If the `election_id` is unset, the sender's `election_id` is considered lower than any @@ -5927,18 +5839,19 @@ Note that election_id : { high: 0 low: 0 } ``` is different from an unset `election_id`, see -[the section on default-valued fields](#sec-default-valued-fields). +xref:sec-default-valued-fields[the section on default-valued fields]. -## Digest Messages +=== Digest Messages -See the [DigestEntry](#sec-digestentry) section. +See <<#sec-digestentry>>. -## Table Idle Timeout Notification { #sec-table-idle-timeout-notification} +[#sec-table-idle-timeout-notification] +=== Table Idle Timeout Notification When a table supports idle timeout (as per the P4Info message), the primary client can specify a TTL value for each entry in the table (see -[Idle-timeout](#sec-idle-timeout) section). If the data plane entry is not hit +xref:sec-idle-timeout[Idle-timeout] section). If the data plane entry is not hit for a lapse of time greater or equal to the TTL, the P4Runtime server should, with best effort, generate an `IdleTimeoutNotification` message on the `StreamChannel` bidirectional stream to the primary client. The primary client @@ -5976,7 +5889,8 @@ server software, the channel or the client can handle. Here is a reasonable pseudo-code implementation for idle timeout for table entries: -~ Begin CPP +[source,c++] +---- IdleTimeoutStream stream; scanning_interval = 10ms; @@ -6002,19 +5916,19 @@ while (true) { } sleep(scanning_interval); } -~ End CPP +---- -## Architecture-Specific Notifications +=== Architecture-Specific Notifications P4Runtime supports streaming arbitrary Protobuf messages between the server and -the client on `StreamChannel`, by including an `Any` Protobuf field [@ProtoAny] +the client on `StreamChannel`, by including an `Any` Protobuf field cite:[ProtoAny] named `other` in both `StreamMessageRequest` and `StreamMessageResponse`. This enables support for architecture-specific externs which require asynchronous -streaming of data from the server to the client, much like the [PSA `Digest` -extern](#sec-digestentry). See section on [Extending P4Runtime for non-PSA -Architectures](#sec-extending-p4runtime) for more information. +streaming of data from the server to the client, much like the xref:sec-digestentry[PSA `Digest` +extern]. See section on xref:sec-extending-p4runtime[Extending P4Runtime for non-PSA Architectures] for more information. -## Stream Error Reporting { #sec-stream-error-reporting} +[#sec-stream-error-reporting] +=== Stream Error Reporting The P4Runtime server can asynchronously report errors which occur when processing `StreamMessageRequest` messages, using the `error` message field (of @@ -6025,7 +5939,7 @@ out-of-band mechanism to enable / disable this feature. The `StreamError` message has the following fields: * `canonical_code`, which must be set to the appropriate canonical error code - [@gRPCStatusCodes]. + cite:[gRPCStatusCodes]. * `message`, an optional developer-facing error message describing the error. * `space` and `code`, which are optional and can be used by vendors to provide additional details on the error. `code` is a numeric error code drawn from a @@ -6043,7 +5957,7 @@ The `StreamError` message has the following fields: `PacketOut` message received on the stream channel) so that the client can know exactly what packet-out triggered the error. -The appropriate canonical error code [@gRPCStatusCodes] should be used when +The appropriate canonical error code cite:[gRPCStatusCodes] should be used when populating the `canonical_code` field. For example: * if a controller is not allowed to send a `PacketOut` message under its @@ -6056,26 +5970,29 @@ populating the `canonical_code` field. For example: in P4Info, the code should be set to `INVALID_ARGUMENT`. The server may choose to assign a lower priority to error reporting messages and -drop them first if the stream channel comes under heavy load, ⪚ because of a +drop them first if the stream channel comes under heavy load, e.g. because of a burst of `PacketIn` messages. Note that client arbitration errors are never reported using the `StreamError` message. Invalid `MasterArbitrationUpdate` messages sent by the client cause the stream to be terminated and the appropriate error code to be returned to the -client immediately. See section [#sec-arbitration-updates]. +client immediately. See section <<#sec-arbitration-updates>>. -### Examples of `StreamError` Messages +[#sec-stream-error-messages] +==== Examples of `StreamError` Messages * **Malformed packet-out metadata.** If the server receives a `PacketOut` message with a `metadata` field with id 7 which is not included in the P4Info `ControllerPacketMetadata` message for "packet_out", the server may send the following `StreamMessageResponse` back to the client: -~ Begin Prototext + +[source,protobuf] +---- error { - canonical_code: 3 # INVALID_ARGUMENT + canonical_code: 3 // INVALID_ARGUMENT message: "Unknown metadata field id 7." packet_out { - # copied from the PacketOut message received from the client + // copied from the PacketOut message received from the client packet_out { payload: ... metadata { @@ -6083,63 +6000,66 @@ error { name: "I_should_not_be_here" bitwidth: 32 } - # more metadata + // more metadata + ... } } -} -~ End Prototext +---- * **Packet-out which exceeds the MTU.** If the server receives a `PacketOut` message which requires injecting a raw data plane packet that exceeds the MTU (Maximum Transmission Unit) for the egress link, the server may generate the following `StreamMessageResponse`: -~ Begin Prototext + +[source,protobuf] +---- error { - canonical_code: 3 # INVALID_ARGUMENT + canonical_code: 3 // INVALID_ARGUMENT message: "Packet exceeds the MTU for port." space: "targetX-psa-vendor1" - code: 123 # MTU_EXCEEDED + code: 123 // MTU_EXCEEDED packet_out { - # we do not set the packet_out field as it does not provide any - # extra information to the client + // we do not set the packet_out field as it does not provide any + // extra information to the client } } -~ End Prototext +---- -# `Capabilities` RPC +== `Capabilities` RPC The `Capabilities` RPC offers a mechanism through which a P4Runtime client can discover the capabilities of the P4Runtime server implementation. At the moment, the `CapabilitiesRequest` message is empty and the `CapabilitiesResponse` message only includes the `p4runtime_api_version` string field. This field must -be set to the full semantic version string [@SemVer] corresponding to the -version of the P4Runtime API implemented by the server, ⪚ "1.1.0-rc.1". +be set to the full semantic version string cite:[SemVer] corresponding to the +version of the P4Runtime API implemented by the server, e.g. "1.1.0-rc.1". Future versions of P4Runtime may introduce more advanced capability discovery -features. For example, P4Runtime supports three [atomicity -modes](#sec-batch-atomicity) for `WriteRequest` batches, two of them being +features. For example, P4Runtime supports three xref:sec-batch-atomicity[atomicity +modes] for `WriteRequest` batches, two of them being optional. In the future we may decide to leverage the `CapabilitiesResponse` message to enable the server to report to the client which subset of these atomicity modes is supported. The semantic version string included in `CapabilitiesResponse` can be used by the client to determine which exact feature set is implemented by the server, -since [minor releases](#sec-p4runtime-versioning) may introduce new +since xref:sec-p4runtime-versioning[minor releases] may introduce new functionality. However, because the `Capabilities` RPC itself was introduced in version 1.1 of P4Runtime, the client should assume that any server which does -not implement this RPC (&ie; an `UNIMPLEMENTED` error is returned by the +not implement this RPC (i.e. an `UNIMPLEMENTED` error is returned by the P4Runtime service) implements an older version of the P4Runtime specification (1.0 or a pre-release version of 1.0). -# Portability Considerations +== Portability Considerations -## PSA Metadata Translation { #sec-psa-metadata-translation} +[#sec-psa-metadata-translation] +=== PSA Metadata Translation The *Portable Switch Architecture* (PSA) defines standard metadata, whose data plane types are different on different PSA targets. In order to enable uniform programming of multiple PSA targets, a centralized remote controller may define its own types and numbering of such PSA standard metadata -[@PSATranslation]. For such metadata, a translation between the controller's +cite:[PSATranslation]. For such metadata, a translation between the controller's metadata values and the corresponding target-specific metadata values is required at runtime. In this section, we will base our discussions on port metadata, although the same translation principles apply to other standard PSA @@ -6147,14 +6067,11 @@ metadata such as class of service. Since the `@p4runtime_translation` annotation can be applied to any user-defined type, these principles also apply to translated types which are not declared as part of the PSA architecture. -~ Figure { #fig-psa-metadata-translation; \ -caption: "P4Runtime Metadata Translation for the Portable Switch Architecture" } -![psa-metadata-translation] -~ -[psa-metadata-translation]: build/psa-metadata-translation.[svg,png] \ -{ height: 7cm; page-align: here } +.P4Runtime Metadata Translation for the Portable Switch Architecture +[#fig-psa-metadata-translation] +image::psa-metadata-translation.png[width=400,align="center"] -Figure [#fig-psa-metadata-translation] illustrates a motivating example, +<<#fig-psa-metadata-translation>> illustrates a motivating example, where a centralized controller is controlling two P4Runtime targets in a fabric. Switch 1 and Switch 2 use different PSA devices, each defining its own port type and number space. In this example, Switch 1 uses a device with 9-bit space for @@ -6165,30 +6082,32 @@ numbers to a target's 9-bit or 10-bit port numbers is input to the switch via the non-forwarding switch config data that is delivered separately to the switch. -### Translation of Port Numbers { #sec-translation-of-port-numbers} +[#sec-translation-of-port-numbers] +==== Translation of Port Numbers In order to support the above SDN use case, P4Runtime requires translation of port metadata values between the controller's space and the PSA device's space as needed. Such translation is enabled by identifying a P4 entity (match field, action parameter, controller-header field or other) as being a PSA port metadata type. For this purpose, PSA defines the port metadata field type using special -[user-defined P4 types](#sec-user-defined-types), namely `PortId_t` and +xref:sec-user-defined-types[user-defined P4 types], namely `PortId_t` and `PortIdInHeader_t`, instead of standard P4 bitstrings. The P4Info entries for all P4 entities whose type is one of the special PSA port types use a controller-defined 32-bit type instead of the data plane bitwidth defined in the P4 program. The following PSA port metadata types are defined in *psa.p4* for the PSA device in Switch 1. -~ Begin P4Example +[source,p4] +---- @p4runtime_translation("p4.org/psa/v1/PortId_t", 32) type bit<9> PortId_t; @p4runtime_translation("p4.org/psa/v1/PortIdInHeader_t", 32) type bit<32> PortIdInHeader_t; -~ End P4Example +---- The first argument to the `@p4runtime_translation` annotation is a URI that -indicates to the P4Runtime server which numerical mapping --- provided by the -out-of-band switch configuration mechanism --- to use to translate between the +indicates to the P4Runtime server which numerical mapping — provided by the +out-of-band switch configuration mechanism — to use to translate between the SDN value and the data plane value. The second argument is the bitwidth of the SDN representation of the translated entity (32-bit in the case of ports). @@ -6198,7 +6117,8 @@ ports in the device-specific port number space. P4Runtime reserves device-independent and controller-specific 32-bit constants for the CPU port and the recirculation port as follows: -~ Begin Proto +[source,protobuf] +---- enum SdnPort { SDN_PORT_UNKNOWN = 0; @@ -6213,22 +6133,23 @@ enum SdnPort { SDN_PORT_RECIRCULATE = 0xfffffffa; SDN_PORT_CPU = 0xfffffffd; } -~ End Proto +---- -The switch config will map `SDN_PORT_RECIRCULATE` and `SDN_PORT_CPU` --- as well -as any SDN port number corresponding to a "regular" front-panel port --- to the +The switch config will map `SDN_PORT_RECIRCULATE` and `SDN_PORT_CPU` — as well +as any SDN port number corresponding to a "regular" front-panel port — to the corresponding device-specific values, in order to enable the P4Runtime server to perform the translation. The sub-sections below detail the translation mechanics for different usage of PSA port types in P4 programs. -### Translation of Packet-IO Header Fields +==== Translation of Packet-IO Header Fields Port type fields can be part of header types. For example, ports may be part of Packet IO headers, as in the following example:. -~ Begin P4Example +[source,p4] +---- @controller_header("packet_out") header PacketOut_t { PortIdInHeader_t egress_port; @@ -6238,7 +6159,7 @@ header PacketOut_t { header PacketIn_t { PortIdInHeader_t ingress_port; } -~ End P4Example +---- The header-level annotation `@controller_header` is a standard P4Runtime annotation that identifies a header type for a controller packet-out or @@ -6248,7 +6169,7 @@ packet-out metadata (`egress_port`) value of width 32-bit from the given set of SDN port values in the switch config. The server will then translate the SDN port value into the device-specific port value from the mapping provided in the out-of-band switch configuration (the mapping can be identified using the -translation URI --- first argument to the `@p4runtime_translation` +translation URI — first argument to the `@p4runtime_translation` annotation). Any subsequent reference to the `egress_port` field in the data plane will use the translated value. `PortIdInHeader_t` is used in the header definition instead of `PortId_t` to guarantee byte-aligned headers in @@ -6264,12 +6185,13 @@ config. The server will then insert the translated controller-specific value in the packet-in metadata fields before sending the packet over the stream channel to the controller. -### Translation of Match Fields +==== Translation of Match Fields Port type entities, particularly ingress and egress port standard metadata, may be used as match fields in a P4 table's match key as shown in the example below: -~ Begin P4Example +[source,p4] +---- table t { key = { istd.ingress_port: exact; // PSA standard metadata ingress port @@ -6278,7 +6200,7 @@ table t { drop; } } -~ End P4Example +---- Table `t` has an exact match on PSA standard metadata ingress port (`istd.ingress_port`). Since the field is of type `PortId_t`, the P4Info @@ -6299,12 +6221,13 @@ require that for these match kinds the port match be either *de facto* "exact" (0xffffffff mask for `TERNARY`, prefix-length of 32 for `LPM`, or same low and high bounds for `RANGE`) or "don't care". -### Translation of Action Parameters +==== Translation of Action Parameters `PortId_t` type parameters can be part of a P4 action definition as shown in the example below: -~ Begin P4Example +[source,p4] +---- action a(PortId_t p) { istd.egress_port = p; // PSA standard metadata egress port } @@ -6317,7 +6240,7 @@ table t { a; } } -~ End P4Example +---- The controller may write entries in table `t` with action `a` to set the egress port as shown in the P4 code above. The action parameter `p` is of type @@ -6327,7 +6250,7 @@ translation is required for this parameter. The P4Runtime server will use the switch configuration to translate action parameter values between the controller and the target device. -### Port Translation for PSA Extern APIs +==== Port Translation for PSA Extern APIs The P4Runtime API for action selectors supports specifying a watch field per member in an action profile group that is programmed in a selector. This field @@ -6347,25 +6270,27 @@ representation of the port(s). The P4Runtime server will translate these SDN ports to device-specific port numbers for multicasting and cloning in the data plane. -### Using Port as an Index to a Register, Indirect Counter or Indirect Meter +==== Using Port as an Index to a Register, Indirect Counter or Indirect Meter P4Runtime supports using a translated value (`PortId_t` or any other translated type for which the underlying built-in type is `bit`) as an index to a register, indirect counter, or indirect meter. -~ Begin P4Example +[source,p4] +---- Counter /* counter entry type */, PortId_t /* index type */>( 32w1024, PSA_CounterType_t.PACKETS) counter; action a(PortId_t p) { istd.egress_port = p; // PSA standard metadata egress port counter.count(p); } -~ End P4Example +---- This P4 Counter declaration will translate into the following entry in the P4Info messsage: -~ Begin Prototext +[source,protobuf] +---- counters { preamble { id: 0x12000001 @@ -6378,17 +6303,18 @@ counters { name: "PortId_t" } } -~ End Prototext +---- The controller may read and write counter values from indexed counter `counter` using SDN port numbers as indices, and not device-specific port numbers. The `index_type_name` field in the P4Info message is a signal to the P4Runtime server that translation is required. -# P4Runtime Versioning { #sec-p4runtime-versioning} +[#sec-p4runtime-versioning] +== P4Runtime Versioning P4Runtime follows the Google guidelines for versioning cloud APIs -[@APIVersioning]. We use a `MAJOR.MINOR.PATCH` style version number scheme and +cite:[APIVersioning]. We use a `MAJOR.MINOR.PATCH` style version number scheme and we increment the: * `MAJOR` version when we make incompatible API changes, @@ -6403,13 +6329,13 @@ different Protobuf packages, `p4` depends on `p4.config` and is not meant to be used without it, which is why both packages use the same versioning scheme and the same versioning cadence. -As recommended in [@APIVersioning], we may consider using pre-GA release +As recommended in cite:[APIVersioning], we may consider using pre-GA release suffixes (such as *alpha* or *beta*) in the Protobuf package name for future major versions, although we have chosen not to do so when developing version 1 (v1). Within a major version, the API must be evolved in a Protobuf -backwards-compatible manner. [@APIVersioningBackwardsCompatibility] describes +backwards-compatible manner. cite:[APIVersioningBackwardsCompatibility] describes what constitute a backwards-compatible change. We expect `MAJOR` version bumps to be a **rare** event. @@ -6421,10 +6347,11 @@ attempting to connect to the corresponding service. We may consider including a P4Runtime RPC to query minor + patch version numbers in future releases. All versions of P4Runtime, including pre-release versions, are tagged in the -P4Runtime Github repository [@P4RuntimeRepo] and the version label follows -semantic versioning rules [@SemVer]. +P4Runtime Github repository cite:[P4RuntimeRepo] and the version label follows +semantic versioning rules cite:[SemVer]. -# Extending P4Runtime for non-PSA Architectures { #sec-extending-p4runtime} +[#sec-extending-p4runtime] +== Extending P4Runtime for non-PSA Architectures P4Runtime includes native support for PSA programs and in particular support for runtime control of PSA extern instances. While the definition of Protobuf @@ -6447,7 +6374,8 @@ as the major version number for the P4Runtime version they "extend". For the remainder of this section, we will refer to these two files as *p4info-ext* and *p4runtime-ext* respectively. -## Extending P4Runtime for Architecture-Specific Externs +<<< +=== Extending P4Runtime for Architecture-Specific Externs Each P4 architecture can define its own set of extern types. Controlling them at runtime requires defining new Protobuf messages in both *p4info-ext* and @@ -6455,79 +6383,82 @@ runtime requires defining new Protobuf messages in both *p4info-ext* and that the new architecture we are trying to support in P4Runtime includes the following extern definition, which we will use as a running example: -~ Begin P4Example +[source,p4] +---- // T must be a bit type, it indicates the width of each counter cell extern MyNewPacketCounter { counter(bit<32> size); increment(in bit<32> index); } -~ End P4Example +---- -### Extending the P4Info message +==== Extending the P4Info message * Id prefixes `0x81` through `0xfe` are reserved for architecture-specific externs. It is recommended that *p4info-ext* include a `P4Ids` message based - on the one in p4info.proto that the P4 compiler can refer to when [assigning - IDs](#sec-id-allocation) to each extern instance. + on the one in p4info.proto that the P4 compiler can refer to when xref:sec-id-allocation[assigning IDs] to each extern instance. -~ Begin Proto +[source,protobuf] +---- message P4Ids { enum Prefix { UNSPECIFIED = 0; MY_NEW_PACKET_COUNTER = 0x81; } } -~ End Proto +---- * *p4info-ext* should include a Protobuf message definition for every extern type that can be controlled at runtime. For every extern instance of this type, the compiler will generate an instance of this Protobuf message and embed it appropriately in the corresponding - [`p4.config.v1.ExternInstance`](#sec-p4info-extern) message as the `info` - field, which is of type `Any` [@ProtoAny]. + xref:sec-p4info-extern[`p4.config.v1.ExternInstance`] message as the `info` + field, which is of type `Any` cite:[ProtoAny]. -~ Begin Proto +[source,protobuf] +---- message MyNewPacketCounter { // corresponds to the T type parameter in the P4 extern definition p4.config.v1.P4DataTypeSpec type_spec = 1; // constructor argument int64 size = 2; } -~ End Proto +---- -### Extending the P4Runtime Service +==== Extending the P4Runtime Service Just like *p4info-ext*, *p4runtime-ext* should include a Protobuf message definition for every extern type that can be controlled at runtime. This message should include the extern-specific parameters defining the read or write operation to be performed by the P4Runtime server on the corresponding extern instance. Instances of this architecture-specific message are meant to be -embedded in an [`ExternEntry`](#sec-extern-entry) message generated by the +embedded in an xref:sec-extern-entry[`ExternEntry`] message generated by the P4Runtime client. Here is a possible Protobuf message for our `MyNewPacketCounter` P4 extern: -~ Begin Proto +[source,protobuf] +---- // This message enables reading / writing data to the counter at the provided // index message MyNewPacketCounter { int64 index = 1; p4.v1.P4Data data = 2; } -~ End Proto +---- P4Runtime also supports streaming arbitrary Protobuf messages between the server -and the client, by including an `Any` Protobuf field [@ProtoAny] named `other` +and the client, by including an `Any` Protobuf field cite:[ProtoAny] named `other` in both `p4.v1.StreamMessageRequest` and `p4.v1.StreamMessageResponse`. Architectures that wish to leverage this support should define the appropriate Protobuf messages for this bidirectional streaming in *p4runtime-ext* and embed instances of these messages in `p4.v1.StreamMessageRequest` and `p4.v1.StreamMessageResponse` as appropriate. -## Architecture-Specific Table Extensions +=== Architecture-Specific Table Extensions -### New Match Types +==== New Match Types -An architecture may introduce new table match types [@P4MatchTypes]. P4Runtime +An architecture may introduce new table match types cite:[P4MatchTypes]. P4Runtime accounts for this by providing the following hooks: * The `match` field in `p4.config.v1.MatchField` (p4info.proto) is a `oneof` @@ -6536,7 +6467,7 @@ accounts for this by providing the following hooks: string. * The `field_match_type` field in `p4.v1.FieldMatch` (p4runtime.proto) is a - `oneof` which includes an `Any` Protobuf message [@ProtoAny] field + `oneof` which includes an `Any` Protobuf message cite:[ProtoAny] field (`other`). *p4info-ext* should include a Protobuf message definition for each architecture-specific match type, which can be used to encode values for match key elements which use this match type type in the P4 table @@ -6544,30 +6475,30 @@ accounts for this by providing the following hooks: `other` field, which can then be decoded by the P4Runtime server using the match type name included in P4Info. -### New Table Properties +==== New Table Properties An architecture may introduce additional table properties -[@P4TableProperties]. In some instances, it can be desirable to include the +cite:[P4TableProperties]. In some instances, it can be desirable to include the information contained in table properties in P4Info, which is why the `p4.config.v1.Table` message includes the `other_properties` `Any` Protobuf -field [@ProtoAny]. At the moment, there is not any mechanism to extend the +field cite:[ProtoAny]. At the moment, there is not any mechanism to extend the `p4.v1.TableEntry` message based on the value of architecture-specific table properties, but we may include on in future versions of the API. -# Known Limitations of Current P4Runtime Version +== Known Limitations of Current P4Runtime Version * `FieldMatch`, action `Param`, and controller packet metadata fields only - support unsigned bitstrings, &ie; values of one of the following types (not + support unsigned bitstrings, i.e. values of one of the following types (not the more general `P4Data`): - * `bit` - * `bool`. Note that as far as the `P4Info` message contents and + ** `bit` + ** `bool`. Note that as far as the `P4Info` message contents and thus controller software is concerned, such fields of type `bool` will be indistinguishable from those that have been declared with type `bit<1>`. P4Runtime server software will automatically perform any conversion needed between the type `bit<1>` values in P4Runtime messages and the data plane representation. - * an `enum` with underlying type `bit` - * a `type` or `typedef` with an underlying type that is one of the above (or + ** an `enum` with underlying type `bit` + ** a `type` or `typedef` with an underlying type that is one of the above (or in general a "chain" of `type` and/or `typedef` that eventually ends with one of the types above) @@ -6587,82 +6518,80 @@ properties, but we may include on in future versions of the API. in particular, there is no way for a client to query the supported minor + patch version numbers. -# Appendix { @h1:"A"} - -## Revision History +[appendix] += Appendix -### Changes in v1.4.1 +=== Revision History +==== Changes in v1.4.1 No content changes; tag was incremented only. -### Changes in v1.4.0 +==== Changes in v1.4.0 * Actions - * Fix invalid `action_profile_id` in the One Shot Action Selector Programming - example. - * Specify that `max_group_size` must be less than or equal to `size` for - Action Selectors. - * Add a `selector_size_semantics` field to the `ActionProfile` message - in P4Info. + ** Fix invalid `action_profile_id` in the One Shot Action Selector Programming + example. + Specify that `max_group_size` must be less than or equal to `size` for + Action Selectors. + ** Add a `selector_size_semantics` field to the `ActionProfile` message + in P4Info. * Controller Sessions, Roles, Arbitration: - * Clarify controller session establishment, maintenance, role and arbitration - * Simplify specification for arbitration updates for which there is no change + ** Clarify controller session establishment, maintenance, role and arbitration + ** Simplify specification for arbitration updates for which there is no change to the controller's `election_id`; in particular, a "no-op" arbitration update from a primary controller (the controller already was, and remains, the primary controller) is essentially treated the same way as an arbitration update which leads to the election of a new primary controller. - * Add support for string role identifiers and deprecate integer role + ** Add support for string role identifiers and deprecate integer role identifiers. - * Add support for specifying a role in `ReadRequest` messages: if present, + ** Add support for specifying a role in `ReadRequest` messages: if present, only entities belonging to this specific role are returned. - * Clarify that the (`device_id`, `role`, `election_id`) 3-tuples are only + ** Clarify that the (`device_id`, `role`, `election_id`) 3-tuples are only unique for live controllers. * Generated code - * Enable C++ Arena Allocation [@ArenaAllocation] by default in - p4runtime.proto. - * Added Rust code generation + ** Enable C++ Arena Allocation cite:[ArenaAllocation] by default in + p4runtime.proto. + ** Added Rust code generation * Meters - * Add a `Type` field to the `MeterSpec` message allowing users to restrict - the type of meters that can be used for a table and a new `eburst` field to - the `MeterConfig` message for use with one of the new `MeterSpec` types. - See section on [Meter & DirectMeter](#sec-meter-directmeter). - * Defined new meter annotations `@two_rate_three_color`, + ** Add a `Type` field to the `MeterSpec` message allowing users to restrict + the type of meters that can be used for a table and a new `eburst` field to + the `MeterConfig` message for use with one of the new `MeterSpec` types. + See section on xref:sec-meter-directmeter[Meter & DirectMeter]. + ** Defined new meter annotations `@two_rate_three_color`, `@single_rate_two_color`, `@single_rate_three_color` - * Enable P4Runtime servers to provide per-color counter values when direct or - indirect meter entries are read. + ** Enable P4Runtime servers to provide per-color counter values when direct or + indirect meter entries are read. * Miscellaneous - * Add a `PlatformProperties` message specifying desired underlying platform + ** Add a `PlatformProperties` message specifying desired underlying platform properties to the `PkgInfo` message. - * Specify Read behavior in the absence of a P4Info + ** Specify Read behavior in the absence of a P4Info (`ForwardingPipelineConfig` not set yet). - * Clarify that for updates of type `INSERT`, error codes other than + ** Clarify that for updates of type `INSERT`, error codes other than `INVALID_ARGUMENT` can be returned when applicable. - * Clarified the meaning of set and unset scalar and message fields, see - section on [default-valued fields](#sec-default-valued-fields). - * Described Dataplane Volatile Objects, see section on - [Dataplane Volatile Objects](sec-data-plane-volatile-objects). - * Clarified use of bytestrings in messages, see section on - [Bytestrings](#sec-bytestrings) + ** Clarified the meaning of set and unset scalar and message fields, see + section on xref:sec-default-valued-fields[default-valued fields]. + ** Described Dataplane Volatile Objects, see section on xref:sec-data-plane-volatile-objects[Dataplane Volatile Objects]. + ** Clarified use of bytestrings in messages, see section on xref:sec-bytestrings[Bytestrings]. * Replication - * Add a `metadata` field to the `MulticastGroupEntry` message. - * In message `Replica`, replaced primitive field `uint32 egress_port` + ** Add a `metadata` field to the `MulticastGroupEntry` message. + ** In message `Replica`, replaced primitive field `uint32 egress_port` in a compatible manner with new `oneof port_kind` containing preferred new field `bytes port`. * Tables - * Clarify that the limitation on supported types for `FieldMatch`, action + ** Clarify that the limitation on supported types for `FieldMatch`, action `Param`, and Packet IO metadata fields (no support for signed integers, with type `int`) apply to all minor revisions of P4Runtime v1, not just to P4Runtime v1.0. - * Add `has_initial_entries` and `is_const` field fields to `Table` message to + ** Add `has_initial_entries` and `is_const` field fields to `Table` message to distinguish mutable and immutable initial table entries, - see section on [Constant Tables](#sec-constant-tables). + see section on xref:sec-constant-tables[Constant Tables]. -### Changes in v1.3.0 +==== Changes in v1.3.0 * Add IANA assigned TCP port, 9559, to P4Runtime server discussion. * Move "Security considerations" section to P4Runtime server discussion. @@ -6673,10 +6602,10 @@ No content changes; tag was incremented only. * Clarify that source locations for annotations are optional in the P4Info message. -### Changes in v1.2.0 +==== Changes in v1.2.0 * Add new `OPTIONAL` match kind. At the moment, `OPTIONAL` is only supported by - the v1model architecture [@v1model], and not by PSA. It will eventually be + the v1model architecture cite:[v1model], and not by PSA. It will eventually be included in the core P4 language. * Add support in P4Info for structured annotations, which are used to annotate objects with key-value lists or expression lists. @@ -6693,7 +6622,7 @@ No content changes; tag was incremented only. * Add optional P4 source locations to both structured and unstructured annotations. -### Changes in v1.1.0 +==== Changes in v1.1.0 * Major overhaul of master-arbitration: while the Protobuf messages did not change, the state machine that the server needs to implement is significantly @@ -6718,39 +6647,39 @@ No content changes; tag was incremented only. * Document that `@p4runtime_translation` need only be supported when applied to type declarations in P4. -## P4 Annotations +=== P4 Annotations -Table [#tab-p4-annotations] lists P4~16~ annotations introduced primarily for +<<#tab-p4-annotations>> lists P4~16~ annotations introduced primarily for the purpose of adding features for the P4Runtime API. -~ TableFigure { #tab-p4-annotations; \ - caption: "P4 annotations introduced by P4Runtime"; \ - page-align: forcehere; } -|-------------------------- |---------------------------------------| -| Annotation | Description | -+----------------------------+---------------------------------------+ -| `@brief` | See section [#sec-annotating-p4-entities-with-documentation] | -| `@controller_header` | See section [#sec-controller-packet-meta] | -| `@description` | See section [#sec-annotating-p4-entities-with-documentation] | -| `@id` | See section [#sec-id-allocation] | -| `@max_group_size` | See sections [#sec-p4info-action-profile], [#sec-action-profile-group-programming] | -| `@selector_size_semantics` | See section [#sec-p4info-action-profile] | -| `@max_member_weight` | See section [#sec-p4info-action-profile] | -| `@two_rate_three_color` | See section [#sec-meter-directmeter] | -| `@single_rate_three_color` | See section [#sec-meter-directmeter] | -| `@single_rate_two_color` | See section [#sec-meter-directmeter] | -| `@pkginfo` | See section [#sec-annotating-p4-code-with-pkginfo] | -| `@platform_property` | See section [#sec-annotating-p4-code-with-pkginfo] | -| `@p4runtime_translation` | See sections [#sec-user-defined-types], [#sec-translation-of-port-numbers] | -+----------------------------+---------------------------------------+ -~ - -## A More Complex Value Set Example { #sec-value-set-example} +.P4 annotations introduced by P4Runtime +[cols="2",width=80%, align=center, options=header, unbreakable] +[#tab-p4-annotations] +|=== +| Annotation | Description +| `@brief` | See <<#sec-annotating-p4-entities-with-documentation>> +| `@controller_header` | See <<#sec-controller-packet-meta>> +| `@description` | See <<#sec-annotating-p4-entities-with-documentation>> +| `@id` | See <<#sec-id-allocation>> +| `@max_group_size` | See <<#sec-p4info-action-profile>>, <<#sec-action-profile-group-programming>> +| `@selector_size_semantics` | See <<#sec-p4info-action-profile>> +| `@max_member_weight` | See <<#sec-p4info-action-profile>> +| `@two_rate_three_color` | See <<#sec-meter-directmeter>> +| `@single_rate_three_color` | See <<#sec-meter-directmeter>> +| `@single_rate_two_color` | See <<#sec-meter-directmeter>> +| `@pkginfo` | See <<#sec-annotating-p4-code-with-pkginfo>> +| `@platform_property` | See <<#sec-annotating-p4-code-with-pkginfo>> +| `@p4runtime_translation` | See <<#sec-user-defined-types>>, <<#sec-translation-of-port-numbers>> +|=== + +[#sec-value-set-example] +=== A More Complex Value Set Example This section includes a more complex Value Set example, with multiple matches of different kinds. -~ Begin P4Example +[source,p4] +---- struct match_t { bit<8> f8; @match(ternary) bit<16> f16; @@ -6758,12 +6687,13 @@ struct match_t { } @id(1) value_set(4) pvs; select ({ hdr.f8, hdr.f16, hdr.f32 }) { /* ... */ } -~ End P4Example +---- This P4 Value Set declaration will translate into the following entry in the P4Info messsage: -~ Begin Prototext +[source,protobuf] +---- value_sets { preamble { id: 0x03000001 @@ -6789,12 +6719,13 @@ value_sets { } size: 4 } -~ End Prototext +---- A P4Runtime client can set the membership for this Value Set with `WriteRequest` messages similar to this one: -~ Begin Prototext +[source,protobuf] +---- type: MODIFY entity { value_set_entry { @@ -6804,10 +6735,10 @@ entity { field_id: 1 exact { value: 0xac } } - # match for field_id 2 is missing => don't care match + // match for field_id 2 is missing => don't care match match { field_id: 3 - other { ... } # some serialized Any message (architecture-specific) + other { ... } // some serialized Any message (architecture-specific) } } members { @@ -6821,26 +6752,24 @@ entity { } match { field_id: 3 - other { ... } # some serialized Any message (architecture-specific) + other { ... } // some serialized Any message (architecture-specific) } } } } -~ End Prototext - +---- -## Guidelines for Implementations +=== Guidelines for Implementations This section contains practical advice for implementing P4Runtime clients and servers. -### gRPC Metadata Maximum Size +==== gRPC Metadata Maximum Size In gRPC, the status of a RPC request is sent as metadata, whose size is limited by the `grpc.max_metadata_size` gRPC channel argument. By default, this limit is 8KB, which can be a problem for the `Write` P4Runtime RPC. The `Write` RPC -returns an individual error for every item in a batch (see Section -[#sec-write-rpc]), which can quickly result in a status size over 8KB. In that +returns an individual error for every item in a batch (see <<#sec-write-rpc>>), which can quickly result in a status size over 8KB. In that case, the gRPC server would not send the status, but instead send a `RESOURCE_EXHAUSTED` error, without any of the individual errors. @@ -6854,17 +6783,18 @@ channel. As a rule of thumb, it might make sense to allow for at least `8192 + MAX_UPDATES_PER_WRITE * 100` bytes of metadata. For example, in C++, one can create a client channel as follows: -~ Begin CPP +[source,c++] +---- const int MAX_UPDATES_PER_WRITE = 100; ::grpc::ChannelArguments arguments; arguments.SetInt(GRPC_ARG_MAX_METADATA_SIZE, 8192 + MAX_UPDATES_PER_WRITE*100); return grpc::CreateCustomChannel(address, credentials, arguments); -~ End CPP +---- -### gRPC Server Maximum Receive Message Size +==== gRPC Server Maximum Receive Message Size At the time of writing, the default maximum receive message size in gRPC is 4MB ---- while the default maximum send message size is unlimited. This can be a +— while the default maximum send message size is unlimited. This can be a problem for the `SetForwardingPipelineConfig` RPC, since for some targets the binary `p4_device_config` can exceed 4MB, in which case by default the P4Runtime server would return an `INVALID_ARGUMENT` error. To a lesser extent, this may @@ -6876,39 +6806,43 @@ possible values of `p4_device_config` for their target(s). This can be done by setting the `grpc.max_receive_message_length` when building the gRPC server. For example, in C++, one can set the maximum receive message size as follows: -~ Begin CPP + +[source,c++] +---- const int MAX_RECEIVE_MESSAGE_SIZE = 128 * 1024 * 1024; // 128MB ::grpc::ServerBuilder server_builder; builder.AddListeningPort(/*...*/); builder.RegisterService(/*...*/); // register P4Runtime service builder.SetMaxReceiveMessageSize(MAX_RECEIVE_MESSAGE_SIZE); builder.BuildAndStart(); -~ End CPP +---- On the client side, we recommend that P4Runtime clients do not use `Write` -batches larger than the default maximum receive message size (4MB) --- in case -the server did not deem necessary to increase the default value ---, unless the +batches larger than the default maximum receive message size (4MB) — in case +the server did not deem necessary to increase the default value —, unless the clients are aware that the server is using a larger maximum receive message size. The gRPC server running the P4Runtime service must not set the maximum receive message size to a value smaller than the default (4MB). -## P4Runtime Entries files { #sec-entries-files } +[#sec-entries-files] +=== P4Runtime Entries files -The open source P4 compiler `p4c` [@p4c] implements an option to -generate an "entries file", &ie; a file that contains all table +The open source P4 compiler `p4c` cite:[p4c] implements an option to +generate an "entries file", i.e. a file that contains all table entries declared via the `entries` table property within the program. An example P4~16~ program that can be used to demonstrate this -capability is `table-entries-ternary-bmv2.p4` [@p4cTestProgramForConstEntries]: - - git clone https://github.com/p4lang/p4c - cd p4c/testdata/p4_16_samples - mkdir tmp - p4test --arch v1model \ - --p4runtime-files tmp/p4info.txt \ - --p4runtime-entries-files tmp/entries.txt \ - table-entries-ternary-bmv2.p4 - +capability is `table-entries-ternary-bmv2.p4` cite:[p4cTestProgramForConstEntries]: +[source, bash] +---- +git clone https://github.com/p4lang/p4c +cd p4c/testdata/p4_16_samples +mkdir tmp +p4test --arch v1model \ + --p4runtime-files tmp/p4info.txt \ + --p4runtime-entries-files tmp/entries.txt \ + table-entries-ternary-bmv2.p4 +---- You can replace the `.txt` suffix of the file name `tmp/entries.txt` in the example command above with `.json` or `.bin`. The `.bin` format is a binary P4Runtime API protobuf message format. The `.txt` @@ -6937,6 +6871,8 @@ containing the data for one table entry. Note that if a P4Runtime client attempted to send a `WriteRequest` to a P4Runtime server with the contents of the entries file, the server must return an error for each entry that has `is_const` true, as -described in Section [#sec-table-entry]. +described in <<#sec-table-entry>>. -[BIB] +[bibliography] +== References +bibliography::references.bib[ieee] \ No newline at end of file diff --git a/docs/v1/README.md b/docs/v1/README.md index e0d2141a..3db0f2e0 100644 --- a/docs/v1/README.md +++ b/docs/v1/README.md @@ -5,62 +5,80 @@ specification document. # Markup version -The markup version uses Madoko (https://www.madoko.net) to produce +The markup version uses AsciiDoc (https://docs.asciidoctor.org/) to produce HTML and PDF versions of the documentation. Pre-built versions of the documentation are available on the [P4.org specifications page](https://p4.org/specs). Files: -- `P4Runtime-spec.mdk` is the main file. -- assets: Figures - - `*.odg` - OfficeLibre source drawing file used to export images. These are +- `P4Runtime-Spec.adoc` is the main file. +- resources: + - figs + - `*.odg` - OfficeLibre source drawing file used to export images. These are bulk-rendered at build time into .svg and .png images via `soffice` command-line (required in build environment) + - fonts + - `*.ttf` - Type font source file used to export fonts. + - theme: + - `*.yaml` - Describes how PDF P4Runtime specification will be displayed. + - `*.css` - Describes how HTML P4Runtime specification will displayed. + - `*.bib` - Bibliography file that contains a list of bibliographical item, such as articles, books, and theses. - `Makefile` builds documentation in the build subdirectory -- `p4.json` is providing custom syntax highlighting for P4. It is a rip off from - the cpp.json provided by Madoko (the "extend" clause does not work, my version - of Madoko asks for a "tokenizer" to be defined). Style customization for each - token can be done using CSS style attributes (see `token.keyword` in - `P4Runtime-spec.mdk`). -## Building +## Document Figures -The easiest way to render the Madoko specification documentation is to use the -`p4lang/p4rt-madoko:latest` Docker` image: +The P4Runtime specification can be generated on your local machine or via the Docker container. Due to this, the document figure can be rendered using two different approaches - docker run -v `pwd`/docs/v1:/usr/src/p4-spec p4lang/p4rt-madoko:latest make +### Local machine -### Linux +You need to install [LibreOffice](https://nl.libreoffice.org/) on your local machine. + +Each image in the specification has a corresponding `.odg` file under +`resources/figs/`. These are LibreOffice drawing files. The files are rendered into +`.svg` and `.png` images (for HTML and PDF output, resepectively) at build time, +using the `soffice` command-line tool. The page size for each image should be +adjusted manually by the author ("artist") to just fit the image on the +apparrent "page," to minimize padding around the image in the rendered +document. Use the menu item `Format | Page/Size Properties.` See the example +screen shot below. (Do not check the "Fit object to paper format" box - it will +change the object's aspect ratio.) +![LibreOffice](libre-office.png) + +Commands to convert a image from `.odg` to `.svg` and/or `.png`: ``` -sudo apt-get install nodejs -sudo npm install madoko -g -sudo apt-get install libreoffice -sudo apt-get install texlive-science texlive-xetex -make [all | html | pdf ] +soffice --convert-to svg figure_name.odg +soffice --convert-to png figure_name.odg ``` -In particular (on Ubuntu 16.04 at least), don't try `sudo apt-get install npm` -because `npm` is already included and this will yield a bunch of confusing error -messages from `apt-get`. -`dvipng` is used to render "math" inside BibTex (used for bibliography) -titles, so you will need to install it as well. On Debian-based Linux, it can be -done with `sudo apt-get install dvipng`. +Commands to convert a image `.odg` to `.svg` and/or `.png` and move to `resources/figs/`: -### MacOS - -We use the [local -installation](http://research.microsoft.com/en-us/um/people/daan/madoko/doc/reference.html#sec-installation-and-usage) -method. For Mac OS, I installed node.js using Homebrew and then Madoko using -npm: ``` -brew install node.js -npm install madoko -g +soffice --convert-to svg --outdir resources/figs/ figure_name.odg +soffice --convert-to png --outdir resources/figs/ figure_name.odg +``` +### Docker container + +The Docker container, generated from the `p4lang/p4rt-asciidoc:latest` image, does not require +LibreOffice to be installed. + + +## Building + +The easiest way to render the AsciiDoc specification documentation is to use the +`p4lang/p4rt-asciidoc:latest` Docker` image: + + docker run -v `pwd`/docs/v1:/usr/src/p4-spec p4lang/p4rt-asciidoc:latest make build_spec_with_images + +### Linux ``` -Note that to build the PDF you need a functional TeX version installed. +You can use the [local installation](https://github.com/p4lang/p4-spec/blob/main/p4-16/spec/install-asciidoctor-linux.sh) method, and you also need to install LibreOffice to render the +images into .svg and .png formats. + +### MacOS + +We do not yet have instructions for generating PDF and HTML from AsciiDoc source on macOS. You are welcome to contribute documentation for how to do so if you find instructions that work. ### Windows -You need to install miktex [http://miktex.org/], madoko -[https://www.madoko.net/] and node.js [https://nodejs.org/en/]. To -build you can invoke the make.bat script. +We do not yet have instructions for generating PDF and HTML from AsciiDoc source on Windows. You are welcome to contribute documentation for how to do so if you find instructions that work. diff --git a/docs/v1/bibref_no_title.js b/docs/v1/bibref_no_title.js deleted file mode 100644 index a67417f0..00000000 --- a/docs/v1/bibref_no_title.js +++ /dev/null @@ -1,5 +0,0 @@ -$(document).ready(function(){ - $('a').filter(".bibref").hover(function(e){ - $(this).attr('title', ''); - }); -}); diff --git a/docs/v1/cpp.json b/docs/v1/cpp.json deleted file mode 100644 index 19c08a42..00000000 --- a/docs/v1/cpp.json +++ /dev/null @@ -1,105 +0,0 @@ -{ - "displayName": "C++", - "name": "cpp", - "mimeTypes": ["text/cpp","text/c"], - "fileExtensions": ["cpp","c++","h","c"], - - "lineComment": "//", - "blockCommentStart": "/*", - "blockCommentEnd": "*/", - - "keywords": [ - "alignas", "alignof", "and", "and_eq", "asm", "auto", "bitand", "bitor", "bool", "break", "case", - "catch", "char", "char16_t", "char32_t", "class", "compl", "const", "constexpr", "const_cast", - "continue", "decltype", "default", "delete", "do", "double", "dynamic_cast", "else", - "enum", "explicit", "export", "extern", "false", "float", "for", "friend", "goto", "if", "inline", - "int", "long", "mutable", "namespace", "new", "noexcept", "not", "not_eq", "nullptr", "operator", - "or", "or_eq", "private", "protected", "public", "register", "reinterpret_cast", - "return", "short", "signed", "sizeof", "static", "static_assert", "static_cast", "struct", - "switch", "template", "this", "thread_local", "throw", "true", "try", "typedef", "typeid", - "typename", "union", "unsigned", "using", "virtual", "void", "volatile", "wchar_t", "while", - "xor", "xor_eq" - ], - - "typeKeywords": [ - "bool", "double", "byte", "int", "short", "char", "void", "long", "float", - "char32_t", "unsigned", "wchar_t", "char16_t" - ], - - "directives": [ - "include","if","endif","ifdef","define","line","warning","error" - ], - - "operators": [ - "=", ">", "<", "!", "~", "?", ":", - "==", "<=", ">=", "!=", "&&", "||", "++", "--", - "+", "-", "*", "/", "&", "|", "^", "%", "<<", - ">>", ">>>", "+=", "-=", "*=", "/=", "&=", "|=", - "^=", "%=", "<<=", ">>=", ">>>=" - ], - - "symbols": "[=>)([A-Z][\\w]*)", ["keyword", "identifier"] ], - ["[A-Z][\\w]*(?!\\s*[\\w\\(])", "type.identifier" ], - ["[A-Z][A-Z0-9_]*(?![\\w\\(])", "type.identifier" ], - - ["^(\\s*#)(\\w+)(.*)", { "cases": { - "$2@directives": ["namespace","namespace","string"], - "@default": ["meta","meta","string"] - } } ], - - { "include": "@whitespace" }, - - ["[{}()\\[\\]]", "@brackets"], - ["[<>](?!@symbols)", "@brackets"], - ["@symbols", { "cases": { "@operators": "operator", - "@default" : "" } } ], - - ["\\d*\\.\\d+([eE][\\-+]?\\d+)?[fFdD]?", "number.float"], - ["0[xX][0-9a-fA-F_]*[0-9a-fA-F][Ll]?", "number.hex"], - ["0[0-7_]*[0-7][Ll]?", "number.octal"], - ["0[bB][0-1_]*[0-1][Ll]?", "number.binary"], - ["\\d+[lL]?", "number"], - - ["[;,.]", "delimiter"], - - ["[lL]\"([^\"\\\\]|\\\\.)*$", "string.invalid" ], - ["\"", "string", "@string" ], - - ["'[^\\\\']'", "string"], - ["(')(@escapes)(')", ["string","string.escape","string"]], - ["'", "string.invalid"] - ], - - "whitespace": [ - ["[ \\t\\r\\n]+", "white"], - ["\\/\\*", "comment", "@comment" ], - ["\\/\\/.*$", "comment"] - ], - - "comment": [ - ["[^\\/*]+", "comment" ], - ["\\/\\*", "comment.invalid" ], - ["\\*/", "comment", "@pop" ], - ["[\\/*]", "comment" ] - ], - - "string": [ - ["[^\\\\\"]+", "string"], - ["@escapes", "string.escape"], - ["\\\\.", "string.escape.invalid"], - ["\"", "string", "@pop" ] - ] - } -} diff --git a/docs/v1/libre-office.png b/docs/v1/libre-office.png new file mode 100644 index 00000000..155c8f07 Binary files /dev/null and b/docs/v1/libre-office.png differ diff --git a/docs/v1/make.bat b/docs/v1/make.bat deleted file mode 100644 index 2f789eaa..00000000 --- a/docs/v1/make.bat +++ /dev/null @@ -1 +0,0 @@ -madoko --pdf -vv --png --odir=build P4Runtime-spec.mdk \ No newline at end of file diff --git a/docs/v1/p4.json b/docs/v1/p4.json deleted file mode 100644 index f8148c37..00000000 --- a/docs/v1/p4.json +++ /dev/null @@ -1,113 +0,0 @@ -{ - "displayName": "P4", - "name": "p4", - - "lineComment": "//", - "blockCommentStart": "/*", - "blockCommentEnd": "*/", - - "keywords": [ "action", "apply", "control", "default", "else", - "extern", "exit", "false", "if", - "package", "parser", "return", "select", "state", "switch", - "table", "transition", "true", "typedef", "verify" - ], - - "extraKeywords": [], - - "typeKeywords": [ - "bool", "bit", "const", "enum", "entries", "error", "header", "header_union", "in", "inout", "int", "match_kind", "out", "tuple", "struct", "varbit", "void", "type" - ], - - "extraTypeKeywords": [], - - "directives": [ - "include","if","endif","ifdef","define","ifndef","undef","line" - ], - - "annotations": [ - "atomic", "defaultonly", "name", "priority", "tableonly", "hidden", "globalname" - ], - - "operators": [ - "=", ">", "<", "!", "~", "?", ":", - "==", "<=", ">=", "!=", "&&", "||", "++", - "+", "-", "*", "/", "&", "|", "^", "%", "<<", - ">>", "&&&", ".." - ], - - "extraOperators": [], - - "symbols": "[=>](?!@symbols)", "@brackets"], - ["@symbols", { "cases": { - "@operators": "operator", - "@extraOperators": "operator.extra", - "@default" : "" } } ], - - ["\\d*\\.\\d+([eE][\\-+]?\\d+)?[fFdD]?", "number.float"], - ["0[xX][0-9a-fA-F_]*[0-9a-fA-F][Ll]?", "number.hex"], - ["0[0-7_]*[0-7][Ll]?", "number.octal"], - ["0[bB][0-1_]*[0-1][Ll]?", "number.binary"], - ["\\d+[lL]?", "number"], - - ["[;,.]", "delimiter"], - - ["[lL]\"([^\"\\\\]|\\\\.)*$", "string.invalid"], - ["\"", "string", "@string" ], - - - ["'[^\\\\']'", "string"], - ["(')(@escapes)(')", ["string","string.escape","string"]], - ["'", "string.invalid"] - ], - - "whitespace": [ - ["[ \\t\\r\\n]+", "white"], - ["\\/\\*", "comment", "@comment" ], - ["\\/\\/.*$", "comment"] - ], - - "comment": [ - ["[^\\/*]+", "comment" ], - ["\\/\\*", "comment.invalid" ], - ["\\*/", "comment", "@pop" ], - ["[\\/*]", "comment" ] - ], - - "string": [ - ["[^\\\\\"]+", "string"], - ["@escapes", "string.escape"], - ["\\\\.", "string.escape.invalid"], - ["\"", "string", "@pop" ] - ] - } -} diff --git a/docs/v1/proto.json b/docs/v1/proto.json deleted file mode 100644 index 0a09fb9c..00000000 --- a/docs/v1/proto.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "displayName": "Protobuf", - "name": "proto", - - "lineComment": "//", - "blockCommentStart": "/*", - "blockCommentEnd": "*/", - - "keywords": [ - "syntax", "import", "package", "service", - "rpc", "returns", - "message", "oneof", "repeated", - "reserved", - "=" - ], - - "extraKeywords": ["optimize_for", "cc_enable_arenas", "objc_class_prefix", "deprecated"], - - "typeKeywords": [ - "enum", - "double", "float", - "int32", "int64", "uint32", "uint64", "sint32", "sint64", - "fixed32", "fixed64", "sfixed32", "sfixed64", - "bool", - "string", "bytes" - ], - - "directives": [], - - "symbols": "[=> p, #preamble > .sectionbody > .paragraph:first-of-type p { font-size: 1.23333em; line-height: 1.6; } + +.subheader, #content #toctitle, .admonitionblock td.content > .title, .exampleblock > .title, .imageblock > .title, .videoblock > .title, .listingblock > .title, .literalblock > .title, .openblock > .title, .paragraph > .title, .quoteblock > .title, .sidebarblock > .title, .tableblock > .title, .verseblock > .title, .dlist > .title, .olist > .title, .ulist > .title, .qlist > .title, .hdlist > .title, .tableblock > caption { line-height: 1.4; color: #030303; font-weight: 300; margin-top: 0.2em; margin-bottom: 0.5em; } + +/* Typography resets */ +div, dl, dt, dd, ul, ol, li, h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6, pre, form, p, blockquote, th, td { margin: 0; padding: 0; direction: ltr; } + +/* Default Link Styles */ +a { color: #4183c4; text-decoration: none; line-height: inherit; } +a:hover, a:focus { color: #4183c4; } +a img { border: none; } + +/* Default paragraph styles */ +p { font-family: helvetica, arial, freesans, clean, sans-serif; font-weight: normal; font-size: 1em; line-height: 1.4; margin-bottom: 1em; text-rendering: optimizeLegibility; } +p aside { font-size: 0.93333em; line-height: 1.35; font-style: italic; } + +/* Default header styles */ +h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6 { font-family: helvetica, arial, freesans, clean, sans-serif; font-weight: bold; font-style: normal; color: #333333; text-rendering: optimizeLegibility; margin-top: 0.2em; margin-bottom: 0.5em; line-height: 1.2em; } +h1 small, h2 small, h3 small, #toctitle small, .sidebarblock > .content > .title small, h4 small, h5 small, h6 small { font-size: 60%; color: gray; line-height: 0; } + +h1 { font-size: 1.33333em; } + +h2 { font-size: 0.93333em; } + +h3, #toctitle, .sidebarblock > .content > .title { font-size: 0.86667em; } + +h4 { font-size: 0.66667em; } + +h5 { font-size: 1em; } + +h6 { font-size: 1em; } + +hr { border: solid #dddddd; border-width: 1px 0 0; clear: both; margin: 1.33333em 0 1.26667em; height: 0; } + +/* Helpful Typography Defaults */ +em, i { font-style: italic; line-height: inherit; } + +strong, b { font-weight: bold; line-height: inherit; } + +small { font-size: 60%; line-height: inherit; } + +code { font-family: Monaco, "DejaVu Sans Mono", "Courier New", monospace; font-weight: normal; color: #B12146; } + +/* Lists */ +ul, ol, dl { font-size: 1em; line-height: 1.4; margin-bottom: 1em; list-style-position: outside; font-family: helvetica, arial, freesans, clean, sans-serif; } + +ul, ol { margin-left: 0.7em; } + +/* Unordered Lists */ +ul li ul, ul li ol { margin-left: 1.33333em; margin-bottom: 0; font-size: 1em; /* Override nested font-size change */ } +ul.square li ul, ul.circle li ul, ul.disc li ul { list-style: inherit; } +ul.square { list-style-type: square; } +ul.circle { list-style-type: circle; } +ul.disc { list-style-type: disc; } +ul.no-bullet { list-style: none; } + + +/* Ordered Lists */ +ol li ul, ol li ol { margin-left: 1.33333em; margin-bottom: 0; } + +/* Definition Lists */ +dl dt { margin-bottom: 0.33333em; font-weight: bold; } +dl dd { margin-bottom: 1.33333em; } + +/* Abbreviations */ +abbr, acronym { text-transform: uppercase; font-size: 90%; color: #030303; border-bottom: 1px dotted #dddddd; cursor: help; } + +abbr { text-transform: none; } + +/* Blockquotes */ +blockquote { margin: 0 0 1em; padding: 0; border-left: none; } +blockquote cite { display: block; font-size: 0.86667em; color: #030303; } +blockquote cite:before { content: "\2014 \0020"; } +blockquote cite a, blockquote cite a:visited { color: #030303; } + +blockquote, blockquote p { line-height: 1.4; color: #030303; } + +/* Microformats */ +.vcard { display: inline-block; margin: 0 0 1.33333em 0; border: 1px solid #dddddd; padding: 0.66667em 0.8em; } +.vcard li { margin: 0; display: block; } +.vcard .fn { font-weight: bold; font-size: 1em; } + +.vevent .summary { font-weight: bold; } +.vevent abbr { cursor: auto; text-decoration: none; font-weight: bold; border: none; padding: 0 0.06667em; } + +@media only screen and (min-width: 768px) { h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6 { line-height: 1.4; } + h1 { font-size: 2em; } + h2 { font-size: 1.6em; } + h3, #toctitle, .sidebarblock > .content > .title { font-size: 1.2em; } + h4 { font-size: 1em; } } +/* Print styles. Inlined to avoid required HTTP connection: www.phpied.com/delay-loading-your-print-css/ Credit to Paul Irish and HTML5 Boilerplate (html5boilerplate.com) +*/ +.print-only { display: none !important; } + +@media print { * { background: transparent !important; color: #000 !important; /* Black prints faster: h5bp.com/s */ box-shadow: none !important; text-shadow: none !important; } + a, a:visited { text-decoration: underline; } + a[href]:after { content: " (" attr(href) ")"; } + abbr[title]:after { content: " (" attr(title) ")"; } + .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } + pre, blockquote { border: 1px solid #999999; page-break-inside: avoid; } + thead { display: table-header-group; /* h5bp.com/t */ } + tr, img { page-break-inside: avoid; } + img { max-width: 100% !important; } + @page { margin: 0.5cm; } + p, h2, h3, #toctitle, .sidebarblock > .content > .title { orphans: 3; widows: 3; } + h2, h3, #toctitle, .sidebarblock > .content > .title { page-break-after: avoid; } + .hide-on-print { display: none !important; } + .print-only { display: block !important; } + .hide-for-print { display: none !important; } + .show-for-print { display: inherit !important; } } +/* Tables */ +table { background: white; margin-bottom: 1.33333em; border: solid 1px #dddddd; } +table thead, table tfoot { background: whitesmoke; font-weight: bold; } +table thead tr th, table thead tr td, table tfoot tr th, table tfoot tr td { padding: 0.53333em 0.66667em 0.66667em; font-size: 0.93333em; color: #222222; text-align: left; } +table tr th, table tr td { padding: 0.6em 0.66667em; font-size: 0.8em; color: black; } +table tr.even, table tr.alt, table tr:nth-of-type(even) { background: #f9f9f9; } +table thead tr th, table tfoot tr th, table tbody tr td, table tr td, table tfoot tr td { display: table-cell; line-height: 1.4; } + +a:hover, a:focus { text-decoration: underline; } + +.clearfix:before, .clearfix:after, .float-group:before, .float-group:after { content: " "; display: table; } +.clearfix:after, .float-group:after { clear: both; } + +*:not(pre) > code { font-size: 0.86667em; padding: 1px 5px 1px 5px; white-space: nowrap; background-color: transparent; border: 1px solid #dddddd; -webkit-border-radius: 3px; border-radius: 3px; text-shadow: none; } + +pre, pre > code { line-height: 1.6; color: black; font-family: Consolas, "Liberation Mono", Courier, monospace; font-weight: normal; } + +kbd.keyseq { color: black; } + +kbd:not(.keyseq) { display: inline-block; color: black; font-size: 0.8em; line-height: 1.4; background-color: #F7F7F7; border: 1px solid #ccc; -webkit-border-radius: 3px; border-radius: 3px; -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 0 0 2px white inset; box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 0 0 2px white inset; margin: -0.15em 0.15em 0 0.15em; padding: 0.2em 0.6em 0.2em 0.5em; vertical-align: middle; white-space: nowrap; } + +kbd kbd:first-child { margin-left: 0; } + +kbd kbd:last-child { margin-right: 0; } + +.menuseq, .menu { color: #1a1a1a; } + +#header, #content, #footnotes, #footer { width: 100%; margin-left: auto; margin-right: auto; margin-top: 0; margin-bottom: 0; max-width: 66.66667em; *zoom: 1; position: relative; padding-left: 1em; padding-right: 1em; } +#header:before, #header:after, #content:before, #content:after, #footnotes:before, #footnotes:after, #footer:before, #footer:after { content: " "; display: table; } +#header:after, #content:after, #footnotes:after, #footer:after { clear: both; } + +#header { margin-bottom: 2.66667em;} +#header > h1 { color: #030303; font-weight: 300; border-bottom: 1px solid #dddddd; margin-bottom: -28px; padding-bottom: 32px; text-align: center; } +#header span { color: #030303;} +#header #revnumber { text-align: center; text-transform: capitalize;} +#header br { display: none; } +#header br + span { padding-left: 3px; } +#header br + span:before { content: "\2013 \0020"; } +#header br + span.author { padding-left: 0; } +#header br + span.author:before { content: ", "; } + +#toc { border-bottom: 0 solid #dddddd; padding-bottom: 1.33333em; } +#toc > ul { margin-left: 0.26667em; } +#toc ul.sectlevel0 > li > a { font-style: italic; } +#toc ul.sectlevel0 ul.sectlevel1 { margin-left: 0; margin-top: 0.5em; margin-bottom: 0.5em; } +#toc ul { list-style-type: none; } + +#toctitle { color: #030303; } + +@media only screen and (min-width: 1280px) { body.toc2 { padding-left: 20em; } + #toc.toc2 { position: fixed; width: 20em; left: 0; top: 0; border-right: 1px solid #dddddd; border-bottom: 0; z-index: 1000; padding: 1em; height: 100%; overflow: auto; } + #toc.toc2 #toctitle { margin-top: 0; } + #toc.toc2 > ul { font-size: .95em; } + #toc.toc2 ul ul { margin-left: 0; padding-left: 1.33333em; } + #toc.toc2 ul.sectlevel0 ul.sectlevel1 { padding-left: 0; margin-top: 0.5em; margin-bottom: 0.5em; } + body.toc2.toc-right { padding-left: 0; padding-right: 20em; } + body.toc2.toc-right #toc.toc2 { border-right: 0; border-left: 1px solid #dddddd; left: auto; right: 0; } } +#content #toc { border-style: solid; border-width: 1px; border-color: #e6e6e6; margin-bottom: 1.33333em; padding: 1.33333em; background: white; border-width: 0; -webkit-border-radius: 3px; border-radius: 3px; } +#content #toc > :first-child { margin-top: 0; } +#content #toc > :last-child { margin-bottom: 0; } +#content #toc a { text-decoration: none; } + +#content #toctitle { font-weight: bold; font-family: helvetica, arial, freesans, clean, sans-serif; font-size: 1.06667em; padding-left: 0.13333em; } + +#footer { max-width: 100%; background-color: whitesmoke; padding: 1.33333em; } + +#footer-text { color: #030303; line-height: 1.26; } + +.sect1 { padding-bottom: 1.33333em; } + +.sect1 + .sect1 { border-top: 0 solid #dddddd; } + +#content h1 > a.anchor, h2 > a.anchor, h3 > a.anchor, #toctitle > a.anchor, .sidebarblock > .content > .title > a.anchor, h4 > a.anchor, h5 > a.anchor, h6 > a.anchor { position: absolute; width: 1em; margin-left: -1em; display: block; text-decoration: none; visibility: hidden; text-align: center; font-weight: normal; } +#content h1 > a.anchor:before, h2 > a.anchor:before, h3 > a.anchor:before, #toctitle > a.anchor:before, .sidebarblock > .content > .title > a.anchor:before, h4 > a.anchor:before, h5 > a.anchor:before, h6 > a.anchor:before { content: '\00A7'; font-size: .85em; vertical-align: text-top; display: block; margin-top: 0.05em; } +#content h1:hover > a.anchor, #content h1 > a.anchor:hover, h2:hover > a.anchor, h2 > a.anchor:hover, h3:hover > a.anchor, #toctitle:hover > a.anchor, .sidebarblock > .content > .title:hover > a.anchor, h3 > a.anchor:hover, #toctitle > a.anchor:hover, .sidebarblock > .content > .title > a.anchor:hover, h4:hover > a.anchor, h4 > a.anchor:hover, h5:hover > a.anchor, h5 > a.anchor:hover, h6:hover > a.anchor, h6 > a.anchor:hover { visibility: visible; } +#content h1 > a.link, h2 > a.link, h3 > a.link, #toctitle > a.link, .sidebarblock > .content > .title > a.link, h4 > a.link, h5 > a.link, h6 > a.link { color: #0830df; text-decoration: none; } +#content h1 > a.link:hover, h2 > a.link:hover, h3 > a.link:hover, #toctitle > a.link:hover, .sidebarblock > .content > .title > a.link:hover, h4 > a.link:hover, h5 > a.link:hover, h6 > a.link:hover { color: #262626; } + +.imageblock, .literalblock, .listingblock, .verseblock, .videoblock { margin-bottom: 1.33333em; } + +.admonitionblock td.content > .title, .exampleblock > .title, .imageblock > .title, .videoblock > .title, .listingblock > .title, .literalblock > .title, .openblock > .title, .paragraph > .title, .quoteblock > .title, .sidebarblock > .title, .tableblock > .title, .verseblock > .title, .dlist > .title, .olist > .title, .ulist > .title, .qlist > .title, .hdlist > .title { text-align: center; font-weight: lighter;} + +.tableblock > caption {caption-side: bottom; font-weight:lighter; white-space: nowrap; overflow: visible;} + +table.tableblock #preamble > .sectionbody > .paragraph:first-of-type p { font-size: inherit; } + +.admonitionblock > table { border: 0; background: none; width: 100%; } +.admonitionblock > table td.icon { text-align: center; width: 80px; } +.admonitionblock > table td.icon img { max-width: none; } +.admonitionblock > table td.icon .title { font-weight: bold; text-transform: uppercase; } +.admonitionblock > table td.content { padding-left: 1.2em; padding-right: 1.33333em; border-left: 1px solid #dddddd; color: #030303; } +.admonitionblock > table td.content > :last-child > :last-child { margin-bottom: 0; } + +.exampleblock > .content { border-style: solid; border-width: 1px; border-color: #e6e6e6; margin-bottom: 1.33333em; padding: 1.33333em; background: white; -webkit-border-radius: 3px; border-radius: 3px; } +.exampleblock > .content > :first-child { margin-top: 0; } +.exampleblock > .content > :last-child { margin-bottom: 0; } +.exampleblock > .content h1, .exampleblock > .content h2, .exampleblock > .content h3, .exampleblock > .content #toctitle, .sidebarblock.exampleblock > .content > .title, .exampleblock > .content h4, .exampleblock > .content h5, .exampleblock > .content h6, .exampleblock > .content p { color: #333333; } +.exampleblock > .content h1, .exampleblock > .content h2, .exampleblock > .content h3, .exampleblock > .content #toctitle, .sidebarblock.exampleblock > .content > .title, .exampleblock > .content h4, .exampleblock > .content h5, .exampleblock > .content h6 { line-height: 1; margin-bottom: 0.66667em; } +.exampleblock > .content h1.subheader, .exampleblock > .content h2.subheader, .exampleblock > .content h3.subheader, .exampleblock > .content .subheader#toctitle, .sidebarblock.exampleblock > .content > .subheader.title, .exampleblock > .content h4.subheader, .exampleblock > .content h5.subheader, .exampleblock > .content h6.subheader { line-height: 1.4; } + +.exampleblock.result > .content { -webkit-box-shadow: 0 1px 8px #eaeaea; box-shadow: 0 1px 8px #eaeaea; } + +.sidebarblock { border-style: solid; border-width: 1px; border-color: #e6e6e6; margin-bottom: 1.33333em; padding: 1.33333em; background: white; -webkit-border-radius: 3px; border-radius: 3px; } +.sidebarblock > :first-child { margin-top: 0; } +.sidebarblock > :last-child { margin-bottom: 0; } +.sidebarblock h1, .sidebarblock h2, .sidebarblock h3, .sidebarblock #toctitle, .sidebarblock > .content > .title, .sidebarblock h4, .sidebarblock h5, .sidebarblock h6, .sidebarblock p { color: #333333; } +.sidebarblock h1, .sidebarblock h2, .sidebarblock h3, .sidebarblock #toctitle, .sidebarblock > .content > .title, .sidebarblock h4, .sidebarblock h5, .sidebarblock h6 { line-height: 1; margin-bottom: 0.66667em; } +.sidebarblock h1.subheader, .sidebarblock h2.subheader, .sidebarblock h3.subheader, .sidebarblock .subheader#toctitle, .sidebarblock > .content > .subheader.title, .sidebarblock h4.subheader, .sidebarblock h5.subheader, .sidebarblock h6.subheader { line-height: 1.4; } +.sidebarblock > .content > .title { color: #030303; margin-top: 0; line-height: 1.4; border-width: 0 0 1px 0; border-style: solid; border-color: #cacaca; } + +.exampleblock > .content > :last-child > :last-child, .exampleblock > .content .olist > ol > li:last-child > :last-child, .exampleblock > .content .ulist > ul > li:last-child > :last-child, .exampleblock > .content .qlist > ol > li:last-child > :last-child, .sidebarblock > .content > :last-child > :last-child, .sidebarblock > .content .olist > ol > li:last-child > :last-child, .sidebarblock > .content .ulist > ul > li:last-child > :last-child, .sidebarblock > .content .qlist > ol > li:last-child > :last-child { margin-bottom: 0; } + +.literalblock > .content pre, .listingblock > .content pre { background: #F7F7F7; border-width: 2px; border-style: solid; border-color: #dddddd; -webkit-border-radius: 3px; border-radius: 3px; padding: 0.66667em; word-wrap: break-word; } +.literalblock > .content pre.nowrap, .listingblock > .content pre.nowrap { overflow-x: auto; white-space: pre; word-wrap: normal; } +.literalblock > .content pre > code, .listingblock > .content pre > code { display: block; } +@media only screen { .literalblock > .content pre, .listingblock > .content pre { font-size: 0.69333em; } } +@media only screen and (min-width: 768px) { .literalblock > .content pre, .listingblock > .content pre { font-size: 0.78em; } } +@media only screen and (min-width: 1280px) { .literalblock > .content pre, .listingblock > .content pre { font-size: 0.86667em; } } + +.listingblock > .content { position: relative; } + +.listingblock:hover code[class*=" language-"]:before { text-transform: uppercase; font-size: 0.9em; color: #999; position: absolute; top: 0.4em; right: 0.4em; } + +.listingblock:hover code.asciidoc:before { content: "asciidoc"; } +.listingblock:hover code.clojure:before { content: "clojure"; } +.listingblock:hover code.css:before { content: "css"; } +.listingblock:hover code.groovy:before { content: "groovy"; } +.listingblock:hover code.html:before { content: "html"; } +.listingblock:hover code.java:before { content: "java"; } +.listingblock:hover code.javascript:before { content: "javascript"; } +.listingblock:hover code.python:before { content: "python"; } +.listingblock:hover code.ruby:before { content: "ruby"; } +.listingblock:hover code.scss:before { content: "scss"; } +.listingblock:hover code.xml:before { content: "xml"; } +.listingblock:hover code.yaml:before { content: "yaml"; } + +.listingblock.terminal pre .command:before { content: attr(data-prompt); padding-right: 0.5em; color: #999; } + +.listingblock.terminal pre .command:not([data-prompt]):before { content: '$'; } + +table.pyhltable { border: 0; margin-bottom: 0; } + +table.pyhltable td { vertical-align: top; padding-top: 0; padding-bottom: 0; } + +table.pyhltable td.code { padding-left: .75em; padding-right: 0; } + +.highlight.pygments .lineno, table.pyhltable td:not(.code) { color: #999; padding-left: 0; padding-right: .5em; border-right: 1px solid #dddddd; } + +.highlight.pygments .lineno { display: inline-block; margin-right: .25em; } + +table.pyhltable .linenodiv { background-color: transparent !important; padding-right: 0 !important; } + +.quoteblock { margin: 0 0 1em; padding: 0; border-left: none; } +.quoteblock blockquote { margin: 0 0 1em 0; padding: 0 0 0.6em 0; border: 0; } +.quoteblock blockquote > .paragraph:last-child p { margin-bottom: 0; } +.quoteblock .attribution { margin-top: -.25em; padding-bottom: 0.6em; font-size: 0.86667em; color: #666666; } +.quoteblock .attribution br { display: none; } +.quoteblock .attribution cite { display: block; margin-bottom: 0.66667em; } + +table thead th, table tfoot th { font-weight: bold; } + +table.tableblock.grid-all { border-collapse: separate; border-spacing: 1px; -webkit-border-radius: 3px; border-radius: 3px; border-top: 1px solid #dddddd; border-bottom: 1px solid #dddddd; } + +table.tableblock.frame-topbot, table.tableblock.frame-none { border-left: 0; border-right: 0; } + +table.tableblock.frame-sides, table.tableblock.frame-none { border-top: 0; border-bottom: 0; } + +table.tableblock td .paragraph:last-child p, table.tableblock td > p:last-child { margin-bottom: 0; } + +th.tableblock.halign-left, td.tableblock.halign-left { text-align: left; } + +th.tableblock.halign-right, td.tableblock.halign-right { text-align: right; } + +th.tableblock.halign-center, td.tableblock.halign-center { text-align: center; } + +th.tableblock.valign-top, td.tableblock.valign-top { vertical-align: top; } + +th.tableblock.valign-bottom, td.tableblock.valign-bottom { vertical-align: bottom; } + +th.tableblock.valign-middle, td.tableblock.valign-middle { vertical-align: middle; } + +p.tableblock.header { color: #222222; font-weight: bold; } + +td > div.verse { white-space: pre; } + +ol { margin-left: 0.96667em; } + +ul li ol { margin-left: 0.7em; } + +dl dd { margin-left: 1.125em; } + +dl dd:last-child, dl dd:last-child > :last-child { margin-bottom: 0; } + +ol > li p, ul > li p, ul dd, ol dd, .olist .olist, .ulist .ulist, .ulist .olist, .olist .ulist { margin-bottom: 0.5em; } + +ul.unstyled, ol.unnumbered, ul.checklist, ul.none { list-style-type: none; } + +ul.unstyled, ol.unnumbered, ul.checklist { margin-left: 0.66667em; } + +ul.checklist li > p:first-child > i[class^="icon-check"]:first-child, ul.checklist li > p:first-child > input[type="checkbox"]:first-child { margin-right: 0.25em; } + +ul.checklist li > p:first-child > input[type="checkbox"]:first-child { position: relative; top: 1px; } + +ul.inline { margin: 0 auto 0.5em auto; margin-left: -1.46667em; margin-right: 0; padding: 0; list-style: none; overflow: hidden; } +ul.inline > li { list-style: none; float: left; margin-left: 1.46667em; display: block; } +ul.inline > li > * { display: block; } + +.unstyled dl dt { font-weight: normal; font-style: normal; } + +ol.arabic { list-style-type: decimal; } + +ol.decimal { list-style-type: decimal-leading-zero; } + +ol.loweralpha { list-style-type: lower-alpha; } + +ol.upperalpha { list-style-type: upper-alpha; } + +ol.lowerroman { list-style-type: lower-roman; } + +ol.upperroman { list-style-type: upper-roman; } + +ol.lowergreek { list-style-type: lower-greek; } + +.hdlist > table, .colist > table { border: 0; background: none; } +.hdlist > table > tbody > tr, .colist > table > tbody > tr { background: none; } + +td.hdlist1 { padding-right: .8em; font-weight: bold; } + +td.hdlist1, td.hdlist2 { vertical-align: top; } + +.literalblock + .colist, .listingblock + .colist { margin-top: -0.5em; } + +.colist > table tr > td:first-of-type { padding: 0 .8em; line-height: 1; } +.colist > table tr > td:last-of-type { padding: 0.26667em 0; } + +.qanda > ol > li > p > em:only-child { color: #3876b4; } + +.thumb, .th { line-height: 0; display: inline-block; border: solid 4px white; -webkit-box-shadow: 0 0 0 1px #dddddd; box-shadow: 0 0 0 1px #dddddd; } + +.imageblock.left, .imageblock[style*="float: left"] { margin: 0.26667em 0.66667em 1.33333em 0; } +.imageblock.right, .imageblock[style*="float: right"] { margin: 0.26667em 0 1.33333em 0.66667em; } +.imageblock > .title { margin-bottom: 0; } +.imageblock.thumb, .imageblock.th { border-width: 6px; } +.imageblock.thumb > .title, .imageblock.th > .title { padding: 0 0.13333em; } + +.image.left, .image.right { margin-top: 0.26667em; margin-bottom: 0.26667em; display: inline-block; line-height: 0; } +.image.left { margin-right: 0.66667em; } +.image.right { margin-left: 0.66667em; } + +a.image { text-decoration: none; } + +span.footnote, span.footnoteref { vertical-align: super; font-size: 0.93333em; } +span.footnote a, span.footnoteref a { text-decoration: none; } + +#footnotes { padding-top: 0.8em; padding-bottom: 0.8em; margin-bottom: 0.66667em; } +#footnotes hr { width: 20%; min-width: 6.66667em; margin: -.25em 0 .75em 0; border-width: 1px 0 0 0; } +#footnotes .footnote { padding: 0 0.4em; line-height: 1.3; font-size: 0.93333em; margin-left: 1.2em; text-indent: -1.2em; margin-bottom: .2em; } +#footnotes .footnote a:first-of-type { font-weight: bold; text-decoration: none; } +#footnotes .footnote:last-of-type { margin-bottom: 0; } + +#content #footnotes { margin-top: -0.66667em; margin-bottom: 0; padding: 0.8em 0; } + +.gist .file-data > table { border: none; background: #fff; width: 100%; margin-bottom: 0; } +.gist .file-data > table td.line-data { width: 99%; } + +div.unbreakable { page-break-inside: avoid; } + +.big { font-size: larger; } + +.small { font-size: smaller; } + +.underline { text-decoration: underline; } + +.overline { text-decoration: overline; } + +.line-through { text-decoration: line-through; } + +.aqua { color: #00bfbf; } + +.aqua-background { background-color: #00fafa; } + +.black { color: black; } + +.black-background { background-color: black; } + +.blue { color: #0000bf; } + +.blue-background { background-color: #0000fa; } + +.fuchsia { color: #bf00bf; } + +.fuchsia-background { background-color: #fa00fa; } + +.gray { color: #606060; } + +.gray-background { background-color: #7d7d7d; } + +.green { color: #006000; } + +.green-background { background-color: #007d00; } + +.lime { color: #00bf00; } + +.lime-background { background-color: #00fa00; } + +.maroon { color: #600000; } + +.maroon-background { background-color: #7d0000; } + +.navy { color: #000060; } + +.navy-background { background-color: #00007d; } + +.olive { color: #606000; } + +.olive-background { background-color: #7d7d00; } + +.purple { color: #600060; } + +.purple-background { background-color: #7d007d; } + +.red { color: #bf0000; } + +.red-background { background-color: #fa0000; } + +.silver { color: #909090; } + +.silver-background { background-color: #bcbcbc; } + +.teal { color: #006060; } + +.teal-background { background-color: #007d7d; } + +.white { color: #bfbfbf; } + +.white-background { background-color: #fafafa; } + +.yellow { color: #bfbf00; } + +.yellow-background { background-color: #fafa00; } + +span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } + +.admonitionblock td.icon [class^="icon-"]:before { font-size: 2.5em; text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5); cursor: default; } +.admonitionblock td.icon .icon-note:before { content: "\f05a"; color: #4183c4; color: #2e6295; } +.admonitionblock td.icon .icon-tip:before { content: "\f0eb"; text-shadow: 1px 1px 2px rgba(155, 155, 0, 0.8); color: #111; } +.admonitionblock td.icon .icon-warning:before { content: "\f071"; color: #bf6900; } +.admonitionblock td.icon .icon-caution:before { content: "\f06d"; color: #bf3400; } +.admonitionblock td.icon .icon-important:before { content: "\f06a"; color: #bf0000; } + +.conum { display: inline-block; color: white !important; background-color: #333333; -webkit-border-radius: 100px; border-radius: 100px; text-align: center; width: 20px; height: 20px; font-size: 12px; font-weight: bold; line-height: 20px; font-family: Arial, sans-serif; font-style: normal; position: relative; top: -2px; letter-spacing: -1px; } +.conum * { color: white !important; } +.conum + b { display: none; } +.conum:after { content: attr(data-value); } +.conum:not([data-value]):empty { display: none; } + +p.lead, .paragraph.lead > p, #preamble > .sectionbody > .paragraph:first-of-type p { font-size: 1em; color: #666666; } + +h2 { color: #030303; border-bottom: 1px solid #dddddd; } + +#content h1 > a.anchor, h2 > a.anchor, h3 > a.anchor, #toctitle > a.anchor, .sidebarblock > .content > .title > a.anchor, h4 > a.anchor, h5 > a.anchor, h6 > a.anchor { color: #333333; } + +#header, #content, #footnotes { max-width: 660px; padding-left: 0; padding-right: 0; } + + +#content ul li { padding-left: 0; } + +.olist.procedure > ol { counter-reset: li; list-style: none; position: relative; } +.olist.procedure > ol > li { position: relative; padding: 5px 0 5px 55px; margin-bottom: 5px; } +.olist.procedure > ol > li:before { content: counter(li); counter-increment: li; position: absolute; top: 0; left: 0; height: 100%; width: 30px; padding: 0 10px 0 0; color: #999; font-size: 1.46667em; font-weight: bold; line-height: 1.6; text-align: right; border-right: 1px solid #ddd; } + +.quoteblock blockquote { background: url('../images/github/blockquote-arrow.png?1372292342') 0 2px no-repeat; padding-left: 1em; } + +.sidebarblock > .content > .title { margin-top: -20px; margin-right: -20px; margin-left: -20px; margin-bottom: 20px; padding: 1em; font-size: 0.8em; background: #eaeaea; } \ No newline at end of file diff --git a/docs/v1/resources/theme/p4-theme.yml b/docs/v1/resources/theme/p4-theme.yml new file mode 100644 index 00000000..0f4a9993 --- /dev/null +++ b/docs/v1/resources/theme/p4-theme.yml @@ -0,0 +1,287 @@ +extends: ~ +font: + catalog: + UtopiaStd-Regular: + normal: Utopia/utopia-regular.ttf + bold: Utopia/utopia-bold.ttf + italic: Utopia/utopia-italic.ttf + bold_italic: Utopia/utopia-bolditalic.ttf + LuxiMono: + normal: LuxiMono/luximr.ttf + bold: LuxiMono/luximb.ttf + italic: LuxiMono/luximri.ttf + bold_italic: LuxiMono/luximbi.ttf + OpenSans: + normal: OpenSans/OpenSans-Regular.ttf + bold: OpenSans/OpenSans-Bold.ttf + italic: OpenSans/OpenSans-Italic.ttf + bold_italic: OpenSans/OpenSans-BoldItalic.ttf +page: + layout: portrait + initial_zoom: FitH + margin: [0.5in, 0.67in, 0.67in, 0.67in] + # margin_inner and margin_outer keys are used for recto/verso print margins when media=prepress + margin_inner: 0.75in + margin_outer: 0.59in + size: A4 + numbering: + start-at: toc +base: + text_align: justify + font_color: 333333 + font_family: UtopiaStd-Regular + font_size: 10.5 + # line_height_length is really just a vertical spacing variable; it's not actually the height of a line + line_height_length: 12 + # The Noto font family has a built-in line height of 1.36 + # With this line_height, a line of text will occupy a height of 15.78pt + line_height: $base_line_height_length / 10.5 + font_size_large: round($base_font_size * 1.25) + font_size_small: round($base_font_size * 0.85) + font_size_min: $base_font_size * 0.75 + font_style: normal + border_color: EEEEEE + border_radius: 4 + border_width: 0.5 +role: + lead: + font_size: $base_font_size_large + line-through: + text_decoration: line-through + underline: + text_decoration: underline + big: + font_size: 1.2em + small: + font_size: 0.8em + subtitle: + font_color: 999999 + font_size: 0.8em + font_style: normal_italic +vertical_rhythm: $base_line_height_length +horizontal_rhythm: $base_line_height_length +link: + font_color: 428BCA +# codespan is currently used for monospaced phrases and table cells +codespan: + font_color: #B12146 + font_family: OpenSans +kbd: + background_color: F5F5F5 + border_color: CCCCCC + border_offset: 2 + border_radius: 2 + border_width: 0.5 + font_family: $codespan_font_family + separator_content: "\u202f+\u202f\u200b" +mark: + background_color: FFFF00 + border_offset: 1 +menu: + caret_content: "\u00a0\u203a " + font_style: bold +heading: + font-family: OpenSans + font-color: #262626 + font-size: 17 + font-style: bold + # h1 is used for part titles (book doctype) or the doctitle (article doctype) + h1_font_size: floor($base_font_size * 2.6) + # h2 is used for chapter titles (book doctype only) + h2_font_size: floor($base_font_size * 2.15) + h3_font_size: round($base_font_size * 1.7) + h4_font_size: $base_font_size_large + h5_font_size: $base_font_size + h6_font_size: $base_font_size_small + # rely on built-in line height in Noto + line_height: 1 + margin_top: $vertical_rhythm * 0.4 + margin_bottom: $vertical_rhythm * 0.9 + min_height_after: auto + chapter: + break-before: auto +title_page: + font-family: OpenSans + font-color: #080808 + font-size: 25 + font-style: bold + text-align: center + logo: + image: image:logo.png[pdfwidth=3.0in,align=center] + subtitle: + font-family: OpenSans + font-color: #080808 + font-size: 15 + text-align: center + line_height: 1 + revision: + font-family: OpenSans + font-color: #080808 + font-size: 10 + text-align: center + margin_top: $base_font_size * 1.25 +block: + margin_bottom: $vertical_rhythm +caption: + align: left + font_size: $base_font_size * 0.95 + font_style: italic + # FIXME perhaps set line_height instead of / in addition to margins? + margin_inside: $vertical_rhythm / 3 + margin_outside: 0 +abstract: + title: + font-family: UtopiaStd-Regular + font-color: #080808 + font-size: 15 + font-style: italic + text-align: center +admonition: + column_rule_color: $base_border_color + column_rule_width: $base_border_width + padding: [$vertical_rhythm / 3.0, $horizontal_rhythm, $vertical_rhythm / 3.0, $horizontal_rhythm] + label: + text_transform: uppercase + font_style: bold +quote: + font_size: $base_font_size_large + border_color: $base_border_color + border_width: 0 + border_left_width: $horizontal_rhythm / 3 + padding: [$vertical_rhythm / 4, $horizontal_rhythm, $vertical_rhythm / 4, $horizontal_rhythm + $quote_border_left_width / 2] + cite: + font_size: $base_font_size_small + font_color: $role_subtitle_font_color +verse: + font_size: $quote_font_size + border_color: $quote_border_color + border_width: $quote_border_width + border_left_width: $quote_border_left_width + padding: $quote_padding + cite: + font_size: $quote_cite_font_size + font_color: $quote_cite_font_color +# code is used for literal, listing, and source blocks and literal table cells +code: + font_color: #fcf3d9 + font_family: LuxiMono + font_size: 9 + padding: $code_font_size + line_height: 1.25 + # line_gap is an experimental property to control how a background color is applied to an inline block element + line_gap: 3.8 + background_color: F5F5F5 + border_color: CCCCCC + border_radius: $base_border_radius + border_width: 0.75 +conum: + font_family: $codespan_font_family + font_color: $codespan_font_color + font_size: $base_font_size + line_height: 4 / 3 + glyphs: circled +example: + border_color: $base_border_color + border_radius: $base_border_radius + border_width: 0.75 + padding: [$vertical_rhythm, $horizontal_rhythm, $vertical_rhythm, $horizontal_rhythm] +image: + caption: + font-family: UtopiaStd-Regular + font-color: #080808 + font-size: 10 + font-style: italic + text-align: center +prose: + margin_bottom: $block_margin_bottom +sidebar: + background_color: EEEEEE + border_color: E1E1E1 + border_radius: $base_border_radius + border_width: $base_border_width + padding: [$vertical_rhythm, $vertical_rhythm * 1.25, $vertical_rhythm, $vertical_rhythm * 1.25] + title: + text_align: center + font_color: $heading_font_color + font_size: $heading_h4_font_size + font_style: $heading_font_style +thematic_break: + border_color: $base_border_color + border_style: solid + border_width: $base_border_width + padding: [$vertical_rhythm * 0.5, 0] +list: + indent: $horizontal_rhythm * 1.5 + #marker_font_color: 404040 + # NOTE list_item_spacing only applies to list items that do not have complex content + item_spacing: $vertical_rhythm / 2 +# Force all indent levels of an unordered list to use the same font +# glyph for all symbols, because the fonts we are using do not have +# definitions for any of the ones that asciidoctor-pdf normally uses. +# If we change our fonts to have definitions for the default code points +# used by asciidoctor-pdf, the 'ulist:' section can be deleted. +ulist: + marker: + square: + content: "\u2022" + circle: + content: "\u2022" + checked: + content: "\u2022" + unchecked: + content: "\u2022" +description_list: + term_font_style: bold + term_spacing: $vertical_rhythm / 4 + description_indent: $horizontal_rhythm * 1.25 +callout_list: + margin_top_after_code: -$block_margin_bottom / 2 +table: + border_color: DDDDDD + border_width: $base_border_width + grid_width: $base_border_width + cell_padding: 3 + head: + font_style: bold + border_bottom_width: $base_border_width * 2.5 + body: + stripe_background_color: F9F9F9 + foot: + background_color: F0F0F0 + caption: + text-align: center + end: bottom +toc: + indent: $horizontal_rhythm + font-family: OpenSans + font-color: #01226e + font-size: 10 + indent: 20 + title: + font-family: OpenSans + font-color: #080808 + font-size: 15 + font-style: bold + dot-leader: + content: ". " + levels: 2 3 4 5 +footnotes: + font_size: round($base_font_size * 0.75) + item_spacing: $list_item_spacing / 2 +index: + column_gap: $vertical_rhythm +header: + font_size: $base_font_size_small + line_height: 1 + vertical_align: middle +footer: + font_size: 10 + font-family: UtopiaStd-Regular + height: 1in + columns: =100% + recto: + center: + content: '{page-number}' + verso: + center: + content: '{page-number}' diff --git a/docs/v1/references.bib b/docs/v1/resources/theme/references.bib similarity index 84% rename from docs/v1/references.bib rename to docs/v1/resources/theme/references.bib index 01b95f7c..ae47c0d6 100644 --- a/docs/v1/references.bib +++ b/docs/v1/resources/theme/references.bib @@ -31,7 +31,7 @@ @ONLINE { Stratum @ONLINE { P4ComplexTypes, title = "Complex types in $P4_{16}$", - url = "https://p4.org/wp-content/uploads/2024/10/P4-16-spec-v1.2.5.html#sec-p4-type" + url = "https://p4.org/p4-spec/docs/P4-16-v1.2.1.html#sec-p4-type" } @ONLINE { ProtoDefaults, @@ -47,22 +47,22 @@ @ONLINE { PIRepo @ONLINE { P4TableProperties, title = "Table properties in $P4_{16}$", - url = "https://p4.org/wp-content/uploads/2024/10/P4-16-spec-v1.2.5.html#sec-table-props" + url = "https://p4.org/p4-spec/docs/P4-16-v1.2.1.html#sec-table-props" } @ONLINE { P4ValueSets, title = "Value Sets in $P4_{16}$", - url = "https://p4.org/wp-content/uploads/2024/10/P4-16-spec-v1.2.5.html#sec-value-set" + url = "https://p4.org/p4-spec/docs/P4-16-v1.2.1.html#sec-value-set" } @ONLINE { P4SelectExpr, title = "Select expressions in $P4_{16}$", - url = "https://p4.org/wp-content/uploads/2024/10/P4-16-spec-v1.2.5.html#sec-select" + url = "https://p4.org/p4-spec/docs/P4-16-v1.2.1.html#sec-select" } @ONLINE { P4Revisions110, title = "Summary of changes made in $P4_{16}$ version 1.1.0", - url = "https://p4.org/wp-content/uploads/2024/10/P4-16-spec-v1.2.5.html#sec-summary-of-changes-made-in-version-110" + url = "https://p4.org/p4-spec/docs/P4-16-v1.2.1.html#sec-summary-of-changes-made-in-version-110" } @ONLINE { P4Revisions122, @@ -77,7 +77,7 @@ @ONLINE { P4Revisions124 @ONLINE { P4Spec, title = "$P4_{16}$ 1.2.1 specification", - url = "https://p4.org/wp-content/uploads/2024/10/P4-16-spec-v1.2.5.html" + url = "https://p4.org/p4-spec/docs/P4-16-v1.2.1.html" } @ONLINE { PSA, @@ -92,7 +92,7 @@ @ONLINE { PNA @ONLINE { P4Enums, title = "Enums in $P4_{16}$", - url = "https://p4.org/wp-content/uploads/2024/10/P4-16-spec-v1.2.5.html#sec-enum-types" + url = "https://p4.org/p4-spec/docs/P4-16-v1.2.1.html#sec-enum-types" } @ONLINE { ProtoAny, @@ -128,7 +128,7 @@ @ONLINE { P4APIWGCharter @ONLINE { P4NewTypes, title = "Introducing new types in $P4_{16}$", - url = "https://p4.org/wp-content/uploads/2024/10/P4-16-spec-v1.2.5.html#sec-newtype" + url = "https://p4.org/p4-spec/docs/P4-16-v1.2.1.html#sec-newtype" } @ONLINE { APIVersioning, @@ -158,7 +158,7 @@ @ONLINE { RFC2697 @ONLINE { P4MatchTypes, title = "Match types in P4", - url = "https://p4.org/wp-content/uploads/2024/10/P4-16-spec-v1.2.5.html#sec-match-kind-type" + url = "https://p4.org/p4-spec/docs/P4-16-v1.2.1.html#sec-match-kind-type" } @ONLINE { gRPCStreamC, @@ -173,7 +173,7 @@ @ONLINE { gRPCAuth @ONLINE { P4ActionAnnotations, title = "P4 standard annotations on table actions", - url = "https://p4.org/wp-content/uploads/2024/10/P4-16-spec-v1.2.5.html#sec-table-action-anno" + url = "https://p4.org/p4-spec/docs/P4-16-v1.2.1.html#sec-table-action-anno" } @ONLINE { PSAActionSelector, @@ -193,7 +193,7 @@ @ONLINE { PSAAtomicityOfControlPlaneOps @ONLINE { P4Concurrency, title = "P4 Concurrency Model", - url = "https://p4.org/wp-content/uploads/2024/10/P4-16-spec-v1.2.5.html#sec-concurrency" + url = "https://p4.org/p4-spec/docs/P4-16-v1.2.1.html#sec-concurrency" } @ONLINE { PSATranslation, @@ -213,7 +213,7 @@ @ONLINE { ProtoOneOfBackwardsCompatibility @ONLINE { P4Annotations, title = "P4 Annotations", - url = "https://p4.org/wp-content/uploads/2024/10/P4-16-spec-v1.2.5.html#sec-annotations" + url = "https://p4.org/p4-spec/docs/P4-16-v1.2.1.html#sec-annotations" } @ONLINE { v1model, diff --git a/tools/madokolint.conf.json b/tools/asciidocint.conf.json similarity index 100% rename from tools/madokolint.conf.json rename to tools/asciidocint.conf.json diff --git a/tools/madokolint.py b/tools/asciidoclint.py similarity index 86% rename from tools/madokolint.py rename to tools/asciidoclint.py index 1438728f..3a8b31ae 100755 --- a/tools/madokolint.py +++ b/tools/asciidoclint.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -# Copyright 2019 VMware, Inc. +# Copyright 2024 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,10 +18,10 @@ # DISCLAIMER: This is a work in progress. This linter was written specifically # for the P4Runtime specification document and may not be useful for other -# Madoko documents, as it may be making some assumptions as to how the document +# AsciiDoc documents, as it may be making some assumptions as to how the document # was written. -# TODO: handle Madoko includes (we do not use them for the P4Runtime spec)? + import argparse @@ -33,25 +33,25 @@ import traceback -DEFAULT_CONF = 'madokolint.conf.json' +DEFAULT_CONF = 'asciidocint.conf.json' LINE_WRAP_LENGTH = 80 -parser = argparse.ArgumentParser(description='Lint tool for Madoko code') +parser = argparse.ArgumentParser(description='Lint tool for Asciidoc code') parser.add_argument('files', metavar='FILE', type=str, nargs='+', help='Input files') parser.add_argument('--conf', type=str, help='Configuration file for lint tool') -class MadokoFmtError(Exception): +class AsciidocFmtError(Exception): def __init__(self, filename, lineno, description): self.filename = filename self.lineno = lineno self.description = description def __str__(self): - return "Unexpected Madoko code in file {} at line {}: {}".format( + return "Unexpected Asciidoc code in file {} at line {}: {}".format( self.filename, self.lineno, self.description) @@ -122,7 +122,7 @@ def exit(self, line, filename, lineno): class ContextSkipBlocks(Context): - """A context used to only visit Madoko code outside of blocks.""" + """A context used to only visit Asciidoc code outside of blocks.""" Block = namedtuple('Block', ['num_tildes', 'name']) @@ -143,19 +143,19 @@ def enter(self, line, filename, lineno): return False if has_end: if not self.blocks_stack: - raise MadokoFmtError(filename, lineno, "Block end line but no block was begun") + raise AsciidocFmtError(filename, lineno, "Block end line but no block was begun") expected = self.blocks_stack.pop() if num_tildes != expected.num_tildes or blockname != expected.name: - raise MadokoFmtError( + raise AsciidocFmtError( filename, lineno, "Block end line does not match last visited block begin line") return False if blockname is None: if not self.blocks_stack: - raise MadokoFmtError(filename, lineno, "Block end line but no block was begun") + raise AsciidocFmtError(filename, lineno, "Block end line but no block was begun") expected = self.blocks_stack.pop() if num_tildes != expected.num_tildes: - raise MadokoFmtError( + raise AsciidocFmtError( filename, lineno, "Block end line does not match last visited block begin line") return False @@ -168,7 +168,7 @@ def enter(self, line, filename, lineno): # TODO: would "skip metadata" be more generic? class ContextAfterTitle(Context): - """A context used to visit only Madoko code after the [TITLE] block element. + """A context used to visit only Asciidoc code after the [TITLE] block element. """ def __init__(self, *args): @@ -244,22 +244,6 @@ def check(line, lineno): foreach_line(path, Context(), check) -def check_predefined_abbreviations(path): - abbreviations = { - 'e.g.': '⪚', - 'i.e.': '&ie;', - 'et al.': '&etal;', - } - - def check(line, lineno): - for k, v in abbreviations.items(): - if k in line: - lint_state.error(path, lineno, line, - "contains '{}', use '{}' instead".format(k, v)) - - foreach_line(path, ContextCompose(ContextAfterTitle(), ContextSkipBlocks()), check) - - def check_keywords(path): def check(line, lineno): for word in line.split(): @@ -275,7 +259,6 @@ def check(line, lineno): def process_one(path): check_line_wraps(path) - check_predefined_abbreviations(path) check_trailing_whitespace(path) check_keywords(path) @@ -288,7 +271,7 @@ def main(): print("'{}' is not a valid file path".format(f)) sys.exit(1) _, ext = os.path.splitext(f) - if ext != ".mdk": + if ext != ".adoc": print("'{}' does not have an .mdk extension") sys.exit(1) @@ -313,7 +296,7 @@ def main(): for f in args.files: try: process_one(f) - except MadokoFmtError as e: + except AsciidocFmtError as e: print(e) errors_cnt = lint_state.errors_cnt