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