Skip to content

Commit dd0c35c

Browse files
committed
async: Add UDP traits
Closes: rust-embedded-community#71
1 parent 471a49a commit dd0c35c

File tree

2 files changed

+200
-0
lines changed

2 files changed

+200
-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::{ConnectedUdp, BoundUdp, UnboundUdp, UdpStack};

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

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

0 commit comments

Comments
 (0)