blob: 0357d122daaee331aa29277709b30e6331173e23 [file]
.. _module-pw_rpc2-protocol:
=============
Wire protocol
=============
.. pigweed-module-subpage::
:name: pw_rpc2
Pigweed RPC 2 uses a binary-framed wire protocol designed for low processing
overhead. Each packet consists of a fixed-size header (dependent on the packet
type) sharing a common prefix followed by type-specific fields. In packets with
payloads, the payload is located immediately following the header.
All multi-byte numeric fields are encoded in **little-endian** byte order.
----------------------------------
Connection state and establishment
----------------------------------
As RPC 2 runs over a generic ``Socket`` interface, a newly established
connection must first identify that the peer is a compatible RPC endpoint
before regular data flow can begin.
To achieve this, RPC 2 uses two distinct categories of packets:
- **Handshake packets** are used during the initial establishment phase. They
contain magic numbers which identify the sender as an RPC endpoint.
- **RPC packets** are used for regular data exchange. After the handshake
completes, all subsequent packets over the connection are one of these.
-------------------
Three-way handshake
-------------------
Each connection initiated by an RPC client and accepted by a server begins with
a three-way handshake loosely modeled after TCP. Its purpose is to identify the
endpoints and ensure compatibility. In the future, it may be extended to support
negotiation of connection parameters.
Handshake flow
==============
The handshake begins when the client opens the connection.
1. The client sends a ``SYN`` (type 1) with its protocol version (currently
always ``1``).
2. The server responds with a ``SYN_ACK`` (type 2) containing the minimum of its
version and the client's (currently also ``1``).
3. The client sends an ``ACK`` (type 3), accepting the server's version, to
complete the handshake.
As there is currently only version ``1``, this handshake primarily establishes
that both endpoints speak RPC. However, clients and servers should be written to
accept newer versions from the peer, falling back if they aren't supported.
If either endpoint receives an invalid or unexpected packet during the
handshake, it should terminate the connection.
.. mermaid::
:alt: 3-way handshake flow
:align: center
sequenceDiagram
autonumber
actor C as Initiator (Client)
actor S as Responder (Server)
C->>S: SYN (version = 1)
Note over S: Validate magic ('PRPC')
S->>C: SYN_ACK (negotiated_version)
Note over C: Validate response magic and version
C->>S: ACK (negotiated_version)
Note over C,S: Connection established, begin RPC phase
C->>S: REQUEST (service_id, method_id, call_id)
Handshake packet
================
The handshake uses a dedicated 8-byte packet.
.. list-table::
:header-rows: 1
:widths: 15 15 20 50
* - Offset
- Size
- Name
- Description
* - 0
- 4
- ``magic`` (``uint32_t``)
- ``0x43505250`` ("PRPC")
* - 4
- 1
- ``version`` (``uint8_t``)
- Protocol version, currently ``1``
* - 5
- 1
- ``type`` (``uint8_t``)
- ``1`` (SYN), ``2`` (SYN_ACK), or ``3`` (ACK)
* - 6
- 2
- ``reserved`` (``uint16_t``)
- Written as ``0``, ignored on receipt
-----------
RPC packets
-----------
Once the handshake is complete, RPC packets are sent over the connection.
RPC packet header structure
===========================
Every RPC packet begins with a common 5-byte header:
.. list-table::
:header-rows: 1
:widths: 15 15 20 50
* - Offset
- Size
- Name
- Description
* - 0
- 4
- ``call_id`` (``uint32_t``)
- Client-assigned ID for this specific invocation. Selected when sending
the initial request, and echoed afterwards.
* - 4
- 1
- ``type`` (``uint8_t``)
- The type of packet. Determines further fields.
.. _module-pw_rpc2-protocol-types:
RPC packet types
================
The protocol defines eight types of RPC packet. In each type, bit 0 indicates
the direction of the packet; a value of 0 means client-to-server, while 1
means server-to-client.
.. list-table::
:widths: 20 10 15 55
:header-rows: 1
* - Packet type
- Value
- Direction
- Description
* - ``REQUEST``
- ``0x02``
- Client -> server
- Client initiates an RPC call. Specifies the target ``service_id``,
``method_id``, and optional initial payload.
* - ``RESPONSE``
- ``0x03``
- Server -> client
- The single final response payload from the server in a unary or client
streaming RPC.
* - ``CLIENT_MESSAGE``
- ``0x04``
- Client -> server
- A stream data message in a client or bidirectional streaming RPC.
* - ``SERVER_MESSAGE``
- ``0x05``
- Server -> client
- A stream data message in a server or bidirectional streaming RPC.
* - ``CLIENT_STREAM_END``
- ``0x06``
- Client -> server
- Signals normal completion of a client-to-server stream
(client streaming, bidirectional). The server may continue transmitting.
* - ``SERVER_STREAM_END``
- ``0x07``
- Server -> client
- Signals normal completion of a server-to-client stream
(server streaming, bidirectional) and finishes the RPC call.
* - ``CLIENT_ERROR``
- ``0x08``
- Client -> server
- Signals abnormal call termination or a protocol fault from the client.
Carries a 16-bit ``ClientError`` value.
* - ``SERVER_ERROR``
- ``0x09``
- Server -> client
- Signals abnormal call termination or a protocol fault from the server.
Carries a 16-bit ``ServerError`` value.
Packet structures
=================
The fields of each RPC packet are listed below.
.. admonition:: Payload framing
RPC 2 runs over a datagram socket, so each packet from the peer is received
in full. There is no length field in the protocol itself. Transport
implementations used with RPC must ensure that buffers read from the peer
over an RPC connection contain only, and exactly, the bytes of a single
packet without any padding.
``REQUEST`` packet (type ``0x02``)
----------------------------------
Initiated by a client to invoke an RPC. Its header size is **13 bytes**.
.. list-table::
:header-rows: 1
:widths: 15 15 20 50
* - Offset
- Size
- Name
- Description
* - 0
- 5
- Header
- Common RPC packet header with ``type`` ``0x02``
* - 5
- 4
- ``service_id`` (``uint32_t``)
- ID of the service to invoke
* - 9
- 4
- ``method_id`` (``uint32_t``)
- ID of the method to invoke
* - 13
- Variable
- Payload
- Remainder of the packet
``RESPONSE`` packet (type ``0x03``)
-----------------------------------
Carries the final response from the server in a unary or client streaming RPC.
Its header size is **5 bytes**.
.. list-table::
:header-rows: 1
:widths: 15 15 20 50
* - Offset
- Size
- Name
- Description
* - 0
- 5
- Header
- Common RPC packet header with ``type`` ``0x03``
* - 5
- Variable
- Payload
- Remainder of the packet
``CLIENT_MESSAGE`` and ``SERVER_MESSAGE`` packets (types ``0x04`` and ``0x05``)
-------------------------------------------------------------------------------
Carries a streamed datagram from client to server (``0x04``) or server to client
(``0x05``). Its header size is **5 bytes**.
.. list-table::
:header-rows: 1
:widths: 15 15 20 50
* - Offset
- Size
- Name
- Description
* - 0
- 5
- Header
- Common RPC packet header with ``type`` ``0x04`` or ``0x05``
* - 5
- Variable
- Payload
- Remainder of the packet
``CLIENT_STREAM_END`` and ``SERVER_STREAM_END`` packets (types ``0x06`` and ``0x07``)
-------------------------------------------------------------------------------------
Signals the normal completion of a stream from client to server (``0x06``) or
server to client (``0x07``). Its header size is **5 bytes**.
.. list-table::
:header-rows: 1
:widths: 15 15 20 50
* - Offset
- Size
- Name
- Description
* - 0
- 5
- Header
- Common RPC packet header with ``type`` ``0x06`` or ``0x07``
``CLIENT_ERROR`` and ``SERVER_ERROR`` packets (types ``0x08`` and ``0x09``)
---------------------------------------------------------------------------
Aborts an RPC call immediately. Its header size is **7 bytes**.
.. list-table::
:header-rows: 1
:widths: 15 15 20 50
* - Offset
- Size
- Name
- Description
* - 0
- 5
- Header
- Common RPC packet header with ``type`` ``0x08`` or ``0x09``
* - 5
- 2
- ``error`` (``uint16_t``)
- ``ClientError`` (``0x08``) or ``ServerError`` (``0x09``) value
RPC error codes
===============
``CLIENT_ERROR`` and ``SERVER_ERROR`` packets contain one of several error
codes, whose meanings are listed below.
.. list-table:: Client error codes
:header-rows: 1
:widths: 10 40 50
* - Value
- Name
- Description
* - ``0``
- ``OK``
- No error. Never sent.
* - ``1``
- ``UNKNOWN``
- Unrecognized error code.
* - ``2``
- ``INTERNAL``
- A bug in the RPC implementation.
* - ``3``
- ``CANCELLED``
- The call was deliberately cancelled by the client-side application code.
* - ``4``
- ``RECEIVED_PACKET_FOR_SERVER``
- The client received a packet type that may only be sent from a client
to a server.
.. list-table:: Server error codes
:header-rows: 1
:widths: 10 40 50
* - Value
- Name
- Description
* - ``0``
- ``OK``
- No error. Never sent.
* - ``1``
- ``UNKNOWN``
- Unrecognized error code.
* - ``2``
- ``INTERNAL``
- A bug in the RPC implementation.
* - ``3``
- ``CANCELLED``
- The call was deliberately cancelled by the server-side application code.
* - ``4``
- ``RECEIVED_PACKET_FOR_CLIENT``
- The server received a packet type that may only be sent from a server
to a client.
* - ``5``
- ``DROPPED_WITHOUT_RESPONSE``
- The server released a unary call without sending a response or
cancelling it.
* - ``6``
- ``SERVICE_UNREGISTERED``
- The target service was unregistered from the server while the call was
running.
* - ``7``
- ``UNKNOWN_SERVICE``
- The requested service is not registered on the server.
* - ``8``
- ``UNKNOWN_METHOD``
- The requested method is not registered on the target service.
* - ``9``
- ``INVALID_REQUEST_PAYLOAD``
- The request payload was invalid.
* - ``10``
- ``FAILED_TO_ALLOCATE_CALL``
- Failed to allocate call state for an incoming request.
* - ``11``
- ``FAILED_TO_ALLOCATE_CALL_RESOURCES_WHILE_RUNNING``
- Failed to allocate necessary resources while running the call.