1.. _module-pw_rpc: 2 3------ 4pw_rpc 5------ 6The ``pw_rpc`` module provides a system for defining and invoking remote 7procedure calls (RPCs) on a device. 8 9This document discusses the ``pw_rpc`` protocol and its C++ implementation. 10``pw_rpc`` implementations for other languages are described in their own 11documents: 12 13.. toctree:: 14 :maxdepth: 1 15 16 py/docs 17 ts/docs 18 19.. admonition:: Try it out! 20 21 For a quick intro to ``pw_rpc``, see the 22 :ref:`module-pw_hdlc-rpc-example` in the :ref:`module-pw_hdlc` module. 23 24.. warning:: 25 26 This documentation is under construction. Many sections are outdated or 27 incomplete. The content needs to be reorgnanized. 28 29Implementations 30=============== 31Pigweed provides several client and server implementations of ``pw_rpc``. 32 33.. list-table:: 34 :header-rows: 1 35 36 * - Language 37 - Server 38 - Client 39 * - C++ (raw) 40 - ✅ 41 - ✅ 42 * - C++ (Nanopb) 43 - ✅ 44 - ✅ 45 * - C++ (pw_protobuf) 46 - planned 47 - planned 48 * - Java 49 - 50 - in development 51 * - Python 52 - 53 - ✅ 54 * - TypeScript 55 - 56 - in development 57 58RPC semantics 59============= 60The semantics of ``pw_rpc`` are similar to `gRPC 61<https://grpc.io/docs/what-is-grpc/core-concepts/>`_. 62 63RPC call lifecycle 64------------------ 65In ``pw_rpc``, an RPC begins when the client sends a request packet. The server 66receives the request, looks up the relevant service method, then calls into the 67RPC function. The RPC is considered active until the server sends a response 68packet with the RPC's status. The client may terminate an ongoing RPC by 69cancelling it. 70 71``pw_rpc`` supports only one RPC invocation per service/method/channel. If a 72client calls an ongoing RPC on the same channel, the server cancels the ongoing 73call and reinvokes the RPC with the new request. This applies to unary and 74streaming RPCs, though the server may not have an opportunity to cancel a 75synchronously handled unary RPC before it completes. The same RPC may be invoked 76multiple times simultaneously if the invocations are on different channels. 77 78Status codes 79------------ 80``pw_rpc`` call objects (``ClientReaderWriter``, ``ServerReaderWriter``, etc.) 81use certain status codes to indicate what occurred. These codes are returned 82from functions like ``Write()`` or ``Finish()``. 83 84* ``OK`` -- The operation succeeded. 85* ``UNAVAILABLE`` -- The channel is not currently registered with the server or 86 client. 87* ``UNKNOWN`` -- Sending a packet failed due to an unrecoverable 88 :cpp:func:`pw::rpc::ChannelOutput::Send` error. 89 90Unrequested responses 91--------------------- 92``pw_rpc`` supports sending responses to RPCs that have not yet been invoked by 93a client. This is useful in testing and in situations like an RPC that triggers 94reboot. After the reboot, the device opens the writer object and sends its 95response to the client. 96 97The C++ API for opening a server reader/writer takes the generated RPC function 98as a template parameter. The server to use, channel ID, and service instance are 99passed as arguments. The API is the same for all RPC types, except the 100appropriate reader/writer class must be used. 101 102.. code-block:: c++ 103 104 // Open a ServerWriter for a server streaming RPC. 105 auto writer = RawServerWriter::Open<pw_rpc::raw::ServiceName::MethodName>( 106 server, channel_id, service_instance); 107 108 // Send some responses, even though the client has not yet called this RPC. 109 CHECK_OK(writer.Write(encoded_response_1)); 110 CHECK_OK(writer.Write(encoded_response_2)); 111 112 // Finish the RPC. 113 CHECK_OK(writer.Finish(OkStatus())); 114 115Creating an RPC 116=============== 117 1181. RPC service declaration 119-------------------------- 120Pigweed RPCs are declared in a protocol buffer service definition. 121 122* `Protocol Buffer service documentation 123 <https://developers.google.com/protocol-buffers/docs/proto3#services>`_ 124* `gRPC service definition documentation 125 <https://grpc.io/docs/what-is-grpc/core-concepts/#service-definition>`_ 126 127.. code-block:: protobuf 128 129 syntax = "proto3"; 130 131 package foo.bar; 132 133 message Request {} 134 135 message Response { 136 int32 number = 1; 137 } 138 139 service TheService { 140 rpc MethodOne(Request) returns (Response) {} 141 rpc MethodTwo(Request) returns (stream Response) {} 142 } 143 144This protocol buffer is declared in a ``BUILD.gn`` file as follows: 145 146.. code-block:: python 147 148 import("//build_overrides/pigweed.gni") 149 import("$dir_pw_protobuf_compiler/proto.gni") 150 151 pw_proto_library("the_service_proto") { 152 sources = [ "foo_bar/the_service.proto" ] 153 } 154 155.. admonition:: proto2 or proto3 syntax? 156 157 Always use proto3 syntax rather than proto2 for new protocol buffers. Proto2 158 protobufs can be compiled for ``pw_rpc``, but they are not as well supported 159 as proto3. Specifically, ``pw_rpc`` lacks support for non-zero default values 160 in proto2. When using Nanopb with ``pw_rpc``, proto2 response protobufs with 161 non-zero field defaults should be manually initialized to the default struct. 162 163 In the past, proto3 was sometimes avoided because it lacked support for field 164 presence detection. Fortunately, this has been fixed: proto3 now supports 165 ``optional`` fields, which are equivalent to proto2 ``optional`` fields. 166 167 If you need to distinguish between a default-valued field and a missing field, 168 mark the field as ``optional``. The presence of the field can be detected 169 with a ``HasField(name)`` or ``has_<field>`` member, depending on the library. 170 171 Optional fields have some overhead --- default-valued fields are included in 172 the encoded proto, and, if using Nanopb, the proto structs have a 173 ``has_<field>`` flag for each optional field. Use plain fields if field 174 presence detection is not needed. 175 176 .. code-block:: protobuf 177 178 syntax = "proto3"; 179 180 message MyMessage { 181 // Leaving this field unset is equivalent to setting it to 0. 182 int32 number = 1; 183 184 // Setting this field to 0 is different from leaving it unset. 185 optional int32 other_number = 2; 186 } 187 1882. RPC code generation 189---------------------- 190``pw_rpc`` generates a C++ header file for each ``.proto`` file. This header is 191generated in the build output directory. Its exact location varies by build 192system and toolchain, but the C++ include path always matches the sources 193declaration in the ``pw_proto_library``. The ``.proto`` extension is replaced 194with an extension corresponding to the protobuf library in use. 195 196================== =============== =============== ============= 197Protobuf libraries Build subtarget Protobuf header pw_rpc header 198================== =============== =============== ============= 199Raw only .raw_rpc (none) .raw_rpc.pb.h 200Nanopb or raw .nanopb_rpc .pb.h .rpc.pb.h 201pw_protobuf or raw .pwpb_rpc .pwpb.h .rpc.pwpb.h 202================== =============== =============== ============= 203 204For example, the generated RPC header for ``"foo_bar/the_service.proto"`` is 205``"foo_bar/the_service.rpc.pb.h"`` for Nanopb or 206``"foo_bar/the_service.raw_rpc.pb.h"`` for raw RPCs. 207 208The generated header defines a base class for each RPC service declared in the 209``.proto`` file. A service named ``TheService`` in package ``foo.bar`` would 210generate the following base class for Nanopb: 211 212.. cpp:class:: template <typename Implementation> foo::bar::pw_rpc::nanopb::TheService::Service 213 2143. RPC service definition 215------------------------- 216The serivce class is implemented by inheriting from the generated RPC service 217base class and defining a method for each RPC. The methods must match the name 218and function signature for one of the supported protobuf implementations. 219Services may mix and match protobuf implementations within one service. 220 221.. tip:: 222 223 The generated code includes RPC service implementation stubs. You can 224 reference or copy and paste these to get started with implementing a service. 225 These stub classes are generated at the bottom of the pw_rpc proto header. 226 227 To use the stubs, do the following: 228 229 #. Locate the generated RPC header in the build directory. For example: 230 231 .. code-block:: sh 232 233 find out/ -name <proto_name>.rpc.pb.h 234 235 #. Scroll to the bottom of the generated RPC header. 236 #. Copy the stub class declaration to a header file. 237 #. Copy the member function definitions to a source file. 238 #. Rename the class or change the namespace, if desired. 239 #. List these files in a build target with a dependency on the 240 ``pw_proto_library``. 241 242A Nanopb implementation of this service would be as follows: 243 244.. code-block:: cpp 245 246 #include "foo_bar/the_service.rpc.pb.h" 247 248 namespace foo::bar { 249 250 class TheService : public pw_rpc::nanopb::TheService::Service<TheService> { 251 public: 252 pw::Status MethodOne(const foo_bar_Request& request, 253 foo_bar_Response& response) { 254 // implementation 255 return pw::OkStatus(); 256 } 257 258 void MethodTwo(const foo_bar_Request& request, 259 ServerWriter<foo_bar_Response>& response) { 260 // implementation 261 response.Write(foo_bar_Response{.number = 123}); 262 } 263 }; 264 265 } // namespace foo::bar 266 267The Nanopb implementation would be declared in a ``BUILD.gn``: 268 269.. code-block:: python 270 271 import("//build_overrides/pigweed.gni") 272 273 import("$dir_pw_build/target_types.gni") 274 275 pw_source_set("the_service") { 276 public_configs = [ ":public" ] 277 public = [ "public/foo_bar/service.h" ] 278 public_deps = [ ":the_service_proto.nanopb_rpc" ] 279 } 280 281.. attention:: 282 283 pw_rpc's generated classes will support using ``pw_protobuf`` or raw buffers 284 (no protobuf library) in the future. 285 2864. Register the service with a server 287------------------------------------- 288This example code sets up an RPC server with an :ref:`HDLC<module-pw_hdlc>` 289channel output and the example service. 290 291.. code-block:: cpp 292 293 // Set up the output channel for the pw_rpc server to use. This configures the 294 // pw_rpc server to use HDLC over UART; projects not using UART and HDLC must 295 // adapt this as necessary. 296 pw::stream::SysIoWriter writer; 297 pw::rpc::RpcChannelOutput<kMaxTransmissionUnit> hdlc_channel_output( 298 writer, pw::hdlc::kDefaultRpcAddress, "HDLC output"); 299 300 pw::rpc::Channel channels[] = { 301 pw::rpc::Channel::Create<1>(&hdlc_channel_output)}; 302 303 // Declare the pw_rpc server with the HDLC channel. 304 pw::rpc::Server server(channels); 305 306 foo::bar::TheService the_service; 307 pw::rpc::SomeOtherService some_other_service; 308 309 void RegisterServices() { 310 // Register the foo.bar.TheService example service and another service. 311 server.RegisterService(the_service, some_other_service); 312 } 313 314 int main() { 315 // Set up the server. 316 RegisterServices(); 317 318 // Declare a buffer for decoding incoming HDLC frames. 319 std::array<std::byte, kMaxTransmissionUnit> input_buffer; 320 321 PW_LOG_INFO("Starting pw_rpc server"); 322 pw::hdlc::ReadAndProcessPackets( 323 server, hdlc_channel_output, input_buffer); 324 } 325 326Channels 327======== 328``pw_rpc`` sends all of its packets over channels. These are logical, 329application-layer routes used to tell the RPC system where a packet should go. 330 331Channels over a client-server connection must all have a unique ID, which can be 332assigned statically at compile time or dynamically. 333 334.. code-block:: cpp 335 336 // Creating a channel with the static ID 3. 337 pw::rpc::Channel static_channel = pw::rpc::Channel::Create<3>(&output); 338 339 // Grouping channel IDs within an enum can lead to clearer code. 340 enum ChannelId { 341 kUartChannel = 1, 342 kSpiChannel = 2, 343 }; 344 345 // Creating a channel with a static ID defined within an enum. 346 pw::rpc::Channel another_static_channel = 347 pw::rpc::Channel::Create<ChannelId::kUartChannel>(&output); 348 349 // Creating a channel with a dynamic ID (note that no output is provided; it 350 // will be set when the channel is used. 351 pw::rpc::Channel dynamic_channel; 352 353Sometimes, the ID and output of a channel are not known at compile time as they 354depend on information stored on the physical device. To support this use case, a 355dynamically-assignable channel can be configured once at runtime with an ID and 356output. 357 358.. code-block:: cpp 359 360 // Create a dynamic channel without a compile-time ID or output. 361 pw::rpc::Channel dynamic_channel; 362 363 void Init() { 364 // Called during boot to pull the channel configuration from the system. 365 dynamic_channel.Configure(GetChannelId(), some_output); 366 } 367 368Adding and removing channels 369---------------------------- 370New channels may be registered with the ``OpenChannel`` function. If dynamic 371allocation is enabled (:c:macro:`PW_RPC_DYNAMIC_ALLOCATION` is 1), any number of 372channels may be registered. If dynamic allocation is disabled, new channels may 373only be registered if there are availale channel slots in the span provided to 374the RPC endpoint at construction. 375 376A channel may be closed and unregistered with an endpoint by calling 377``ChannelClose`` on the endpoint with the corresponding channel ID. This 378will terminate any pending calls and call their ``on_error`` callback 379with the ``ABORTED`` status. 380 381.. code-block:: cpp 382 383 // When a channel is closed, any pending calls will receive 384 // on_error callbacks with ABORTED status. 385 client->CloseChannel(1); 386 387Services 388======== 389A service is a logical grouping of RPCs defined within a .proto file. ``pw_rpc`` 390uses these .proto definitions to generate code for a base service, from which 391user-defined RPCs are implemented. 392 393``pw_rpc`` supports multiple protobuf libraries, and the generated code API 394depends on which is used. 395 396.. _module-pw_rpc-protobuf-library-apis: 397 398Protobuf library APIs 399===================== 400 401.. toctree:: 402 :maxdepth: 1 403 404 nanopb/docs 405 406Testing a pw_rpc integration 407============================ 408After setting up a ``pw_rpc`` server in your project, you can test that it is 409working as intended by registering the provided ``EchoService``, defined in 410``echo.proto``, which echoes back a message that it receives. 411 412.. literalinclude:: echo.proto 413 :language: protobuf 414 :lines: 14- 415 416For example, in C++ with nanopb: 417 418.. code:: c++ 419 420 #include "pw_rpc/server.h" 421 422 // Include the apporpriate header for your protobuf library. 423 #include "pw_rpc/echo_service_nanopb.h" 424 425 constexpr pw::rpc::Channel kChannels[] = { /* ... */ }; 426 static pw::rpc::Server server(kChannels); 427 428 static pw::rpc::EchoService echo_service; 429 430 void Init() { 431 server.RegisterService(echo_service); 432 } 433 434Benchmarking and stress testing 435------------------------------- 436 437.. toctree:: 438 :maxdepth: 1 439 :hidden: 440 441 benchmark 442 443``pw_rpc`` provides an RPC service and Python module for stress testing and 444benchmarking a ``pw_rpc`` deployment. See :ref:`module-pw_rpc-benchmark`. 445 446Naming 447====== 448 449Reserved names 450-------------- 451``pw_rpc`` reserves a few service method names so they can be used for generated 452classes. The following names cannnot be used for service methods: 453 454- ``Client`` 455- ``Service`` 456- Any reserved words in the languages ``pw_rpc`` supports (e.g. ``class``). 457 458``pw_rpc`` does not reserve any service names, but the restriction of avoiding 459reserved words in supported languages applies. 460 461Service naming style 462-------------------- 463``pw_rpc`` service names should use capitalized camel case and should not use 464the term "Service". Appending "Service" to a service name is redundant, similar 465to appending "Class" or "Function" to a class or function name. The 466C++ implementation class may use "Service" in its name, however. 467 468For example, a service for accessing a file system should simply be named 469``service FileSystem``, rather than ``service FileSystemService``, in the 470``.proto`` file. 471 472.. code-block:: protobuf 473 474 // file.proto 475 package pw.file; 476 477 service FileSystem { 478 rpc List(ListRequest) returns (stream ListResponse); 479 } 480 481The C++ service implementation class may append "Service" to the name. 482 483.. code-block:: cpp 484 485 // file_system_service.h 486 #include "pw_file/file.raw_rpc.pb.h" 487 488 namespace pw::file { 489 490 class FileSystemService : public pw_rpc::raw::FileSystem::Service<FileSystemService> { 491 void List(ConstByteSpan request, RawServerWriter& writer); 492 }; 493 494 } 495 496For upstream Pigweed services, this naming style is a requirement. Note that 497some services created before this was established may use non-compliant 498names. For Pigweed users, this naming style is a suggestion. 499 500Protocol description 501==================== 502Pigweed RPC servers and clients communicate using ``pw_rpc`` packets. These 503packets are used to send requests and responses, control streams, cancel ongoing 504RPCs, and report errors. 505 506Packet format 507------------- 508Pigweed RPC packets consist of a type and a set of fields. The packets are 509encoded as protocol buffers. The full packet format is described in 510``pw_rpc/pw_rpc/internal/packet.proto``. 511 512.. literalinclude:: internal/packet.proto 513 :language: protobuf 514 :lines: 14- 515 516The packet type and RPC type determine which fields are present in a Pigweed RPC 517packet. Each packet type is only sent by either the client or the server. 518These tables describe the meaning of and fields included with each packet type. 519 520Client-to-server packets 521^^^^^^^^^^^^^^^^^^^^^^^^ 522+-------------------+-------------------------------------+ 523| packet type | description | 524+===================+=====================================+ 525| REQUEST | Invoke an RPC | 526| | | 527| | .. code-block:: text | 528| | | 529| | - channel_id | 530| | - service_id | 531| | - method_id | 532| | - payload | 533| | (unary & server streaming only) | 534| | - call_id (optional) | 535| | | 536+-------------------+-------------------------------------+ 537| CLIENT_STREAM | Message in a client stream | 538| | | 539| | .. code-block:: text | 540| | | 541| | - channel_id | 542| | - service_id | 543| | - method_id | 544| | - payload | 545| | - call_id (if set in REQUEST) | 546| | | 547+-------------------+-------------------------------------+ 548| CLIENT_STREAM_END | Client stream is complete | 549| | | 550| | .. code-block:: text | 551| | | 552| | - channel_id | 553| | - service_id | 554| | - method_id | 555| | - call_id (if set in REQUEST) | 556| | | 557+-------------------+-------------------------------------+ 558| CLIENT_ERROR | Abort an ongoing RPC | 559| | | 560| | .. code-block:: text | 561| | | 562| | - channel_id | 563| | - service_id | 564| | - method_id | 565| | - status | 566| | - call_id (if set in REQUEST) | 567| | | 568+-------------------+-------------------------------------+ 569 570**Client errors** 571 572The client sends ``CLIENT_ERROR`` packets to a server when it receives a packet 573it did not request. If possible, the server should abort it. 574 575The status code indicates the type of error. The status code is logged, but all 576status codes result in the same action by the server: aborting the RPC. 577 578* ``CANCELLED`` -- The client requested that the RPC be cancelled. 579* ``ABORTED`` -- The RPC was aborted due its channel being closed. 580* ``NOT_FOUND`` -- Received a packet for a service method the client does not 581 recognize. 582* ``FAILED_PRECONDITION`` -- Received a packet for a service method that the 583 client did not invoke. 584* ``DATA_LOSS`` -- Received a corrupt packet for a pending service method. 585* ``INVALID_ARGUMENT`` -- The server sent a packet type to an RPC that does not 586 support it (a ``SERVER_STREAM`` was sent to an RPC with no server stream). 587* ``UNAVAILABLE`` -- Received a packet for an unknown channel. 588 589Server-to-client packets 590^^^^^^^^^^^^^^^^^^^^^^^^ 591+-------------------+-------------------------------------+ 592| packet type | description | 593+===================+=====================================+ 594| RESPONSE | The RPC is complete | 595| | | 596| | .. code-block:: text | 597| | | 598| | - channel_id | 599| | - service_id | 600| | - method_id | 601| | - status | 602| | - payload | 603| | (unary & client streaming only) | 604| | - call_id (if set in REQUEST) | 605| | | 606+-------------------+-------------------------------------+ 607| SERVER_STREAM | Message in a server stream | 608| | | 609| | .. code-block:: text | 610| | | 611| | - channel_id | 612| | - service_id | 613| | - method_id | 614| | - payload | 615| | - call_id (if set in REQUEST) | 616| | | 617+-------------------+-------------------------------------+ 618| SERVER_ERROR | Received unexpected packet | 619| | | 620| | .. code-block:: text | 621| | | 622| | - channel_id | 623| | - service_id (if relevant) | 624| | - method_id (if relevant) | 625| | - status | 626| | - call_id (if set in REQUEST) | 627| | | 628+-------------------+-------------------------------------+ 629 630All server packets contain the same ``call_id`` that was set in the initial 631request made by the client, if any. 632 633**Server errors** 634 635The server sends ``SERVER_ERROR`` packets when it receives a packet it cannot 636process. The client should abort any RPC for which it receives an error. The 637status field indicates the type of error. 638 639* ``NOT_FOUND`` -- The requested service or method does not exist. 640* ``FAILED_PRECONDITION`` -- A client stream or cancel packet was sent for an 641 RPC that is not pending. 642* ``INVALID_ARGUMENT`` -- The client sent a packet type to an RPC that does not 643 support it (a ``CLIENT_STREAM`` was sent to an RPC with no client stream). 644* ``RESOURCE_EXHAUSTED`` -- The request came on a new channel, but a channel 645 could not be allocated for it. 646* ``ABORTED`` -- The RPC was aborted due its channel being closed. 647* ``INTERNAL`` -- The server was unable to respond to an RPC due to an 648 unrecoverable internal error. 649* ``UNAVAILABLE`` -- Received a packet for an unknown channel. 650 651Inovking a service method 652------------------------- 653Calling an RPC requires a specific sequence of packets. This section describes 654the protocol for calling service methods of each type: unary, server streaming, 655client streaming, and bidirectional streaming. 656 657The basic flow for all RPC invocations is as follows: 658 659 * Client sends a ``REQUEST`` packet. Includes a payload for unary & server 660 streaming RPCs. 661 * For client and bidirectional streaming RPCs, the client may send any number 662 of ``CLIENT_STREAM`` packets with payloads. 663 * For server and bidirectional streaming RPCs, the server may send any number 664 of ``SERVER_STREAM`` packets. 665 * The server sends a ``RESPONSE`` packet. Includes a payload for unary & 666 client streaming RPCs. The RPC is complete. 667 668The client may cancel an ongoing RPC at any time by sending a ``CLIENT_ERROR`` 669packet with status ``CANCELLED``. The server may finish an ongoing RPC at any 670time by sending the ``RESPONSE`` packet. 671 672Unary RPC 673^^^^^^^^^ 674In a unary RPC, the client sends a single request and the server sends a single 675response. 676 677.. image:: unary_rpc.svg 678 679The client may attempt to cancel a unary RPC by sending a ``CLIENT_ERROR`` 680packet with status ``CANCELLED``. The server sends no response to a cancelled 681RPC. If the server processes the unary RPC synchronously (the handling thread 682sends the response), it may not be possible to cancel the RPC. 683 684.. image:: unary_rpc_cancelled.svg 685 686Server streaming RPC 687^^^^^^^^^^^^^^^^^^^^ 688In a server streaming RPC, the client sends a single request and the server 689sends any number of ``SERVER_STREAM`` packets followed by a ``RESPONSE`` packet. 690 691.. image:: server_streaming_rpc.svg 692 693The client may terminate a server streaming RPC by sending a ``CLIENT_STREAM`` 694packet with status ``CANCELLED``. The server sends no response. 695 696.. image:: server_streaming_rpc_cancelled.svg 697 698Client streaming RPC 699^^^^^^^^^^^^^^^^^^^^ 700In a client streaming RPC, the client starts the RPC by sending a ``REQUEST`` 701packet with no payload. It then sends any number of messages in 702``CLIENT_STREAM`` packets, followed by a ``CLIENT_STREAM_END``. The server sends 703a single ``RESPONSE`` to finish the RPC. 704 705.. image:: client_streaming_rpc.svg 706 707The server may finish the RPC at any time by sending its ``RESPONSE`` packet, 708even if it has not yet received the ``CLIENT_STREAM_END`` packet. The client may 709terminate the RPC at any time by sending a ``CLIENT_ERROR`` packet with status 710``CANCELLED``. 711 712.. image:: client_streaming_rpc_cancelled.svg 713 714Bidirectional streaming RPC 715^^^^^^^^^^^^^^^^^^^^^^^^^^^ 716In a bidirectional streaming RPC, the client sends any number of requests and 717the server sends any number of responses. The client invokes the RPC by sending 718a ``REQUEST`` with no payload. It sends a ``CLIENT_STREAM_END`` packet when it 719has finished sending requests. The server sends a ``RESPONSE`` packet to finish 720the RPC. 721 722.. image:: bidirectional_streaming_rpc.svg 723 724The server may finish the RPC at any time by sending the ``RESPONSE`` packet, 725even if it has not received the ``CLIENT_STREAM_END`` packet. The client may 726terminate the RPC at any time by sending a ``CLIENT_ERROR`` packet with status 727``CANCELLED``. 728 729.. image:: bidirectional_streaming_rpc_cancelled.svg 730 731RPC server 732========== 733Declare an instance of ``rpc::Server`` and register services with it. 734 735.. admonition:: TODO 736 737 Document the public interface 738 739Size report 740----------- 741The following size report showcases the memory usage of the core RPC server. It 742is configured with a single channel using a basic transport interface that 743directly reads from and writes to ``pw_sys_io``. The transport has a 128-byte 744packet buffer, which comprises the plurality of the example's RAM usage. This is 745not a suitable transport for an actual product; a real implementation would have 746additional overhead proportional to the complexity of the transport. 747 748.. include:: server_size 749 750RPC server implementation 751------------------------- 752 753The Method class 754^^^^^^^^^^^^^^^^ 755The RPC Server depends on the ``pw::rpc::internal::Method`` class. ``Method`` 756serves as the bridge between the ``pw_rpc`` server library and the user-defined 757RPC functions. Each supported protobuf implementation extends ``Method`` to 758implement its request and response proto handling. The ``pw_rpc`` server 759calls into the ``Method`` implementation through the base class's ``Invoke`` 760function. 761 762``Method`` implementations store metadata about each method, including a 763function pointer to the user-defined method implementation. They also provide 764``static constexpr`` functions for creating each type of method. ``Method`` 765implementations must satisfy the ``MethodImplTester`` test class in 766``pw_rpc/internal/method_impl_tester.h``. 767 768See ``pw_rpc/internal/method.h`` for more details about ``Method``. 769 770Packet flow 771^^^^^^^^^^^ 772 773Requests 774~~~~~~~~ 775 776.. image:: request_packets.svg 777 778Responses 779~~~~~~~~~ 780 781.. image:: response_packets.svg 782 783RPC client 784========== 785The RPC client is used to send requests to a server and manages the contexts of 786ongoing RPCs. 787 788Setting up a client 789------------------- 790The ``pw::rpc::Client`` class is instantiated with a list of channels that it 791uses to communicate. These channels can be shared with a server, but multiple 792clients cannot use the same channels. 793 794To send incoming RPC packets from the transport layer to be processed by a 795client, the client's ``ProcessPacket`` function is called with the packet data. 796 797.. code:: c++ 798 799 #include "pw_rpc/client.h" 800 801 namespace { 802 803 pw::rpc::Channel my_channels[] = { 804 pw::rpc::Channel::Create<1>(&my_channel_output)}; 805 pw::rpc::Client my_client(my_channels); 806 807 } // namespace 808 809 // Called when the transport layer receives an RPC packet. 810 void ProcessRpcPacket(ConstByteSpan packet) { 811 my_client.ProcessPacket(packet); 812 } 813 814.. _module-pw_rpc-making-calls: 815 816Making RPC calls 817---------------- 818RPC calls are not made directly through the client, but using one of its 819registered channels instead. A service client class is generated from a .proto 820file for each selected protobuf library, which is then used to send RPC requests 821through a given channel. The API for this depends on the protobuf library; 822please refer to the 823:ref:`appropriate documentation<module-pw_rpc-protobuf-library-apis>`. Multiple 824service client implementations can exist simulatenously and share the same 825``Client`` class. 826 827When a call is made, a ``pw::rpc::ClientCall`` object is returned to the caller. 828This object tracks the ongoing RPC call, and can be used to manage it. An RPC 829call is only active as long as its ``ClientCall`` object is alive. 830 831.. tip:: 832 Use ``std::move`` when passing around ``ClientCall`` objects to keep RPCs 833 alive. 834 835Example 836^^^^^^^ 837.. code-block:: c++ 838 839 #include "pw_rpc/echo_service_nanopb.h" 840 841 namespace { 842 // Generated clients are namespaced with their proto library. 843 using EchoClient = pw_rpc::nanopb::EchoService::Client; 844 845 // RPC channel ID on which to make client calls. 846 constexpr uint32_t kDefaultChannelId = 1; 847 848 EchoClient::EchoCall echo_call; 849 850 // Callback invoked when a response is received. This is called synchronously 851 // from Client::ProcessPacket. 852 void EchoResponse(const pw_rpc_EchoMessage& response, 853 pw::Status status) { 854 if (status.ok()) { 855 PW_LOG_INFO("Received echo response: %s", response.msg); 856 } else { 857 PW_LOG_ERROR("Echo failed with status %d", 858 static_cast<int>(status.code())); 859 } 860 } 861 862 } // namespace 863 864 void CallEcho(const char* message) { 865 // Create a client to call the EchoService. 866 EchoClient echo_client(my_rpc_client, kDefaultChannelId); 867 868 pw_rpc_EchoMessage request = pw_rpc_EchoMessage_init_default; 869 pw::string::Copy(message, request.msg); 870 871 // By assigning the returned ClientCall to the global echo_call, the RPC 872 // call is kept alive until it completes. When a response is received, it 873 // will be logged by the handler function and the call will complete. 874 echo_call = echo_client.Echo(request, EchoResponse); 875 if (!echo_call.active()) { 876 // The RPC call was not sent. This could occur due to, for example, an 877 // invalid channel ID. Handle if necessary. 878 } 879 } 880 881Client implementation details 882----------------------------- 883 884The ClientCall class 885^^^^^^^^^^^^^^^^^^^^ 886``ClientCall`` stores the context of an active RPC, and serves as the user's 887interface to the RPC client. The core RPC library provides a base ``ClientCall`` 888class with common functionality, which is then extended for RPC client 889implementations tied to different protobuf libraries to provide convenient 890interfaces for working with RPCs. 891 892The RPC server stores a list of all of active ``ClientCall`` objects. When an 893incoming packet is recieved, it dispatches to one of its active calls, which 894then decodes the payload and presents it to the user. 895 896ClientServer 897============ 898Sometimes, a device needs to both process RPCs as a server, as well as making 899calls to another device as a client. To do this, both a client and server must 900be set up, and incoming packets must be sent to both of them. 901 902Pigweed simplifies this setup by providing a ``ClientServer`` class which wraps 903an RPC client and server with the same set of channels. 904 905.. code-block:: cpp 906 907 pw::rpc::Channel channels[] = { 908 pw::rpc::Channel::Create<1>(&channel_output)}; 909 910 // Creates both a client and a server. 911 pw::rpc::ClientServer client_server(channels); 912 913 void ProcessRpcData(pw::ConstByteSpan packet) { 914 // Calls into both the client and the server, sending the packet to the 915 // appropriate one. 916 client_server.ProcessPacket(packet); 917 } 918 919Testing 920======= 921``pw_rpc`` provides utilities for unit testing RPC services and client calls. 922 923Client unit testing in C++ 924-------------------------- 925``pw_rpc`` supports invoking RPCs, simulating server responses, and checking 926what packets are sent by an RPC client in tests. Both raw and Nanopb interfaces 927are supported. Code that uses the raw API may be tested with the Nanopb test 928helpers, and vice versa. 929 930To test code that invokes RPCs, declare a ``RawClientTestContext`` or 931``NanopbClientTestContext``. These test context objects provide a 932preconfigured RPC client, channel, server fake, and buffer for encoding packets. 933These test classes are defined in ``pw_rpc/raw/client_testing.h`` and 934``pw_rpc/nanopb/client_testing.h``. 935 936Use the context's ``client()`` and ``channel()`` to invoke RPCs. Use the 937context's ``server()`` to simulate responses. To verify that the client sent the 938expected data, use the context's ``output()``, which is a ``FakeChannelOutput``. 939 940For example, the following tests a class that invokes an RPC. It checks that 941the expected data was sent and then simulates a response from the server. 942 943.. code-block:: cpp 944 945 #include "pw_rpc/raw/client_testing.h" 946 947 class ThingThatCallsRpcs { 948 public: 949 // To support injecting an RPC client for testing, classes that make RPC 950 // calls should take an RPC client and channel ID or an RPC service client 951 // (e.g. pw_rpc::raw::MyService::Client). 952 ThingThatCallsRpcs(pw::rpc::Client& client, uint32_t channel_id); 953 954 void DoSomethingThatInvokesAnRpc(); 955 956 bool SetToTrueWhenRpcCompletes(); 957 }; 958 959 TEST(TestAThing, InvokesRpcAndHandlesResponse) { 960 RawClientTestContext context; 961 ThingThatCallsRpcs thing(context.client(), context.channel().id()); 962 963 // Execute the code that invokes the MyService.TheMethod RPC. 964 things.DoSomethingThatInvokesAnRpc(); 965 966 // Find and verify the payloads sent for the MyService.TheMethod RPC. 967 auto msgs = context.output().payloads<pw_rpc::raw::MyService::TheMethod>(); 968 ASSERT_EQ(msgs.size(), 1u); 969 970 VerifyThatTheExpectedMessageWasSent(msgs.back()); 971 972 // Send the response packet from the server and verify that the class reacts 973 // accordingly. 974 EXPECT_FALSE(thing.SetToTrueWhenRpcCompletes()); 975 976 context_.server().SendResponse<pw_rpc::raw::MyService::TheMethod>( 977 final_message, OkStatus()); 978 979 EXPECT_TRUE(thing.SetToTrueWhenRpcCompletes()); 980 } 981 982Integration testing with ``pw_rpc`` 983----------------------------------- 984``pw_rpc`` provides utilities to simplify writing integration tests for systems 985that communicate with ``pw_rpc``. The integration test utitilies set up a socket 986to use for IPC between an RPC server and client process. 987 988The server binary uses the system RPC server facade defined 989``pw_rpc_system_server/rpc_server.h``. The client binary uses the functions 990defined in ``pw_rpc/integration_testing.h``: 991 992.. cpp:var:: constexpr uint32_t kChannelId 993 994 The RPC channel for integration test RPCs. 995 996.. cpp:function:: pw::rpc::Client& pw::rpc::integration_test::Client() 997 998 Returns the global RPC client for integration test use. 999 1000.. cpp:function:: pw::Status pw::rpc::integration_test::InitializeClient(int argc, char* argv[], const char* usage_args = "PORT") 1001 1002 Initializes logging and the global RPC client for integration testing. Starts 1003 a background thread that processes incoming. 1004 1005Module Configuration Options 1006============================ 1007The following configurations can be adjusted via compile-time configuration of 1008this module, see the 1009:ref:`module documentation <module-structure-compile-time-configuration>` for 1010more details. 1011 1012.. c:macro:: PW_RPC_CLIENT_STREAM_END_CALLBACK 1013 1014 In client and bidirectional RPCs, pw_rpc clients may signal that they have 1015 finished sending requests with a CLIENT_STREAM_END packet. While this can be 1016 useful in some circumstances, it is often not necessary. 1017 1018 This option controls whether or not include a callback that is called when 1019 the client stream ends. The callback is included in all ServerReader/Writer 1020 objects as a pw::Function, so may have a significant cost. 1021 1022 This is disabled by default. 1023 1024.. c:macro:: PW_RPC_NANOPB_STRUCT_MIN_BUFFER_SIZE 1025 1026 The Nanopb-based pw_rpc implementation allocates memory to use for Nanopb 1027 structs for the request and response protobufs. The template function that 1028 allocates these structs rounds struct sizes up to this value so that 1029 different structs can be allocated with the same function. Structs with sizes 1030 larger than this value cause an extra function to be created, which slightly 1031 increases code size. 1032 1033 Ideally, this value will be set to the size of the largest Nanopb struct used 1034 as an RPC request or response. The buffer can be stack or globally allocated 1035 (see ``PW_RPC_NANOPB_STRUCT_BUFFER_STACK_ALLOCATE``). 1036 1037 This defaults to 64 Bytes. 1038 1039.. c:macro:: PW_RPC_USE_GLOBAL_MUTEX 1040 1041 Enable global synchronization for RPC calls. If this is set, a backend must 1042 be configured for pw_sync:mutex. 1043 1044 This is disabled by default. 1045 1046.. c:macro:: PW_RPC_DYNAMIC_ALLOCATION 1047 1048 Whether pw_rpc should use dynamic memory allocation internally. If enabled, 1049 pw_rpc dynamically allocates channels and its encoding buffers. RPC users may 1050 use dynamic allocation independently of this option (e.g. to allocate pw_rpc 1051 call objects). 1052 1053 The semantics for allocating and initializing channels change depending on 1054 this option. If dynamic allocation is disabled, pw_rpc endpoints (servers or 1055 clients) use an externally-allocated, fixed-size array of channels. 1056 That array must include unassigned channels or existing channels must be 1057 closed to add new channels. 1058 1059 If dynamic allocation is enabled, an span of channels may be passed to the 1060 endpoint at construction, but these channels are only used to initialize its 1061 internal std::vector of channels. External channel objects are NOT used by 1062 the endpoint cannot be updated if dynamic allocation is enabled. No 1063 unassigned channels should be passed to the endpoint; they will be ignored. 1064 Any number of channels may be added to the endpoint, without closing existing 1065 channels, but adding channels will use more memory. 1066 1067.. c:macro:: PW_RPC_CONFIG_LOG_LEVEL 1068 1069 The log level to use for this module. Logs below this level are omitted. 1070 1071 This defaults to ``PW_LOG_LEVEL_INFO``. 1072 1073.. c:macro:: PW_RPC_CONFIG_LOG_MODULE_NAME 1074 1075 The log module name to use for this module. 1076 1077 This defaults to ``"PW_RPC"``. 1078 1079.. c:macro:: PW_RPC_NANOPB_STRUCT_BUFFER_STACK_ALLOCATE 1080 1081 This option determines whether to allocate the Nanopb structs on the stack or 1082 in a global variable. Globally allocated structs are NOT thread safe, but 1083 work fine when the RPC server's ProcessPacket function is only called from 1084 one thread. 1085 1086 This is enabled by default. 1087 1088Sharing server and client code 1089============================== 1090Streaming RPCs support writing multiple requests or responses. To facilitate 1091sharing code between servers and clients, ``pw_rpc`` provides the 1092``pw::rpc::Writer`` interface. On the client side, a client or bidirectional 1093streaming RPC call object (``ClientWriter`` or ``ClientReaderWriter``) can be 1094used as a ``pw::rpc::Writer&``. On the server side, a server or bidirectional 1095streaming RPC call object (``ServerWriter`` or ``ServerReaderWriter``) can be 1096used as a ``pw::rpc::Writer&``. 1097 1098Zephyr 1099====== 1100To enable ``pw_rpc.*`` for Zephyr add ``CONFIG_PIGWEED_RPC=y`` to the project's 1101configuration. This will enable the Kconfig menu for the following: 1102 1103* ``pw_rpc.server`` which can be enabled via ``CONFIG_PIGWEED_RPC_SERVER=y``. 1104* ``pw_rpc.client`` which can be enabled via ``CONFIG_PIGWEED_RPC_CLIENT=y``. 1105* ``pw_rpc.client_server`` which can be enabled via 1106 ``CONFIG_PIGWEED_RPC_CLIENT_SERVER=y``. 1107* ``pw_rpc.common` which can be enabled via ``CONFIG_PIGWEED_RPC_COMMON=y``. 1108 1109Encoding and sending packets 1110============================ 1111``pw_rpc`` has to manage interactions among multiple RPC clients, servers, 1112client calls, and server calls. To safely synchronize these interactions with 1113minimal overhead, ``pw_rpc`` uses a single, global mutex (when 1114``PW_RPC_USE_GLOBAL_MUTEX`` is enabled). 1115 1116Because ``pw_rpc`` uses a global mutex, it also uses a global buffer to encode 1117outgoing packets. The size of the buffer is set with 1118``PW_RPC_ENCODING_BUFFER_SIZE``, which defaults to 512 B. 1119 1120Users of ``pw_rpc`` must implement the :cpp:class:`pw::rpc::ChannelOutput` 1121interface. 1122 1123.. _module-pw_rpc-ChannelOutput: 1124.. cpp:class:: pw::rpc::ChannelOutput 1125 1126 ``pw_rpc`` endpoints use :cpp:class:`ChannelOutput` instances to send packets. 1127 Systems that integrate pw_rpc must use one or more :cpp:class:`ChannelOutput` 1128 instances. 1129 1130 .. cpp:member:: static constexpr size_t kUnlimited = std::numeric_limits<size_t>::max() 1131 1132 Value returned from :cpp:func:`MaximumTransmissionUnit` to indicate an 1133 unlimited MTU. 1134 1135 .. cpp:function:: virtual size_t MaximumTransmissionUnit() 1136 1137 Returns the size of the largest packet the :cpp:class:`ChannelOutput` can 1138 send. :cpp:class:`ChannelOutput` implementations should only override this 1139 function if they impose a limit on the MTU. The default implementation 1140 returns :cpp:member:`kUnlimited`, which indicates that there is no MTU 1141 limit. 1142 1143 .. cpp:function:: virtual pw::Status Send(std::span<std::byte> packet) 1144 1145 Sends an encoded RPC packet. Returns OK if further packets may be sent, even 1146 if the current packet could not be sent. Returns any other status if the 1147 Channel is no longer able to send packets. 1148 1149 The RPC system's internal lock is held while this function is called. Avoid 1150 long-running operations, since these will delay any other users of the RPC 1151 system. 1152 1153 .. danger:: 1154 1155 No ``pw_rpc`` APIs may be accessed in this function! Implementations MUST 1156 NOT access any RPC endpoints (:cpp:class:`pw::rpc::Client`, 1157 :cpp:class:`pw::rpc::Server`) or call objects 1158 (:cpp:class:`pw::rpc::ServerReaderWriter`, 1159 :cpp:class:`pw::rpc::ClientReaderWriter`, etc.) inside the :cpp:func:`Send` 1160 function or any descendent calls. Doing so will result in deadlock! RPC APIs 1161 may be used by other threads, just not within :cpp:func:`Send`. 1162 1163 The buffer provided in ``packet`` must NOT be accessed outside of this 1164 function. It must be sent immediately or copied elsewhere before the 1165 function returns. 1166