diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2022-08-11 14:17:11 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-08-11 14:17:11 +0000 |
commit | 6ffca81a38d2c7f57da667ff49b4296c4eba78e2 (patch) | |
tree | 43b1ce2ccac5bda85e805a87ec1e71e259f0a970 /embassy-net | |
parent | 6cae87ee5da4e137fa3990b81cdb53aeebc676e0 (diff) | |
parent | ef473827a2beaca120f45fbe490f84a0be7d381d (diff) | |
download | embassy-6ffca81a38d2c7f57da667ff49b4296c4eba78e2.zip |
Merge #880
880: Add UDP socket support r=Dirbaio a=arturkow2000
Co-authored-by: Artur Kowalski <artur.kowalski@3mdeb.com>
Co-authored-by: Artur Kowalski <arturkow2000@gmail.com>
Diffstat (limited to 'embassy-net')
-rw-r--r-- | embassy-net/Cargo.toml | 1 | ||||
-rw-r--r-- | embassy-net/src/lib.rs | 5 | ||||
-rw-r--r-- | embassy-net/src/udp.rs | 157 |
3 files changed, 163 insertions, 0 deletions
diff --git a/embassy-net/Cargo.toml b/embassy-net/Cargo.toml index fface207..e4d8c2c2 100644 --- a/embassy-net/Cargo.toml +++ b/embassy-net/Cargo.toml @@ -18,6 +18,7 @@ std = [] defmt = ["dep:defmt", "smoltcp/defmt"] +udp = ["smoltcp/socket-udp"] tcp = ["smoltcp/socket-tcp"] dns = ["smoltcp/socket-dns"] dhcpv4 = ["medium-ethernet", "smoltcp/socket-dhcpv4"] diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs index 1c5ba103..83d36471 100644 --- a/embassy-net/src/lib.rs +++ b/embassy-net/src/lib.rs @@ -16,6 +16,9 @@ pub use stack::{Config, ConfigStrategy, Stack, StackResources}; #[cfg(feature = "tcp")] pub mod tcp; +#[cfg(feature = "udp")] +pub mod udp; + // smoltcp reexports pub use smoltcp::phy::{DeviceCapabilities, Medium}; pub use smoltcp::time::{Duration as SmolDuration, Instant as SmolInstant}; @@ -24,3 +27,5 @@ pub use smoltcp::wire::{EthernetAddress, HardwareAddress}; pub use smoltcp::wire::{IpAddress, IpCidr, Ipv4Address, Ipv4Cidr}; #[cfg(feature = "proto-ipv6")] pub use smoltcp::wire::{Ipv6Address, Ipv6Cidr}; +#[cfg(feature = "udp")] +pub use smoltcp::{socket::udp::PacketMetadata, wire::IpListenEndpoint}; diff --git a/embassy-net/src/udp.rs b/embassy-net/src/udp.rs new file mode 100644 index 00000000..78b09a49 --- /dev/null +++ b/embassy-net/src/udp.rs @@ -0,0 +1,157 @@ +use core::cell::UnsafeCell; +use core::mem; +use core::task::Poll; + +use futures::future::poll_fn; +use smoltcp::iface::{Interface, SocketHandle}; +use smoltcp::socket::udp::{self, PacketMetadata}; +use smoltcp::wire::{IpEndpoint, IpListenEndpoint}; + +use super::stack::SocketStack; +use crate::{Device, Stack}; + +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum BindError { + /// The socket was already open. + InvalidState, + /// No route to host. + NoRoute, +} + +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// No route to host. + NoRoute, +} + +pub struct UdpSocket<'a> { + stack: &'a UnsafeCell<SocketStack>, + handle: SocketHandle, +} + +impl<'a> UdpSocket<'a> { + pub fn new<D: Device>( + stack: &'a Stack<D>, + rx_meta: &'a mut [PacketMetadata], + rx_buffer: &'a mut [u8], + tx_meta: &'a mut [PacketMetadata], + tx_buffer: &'a mut [u8], + ) -> Self { + // safety: not accessed reentrantly. + let s = unsafe { &mut *stack.socket.get() }; + + let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) }; + let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; + let tx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(tx_meta) }; + let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) }; + let handle = s.sockets.add(udp::Socket::new( + udp::PacketBuffer::new(rx_meta, rx_buffer), + udp::PacketBuffer::new(tx_meta, tx_buffer), + )); + + Self { + stack: &stack.socket, + handle, + } + } + + pub fn bind<T>(&mut self, endpoint: T) -> Result<(), BindError> + where + T: Into<IpListenEndpoint>, + { + let mut endpoint = endpoint.into(); + + // safety: not accessed reentrantly. + if endpoint.port == 0 { + // If user didn't specify port allocate a dynamic port. + endpoint.port = unsafe { &mut *self.stack.get() }.get_local_port(); + } + + // safety: not accessed reentrantly. + match unsafe { self.with_mut(|s, _| s.bind(endpoint)) } { + Ok(()) => Ok(()), + Err(udp::BindError::InvalidState) => Err(BindError::InvalidState), + Err(udp::BindError::Unaddressable) => Err(BindError::NoRoute), + } + } + + /// SAFETY: must not call reentrantly. + unsafe fn with<R>(&self, f: impl FnOnce(&udp::Socket, &Interface) -> R) -> R { + let s = &*self.stack.get(); + let socket = s.sockets.get::<udp::Socket>(self.handle); + f(socket, &s.iface) + } + + /// SAFETY: must not call reentrantly. + unsafe fn with_mut<R>(&self, f: impl FnOnce(&mut udp::Socket, &mut Interface) -> R) -> R { + let s = &mut *self.stack.get(); + let socket = s.sockets.get_mut::<udp::Socket>(self.handle); + let res = f(socket, &mut s.iface); + s.waker.wake(); + res + } + + pub async fn recv_from(&self, buf: &mut [u8]) -> Result<(usize, IpEndpoint), Error> { + poll_fn(move |cx| unsafe { + self.with_mut(|s, _| match s.recv_slice(buf) { + Ok(x) => Poll::Ready(Ok(x)), + // No data ready + Err(udp::RecvError::Exhausted) => { + //s.register_recv_waker(cx.waker()); + cx.waker().wake_by_ref(); + Poll::Pending + } + }) + }) + .await + } + + pub async fn send_to<T>(&self, buf: &[u8], remote_endpoint: T) -> Result<(), Error> + where + T: Into<IpEndpoint>, + { + let remote_endpoint = remote_endpoint.into(); + poll_fn(move |cx| unsafe { + self.with_mut(|s, _| match s.send_slice(buf, remote_endpoint) { + // Entire datagram has been sent + Ok(()) => Poll::Ready(Ok(())), + Err(udp::SendError::BufferFull) => { + s.register_send_waker(cx.waker()); + Poll::Pending + } + Err(udp::SendError::Unaddressable) => Poll::Ready(Err(Error::NoRoute)), + }) + }) + .await + } + + pub fn endpoint(&self) -> IpListenEndpoint { + unsafe { self.with(|s, _| s.endpoint()) } + } + + pub fn is_open(&self) -> bool { + unsafe { self.with(|s, _| s.is_open()) } + } + + pub fn close(&mut self) { + unsafe { self.with_mut(|s, _| s.close()) } + } + + pub fn may_send(&self) -> bool { + unsafe { self.with(|s, _| s.can_send()) } + } + + pub fn may_recv(&self) -> bool { + unsafe { self.with(|s, _| s.can_recv()) } + } +} + +impl Drop for UdpSocket<'_> { + fn drop(&mut self) { + // safety: not accessed reentrantly. + let s = unsafe { &mut *self.stack.get() }; + s.sockets.remove(self.handle); + } +} |