|
| 1 | +//! UDP sockets usable through [embedded_nal_async] |
| 2 | +//! |
| 3 | +//! The full [embedded_nal_async::UdpStack] is *not* implemented at the moment: As its API allows |
| 4 | +//! arbitrary creation of movable sockets, embassy's [udp::UdpSocket] type could only be crated if |
| 5 | +//! the NAL stack had a pre-allocated pool of sockets with their respective buffers. Nothing rules |
| 6 | +//! out such a type, but at the moment, only the bound or connected socket types are implemented |
| 7 | +//! with their own constructors from an embassy [crate::Stack] -- for many applications, those are |
| 8 | +//! useful enough. (FIXME: Given we construct from Socket, Stack could really be implemented on |
| 9 | +//! `Cell<Option<Socket>>` by `.take()`ing, couldn't it?) |
| 10 | +//! |
| 11 | +//! The constructors of the various socket types mimick the UdpStack's socket creation functions, |
| 12 | +//! but take an owned (uninitialized) Socket instead of a shared stack. |
| 13 | +//! |
| 14 | +//! No `bind_single` style constructor is currently provided. FIXME: Not sure we have all the |
| 15 | +//! information at bind time to specialize a wildcard address into a concrete address and return |
| 16 | +//! it. Should the NAL trait be updated to disallow using wildcard addresses on `bind_single`, and |
| 17 | +//! merely allow unspecified ports to get an ephemeral one? |
| 18 | +
|
| 19 | +use core::future::poll_fn; |
| 20 | + |
| 21 | +use embedded_nal_async as nal; |
| 22 | +use smoltcp::wire::{IpAddress, IpEndpoint}; |
| 23 | + |
| 24 | +use embassy_net::udp; |
| 25 | + |
| 26 | +mod util; |
| 27 | +pub use util::Error; |
| 28 | +use util::{is_unspec_ip, sockaddr_nal2smol, sockaddr_smol2nal}; |
| 29 | + |
| 30 | +pub struct ConnectedUdp<'a> { |
| 31 | + remote: IpEndpoint, |
| 32 | + // The local port is stored in the socket, as it gets bound. This value is populated lazily: |
| 33 | + // embassy only decides at udp::Socket::dispatch time whence to send, and while we could |
| 34 | + // duplicate the code for the None case of the local_address by calling the right |
| 35 | + // get_source_address function, we'd still need an interface::Context / an interface to call |
| 36 | + // this through, and AFAICT we don't get access to that. |
| 37 | + local: Option<IpAddress>, |
| 38 | + socket: udp::UdpSocket<'a>, |
| 39 | +} |
| 40 | + |
| 41 | +/// A UDP socket that has been bound locally (either to a unique address or just to a port) |
| 42 | +/// |
| 43 | +/// Its operations are accessible through the [nal::UnconnectedUdp] trait. |
| 44 | +pub struct UnconnectedUdp<'a> { |
| 45 | + socket: udp::UdpSocket<'a>, |
| 46 | +} |
| 47 | + |
| 48 | +impl<'a> ConnectedUdp<'a> { |
| 49 | + /// Create a ConnectedUdp by assigning it a remote and a concrete local address |
| 50 | + /// |
| 51 | + /// ## Prerequisites |
| 52 | + /// |
| 53 | + /// The `socket` must be open (in the sense of smoltcp's `.is_open()`) -- unbound and |
| 54 | + /// unconnected. |
| 55 | + pub async fn connect_from( |
| 56 | + mut socket: udp::UdpSocket<'a>, |
| 57 | + local: nal::SocketAddr, |
| 58 | + remote: nal::SocketAddr, |
| 59 | + ) -> Result<Self, Error> { |
| 60 | + socket.bind(sockaddr_nal2smol(local)?)?; |
| 61 | + |
| 62 | + Ok(ConnectedUdp { |
| 63 | + remote: sockaddr_nal2smol(remote)?, |
| 64 | + // FIXME: We could check if local was fully (or sufficiently, picking the port from the |
| 65 | + // socket) specified and store if yes -- for a first iteration, leaving that to the |
| 66 | + // fallback path we need anyway in case local is [::]. |
| 67 | + local: None, |
| 68 | + socket, |
| 69 | + }) |
| 70 | + } |
| 71 | + |
| 72 | + /// Create a ConnectedUdp by assigning it a remote and a local address (the latter may happen |
| 73 | + /// lazily) |
| 74 | + /// |
| 75 | + /// ## Prerequisites |
| 76 | + /// |
| 77 | + /// The `socket` must be open (in the sense of smoltcp's `.is_open()`) -- unbound and |
| 78 | + /// unconnected. |
| 79 | + pub async fn connect(socket: udp::UdpSocket<'a> /*, ... */) -> Result<Self, udp::BindError> { |
| 80 | + // This is really just a copy of the provided `embedded_nal::udp::UdpStack::connect` method |
| 81 | + todo!() |
| 82 | + } |
| 83 | +} |
| 84 | + |
| 85 | +impl<'a> UnconnectedUdp<'a> { |
| 86 | + /// Create an UnconnectedUdp. |
| 87 | + /// |
| 88 | + /// The `local` address may be anything from fully specified (address and port) to fully |
| 89 | + /// unspecified (port 0, all-zeros address). |
| 90 | + /// |
| 91 | + /// ## Prerequisites |
| 92 | + /// |
| 93 | + /// The `socket` must be open (in the sense of smoltcp's `.is_open()`) -- unbound and |
| 94 | + /// unconnected. |
| 95 | + pub async fn bind_multiple(mut socket: udp::UdpSocket<'a>, local: nal::SocketAddr) -> Result<Self, Error> { |
| 96 | + socket.bind(sockaddr_nal2smol(local)?)?; |
| 97 | + |
| 98 | + Ok(UnconnectedUdp { socket }) |
| 99 | + } |
| 100 | +} |
| 101 | + |
| 102 | +impl<'a> nal::UnconnectedUdp for UnconnectedUdp<'a> { |
| 103 | + type Error = Error; |
| 104 | + async fn send( |
| 105 | + &mut self, |
| 106 | + local: embedded_nal_async::SocketAddr, |
| 107 | + remote: embedded_nal_async::SocketAddr, |
| 108 | + buf: &[u8], |
| 109 | + ) -> Result<(), Error> { |
| 110 | + // While the underlying layers probably don't care, we're not passing on the port |
| 111 | + // information, so the underlying layers won't even have a *chance* to care if we don't |
| 112 | + // check here. |
| 113 | + debug_assert!( |
| 114 | + local.port() == 0 || local.port() == self.socket.with(|s, _| s.endpoint().port), |
| 115 | + "Port of local address, when given, must match bound port." |
| 116 | + ); |
| 117 | + |
| 118 | + let remote_endpoint = smoltcp::socket::udp::UdpMetadata { |
| 119 | + local_address: if is_unspec_ip(local) { |
| 120 | + None |
| 121 | + } else { |
| 122 | + // A conversion of the addr part only might be cheaper, but would also mean we need |
| 123 | + // two functions |
| 124 | + Some(sockaddr_nal2smol(local)?.addr) |
| 125 | + }, |
| 126 | + ..sockaddr_nal2smol(remote)?.into() |
| 127 | + }; |
| 128 | + poll_fn(move |cx| self.socket.poll_send_to(buf, remote_endpoint, cx)).await?; |
| 129 | + Ok(()) |
| 130 | + } |
| 131 | + async fn receive_into( |
| 132 | + &mut self, |
| 133 | + buf: &mut [u8], |
| 134 | + ) -> Result<(usize, embedded_nal_async::SocketAddr, embedded_nal_async::SocketAddr), Error> { |
| 135 | + // FIXME: The truncation is an issue -- we may need to change poll_recv_from to poll_recv |
| 136 | + // and copy from the slice ourselves to get the trait's behavior |
| 137 | + let (size, metadata) = poll_fn(|cx| self.socket.poll_recv_from(buf, cx)).await?; |
| 138 | + Ok(( |
| 139 | + size, |
| 140 | + sockaddr_smol2nal(IpEndpoint { |
| 141 | + addr: metadata |
| 142 | + .local_address |
| 143 | + .expect("Local address is always populated on receive"), |
| 144 | + port: self.socket.with(|s, _| s.endpoint().port), |
| 145 | + }), |
| 146 | + sockaddr_smol2nal(metadata.endpoint), |
| 147 | + )) |
| 148 | + } |
| 149 | +} |
0 commit comments