Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 107 additions & 1 deletion embassy-net/src/icmp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub use smoltcp::wire::{Icmpv4Message, Icmpv4Packet, Icmpv4Repr};
#[cfg(feature = "proto-ipv6")]
pub use smoltcp::wire::{Icmpv6Message, Icmpv6Packet, Icmpv6Repr};

use crate::Stack;
use crate::{Stack, TryError};

/// Error returned by [`IcmpSocket::bind`].
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
Expand Down Expand Up @@ -149,6 +149,21 @@ impl<'a> IcmpSocket<'a> {
poll_fn(|cx| self.poll_recv_from(buf, cx))
}

/// Receive a datagram.
///
/// This method will not wait for a datagram to be received.
///
/// If no datagram is available, this method will return `Err(TryError::WouldBlock)`.
///
/// Returns the number of bytes received and the remote endpoint.
pub fn try_recv_from(&self, buf: &mut [u8]) -> Result<(usize, IpAddress), TryError<RecvError>> {
self.with_mut(|s, _| match s.recv_slice(buf) {
Ok((n, meta)) => Ok((n, meta)),
Err(icmp::RecvError::Truncated) => Err(TryError::Other(RecvError::Truncated)),
Err(icmp::RecvError::Exhausted) => Err(TryError::WouldBlock),
})
}

/// Receive a datagram.
///
/// When no datagram is available, this method will return `Poll::Pending` and
Expand Down Expand Up @@ -192,6 +207,23 @@ impl<'a> IcmpSocket<'a> {
.await
}

/// Dequeue a packet received from a remote endpoint and calls the provided function with the
/// slice of the packet and the remote endpoint address.
///
/// This method will not wait for a datagram to be received.
///
/// If no datagram is available, this method will return `Err(TryError::WouldBlock)`.
pub fn try_recv_from_with<F, R>(&self, f: F) -> Result<R, TryError<RecvError>>
where
F: FnOnce((&[u8], IpAddress)) -> R,
{
self.with_mut(|s, _| match s.recv() {
Ok(x) => Ok(f(x)),
Err(icmp::RecvError::Exhausted) => Err(TryError::WouldBlock),
Err(icmp::RecvError::Truncated) => Err(TryError::Other(RecvError::Truncated)),
})
}

/// Wait until the socket becomes writable.
///
/// A socket becomes writable when there is space in the buffer, from initial memory or after
Expand Down Expand Up @@ -234,6 +266,40 @@ impl<'a> IcmpSocket<'a> {
poll_fn(move |cx| self.poll_send_to(buf, remote_endpoint, cx)).await
}

/// Send a datagram to the specified remote endpoint.
///
/// This method will not wait for the buffer to become free.
///
/// If the socket's send buffer is full, this method will return `Err(TryError::WouldBlock)`.
///
/// If the socket's send buffer is too small to fit `buf`, this method will return `Err(TryError::Other(SendError::PacketTooLarge))`
///
/// When the remote endpoint is not reachable, this method will return `Err(TryError::Other(SendError::NoRoute))`
pub fn try_send_to<T>(&self, buf: &[u8], remote_endpoint: T) -> Result<(), TryError<SendError>>
where
T: Into<IpAddress>,
{
let remote_endpoint: IpAddress = remote_endpoint.into();

// Check if packet can ever fit in the transmit buffer
if self.with(|s, _| s.payload_send_capacity() < buf.len()) {
return Err(TryError::Other(SendError::PacketTooLarge));
}

self.with_mut(|s, _| match s.send_slice(buf, remote_endpoint.into()) {
// Entire datagram has been sent
Ok(()) => Ok(()),
Err(icmp::SendError::BufferFull) => Err(TryError::WouldBlock),
Err(icmp::SendError::Unaddressable) => {
if s.is_open() {
Err(TryError::Other(SendError::NoRoute))
} else {
Err(TryError::Other(SendError::SocketNotBound))
}
}
})
}

/// Send a datagram to the specified remote endpoint.
///
/// When the datagram has been sent, this method will return `Poll::Ready(Ok())`.
Expand Down Expand Up @@ -302,6 +368,33 @@ impl<'a> IcmpSocket<'a> {
.await
}

/// Enqueue a packet to be sent to a given remote address with a zero-copy function.
///
/// This method will not wait for the buffer to become free.
///
/// If the socket's send buffer is full, this method will return `Err(TryError::WouldBlock)`.
///
/// If the socket's send buffer is too small to fit `size`, this method will return `Err(TryError::Other(SendError::PacketTooLarge))`
///
/// When the remote endpoint is not reachable, this method will return `Err(TryError::Other(SendError::NoRoute))`
pub fn try_send_to_with<T, F, R>(&mut self, size: usize, remote_endpoint: T, f: F) -> Result<R, TryError<SendError>>
where
T: Into<IpAddress>,
F: FnOnce(&mut [u8]) -> R,
{
// Check if packet can ever fit in the transmit buffer
if self.with(|s, _| s.payload_send_capacity() < size) {
return Err(TryError::Other(SendError::PacketTooLarge));
}

let remote_endpoint = remote_endpoint.into();
self.with_mut(|s, _| match s.send(size, remote_endpoint) {
Ok(buf) => Ok(f(buf)),
Err(icmp::SendError::BufferFull) => Err(TryError::WouldBlock),
Err(icmp::SendError::Unaddressable) => Err(TryError::Other(SendError::NoRoute)),
})
}

/// Flush the socket.
///
/// This method will wait until the socket is flushed.
Expand All @@ -318,6 +411,19 @@ impl<'a> IcmpSocket<'a> {
})
}

/// Try to flush the socket.
///
/// This method will check if the socket is flushed, and if not, return `Err(TryError::WouldBlock)`.
pub fn try_flush(&mut self) -> Result<(), TryError<SendError>> {
self.with(|s, _| {
if s.send_queue() == 0 {
Ok(())
} else {
Err(TryError::WouldBlock)
}
})
}

/// Check whether the socket is open.
pub fn is_open(&self) -> bool {
self.with(|s, _| s.is_open())
Expand Down
14 changes: 14 additions & 0 deletions embassy-net/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,20 @@ const MAX_QUERIES: usize = 4;
#[cfg(feature = "dhcpv4-hostname")]
const MAX_HOSTNAME_LEN: usize = 32;

/// Error returned by `try_*` socket methods.
///
/// `WouldBlock` indicates the operation would block (e.g. no data available,
/// send buffer full). `Other` wraps the socket-specific error type for any
/// other failure.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TryError<T> {
/// The operation would block; try again later.
WouldBlock,
/// A socket-specific error occurred.
Other(T),
}

/// Memory resources needed for a network stack.
pub struct StackResources<const SOCK: usize> {
sockets: MaybeUninit<[SocketStorage<'static>; SOCK]>,
Expand Down
42 changes: 40 additions & 2 deletions embassy-net/src/raw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ use smoltcp::socket::raw;
pub use smoltcp::socket::raw::PacketMetadata;
pub use smoltcp::wire::{IpProtocol, IpVersion};

use crate::Stack;
use crate::{Stack, TryError};

/// Error returned by [`RawSocket::recv`] and [`RawSocket::send`].
/// Error returned by [`RawSocket::recv`].
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum RecvError {
Expand Down Expand Up @@ -76,6 +76,19 @@ impl<'a> RawSocket<'a> {
poll_fn(move |cx| self.poll_recv(buf, cx)).await
}

/// Receive a datagram.
///
/// This method will not wait for a datagram to be received.
///
/// If no datagram is available, this method will return `Err(TryError::WouldBlock)`.
pub fn try_recv(&self, buf: &mut [u8]) -> Result<usize, TryError<RecvError>> {
self.with_mut(|s, _| match s.recv_slice(buf) {
Ok(n) => Ok(n),
Err(raw::RecvError::Truncated) => Err(TryError::Other(RecvError::Truncated)),
Err(raw::RecvError::Exhausted) => Err(TryError::WouldBlock),
})
}

/// Wait until a datagram can be read.
///
/// When no datagram is readable, this method will return `Poll::Pending` and
Expand Down Expand Up @@ -144,6 +157,18 @@ impl<'a> RawSocket<'a> {
poll_fn(|cx| self.poll_send(buf, cx))
}

/// Send a datagram.
///
/// This method will not wait for the buffer to become free.
///
/// If the socket's send buffer is full, this method will return `Err(TryError::WouldBlock)`.
pub fn try_send(&self, buf: &[u8]) -> Result<(), TryError<core::convert::Infallible>> {
self.with_mut(|s, _| match s.send_slice(buf) {
Ok(()) => Ok(()),
Err(raw::SendError::BufferFull) => Err(TryError::WouldBlock),
})
}

/// Send a datagram.
///
/// When the datagram has been sent, this method will return `Poll::Ready(Ok())`.
Expand Down Expand Up @@ -176,6 +201,19 @@ impl<'a> RawSocket<'a> {
})
})
}

/// Try to flush the socket.
///
/// This method will check if the socket is flushed, and if not, return `Err(TryError::WouldBlock)`.
pub fn try_flush(&mut self) -> Result<(), TryError<core::convert::Infallible>> {
self.with_mut(|s, _| {
if s.send_queue() == 0 {
Ok(())
} else {
Err(TryError::WouldBlock)
}
})
}
}

impl Drop for RawSocket<'_> {
Expand Down
Loading