1.. _module-pw_rpc: 2 3====== 4pw_rpc 5====== 6.. pigweed-module:: 7 :name: pw_rpc 8 9The ``pw_rpc`` module provides a system for defining and invoking remote 10procedure calls (RPCs) on a device. 11 12This document discusses the ``pw_rpc`` protocol and its C++ implementation. 13``pw_rpc`` implementations for other languages are described in their own 14documents: 15 16.. toctree:: 17 :maxdepth: 1 18 19 py/docs 20 ts/docs 21 22.. admonition:: Try it out! 23 24 For a quick intro to ``pw_rpc``, see the 25 :ref:`module-pw_hdlc-rpc-example` in the :ref:`module-pw_hdlc` module. 26 27.. warning:: 28 29 This documentation is under construction. Many sections are outdated or 30 incomplete. The content needs to be reorgnanized. 31 32--------------- 33Implementations 34--------------- 35Pigweed provides several client and server implementations of ``pw_rpc``. 36 37.. list-table:: 38 :header-rows: 1 39 40 * - Language 41 - Server 42 - Client 43 * - C++ (raw) 44 - ✅ 45 - ✅ 46 * - C++ (Nanopb) 47 - ✅ 48 - ✅ 49 * - C++ (pw_protobuf) 50 - ✅ 51 - ✅ 52 * - Java 53 - 54 - ✅ 55 * - Python 56 - 57 - ✅ 58 * - TypeScript 59 - 60 - in development 61 62.. warning:: 63 64 ``pw_protobuf`` and ``nanopb`` RPC services cannot currently coexist within the 65 same RPC server. Unless you are running multiple RPC servers, you cannot 66 incrementally migrate services from one protobuf implementation to another, 67 or otherwise mix and match. See 68 `Issue 234874320 <https://issues.pigweed.dev/issues/234874320>`_. 69 70------------- 71RPC semantics 72------------- 73The semantics of ``pw_rpc`` are similar to `gRPC 74<https://grpc.io/docs/what-is-grpc/core-concepts/>`_. 75 76RPC call lifecycle 77================== 78In ``pw_rpc``, an RPC begins when the client sends an initial packet. The server 79receives the packet, looks up the relevant service method, then calls into the 80RPC function. The RPC is considered active until the server sends a status to 81finish the RPC. The client may terminate an ongoing RPC by cancelling it. 82Multiple concurrent RPC requests to the same method may be made simultaneously 83(Note: Concurrent requests are not yet possible using the Java client. See 84`Issue 237418397 <https://issues.pigweed.dev/issues/237418397>`_). 85 86Depending the type of RPC, the client and server exchange zero or more protobuf 87request or response payloads. There are four RPC types: 88 89* **Unary**. The client sends one request and the server sends one 90 response with a status. 91* **Server streaming**. The client sends one request and the server sends zero 92 or more responses followed by a status. 93* **Client streaming**. The client sends zero or more requests and the server 94 sends one response with a status. 95* **Bidirectional streaming**. The client sends zero or more requests and the 96 server sends zero or more responses followed by a status. 97 98Events 99------ 100The key events in the RPC lifecycle are: 101 102* **Start**. The client initiates the RPC. The server's RPC body executes. 103* **Finish**. The server sends a status and completes the RPC. The client calls 104 a callback. 105* **Request**. The client sends a request protobuf. The server calls a callback 106 when it receives it. In unary and server streaming RPCs, there is only one 107 request and it is handled when the RPC starts. 108* **Response**. The server sends a response protobuf. The client calls a 109 callback when it receives it. In unary and client streaming RPCs, there is 110 only one response and it is handled when the RPC completes. 111* **Error**. The server or client terminates the RPC abnormally with a status. 112 The receiving endpoint calls a callback. 113* **Request Completion**. The client sends a message that it would like to 114 request call completion. The server calls a callback when it receives it. Some 115 servers may ignore the request completion message. In client and bidirectional 116 streaming RPCs, this also indicates that client has finished sending requests. 117 118Status codes 119============ 120``pw_rpc`` call objects (``ClientReaderWriter``, ``ServerReaderWriter``, etc.) 121use certain status codes to indicate what occurred. These codes are returned 122from functions like ``Write()`` or ``Finish()``. 123 124* ``OK`` -- The operation succeeded. 125* ``UNAVAILABLE`` -- The channel is not currently registered with the server or 126 client. 127* ``UNKNOWN`` -- Sending a packet failed due to an unrecoverable 128 :cpp:func:`pw::rpc::ChannelOutput::Send` error. 129 130Unrequested responses 131===================== 132``pw_rpc`` supports sending responses to RPCs that have not yet been invoked by 133a client. This is useful in testing and in situations like an RPC that triggers 134reboot. After the reboot, the device opens the writer object and sends its 135response to the client. 136 137The C++ API for opening a server reader/writer takes the generated RPC function 138as a template parameter. The server to use, channel ID, and service instance are 139passed as arguments. The API is the same for all RPC types, except the 140appropriate reader/writer class must be used. 141 142.. code-block:: c++ 143 144 // Open a ServerWriter for a server streaming RPC. 145 auto writer = RawServerWriter::Open<pw_rpc::raw::ServiceName::MethodName>( 146 server, channel_id, service_instance); 147 148 // Send some responses, even though the client has not yet called this RPC. 149 CHECK_OK(writer.Write(encoded_response_1)); 150 CHECK_OK(writer.Write(encoded_response_2)); 151 152 // Finish the RPC. 153 CHECK_OK(writer.Finish(OkStatus())); 154 155Errata 156------ 157Prior to support for concurrent requests to a single method, no identifier was 158present to distinguish different calls to the same method. When a "call ID" 159feature was first introduced to solve this issue, existing clients and servers 160(1) set this value to zero and (2) ignored this value. 161 162When initial support for concurrent methods was added, a separate "open call ID" 163was introduced to distinguish unrequested responses. However, legacy servers 164built prior to this change continue to send unrequested responses with call ID 165zero. Prior to `this fix 166<https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/192311>`_, clients 167which used "open call ID" would not accept unrequested responses from legacy 168servers. Clients built after that change will accept unrequested responses which 169use both "open call ID" and call ID zero. 170 171See `Issue 237418397 <https://issues.pigweed.dev/issues/237418397>`_ for more 172details and discussion. 173 174--------------- 175Creating an RPC 176--------------- 177 1781. RPC service declaration 179========================== 180Pigweed RPCs are declared in a protocol buffer service definition. 181 182* `Protocol Buffer service documentation 183 <https://developers.google.com/protocol-buffers/docs/proto3#services>`_ 184* `gRPC service definition documentation 185 <https://grpc.io/docs/what-is-grpc/core-concepts/#service-definition>`_ 186 187.. code-block:: protobuf 188 189 syntax = "proto3"; 190 191 package foo.bar; 192 193 message Request {} 194 195 message Response { 196 int32 number = 1; 197 } 198 199 service TheService { 200 rpc MethodOne(Request) returns (Response) {} 201 rpc MethodTwo(Request) returns (stream Response) {} 202 } 203 204This protocol buffer is declared in a ``BUILD.gn`` file as follows: 205 206.. code-block:: python 207 208 import("//build_overrides/pigweed.gni") 209 import("$dir_pw_protobuf_compiler/proto.gni") 210 211 pw_proto_library("the_service_proto") { 212 sources = [ "foo_bar/the_service.proto" ] 213 } 214 215.. admonition:: proto2 or proto3 syntax? 216 217 Always use proto3 syntax rather than proto2 for new protocol buffers. Proto2 218 protobufs can be compiled for ``pw_rpc``, but they are not as well supported 219 as proto3. Specifically, ``pw_rpc`` lacks support for non-zero default values 220 in proto2. When using Nanopb with ``pw_rpc``, proto2 response protobufs with 221 non-zero field defaults should be manually initialized to the default struct. 222 223 In the past, proto3 was sometimes avoided because it lacked support for field 224 presence detection. Fortunately, this has been fixed: proto3 now supports 225 ``optional`` fields, which are equivalent to proto2 ``optional`` fields. 226 227 If you need to distinguish between a default-valued field and a missing field, 228 mark the field as ``optional``. The presence of the field can be detected 229 with ``std::optional``, a ``HasField(name)``, or ``has_<field>`` member, 230 depending on the library. 231 232 Optional fields have some overhead --- if using Nanopb, default-valued fields 233 are included in the encoded proto, and the proto structs have a 234 ``has_<field>`` flag for each optional field. Use plain fields if field 235 presence detection is not needed. 236 237 .. code-block:: protobuf 238 239 syntax = "proto3"; 240 241 message MyMessage { 242 // Leaving this field unset is equivalent to setting it to 0. 243 int32 number = 1; 244 245 // Setting this field to 0 is different from leaving it unset. 246 optional int32 other_number = 2; 247 } 248 2492. RPC code generation 250====================== 251``pw_rpc`` generates a C++ header file for each ``.proto`` file. This header is 252generated in the build output directory. Its exact location varies by build 253system and toolchain, but the C++ include path always matches the sources 254declaration in the ``pw_proto_library``. The ``.proto`` extension is replaced 255with an extension corresponding to the protobuf library in use. 256 257================== =============== =============== ============= 258Protobuf libraries Build subtarget Protobuf header pw_rpc header 259================== =============== =============== ============= 260Raw only .raw_rpc (none) .raw_rpc.pb.h 261Nanopb or raw .nanopb_rpc .pb.h .rpc.pb.h 262pw_protobuf or raw .pwpb_rpc .pwpb.h .rpc.pwpb.h 263================== =============== =============== ============= 264 265For example, the generated RPC header for ``"foo_bar/the_service.proto"`` is 266``"foo_bar/the_service.rpc.pb.h"`` for Nanopb or 267``"foo_bar/the_service.raw_rpc.pb.h"`` for raw RPCs. 268 269The generated header defines a base class for each RPC service declared in the 270``.proto`` file. A service named ``TheService`` in package ``foo.bar`` would 271generate the following base class for pw_protobuf: 272 273.. cpp:class:: template <typename Implementation> foo::bar::pw_rpc::pwpb::TheService::Service 274 2753. RPC service definition 276========================= 277The serivce class is implemented by inheriting from the generated RPC service 278base class and defining a method for each RPC. The methods must match the name 279and function signature for one of the supported protobuf implementations. 280Services may mix and match protobuf implementations within one service. 281 282.. tip:: 283 284 The generated code includes RPC service implementation stubs. You can 285 reference or copy and paste these to get started with implementing a service. 286 These stub classes are generated at the bottom of the pw_rpc proto header. 287 288 To use the stubs, do the following: 289 290 #. Locate the generated RPC header in the build directory. For example: 291 292 .. code-block:: sh 293 294 find out/ -name <proto_name>.rpc.pwpb.h 295 296 #. Scroll to the bottom of the generated RPC header. 297 #. Copy the stub class declaration to a header file. 298 #. Copy the member function definitions to a source file. 299 #. Rename the class or change the namespace, if desired. 300 #. List these files in a build target with a dependency on the 301 ``pw_proto_library``. 302 303A pw_protobuf implementation of this service would be as follows: 304 305.. code-block:: cpp 306 307 #include "foo_bar/the_service.rpc.pwpb.h" 308 309 namespace foo::bar { 310 311 class TheService : public pw_rpc::pwpb::TheService::Service<TheService> { 312 public: 313 pw::Status MethodOne(const Request::Message& request, 314 Response::Message& response) { 315 // implementation 316 response.number = 123; 317 return pw::OkStatus(); 318 } 319 320 void MethodTwo(const Request::Message& request, 321 ServerWriter<Response::Message>& response) { 322 // implementation 323 response.Write({.number = 123}); 324 } 325 }; 326 327 } // namespace foo::bar 328 329The pw_protobuf implementation would be declared in a ``BUILD.gn``: 330 331.. code-block:: python 332 333 import("//build_overrides/pigweed.gni") 334 335 import("$dir_pw_build/target_types.gni") 336 337 pw_source_set("the_service") { 338 public_configs = [ ":public" ] 339 public = [ "public/foo_bar/service.h" ] 340 public_deps = [ ":the_service_proto.pwpb_rpc" ] 341 } 342 3434. Register the service with a server 344===================================== 345This example code sets up an RPC server with an :ref:`HDLC<module-pw_hdlc>` 346channel output and the example service. 347 348.. code-block:: cpp 349 350 // Set up the output channel for the pw_rpc server to use. This configures the 351 // pw_rpc server to use HDLC over UART; projects not using UART and HDLC must 352 // adapt this as necessary. 353 pw::stream::SysIoWriter writer; 354 pw::rpc::FixedMtuChannelOutput<kMaxTransmissionUnit> hdlc_channel_output( 355 writer, pw::hdlc::kDefaultRpcAddress, "HDLC output"); 356 357 // Allocate an array of channels for the server to use. If dynamic allocation 358 // is enabled (PW_RPC_DYNAMIC_ALLOCATION=1), the server can be initialized 359 // without any channels, and they can be added later. 360 pw::rpc::Channel channels[] = { 361 pw::rpc::Channel::Create<1>(&hdlc_channel_output)}; 362 363 // Declare the pw_rpc server with the HDLC channel. 364 pw::rpc::Server server(channels); 365 366 foo::bar::TheService the_service; 367 pw::rpc::SomeOtherService some_other_service; 368 369 void RegisterServices() { 370 // Register the foo.bar.TheService example service and another service. 371 server.RegisterService(the_service, some_other_service); 372 } 373 374 int main() { 375 // Set up the server. 376 RegisterServices(); 377 378 // Declare a buffer for decoding incoming HDLC frames. 379 constexpr size_t kDecoderBufferSize = 380 pw::hdlc::Decoder::RequiredBufferSizeForFrameSize(kMaxTransmissionUnit); 381 382 std::array<std::byte, kDecoderBufferSize> input_buffer; 383 384 PW_LOG_INFO("Starting pw_rpc server"); 385 pw::hdlc::Decoder decoder(input_buffer); 386 387 while (true) { 388 std::byte byte; 389 pw::Status ret_val = pw::sys_io::ReadByte(&byte); 390 if (!ret_val.ok()) { 391 return ret_val; 392 } 393 if (auto result = decoder.Process(byte); result.ok()) { 394 pw::hdlc::Frame& frame = result.value(); 395 if (frame.address() == pw::hdlc::kDefaultRpcAddress) { 396 server.ProcessPacket(frame.data()); 397 } 398 } 399 } 400 } 401 402-------- 403Channels 404-------- 405``pw_rpc`` sends all of its packets over channels. These are logical, 406application-layer routes used to tell the RPC system where a packet should go. 407 408Channels over a client-server connection must all have a unique ID, which can be 409assigned statically at compile time or dynamically. 410 411.. code-block:: cpp 412 413 // Creating a channel with the static ID 3. 414 pw::rpc::Channel static_channel = pw::rpc::Channel::Create<3>(&output); 415 416 // Grouping channel IDs within an enum can lead to clearer code. 417 enum ChannelId { 418 kUartChannel = 1, 419 kSpiChannel = 2, 420 }; 421 422 // Creating a channel with a static ID defined within an enum. 423 pw::rpc::Channel another_static_channel = 424 pw::rpc::Channel::Create<ChannelId::kUartChannel>(&output); 425 426 // Creating a channel with a dynamic ID (note that no output is provided; it 427 // will be set when the channel is used. 428 pw::rpc::Channel dynamic_channel; 429 430Sometimes, the ID and output of a channel are not known at compile time as they 431depend on information stored on the physical device. To support this use case, a 432dynamically-assignable channel can be configured once at runtime with an ID and 433output. 434 435.. code-block:: cpp 436 437 // Create a dynamic channel without a compile-time ID or output. 438 pw::rpc::Channel dynamic_channel; 439 440 void Init() { 441 // Called during boot to pull the channel configuration from the system. 442 dynamic_channel.Configure(GetChannelId(), some_output); 443 } 444 445Adding and removing channels 446============================ 447New channels may be registered with the ``OpenChannel`` function. If dynamic 448allocation is enabled (:c:macro:`PW_RPC_DYNAMIC_ALLOCATION` is 1), any number of 449channels may be registered. If dynamic allocation is disabled, new channels may 450only be registered if there are availale channel slots in the span provided to 451the RPC endpoint at construction. 452 453A channel may be closed and unregistered with an endpoint by calling 454``ChannelClose`` on the endpoint with the corresponding channel ID. This 455will terminate any pending calls and call their ``on_error`` callback 456with the ``ABORTED`` status. 457 458.. code-block:: cpp 459 460 // When a channel is closed, any pending calls will receive 461 // on_error callbacks with ABORTED status. 462 client->CloseChannel(1); 463 464.. _module-pw_rpc-remap: 465 466Remapping channels 467================== 468Some pw_rpc deployments may find it helpful to remap channel IDs in RPC packets. 469This can remove the need for globally known channel IDs. Clients can use a 470generic channel ID. The server remaps the generic channel ID to an ID associated 471with the transport the client is using. 472 473.. cpp:namespace-push:: pw::rpc 474 475.. doxygengroup:: pw_rpc_channel_functions 476 :content-only: 477 478.. cpp:namespace-pop:: 479 480A future revision of the pw_rpc protocol will remove the need for global channel 481IDs without requiring remapping. 482 483Example deployment 484------------------ 485This section describes a hypothetical pw_rpc deployment that supports arbitrary 486pw_rpc clients with one pw_rpc server. Note that this assumes that the 487underlying transport provides some sort of addressing that the server-side can 488associate with a channel ID. 489 490- A pw_rpc server is running on one core. A variable number of pw_rpc clients 491 need to call RPCs on the server from a different core. 492- The client core opens a socket (or similar feature) to connect to the server 493 core. 494- The server core detects the inbound connection and allocates a new channel ID. 495 It creates a new channel by calling :cpp:func:`pw::rpc::Server::OpenChannel` 496 with the channel ID and a :cpp:class:`pw::rpc::ChannelOutput` associated with 497 the new connection. 498- The server maintains a mapping between channel IDs and pw_rpc client 499 connections. 500- On the client core, pw_rpc clients all use the same channel ID (e.g. ``1``). 501- As packets arrive from pw_rpc client connections, the server-side code calls 502 :cpp:func:`pw::rpc::ChangeEncodedChannelId` on the encoded packet to replace 503 the generic channel ID (``1``) with the server-side channel ID allocated when 504 the client connected. The encoded packet is then passed to 505 :cpp:func:`pw::rpc::Server::ProcessPacket`. 506- When the server sends pw_rpc packets, the :cpp:class:`pw::rpc::ChannelOutput` 507 calls :cpp:func:`pw::rpc::ChangeEncodedChannelId` to set the channel ID back 508 to the generic ``1``. 509 510-------- 511Services 512-------- 513A service is a logical grouping of RPCs defined within a .proto file. ``pw_rpc`` 514uses these .proto definitions to generate code for a base service, from which 515user-defined RPCs are implemented. 516 517``pw_rpc`` supports multiple protobuf libraries, and the generated code API 518depends on which is used. 519 520Services must be registered with a server in order to call their methods. 521Services may later be unregistered, which aborts calls for methods in that 522service and prevents future calls to them, until the service is re-registered. 523 524.. _module-pw_rpc-protobuf-library-apis: 525 526--------------------- 527Protobuf library APIs 528--------------------- 529 530.. toctree:: 531 :maxdepth: 1 532 533 pwpb/docs 534 nanopb/docs 535 536---------------------------- 537Testing a pw_rpc integration 538---------------------------- 539After setting up a ``pw_rpc`` server in your project, you can test that it is 540working as intended by registering the provided ``EchoService``, defined in 541``echo.proto``, which echoes back a message that it receives. 542 543.. literalinclude:: echo.proto 544 :language: protobuf 545 :lines: 14- 546 547For example, in C++ with pw_protobuf: 548 549.. code-block:: c++ 550 551 #include "pw_rpc/server.h" 552 553 // Include the apporpriate header for your protobuf library. 554 #include "pw_rpc/echo_service_pwpb.h" 555 556 constexpr pw::rpc::Channel kChannels[] = { /* ... */ }; 557 static pw::rpc::Server server(kChannels); 558 559 static pw::rpc::EchoService echo_service; 560 561 void Init() { 562 server.RegisterService(echo_service); 563 } 564 565Benchmarking and stress testing 566=============================== 567 568.. toctree:: 569 :maxdepth: 1 570 :hidden: 571 572 benchmark 573 574``pw_rpc`` provides an RPC service and Python module for stress testing and 575benchmarking a ``pw_rpc`` deployment. See :ref:`module-pw_rpc-benchmark`. 576 577------ 578Naming 579------ 580 581Reserved names 582============== 583``pw_rpc`` reserves a few service method names so they can be used for generated 584classes. The following names cannnot be used for service methods: 585 586- ``Client`` 587- ``Service`` 588- Any reserved words in the languages ``pw_rpc`` supports (e.g. ``class``). 589 590``pw_rpc`` does not reserve any service names, but the restriction of avoiding 591reserved words in supported languages applies. 592 593Service naming style 594==================== 595``pw_rpc`` service names should use capitalized camel case and should not use 596the term "Service". Appending "Service" to a service name is redundant, similar 597to appending "Class" or "Function" to a class or function name. The 598C++ implementation class may use "Service" in its name, however. 599 600For example, a service for accessing a file system should simply be named 601``service FileSystem``, rather than ``service FileSystemService``, in the 602``.proto`` file. 603 604.. code-block:: protobuf 605 606 // file.proto 607 package pw.file; 608 609 service FileSystem { 610 rpc List(ListRequest) returns (stream ListResponse); 611 } 612 613The C++ service implementation class may append "Service" to the name. 614 615.. code-block:: cpp 616 617 // file_system_service.h 618 #include "pw_file/file.raw_rpc.pb.h" 619 620 namespace pw::file { 621 622 class FileSystemService : public pw_rpc::raw::FileSystem::Service<FileSystemService> { 623 void List(ConstByteSpan request, RawServerWriter& writer); 624 }; 625 626 } // namespace pw::file 627 628For upstream Pigweed services, this naming style is a requirement. Note that 629some services created before this was established may use non-compliant 630names. For Pigweed users, this naming style is a suggestion. 631 632------------------------------ 633C++ payload sizing limitations 634------------------------------ 635The individual size of each sent RPC request or response is limited by 636``pw_rpc``'s ``PW_RPC_ENCODING_BUFFER_SIZE_BYTES`` configuration option when 637using Pigweed's C++ implementation. While multiple RPC messages can be enqueued 638(as permitted by the underlying transport), if a single individual sent message 639exceeds the limitations of the statically allocated encode buffer, the packet 640will fail to encode and be dropped. 641 642This applies to all C++ RPC service implementations (nanopb, raw, and pwpb), 643so it's important to ensure request and response message sizes do not exceed 644this limitation. 645 646As ``pw_rpc`` has some additional encoding overhead, a helper, 647``pw::rpc::MaxSafePayloadSize()`` is provided to expose the practical max RPC 648message payload size. 649 650.. code-block:: cpp 651 652 #include "pw_file/file.raw_rpc.pb.h" 653 #include "pw_rpc/channel.h" 654 655 namespace pw::file { 656 657 class FileSystemService : public pw_rpc::raw::FileSystem::Service<FileSystemService> { 658 public: 659 void List(ConstByteSpan request, RawServerWriter& writer); 660 661 private: 662 // Allocate a buffer for building proto responses. 663 static constexpr size_t kEncodeBufferSize = pw::rpc::MaxSafePayloadSize(); 664 std::array<std::byte, kEncodeBufferSize> encode_buffer_; 665 }; 666 667 } // namespace pw::file 668 669-------------------- 670Protocol description 671-------------------- 672Pigweed RPC servers and clients communicate using ``pw_rpc`` packets. These 673packets are used to send requests and responses, control streams, cancel ongoing 674RPCs, and report errors. 675 676Packet format 677============= 678Pigweed RPC packets consist of a type and a set of fields. The packets are 679encoded as protocol buffers. The full packet format is described in 680``pw_rpc/pw_rpc/internal/packet.proto``. 681 682.. literalinclude:: internal/packet.proto 683 :language: protobuf 684 :lines: 14- 685 686The packet type and RPC type determine which fields are present in a Pigweed RPC 687packet. Each packet type is only sent by either the client or the server. 688These tables describe the meaning of and fields included with each packet type. 689 690Client-to-server packets 691------------------------ 692+---------------------------+-------------------------------------+ 693| packet type | description | 694+===========================+=====================================+ 695| REQUEST | Invoke an RPC | 696| | | 697| | .. code-block:: text | 698| | | 699| | - channel_id | 700| | - service_id | 701| | - method_id | 702| | - payload | 703| | (unary & server streaming only) | 704| | - call_id (optional) | 705| | | 706+---------------------------+-------------------------------------+ 707| CLIENT_STREAM | Message in a client stream | 708| | | 709| | .. code-block:: text | 710| | | 711| | - channel_id | 712| | - service_id | 713| | - method_id | 714| | - payload | 715| | - call_id (if set in REQUEST) | 716| | | 717+---------------------------+-------------------------------------+ 718| CLIENT_REQUEST_COMPLETION | Client requested stream completion | 719| | | 720| | .. code-block:: text | 721| | | 722| | - channel_id | 723| | - service_id | 724| | - method_id | 725| | - call_id (if set in REQUEST) | 726| | | 727+---------------------------+-------------------------------------+ 728| CLIENT_ERROR | Abort an ongoing RPC | 729| | | 730| | .. code-block:: text | 731| | | 732| | - channel_id | 733| | - service_id | 734| | - method_id | 735| | - status | 736| | - call_id (if set in REQUEST) | 737| | | 738+---------------------------+-------------------------------------+ 739 740**Client errors** 741 742The client sends ``CLIENT_ERROR`` packets to a server when it receives a packet 743it did not request. If possible, the server should abort it. 744 745The status code indicates the type of error. The status code is logged, but all 746status codes result in the same action by the server: aborting the RPC. 747 748* ``CANCELLED`` -- The client requested that the RPC be cancelled. 749* ``ABORTED`` -- The RPC was aborted due its channel being closed. 750* ``NOT_FOUND`` -- Received a packet for a service method the client does not 751 recognize. 752* ``FAILED_PRECONDITION`` -- Received a packet for a service method that the 753 client did not invoke. 754* ``DATA_LOSS`` -- Received a corrupt packet for a pending service method. 755* ``INVALID_ARGUMENT`` -- The server sent a packet type to an RPC that does not 756 support it (a ``SERVER_STREAM`` was sent to an RPC with no server stream). 757* ``UNAVAILABLE`` -- Received a packet for an unknown channel. 758 759Server-to-client packets 760------------------------ 761+-------------------+-------------------------------------+ 762| packet type | description | 763+===================+=====================================+ 764| RESPONSE | The RPC is complete | 765| | | 766| | .. code-block:: text | 767| | | 768| | - channel_id | 769| | - service_id | 770| | - method_id | 771| | - status | 772| | - payload | 773| | (unary & client streaming only) | 774| | - call_id (if set in REQUEST) | 775| | | 776+-------------------+-------------------------------------+ 777| SERVER_STREAM | Message in a server stream | 778| | | 779| | .. code-block:: text | 780| | | 781| | - channel_id | 782| | - service_id | 783| | - method_id | 784| | - payload | 785| | - call_id (if set in REQUEST) | 786| | | 787+-------------------+-------------------------------------+ 788| SERVER_ERROR | Received unexpected packet | 789| | | 790| | .. code-block:: text | 791| | | 792| | - channel_id | 793| | - service_id (if relevant) | 794| | - method_id (if relevant) | 795| | - status | 796| | - call_id (if set in REQUEST) | 797| | | 798+-------------------+-------------------------------------+ 799 800All server packets contain the same ``call_id`` that was set in the initial 801request made by the client, if any. 802 803**Server errors** 804 805The server sends ``SERVER_ERROR`` packets when it receives a packet it cannot 806process. The client should abort any RPC for which it receives an error. The 807status field indicates the type of error. 808 809* ``NOT_FOUND`` -- The requested service or method does not exist. 810* ``FAILED_PRECONDITION`` -- A client stream or cancel packet was sent for an 811 RPC that is not pending. 812* ``INVALID_ARGUMENT`` -- The client sent a packet type to an RPC that does not 813 support it (a ``CLIENT_STREAM`` was sent to an RPC with no client stream). 814* ``RESOURCE_EXHAUSTED`` -- The request came on a new channel, but a channel 815 could not be allocated for it. 816* ``ABORTED`` -- The RPC was aborted due its channel being closed. 817* ``INTERNAL`` -- The server was unable to respond to an RPC due to an 818 unrecoverable internal error. 819* ``UNAVAILABLE`` -- Received a packet for an unknown channel. 820 821Invoking a service method 822========================= 823Calling an RPC requires a specific sequence of packets. This section describes 824the protocol for calling service methods of each type: unary, server streaming, 825client streaming, and bidirectional streaming. 826 827The basic flow for all RPC invocations is as follows: 828 829* Client sends a ``REQUEST`` packet. Includes a payload for unary & server 830 streaming RPCs. 831* For client and bidirectional streaming RPCs, the client may send any number of 832 ``CLIENT_STREAM`` packets with payloads. 833* For server and bidirectional streaming RPCs, the server may send any number of 834 ``SERVER_STREAM`` packets. 835* The server sends a ``RESPONSE`` packet. Includes a payload for unary & client 836 streaming RPCs. The RPC is complete. 837 838The client may cancel an ongoing RPC at any time by sending a ``CLIENT_ERROR`` 839packet with status ``CANCELLED``. The server may finish an ongoing RPC at any 840time by sending the ``RESPONSE`` packet. 841 842Unary RPC 843--------- 844In a unary RPC, the client sends a single request and the server sends a single 845response. 846 847.. mermaid:: 848 :alt: Unary RPC 849 :align: center 850 851 sequenceDiagram 852 participant C as Client 853 participant S as Server 854 C->>+S: request 855 Note left of C: PacketType.REQUEST<br>channel ID<br>service ID<br>method ID<br>payload 856 857 S->>-C: response 858 Note right of S: PacketType.RESPONSE<br>channel ID<br>service ID<br>method ID<br>payload<br>status 859 860The client may attempt to cancel a unary RPC by sending a ``CLIENT_ERROR`` 861packet with status ``CANCELLED``. The server sends no response to a cancelled 862RPC. If the server processes the unary RPC synchronously (the handling thread 863sends the response), it may not be possible to cancel the RPC. 864 865.. mermaid:: 866 :alt: Cancelled Unary RPC 867 :align: center 868 869 sequenceDiagram 870 participant C as Client 871 participant S as Server 872 C->>+S: request 873 Note left of C: PacketType.REQUEST<br>channel ID<br>service ID<br>method ID<br>payload 874 875 C->>S: cancel 876 Note left of C: PacketType.CLIENT_ERROR<br>channel ID<br>service ID<br>method ID<br>status=CANCELLED 877 878 879Server streaming RPC 880-------------------- 881In a server streaming RPC, the client sends a single request and the server 882sends any number of ``SERVER_STREAM`` packets followed by a ``RESPONSE`` packet. 883 884.. mermaid:: 885 :alt: Server Streaming RPC 886 :align: center 887 888 sequenceDiagram 889 participant C as Client 890 participant S as Server 891 C->>+S: request 892 Note left of C: PacketType.REQUEST<br>channel ID<br>service ID<br>method ID<br>payload 893 894 S-->>C: messages (zero or more) 895 Note right of S: PacketType.SERVER_STREAM<br>channel ID<br>service ID<br>method ID<br>payload 896 897 S->>-C: done 898 Note right of S: PacketType.RESPONSE<br>channel ID<br>service ID<br>method ID<br>status 899 900 901The client may terminate a server streaming RPC by sending a ``CLIENT_STREAM`` 902packet with status ``CANCELLED``. The server sends no response. 903 904.. mermaid:: 905 :alt: Cancelled Server Streaming RPC 906 :align: center 907 908 sequenceDiagram 909 participant C as Client 910 participant S as Server 911 C->>S: request 912 Note left of C: PacketType.REQUEST<br>channel ID<br>service ID<br>method ID<br>payload 913 914 S-->>C: messages (zero or more) 915 Note right of S: PacketType.SERVER_STREAM<br>channel ID<br>service ID<br>method ID<br>payload 916 917 C->>S: cancel 918 Note left of C: PacketType.CLIENT_ERROR<br>channel ID<br>service ID<br>method ID<br>status=CANCELLED 919 920 921Client streaming RPC 922-------------------- 923In a client streaming RPC, the client starts the RPC by sending a ``REQUEST`` 924packet with no payload. It then sends any number of messages in 925``CLIENT_STREAM`` packets, followed by a ``CLIENT_REQUEST_COMPLETION``. The server sends 926a single ``RESPONSE`` to finish the RPC. 927 928.. mermaid:: 929 :alt: Client Streaming RPC 930 :align: center 931 932 sequenceDiagram 933 participant C as Client 934 participant S as Server 935 C->>S: start 936 Note left of C: PacketType.REQUEST<br>channel ID<br>service ID<br>method ID<br>payload 937 938 C-->>S: messages (zero or more) 939 Note left of C: PacketType.CLIENT_STREAM<br>channel ID<br>service ID<br>method ID<br>payload 940 941 C->>S: done 942 Note left of C: PacketType.CLIENT_REQUEST_COMPLETION<br>channel ID<br>service ID<br>method ID 943 944 S->>C: response 945 Note right of S: PacketType.RESPONSE<br>channel ID<br>service ID<br>method ID<br>payload<br>status 946 947The server may finish the RPC at any time by sending its ``RESPONSE`` packet, 948even if it has not yet received the ``CLIENT_REQUEST_COMPLETION`` packet. The client may 949terminate the RPC at any time by sending a ``CLIENT_ERROR`` packet with status 950``CANCELLED``. 951 952.. mermaid:: 953 :alt: Cancelled Client Streaming RPC 954 :align: center 955 956 sequenceDiagram 957 participant C as Client 958 participant S as Server 959 C->>S: start 960 Note left of C: PacketType.REQUEST<br>channel ID<br>service ID<br>method ID 961 962 C-->>S: messages (zero or more) 963 Note left of C: PacketType.CLIENT_STREAM<br>channel ID<br>service ID<br>method ID<br>payload 964 965 C->>S: cancel 966 Note right of S: PacketType.CLIENT_ERROR<br>channel ID<br>service ID<br>method ID<br>status=CANCELLED 967 968Bidirectional streaming RPC 969--------------------------- 970In a bidirectional streaming RPC, the client sends any number of requests and 971the server sends any number of responses. The client invokes the RPC by sending 972a ``REQUEST`` with no payload. It sends a ``CLIENT_REQUEST_COMPLETION`` packet when it 973has finished sending requests. The server sends a ``RESPONSE`` packet to finish 974the RPC. 975 976.. mermaid:: 977 :alt: Bidirectional Streaming RPC 978 :align: center 979 980 sequenceDiagram 981 participant C as Client 982 participant S as Server 983 C->>S: start 984 Note left of C: PacketType.REQUEST<br>channel ID<br>service ID<br>method ID<br>payload 985 986 C-->>S: messages (zero or more) 987 Note left of C: PacketType.CLIENT_STREAM<br>channel ID<br>service ID<br>method ID<br>payload 988 989 C-->S: (messages in any order) 990 991 S-->>C: messages (zero or more) 992 Note right of S: PacketType.SERVER_STREAM<br>channel ID<br>service ID<br>method ID<br>payload 993 994 C->>S: done 995 Note left of C: PacketType.CLIENT_REQUEST_COMPLETION<br>channel ID<br>service ID<br>method ID 996 997 S->>C: done 998 Note right of S: PacketType.RESPONSE<br>channel ID<br>service ID<br>method ID<br>status 999 1000 1001The server may finish the RPC at any time by sending the ``RESPONSE`` packet, 1002even if it has not received the ``CLIENT_REQUEST_COMPLETION`` packet. The client may 1003terminate the RPC at any time by sending a ``CLIENT_ERROR`` packet with status 1004``CANCELLED``. 1005 1006.. mermaid:: 1007 :alt: Client Streaming RPC 1008 :align: center 1009 1010 sequenceDiagram 1011 participant C as Client 1012 participant S as Server 1013 C->>S: start 1014 Note left of C: PacketType.REQUEST<br>channel ID<br>service ID<br>method ID<br>payload 1015 1016 C-->>S: messages (zero or more) 1017 Note left of C: PacketType.CLIENT_STREAM<br>channel ID<br>service ID<br>method ID<br>payload 1018 1019 C->>S: done 1020 Note left of C: PacketType.CLIENT_REQUEST_COMPLETION<br>channel ID<br>service ID<br>method ID 1021 1022 S->>C: response 1023 Note right of S: PacketType.RESPONSE<br>channel ID<br>service ID<br>method ID<br>payload<br>status 1024 1025------- 1026C++ API 1027------- 1028 1029RPC server 1030========== 1031Declare an instance of ``rpc::Server`` and register services with it. 1032 1033.. admonition:: TODO 1034 1035 Document the public interface 1036 1037Size report 1038----------- 1039The following size report showcases the memory usage of the core RPC server. It 1040is configured with a single channel using a basic transport interface that 1041directly reads from and writes to ``pw_sys_io``. The transport has a 128-byte 1042packet buffer, which comprises the plurality of the example's RAM usage. This is 1043not a suitable transport for an actual product; a real implementation would have 1044additional overhead proportional to the complexity of the transport. 1045 1046.. include:: server_size 1047 1048RPC server implementation 1049------------------------- 1050 1051The Method class 1052^^^^^^^^^^^^^^^^ 1053The RPC Server depends on the ``pw::rpc::internal::Method`` class. ``Method`` 1054serves as the bridge between the ``pw_rpc`` server library and the user-defined 1055RPC functions. Each supported protobuf implementation extends ``Method`` to 1056implement its request and response proto handling. The ``pw_rpc`` server 1057calls into the ``Method`` implementation through the base class's ``Invoke`` 1058function. 1059 1060``Method`` implementations store metadata about each method, including a 1061function pointer to the user-defined method implementation. They also provide 1062``static constexpr`` functions for creating each type of method. ``Method`` 1063implementations must satisfy the ``MethodImplTester`` test class in 1064``pw_rpc/internal/method_impl_tester.h``. 1065 1066See ``pw_rpc/internal/method.h`` for more details about ``Method``. 1067 1068Packet flow 1069^^^^^^^^^^^ 1070 1071Requests 1072........ 1073 1074.. mermaid:: 1075 :alt: Request Packet Flow 1076 1077 flowchart LR 1078 packets[Packets] 1079 1080 subgraph pw_rpc [pw_rpc Library] 1081 direction TB 1082 internalMethod[[internal::Method]] 1083 Server --> Service --> internalMethod 1084 end 1085 1086 packets --> Server 1087 1088 generatedServices{{generated services}} 1089 userDefinedRPCs(user-defined RPCs) 1090 1091 generatedServices --> userDefinedRPCs 1092 internalMethod --> generatedServices 1093 1094Responses 1095......... 1096 1097.. mermaid:: 1098 :alt: Request Packet Flow 1099 1100 flowchart LR 1101 generatedServices{{generated services}} 1102 userDefinedRPCs(user-defined RPCs) 1103 1104 subgraph pw_rpc [pw_rpc Library] 1105 direction TB 1106 internalMethod[[internal::Method]] 1107 internalMethod --> Server --> Channel 1108 end 1109 1110 packets[Packets] 1111 Channel --> packets 1112 1113 userDefinedRPCs --> generatedServices 1114 generatedServices --> internalMethod 1115 1116RPC client 1117========== 1118The RPC client is used to send requests to a server and manages the contexts of 1119ongoing RPCs. 1120 1121Setting up a client 1122------------------- 1123The ``pw::rpc::Client`` class is instantiated with a list of channels that it 1124uses to communicate. These channels can be shared with a server, but multiple 1125clients cannot use the same channels. 1126 1127To send incoming RPC packets from the transport layer to be processed by a 1128client, the client's ``ProcessPacket`` function is called with the packet data. 1129 1130.. code-block:: c++ 1131 1132 #include "pw_rpc/client.h" 1133 1134 namespace { 1135 1136 pw::rpc::Channel my_channels[] = { 1137 pw::rpc::Channel::Create<1>(&my_channel_output)}; 1138 pw::rpc::Client my_client(my_channels); 1139 1140 } // namespace 1141 1142 // Called when the transport layer receives an RPC packet. 1143 void ProcessRpcPacket(ConstByteSpan packet) { 1144 my_client.ProcessPacket(packet); 1145 } 1146 1147Note that client processing such as callbacks will be invoked within 1148the body of ``ProcessPacket``. 1149 1150If certain packets need to be filtered out, or if certain client processing 1151needs to be invoked from a specific thread or context, the ``PacketMeta`` class 1152can be used to determine which service or channel a packet is targeting. After 1153filtering, ``ProcessPacket`` can be called from the appropriate environment. 1154 1155.. _module-pw_rpc-making-calls: 1156 1157Making RPC calls 1158---------------- 1159RPC calls are not made directly through the client, but using one of its 1160registered channels instead. A service client class is generated from a .proto 1161file for each selected protobuf library, which is then used to send RPC requests 1162through a given channel. The API for this depends on the protobuf library; 1163please refer to the 1164:ref:`appropriate documentation<module-pw_rpc-protobuf-library-apis>`. Multiple 1165service client implementations can exist simulatenously and share the same 1166``Client`` class. 1167 1168When a call is made, a call object is returned to the caller. This object tracks 1169the ongoing RPC call, and can be used to manage it. An RPC call is only active 1170as long as its call object is alive. 1171 1172.. tip:: 1173 1174 Use ``std::move`` when passing around call objects to keep RPCs alive. 1175 1176Example 1177^^^^^^^ 1178.. code-block:: c++ 1179 1180 #include "pw_rpc/echo_service_nanopb.h" 1181 1182 namespace { 1183 // Generated clients are namespaced with their proto library. 1184 using EchoClient = pw_rpc::nanopb::EchoService::Client; 1185 1186 // RPC channel ID on which to make client calls. RPC calls cannot be made on 1187 // channel 0 (Channel::kUnassignedChannelId). 1188 constexpr uint32_t kDefaultChannelId = 1; 1189 1190 pw::rpc::NanopbUnaryReceiver<pw_rpc_EchoMessage> echo_call; 1191 1192 // Callback invoked when a response is received. This is called synchronously 1193 // from Client::ProcessPacket. 1194 void EchoResponse(const pw_rpc_EchoMessage& response, 1195 pw::Status status) { 1196 if (status.ok()) { 1197 PW_LOG_INFO("Received echo response: %s", response.msg); 1198 } else { 1199 PW_LOG_ERROR("Echo failed with status %d", 1200 static_cast<int>(status.code())); 1201 } 1202 } 1203 1204 } // namespace 1205 1206 void CallEcho(const char* message) { 1207 // Create a client to call the EchoService. 1208 EchoClient echo_client(my_rpc_client, kDefaultChannelId); 1209 1210 pw_rpc_EchoMessage request{}; 1211 pw::string::Copy(message, request.msg); 1212 1213 // By assigning the returned call to the global echo_call, the RPC 1214 // call is kept alive until it completes. When a response is received, it 1215 // will be logged by the handler function and the call will complete. 1216 echo_call = echo_client.Echo(request, EchoResponse); 1217 if (!echo_call.active()) { 1218 // The RPC call was not sent. This could occur due to, for example, an 1219 // invalid channel ID. Handle if necessary. 1220 } 1221 } 1222 1223Call objects 1224============ 1225An RPC call is represented by a call object. Server and client calls use the 1226same base call class in C++, but the public API is different depending on the 1227type of call (see `RPC call lifecycle`_) and whether it is being used by the 1228server or client. 1229 1230The public call types are as follows: 1231 1232.. list-table:: 1233 :header-rows: 1 1234 1235 * - RPC Type 1236 - Server call 1237 - Client call 1238 * - Unary 1239 - ``(Raw|Nanopb|Pwpb)UnaryResponder`` 1240 - ``(Raw|Nanopb|Pwpb)UnaryReceiver`` 1241 * - Server streaming 1242 - ``(Raw|Nanopb|Pwpb)ServerWriter`` 1243 - ``(Raw|Nanopb|Pwpb)ClientReader`` 1244 * - Client streaming 1245 - ``(Raw|Nanopb|Pwpb)ServerReader`` 1246 - ``(Raw|Nanopb|Pwpb)ClientWriter`` 1247 * - Bidirectional streaming 1248 - ``(Raw|Nanopb|Pwpb)ServerReaderWriter`` 1249 - ``(Raw|Nanopb|Pwpb)ClientReaderWriter`` 1250 1251Client call API 1252--------------- 1253Client call objects provide a few common methods. 1254 1255.. cpp:class:: pw::rpc::ClientCallType 1256 1257 The ``ClientCallType`` will be one of the following types: 1258 1259 - ``(Raw|Nanopb|Pwpb)UnaryReceiver`` for unary 1260 - ``(Raw|Nanopb|Pwpb)ClientReader`` for server streaming 1261 - ``(Raw|Nanopb|Pwpb)ClientWriter`` for client streaming 1262 - ``(Raw|Nanopb|Pwpb)ClientReaderWriter`` for bidirectional streaming 1263 1264 .. cpp:function:: bool active() const 1265 1266 Returns true if the call is active. 1267 1268 .. cpp:function:: uint32_t channel_id() const 1269 1270 Returns the channel ID of this call, which is 0 if the call is inactive. 1271 1272 .. cpp:function:: uint32_t id() const 1273 1274 Returns the call ID, a unique identifier for this call. 1275 1276 .. cpp:function:: void Write(RequestType) 1277 1278 Only available on client and bidirectional streaming calls. Sends a stream 1279 request. Returns: 1280 1281 - ``OK`` - the request was successfully sent 1282 - ``FAILED_PRECONDITION`` - the writer is closed 1283 - ``INTERNAL`` - pw_rpc was unable to encode message; does not apply to 1284 raw calls 1285 - other errors - the :cpp:class:`ChannelOutput` failed to send the packet; 1286 the error codes are determined by the :cpp:class:`ChannelOutput` 1287 implementation 1288 1289 .. cpp:function:: pw::Status RequestCompletion() 1290 1291 Notifies the server that client has requested for call completion. On 1292 client and bidirectional streaming calls no further client stream messages 1293 will be sent. 1294 1295 .. cpp:function:: pw::Status Cancel() 1296 1297 Cancels this RPC. Closes the call and sends a ``CANCELLED`` error to the 1298 server. Return statuses are the same as :cpp:func:`Write`. 1299 1300 .. cpp:function:: void Abandon() 1301 1302 Closes this RPC locally. Sends a ``CLIENT_REQUEST_COMPLETION``, but no cancellation 1303 packet. Future packets for this RPC are dropped, and the client sends a 1304 ``FAILED_PRECONDITION`` error in response because the call is not active. 1305 1306 .. cpp:function:: void CloseAndWaitForCallbacks() 1307 1308 Abandons this RPC and additionally blocks on completion of any running callbacks. 1309 1310 .. cpp:function:: void set_on_completed(pw::Function<void(ResponseTypeIfUnaryOnly, pw::Status)>) 1311 1312 Sets the callback that is called when the RPC completes normally. The 1313 signature depends on whether the call has a unary or stream response. 1314 1315 .. cpp:function:: void set_on_error(pw::Function<void(pw::Status)>) 1316 1317 Sets the callback that is called when the RPC is terminated due to an error. 1318 1319 .. cpp:function:: void set_on_next(pw::Function<void(ResponseType)>) 1320 1321 Only available on server and bidirectional streaming calls. Sets the callback 1322 that is called for each stream response. 1323 1324Callbacks 1325--------- 1326The C++ call objects allow users to set callbacks that are invoked when RPC 1327`events`_ occur. 1328 1329.. list-table:: 1330 :header-rows: 1 1331 1332 * - Name 1333 - Stream signature 1334 - Non-stream signature 1335 - Server 1336 - Client 1337 * - ``on_error`` 1338 - ``void(pw::Status)`` 1339 - ``void(pw::Status)`` 1340 - ✅ 1341 - ✅ 1342 * - ``on_next`` 1343 - n/a 1344 - ``void(const PayloadType&)`` 1345 - ✅ 1346 - ✅ 1347 * - ``on_completed`` 1348 - ``void(pw::Status)`` 1349 - ``void(const PayloadType&, pw::Status)`` 1350 - 1351 - ✅ 1352 * - ``on_client_requested_completion`` 1353 - ``void()`` 1354 - n/a 1355 - ✅ (:c:macro:`optional <PW_RPC_COMPLETION_REQUEST_CALLBACK>`) 1356 - 1357 1358Limitations and restrictions 1359^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1360RPC callbacks are free to perform most actions, including invoking new RPCs or 1361cancelling pending calls. However, the C++ implementation imposes some 1362limitations and restrictions that must be observed. 1363 1364Destructors & moves wait for callbacks to complete 1365................................................... 1366* Callbacks must not destroy their call object. Attempting to do so will result 1367 in deadlock. 1368* Other threads may destroy a call while its callback is running, but that 1369 thread will block until all callbacks complete. 1370* Callbacks must not move their call object if it the call is still active. They 1371 may move their call object after it has terminated. Callbacks may move a 1372 different call into their call object, since moving closes the destination 1373 call. 1374* Other threads may move a call object while it has a callback running, but they 1375 will block until the callback completes if the call is still active. 1376 1377.. warning:: 1378 1379 Deadlocks or crashes occur if a callback: 1380 1381 - attempts to destroy its call object 1382 - attempts to move its call object while the call is still active 1383 - never returns 1384 1385 If ``pw_rpc`` a callback violates these restrictions, a crash may occur, 1386 depending on the value of :c:macro:`PW_RPC_CALLBACK_TIMEOUT_TICKS`. These 1387 crashes have a message like the following: 1388 1389 .. code-block:: text 1390 1391 A callback for RPC 1:cc0f6de0/31e616ce has not finished after 10000 ticks. 1392 This may indicate that an RPC callback attempted to destroy or move its own 1393 call object, which is not permitted. Fix this condition or change the value of 1394 PW_RPC_CALLBACK_TIMEOUT_TICKS to avoid this crash. 1395 1396 See https://pigweed.dev/pw_rpc#destructors-moves-wait-for-callbacks-to-complete 1397 for details. 1398 1399Only one thread at a time may execute ``on_next`` 1400................................................. 1401Only one thread may execute the ``on_next`` callback for a specific service 1402method at a time. If a second thread calls ``ProcessPacket()`` with a stream 1403packet before the ``on_next`` callback for the previous packet completes, the 1404second packet will be dropped. The RPC endpoint logs a warning when this occurs. 1405 1406Example warning for a dropped stream packet: 1407 1408.. code-block:: text 1409 1410 WRN Received stream packet for 1:cc0f6de0/31e616ce before the callback for 1411 a previous packet completed! This packet will be dropped. This can be 1412 avoided by handling packets for a particular RPC on only one thread. 1413 1414RPC calls introspection 1415======================= 1416``pw_rpc`` provides ``pw_rpc/method_info.h`` header that allows to obtain 1417information about the generated RPC method in compile time. 1418 1419For now it provides only two types: ``MethodRequestType<RpcMethod>`` and 1420``MethodResponseType<RpcMethod>``. They are aliases to the types that are used 1421as a request and response respectively for the given RpcMethod. 1422 1423Example 1424------- 1425We have an RPC service ``SpecialService`` with ``MyMethod`` method: 1426 1427.. code-block:: protobuf 1428 1429 package some.package; 1430 service SpecialService { 1431 rpc MyMethod(MyMethodRequest) returns (MyMethodResponse) {} 1432 } 1433 1434We also have a templated Storage type alias: 1435 1436.. code-block:: c++ 1437 1438 template <auto kMethod> 1439 using Storage = 1440 std::pair<MethodRequestType<kMethod>, MethodResponseType<kMethod>>; 1441 1442``Storage<some::package::pw_rpc::pwpb::SpecialService::MyMethod>`` will 1443instantiate as: 1444 1445.. code-block:: c++ 1446 1447 std::pair<some::package::MyMethodRequest::Message, 1448 some::package::MyMethodResponse::Message>; 1449 1450.. note:: 1451 1452 Only nanopb and pw_protobuf have real types as 1453 ``MethodRequestType<RpcMethod>``/``MethodResponseType<RpcMethod>``. Raw has 1454 them both set as ``void``. In reality, they are ``pw::ConstByteSpan``. Any 1455 helper/trait that wants to use this types for raw methods should do a custom 1456 implementation that copies the bytes under the span instead of copying just 1457 the span. 1458 1459.. _module-pw_rpc-client-sync-call-wrappers: 1460 1461Client synchronous call wrappers 1462================================ 1463.. doxygenfile:: pw_rpc/synchronous_call.h 1464 :sections: detaileddescription 1465 1466Example 1467------- 1468.. code-block:: c++ 1469 1470 #include "pw_rpc/synchronous_call.h" 1471 1472 void InvokeUnaryRpc() { 1473 pw::rpc::Client client; 1474 pw::rpc::Channel channel; 1475 1476 RoomInfoRequest request; 1477 SynchronousCallResult<RoomInfoResponse> result = 1478 SynchronousCall<Chat::GetRoomInformation>(client, channel.id(), request); 1479 1480 if (result.is_rpc_error()) { 1481 ShutdownClient(client); 1482 } else if (result.is_server_error()) { 1483 HandleServerError(result.status()); 1484 } else if (result.is_timeout()) { 1485 // SynchronousCall will block indefinitely, so we should never get here. 1486 PW_UNREACHABLE(); 1487 } 1488 HandleRoomInformation(std::move(result).response()); 1489 } 1490 1491 void AnotherExample() { 1492 pw_rpc::nanopb::Chat::Client chat_client(client, channel); 1493 constexpr auto kTimeout = pw::chrono::SystemClock::for_at_least(500ms); 1494 1495 RoomInfoRequest request; 1496 auto result = SynchronousCallFor<Chat::GetRoomInformation>( 1497 chat_client, request, kTimeout); 1498 1499 if (result.is_timeout()) { 1500 RetryRoomRequest(); 1501 } else { 1502 ... 1503 } 1504 } 1505 1506The ``SynchronousCallResult<Response>`` is also compatible with the 1507:c:macro:`PW_TRY` family of macros, but users should be aware that their use 1508will lose information about the type of error. This should only be used if the 1509caller will handle all error scenarios the same. 1510 1511.. code-block:: c++ 1512 1513 pw::Status SyncRpc() { 1514 const RoomInfoRequest request; 1515 PW_TRY_ASSIGN(const RoomInfoResponse& response, 1516 SynchronousCall<Chat::GetRoomInformation>(client, request)); 1517 HandleRoomInformation(response); 1518 return pw::OkStatus(); 1519 } 1520 1521ClientServer 1522============ 1523Sometimes, a device needs to both process RPCs as a server, as well as making 1524calls to another device as a client. To do this, both a client and server must 1525be set up, and incoming packets must be sent to both of them. 1526 1527Pigweed simplifies this setup by providing a ``ClientServer`` class which wraps 1528an RPC client and server with the same set of channels. 1529 1530.. code-block:: cpp 1531 1532 pw::rpc::Channel channels[] = { 1533 pw::rpc::Channel::Create<1>(&channel_output)}; 1534 1535 // Creates both a client and a server. 1536 pw::rpc::ClientServer client_server(channels); 1537 1538 void ProcessRpcData(pw::ConstByteSpan packet) { 1539 // Calls into both the client and the server, sending the packet to the 1540 // appropriate one. 1541 client_server.ProcessPacket(packet); 1542 } 1543 1544Testing 1545======= 1546``pw_rpc`` provides utilities for unit testing RPC services and client calls. 1547 1548Client unit testing in C++ 1549-------------------------- 1550``pw_rpc`` supports invoking RPCs, simulating server responses, and checking 1551what packets are sent by an RPC client in tests. Raw, Nanopb and Pwpb interfaces 1552are supported. Code that uses the raw API may be tested with the raw test 1553helpers, and vice versa. The Nanopb and Pwpb APIs also provides a test helper 1554with a real client-server pair that supports testing of asynchronous messaging. 1555 1556To test synchronous code that invokes RPCs, declare a ``RawClientTestContext``, 1557``PwpbClientTestContext``, or ``NanopbClientTestContext``. These test context 1558objects provide a preconfigured RPC client, channel, server fake, and buffer for 1559encoding packets. 1560 1561These test classes are defined in ``pw_rpc/raw/client_testing.h``, 1562``pw_rpc/pwpb/client_testing.h``, or ``pw_rpc/nanopb/client_testing.h``. 1563 1564Use the context's ``client()`` and ``channel()`` to invoke RPCs. Use the 1565context's ``server()`` to simulate responses. To verify that the client sent the 1566expected data, use the context's ``output()``, which is a ``FakeChannelOutput``. 1567 1568For example, the following tests a class that invokes an RPC. It checks that 1569the expected data was sent and then simulates a response from the server. 1570 1571.. code-block:: cpp 1572 1573 #include "pw_rpc/raw/client_testing.h" 1574 1575 class ClientUnderTest { 1576 public: 1577 // To support injecting an RPC client for testing, classes that make RPC 1578 // calls should take an RPC client and channel ID or an RPC service client 1579 // (e.g. pw_rpc::raw::MyService::Client). 1580 ClientUnderTest(pw::rpc::Client& client, uint32_t channel_id); 1581 1582 void DoSomethingThatInvokesAnRpc(); 1583 1584 bool SetToTrueWhenRpcCompletes(); 1585 }; 1586 1587 TEST(TestAThing, InvokesRpcAndHandlesResponse) { 1588 RawClientTestContext context; 1589 ClientUnderTest thing(context.client(), context.channel().id()); 1590 1591 // Execute the code that invokes the MyService.TheMethod RPC. 1592 things.DoSomethingThatInvokesAnRpc(); 1593 1594 // Find and verify the payloads sent for the MyService.TheMethod RPC. 1595 auto msgs = context.output().payloads<pw_rpc::raw::MyService::TheMethod>(); 1596 ASSERT_EQ(msgs.size(), 1u); 1597 1598 VerifyThatTheExpectedMessageWasSent(msgs.back()); 1599 1600 // Send the response packet from the server and verify that the class reacts 1601 // accordingly. 1602 EXPECT_FALSE(thing.SetToTrueWhenRpcCompletes()); 1603 1604 context_.server().SendResponse<pw_rpc::raw::MyService::TheMethod>( 1605 final_message, OkStatus()); 1606 1607 EXPECT_TRUE(thing.SetToTrueWhenRpcCompletes()); 1608 } 1609 1610To test client code that uses asynchronous responses, encapsulates multiple 1611rpc calls to one or more services, or uses a custom service implemenation, 1612declare a ``NanopbClientServerTestContextThreaded`` or 1613``PwpbClientServerTestContextThreaded``. These test object are defined in 1614``pw_rpc/nanopb/client_server_testing_threaded.h`` and 1615``pw_rpc/pwpb/client_server_testing_threaded.h``. 1616 1617Use the context's ``server()`` to register a ``Service`` implementation, and 1618``client()`` and ``channel()`` to invoke RPCs. Create a ``Thread`` using the 1619context as a ``ThreadCore`` to have it asycronously forward request/responses or 1620call ``ForwardNewPackets`` to synchronously process all messages. To verify that 1621the client/server sent the expected data, use the context's 1622``request(uint32_t index)`` and ``response(uint32_t index)`` to retrieve the 1623ordered messages. 1624 1625For example, the following tests a class that invokes an RPC and blocks till a 1626response is received. It verifies that expected data was both sent and received. 1627 1628.. code-block:: cpp 1629 1630 #include "my_library_protos/my_service.rpc.pb.h" 1631 #include "pw_rpc/nanopb/client_server_testing_threaded.h" 1632 #include "pw_thread_stl/options.h" 1633 1634 class ClientUnderTest { 1635 public: 1636 // To support injecting an RPC client for testing, classes that make RPC 1637 // calls should take an RPC client and channel ID or an RPC service client 1638 // (e.g. pw_rpc::raw::MyService::Client). 1639 ClientUnderTest(pw::rpc::Client& client, uint32_t channel_id); 1640 1641 Status BlockOnResponse(uint32_t value); 1642 }; 1643 1644 1645 class TestService final : public MyService<TestService> { 1646 public: 1647 Status TheMethod(const pw_rpc_test_TheMethod& request, 1648 pw_rpc_test_TheMethod& response) { 1649 response.value = request.integer + 1; 1650 return pw::OkStatus(); 1651 } 1652 }; 1653 1654 TEST(TestServiceTest, ReceivesUnaryRpcReponse) { 1655 NanopbClientServerTestContextThreaded<> ctx(pw::thread::stl::Options{}); 1656 TestService service; 1657 ctx.server().RegisterService(service); 1658 ClientUnderTest client(ctx.client(), ctx.channel().id()); 1659 1660 // Execute the code that invokes the MyService.TheMethod RPC. 1661 constexpr uint32_t value = 1; 1662 const auto result = client.BlockOnResponse(value); 1663 const auto request = ctx.request<MyService::TheMethod>(0); 1664 const auto response = ctx.resonse<MyService::TheMethod>(0); 1665 1666 // Verify content of messages 1667 EXPECT_EQ(result, pw::OkStatus()); 1668 EXPECT_EQ(request.value, value); 1669 EXPECT_EQ(response.value, value + 1); 1670 } 1671 1672Use the context's 1673``response(uint32_t index, Response<kMethod>& response)`` to decode messages 1674into a provided response object. You would use this version if decoder callbacks 1675are needed to fully decode a message. For instance if it uses ``repeated`` 1676fields. 1677 1678.. code-block:: cpp 1679 1680 TestResponse::Message response{}; 1681 response.repeated_field.SetDecoder( 1682 [&values](TestResponse::StreamDecoder& decoder) { 1683 return decoder.ReadRepeatedField(values); 1684 }); 1685 ctx.response<test::GeneratedService::TestAnotherUnaryRpc>(0, response); 1686 1687Synchronous versions of these test contexts also exist that may be used on 1688non-threaded systems ``NanopbClientServerTestContext`` and 1689``PwpbClientServerTestContext``. While these do not allow for asynchronous 1690messaging they support the use of service implemenations and use a similar 1691syntax. When these are used ``.ForwardNewPackets()`` should be called after each 1692rpc call to trigger sending of queued messages. 1693 1694For example, the following tests a class that invokes an RPC that is responded 1695to with a test service implemenation. 1696 1697.. code-block:: cpp 1698 1699 #include "my_library_protos/my_service.rpc.pb.h" 1700 #include "pw_rpc/nanopb/client_server_testing.h" 1701 1702 class ClientUnderTest { 1703 public: 1704 ClientUnderTest(pw::rpc::Client& client, uint32_t channel_id); 1705 1706 Status SendRpcCall(uint32_t value); 1707 }; 1708 1709 1710 class TestService final : public MyService<TestService> { 1711 public: 1712 Status TheMethod(const pw_rpc_test_TheMethod& request, 1713 pw_rpc_test_TheMethod& response) { 1714 response.value = request.integer + 1; 1715 return pw::OkStatus(); 1716 } 1717 }; 1718 1719 TEST(TestServiceTest, ReceivesUnaryRpcResponse) { 1720 NanopbClientServerTestContext<> ctx(); 1721 TestService service; 1722 ctx.server().RegisterService(service); 1723 ClientUnderTest client(ctx.client(), ctx.channel().id()); 1724 1725 // Execute the code that invokes the MyService.TheMethod RPC. 1726 constexpr uint32_t value = 1; 1727 const auto result = client.SendRpcCall(value); 1728 // Needed after ever RPC call to trigger forward of packets 1729 ctx.ForwardNewPackets(); 1730 const auto request = ctx.request<MyService::TheMethod>(0); 1731 const auto response = ctx.response<MyService::TheMethod>(0); 1732 1733 // Verify content of messages 1734 EXPECT_EQ(result, pw::OkStatus()); 1735 EXPECT_EQ(request.value, value); 1736 EXPECT_EQ(response.value, value + 1); 1737 } 1738 1739Custom packet processing for ClientServerTestContext 1740^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1741Optional constructor arguments for nanopb/pwpb ``*ClientServerTestContext`` and 1742``*ClientServerTestContextThreaded`` allow allow customized packet processing. 1743By default the only thing is done is ``ProcessPacket()`` call on the 1744``ClientServer`` instance. 1745 1746For cases when additional instrumentation or offloading to separate thread is 1747needed, separate client and server processors can be passed to context 1748constructors. A packet processor is a function that returns ``pw::Status`` and 1749accepts two arguments: ``pw::rpc::ClientServer&`` and ``pw::ConstByteSpan``. 1750Default packet processing is equivalent to the next processor: 1751 1752.. code-block:: cpp 1753 1754 [](ClientServer& client_server, pw::ConstByteSpan packet) -> pw::Status { 1755 return client_server.ProcessPacket(packet); 1756 }; 1757 1758The Server processor will be applied to all packets sent to the server (i.e. 1759requests) and client processor will be applied to all packets sent to the client 1760(i.e. responses). 1761 1762.. note:: 1763 1764 The packet processor MUST call ``ClientServer::ProcessPacket()`` method. 1765 Otherwise the packet won't be processed. 1766 1767.. note:: 1768 1769 If the packet processor offloads processing to the separate thread, it MUST 1770 copy the ``packet``. After the packet processor returns, the underlying array 1771 can go out of scope or be reused for other purposes. 1772 1773SendResponseIfCalled() helper 1774----------------------------- 1775``SendResponseIfCalled()`` function waits on ``*ClientTestContext*`` output to 1776have a call for the specified method and then responses to it. It supports 1777timeout for the waiting part (default timeout is 100ms). 1778 1779.. code-block:: c++ 1780 1781 #include "pw_rpc/test_helpers.h" 1782 1783 pw::rpc::PwpbClientTestContext client_context; 1784 other::pw_rpc::pwpb::OtherService::Client other_service_client( 1785 client_context.client(), client_context.channel().id()); 1786 1787 PW_PWPB_TEST_METHOD_CONTEXT(MyService, GetData) 1788 context(other_service_client); 1789 context.call({}); 1790 1791 ASSERT_OK(pw::rpc::test::SendResponseIfCalled< 1792 other::pw_rpc::pwpb::OtherService::GetPart>( 1793 client_context, {.value = 42})); 1794 1795 // At this point MyService::GetData handler received the GetPartResponse. 1796 1797Integration testing with ``pw_rpc`` 1798----------------------------------- 1799``pw_rpc`` provides utilities to simplify writing integration tests for systems 1800that communicate with ``pw_rpc``. The integration test utitilies set up a socket 1801to use for IPC between an RPC server and client process. 1802 1803The server binary uses the system RPC server facade defined 1804``pw_rpc_system_server/rpc_server.h``. The client binary uses the functions 1805defined in ``pw_rpc/integration_testing.h``: 1806 1807.. cpp:var:: constexpr uint32_t kChannelId 1808 1809 The RPC channel for integration test RPCs. 1810 1811.. cpp:function:: pw::rpc::Client& pw::rpc::integration_test::Client() 1812 1813 Returns the global RPC client for integration test use. 1814 1815.. cpp:function:: pw::Status pw::rpc::integration_test::InitializeClient(int argc, char* argv[], const char* usage_args = "PORT") 1816 1817 Initializes logging and the global RPC client for integration testing. Starts 1818 a background thread that processes incoming. 1819 1820Module Configuration Options 1821============================ 1822The following configurations can be adjusted via compile-time configuration of 1823this module, see the 1824:ref:`module documentation <module-structure-compile-time-configuration>` for 1825more details. 1826 1827.. doxygenfile:: pw_rpc/public/pw_rpc/internal/config.h 1828 :sections: define 1829 1830Sharing server and client code 1831============================== 1832Streaming RPCs support writing multiple requests or responses. To facilitate 1833sharing code between servers and clients, ``pw_rpc`` provides the 1834``pw::rpc::Writer`` interface. On the client side, a client or bidirectional 1835streaming RPC call object (``ClientWriter`` or ``ClientReaderWriter``) can be 1836used as a ``pw::rpc::Writer&``. On the server side, a server or bidirectional 1837streaming RPC call object (``ServerWriter`` or ``ServerReaderWriter``) can be 1838used as a ``pw::rpc::Writer&``. Call ``as_writer()`` to get a ``Writer&`` of the 1839client or server call object. 1840 1841Zephyr 1842====== 1843To enable ``pw_rpc.*`` for Zephyr add ``CONFIG_PIGWEED_RPC=y`` to the project's 1844configuration. This will enable the Kconfig menu for the following: 1845 1846* ``pw_rpc.server`` which can be enabled via ``CONFIG_PIGWEED_RPC_SERVER=y``. 1847* ``pw_rpc.client`` which can be enabled via ``CONFIG_PIGWEED_RPC_CLIENT=y``. 1848* ``pw_rpc.client_server`` which can be enabled via 1849 ``CONFIG_PIGWEED_RPC_CLIENT_SERVER=y``. 1850* ``pw_rpc.common` which can be enabled via ``CONFIG_PIGWEED_RPC_COMMON=y``. 1851 1852Encoding and sending packets 1853============================ 1854``pw_rpc`` has to manage interactions among multiple RPC clients, servers, 1855client calls, and server calls. To safely synchronize these interactions with 1856minimal overhead, ``pw_rpc`` uses a single, global mutex (when 1857``PW_RPC_USE_GLOBAL_MUTEX`` is enabled). 1858 1859Because ``pw_rpc`` uses a global mutex, it also uses a global buffer to encode 1860outgoing packets. The size of the buffer is set with 1861``PW_RPC_ENCODING_BUFFER_SIZE_BYTES``, which defaults to 512 B. If dynamic 1862allocation is enabled, this size does not affect how large RPC messages can be, 1863but it is still used for sizing buffers in test utilities. 1864 1865Users of ``pw_rpc`` must implement the :cpp:class:`pw::rpc::ChannelOutput` 1866interface. 1867 1868.. _module-pw_rpc-ChannelOutput: 1869.. cpp:class:: pw::rpc::ChannelOutput 1870 1871 ``pw_rpc`` endpoints use :cpp:class:`ChannelOutput` instances to send 1872 packets. Systems that integrate pw_rpc must use one or more 1873 :cpp:class:`ChannelOutput` instances. 1874 1875 .. cpp:member:: static constexpr size_t kUnlimited = std::numeric_limits<size_t>::max() 1876 1877 Value returned from :cpp:func:`MaximumTransmissionUnit` to indicate an 1878 unlimited MTU. 1879 1880 .. cpp:function:: virtual size_t MaximumTransmissionUnit() 1881 1882 Returns the size of the largest packet the :cpp:class:`ChannelOutput` can 1883 send. :cpp:class:`ChannelOutput` implementations should only override this 1884 function if they impose a limit on the MTU. The default implementation 1885 returns :cpp:member:`kUnlimited`, which indicates that there is no MTU 1886 limit. 1887 1888 .. cpp:function:: virtual pw::Status Send(span<std::byte> packet) 1889 1890 Sends an encoded RPC packet. Returns OK if further packets may be sent, 1891 even if the current packet could not be sent. Returns any other status if 1892 the Channel is no longer able to send packets. 1893 1894 The RPC system's internal lock is held while this function is 1895 called. Avoid long-running operations, since these will delay any other 1896 users of the RPC system. 1897 1898 .. danger:: 1899 1900 No ``pw_rpc`` APIs may be accessed in this function! Implementations 1901 MUST NOT access any RPC endpoints (:cpp:class:`pw::rpc::Client`, 1902 :cpp:class:`pw::rpc::Server`) or call objects 1903 (:cpp:class:`pw::rpc::ServerReaderWriter` 1904 :cpp:class:`pw::rpc::ClientReaderWriter`, etc.) inside the 1905 :cpp:func:`Send` function or any descendent calls. Doing so will result 1906 in deadlock! RPC APIs may be used by other threads, just not within 1907 :cpp:func:`Send`. 1908 1909 The buffer provided in ``packet`` must NOT be accessed outside of this 1910 function. It must be sent immediately or copied elsewhere before the 1911 function returns. 1912 1913Evolution 1914========= 1915Concurrent requests were not initially supported in pw_rpc (added in `C++ 1916<https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/109077>`_, `Python 1917<https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/139610>`_, and 1918`TypeScript 1919<https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/160792>`_). As a 1920result, some user-written service implementations may not expect or correctly 1921support concurrent requests. 1922