diff --git a/embassy-net/Cargo.toml b/embassy-net/Cargo.toml index fe9514e4..0a47c5d9 100644 --- a/embassy-net/Cargo.toml +++ b/embassy-net/Cargo.toml @@ -11,6 +11,9 @@ src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net/s features = ["nightly", "unstable-traits", "defmt", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "igmp"] target = "thumbv7em-none-eabi" +[package.metadata.docs.rs] +features = ["nightly", "unstable-traits", "defmt", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "igmp"] + [features] default = [] std = [] diff --git a/embassy-net/src/dns.rs b/embassy-net/src/dns.rs index 97320e44..3fd235b2 100644 --- a/embassy-net/src/dns.rs +++ b/embassy-net/src/dns.rs @@ -1,4 +1,9 @@ -//! DNS socket with async support. +//! DNS client compatible with the `embedded-nal-async` traits. +//! +//! This exists only for compatibility with crates that use `embedded-nal-async`. +//! Prefer using [`Stack::dns_query`](crate::Stack::dns_query) directly if you're +//! not using `embedded-nal-async`. + use heapless::Vec; pub use smoltcp::socket::dns::{DnsQuery, Socket}; pub(crate) use smoltcp::socket::dns::{GetQueryResultError, StartQueryError}; @@ -34,7 +39,11 @@ impl From for Error { } } -/// Async socket for making DNS queries. +/// DNS client compatible with the `embedded-nal-async` traits. +/// +/// This exists only for compatibility with crates that use `embedded-nal-async`. +/// Prefer using [`Stack::dns_query`](crate::Stack::dns_query) directly if you're +/// not using `embedded-nal-async`. pub struct DnsSocket<'a, D> where D: Driver + 'static, diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs index 572eda3b..9487c091 100644 --- a/embassy-net/src/lib.rs +++ b/embassy-net/src/lib.rs @@ -1,12 +1,12 @@ #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(feature = "nightly", feature(async_fn_in_trait, impl_trait_projections))] #![cfg_attr(feature = "nightly", allow(incomplete_features))] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; -pub use embassy_net_driver as driver; - mod device; #[cfg(feature = "dns")] pub mod dns; @@ -20,11 +20,14 @@ use core::cell::RefCell; use core::future::{poll_fn, Future}; use core::task::{Context, Poll}; +pub use embassy_net_driver as driver; use embassy_net_driver::{Driver, LinkState, Medium}; use embassy_sync::waitqueue::WakerRegistration; use embassy_time::{Instant, Timer}; use futures::pin_mut; use heapless::Vec; +#[cfg(feature = "igmp")] +pub use smoltcp::iface::MulticastError; use smoltcp::iface::{Interface, SocketHandle, SocketSet, SocketStorage}; #[cfg(feature = "dhcpv4")] use smoltcp::socket::dhcpv4::{self, RetryConfig}; @@ -44,6 +47,7 @@ const LOCAL_PORT_MAX: u16 = 65535; #[cfg(feature = "dns")] const MAX_QUERIES: usize = 4; +/// Memory resources needed for a network stack. pub struct StackResources { sockets: [SocketStorage<'static>; SOCK], #[cfg(feature = "dns")] @@ -51,6 +55,7 @@ pub struct StackResources { } impl StackResources { + /// Create a new set of stack resources. pub fn new() -> Self { #[cfg(feature = "dns")] const INIT: Option = None; @@ -62,23 +67,35 @@ impl StackResources { } } +/// Static IP address configuration. #[derive(Debug, Clone, PartialEq, Eq)] pub struct StaticConfig { + /// IP address and subnet mask. pub address: Ipv4Cidr, + /// Default gateway. pub gateway: Option, + /// DNS servers. pub dns_servers: Vec, } +/// DHCP configuration. #[cfg(feature = "dhcpv4")] #[derive(Debug, Clone, PartialEq, Eq)] pub struct DhcpConfig { + /// Maximum lease duration. + /// + /// If not set, the lease duration specified by the server will be used. + /// If set, the lease duration will be capped at this value. pub max_lease_duration: Option, + /// Retry configuration. pub retry_config: RetryConfig, - /// Ignore NAKs. + /// Ignore NAKs from DHCP servers. + /// + /// This is not compliant with the DHCP RFCs, since theoretically we must stop using the assigned IP when receiving a NAK. This can increase reliability on broken networks with buggy routers or rogue DHCP servers, however. pub ignore_naks: bool, - /// Server port config + /// Server port. This is almost always 67. Do not change unless you know what you're doing. pub server_port: u16, - /// Client port config + /// Client port. This is almost always 68. Do not change unless you know what you're doing. pub client_port: u16, } @@ -95,12 +112,18 @@ impl Default for DhcpConfig { } } +/// Network stack configuration. pub enum Config { + /// Use a static IP address configuration. Static(StaticConfig), + /// Use DHCP to obtain an IP address configuration. #[cfg(feature = "dhcpv4")] Dhcp(DhcpConfig), } +/// A network stack. +/// +/// This is the main entry point for the network stack. pub struct Stack { pub(crate) socket: RefCell, inner: RefCell>, @@ -126,6 +149,7 @@ pub(crate) struct SocketStack { } impl Stack { + /// Create a new network stack. pub fn new( mut device: D, config: Config, @@ -203,22 +227,30 @@ impl Stack { f(&mut *self.socket.borrow_mut(), &mut *self.inner.borrow_mut()) } + /// Get the MAC address of the network interface. pub fn ethernet_address(&self) -> [u8; 6] { self.with(|_s, i| i.device.ethernet_address()) } + /// Get whether the link is up. pub fn is_link_up(&self) -> bool { self.with(|_s, i| i.link_up) } + /// Get whether the network stack has a valid IP configuration. + /// This is true if the network stack has a static IP configuration or if DHCP has completed pub fn is_config_up(&self) -> bool { self.with(|_s, i| i.config.is_some()) } + /// Get the current IP configuration. pub fn config(&self) -> Option { self.with(|_s, i| i.config.clone()) } + /// Run the network stack. + /// + /// You must call this in a background task, to process network events. pub async fn run(&self) -> ! { poll_fn(|cx| { self.with_mut(|s, i| i.poll(cx, s)); @@ -301,7 +333,8 @@ impl Stack { #[cfg(feature = "igmp")] impl Stack { - pub fn join_multicast_group(&self, addr: T) -> Result + /// Join a multicast group. + pub fn join_multicast_group(&self, addr: T) -> Result where T: Into, { @@ -313,7 +346,8 @@ impl Stack { }) } - pub fn leave_multicast_group(&self, addr: T) -> Result + /// Leave a multicast group. + pub fn leave_multicast_group(&self, addr: T) -> Result where T: Into, { @@ -325,6 +359,7 @@ impl Stack { }) } + /// Get whether the network stack has joined the given multicast group. pub fn has_multicast_group>(&self, addr: T) -> bool { self.socket.borrow().iface.has_multicast_group(addr) } diff --git a/embassy-net/src/tcp.rs b/embassy-net/src/tcp.rs index 05b8bb54..732b6d21 100644 --- a/embassy-net/src/tcp.rs +++ b/embassy-net/src/tcp.rs @@ -1,3 +1,13 @@ +//! TCP sockets. +//! +//! # Listening +//! +//! `embassy-net` does not have a `TcpListener`. Instead, individual `TcpSocket`s can be put into +//! listening mode by calling [`TcpSocket::accept`]. +//! +//! Incoming connections when no socket is listening are rejected. To accept many incoming +//! connections, create many sockets and put them all into listening mode. + use core::cell::RefCell; use core::future::poll_fn; use core::mem; @@ -13,12 +23,17 @@ use smoltcp::wire::{IpEndpoint, IpListenEndpoint}; use crate::time::duration_to_smoltcp; use crate::{SocketStack, Stack}; +/// Error returned by TcpSocket read/write functions. #[derive(PartialEq, Eq, Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Error { + /// The connection was reset. + /// + /// This can happen on receiving a RST packet, or on timeout. ConnectionReset, } +/// Error returned by [`TcpSocket::connect`]. #[derive(PartialEq, Eq, Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ConnectError { @@ -32,6 +47,7 @@ pub enum ConnectError { NoRoute, } +/// Error returned by [`TcpSocket::accept`]. #[derive(PartialEq, Eq, Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum AcceptError { @@ -43,35 +59,50 @@ pub enum AcceptError { ConnectionReset, } +/// A TCP socket. pub struct TcpSocket<'a> { io: TcpIo<'a>, } +/// The reader half of a TCP socket. pub struct TcpReader<'a> { io: TcpIo<'a>, } +/// The writer half of a TCP socket. pub struct TcpWriter<'a> { io: TcpIo<'a>, } impl<'a> TcpReader<'a> { + /// Read data from the socket. + /// + /// Returns how many bytes were read, or an error. If no data is available, it waits + /// until there is at least one byte available. pub async fn read(&mut self, buf: &mut [u8]) -> Result { self.io.read(buf).await } } impl<'a> TcpWriter<'a> { + /// Write data to the socket. + /// + /// Returns how many bytes were written, or an error. If the socket is not ready to + /// accept data, it waits until it is. pub async fn write(&mut self, buf: &[u8]) -> Result { self.io.write(buf).await } + /// Flushes the written data to the socket. + /// + /// This waits until all data has been sent, and ACKed by the remote host. pub async fn flush(&mut self) -> Result<(), Error> { self.io.flush().await } } impl<'a> TcpSocket<'a> { + /// Create a new TCP socket on the given stack, with the given buffers. pub fn new(stack: &'a Stack, rx_buffer: &'a mut [u8], tx_buffer: &'a mut [u8]) -> Self { let s = &mut *stack.socket.borrow_mut(); let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; @@ -89,10 +120,12 @@ impl<'a> TcpSocket<'a> { } } + /// Split the socket into reader and a writer halves. pub fn split(&mut self) -> (TcpReader<'_>, TcpWriter<'_>) { (TcpReader { io: self.io }, TcpWriter { io: self.io }) } + /// Connect to a remote host. pub async fn connect(&mut self, remote_endpoint: T) -> Result<(), ConnectError> where T: Into, @@ -122,6 +155,9 @@ impl<'a> TcpSocket<'a> { .await } + /// Accept a connection from a remote host. + /// + /// This function puts the socket in listening mode, and waits until a connection is received. pub async fn accept(&mut self, local_endpoint: T) -> Result<(), AcceptError> where T: Into, @@ -144,56 +180,98 @@ impl<'a> TcpSocket<'a> { .await } + /// Read data from the socket. + /// + /// Returns how many bytes were read, or an error. If no data is available, it waits + /// until there is at least one byte available. pub async fn read(&mut self, buf: &mut [u8]) -> Result { self.io.read(buf).await } + /// Write data to the socket. + /// + /// Returns how many bytes were written, or an error. If the socket is not ready to + /// accept data, it waits until it is. pub async fn write(&mut self, buf: &[u8]) -> Result { self.io.write(buf).await } + /// Flushes the written data to the socket. + /// + /// This waits until all data has been sent, and ACKed by the remote host. pub async fn flush(&mut self) -> Result<(), Error> { self.io.flush().await } + /// Set the timeout for the socket. + /// + /// If the timeout is set, the socket will be closed if no data is received for the + /// specified duration. pub fn set_timeout(&mut self, duration: Option) { self.io .with_mut(|s, _| s.set_timeout(duration.map(duration_to_smoltcp))) } + /// Set the keep-alive interval for the socket. + /// + /// If the keep-alive interval is set, the socket will send keep-alive packets after + /// the specified duration of inactivity. + /// + /// If not set, the socket will not send keep-alive packets. pub fn set_keep_alive(&mut self, interval: Option) { self.io .with_mut(|s, _| s.set_keep_alive(interval.map(duration_to_smoltcp))) } + /// Set the hop limit field in the IP header of sent packets. pub fn set_hop_limit(&mut self, hop_limit: Option) { self.io.with_mut(|s, _| s.set_hop_limit(hop_limit)) } + /// Get the local endpoint of the socket. + /// + /// Returns `None` if the socket is not bound (listening) or not connected. pub fn local_endpoint(&self) -> Option { self.io.with(|s, _| s.local_endpoint()) } + /// Get the remote endpoint of the socket. + /// + /// Returns `None` if the socket is not connected. pub fn remote_endpoint(&self) -> Option { self.io.with(|s, _| s.remote_endpoint()) } + /// Get the state of the socket. pub fn state(&self) -> State { self.io.with(|s, _| s.state()) } + /// Close the write half of the socket. + /// + /// This closes only the write half of the socket. The read half side remains open, the + /// socket can still receive data. + /// + /// Data that has been written to the socket and not yet sent (or not yet ACKed) will still + /// still sent. The last segment of the pending to send data is sent with the FIN flag set. pub fn close(&mut self) { self.io.with_mut(|s, _| s.close()) } + /// Forcibly close the socket. + /// + /// This instantly closes both the read and write halves of the socket. Any pending data + /// that has not been sent will be lost. pub fn abort(&mut self) { self.io.with_mut(|s, _| s.abort()) } + /// Get whether the socket is ready to send data, i.e. whether there is space in the send buffer. pub fn may_send(&self) -> bool { self.io.with(|s, _| s.may_send()) } + /// Get whether the socket is ready to receive data, i.e. whether there is some pending data in the receive buffer. pub fn may_recv(&self) -> bool { self.io.with(|s, _| s.may_recv()) } @@ -345,6 +423,7 @@ mod embedded_io_impls { } } +/// TCP client compatible with `embedded-nal-async` traits. #[cfg(all(feature = "unstable-traits", feature = "nightly"))] pub mod client { use core::cell::UnsafeCell; @@ -356,14 +435,16 @@ pub mod client { use super::*; - /// TCP client capable of creating up to N multiple connections with tx and rx buffers according to TX_SZ and RX_SZ. + /// TCP client connection pool compatible with `embedded-nal-async` traits. + /// + /// The pool is capable of managing up to N concurrent connections with tx and rx buffers according to TX_SZ and RX_SZ. pub struct TcpClient<'d, D: Driver, const N: usize, const TX_SZ: usize = 1024, const RX_SZ: usize = 1024> { stack: &'d Stack, state: &'d TcpClientState, } impl<'d, D: Driver, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpClient<'d, D, N, TX_SZ, RX_SZ> { - /// Create a new TcpClient + /// Create a new `TcpClient`. pub fn new(stack: &'d Stack, state: &'d TcpClientState) -> Self { Self { stack, state } } @@ -400,6 +481,7 @@ pub mod client { } } + /// Opened TCP connection in a [`TcpClient`]. pub struct TcpConnection<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> { socket: TcpSocket<'d>, state: &'d TcpClientState, @@ -458,6 +540,7 @@ pub mod client { } impl TcpClientState { + /// Create a new `TcpClientState`. pub const fn new() -> Self { Self { pool: Pool::new() } } diff --git a/embassy-net/src/udp.rs b/embassy-net/src/udp.rs index fe425914..c9843cfe 100644 --- a/embassy-net/src/udp.rs +++ b/embassy-net/src/udp.rs @@ -1,3 +1,5 @@ +//! UDP sockets. + use core::cell::RefCell; use core::future::poll_fn; use core::mem; @@ -11,6 +13,7 @@ use smoltcp::wire::{IpEndpoint, IpListenEndpoint}; use crate::{SocketStack, Stack}; +/// Error returned by [`UdpSocket::bind`]. #[derive(PartialEq, Eq, Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum BindError { @@ -20,6 +23,7 @@ pub enum BindError { NoRoute, } +/// Error returned by [`UdpSocket::recv_from`] and [`UdpSocket::send_to`]. #[derive(PartialEq, Eq, Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Error { @@ -27,12 +31,14 @@ pub enum Error { NoRoute, } +/// An UDP socket. pub struct UdpSocket<'a> { stack: &'a RefCell, handle: SocketHandle, } impl<'a> UdpSocket<'a> { + /// Create a new UDP socket using the provided stack and buffers. pub fn new( stack: &'a Stack, rx_meta: &'a mut [PacketMetadata], @@ -57,6 +63,7 @@ impl<'a> UdpSocket<'a> { } } + /// Bind the socket to a local endpoint. pub fn bind(&mut self, endpoint: T) -> Result<(), BindError> where T: Into, @@ -89,6 +96,11 @@ impl<'a> UdpSocket<'a> { res } + /// Receive a datagram. + /// + /// This method will wait until a datagram is received. + /// + /// Returns the number of bytes received and the remote endpoint. pub async fn recv_from(&self, buf: &mut [u8]) -> Result<(usize, IpEndpoint), Error> { poll_fn(move |cx| { self.with_mut(|s, _| match s.recv_slice(buf) { @@ -103,6 +115,7 @@ impl<'a> UdpSocket<'a> { .await } + /// Send a datagram to the specified remote endpoint. pub async fn send_to(&self, buf: &[u8], remote_endpoint: T) -> Result<(), Error> where T: Into, @@ -122,22 +135,28 @@ impl<'a> UdpSocket<'a> { .await } + /// Returns the local endpoint of the socket. pub fn endpoint(&self) -> IpListenEndpoint { self.with(|s, _| s.endpoint()) } + /// Returns whether the socket is open. + pub fn is_open(&self) -> bool { self.with(|s, _| s.is_open()) } + /// Close the socket. pub fn close(&mut self) { self.with_mut(|s, _| s.close()) } + /// Returns whether the socket is ready to send data, i.e. it has enough buffer space to hold a packet. pub fn may_send(&self) -> bool { self.with(|s, _| s.can_send()) } + /// Returns whether the socket is ready to receive data, i.e. it has received a packet that's now in the buffer. pub fn may_recv(&self) -> bool { self.with(|s, _| s.can_recv()) }