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