Skip to content

Commit d3443f6

Browse files
committed
async: Add UDP traits
Closes: #71
1 parent 4b5038e commit d3443f6

File tree

2 files changed

+227
-0
lines changed

2 files changed

+227
-0
lines changed

embedded-nal-async/src/stack/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
mod tcp;
2+
mod udp;
23

34
pub use tcp::TcpConnect;
5+
pub use udp::{BoundUdp, ConnectedUdp, UdpStack, UnboundUdp};

embedded-nal-async/src/stack/udp.rs

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
use core::future::Future;
2+
use no_std_net::SocketAddr;
3+
4+
/// This trait is implemented by UDP sockets.
5+
///
6+
/// The socket it represents is both bound (has a local IP address, port and interface) and
7+
/// connected (has a remote IP address and port).
8+
///
9+
/// The term "connected" here refers to the semantics of POSIX datagram sockets, through which datagrams
10+
/// are sent and received without having a remote address per call. It does not imply any process
11+
/// of establishing a connection (which is absent in UDP). While there is typically no POSIX
12+
/// `bind()` call in the creation of such sockets, these are implicitly bound to a suitable local
13+
/// address at connect time.
14+
pub trait ConnectedUdp {
15+
type Error: embedded_io::Error;
16+
17+
/// Send the provided data to the connected peer
18+
fn send(&mut self, data: &[u8]) -> Self::SendFuture<'_>;
19+
type SendFuture<'a>: Future<Output = Result<(), Self::Error>>
20+
where
21+
Self: 'a;
22+
23+
/// Receive a datagram into the provided buffer.
24+
///
25+
/// If the received datagram exceeds the buffer's length, it is received regardless, and the
26+
/// remaining bytes are discarded. The full datagram size is still indicated in the result,
27+
/// allowing the recipient to detect that truncation.
28+
///
29+
/// ## Compatibility note
30+
///
31+
/// This deviates from the sync/nb equivalent trait in that it describes the overflow behavior
32+
/// (a possibility not considered there). The name deviates from the original `receive()` to
33+
/// make room for a version that is more zero-copy friendly.
34+
fn receive_into(&mut self, buffer: &mut [u8]) -> Self::ReceiveIntoFuture<'_>;
35+
type ReceiveIntoFuture<'a>: Future<Output = Result<usize, Self::Error>>
36+
where
37+
Self: 'a;
38+
39+
// WIP to allow zero-copy operation
40+
// The plain receive is simple and can be provided -- implementations that don't populate
41+
// receive calls from scatter-gather can just return a slice of the raw data instead, and rely
42+
// on the socket still being exclusively owned. receive_oned is harder as providing it requires
43+
// alloc.
44+
//
45+
// fn receive(&mut self, buffer: &mut [u8]) -> impl Future<Output = Result<impl AsRef<u8> + '_, Self::Error>>;
46+
// fn receive_owned(&mut self) -> impl Future<Output = Result<impl AsRef<u8> + 'static, Self::Error>>;
47+
}
48+
49+
/// This trait is implemented by UDP sockets.
50+
///
51+
/// The socket it represents is both bound (has a local IP address, port and interface) but not
52+
/// connected; its peer IP address is explicit in every call.
53+
///
54+
/// This is similar to a POSIX datagram socket that has been bound to a concrete address.
55+
pub trait BoundUdp {
56+
type Error: embedded_io::Error;
57+
58+
/// Send the provided data to the connected peer
59+
fn send_to(&mut self, remote: SocketAddr, data: &[u8]) -> Self::SendToFuture<'_>;
60+
type SendToFuture<'a>: Future<Output = Result<(), Self::Error>>
61+
where
62+
Self: 'a;
63+
64+
/// Receive a datagram into the provided buffer.
65+
///
66+
/// If the received datagram exceeds the buffer's length, it is received regardless, and the
67+
/// remaining bytes are discarded. The full datagram size is still indicated in the result,
68+
/// allowing the recipient to detect that truncation.
69+
///
70+
/// The remote address is given in the result along with the number of bytes.
71+
///
72+
/// ## Compatibility note
73+
///
74+
/// This deviates from the sync/nb equivalent trait in that it describes the overflow behavior
75+
/// (a possibility not considered there). The name deviates from the original `receive()` to
76+
/// make room for a version that is more zero-copy friendly.
77+
fn receive_from_into(&mut self, buffer: &mut [u8]) -> Self::ReceiveFromIntoFuture<'_>;
78+
type ReceiveFromIntoFuture<'a>: Future<Output = Result<(usize, SocketAddr), Self::Error>>
79+
where
80+
Self: 'a;
81+
}
82+
83+
/// This trait is implemented by UDP sockets.
84+
///
85+
/// The socket it represents is neither bound (has no single local IP address, port and interface)
86+
/// nor connected (has no remote IP address and port). Both are explicitly given in every call.
87+
///
88+
/// There may be constraints placed on an unbound socket at creation time that limit the range of
89+
/// local addresses (further than the natural limitation of only using addresses assigned to the
90+
/// host).
91+
///
92+
/// A typical example of this kind of socket is a POSIX datagram socket that has been bound to
93+
/// "any" address (`[::]` or `0.0.0.0`) but to a particular port.
94+
pub trait UnboundUdp {
95+
type Error: embedded_io::Error;
96+
97+
/// Send the provided data to the connected peer
98+
///
99+
/// ## Sending initial messages
100+
///
101+
/// The local address can be left unspecified by leaving any of its component zero -- that
102+
/// gives the "any" address (`[::]` / `0.0.0.0`), the uncspecified port (0) or the unspecified
103+
/// zone identifier (0). Unless the operating system provides facilities exceeding this crate's traits for
104+
/// enumerating local interfaces and addresses, this is the only way to initiate outbound
105+
/// traffic.
106+
///
107+
/// ## Responding to messages
108+
///
109+
/// Users who have previously received data from a peer and want to respond have a choice of
110+
/// sending from the address to which the original datagram was addressed, or from an unbound
111+
/// address. Both are valid choices in some situations, and the right choice depends on the
112+
/// protocol used.
113+
fn send(&mut self, local: SocketAddr, remote: SocketAddr, data: &[u8]) -> Self::SendFuture<'_>;
114+
type SendFuture<'a>: Future<Output = Result<(), Self::Error>>
115+
where
116+
Self: 'a;
117+
118+
/// Receive a datagram into the provided buffer.
119+
///
120+
/// If the received datagram exceeds the buffer's length, it is received regardless, and the
121+
/// remaining bytes are discarded. The full datagram size is still indicated in the result,
122+
/// allowing the recipient to detect that truncation.
123+
///
124+
/// The local and remote address are given, in that order, in the result along with the number
125+
/// of bytes.
126+
fn receive(&mut self, buffer: &mut [u8]) -> Self::ReceiveFuture<'_>;
127+
type ReceiveFuture<'a>: Future<Output = Result<(usize, SocketAddr, SocketAddr), Self::Error>>
128+
where
129+
Self: 'a;
130+
}
131+
132+
/// This trait is implemented by UDP/IP stacks. The trait allows the underlying driver to
133+
/// construct multiple connections that implement the I/O traits from embedded-io.
134+
///
135+
/// Note that stacks with exotic connection creation methods may still not implement this, yet have
136+
/// objects that implement [`ConnectedUdp`] or similar.
137+
pub trait UdpStack {
138+
/// Error type returned on socket creation failure.
139+
type Error: embedded_io::Error;
140+
141+
type Connected<'m>: ConnectedUdp
142+
where
143+
Self: 'm;
144+
type Bound<'m>: BoundUdp
145+
where
146+
Self: 'm;
147+
type Unbound<'m>: UnboundUdp
148+
where
149+
Self: 'm;
150+
151+
/// Create a socket that has a fixed remote address.
152+
///
153+
/// The local address is chosen automatically.
154+
///
155+
/// While asynchronous traits implemented through GAT can not have provided default methods,
156+
/// implementers are encouraged to use the hidden `.connect_default()` method if all they would
157+
/// do is delegating to [`connect_from`] with a suitable unspecified local address.
158+
fn connect(&self, remote: SocketAddr) -> Self::ConnectFuture<'_>;
159+
type ConnectFuture<'a>: Future<Output = Result<(SocketAddr, Self::Connected<'a>), Self::Error>>
160+
where
161+
Self: 'a;
162+
163+
/// Create a socket that has a fixed remote address.
164+
///
165+
/// The local address is given explicitly, but may be partially unspecified; it is fixed by the
166+
/// network stack at connection time. The full local address is returned along with the
167+
/// connected socket, primarily for debugging purposes.
168+
fn connect_from(&self, local: SocketAddr, remote: SocketAddr) -> Self::ConnectFromFuture<'_>;
169+
type ConnectFromFuture<'a>: Future<
170+
Output = Result<(SocketAddr, Self::Connected<'a>), Self::Error>,
171+
> where
172+
Self: 'a;
173+
174+
/// Helper that implements [`connect()`] using [`connect_from()`].
175+
#[doc(hidden)]
176+
fn connect_default(&self, remote: SocketAddr) -> Self::ConnectFromFuture<'_> {
177+
use no_std_net::{Ipv4Addr, Ipv6Addr, SocketAddr::*, SocketAddrV4, SocketAddrV6};
178+
179+
let local = match remote {
180+
V4(_) => V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)),
181+
V6(_) => V6(SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, 0, 0, 0)),
182+
};
183+
self.connect_from(local, remote)
184+
}
185+
186+
/// Create a socket that has a fixed local address.
187+
///
188+
/// Note that the giving an unspecified address here is *not* the same as a POSIX `bind()` --
189+
/// if the underlying stack supports multiple local addresses, it will pick *one* of the
190+
/// applicable addresses, rather than binding to all of them.
191+
///
192+
/// The full local address is returned along with the bound socket; it may then be passed on to
193+
/// other protocols for advertising purposes.
194+
fn bind_single(&self, local: SocketAddr) -> Self::BindSingleFuture<'_>;
195+
type BindSingleFuture<'a>: Future<Output = Result<(SocketAddr, Self::Bound<'a>), Self::Error>>
196+
where
197+
Self: 'a;
198+
199+
/// Create a socket that has no single fixed local address.
200+
///
201+
/// The IP address part of the local address is typically left unspecified, and the port is
202+
/// given. There are use cases for other constellations, and this interface does not rule out
203+
/// that they can be used, but they are rare (e.g. using the same IP address on different
204+
/// network interfaces, and listening to datagrams arriving at any of them) or not well
205+
/// supported by operating systems (e.g., binding to all ports at the same is not possible on
206+
/// POSIX systems, where giving port 0 to a bind makes the OS pick *some* suitable port).
207+
///
208+
/// Caveats:
209+
///
210+
/// * There is currently no way to pass in a local address that has an unspecified address
211+
/// family (which would effectively create a single socket that servers both IPv4 and IPv6);
212+
/// it is not specified whether stacks that use V6MAPPED IPv4 addresses could simply used
213+
/// that mechanism.
214+
///
215+
/// * It is currently not specified whether this mechanism can be used to join multicast
216+
/// groups.
217+
///
218+
/// * There is currently no hybrid binding that allows emulating what POSIX systems do when
219+
/// binding to `[::]:0`, that is, picking some available port but then still leaving the
220+
/// interface and IP address unspecified.
221+
fn bind_multiple(&self, local: SocketAddr) -> Self::BindMultipleFuture<'_>;
222+
type BindMultipleFuture<'a>: Future<Output = Result<Self::Unbound<'a>, Self::Error>>
223+
where
224+
Self: 'a;
225+
}

0 commit comments

Comments
 (0)