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