From 4b5038e4c70702d8597e323657f8edc0cacc9311 Mon Sep 17 00:00:00 2001 From: chrysn Date: Mon, 15 Aug 2022 15:55:20 +0200 Subject: [PATCH 01/11] gitignore: Also ignore target inside nested crate --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6936990..45fad16 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target +/embedded-nal-async/target **/*.rs.bk Cargo.lock From d3443f6a560379b7b78cd28f7e1b5fb7c380b0b1 Mon Sep 17 00:00:00 2001 From: chrysn Date: Mon, 15 Aug 2022 15:55:35 +0200 Subject: [PATCH 02/11] async: Add UDP traits Closes: https://github.com/rust-embedded-community/embedded-nal/issues/71 --- embedded-nal-async/src/stack/mod.rs | 2 + embedded-nal-async/src/stack/udp.rs | 225 ++++++++++++++++++++++++++++ 2 files changed, 227 insertions(+) create mode 100644 embedded-nal-async/src/stack/udp.rs diff --git a/embedded-nal-async/src/stack/mod.rs b/embedded-nal-async/src/stack/mod.rs index 92d6486..08ad0d5 100644 --- a/embedded-nal-async/src/stack/mod.rs +++ b/embedded-nal-async/src/stack/mod.rs @@ -1,3 +1,5 @@ mod tcp; +mod udp; pub use tcp::TcpConnect; +pub use udp::{BoundUdp, ConnectedUdp, UdpStack, UnboundUdp}; diff --git a/embedded-nal-async/src/stack/udp.rs b/embedded-nal-async/src/stack/udp.rs new file mode 100644 index 0000000..0079364 --- /dev/null +++ b/embedded-nal-async/src/stack/udp.rs @@ -0,0 +1,225 @@ +use core::future::Future; +use no_std_net::SocketAddr; + +/// This trait is implemented by UDP sockets. +/// +/// The socket it represents is both bound (has a local IP address, port and interface) and +/// connected (has a remote IP address and port). +/// +/// The term "connected" here refers to the semantics of POSIX datagram sockets, through which datagrams +/// are sent and received without having a remote address per call. It does not imply any process +/// of establishing a connection (which is absent in UDP). While there is typically no POSIX +/// `bind()` call in the creation of such sockets, these are implicitly bound to a suitable local +/// address at connect time. +pub trait ConnectedUdp { + type Error: embedded_io::Error; + + /// Send the provided data to the connected peer + fn send(&mut self, data: &[u8]) -> Self::SendFuture<'_>; + type SendFuture<'a>: Future> + where + Self: 'a; + + /// Receive a datagram into the provided buffer. + /// + /// If the received datagram exceeds the buffer's length, it is received regardless, and the + /// remaining bytes are discarded. The full datagram size is still indicated in the result, + /// allowing the recipient to detect that truncation. + /// + /// ## Compatibility note + /// + /// This deviates from the sync/nb equivalent trait in that it describes the overflow behavior + /// (a possibility not considered there). The name deviates from the original `receive()` to + /// make room for a version that is more zero-copy friendly. + fn receive_into(&mut self, buffer: &mut [u8]) -> Self::ReceiveIntoFuture<'_>; + type ReceiveIntoFuture<'a>: Future> + where + Self: 'a; + + // WIP to allow zero-copy operation + // The plain receive is simple and can be provided -- implementations that don't populate + // receive calls from scatter-gather can just return a slice of the raw data instead, and rely + // on the socket still being exclusively owned. receive_oned is harder as providing it requires + // alloc. + // + // fn receive(&mut self, buffer: &mut [u8]) -> impl Future + '_, Self::Error>>; + // fn receive_owned(&mut self) -> impl Future + 'static, Self::Error>>; +} + +/// This trait is implemented by UDP sockets. +/// +/// The socket it represents is both bound (has a local IP address, port and interface) but not +/// connected; its peer IP address is explicit in every call. +/// +/// This is similar to a POSIX datagram socket that has been bound to a concrete address. +pub trait BoundUdp { + type Error: embedded_io::Error; + + /// Send the provided data to the connected peer + fn send_to(&mut self, remote: SocketAddr, data: &[u8]) -> Self::SendToFuture<'_>; + type SendToFuture<'a>: Future> + where + Self: 'a; + + /// Receive a datagram into the provided buffer. + /// + /// If the received datagram exceeds the buffer's length, it is received regardless, and the + /// remaining bytes are discarded. The full datagram size is still indicated in the result, + /// allowing the recipient to detect that truncation. + /// + /// The remote address is given in the result along with the number of bytes. + /// + /// ## Compatibility note + /// + /// This deviates from the sync/nb equivalent trait in that it describes the overflow behavior + /// (a possibility not considered there). The name deviates from the original `receive()` to + /// make room for a version that is more zero-copy friendly. + fn receive_from_into(&mut self, buffer: &mut [u8]) -> Self::ReceiveFromIntoFuture<'_>; + type ReceiveFromIntoFuture<'a>: Future> + where + Self: 'a; +} + +/// This trait is implemented by UDP sockets. +/// +/// The socket it represents is neither bound (has no single local IP address, port and interface) +/// nor connected (has no remote IP address and port). Both are explicitly given in every call. +/// +/// There may be constraints placed on an unbound socket at creation time that limit the range of +/// local addresses (further than the natural limitation of only using addresses assigned to the +/// host). +/// +/// A typical example of this kind of socket is a POSIX datagram socket that has been bound to +/// "any" address (`[::]` or `0.0.0.0`) but to a particular port. +pub trait UnboundUdp { + type Error: embedded_io::Error; + + /// Send the provided data to the connected peer + /// + /// ## Sending initial messages + /// + /// The local address can be left unspecified by leaving any of its component zero -- that + /// gives the "any" address (`[::]` / `0.0.0.0`), the uncspecified port (0) or the unspecified + /// zone identifier (0). Unless the operating system provides facilities exceeding this crate's traits for + /// enumerating local interfaces and addresses, this is the only way to initiate outbound + /// traffic. + /// + /// ## Responding to messages + /// + /// Users who have previously received data from a peer and want to respond have a choice of + /// sending from the address to which the original datagram was addressed, or from an unbound + /// address. Both are valid choices in some situations, and the right choice depends on the + /// protocol used. + fn send(&mut self, local: SocketAddr, remote: SocketAddr, data: &[u8]) -> Self::SendFuture<'_>; + type SendFuture<'a>: Future> + where + Self: 'a; + + /// Receive a datagram into the provided buffer. + /// + /// If the received datagram exceeds the buffer's length, it is received regardless, and the + /// remaining bytes are discarded. The full datagram size is still indicated in the result, + /// allowing the recipient to detect that truncation. + /// + /// The local and remote address are given, in that order, in the result along with the number + /// of bytes. + fn receive(&mut self, buffer: &mut [u8]) -> Self::ReceiveFuture<'_>; + type ReceiveFuture<'a>: Future> + where + Self: 'a; +} + +/// This trait is implemented by UDP/IP stacks. The trait allows the underlying driver to +/// construct multiple connections that implement the I/O traits from embedded-io. +/// +/// Note that stacks with exotic connection creation methods may still not implement this, yet have +/// objects that implement [`ConnectedUdp`] or similar. +pub trait UdpStack { + /// Error type returned on socket creation failure. + type Error: embedded_io::Error; + + type Connected<'m>: ConnectedUdp + where + Self: 'm; + type Bound<'m>: BoundUdp + where + Self: 'm; + type Unbound<'m>: UnboundUdp + where + Self: 'm; + + /// Create a socket that has a fixed remote address. + /// + /// The local address is chosen automatically. + /// + /// While asynchronous traits implemented through GAT can not have provided default methods, + /// implementers are encouraged to use the hidden `.connect_default()` method if all they would + /// do is delegating to [`connect_from`] with a suitable unspecified local address. + fn connect(&self, remote: SocketAddr) -> Self::ConnectFuture<'_>; + type ConnectFuture<'a>: Future), Self::Error>> + where + Self: 'a; + + /// Create a socket that has a fixed remote address. + /// + /// The local address is given explicitly, but may be partially unspecified; it is fixed by the + /// network stack at connection time. The full local address is returned along with the + /// connected socket, primarily for debugging purposes. + fn connect_from(&self, local: SocketAddr, remote: SocketAddr) -> Self::ConnectFromFuture<'_>; + type ConnectFromFuture<'a>: Future< + Output = Result<(SocketAddr, Self::Connected<'a>), Self::Error>, + > where + Self: 'a; + + /// Helper that implements [`connect()`] using [`connect_from()`]. + #[doc(hidden)] + fn connect_default(&self, remote: SocketAddr) -> Self::ConnectFromFuture<'_> { + use no_std_net::{Ipv4Addr, Ipv6Addr, SocketAddr::*, SocketAddrV4, SocketAddrV6}; + + let local = match remote { + V4(_) => V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)), + V6(_) => V6(SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, 0, 0, 0)), + }; + self.connect_from(local, remote) + } + + /// Create a socket that has a fixed local address. + /// + /// Note that the giving an unspecified address here is *not* the same as a POSIX `bind()` -- + /// if the underlying stack supports multiple local addresses, it will pick *one* of the + /// applicable addresses, rather than binding to all of them. + /// + /// The full local address is returned along with the bound socket; it may then be passed on to + /// other protocols for advertising purposes. + fn bind_single(&self, local: SocketAddr) -> Self::BindSingleFuture<'_>; + type BindSingleFuture<'a>: Future), Self::Error>> + where + Self: 'a; + + /// Create a socket that has no single fixed local address. + /// + /// The IP address part of the local address is typically left unspecified, and the port is + /// given. There are use cases for other constellations, and this interface does not rule out + /// that they can be used, but they are rare (e.g. using the same IP address on different + /// network interfaces, and listening to datagrams arriving at any of them) or not well + /// supported by operating systems (e.g., binding to all ports at the same is not possible on + /// POSIX systems, where giving port 0 to a bind makes the OS pick *some* suitable port). + /// + /// Caveats: + /// + /// * There is currently no way to pass in a local address that has an unspecified address + /// family (which would effectively create a single socket that servers both IPv4 and IPv6); + /// it is not specified whether stacks that use V6MAPPED IPv4 addresses could simply used + /// that mechanism. + /// + /// * It is currently not specified whether this mechanism can be used to join multicast + /// groups. + /// + /// * There is currently no hybrid binding that allows emulating what POSIX systems do when + /// binding to `[::]:0`, that is, picking some available port but then still leaving the + /// interface and IP address unspecified. + fn bind_multiple(&self, local: SocketAddr) -> Self::BindMultipleFuture<'_>; + type BindMultipleFuture<'a>: Future, Self::Error>> + where + Self: 'a; +} From e52dd7eff5a4ef4af2855b1747f4b2219dd1a2c9 Mon Sep 17 00:00:00 2001 From: chrysn Date: Mon, 15 Aug 2022 17:00:11 +0200 Subject: [PATCH 03/11] async UDP: Simplify to 2 rather than 3 traits --- embedded-nal-async/src/stack/mod.rs | 2 +- embedded-nal-async/src/stack/udp.rs | 78 ++++++++++++----------------- 2 files changed, 33 insertions(+), 47 deletions(-) diff --git a/embedded-nal-async/src/stack/mod.rs b/embedded-nal-async/src/stack/mod.rs index 08ad0d5..65800db 100644 --- a/embedded-nal-async/src/stack/mod.rs +++ b/embedded-nal-async/src/stack/mod.rs @@ -2,4 +2,4 @@ mod tcp; mod udp; pub use tcp::TcpConnect; -pub use udp::{BoundUdp, ConnectedUdp, UdpStack, UnboundUdp}; +pub use udp::{ConnectedUdp, UdpStack, UnconnectedUdp}; diff --git a/embedded-nal-async/src/stack/udp.rs b/embedded-nal-async/src/stack/udp.rs index 0079364..314852f 100644 --- a/embedded-nal-async/src/stack/udp.rs +++ b/embedded-nal-async/src/stack/udp.rs @@ -1,3 +1,18 @@ +//! Traits for using UDP on embedded devices +//! +//! ## Notes for implementers +//! +//! * At several places, the APIs expect to provide a local address. Backends that can not obtain +//! it, such as some AT-command based stacks, may pretend to have performed some form of network address +//! translation, and present invalid addresses as the local address. +//! +//! * Implementing [`UdpStack::Bound`] and [`UdpStack::Unbound`] unconnected sockets separately +//! allows discarding the local addresses in the bound case. With LTO enabled, all the overhead +//! compared with a third trait variant between [ConnectedUdp] and [UnconnectedUdp] (in which the +//! local address is static but the remote address is flexible) should optimized out. +//! Implementing `Bound` and `Unbound` with the same type is expected to be a common choice. + use core::future::Future; use no_std_net::SocketAddr; @@ -48,53 +63,17 @@ pub trait ConnectedUdp { /// This trait is implemented by UDP sockets. /// -/// The socket it represents is both bound (has a local IP address, port and interface) but not -/// connected; its peer IP address is explicit in every call. +/// The socket it represents is not necessarily bound (may not have a single local IP address, port +/// and interface), and is typically not connected (has no remote IP address and port). Both are +/// addresses are explicitly given in every call. /// -/// This is similar to a POSIX datagram socket that has been bound to a concrete address. -pub trait BoundUdp { +/// If there were constraints in place at socket creation time (typically on the local side), the +/// caller MUST pass in the same (or compatible) values, MAY and pass in unspecified values where +/// applicable. The implementer MAY check them for compatibility, and SHOULD do that in debug mode. +pub trait UnconnectedUdp { type Error: embedded_io::Error; - /// Send the provided data to the connected peer - fn send_to(&mut self, remote: SocketAddr, data: &[u8]) -> Self::SendToFuture<'_>; - type SendToFuture<'a>: Future> - where - Self: 'a; - - /// Receive a datagram into the provided buffer. - /// - /// If the received datagram exceeds the buffer's length, it is received regardless, and the - /// remaining bytes are discarded. The full datagram size is still indicated in the result, - /// allowing the recipient to detect that truncation. - /// - /// The remote address is given in the result along with the number of bytes. - /// - /// ## Compatibility note - /// - /// This deviates from the sync/nb equivalent trait in that it describes the overflow behavior - /// (a possibility not considered there). The name deviates from the original `receive()` to - /// make room for a version that is more zero-copy friendly. - fn receive_from_into(&mut self, buffer: &mut [u8]) -> Self::ReceiveFromIntoFuture<'_>; - type ReceiveFromIntoFuture<'a>: Future> - where - Self: 'a; -} - -/// This trait is implemented by UDP sockets. -/// -/// The socket it represents is neither bound (has no single local IP address, port and interface) -/// nor connected (has no remote IP address and port). Both are explicitly given in every call. -/// -/// There may be constraints placed on an unbound socket at creation time that limit the range of -/// local addresses (further than the natural limitation of only using addresses assigned to the -/// host). -/// -/// A typical example of this kind of socket is a POSIX datagram socket that has been bound to -/// "any" address (`[::]` or `0.0.0.0`) but to a particular port. -pub trait UnboundUdp { - type Error: embedded_io::Error; - - /// Send the provided data to the connected peer + /// Send the provided data to a peer /// /// ## Sending initial messages /// @@ -110,6 +89,13 @@ pub trait UnboundUdp { /// sending from the address to which the original datagram was addressed, or from an unbound /// address. Both are valid choices in some situations, and the right choice depends on the /// protocol used. + /// + /// Note that users of sockets created through [`UdpSocket::bind_single()`] should always pass + /// in that single address -- even though they've made their intention clear at construction. + /// They can pass either the one obtained at socket creation time, or the one obtained at + /// receive time; these should be equal. This allows implementations of the trait to use a + /// single kind of socket for both sockets bound to a single and sockets bound to multiple + /// addresses. fn send(&mut self, local: SocketAddr, remote: SocketAddr, data: &[u8]) -> Self::SendFuture<'_>; type SendFuture<'a>: Future> where @@ -141,10 +127,10 @@ pub trait UdpStack { type Connected<'m>: ConnectedUdp where Self: 'm; - type Bound<'m>: BoundUdp + type Bound<'m>: UnconnectedUdp where Self: 'm; - type Unbound<'m>: UnboundUdp + type Unbound<'m>: UnconnectedUdp where Self: 'm; From 3f716acdd1e7ee463666953eddf963e50eaf8a0c Mon Sep 17 00:00:00 2001 From: chrysn Date: Wed, 28 Sep 2022 20:55:14 +0200 Subject: [PATCH 04/11] async UDP: Documentation fixes; receive_into consistency One trait's receive_into still used the old receive name. --- embedded-nal-async/src/stack/udp.rs | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/embedded-nal-async/src/stack/udp.rs b/embedded-nal-async/src/stack/udp.rs index 314852f..f09ef9b 100644 --- a/embedded-nal-async/src/stack/udp.rs +++ b/embedded-nal-async/src/stack/udp.rs @@ -27,10 +27,12 @@ use no_std_net::SocketAddr; /// `bind()` call in the creation of such sockets, these are implicitly bound to a suitable local /// address at connect time. pub trait ConnectedUdp { + /// Error type returned by send and receive operations. type Error: embedded_io::Error; /// Send the provided data to the connected peer fn send(&mut self, data: &[u8]) -> Self::SendFuture<'_>; + /// Return type of the [`.send()`] method type SendFuture<'a>: Future> where Self: 'a; @@ -47,6 +49,7 @@ pub trait ConnectedUdp { /// (a possibility not considered there). The name deviates from the original `receive()` to /// make room for a version that is more zero-copy friendly. fn receive_into(&mut self, buffer: &mut [u8]) -> Self::ReceiveIntoFuture<'_>; + /// Return type of the [`.receive_into()`] method type ReceiveIntoFuture<'a>: Future> where Self: 'a; @@ -71,6 +74,7 @@ pub trait ConnectedUdp { /// caller MUST pass in the same (or compatible) values, MAY and pass in unspecified values where /// applicable. The implementer MAY check them for compatibility, and SHOULD do that in debug mode. pub trait UnconnectedUdp { + /// Error type returned by send and receive operations. type Error: embedded_io::Error; /// Send the provided data to a peer @@ -90,13 +94,14 @@ pub trait UnconnectedUdp { /// address. Both are valid choices in some situations, and the right choice depends on the /// protocol used. /// - /// Note that users of sockets created through [`UdpSocket::bind_single()`] should always pass + /// Note that users of sockets created through [`UdpStack::bind_single()`] should always pass /// in that single address -- even though they've made their intention clear at construction. /// They can pass either the one obtained at socket creation time, or the one obtained at /// receive time; these should be equal. This allows implementations of the trait to use a /// single kind of socket for both sockets bound to a single and sockets bound to multiple /// addresses. fn send(&mut self, local: SocketAddr, remote: SocketAddr, data: &[u8]) -> Self::SendFuture<'_>; + /// Return type of the [`.send()`] method type SendFuture<'a>: Future> where Self: 'a; @@ -109,9 +114,11 @@ pub trait UnconnectedUdp { /// /// The local and remote address are given, in that order, in the result along with the number /// of bytes. - fn receive(&mut self, buffer: &mut [u8]) -> Self::ReceiveFuture<'_>; - type ReceiveFuture<'a>: Future> - where + fn receive_into(&mut self, buffer: &mut [u8]) -> Self::ReceiveIntoFuture<'_>; + /// Return type of the [`.receive_into()`] method + type ReceiveIntoFuture<'a>: Future< + Output = Result<(usize, SocketAddr, SocketAddr), Self::Error>, + > where Self: 'a; } @@ -124,12 +131,15 @@ pub trait UdpStack { /// Error type returned on socket creation failure. type Error: embedded_io::Error; + /// Eventual socket return type of the [`.connect()`] method type Connected<'m>: ConnectedUdp where Self: 'm; + /// Eventual socket return type of the [`.bind_single()`] method type Bound<'m>: UnconnectedUdp where Self: 'm; + /// Eventual return type of the [`.bind_multiple()`] method type Unbound<'m>: UnconnectedUdp where Self: 'm; @@ -140,8 +150,9 @@ pub trait UdpStack { /// /// While asynchronous traits implemented through GAT can not have provided default methods, /// implementers are encouraged to use the hidden `.connect_default()` method if all they would - /// do is delegating to [`connect_from`] with a suitable unspecified local address. + /// do is delegating to [`.connect_from`] with a suitable unspecified local address. fn connect(&self, remote: SocketAddr) -> Self::ConnectFuture<'_>; + /// Future return type of the [`.connect()`] method type ConnectFuture<'a>: Future), Self::Error>> where Self: 'a; @@ -152,6 +163,7 @@ pub trait UdpStack { /// network stack at connection time. The full local address is returned along with the /// connected socket, primarily for debugging purposes. fn connect_from(&self, local: SocketAddr, remote: SocketAddr) -> Self::ConnectFromFuture<'_>; + /// Future return type of the [`.connect_from()`] method type ConnectFromFuture<'a>: Future< Output = Result<(SocketAddr, Self::Connected<'a>), Self::Error>, > where @@ -178,6 +190,7 @@ pub trait UdpStack { /// The full local address is returned along with the bound socket; it may then be passed on to /// other protocols for advertising purposes. fn bind_single(&self, local: SocketAddr) -> Self::BindSingleFuture<'_>; + /// Future return type of the [`.bind_single()`] method type BindSingleFuture<'a>: Future), Self::Error>> where Self: 'a; @@ -205,6 +218,7 @@ pub trait UdpStack { /// binding to `[::]:0`, that is, picking some available port but then still leaving the /// interface and IP address unspecified. fn bind_multiple(&self, local: SocketAddr) -> Self::BindMultipleFuture<'_>; + /// Future return type of the [`.bind_multiple()`] method type BindMultipleFuture<'a>: Future, Self::Error>> where Self: 'a; From 776e6df3483398e3f8d568b15ec63869c7b90bc6 Mon Sep 17 00:00:00 2001 From: chrysn Date: Wed, 28 Sep 2022 22:12:48 +0200 Subject: [PATCH 05/11] async UDP: Make accessible through lib.rs --- embedded-nal-async/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/embedded-nal-async/src/lib.rs b/embedded-nal-async/src/lib.rs index fb3cbd5..8a1ace8 100644 --- a/embedded-nal-async/src/lib.rs +++ b/embedded-nal-async/src/lib.rs @@ -15,3 +15,4 @@ pub use no_std_net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, Socke pub use dns::Dns; pub use embedded_nal::AddrType; pub use stack::TcpConnect; +pub use stack::{ConnectedUdp, UdpStack, UnconnectedUdp}; From f58f4fdfce3d3989d5aec10ae165ad5905be8bbc Mon Sep 17 00:00:00 2001 From: chrysn Date: Wed, 28 Sep 2022 22:13:35 +0200 Subject: [PATCH 06/11] async UDP: Fix lifetimes of send and receive functions Elided lifetimes would have asked too little of the data / buffer references. --- embedded-nal-async/src/stack/udp.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/embedded-nal-async/src/stack/udp.rs b/embedded-nal-async/src/stack/udp.rs index f09ef9b..18722cc 100644 --- a/embedded-nal-async/src/stack/udp.rs +++ b/embedded-nal-async/src/stack/udp.rs @@ -31,7 +31,7 @@ pub trait ConnectedUdp { type Error: embedded_io::Error; /// Send the provided data to the connected peer - fn send(&mut self, data: &[u8]) -> Self::SendFuture<'_>; + fn send<'a>(&'a mut self, data: &'a [u8]) -> Self::SendFuture<'a>; /// Return type of the [`.send()`] method type SendFuture<'a>: Future> where @@ -48,7 +48,7 @@ pub trait ConnectedUdp { /// This deviates from the sync/nb equivalent trait in that it describes the overflow behavior /// (a possibility not considered there). The name deviates from the original `receive()` to /// make room for a version that is more zero-copy friendly. - fn receive_into(&mut self, buffer: &mut [u8]) -> Self::ReceiveIntoFuture<'_>; + fn receive_into<'a>(&'a mut self, buffer: &'a mut [u8]) -> Self::ReceiveIntoFuture<'a>; /// Return type of the [`.receive_into()`] method type ReceiveIntoFuture<'a>: Future> where @@ -100,7 +100,12 @@ pub trait UnconnectedUdp { /// receive time; these should be equal. This allows implementations of the trait to use a /// single kind of socket for both sockets bound to a single and sockets bound to multiple /// addresses. - fn send(&mut self, local: SocketAddr, remote: SocketAddr, data: &[u8]) -> Self::SendFuture<'_>; + fn send<'a>( + &'a mut self, + local: SocketAddr, + remote: SocketAddr, + data: &'a [u8], + ) -> Self::SendFuture<'a>; /// Return type of the [`.send()`] method type SendFuture<'a>: Future> where @@ -114,7 +119,7 @@ pub trait UnconnectedUdp { /// /// The local and remote address are given, in that order, in the result along with the number /// of bytes. - fn receive_into(&mut self, buffer: &mut [u8]) -> Self::ReceiveIntoFuture<'_>; + fn receive_into<'a>(&'a mut self, buffer: &'a mut [u8]) -> Self::ReceiveIntoFuture<'a>; /// Return type of the [`.receive_into()`] method type ReceiveIntoFuture<'a>: Future< Output = Result<(usize, SocketAddr, SocketAddr), Self::Error>, From 0c2e98b42c1da92dbeeb53bde3a9bc9e4f67e1ef Mon Sep 17 00:00:00 2001 From: chrysn Date: Fri, 30 Sep 2022 12:34:49 +0200 Subject: [PATCH 07/11] async UDP: Better names for bound sockets --- embedded-nal-async/src/stack/udp.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/embedded-nal-async/src/stack/udp.rs b/embedded-nal-async/src/stack/udp.rs index 18722cc..fce05da 100644 --- a/embedded-nal-async/src/stack/udp.rs +++ b/embedded-nal-async/src/stack/udp.rs @@ -7,11 +7,12 @@ //! themselves UDP and --> may pretend to have performed some form of network address //! translation, and present invalid addresses as the local address. //! -//! * Implementing [`UdpStack::Bound`] and [`UdpStack::Unbound`] unconnected sockets separately -//! allows discarding the local addresses in the bound case. With LTO enabled, all the overhead -//! compared with a third trait variant between [ConnectedUdp] and [UnconnectedUdp] (in which the -//! local address is static but the remote address is flexible) should optimized out. -//! Implementing `Bound` and `Unbound` with the same type is expected to be a common choice. +//! * Implementing [`UdpStack::UniquelyBound`] and [`UdpStack::MultiplyBound`] unconnected sockets +//! separately allows discarding the local addresses in the bound case. With LTO enabled, all the +//! overhead compared with a third trait variant between [ConnectedUdp] and [UnconnectedUdp] (in +//! which the local address is static but the remote address is flexible) should optimized out. +//! Implementing `UniquelyBound` and `MultiplyBound` with the same type is expected to be a +//! common choice. use core::future::Future; use no_std_net::SocketAddr; @@ -141,11 +142,11 @@ pub trait UdpStack { where Self: 'm; /// Eventual socket return type of the [`.bind_single()`] method - type Bound<'m>: UnconnectedUdp + type UniquelyBound<'m>: UnconnectedUdp where Self: 'm; /// Eventual return type of the [`.bind_multiple()`] method - type Unbound<'m>: UnconnectedUdp + type MultiplyBound<'m>: UnconnectedUdp where Self: 'm; @@ -196,8 +197,9 @@ pub trait UdpStack { /// other protocols for advertising purposes. fn bind_single(&self, local: SocketAddr) -> Self::BindSingleFuture<'_>; /// Future return type of the [`.bind_single()`] method - type BindSingleFuture<'a>: Future), Self::Error>> - where + type BindSingleFuture<'a>: Future< + Output = Result<(SocketAddr, Self::UniquelyBound<'a>), Self::Error>, + > where Self: 'a; /// Create a socket that has no single fixed local address. @@ -224,7 +226,7 @@ pub trait UdpStack { /// interface and IP address unspecified. fn bind_multiple(&self, local: SocketAddr) -> Self::BindMultipleFuture<'_>; /// Future return type of the [`.bind_multiple()`] method - type BindMultipleFuture<'a>: Future, Self::Error>> + type BindMultipleFuture<'a>: Future, Self::Error>> where Self: 'a; } From 01a61fd06e8864020bbe3c93da5f7f61f1e6e11f Mon Sep 17 00:00:00 2001 From: chrysn Date: Fri, 18 Nov 2022 12:28:31 +0100 Subject: [PATCH 08/11] async: GATs have stabilized --- embedded-nal-async/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/embedded-nal-async/src/lib.rs b/embedded-nal-async/src/lib.rs index 8a1ace8..78bfc02 100644 --- a/embedded-nal-async/src/lib.rs +++ b/embedded-nal-async/src/lib.rs @@ -1,7 +1,6 @@ //! # embedded-nal-async - An async Network Abstraction Layer for Embedded Systems #![no_std] -#![feature(generic_associated_types)] #![deny(missing_docs)] #![deny(unsafe_code)] From 98fb223970b7ba55110eef72520c8751df7c53ae Mon Sep 17 00:00:00 2001 From: chrysn Date: Fri, 18 Nov 2022 12:50:29 +0100 Subject: [PATCH 09/11] UDP: use async-fn-in-trait instead of associated types that are Future This removes the lifetimes from the Connect associated types; leaving them would cause errors (possibly related to [12681]), but I think that stacks whose sockets are not 'static can work around this by implementing the stack traits on a &'short stack instead. [12681]: https://github.com/rust-lang/rust/issues/102681 --- embedded-nal-async/src/lib.rs | 1 + embedded-nal-async/src/stack/udp.rs | 79 +++++++---------------------- 2 files changed, 20 insertions(+), 60 deletions(-) diff --git a/embedded-nal-async/src/lib.rs b/embedded-nal-async/src/lib.rs index 78bfc02..a48fc7e 100644 --- a/embedded-nal-async/src/lib.rs +++ b/embedded-nal-async/src/lib.rs @@ -1,6 +1,7 @@ //! # embedded-nal-async - An async Network Abstraction Layer for Embedded Systems #![no_std] +#![feature(async_fn_in_trait)] #![deny(missing_docs)] #![deny(unsafe_code)] diff --git a/embedded-nal-async/src/stack/udp.rs b/embedded-nal-async/src/stack/udp.rs index fce05da..44a512a 100644 --- a/embedded-nal-async/src/stack/udp.rs +++ b/embedded-nal-async/src/stack/udp.rs @@ -14,7 +14,6 @@ //! Implementing `UniquelyBound` and `MultiplyBound` with the same type is expected to be a //! common choice. -use core::future::Future; use no_std_net::SocketAddr; /// This trait is implemented by UDP sockets. @@ -32,11 +31,7 @@ pub trait ConnectedUdp { type Error: embedded_io::Error; /// Send the provided data to the connected peer - fn send<'a>(&'a mut self, data: &'a [u8]) -> Self::SendFuture<'a>; - /// Return type of the [`.send()`] method - type SendFuture<'a>: Future> - where - Self: 'a; + async fn send<'a>(&mut self, data: &[u8]) -> Result<(), Self::Error>; /// Receive a datagram into the provided buffer. /// @@ -49,11 +44,7 @@ pub trait ConnectedUdp { /// This deviates from the sync/nb equivalent trait in that it describes the overflow behavior /// (a possibility not considered there). The name deviates from the original `receive()` to /// make room for a version that is more zero-copy friendly. - fn receive_into<'a>(&'a mut self, buffer: &'a mut [u8]) -> Self::ReceiveIntoFuture<'a>; - /// Return type of the [`.receive_into()`] method - type ReceiveIntoFuture<'a>: Future> - where - Self: 'a; + async fn receive_into(&mut self, buffer: &mut [u8]) -> Result; // WIP to allow zero-copy operation // The plain receive is simple and can be provided -- implementations that don't populate @@ -61,8 +52,8 @@ pub trait ConnectedUdp { // on the socket still being exclusively owned. receive_oned is harder as providing it requires // alloc. // - // fn receive(&mut self, buffer: &mut [u8]) -> impl Future + '_, Self::Error>>; - // fn receive_owned(&mut self) -> impl Future + 'static, Self::Error>>; + // async fn receive(&mut self, buffer: &mut [u8]) -> utput = Result + '_, Self::Error>; + // async fn receive_owned(&mut self) -> Result + 'static, Self::Error>; } /// This trait is implemented by UDP sockets. @@ -101,16 +92,12 @@ pub trait UnconnectedUdp { /// receive time; these should be equal. This allows implementations of the trait to use a /// single kind of socket for both sockets bound to a single and sockets bound to multiple /// addresses. - fn send<'a>( - &'a mut self, + async fn send( + &mut self, local: SocketAddr, remote: SocketAddr, - data: &'a [u8], - ) -> Self::SendFuture<'a>; - /// Return type of the [`.send()`] method - type SendFuture<'a>: Future> - where - Self: 'a; + data: &[u8], + ) -> Result<(), Self::Error>; /// Receive a datagram into the provided buffer. /// @@ -120,12 +107,7 @@ pub trait UnconnectedUdp { /// /// The local and remote address are given, in that order, in the result along with the number /// of bytes. - fn receive_into<'a>(&'a mut self, buffer: &'a mut [u8]) -> Self::ReceiveIntoFuture<'a>; - /// Return type of the [`.receive_into()`] method - type ReceiveIntoFuture<'a>: Future< - Output = Result<(usize, SocketAddr, SocketAddr), Self::Error>, - > where - Self: 'a; + async fn receive_into(& mut self, buffer: & mut [u8]) -> Result<(usize, SocketAddr, SocketAddr), Self::Error>; } /// This trait is implemented by UDP/IP stacks. The trait allows the underlying driver to @@ -138,17 +120,11 @@ pub trait UdpStack { type Error: embedded_io::Error; /// Eventual socket return type of the [`.connect()`] method - type Connected<'m>: ConnectedUdp - where - Self: 'm; + type Connected: ConnectedUdp; /// Eventual socket return type of the [`.bind_single()`] method - type UniquelyBound<'m>: UnconnectedUdp - where - Self: 'm; + type UniquelyBound: UnconnectedUdp; /// Eventual return type of the [`.bind_multiple()`] method - type MultiplyBound<'m>: UnconnectedUdp - where - Self: 'm; + type MultiplyBound: UnconnectedUdp; /// Create a socket that has a fixed remote address. /// @@ -157,34 +133,25 @@ pub trait UdpStack { /// While asynchronous traits implemented through GAT can not have provided default methods, /// implementers are encouraged to use the hidden `.connect_default()` method if all they would /// do is delegating to [`.connect_from`] with a suitable unspecified local address. - fn connect(&self, remote: SocketAddr) -> Self::ConnectFuture<'_>; - /// Future return type of the [`.connect()`] method - type ConnectFuture<'a>: Future), Self::Error>> - where - Self: 'a; + async fn connect(&self, remote: SocketAddr) -> Result<(SocketAddr, Self::Connected), Self::Error>; /// Create a socket that has a fixed remote address. /// /// The local address is given explicitly, but may be partially unspecified; it is fixed by the /// network stack at connection time. The full local address is returned along with the /// connected socket, primarily for debugging purposes. - fn connect_from(&self, local: SocketAddr, remote: SocketAddr) -> Self::ConnectFromFuture<'_>; - /// Future return type of the [`.connect_from()`] method - type ConnectFromFuture<'a>: Future< - Output = Result<(SocketAddr, Self::Connected<'a>), Self::Error>, - > where - Self: 'a; + async fn connect_from(&self, local: SocketAddr, remote: SocketAddr) -> Result<(SocketAddr, Self::Connected), Self::Error>; /// Helper that implements [`connect()`] using [`connect_from()`]. #[doc(hidden)] - fn connect_default(&self, remote: SocketAddr) -> Self::ConnectFromFuture<'_> { + async fn connect_default(&self, remote: SocketAddr) -> Result<(SocketAddr, Self::Connected), Self::Error> { use no_std_net::{Ipv4Addr, Ipv6Addr, SocketAddr::*, SocketAddrV4, SocketAddrV6}; let local = match remote { V4(_) => V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)), V6(_) => V6(SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, 0, 0, 0)), }; - self.connect_from(local, remote) + self.connect_from(local, remote).await } /// Create a socket that has a fixed local address. @@ -195,12 +162,7 @@ pub trait UdpStack { /// /// The full local address is returned along with the bound socket; it may then be passed on to /// other protocols for advertising purposes. - fn bind_single(&self, local: SocketAddr) -> Self::BindSingleFuture<'_>; - /// Future return type of the [`.bind_single()`] method - type BindSingleFuture<'a>: Future< - Output = Result<(SocketAddr, Self::UniquelyBound<'a>), Self::Error>, - > where - Self: 'a; + async fn bind_single(&self, local: SocketAddr) -> Result<(SocketAddr, Self::UniquelyBound), Self::Error>; /// Create a socket that has no single fixed local address. /// @@ -224,9 +186,6 @@ pub trait UdpStack { /// * There is currently no hybrid binding that allows emulating what POSIX systems do when /// binding to `[::]:0`, that is, picking some available port but then still leaving the /// interface and IP address unspecified. - fn bind_multiple(&self, local: SocketAddr) -> Self::BindMultipleFuture<'_>; - /// Future return type of the [`.bind_multiple()`] method - type BindMultipleFuture<'a>: Future, Self::Error>> - where - Self: 'a; + async fn bind_multiple(&self, local: SocketAddr) -> Result; + } From a6223f88c280982a201388b1f74af3c97f9437a9 Mon Sep 17 00:00:00 2001 From: chrysn Date: Fri, 18 Nov 2022 13:00:24 +0100 Subject: [PATCH 10/11] async/UDP: Remove connect_default helper The connect method can now be provided properly. --- embedded-nal-async/src/stack/udp.rs | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/embedded-nal-async/src/stack/udp.rs b/embedded-nal-async/src/stack/udp.rs index 44a512a..7eec596 100644 --- a/embedded-nal-async/src/stack/udp.rs +++ b/embedded-nal-async/src/stack/udp.rs @@ -130,21 +130,10 @@ pub trait UdpStack { /// /// The local address is chosen automatically. /// - /// While asynchronous traits implemented through GAT can not have provided default methods, - /// implementers are encouraged to use the hidden `.connect_default()` method if all they would - /// do is delegating to [`.connect_from`] with a suitable unspecified local address. - async fn connect(&self, remote: SocketAddr) -> Result<(SocketAddr, Self::Connected), Self::Error>; - - /// Create a socket that has a fixed remote address. - /// - /// The local address is given explicitly, but may be partially unspecified; it is fixed by the - /// network stack at connection time. The full local address is returned along with the - /// connected socket, primarily for debugging purposes. - async fn connect_from(&self, local: SocketAddr, remote: SocketAddr) -> Result<(SocketAddr, Self::Connected), Self::Error>; - - /// Helper that implements [`connect()`] using [`connect_from()`]. - #[doc(hidden)] - async fn connect_default(&self, remote: SocketAddr) -> Result<(SocketAddr, Self::Connected), Self::Error> { + /// There is a provided implementation that implements this from the maximally unspecified + /// local address and [`.connect_from()`], but may be provided more efficiently by + /// implementers. + async fn connect(&self, remote: SocketAddr) -> Result<(SocketAddr, Self::Connected), Self::Error> { use no_std_net::{Ipv4Addr, Ipv6Addr, SocketAddr::*, SocketAddrV4, SocketAddrV6}; let local = match remote { @@ -152,7 +141,14 @@ pub trait UdpStack { V6(_) => V6(SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, 0, 0, 0)), }; self.connect_from(local, remote).await - } + } + + /// Create a socket that has a fixed remote address. + /// + /// The local address is given explicitly, but may be partially unspecified; it is fixed by the + /// network stack at connection time. The full local address is returned along with the + /// connected socket, primarily for debugging purposes. + async fn connect_from(&self, local: SocketAddr, remote: SocketAddr) -> Result<(SocketAddr, Self::Connected), Self::Error>; /// Create a socket that has a fixed local address. /// From 5d9dd5a5aae59fa83d9bebc9a3c560fbe1e3fbd0 Mon Sep 17 00:00:00 2001 From: chrysn Date: Fri, 18 Nov 2022 13:23:22 +0100 Subject: [PATCH 11/11] fixup! UDP: use async-fn-in-trait instead of associated types that are Future --- embedded-nal-async/src/stack/udp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embedded-nal-async/src/stack/udp.rs b/embedded-nal-async/src/stack/udp.rs index 7eec596..93e6bfe 100644 --- a/embedded-nal-async/src/stack/udp.rs +++ b/embedded-nal-async/src/stack/udp.rs @@ -31,7 +31,7 @@ pub trait ConnectedUdp { type Error: embedded_io::Error; /// Send the provided data to the connected peer - async fn send<'a>(&mut self, data: &[u8]) -> Result<(), Self::Error>; + async fn send(&mut self, data: &[u8]) -> Result<(), Self::Error>; /// Receive a datagram into the provided buffer. ///