blob: b77b013208d85f17720e3fa74915267df13362e4 [file] [log] [blame]
Wyatt Heplerf9fb90f2020-09-30 18:59:33 -07001.. _module-pw_rpc:
Alexei Frolov26e3ae62020-05-04 17:06:17 -07002
3------
4pw_rpc
5------
6The ``pw_rpc`` module provides a system for defining and invoking remote
7procedure calls (RPCs) on a device.
8
Wyatt Hepler830d26d2021-02-17 09:07:43 -08009This 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
Wyatt Hepler455b4922020-09-18 00:19:21 -070018.. admonition:: Try it out!
19
Wyatt Heplerf9fb90f2020-09-30 18:59:33 -070020 For a quick intro to ``pw_rpc``, see the
Alexei Frolovd3e5cb72021-01-08 13:08:45 -080021 :ref:`module-pw_hdlc-rpc-example` in the :ref:`module-pw_hdlc` module.
Wyatt Hepler455b4922020-09-18 00:19:21 -070022
Wyatt Hepler067dd7e2020-07-14 19:34:32 -070023.. attention::
Alexei Frolov26e3ae62020-05-04 17:06:17 -070024
Wyatt Hepler455b4922020-09-18 00:19:21 -070025 This documentation is under construction.
26
27Creating an RPC
28===============
29
301. RPC service declaration
31--------------------------
32Pigweed RPCs are declared in a protocol buffer service definition.
33
34* `Protocol Buffer service documentation
35 <https://developers.google.com/protocol-buffers/docs/proto3#services>`_
36* `gRPC service definition documentation
37 <https://grpc.io/docs/what-is-grpc/core-concepts/#service-definition>`_
38
39.. code-block:: protobuf
40
41 syntax = "proto3";
42
43 package foo.bar;
44
45 message Request {}
46
47 message Response {
48 int32 number = 1;
49 }
50
51 service TheService {
52 rpc MethodOne(Request) returns (Response) {}
53 rpc MethodTwo(Request) returns (stream Response) {}
54 }
55
56This protocol buffer is declared in a ``BUILD.gn`` file as follows:
57
58.. code-block:: python
59
60 import("//build_overrides/pigweed.gni")
61 import("$dir_pw_protobuf_compiler/proto.gni")
62
63 pw_proto_library("the_service_proto") {
64 sources = [ "foo_bar/the_service.proto" ]
65 }
66
Wyatt Hepler830d26d2021-02-17 09:07:43 -080067.. admonition:: proto2 or proto3 syntax?
68
69 Always use proto3 syntax rather than proto2 for new protocol buffers. Proto2
70 protobufs can be compiled for ``pw_rpc``, but they are not as well supported
71 as proto3. Specifically, ``pw_rpc`` lacks support for non-zero default values
72 in proto2. When using Nanopb with ``pw_rpc``, proto2 response protobufs with
73 non-zero field defaults should be manually initialized to the default struct.
74
75 In the past, proto3 was sometimes avoided because it lacked support for field
76 presence detection. Fortunately, this has been fixed: proto3 now supports
77 ``optional`` fields, which are equivalent to proto2 ``optional`` fields.
78
79 If you need to distinguish between a default-valued field and a missing field,
80 mark the field as ``optional``. The presence of the field can be detected
81 with a ``HasField(name)`` or ``has_<field>`` member, depending on the library.
82
83 Optional fields have some overhead --- default-valued fields are included in
84 the encoded proto, and, if using Nanopb, the proto structs have a
85 ``has_<field>`` flag for each optional field. Use plain fields if field
86 presence detection is not needed.
87
88 .. code-block:: protobuf
89
90 syntax = "proto3";
91
92 message MyMessage {
93 // Leaving this field unset is equivalent to setting it to 0.
94 int32 number = 1;
95
96 // Setting this field to 0 is different from leaving it unset.
97 optional int32 other_number = 2;
98 }
99
Wyatt Hepler8779bcd2020-11-25 07:25:16 -08001002. RPC code generation
101----------------------
102``pw_rpc`` generates a C++ header file for each ``.proto`` file. This header is
103generated in the build output directory. Its exact location varies by build
104system and toolchain, but the C++ include path always matches the sources
105declaration in the ``pw_proto_library``. The ``.proto`` extension is replaced
106with an extension corresponding to the protobuf library in use.
Wyatt Hepler455b4922020-09-18 00:19:21 -0700107
Wyatt Hepler8779bcd2020-11-25 07:25:16 -0800108================== =============== =============== =============
109Protobuf libraries Build subtarget Protobuf header pw_rpc header
110================== =============== =============== =============
111Raw only .raw_rpc (none) .raw_rpc.pb.h
112Nanopb or raw .nanopb_rpc .pb.h .rpc.pb.h
113pw_protobuf or raw .pwpb_rpc .pwpb.h .rpc.pwpb.h
114================== =============== =============== =============
115
116For example, the generated RPC header for ``"foo_bar/the_service.proto"`` is
117``"foo_bar/the_service.rpc.pb.h"`` for Nanopb or
118``"foo_bar/the_service.raw_rpc.pb.h"`` for raw RPCs.
119
120The generated header defines a base class for each RPC service declared in the
121``.proto`` file. A service named ``TheService`` in package ``foo.bar`` would
122generate the following base class:
Wyatt Hepler455b4922020-09-18 00:19:21 -0700123
124.. cpp:class:: template <typename Implementation> foo::bar::generated::TheService
125
Wyatt Hepler8779bcd2020-11-25 07:25:16 -08001263. RPC service definition
127-------------------------
128The serivce class is implemented by inheriting from the generated RPC service
129base class and defining a method for each RPC. The methods must match the name
130and function signature for one of the supported protobuf implementations.
131Services may mix and match protobuf implementations within one service.
132
133.. tip::
134
135 The generated code includes RPC service implementation stubs. You can
136 reference or copy and paste these to get started with implementing a service.
137 These stub classes are generated at the bottom of the pw_rpc proto header.
138
Wyatt Heplerdf38ed12021-03-24 08:42:48 -0700139 To use the stubs, do the following:
140
141 #. Locate the generated RPC header in the build directory. For example:
142
143 .. code-block:: sh
144
145 find out/ -name <proto_name>.rpc.pb.h
146
147 #. Scroll to the bottom of the generated RPC header.
148 #. Copy the stub class declaration to a header file.
149 #. Copy the member function definitions to a source file.
150 #. Rename the class or change the namespace, if desired.
151 #. List these files in a build target with a dependency on the
152 ``pw_proto_library``.
153
Wyatt Hepler455b4922020-09-18 00:19:21 -0700154A Nanopb implementation of this service would be as follows:
155
156.. code-block:: cpp
157
Wyatt Hepler8779bcd2020-11-25 07:25:16 -0800158 #include "foo_bar/the_service.rpc.pb.h"
159
Wyatt Hepler455b4922020-09-18 00:19:21 -0700160 namespace foo::bar {
161
162 class TheService : public generated::TheService<TheService> {
163 public:
164 pw::Status MethodOne(ServerContext& ctx,
165 const foo_bar_Request& request,
166 foo_bar_Response& response) {
167 // implementation
Wyatt Hepler1b3da3a2021-01-07 13:26:57 -0800168 return pw::OkStatus();
Wyatt Hepler455b4922020-09-18 00:19:21 -0700169 }
170
171 void MethodTwo(ServerContext& ctx,
172 const foo_bar_Request& request,
173 ServerWriter<foo_bar_Response>& response) {
174 // implementation
175 response.Write(foo_bar_Response{.number = 123});
176 }
177 };
178
179 } // namespace foo::bar
180
181The Nanopb implementation would be declared in a ``BUILD.gn``:
182
183.. code-block:: python
184
185 import("//build_overrides/pigweed.gni")
186
187 import("$dir_pw_build/target_types.gni")
188
189 pw_source_set("the_service") {
190 public_configs = [ ":public" ]
191 public = [ "public/foo_bar/service.h" ]
Wyatt Hepler8779bcd2020-11-25 07:25:16 -0800192 public_deps = [ ":the_service_proto.nanopb_rpc" ]
Wyatt Hepler455b4922020-09-18 00:19:21 -0700193 }
194
195.. attention::
196
197 pw_rpc's generated classes will support using ``pw_protobuf`` or raw buffers
198 (no protobuf library) in the future.
199
Wyatt Hepler8779bcd2020-11-25 07:25:16 -08002004. Register the service with a server
Wyatt Hepler455b4922020-09-18 00:19:21 -0700201-------------------------------------
Alexei Frolovd3e5cb72021-01-08 13:08:45 -0800202This example code sets up an RPC server with an :ref:`HDLC<module-pw_hdlc>`
Wyatt Heplerf9fb90f2020-09-30 18:59:33 -0700203channel output and the example service.
Wyatt Hepler455b4922020-09-18 00:19:21 -0700204
205.. code-block:: cpp
206
207 // Set up the output channel for the pw_rpc server to use. This configures the
208 // pw_rpc server to use HDLC over UART; projects not using UART and HDLC must
209 // adapt this as necessary.
210 pw::stream::SysIoWriter writer;
211 pw::rpc::RpcChannelOutput<kMaxTransmissionUnit> hdlc_channel_output(
Alexei Frolovd3e5cb72021-01-08 13:08:45 -0800212 writer, pw::hdlc::kDefaultRpcAddress, "HDLC output");
Wyatt Hepler455b4922020-09-18 00:19:21 -0700213
214 pw::rpc::Channel channels[] = {
215 pw::rpc::Channel::Create<1>(&hdlc_channel_output)};
216
217 // Declare the pw_rpc server with the HDLC channel.
218 pw::rpc::Server server(channels);
219
220 pw::rpc::TheService the_service;
221
222 void RegisterServices() {
223 // Register the foo.bar.TheService example service.
224 server.Register(the_service);
225
226 // Register other services
227 }
228
229 int main() {
230 // Set up the server.
231 RegisterServices();
232
233 // Declare a buffer for decoding incoming HDLC frames.
234 std::array<std::byte, kMaxTransmissionUnit> input_buffer;
235
236 PW_LOG_INFO("Starting pw_rpc server");
Alexei Frolovd3e5cb72021-01-08 13:08:45 -0800237 pw::hdlc::ReadAndProcessPackets(
Wyatt Hepler455b4922020-09-18 00:19:21 -0700238 server, hdlc_channel_output, input_buffer);
239 }
Wyatt Hepler948f5472020-06-02 16:52:28 -0700240
Alexei Frolov1c670a22021-04-09 10:18:17 -0700241Channels
242========
243``pw_rpc`` sends all of its packets over channels. These are logical,
244application-layer routes used to tell the RPC system where a packet should go.
245
246Channels over a client-server connection must all have a unique ID, which can be
247assigned statically at compile time or dynamically.
248
249.. code-block:: cpp
250
251 // Creating a channel with the static ID 3.
252 pw::rpc::Channel static_channel = pw::rpc::Channel::Create<3>(&output);
253
254 // Grouping channel IDs within an enum can lead to clearer code.
255 enum ChannelId {
256 kUartChannel = 1,
257 kSpiChannel = 2,
258 };
259
260 // Creating a channel with a static ID defined within an enum.
261 pw::rpc::Channel another_static_channel =
262 pw::rpc::Channel::Create<ChannelId::kUartChannel>(&output);
263
264 // Creating a channel with a dynamic ID (note that no output is provided; it
265 // will be set when the channel is used.
266 pw::rpc::Channel dynamic_channel;
267
Alexei Frolov567e6702021-04-13 09:13:02 -0700268Sometimes, the ID and output of a channel are not known at compile time as they
269depend on information stored on the physical device. To support this use case, a
270dynamically-assignable channel can be configured once at runtime with an ID and
271output.
272
273.. code-block:: cpp
274
275 // Create a dynamic channel without a compile-time ID or output.
276 pw::rpc::Channel dynamic_channel;
277
278 void Init() {
279 // Called during boot to pull the channel configuration from the system.
280 dynamic_channel.Configure(GetChannelId(), some_output);
281 }
282
283
Alexei Frolov7c7a3862020-07-16 15:36:02 -0700284Services
285========
286A service is a logical grouping of RPCs defined within a .proto file. ``pw_rpc``
287uses these .proto definitions to generate code for a base service, from which
288user-defined RPCs are implemented.
289
290``pw_rpc`` supports multiple protobuf libraries, and the generated code API
291depends on which is used.
292
Wyatt Heplerf9fb90f2020-09-30 18:59:33 -0700293.. _module-pw_rpc-protobuf-library-apis:
Alexei Frolov4d2adde2020-08-04 10:19:24 -0700294
295Protobuf library APIs
296=====================
297
Alexei Frolov7c7a3862020-07-16 15:36:02 -0700298.. toctree::
299 :maxdepth: 1
300
301 nanopb/docs
302
303Testing a pw_rpc integration
304============================
305After setting up a ``pw_rpc`` server in your project, you can test that it is
306working as intended by registering the provided ``EchoService``, defined in
Wyatt Hepler752d7d32021-03-02 09:02:23 -0800307``echo.proto``, which echoes back a message that it receives.
Alexei Frolov7c7a3862020-07-16 15:36:02 -0700308
Wyatt Hepler752d7d32021-03-02 09:02:23 -0800309.. literalinclude:: echo.proto
Alexei Frolov7c7a3862020-07-16 15:36:02 -0700310 :language: protobuf
311 :lines: 14-
312
313For example, in C++ with nanopb:
314
315.. code:: c++
316
317 #include "pw_rpc/server.h"
318
319 // Include the apporpriate header for your protobuf library.
320 #include "pw_rpc/echo_service_nanopb.h"
321
322 constexpr pw::rpc::Channel kChannels[] = { /* ... */ };
323 static pw::rpc::Server server(kChannels);
324
325 static pw::rpc::EchoService echo_service;
326
327 void Init() {
328 server.RegisterService(&echo_service);
329 }
330
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700331Protocol description
332====================
333Pigweed RPC servers and clients communicate using ``pw_rpc`` packets. These
334packets are used to send requests and responses, control streams, cancel ongoing
335RPCs, and report errors.
336
337Packet format
338-------------
339Pigweed RPC packets consist of a type and a set of fields. The packets are
340encoded as protocol buffers. The full packet format is described in
Wyatt Heplerba325e42021-03-08 14:23:34 -0800341``pw_rpc/pw_rpc/internal/packet.proto``.
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700342
Wyatt Hepler752d7d32021-03-02 09:02:23 -0800343.. literalinclude:: internal/packet.proto
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700344 :language: protobuf
345 :lines: 14-
346
347The packet type and RPC type determine which fields are present in a Pigweed RPC
Wyatt Hepler0f262352020-07-29 09:51:27 -0700348packet. Each packet type is only sent by either the client or the server.
349These tables describe the meaning of and fields included with each packet type.
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700350
Wyatt Hepler0f262352020-07-29 09:51:27 -0700351Client-to-server packets
352^^^^^^^^^^^^^^^^^^^^^^^^
Wyatt Heplera9211162021-06-12 15:40:11 -0700353+-------------------+-------------------------------------+
354| packet type | description |
355+===================+=====================================+
356| REQUEST | Invoke an RPC |
357| | |
358| | .. code-block:: text |
359| | |
360| | - channel_id |
361| | - service_id |
362| | - method_id |
363| | - payload |
364| | (unary & server streaming only) |
365| | |
366+-------------------+-------------------------------------+
367| CLIENT_STREAM | Message in a client stream |
368| | |
369| | .. code-block:: text |
370| | |
371| | - channel_id |
372| | - service_id |
373| | - method_id |
374| | - payload |
375| | |
376+-------------------+-------------------------------------+
Wyatt Hepler5ba80642021-06-18 12:56:17 -0700377| CLIENT_STREAM_END | Client stream is complete |
378| | |
379| | .. code-block:: text |
380| | |
381| | - channel_id |
382| | - service_id |
383| | - method_id |
384| | |
385+-------------------+-------------------------------------+
Wyatt Heplera9211162021-06-12 15:40:11 -0700386| CLIENT_ERROR | Received unexpected packet |
387| | |
388| | .. code-block:: text |
389| | |
390| | - channel_id |
391| | - service_id |
392| | - method_id |
393| | - status |
394| | |
395+-------------------+-------------------------------------+
396| CANCEL | Cancel an ongoing RPC |
397| | |
398| | .. code-block:: text |
399| | |
400| | - channel_id |
401| | - service_id |
402| | - method_id |
403| | |
404+-------------------+-------------------------------------+
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700405
Wyatt Hepler0f262352020-07-29 09:51:27 -0700406**Errors**
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700407
Wyatt Hepler0f262352020-07-29 09:51:27 -0700408The client sends ``CLIENT_ERROR`` packets to a server when it receives a packet
409it did not request. If the RPC is a streaming RPC, the server should abort it.
410
Wyatt Heplera9211162021-06-12 15:40:11 -0700411The status code indicates the type of error. The status code is logged, but all
412status codes result in the same action by the server: aborting the RPC.
Wyatt Hepler0f262352020-07-29 09:51:27 -0700413
414* ``NOT_FOUND`` -- Received a packet for a service method the client does not
415 recognize.
416* ``FAILED_PRECONDITION`` -- Received a packet for a service method that the
417 client did not invoke.
Wyatt Hepler35240da2021-07-21 08:51:22 -0700418* ``DATA_LOSS`` -- Received a corrupt packet for a pending service method.
Wyatt Hepler0f262352020-07-29 09:51:27 -0700419
420Server-to-client packets
421^^^^^^^^^^^^^^^^^^^^^^^^
Wyatt Heplera9211162021-06-12 15:40:11 -0700422+-------------------+-------------------------------------+
423| packet type | description |
424+===================+=====================================+
Wyatt Hepler5ba80642021-06-18 12:56:17 -0700425| RESPONSE | The RPC is complete |
426| | |
427| | .. code-block:: text |
428| | |
429| | - channel_id |
430| | - service_id |
431| | - method_id |
432| | - status |
433| | - payload |
434| | (unary & client streaming only) |
435| | |
436+-------------------+-------------------------------------+
437| SERVER_STREAM | Message in a server stream |
Wyatt Heplera9211162021-06-12 15:40:11 -0700438| | |
439| | .. code-block:: text |
440| | |
441| | - channel_id |
442| | - service_id |
443| | - method_id |
444| | - payload |
Wyatt Heplera9211162021-06-12 15:40:11 -0700445| | |
446+-------------------+-------------------------------------+
447| SERVER_ERROR | Received unexpected packet |
448| | |
449| | .. code-block:: text |
450| | |
451| | - channel_id |
452| | - service_id (if relevant) |
453| | - method_id (if relevant) |
454| | - status |
455| | |
456+-------------------+-------------------------------------+
Wyatt Hepler0f262352020-07-29 09:51:27 -0700457
458**Errors**
459
460The server sends ``SERVER_ERROR`` packets when it receives a packet it cannot
461process. The client should abort any RPC for which it receives an error. The
462status field indicates the type of error.
463
464* ``NOT_FOUND`` -- The requested service or method does not exist.
Wyatt Heplera9211162021-06-12 15:40:11 -0700465* ``FAILED_PRECONDITION`` -- A client stream or cancel packet was sent for an
466 RPC that is not pending.
Wyatt Hepler01fc15b2021-06-10 18:15:59 -0700467* ``INVALID_ARGUMENT`` -- The client sent a packet type to an RPC that does not
468 support it (e.g. a ``CLIENT_STREAM`` was sent to a server streaming RPC).
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700469* ``RESOURCE_EXHAUSTED`` -- The request came on a new channel, but a channel
470 could not be allocated for it.
Wyatt Hepler712d3672020-07-13 15:52:11 -0700471* ``INTERNAL`` -- The server was unable to respond to an RPC due to an
472 unrecoverable internal error.
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700473
474Inovking a service method
475-------------------------
476Calling an RPC requires a specific sequence of packets. This section describes
477the protocol for calling service methods of each type: unary, server streaming,
478client streaming, and bidirectional streaming.
479
Wyatt Hepler5ba80642021-06-18 12:56:17 -0700480The basic flow for all RPC invocations is as follows:
481
482 * Client sends a ``REQUEST`` packet. Includes a payload for unary & server
483 streaming RPCs.
484 * For client and bidirectional streaming RPCs, the client may send any number
485 of ``CLIENT_STREAM`` packets with payloads.
486 * For server and bidirectional streaming RPCs, the server may send any number
487 of ``SERVER_STREAM`` packets.
488 * The server sends a ``RESPONSE`` packet. Includes a payload for unary &
489 client streaming RPCs. The RPC is complete.
490
491The client may cancel an ongoing RPC at any time by sending a ``CANCEL`` packet.
492The server may finish an ongoing RPC at any time by sending the ``RESPONSE``
493packet.
494
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700495Unary RPC
496^^^^^^^^^
497In a unary RPC, the client sends a single request and the server sends a single
498response.
499
500.. seqdiag::
501 :scale: 110
502
503 seqdiag {
504 default_note_color = aliceblue;
505
506 client -> server [
507 label = "request",
Wyatt Hepler0f262352020-07-29 09:51:27 -0700508 leftnote = "PacketType.REQUEST\nchannel ID\nservice ID\nmethod ID\npayload"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700509 ];
510
511 client <- server [
512 label = "response",
Wyatt Hepler0f262352020-07-29 09:51:27 -0700513 rightnote = "PacketType.RESPONSE\nchannel ID\nservice ID\nmethod ID\npayload\nstatus"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700514 ];
515 }
516
Wyatt Heplera9211162021-06-12 15:40:11 -0700517The client may attempt to cancel a unary RPC by sending a ``CANCEL`` packet. The
518server sends no response to a cancelled RPC. If the server processes the unary
519RPC synchronously (the handling thread sends the response), it may not be
520possible to cancel the RPC.
521
522.. seqdiag::
523 :scale: 110
524
525 seqdiag {
526 default_note_color = aliceblue;
527
528 client -> server [
529 label = "request",
530 leftnote = "PacketType.REQUEST\nchannel ID\nservice ID\nmethod ID\npayload"
531 ];
532
533 client -> server [
534 noactivate,
535 label = "cancel",
536 leftnote = "PacketType.CANCEL\nchannel ID\nservice ID\nmethod ID"
537 ];
538 }
539
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700540Server streaming RPC
541^^^^^^^^^^^^^^^^^^^^
542In a server streaming RPC, the client sends a single request and the server
Wyatt Hepler5ba80642021-06-18 12:56:17 -0700543sends any number of ``SERVER_STREAM`` packets followed by a ``RESPONSE`` packet.
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700544
545.. seqdiag::
546 :scale: 110
547
548 seqdiag {
549 default_note_color = aliceblue;
550
551 client -> server [
552 label = "request",
Wyatt Hepler0f262352020-07-29 09:51:27 -0700553 leftnote = "PacketType.REQUEST\nchannel ID\nservice ID\nmethod ID\npayload"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700554 ];
555
556 client <-- server [
557 noactivate,
Wyatt Heplera9211162021-06-12 15:40:11 -0700558 label = "messages (zero or more)",
Wyatt Hepler5ba80642021-06-18 12:56:17 -0700559 rightnote = "PacketType.SERVER_STREAM\nchannel ID\nservice ID\nmethod ID\npayload"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700560 ];
561
562 client <- server [
563 label = "done",
Wyatt Hepler5ba80642021-06-18 12:56:17 -0700564 rightnote = "PacketType.RESPONSE\nchannel ID\nservice ID\nmethod ID\nstatus"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700565 ];
566 }
567
Wyatt Heplera9211162021-06-12 15:40:11 -0700568The client may terminate a server streaming RPC by sending a ``CANCEL`` packet.
569The server sends no response.
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700570
571.. seqdiag::
572 :scale: 110
573
574 seqdiag {
575 default_note_color = aliceblue;
576
577 client -> server [
578 label = "request",
Wyatt Hepler0f262352020-07-29 09:51:27 -0700579 leftnote = "PacketType.REQUEST\nchannel ID\nservice ID\nmethod ID\npayload"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700580 ];
581
582 client <-- server [
583 noactivate,
Wyatt Heplera9211162021-06-12 15:40:11 -0700584 label = "messages (zero or more)",
Wyatt Hepler5ba80642021-06-18 12:56:17 -0700585 rightnote = "PacketType.SERVER_STREAM\nchannel ID\nservice ID\nmethod ID\npayload"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700586 ];
587
588 client -> server [
589 noactivate,
590 label = "cancel",
Wyatt Heplera9211162021-06-12 15:40:11 -0700591 leftnote = "PacketType.CANCEL\nchannel ID\nservice ID\nmethod ID"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700592 ];
593 }
594
595Client streaming RPC
596^^^^^^^^^^^^^^^^^^^^
Wyatt Heplera9211162021-06-12 15:40:11 -0700597In a client streaming RPC, the client starts the RPC by sending a ``REQUEST``
598packet with no payload. It then sends any number of messages in
599``CLIENT_STREAM`` packets, followed by a ``CLIENT_STREAM_END``. The server sends
Wyatt Hepler5ba80642021-06-18 12:56:17 -0700600a single ``RESPONSE`` to finish the RPC.
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700601
602.. seqdiag::
603 :scale: 110
604
605 seqdiag {
606 default_note_color = aliceblue;
607
608 client -> server [
609 label = "start",
Wyatt Hepler0f262352020-07-29 09:51:27 -0700610 leftnote = "PacketType.REQUEST\nchannel ID\nservice ID\nmethod ID"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700611 ];
612
613 client --> server [
614 noactivate,
Wyatt Heplera9211162021-06-12 15:40:11 -0700615 label = "messages (zero or more)",
616 leftnote = "PacketType.CLIENT_STREAM\nchannel ID\nservice ID\nmethod ID\npayload"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700617 ];
618
619 client -> server [
620 noactivate,
621 label = "done",
Wyatt Hepler0f262352020-07-29 09:51:27 -0700622 leftnote = "PacketType.CLIENT_STREAM_END\nchannel ID\nservice ID\nmethod ID"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700623 ];
624
625 client <- server [
626 label = "response",
Wyatt Hepler0f262352020-07-29 09:51:27 -0700627 rightnote = "PacketType.RESPONSE\nchannel ID\nservice ID\nmethod ID\npayload\nstatus"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700628 ];
629 }
630
Wyatt Heplera9211162021-06-12 15:40:11 -0700631The server may finish the RPC at any time by sending its ``RESPONSE`` packet,
632even if it has not yet received the ``CLIENT_STREAM_END`` packet. The client may
633terminate the RPC at any time by sending a ``CANCEL`` packet.
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700634
635.. seqdiag::
636 :scale: 110
637
638 seqdiag {
639 default_note_color = aliceblue;
640
641 client -> server [
642 label = "start",
Wyatt Hepler0f262352020-07-29 09:51:27 -0700643 leftnote = "PacketType.REQUEST\nchannel ID\nservice ID\nmethod ID"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700644 ];
645
646 client --> server [
647 noactivate,
Wyatt Heplera9211162021-06-12 15:40:11 -0700648 label = "messages (zero or more)",
649 leftnote = "PacketType.CLIENT_STREAM\nchannel ID\nservice ID\nmethod ID\npayload"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700650 ];
651
Wyatt Heplera9211162021-06-12 15:40:11 -0700652 client -> server [
653 noactivate,
654 label = "cancel",
655 rightnote = "PacketType.CANCEL\nchannel ID\nservice ID\nmethod ID"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700656 ];
657 }
658
659Bidirectional streaming RPC
660^^^^^^^^^^^^^^^^^^^^^^^^^^^
661In a bidirectional streaming RPC, the client sends any number of requests and
Wyatt Heplera9211162021-06-12 15:40:11 -0700662the server sends any number of responses. The client invokes the RPC by sending
663a ``REQUEST`` with no payload. It sends a ``CLIENT_STREAM_END`` packet when it
Wyatt Hepler5ba80642021-06-18 12:56:17 -0700664has finished sending requests. The server sends a ``RESPONSE`` packet to finish
665the RPC.
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700666
667.. seqdiag::
668 :scale: 110
669
670 seqdiag {
671 default_note_color = aliceblue;
672
673 client -> server [
674 label = "start",
Wyatt Hepler0f262352020-07-29 09:51:27 -0700675 leftnote = "PacketType.REQUEST\nchannel ID\nservice ID\nmethod ID"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700676 ];
677
678 client --> server [
679 noactivate,
Wyatt Heplera9211162021-06-12 15:40:11 -0700680 label = "messages (zero or more)",
681 leftnote = "PacketType.CLIENT_STREAM\nchannel ID\nservice ID\nmethod ID\npayload"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700682 ];
683
684 ... (messages in any order) ...
685
686 client <-- server [
687 noactivate,
Wyatt Heplera9211162021-06-12 15:40:11 -0700688 label = "messages (zero or more)",
Wyatt Hepler5ba80642021-06-18 12:56:17 -0700689 rightnote = "PacketType.SERVER_STREAM\nchannel ID\nservice ID\nmethod ID\npayload"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700690 ];
691
692 client -> server [
693 noactivate,
694 label = "done",
Wyatt Hepler0f262352020-07-29 09:51:27 -0700695 leftnote = "PacketType.CLIENT_STREAM_END\nchannel ID\nservice ID\nmethod ID"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700696 ];
697
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700698 client <- server [
699 label = "done",
Wyatt Hepler5ba80642021-06-18 12:56:17 -0700700 rightnote = "PacketType.RESPONSE\nchannel ID\nservice ID\nmethod ID\nstatus"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700701 ];
702 }
703
Wyatt Hepler5ba80642021-06-18 12:56:17 -0700704The server may finish the RPC at any time by sending the ``RESPONSE`` packet,
705even if it has not received the ``CLIENT_STREAM_END`` packet. The client may
706terminate the RPC at any time by sending a ``CANCEL`` packet.
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700707
708.. seqdiag::
709 :scale: 110
710
711 seqdiag {
712 default_note_color = aliceblue;
713
714 client -> server [
715 label = "start",
Wyatt Heplera9211162021-06-12 15:40:11 -0700716 leftnote = "PacketType.REQUEST\nchannel ID\nservice ID\nmethod ID"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700717 ];
718
719 client --> server [
720 noactivate,
Wyatt Heplera9211162021-06-12 15:40:11 -0700721 label = "messages (zero or more)",
722 leftnote = "PacketType.CLIENT_STREAM\nchannel ID\nservice ID\nmethod ID\npayload"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700723 ];
724
725 client <-- server [
726 noactivate,
Wyatt Heplera9211162021-06-12 15:40:11 -0700727 label = "messages (zero or more)",
Wyatt Hepler5ba80642021-06-18 12:56:17 -0700728 rightnote = "PacketType.SERVER_STREAM\nchannel ID\nservice ID\nmethod ID\npayload"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700729 ];
730
731 client -> server [
732 noactivate,
733 label = "cancel",
Wyatt Hepler5ba80642021-06-18 12:56:17 -0700734 leftnote = "PacketType.CANCEL\nchannel ID\nservice ID\nmethod ID"
735 ];
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700736 }
737
Wyatt Hepler948f5472020-06-02 16:52:28 -0700738RPC server
739==========
740Declare an instance of ``rpc::Server`` and register services with it.
741
742.. admonition:: TODO
743
744 Document the public interface
745
Alexei Frolovbf33d212020-09-15 17:13:45 -0700746Size report
747-----------
748The following size report showcases the memory usage of the core RPC server. It
749is configured with a single channel using a basic transport interface that
750directly reads from and writes to ``pw_sys_io``. The transport has a 128-byte
751packet buffer, which comprises the plurality of the example's RAM usage. This is
752not a suitable transport for an actual product; a real implementation would have
753additional overhead proportional to the complexity of the transport.
754
755.. include:: server_size
756
Wyatt Hepler948f5472020-06-02 16:52:28 -0700757RPC server implementation
758-------------------------
759
760The Method class
761^^^^^^^^^^^^^^^^
762The RPC Server depends on the ``pw::rpc::internal::Method`` class. ``Method``
763serves as the bridge between the ``pw_rpc`` server library and the user-defined
Wyatt Heplere95bd722020-11-23 07:49:47 -0800764RPC functions. Each supported protobuf implementation extends ``Method`` to
765implement its request and response proto handling. The ``pw_rpc`` server
766calls into the ``Method`` implementation through the base class's ``Invoke``
767function.
Wyatt Hepler948f5472020-06-02 16:52:28 -0700768
Wyatt Heplere95bd722020-11-23 07:49:47 -0800769``Method`` implementations store metadata about each method, including a
770function pointer to the user-defined method implementation. They also provide
771``static constexpr`` functions for creating each type of method. ``Method``
772implementations must satisfy the ``MethodImplTester`` test class in
773``pw_rpc_private/method_impl_tester.h``.
Wyatt Hepler948f5472020-06-02 16:52:28 -0700774
Wyatt Heplere95bd722020-11-23 07:49:47 -0800775See ``pw_rpc/internal/method.h`` for more details about ``Method``.
Wyatt Hepler948f5472020-06-02 16:52:28 -0700776
777Packet flow
778^^^^^^^^^^^
779
780Requests
781~~~~~~~~
782
783.. blockdiag::
784
785 blockdiag {
786 packets [shape = beginpoint];
787
788 group {
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700789 label = "pw_rpc library";
Wyatt Hepler948f5472020-06-02 16:52:28 -0700790
791 server [label = "Server"];
Alexei Frolov9a4d6bf2020-08-04 10:33:26 -0700792 service [label = "Service"];
Wyatt Hepler948f5472020-06-02 16:52:28 -0700793 method [label = "internal::Method"];
794 }
795
796 stubs [label = "generated services", shape = ellipse];
797 user [label = "user-defined RPCs", shape = roundedbox];
798
799 packets -> server -> service -> method -> stubs -> user;
800 packets -> server [folded];
801 method -> stubs [folded];
802 }
803
804Responses
805~~~~~~~~~
806
807.. blockdiag::
808
809 blockdiag {
810 user -> stubs [folded];
811
812 group {
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700813 label = "pw_rpc library";
Wyatt Hepler948f5472020-06-02 16:52:28 -0700814
815 server [label = "Server"];
816 method [label = "internal::Method"];
817 channel [label = "Channel"];
818 }
819
820 stubs [label = "generated services", shape = ellipse];
821 user [label = "user-defined RPCs", shape = roundedbox];
822 packets [shape = beginpoint];
823
824 user -> stubs -> method [folded];
825 method -> server -> channel;
826 channel -> packets [folded];
827 }
Alexei Frolov4d2adde2020-08-04 10:19:24 -0700828
829RPC client
830==========
831The RPC client is used to send requests to a server and manages the contexts of
832ongoing RPCs.
833
834Setting up a client
835-------------------
836The ``pw::rpc::Client`` class is instantiated with a list of channels that it
837uses to communicate. These channels can be shared with a server, but multiple
838clients cannot use the same channels.
839
840To send incoming RPC packets from the transport layer to be processed by a
841client, the client's ``ProcessPacket`` function is called with the packet data.
842
843.. code:: c++
844
845 #include "pw_rpc/client.h"
846
847 namespace {
848
849 pw::rpc::Channel my_channels[] = {
850 pw::rpc::Channel::Create<1>(&my_channel_output)};
851 pw::rpc::Client my_client(my_channels);
852
853 } // namespace
854
855 // Called when the transport layer receives an RPC packet.
856 void ProcessRpcPacket(ConstByteSpan packet) {
857 my_client.ProcessPacket(packet);
858 }
859
Alexei Frolov5a3a61c2020-10-01 18:51:41 -0700860.. _module-pw_rpc-making-calls:
861
Alexei Frolov4d2adde2020-08-04 10:19:24 -0700862Making RPC calls
863----------------
864RPC calls are not made directly through the client, but using one of its
865registered channels instead. A service client class is generated from a .proto
866file for each selected protobuf library, which is then used to send RPC requests
867through a given channel. The API for this depends on the protobuf library;
Wyatt Heplerf9fb90f2020-09-30 18:59:33 -0700868please refer to the
869:ref:`appropriate documentation<module-pw_rpc-protobuf-library-apis>`. Multiple
870service client implementations can exist simulatenously and share the same
871``Client`` class.
Alexei Frolov4d2adde2020-08-04 10:19:24 -0700872
873When a call is made, a ``pw::rpc::ClientCall`` object is returned to the caller.
874This object tracks the ongoing RPC call, and can be used to manage it. An RPC
875call is only active as long as its ``ClientCall`` object is alive.
876
877.. tip::
878 Use ``std::move`` when passing around ``ClientCall`` objects to keep RPCs
879 alive.
880
Alexei Frolov2d737bc2021-04-27 23:03:09 -0700881Example
882^^^^^^^
883.. code-block:: c++
884
885 #include "pw_rpc/echo_service_nanopb.h"
886
887 namespace {
Alexei Frolov2b54ee62021-04-29 14:58:21 -0700888 // Generated clients are namespaced with their proto library.
889 using pw::rpc::nanopb::EchoServiceClient;
890
891 EchoServiceClient::EchoCall echo_call;
Alexei Frolovbebba902021-06-09 17:03:52 -0700892
893 // Callback invoked when a response is received. This is called synchronously
894 // from Client::ProcessPacket.
895 void EchoResponse(const pw_rpc_EchoMessage& response,
896 pw::Status status) {
897 if (status.ok()) {
898 PW_LOG_INFO("Received echo response: %s", response.msg);
899 } else {
900 PW_LOG_ERROR("Echo failed with status %d",
901 static_cast<int>(status.code()));
902 }
903 }
Alexei Frolov2d737bc2021-04-27 23:03:09 -0700904
905 } // namespace
906
Alexei Frolovbebba902021-06-09 17:03:52 -0700907
Alexei Frolov2d737bc2021-04-27 23:03:09 -0700908 void CallEcho(const char* message) {
909 pw_rpc_EchoMessage request = pw_rpc_EchoMessage_init_default;
910 pw::string::Copy(message, request.msg);
911
912 // By assigning the returned ClientCall to the global echo_call, the RPC
913 // call is kept alive until it completes. When a response is received, it
914 // will be logged by the handler function and the call will complete.
Alexei Frolovbebba902021-06-09 17:03:52 -0700915 echo_call = EchoServiceClient::Echo(my_channel, request, EchoResponse);
Alexei Frolov2d737bc2021-04-27 23:03:09 -0700916 }
917
Alexei Frolov4d2adde2020-08-04 10:19:24 -0700918Client implementation details
919-----------------------------
920
921The ClientCall class
922^^^^^^^^^^^^^^^^^^^^
923``ClientCall`` stores the context of an active RPC, and serves as the user's
924interface to the RPC client. The core RPC library provides a base ``ClientCall``
925class with common functionality, which is then extended for RPC client
926implementations tied to different protobuf libraries to provide convenient
927interfaces for working with RPCs.
928
929The RPC server stores a list of all of active ``ClientCall`` objects. When an
930incoming packet is recieved, it dispatches to one of its active calls, which
931then decodes the payload and presents it to the user.
Alexei Frolov3e280922021-04-12 14:53:06 -0700932
933ClientServer
934============
935Sometimes, a device needs to both process RPCs as a server, as well as making
936calls to another device as a client. To do this, both a client and server must
937be set up, and incoming packets must be sent to both of them.
938
939Pigweed simplifies this setup by providing a ``ClientServer`` class which wraps
940an RPC client and server with the same set of channels.
941
942.. code-block:: cpp
943
944 pw::rpc::Channel channels[] = {
945 pw::rpc::Channel::Create<1>(&channel_output)};
946
947 // Creates both a client and a server.
948 pw::rpc::ClientServer client_server(channels);
949
950 void ProcessRpcData(pw::ConstByteSpan packet) {
951 // Calls into both the client and the server, sending the packet to the
952 // appropriate one.
953 client_server.ProcessPacket(packet, output);
954 }