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