1.. _module-pw_rpc-cpp: 2 3===================== 4C++ server and client 5===================== 6.. pigweed-module-subpage:: 7 :name: pw_rpc 8 9This page provides further guidance on how to use the C++ server 10and client libraries. 11 12---------- 13RPC server 14---------- 15Declare an instance of ``rpc::Server`` and register services with it. 16 17Size report 18=========== 19The following size report showcases the memory usage of the core RPC server. It 20is configured with a single channel using a basic transport interface that 21directly reads from and writes to ``pw_sys_io``. The transport has a 128-byte 22packet buffer, which comprises the plurality of the example's RAM usage. This is 23not a suitable transport for an actual product; a real implementation would have 24additional overhead proportional to the complexity of the transport. 25 26.. include:: server_size 27 28RPC server implementation 29========================= 30 31The Method class 32---------------- 33The RPC Server depends on the ``pw::rpc::internal::Method`` class. ``Method`` 34serves as the bridge between the ``pw_rpc`` server library and the user-defined 35RPC functions. Each supported protobuf implementation extends ``Method`` to 36implement its request and response proto handling. The ``pw_rpc`` server 37calls into the ``Method`` implementation through the base class's ``Invoke`` 38function. 39 40``Method`` implementations store metadata about each method, including a 41function pointer to the user-defined method implementation. They also provide 42``static constexpr`` functions for creating each type of method. ``Method`` 43implementations must satisfy the ``MethodImplTester`` test class in 44``pw_rpc/internal/method_impl_tester.h``. 45 46See ``pw_rpc/internal/method.h`` for more details about ``Method``. 47 48Packet flow 49----------- 50 51Requests 52^^^^^^^^ 53 54.. mermaid:: 55 :alt: Request Packet Flow 56 57 flowchart LR 58 packets[Packets] 59 60 subgraph pw_rpc [pw_rpc Library] 61 direction TB 62 internalMethod[[internal::Method]] 63 Server --> Service --> internalMethod 64 end 65 66 packets --> Server 67 68 generatedServices{{generated services}} 69 userDefinedRPCs(user-defined RPCs) 70 71 generatedServices --> userDefinedRPCs 72 internalMethod --> generatedServices 73 74Responses 75^^^^^^^^^ 76 77.. mermaid:: 78 :alt: Request Packet Flow 79 80 flowchart LR 81 generatedServices{{generated services}} 82 userDefinedRPCs(user-defined RPCs) 83 84 subgraph pw_rpc [pw_rpc Library] 85 direction TB 86 internalMethod[[internal::Method]] 87 internalMethod --> Server --> Channel 88 end 89 90 packets[Packets] 91 Channel --> packets 92 93 userDefinedRPCs --> generatedServices 94 generatedServices --> internalMethod 95 96---------- 97RPC client 98---------- 99The RPC client is used to send requests to a server and manages the contexts of 100ongoing RPCs. 101 102Setting up a client 103=================== 104The ``pw::rpc::Client`` class is instantiated with a list of channels that it 105uses to communicate. These channels can be shared with a server, but multiple 106clients cannot use the same channels. 107 108To send incoming RPC packets from the transport layer to be processed by a 109client, the client's ``ProcessPacket`` function is called with the packet data. 110 111.. code-block:: c++ 112 113 #include "pw_rpc/client.h" 114 115 namespace { 116 117 pw::rpc::Channel my_channels[] = { 118 pw::rpc::Channel::Create<1>(&my_channel_output)}; 119 pw::rpc::Client my_client(my_channels); 120 121 } // namespace 122 123 // Called when the transport layer receives an RPC packet. 124 void ProcessRpcPacket(ConstByteSpan packet) { 125 my_client.ProcessPacket(packet); 126 } 127 128Note that client processing such as callbacks will be invoked within 129the body of ``ProcessPacket``. 130 131If certain packets need to be filtered out, or if certain client processing 132needs to be invoked from a specific thread or context, the ``PacketMeta`` class 133can be used to determine which service or channel a packet is targeting. After 134filtering, ``ProcessPacket`` can be called from the appropriate environment. 135 136.. _module-pw_rpc-making-calls: 137 138Making RPC calls 139================ 140RPC calls are not made directly through the client, but using one of its 141registered channels instead. A service client class is generated from a .proto 142file for each selected protobuf library, which is then used to send RPC requests 143through a given channel. The API for this depends on the protobuf library; 144please refer to the 145:ref:`appropriate documentation <module-pw_rpc-libraries>`. Multiple 146service client implementations can exist simulatenously and share the same 147``Client`` class. 148 149When a call is made, a call object is returned to the caller. This object tracks 150the ongoing RPC call, and can be used to manage it. An RPC call is only active 151as long as its call object is alive. 152 153.. tip:: 154 155 Use ``std::move`` when passing around call objects to keep RPCs alive. 156 157Example 158------- 159.. code-block:: c++ 160 161 #include "pw_rpc/echo_service_nanopb.h" 162 163 namespace { 164 // Generated clients are namespaced with their proto library. 165 using EchoClient = pw_rpc::nanopb::EchoService::Client; 166 167 // RPC channel ID on which to make client calls. RPC calls cannot be made on 168 // channel 0 (Channel::kUnassignedChannelId). 169 constexpr uint32_t kDefaultChannelId = 1; 170 171 pw::rpc::NanopbUnaryReceiver<pw_rpc_EchoMessage> echo_call; 172 173 // Callback invoked when a response is received. This is called synchronously 174 // from Client::ProcessPacket. 175 void EchoResponse(const pw_rpc_EchoMessage& response, 176 pw::Status status) { 177 if (status.ok()) { 178 PW_LOG_INFO("Received echo response: %s", response.msg); 179 } else { 180 PW_LOG_ERROR("Echo failed with status %d", 181 static_cast<int>(status.code())); 182 } 183 } 184 185 } // namespace 186 187 void CallEcho(const char* message) { 188 // Create a client to call the EchoService. 189 EchoClient echo_client(my_rpc_client, kDefaultChannelId); 190 191 pw_rpc_EchoMessage request{}; 192 pw::string::Copy(message, request.msg); 193 194 // By assigning the returned call to the global echo_call, the RPC 195 // call is kept alive until it completes. When a response is received, it 196 // will be logged by the handler function and the call will complete. 197 echo_call = echo_client.Echo(request, EchoResponse); 198 if (!echo_call.active()) { 199 // The RPC call was not sent. This could occur due to, for example, an 200 // invalid channel ID. Handle if necessary. 201 } 202 } 203 204-------- 205Channels 206-------- 207``pw_rpc`` sends all of its packets over channels. These are logical, 208application-layer routes used to tell the RPC system where a packet should go. 209 210Channels over a client-server connection must all have a unique ID, which can be 211assigned statically at compile time or dynamically. 212 213.. code-block:: cpp 214 215 // Creating a channel with the static ID 3. 216 pw::rpc::Channel static_channel = pw::rpc::Channel::Create<3>(&output); 217 218 // Grouping channel IDs within an enum can lead to clearer code. 219 enum ChannelId { 220 kUartChannel = 1, 221 kSpiChannel = 2, 222 }; 223 224 // Creating a channel with a static ID defined within an enum. 225 pw::rpc::Channel another_static_channel = 226 pw::rpc::Channel::Create<ChannelId::kUartChannel>(&output); 227 228 // Creating a channel with a dynamic ID (note that no output is provided; it 229 // will be set when the channel is used. 230 pw::rpc::Channel dynamic_channel; 231 232Sometimes, the ID and output of a channel are not known at compile time as they 233depend on information stored on the physical device. To support this use case, a 234dynamically-assignable channel can be configured once at runtime with an ID and 235output. 236 237.. code-block:: cpp 238 239 // Create a dynamic channel without a compile-time ID or output. 240 pw::rpc::Channel dynamic_channel; 241 242 void Init() { 243 // Called during boot to pull the channel configuration from the system. 244 dynamic_channel.Configure(GetChannelId(), some_output); 245 } 246 247Adding and removing channels 248============================ 249New channels may be registered with the ``OpenChannel`` function. If dynamic 250allocation is enabled (:c:macro:`PW_RPC_DYNAMIC_ALLOCATION` is 1), any number of 251channels may be registered. If dynamic allocation is disabled, new channels may 252only be registered if there are availale channel slots in the span provided to 253the RPC endpoint at construction. 254 255A channel may be closed and unregistered with an endpoint by calling 256``ChannelClose`` on the endpoint with the corresponding channel ID. This 257will terminate any pending calls and call their ``on_error`` callback 258with the ``ABORTED`` status. 259 260.. code-block:: cpp 261 262 // When a channel is closed, any pending calls will receive 263 // on_error callbacks with ABORTED status. 264 client->CloseChannel(1); 265 266.. _module-pw_rpc-remap: 267 268Remapping channels 269================== 270Some pw_rpc deployments may find it helpful to remap channel IDs in RPC packets. 271This can remove the need for globally known channel IDs. Clients can use a 272generic channel ID. The server remaps the generic channel ID to an ID associated 273with the transport the client is using. 274 275.. cpp:namespace-push:: pw::rpc 276 277.. doxygengroup:: pw_rpc_channel_functions 278 :content-only: 279 280.. cpp:namespace-pop:: 281 282A future revision of the pw_rpc protocol will remove the need for global channel 283IDs without requiring remapping. 284 285Example deployment 286================== 287This section describes a hypothetical pw_rpc deployment that supports arbitrary 288pw_rpc clients with one pw_rpc server. Note that this assumes that the 289underlying transport provides some sort of addressing that the server-side can 290associate with a channel ID. 291 292- A pw_rpc server is running on one core. A variable number of pw_rpc clients 293 need to call RPCs on the server from a different core. 294- The client core opens a socket (or similar feature) to connect to the server 295 core. 296- The server core detects the inbound connection and allocates a new channel ID. 297 It creates a new channel by calling :cpp:func:`pw::rpc::Server::OpenChannel` 298 with the channel ID and a :cpp:class:`pw::rpc::ChannelOutput` associated with 299 the new connection. 300- The server maintains a mapping between channel IDs and pw_rpc client 301 connections. 302- On the client core, pw_rpc clients all use the same channel ID (e.g. ``1``). 303- As packets arrive from pw_rpc client connections, the server-side code calls 304 :cpp:func:`pw::rpc::ChangeEncodedChannelId` on the encoded packet to replace 305 the generic channel ID (``1``) with the server-side channel ID allocated when 306 the client connected. The encoded packet is then passed to 307 :cpp:func:`pw::rpc::Server::ProcessPacket`. 308- When the server sends pw_rpc packets, the :cpp:class:`pw::rpc::ChannelOutput` 309 calls :cpp:func:`pw::rpc::ChangeEncodedChannelId` to set the channel ID back 310 to the generic ``1``. 311 312------------------------------ 313C++ payload sizing limitations 314------------------------------ 315The individual size of each sent RPC request or response is limited by 316``pw_rpc``'s ``PW_RPC_ENCODING_BUFFER_SIZE_BYTES`` configuration option when 317using Pigweed's C++ implementation. While multiple RPC messages can be enqueued 318(as permitted by the underlying transport), if a single individual sent message 319exceeds the limitations of the statically allocated encode buffer, the packet 320will fail to encode and be dropped. 321 322This applies to all C++ RPC service implementations (nanopb, raw, and pwpb), 323so it's important to ensure request and response message sizes do not exceed 324this limitation. 325 326As ``pw_rpc`` has some additional encoding overhead, a helper, 327``pw::rpc::MaxSafePayloadSize()`` is provided to expose the practical max RPC 328message payload size. 329 330.. code-block:: cpp 331 332 #include "pw_file/file.raw_rpc.pb.h" 333 #include "pw_rpc/channel.h" 334 335 namespace pw::file { 336 337 class FileSystemService : public pw_rpc::raw::FileSystem::Service<FileSystemService> { 338 public: 339 void List(ConstByteSpan request, RawServerWriter& writer); 340 341 private: 342 // Allocate a buffer for building proto responses. 343 static constexpr size_t kEncodeBufferSize = pw::rpc::MaxSafePayloadSize(); 344 std::array<std::byte, kEncodeBufferSize> encode_buffer_; 345 }; 346 347 } // namespace pw::file 348 349------------ 350Call objects 351------------ 352An RPC call is represented by a call object. Server and client calls use the 353same base call class in C++, but the public API is different depending on the 354type of call and whether it is being used by the server or client. See 355:ref:`module-pw_rpc-design-lifecycle`. 356 357The public call types are as follows: 358 359.. list-table:: 360 :header-rows: 1 361 362 * - RPC Type 363 - Server call 364 - Client call 365 * - Unary 366 - ``(Raw|Nanopb|Pwpb)UnaryResponder`` 367 - ``(Raw|Nanopb|Pwpb)UnaryReceiver`` 368 * - Server streaming 369 - ``(Raw|Nanopb|Pwpb)ServerWriter`` 370 - ``(Raw|Nanopb|Pwpb)ClientReader`` 371 * - Client streaming 372 - ``(Raw|Nanopb|Pwpb)ServerReader`` 373 - ``(Raw|Nanopb|Pwpb)ClientWriter`` 374 * - Bidirectional streaming 375 - ``(Raw|Nanopb|Pwpb)ServerReaderWriter`` 376 - ``(Raw|Nanopb|Pwpb)ClientReaderWriter`` 377 378Client call API 379=============== 380Client call objects provide a few common methods. 381 382.. cpp:class:: pw::rpc::ClientCallType 383 384 The ``ClientCallType`` will be one of the following types: 385 386 - ``(Raw|Nanopb|Pwpb)UnaryReceiver`` for unary 387 - ``(Raw|Nanopb|Pwpb)ClientReader`` for server streaming 388 - ``(Raw|Nanopb|Pwpb)ClientWriter`` for client streaming 389 - ``(Raw|Nanopb|Pwpb)ClientReaderWriter`` for bidirectional streaming 390 391 .. cpp:function:: bool active() const 392 393 Returns true if the call is active. 394 395 .. cpp:function:: uint32_t channel_id() const 396 397 Returns the channel ID of this call, which is 0 if the call is inactive. 398 399 .. cpp:function:: uint32_t id() const 400 401 Returns the call ID, a unique identifier for this call. 402 403 .. cpp:function:: void Write(RequestType) 404 405 Only available on client and bidirectional streaming calls. Sends a stream 406 request. Returns: 407 408 - ``OK`` - the request was successfully sent 409 - ``FAILED_PRECONDITION`` - the writer is closed 410 - ``INTERNAL`` - pw_rpc was unable to encode message; does not apply to 411 raw calls 412 - other errors - the :cpp:class:`ChannelOutput` failed to send the packet; 413 the error codes are determined by the :cpp:class:`ChannelOutput` 414 implementation 415 416 .. cpp:function:: void WriteCallback(Function<StatusWithSize(ByteSpan)>) 417 418 Raw RPC only. Invokes the provided callback with the available RPC payload 419 buffer, allowing payloads to be encoded directly into it. Sends a stream 420 packet with the payload if the callback is successful. 421 422 The buffer provided to the callback is only valid for the duration of the 423 callback. The callback should return an OK status with the size of the 424 encoded payload on success, or an error status on failure. 425 426 .. cpp:function:: pw::Status RequestCompletion() 427 428 Notifies the server that client has requested for call completion. On 429 client and bidirectional streaming calls no further client stream messages 430 will be sent. 431 432 .. cpp:function:: pw::Status Cancel() 433 434 Cancels this RPC. Closes the call and sends a ``CANCELLED`` error to the 435 server. Return statuses are the same as :cpp:func:`Write`. 436 437 .. cpp:function:: void Abandon() 438 439 Closes this RPC locally. Sends a ``CLIENT_REQUEST_COMPLETION``, but no cancellation 440 packet. Future packets for this RPC are dropped, and the client sends a 441 ``FAILED_PRECONDITION`` error in response because the call is not active. 442 443 .. cpp:function:: void CloseAndWaitForCallbacks() 444 445 Abandons this RPC and additionally blocks on completion of any running callbacks. 446 447 .. cpp:function:: void set_on_completed(pw::Function<void(ResponseTypeIfUnaryOnly, pw::Status)>) 448 449 Sets the callback that is called when the RPC completes normally. The 450 signature depends on whether the call has a unary or stream response. 451 452 .. cpp:function:: void set_on_error(pw::Function<void(pw::Status)>) 453 454 Sets the callback that is called when the RPC is terminated due to an error. 455 456 .. cpp:function:: void set_on_next(pw::Function<void(ResponseType)>) 457 458 Only available on server and bidirectional streaming calls. Sets the callback 459 that is called for each stream response. 460 461Callbacks 462========= 463The C++ call objects allow users to set callbacks that are invoked when RPC 464:ref:`events <module-pw_rpc-design-events>` occur. 465 466.. list-table:: 467 :header-rows: 1 468 469 * - Name 470 - Stream signature 471 - Non-stream signature 472 - Server 473 - Client 474 * - ``on_error`` 475 - ``void(pw::Status)`` 476 - ``void(pw::Status)`` 477 - ✅ 478 - ✅ 479 * - ``on_next`` 480 - n/a 481 - ``void(const PayloadType&)`` 482 - ✅ 483 - ✅ 484 * - ``on_completed`` 485 - ``void(pw::Status)`` 486 - ``void(const PayloadType&, pw::Status)`` 487 - 488 - ✅ 489 * - ``on_client_requested_completion`` 490 - ``void()`` 491 - n/a 492 - ✅ (:c:macro:`optional <PW_RPC_COMPLETION_REQUEST_CALLBACK>`) 493 - 494 495Limitations and restrictions 496---------------------------- 497RPC callbacks are free to perform most actions, including invoking new RPCs or 498cancelling pending calls. However, the C++ implementation imposes some 499limitations and restrictions that must be observed. 500 501Destructors & moves wait for callbacks to complete 502^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 503* Callbacks must not destroy their call object. Attempting to do so will result 504 in deadlock. 505* Other threads may destroy a call while its callback is running, but that 506 thread will block until all callbacks complete. 507* Callbacks must not move their call object if it the call is still active. They 508 may move their call object after it has terminated. Callbacks may move a 509 different call into their call object, since moving closes the destination 510 call. 511* Other threads may move a call object while it has a callback running, but they 512 will block until the callback completes if the call is still active. 513 514.. warning:: 515 516 Deadlocks or crashes occur if a callback: 517 518 - attempts to destroy its call object 519 - attempts to move its call object while the call is still active 520 - never returns 521 522 If ``pw_rpc`` a callback violates these restrictions, a crash may occur, 523 depending on the value of :c:macro:`PW_RPC_CALLBACK_TIMEOUT_TICKS`. These 524 crashes have a message like the following: 525 526 .. code-block:: text 527 528 A callback for RPC 1:cc0f6de0/31e616ce has not finished after 10000 ticks. 529 This may indicate that an RPC callback attempted to destroy or move its own 530 call object, which is not permitted. Fix this condition or change the value of 531 PW_RPC_CALLBACK_TIMEOUT_TICKS to avoid this crash. 532 533 See https://pigweed.dev/pw_rpc#destructors-moves-wait-for-callbacks-to-complete 534 for details. 535 536Only one thread at a time may execute ``on_next`` 537^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 538Only one thread may execute the ``on_next`` callback for a specific service 539method at a time. If a second thread calls ``ProcessPacket()`` with a stream 540packet before the ``on_next`` callback for the previous packet completes, the 541second packet will be dropped. The RPC endpoint logs a warning when this occurs. 542 543Example warning for a dropped stream packet: 544 545.. code-block:: text 546 547 WRN Received stream packet for 1:cc0f6de0/31e616ce before the callback for 548 a previous packet completed! This packet will be dropped. This can be 549 avoided by handling packets for a particular RPC on only one thread. 550 551----------------------- 552RPC calls introspection 553----------------------- 554``pw_rpc`` provides ``pw_rpc/method_info.h`` header that allows to obtain 555information about the generated RPC method in compile time. 556 557For now it provides only two types: ``MethodRequestType<RpcMethod>`` and 558``MethodResponseType<RpcMethod>``. They are aliases to the types that are used 559as a request and response respectively for the given RpcMethod. 560 561Example 562======= 563We have an RPC service ``SpecialService`` with ``MyMethod`` method: 564 565.. code-block:: protobuf 566 567 package some.package; 568 service SpecialService { 569 rpc MyMethod(MyMethodRequest) returns (MyMethodResponse) {} 570 } 571 572We also have a templated Storage type alias: 573 574.. code-block:: c++ 575 576 template <auto kMethod> 577 using Storage = 578 std::pair<MethodRequestType<kMethod>, MethodResponseType<kMethod>>; 579 580``Storage<some::package::pw_rpc::pwpb::SpecialService::MyMethod>`` will 581instantiate as: 582 583.. code-block:: c++ 584 585 std::pair<some::package::MyMethodRequest::Message, 586 some::package::MyMethodResponse::Message>; 587 588.. note:: 589 590 Only nanopb and pw_protobuf have real types as 591 ``MethodRequestType<RpcMethod>``/``MethodResponseType<RpcMethod>``. Raw has 592 them both set as ``void``. In reality, they are ``pw::ConstByteSpan``. Any 593 helper/trait that wants to use this types for raw methods should do a custom 594 implementation that copies the bytes under the span instead of copying just 595 the span. 596 597.. _module-pw_rpc-client-sync-call-wrappers: 598 599-------------------------------- 600Client synchronous call wrappers 601-------------------------------- 602.. doxygenfile:: pw_rpc/synchronous_call.h 603 :sections: detaileddescription 604 605Example 606======= 607.. code-block:: c++ 608 609 #include "pw_rpc/synchronous_call.h" 610 611 void InvokeUnaryRpc() { 612 pw::rpc::Client client; 613 pw::rpc::Channel channel; 614 615 RoomInfoRequest request; 616 SynchronousCallResult<RoomInfoResponse> result = 617 SynchronousCall<Chat::GetRoomInformation>(client, channel.id(), request); 618 619 if (result.is_rpc_error()) { 620 ShutdownClient(client); 621 } else if (result.is_server_error()) { 622 HandleServerError(result.status()); 623 } else if (result.is_timeout()) { 624 // SynchronousCall will block indefinitely, so we should never get here. 625 PW_UNREACHABLE(); 626 } 627 HandleRoomInformation(std::move(result).response()); 628 } 629 630 void AnotherExample() { 631 pw_rpc::nanopb::Chat::Client chat_client(client, channel); 632 constexpr auto kTimeout = pw::chrono::SystemClock::for_at_least(500ms); 633 634 RoomInfoRequest request; 635 auto result = SynchronousCallFor<Chat::GetRoomInformation>( 636 chat_client, request, kTimeout); 637 638 if (result.is_timeout()) { 639 RetryRoomRequest(); 640 } else { 641 ... 642 } 643 } 644 645The ``SynchronousCallResult<Response>`` is also compatible with the 646:c:macro:`PW_TRY` family of macros, but users should be aware that their use 647will lose information about the type of error. This should only be used if the 648caller will handle all error scenarios the same. 649 650.. code-block:: c++ 651 652 pw::Status SyncRpc() { 653 const RoomInfoRequest request; 654 PW_TRY_ASSIGN(const RoomInfoResponse& response, 655 SynchronousCall<Chat::GetRoomInformation>(client, request)); 656 HandleRoomInformation(response); 657 return pw::OkStatus(); 658 } 659 660------------ 661ClientServer 662------------ 663Sometimes, a device needs to both process RPCs as a server, as well as making 664calls to another device as a client. To do this, both a client and server must 665be set up, and incoming packets must be sent to both of them. 666 667Pigweed simplifies this setup by providing a ``ClientServer`` class which wraps 668an RPC client and server with the same set of channels. 669 670.. code-block:: cpp 671 672 pw::rpc::Channel channels[] = { 673 pw::rpc::Channel::Create<1>(&channel_output)}; 674 675 // Creates both a client and a server. 676 pw::rpc::ClientServer client_server(channels); 677 678 void ProcessRpcData(pw::ConstByteSpan packet) { 679 // Calls into both the client and the server, sending the packet to the 680 // appropriate one. 681 client_server.ProcessPacket(packet); 682 } 683 684.. _module-pw_rpc-cpp-testing: 685 686------- 687Testing 688------- 689``pw_rpc`` provides utilities for unit testing RPC services and client calls. 690 691Client unit testing in C++ 692========================== 693``pw_rpc`` supports invoking RPCs, simulating server responses, and checking 694what packets are sent by an RPC client in tests. Raw, Nanopb and Pwpb interfaces 695are supported. Code that uses the raw API may be tested with the raw test 696helpers, and vice versa. The Nanopb and Pwpb APIs also provides a test helper 697with a real client-server pair that supports testing of asynchronous messaging. 698 699To test synchronous code that invokes RPCs, declare a ``RawClientTestContext``, 700``PwpbClientTestContext``, or ``NanopbClientTestContext``. These test context 701objects provide a preconfigured RPC client, channel, server fake, and buffer for 702encoding packets. 703 704These test classes are defined in ``pw_rpc/raw/client_testing.h``, 705``pw_rpc/pwpb/client_testing.h``, or ``pw_rpc/nanopb/client_testing.h``. 706 707Use the context's ``client()`` and ``channel()`` to invoke RPCs. Use the 708context's ``server()`` to simulate responses. To verify that the client sent the 709expected data, use the context's ``output()``, which is a ``FakeChannelOutput``. 710 711For example, the following tests a class that invokes an RPC. It checks that 712the expected data was sent and then simulates a response from the server. 713 714.. code-block:: cpp 715 716 #include "pw_rpc/raw/client_testing.h" 717 718 class ClientUnderTest { 719 public: 720 // To support injecting an RPC client for testing, classes that make RPC 721 // calls should take an RPC client and channel ID or an RPC service client 722 // (e.g. pw_rpc::raw::MyService::Client). 723 ClientUnderTest(pw::rpc::Client& client, uint32_t channel_id); 724 725 void DoSomethingThatInvokesAnRpc(); 726 727 bool SetToTrueWhenRpcCompletes(); 728 }; 729 730 TEST(TestAThing, InvokesRpcAndHandlesResponse) { 731 RawClientTestContext context; 732 ClientUnderTest thing(context.client(), context.channel().id()); 733 734 // Execute the code that invokes the MyService.TheMethod RPC. 735 things.DoSomethingThatInvokesAnRpc(); 736 737 // Find and verify the payloads sent for the MyService.TheMethod RPC. 738 auto msgs = context.output().payloads<pw_rpc::raw::MyService::TheMethod>(); 739 ASSERT_EQ(msgs.size(), 1u); 740 741 VerifyThatTheExpectedMessageWasSent(msgs.back()); 742 743 // Send the response packet from the server and verify that the class reacts 744 // accordingly. 745 EXPECT_FALSE(thing.SetToTrueWhenRpcCompletes()); 746 747 context_.server().SendResponse<pw_rpc::raw::MyService::TheMethod>( 748 final_message, OkStatus()); 749 750 EXPECT_TRUE(thing.SetToTrueWhenRpcCompletes()); 751 } 752 753To test client code that uses asynchronous responses, encapsulates multiple 754rpc calls to one or more services, or uses a custom service implementation, 755declare a ``NanopbClientServerTestContextThreaded`` or 756``PwpbClientServerTestContextThreaded``. These test object are defined in 757``pw_rpc/nanopb/client_server_testing_threaded.h`` and 758``pw_rpc/pwpb/client_server_testing_threaded.h``. 759 760Use the context's ``server()`` to register a ``Service`` implementation, and 761``client()`` and ``channel()`` to invoke RPCs. Create a ``Thread`` using the 762context as a ``ThreadCore`` to have it asynchronously forward request/responses or 763call ``ForwardNewPackets`` to synchronously process all messages. To verify that 764the client/server sent the expected data, use the context's 765``request(uint32_t index)`` and ``response(uint32_t index)`` to retrieve the 766ordered messages. 767 768For example, the following tests a class that invokes an RPC and blocks till a 769response is received. It verifies that expected data was both sent and received. 770 771.. code-block:: cpp 772 773 #include "my_library_protos/my_service.rpc.pb.h" 774 #include "pw_rpc/nanopb/client_server_testing_threaded.h" 775 #include "pw_thread_stl/options.h" 776 777 class ClientUnderTest { 778 public: 779 // To support injecting an RPC client for testing, classes that make RPC 780 // calls should take an RPC client and channel ID or an RPC service client 781 // (e.g. pw_rpc::raw::MyService::Client). 782 ClientUnderTest(pw::rpc::Client& client, uint32_t channel_id); 783 784 Status BlockOnResponse(uint32_t value); 785 }; 786 787 788 class TestService final : public MyService<TestService> { 789 public: 790 Status TheMethod(const pw_rpc_test_TheMethod& request, 791 pw_rpc_test_TheMethod& response) { 792 response.value = request.integer + 1; 793 return pw::OkStatus(); 794 } 795 }; 796 797 TEST(TestServiceTest, ReceivesUnaryRpcResponse) { 798 NanopbClientServerTestContextThreaded<> ctx(pw::thread::stl::Options{}); 799 TestService service; 800 ctx.server().RegisterService(service); 801 ClientUnderTest client(ctx.client(), ctx.channel().id()); 802 803 // Execute the code that invokes the MyService.TheMethod RPC. 804 constexpr uint32_t value = 1; 805 const auto result = client.BlockOnResponse(value); 806 const auto request = ctx.request<MyService::TheMethod>(0); 807 const auto response = ctx.response<MyService::TheMethod>(0); 808 809 // Verify content of messages 810 EXPECT_EQ(result, pw::OkStatus()); 811 EXPECT_EQ(request.value, value); 812 EXPECT_EQ(response.value, value + 1); 813 } 814 815Use the context's 816``response(uint32_t index, Response<kMethod>& response)`` to decode messages 817into a provided response object. You would use this version if decoder callbacks 818are needed to fully decode a message. For instance if it uses ``repeated`` 819fields. 820 821.. code-block:: cpp 822 823 TestResponse::Message response{}; 824 response.repeated_field.SetDecoder( 825 [&values](TestResponse::StreamDecoder& decoder) { 826 return decoder.ReadRepeatedField(values); 827 }); 828 ctx.response<test::GeneratedService::TestAnotherUnaryRpc>(0, response); 829 830Synchronous versions of these test contexts also exist that may be used on 831non-threaded systems ``NanopbClientServerTestContext`` and 832``PwpbClientServerTestContext``. While these do not allow for asynchronous 833messaging they support the use of service implementations and use a similar 834syntax. When these are used ``.ForwardNewPackets()`` should be called after each 835rpc call to trigger sending of queued messages. 836 837For example, the following tests a class that invokes an RPC that is responded 838to with a test service implementation. 839 840.. code-block:: cpp 841 842 #include "my_library_protos/my_service.rpc.pb.h" 843 #include "pw_rpc/nanopb/client_server_testing.h" 844 845 class ClientUnderTest { 846 public: 847 ClientUnderTest(pw::rpc::Client& client, uint32_t channel_id); 848 849 Status SendRpcCall(uint32_t value); 850 }; 851 852 853 class TestService final : public MyService<TestService> { 854 public: 855 Status TheMethod(const pw_rpc_test_TheMethod& request, 856 pw_rpc_test_TheMethod& response) { 857 response.value = request.integer + 1; 858 return pw::OkStatus(); 859 } 860 }; 861 862 TEST(TestServiceTest, ReceivesUnaryRpcResponse) { 863 NanopbClientServerTestContext<> ctx(); 864 TestService service; 865 ctx.server().RegisterService(service); 866 ClientUnderTest client(ctx.client(), ctx.channel().id()); 867 868 // Execute the code that invokes the MyService.TheMethod RPC. 869 constexpr uint32_t value = 1; 870 const auto result = client.SendRpcCall(value); 871 // Needed after ever RPC call to trigger forward of packets 872 ctx.ForwardNewPackets(); 873 const auto request = ctx.request<MyService::TheMethod>(0); 874 const auto response = ctx.response<MyService::TheMethod>(0); 875 876 // Verify content of messages 877 EXPECT_EQ(result, pw::OkStatus()); 878 EXPECT_EQ(request.value, value); 879 EXPECT_EQ(response.value, value + 1); 880 } 881 882Custom packet processing for ClientServerTestContext 883==================================================== 884Optional constructor arguments for nanopb/pwpb ``*ClientServerTestContext`` and 885``*ClientServerTestContextThreaded`` allow allow customized packet processing. 886By default the only thing is done is ``ProcessPacket()`` call on the 887``ClientServer`` instance. 888 889For cases when additional instrumentation or offloading to separate thread is 890needed, separate client and server processors can be passed to context 891constructors. A packet processor is a function that returns ``pw::Status`` and 892accepts two arguments: ``pw::rpc::ClientServer&`` and ``pw::ConstByteSpan``. 893Default packet processing is equivalent to the next processor: 894 895.. code-block:: cpp 896 897 [](ClientServer& client_server, pw::ConstByteSpan packet) -> pw::Status { 898 return client_server.ProcessPacket(packet); 899 }; 900 901The Server processor will be applied to all packets sent to the server (i.e. 902requests) and client processor will be applied to all packets sent to the client 903(i.e. responses). 904 905.. note:: 906 907 The packet processor MUST call ``ClientServer::ProcessPacket()`` method. 908 Otherwise the packet won't be processed. 909 910.. note:: 911 912 If the packet processor offloads processing to the separate thread, it MUST 913 copy the ``packet``. After the packet processor returns, the underlying array 914 can go out of scope or be reused for other purposes. 915 916SendResponseIfCalled() helper 917============================= 918``SendResponseIfCalled()`` function waits on ``*ClientTestContext*`` output to 919have a call for the specified method and then responses to it. It supports 920timeout for the waiting part (default timeout is 100ms). 921 922.. code-block:: c++ 923 924 #include "pw_rpc/test_helpers.h" 925 926 pw::rpc::PwpbClientTestContext client_context; 927 other::pw_rpc::pwpb::OtherService::Client other_service_client( 928 client_context.client(), client_context.channel().id()); 929 930 PW_PWPB_TEST_METHOD_CONTEXT(MyService, GetData) 931 context(other_service_client); 932 context.call({}); 933 934 PW_TEST_ASSERT_OK(pw::rpc::test::SendResponseIfCalled< 935 other::pw_rpc::pwpb::OtherService::GetPart>( 936 client_context, {.value = 42})); 937 938 // At this point MyService::GetData handler received the GetPartResponse. 939 940Integration testing with ``pw_rpc`` 941=================================== 942``pw_rpc`` provides utilities to simplify writing integration tests for systems 943that communicate with ``pw_rpc``. The integration test utitilies set up a socket 944to use for IPC between an RPC server and client process. 945 946The server binary uses the system RPC server facade defined 947``pw_rpc_system_server/rpc_server.h``. The client binary uses the functions 948defined in ``pw_rpc/integration_testing.h``: 949 950.. cpp:var:: constexpr uint32_t kChannelId 951 952 The RPC channel for integration test RPCs. 953 954.. cpp:function:: pw::rpc::Client& pw::rpc::integration_test::Client() 955 956 Returns the global RPC client for integration test use. 957 958.. cpp:function:: pw::Status pw::rpc::integration_test::InitializeClient(int argc, char* argv[], const char* usage_args = "PORT") 959 960 Initializes logging and the global RPC client for integration testing. Starts 961 a background thread that processes incoming. 962 963--------------------- 964Configuration options 965--------------------- 966The following configurations can be adjusted via compile-time configuration of 967this module, see the 968:ref:`module documentation <module-structure-compile-time-configuration>` for 969more details. 970 971.. doxygenfile:: pw_rpc/public/pw_rpc/internal/config.h 972 :sections: define 973 974------------------------------ 975Sharing server and client code 976------------------------------ 977Streaming RPCs support writing multiple requests or responses. To facilitate 978sharing code between servers and clients, ``pw_rpc`` provides the 979``pw::rpc::Writer`` interface. On the client side, a client or bidirectional 980streaming RPC call object (``ClientWriter`` or ``ClientReaderWriter``) can be 981used as a ``pw::rpc::Writer&``. On the server side, a server or bidirectional 982streaming RPC call object (``ServerWriter`` or ``ServerReaderWriter``) can be 983used as a ``pw::rpc::Writer&``. Call ``as_writer()`` to get a ``Writer&`` of the 984client or server call object. 985 986---------------------------- 987Encoding and sending packets 988---------------------------- 989``pw_rpc`` has to manage interactions among multiple RPC clients, servers, 990client calls, and server calls. To safely synchronize these interactions with 991minimal overhead, ``pw_rpc`` uses a single, global mutex (when 992``PW_RPC_USE_GLOBAL_MUTEX`` is enabled). 993 994Because ``pw_rpc`` uses a global mutex, it also uses a global buffer to encode 995outgoing packets. The size of the buffer is set with 996``PW_RPC_ENCODING_BUFFER_SIZE_BYTES``, which defaults to 512 B. If dynamic 997allocation is enabled, this size does not affect how large RPC messages can be, 998but it is still used for sizing buffers in test utilities. 999 1000Users of ``pw_rpc`` must implement the :cpp:class:`pw::rpc::ChannelOutput` 1001interface. 1002 1003.. _module-pw_rpc-ChannelOutput: 1004.. cpp:class:: pw::rpc::ChannelOutput 1005 1006 ``pw_rpc`` endpoints use :cpp:class:`ChannelOutput` instances to send 1007 packets. Systems that integrate pw_rpc must use one or more 1008 :cpp:class:`ChannelOutput` instances. 1009 1010 .. cpp:member:: static constexpr size_t kUnlimited = std::numeric_limits<size_t>::max() 1011 1012 Value returned from :cpp:func:`MaximumTransmissionUnit` to indicate an 1013 unlimited MTU. 1014 1015 .. cpp:function:: virtual size_t MaximumTransmissionUnit() 1016 1017 Returns the size of the largest packet the :cpp:class:`ChannelOutput` can 1018 send. :cpp:class:`ChannelOutput` implementations should only override this 1019 function if they impose a limit on the MTU. The default implementation 1020 returns :cpp:member:`kUnlimited`, which indicates that there is no MTU 1021 limit. 1022 1023 .. cpp:function:: virtual pw::Status Send(span<std::byte> packet) 1024 1025 Sends an encoded RPC packet. Returns OK if further packets may be sent, 1026 even if the current packet could not be sent. Returns any other status if 1027 the Channel is no longer able to send packets. 1028 1029 The RPC system's internal lock is held while this function is 1030 called. Avoid long-running operations, since these will delay any other 1031 users of the RPC system. 1032 1033 .. danger:: 1034 1035 No ``pw_rpc`` APIs may be accessed in this function! Implementations 1036 MUST NOT access any RPC endpoints (:cpp:class:`pw::rpc::Client`, 1037 :cpp:class:`pw::rpc::Server`) or call objects 1038 (:cpp:class:`pw::rpc::ServerReaderWriter` 1039 :cpp:class:`pw::rpc::ClientReaderWriter`, etc.) inside the 1040 :cpp:func:`Send` function or any descendent calls. Doing so will result 1041 in deadlock! RPC APIs may be used by other threads, just not within 1042 :cpp:func:`Send`. 1043 1044 The buffer provided in ``packet`` must NOT be accessed outside of this 1045 function. It must be sent immediately or copied elsewhere before the 1046 function returns. 1047