xref: /aosp_15_r20/external/pigweed/pw_rpc_transport/docs.rst (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1.. _module-pw_rpc_transport:
2
3================
4pw_rpc_transport
5================
6The ``pw_rpc_transport`` provides a transport layer for ``pw_rpc``.
7
8.. warning::
9  This is an experimental module currently under development. APIs and
10  functionality may change at any time.
11
12``pw_rpc`` provides a system for defining and invoking remote procedure calls
13(RPCs) on a device. It does not include any transports for sending these RPC
14calls. On a real device there could be multiple ways of inter-process and/or
15inter-core communication: hardware mailboxes, shared memory, network sockets,
16Unix domain sockets. ``pw_rpc_transport`` provides means to implement various
17transports and integrate them with ``pw_rpc`` services.
18
19``pw_rpc_transport`` relies on the assumption that a ``pw_rpc`` channel ID
20uniquely identifies both sides of an RPC conversation. It allows developers to
21define transports, egresses and ingresses for various channel IDs and choose
22what framing will be used to send RPC packets over those transports.
23
24RpcFrame
25--------
26Framed RPC data ready to be sent via ``RpcFrameSender``. Consists of a header
27and a payload. Some RPC transport encodings may not require a header and put
28all of the framed data into the payload (in which case the header can be
29an empty span).
30
31A single RPC packet can be split into multiple ``RpcFrame``'s depending on the
32MTU of the transport.
33
34All frames for an RPC packet are expected to be sent and received in order
35without being interleaved by other packets' frames.
36
37RpcFrameSender
38--------------
39Sends RPC frames over some communication channel (e.g. a hardware mailbox,
40shared memory, or a socket). It exposes its MTU size and generally only knows
41how to send an ``RpcFrame`` of a size that doesn't exceed that MTU.
42
43RpcPacketEncoder / RpcPacketDecoder
44-----------------------------------
45``RpcPacketEncoder`` is used to split and frame an RPC packet.
46``RpcPacketDecoder`` then does the opposite e.g. stitches together received
47frames and removes any framing added by the encoder.
48
49RpcEgressHandler
50----------------
51Provides means of sending an RPC packet to its destination. Typically it ties
52together an ``RpcPacketEncoder`` and ``RpcFrameSender``.
53
54RpcIngressHandler
55-----------------
56Provides means of receiving RPC packets over some transport. Typically it has
57logic for reading RPC frames from some transport (a network connection,
58shared memory, or a hardware mailbox), stitching and decoding them with
59``RpcPacketDecoder`` and passing full RPC packets to their intended processor
60via ``RpcPacketProcessor``.
61
62RpcPacketProcessor
63------------------
64Used by ``RpcIngressHandler`` to send the received RPC packet to its intended
65handler (e.g. a pw_rpc ``Service``).
66
67--------------------
68Creating a transport
69--------------------
70RPC transports implement ``pw::rpc::RpcFrameSender``. The transport exposes its
71maximum transmission unit (MTU) and only knows how to send packets of up to the
72size of that MTU.
73
74.. code-block:: cpp
75
76   class MyRpcTransport : public RpcFrameSender {
77   public:
78     size_t mtu() const override { return 128; }
79
80     Status Send(RpcFrame frame) override {
81       // Send the frame via mailbox, shared memory or some other mechanism...
82     }
83   };
84
85--------------------------
86Integration with pw_stream
87--------------------------
88An RpcFrameSender implementaion that wraps a ``pw::stream::Writer`` is provided
89by ``pw::rpc::StreamRpcFrameSender``. As the stream interface doesn't know
90about MTU's, it's up to the user to select one.
91
92.. code-block:: cpp
93
94   stream::SysIoWriter writer;
95   StreamRpcFrameSender<kMtu> sender(writer);
96
97A thread to feed data to a ``pw::rpc::RpcIngressHandler`` from a
98``pw::stream::Reader`` is provided by ``pw::rpc::StreamRpcDispatcher``.
99
100.. code-block:: cpp
101
102   rpc::HdlcRpcIngress<kMaxRpcPacketSize> hdlc_ingress(...);
103   stream::SysIoReader reader;
104
105   // Feed Hdlc ingress with bytes from sysio.
106   rpc::StreamRpcDispatcher<kMaxSysioRead> sysio_dispatcher(reader,
107                                                            hdlc_ingress);
108
109   thread::DetachedThread(SysioDispatcherThreadOptions(),
110                          sysio_dispatcher);
111
112-------------------------------------------
113Using transports: a sample three-node setup
114-------------------------------------------
115
116A transport must be properly registered in order for ``pw_rpc`` to correctly
117route its packets. Below is an example of using a ``SocketRpcTransport`` and
118a (hypothetical) ``SharedMemoryRpcTransport`` to set up RPC connectivity between
119three endpoints.
120
121Node A runs ``pw_rpc`` clients who want to talk to nodes B and C using
122``kChannelAB`` and ``kChannelAC`` respectively. However there is no direct
123connectivity from A to C: only B can talk to C over shared memory while A can
124talk to B over a socket connection. Also, some services on A are self-hosted
125and accessed from the same process on ``kChannelAA``:
126
127.. code-block:: cpp
128
129   // Set up A->B transport over a network socket where B is a server
130   // and A is a client.
131   SocketRpcTransport<kSocketReadBufferSize> a_to_b_transport(
132     SocketRpcTransport<kSocketReadBufferSize>::kAsClient, "localhost",
133     kNodeBPortNumber);
134
135   // LocalRpcEgress handles RPC packets received from other nodes and destined
136   // to this node.
137   LocalRpcEgress<kLocalEgressQueueSize, kMaxPacketSize> local_egress;
138   // HdlcRpcEgress applies HDLC framing to all packets outgoing over the A->B
139   // transport.
140   HdlcRpcEgress<kMaxPacketSize> a_to_b_egress("a->b", a_to_b_transport);
141
142   // List of channels for all packets originated locally at A.
143   std::array tx_channels = {
144     // Self-destined packets go directly to local egress.
145     Channel::Create<kChannelAA>(&local_egress),
146     // Packets to B and C go over A->B transport.
147     Channel::Create<kChannelAB>(&a_to_b_egress),
148     Channel::Create<kChannelAC>(&a_to_b_egress),
149   };
150
151   // Here we list all egresses for the packets _incoming_ from B.
152   std::array b_rx_channels = {
153     // Packets on both AB and AC channels are destined locally; hence sending
154     // to the local egress.
155     ChannelEgress{kChannelAB, local_egress},
156     ChannelEgress{kChannelAC, local_egress},
157   };
158
159   // HdlcRpcIngress complements HdlcRpcEgress: all packets received on
160   // `b_rx_channels` are assumed to have HDLC framing.
161   HdlcRpcIngress<kMaxPacketSize> b_ingress(b_rx_channels);
162
163   // Local egress needs to know how to send received packets to their target
164   // pw_rpc service.
165   ServiceRegistry registry(tx_channels);
166   local_egress.set_packet_processor(registry);
167   // Socket transport needs to be aware of what ingress it's handling.
168   a_to_b_transport.set_ingress(b_ingress);
169
170   // Both RpcSocketTransport and LocalRpcEgress are ThreadCore's and
171   // need to be started in order for packet processing to start.
172   DetachedThread(/*...*/, a_to_b_transport);
173   DetachedThread(/*...*/, local_egress);
174
175Node B setup is the most complicated since it needs to deal with egress
176and ingress from both A and B and needs to support two kinds of transports. Note
177that A is unaware of which transport and framing B is using when talking to C:
178
179.. code-block:: cpp
180
181   // This is the server counterpart to A's client socket.
182   SocketRpcTransport<kSocketReadBufferSize> b_to_a_transport(
183     SocketRpcTransport<kSocketReadBufferSize>::kAsServer, "localhost",
184     kNodeBPortNumber);
185
186   SharedMemoryRpcTransport b_to_c_transport(/*...*/);
187
188   LocalRpcEgress<kLocalEgressQueueSize, kMaxPacketSize> local_egress;
189   HdlcRpcEgress<kMaxPacketSize> b_to_a_egress("b->a", b_to_a_transport);
190   // SimpleRpcEgress applies a very simple length-prefixed framing to B->C
191   // traffic (because HDLC adds unnecessary overhead over shared memory).
192   SimpleRpcEgress<kMaxPacketSize> b_to_c_egress("b->c", b_to_c_transport);
193
194   // List of channels for all packets originated locally at B (note that in
195   // this example B doesn't need to talk to C directly; it only proxies for A).
196   std::array tx_channels = {
197     Channel::Create<kChannelAB>(&b_to_a_egress),
198   };
199
200   // Here we list all egresses for the packets _incoming_ from A.
201   std::array a_rx_channels = {
202     ChannelEgress{kChannelAB, local_egress},
203     ChannelEgress{kChannelAC, b_to_c_egress},
204   };
205
206   // Here we list all egresses for the packets _incoming_ from C.
207   std::array c_rx_channels = {
208     ChannelEgress{kChannelAC, b_to_a_egress},
209   };
210
211   HdlcRpcIngress<kMaxPacketSize> b_ingress(b_rx_channels);
212   SimpleRpcIngress<kMaxPacketSize> c_ingress(c_rx_channels);
213
214   ServiceRegistry registry(tx_channels);
215   local_egress.set_packet_processor(registry);
216
217   b_to_a_transport.set_ingress(a_ingress);
218   b_to_c_transport.set_ingress(c_ingress);
219
220   DetachedThread({}, b_to_a_transport);
221   DetachedThread({}, b_to_c_transport);
222   DetachedThread({}, local_egress);
223
224Node C setup is straightforward since it only needs to handle ingress from B:
225
226.. code-block:: cpp
227
228   SharedMemoryRpcTransport c_to_b_transport(/*...*/);
229   LocalRpcEgress<kLocalEgressQueueSize, kMaxPacketSize> local_egress;
230   SimpleRpcEgress<kMaxPacketSize> c_to_b_egress("c->b", c_to_b_transport);
231
232   std::array tx_channels = {
233     Channel::Create<kChannelAC>(&c_to_b_egress),
234   };
235
236   // Here we list all egresses for the packets _incoming_ from B.
237   std::array b_rx_channels = {
238     ChannelEgress{kChannelAC, local_egress},
239   };
240
241   SimpleRpcIngress<kMaxPacketSize> b_ingress(b_rx_channels);
242
243   ServiceRegistry registry(tx_channels);
244   local_egress.set_packet_processor(registry);
245
246   c_to_b_transport.set_ingress(b_ingress);
247
248   DetachedThread(/*...*/, c_to_b_transport);
249   DetachedThread(/*...*/, local_egress);
250