diff options
| -rw-r--r-- | embassy-net-driver/README.md | 12 | ||||
| -rw-r--r-- | embassy-net-driver/src/lib.rs | 45 | ||||
| -rw-r--r-- | embassy-net/Cargo.toml | 3 | ||||
| -rw-r--r-- | embassy-net/src/dns.rs | 13 | ||||
| -rw-r--r-- | embassy-net/src/lib.rs | 71 | ||||
| -rw-r--r-- | embassy-net/src/tcp.rs | 99 | ||||
| -rw-r--r-- | embassy-net/src/time.rs | 20 | ||||
| -rw-r--r-- | embassy-net/src/udp.rs | 22 | ||||
| -rw-r--r-- | examples/nrf52840/src/bin/usb_ethernet.rs | 2 | ||||
| -rw-r--r-- | examples/rp/src/bin/usb_ethernet.rs | 2 | ||||
| -rw-r--r-- | examples/std/src/bin/net.rs | 3 | ||||
| -rw-r--r-- | examples/std/src/bin/net_udp.rs | 4 | ||||
| -rw-r--r-- | examples/stm32f4/src/bin/usb_ethernet.rs | 2 | ||||
| -rw-r--r-- | examples/stm32f7/src/bin/eth.rs | 2 | ||||
| -rw-r--r-- | examples/stm32h5/src/bin/eth.rs | 2 | ||||
| -rw-r--r-- | examples/stm32h7/src/bin/eth.rs | 2 | ||||
| -rw-r--r-- | examples/stm32l5/src/bin/usb_ethernet.rs | 2 |
17 files changed, 264 insertions, 42 deletions
diff --git a/embassy-net-driver/README.md b/embassy-net-driver/README.md new file mode 100644 index 000000000..84f25492d --- /dev/null +++ b/embassy-net-driver/README.md | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | # embassy-net-driver | ||
| 2 | |||
| 3 | |||
| 4 | ## License | ||
| 5 | |||
| 6 | This work is licensed under either of | ||
| 7 | |||
| 8 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or | ||
| 9 | http://www.apache.org/licenses/LICENSE-2.0) | ||
| 10 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) | ||
| 11 | |||
| 12 | at your option. | ||
diff --git a/embassy-net-driver/src/lib.rs b/embassy-net-driver/src/lib.rs index a39cfecc1..4149bf4a4 100644 --- a/embassy-net-driver/src/lib.rs +++ b/embassy-net-driver/src/lib.rs | |||
| @@ -1,20 +1,57 @@ | |||
| 1 | #![no_std] | 1 | #![no_std] |
| 2 | #![warn(missing_docs)] | ||
| 3 | #![doc = include_str!("../README.md")] | ||
| 2 | 4 | ||
| 3 | use core::task::Context; | 5 | use core::task::Context; |
| 4 | 6 | ||
| 7 | /// Main `embassy-net` driver API. | ||
| 8 | /// | ||
| 9 | /// This is essentially an interface for sending and receiving raw network frames. | ||
| 10 | /// | ||
| 11 | /// The interface is based on _tokens_, which are types that allow to receive/transmit a | ||
| 12 | /// single packet. The `receive` and `transmit` functions only construct such tokens, the | ||
| 13 | /// real sending/receiving operation are performed when the tokens are consumed. | ||
| 5 | pub trait Driver { | 14 | pub trait Driver { |
| 15 | /// A token to receive a single network packet. | ||
| 6 | type RxToken<'a>: RxToken | 16 | type RxToken<'a>: RxToken |
| 7 | where | 17 | where |
| 8 | Self: 'a; | 18 | Self: 'a; |
| 19 | |||
| 20 | /// A token to transmit a single network packet. | ||
| 9 | type TxToken<'a>: TxToken | 21 | type TxToken<'a>: TxToken |
| 10 | where | 22 | where |
| 11 | Self: 'a; | 23 | Self: 'a; |
| 12 | 24 | ||
| 25 | /// Construct a token pair consisting of one receive token and one transmit token. | ||
| 26 | /// | ||
| 27 | /// If there is a packet ready to be received, this function must return `Some`. | ||
| 28 | /// If there isn't, it must return `None`, and wake `cx.waker()` when a packet is ready. | ||
| 29 | /// | ||
| 30 | /// The additional transmit token makes it possible to generate a reply packet based | ||
| 31 | /// on the contents of the received packet. For example, this makes it possible to | ||
| 32 | /// handle arbitrarily large ICMP echo ("ping") requests, where the all received bytes | ||
| 33 | /// need to be sent back, without heap allocation. | ||
| 13 | fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)>; | 34 | fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)>; |
| 35 | |||
| 36 | /// Construct a transmit token. | ||
| 37 | /// | ||
| 38 | /// If there is free space in the transmit buffer to transmit a packet, this function must return `Some`. | ||
| 39 | /// If there isn't, it must return `None`, and wake `cx.waker()` when space becomes available. | ||
| 40 | /// | ||
| 41 | /// Note that [`TxToken::consume`] is infallible, so it is not allowed to return a token | ||
| 42 | /// if there is no free space and fail later. | ||
| 14 | fn transmit(&mut self, cx: &mut Context) -> Option<Self::TxToken<'_>>; | 43 | fn transmit(&mut self, cx: &mut Context) -> Option<Self::TxToken<'_>>; |
| 44 | |||
| 45 | /// Get the link state. | ||
| 46 | /// | ||
| 47 | /// This function must return the current link state of the device, and wake `cx.waker()` when | ||
| 48 | /// the link state changes. | ||
| 15 | fn link_state(&mut self, cx: &mut Context) -> LinkState; | 49 | fn link_state(&mut self, cx: &mut Context) -> LinkState; |
| 16 | 50 | ||
| 51 | /// Get a description of device capabilities. | ||
| 17 | fn capabilities(&self) -> Capabilities; | 52 | fn capabilities(&self) -> Capabilities; |
| 53 | |||
| 54 | /// Get the device's Ethernet address. | ||
| 18 | fn ethernet_address(&self) -> [u8; 6]; | 55 | fn ethernet_address(&self) -> [u8; 6]; |
| 19 | } | 56 | } |
| 20 | 57 | ||
| @@ -140,10 +177,15 @@ impl Default for Medium { | |||
| 140 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | 177 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] |
| 141 | #[non_exhaustive] | 178 | #[non_exhaustive] |
| 142 | pub struct ChecksumCapabilities { | 179 | pub struct ChecksumCapabilities { |
| 180 | /// Checksum behavior for IPv4. | ||
| 143 | pub ipv4: Checksum, | 181 | pub ipv4: Checksum, |
| 182 | /// Checksum behavior for UDP. | ||
| 144 | pub udp: Checksum, | 183 | pub udp: Checksum, |
| 184 | /// Checksum behavior for TCP. | ||
| 145 | pub tcp: Checksum, | 185 | pub tcp: Checksum, |
| 186 | /// Checksum behavior for ICMPv4. | ||
| 146 | pub icmpv4: Checksum, | 187 | pub icmpv4: Checksum, |
| 188 | /// Checksum behavior for ICMPv6. | ||
| 147 | pub icmpv6: Checksum, | 189 | pub icmpv6: Checksum, |
| 148 | } | 190 | } |
| 149 | 191 | ||
| @@ -167,9 +209,12 @@ impl Default for Checksum { | |||
| 167 | } | 209 | } |
| 168 | } | 210 | } |
| 169 | 211 | ||
| 212 | /// The link state of a network device. | ||
| 170 | #[derive(PartialEq, Eq, Clone, Copy)] | 213 | #[derive(PartialEq, Eq, Clone, Copy)] |
| 171 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | 214 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] |
| 172 | pub enum LinkState { | 215 | pub enum LinkState { |
| 216 | /// The link is down. | ||
| 173 | Down, | 217 | Down, |
| 218 | /// The link is up. | ||
| 174 | Up, | 219 | Up, |
| 175 | } | 220 | } |
diff --git a/embassy-net/Cargo.toml b/embassy-net/Cargo.toml index fe9514e46..0a47c5d94 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 | |||
| 11 | features = ["nightly", "unstable-traits", "defmt", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "igmp"] | 11 | features = ["nightly", "unstable-traits", "defmt", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "igmp"] |
| 12 | target = "thumbv7em-none-eabi" | 12 | target = "thumbv7em-none-eabi" |
| 13 | 13 | ||
| 14 | [package.metadata.docs.rs] | ||
| 15 | features = ["nightly", "unstable-traits", "defmt", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "igmp"] | ||
| 16 | |||
| 14 | [features] | 17 | [features] |
| 15 | default = [] | 18 | default = [] |
| 16 | std = [] | 19 | std = [] |
diff --git a/embassy-net/src/dns.rs b/embassy-net/src/dns.rs index 97320e44b..3fd235b2c 100644 --- a/embassy-net/src/dns.rs +++ b/embassy-net/src/dns.rs | |||
| @@ -1,4 +1,9 @@ | |||
| 1 | //! DNS socket with async support. | 1 | //! DNS client compatible with the `embedded-nal-async` traits. |
| 2 | //! | ||
| 3 | //! This exists only for compatibility with crates that use `embedded-nal-async`. | ||
| 4 | //! Prefer using [`Stack::dns_query`](crate::Stack::dns_query) directly if you're | ||
| 5 | //! not using `embedded-nal-async`. | ||
| 6 | |||
| 2 | use heapless::Vec; | 7 | use heapless::Vec; |
| 3 | pub use smoltcp::socket::dns::{DnsQuery, Socket}; | 8 | pub use smoltcp::socket::dns::{DnsQuery, Socket}; |
| 4 | pub(crate) use smoltcp::socket::dns::{GetQueryResultError, StartQueryError}; | 9 | pub(crate) use smoltcp::socket::dns::{GetQueryResultError, StartQueryError}; |
| @@ -34,7 +39,11 @@ impl From<StartQueryError> for Error { | |||
| 34 | } | 39 | } |
| 35 | } | 40 | } |
| 36 | 41 | ||
| 37 | /// Async socket for making DNS queries. | 42 | /// DNS client compatible with the `embedded-nal-async` traits. |
| 43 | /// | ||
| 44 | /// This exists only for compatibility with crates that use `embedded-nal-async`. | ||
| 45 | /// Prefer using [`Stack::dns_query`](crate::Stack::dns_query) directly if you're | ||
| 46 | /// not using `embedded-nal-async`. | ||
| 38 | pub struct DnsSocket<'a, D> | 47 | pub struct DnsSocket<'a, D> |
| 39 | where | 48 | where |
| 40 | D: Driver + 'static, | 49 | D: Driver + 'static, |
diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs index b80784c2b..9487c0913 100644 --- a/embassy-net/src/lib.rs +++ b/embassy-net/src/lib.rs | |||
| @@ -1,17 +1,18 @@ | |||
| 1 | #![cfg_attr(not(feature = "std"), no_std)] | 1 | #![cfg_attr(not(feature = "std"), no_std)] |
| 2 | #![cfg_attr(feature = "nightly", feature(async_fn_in_trait, impl_trait_projections))] | 2 | #![cfg_attr(feature = "nightly", feature(async_fn_in_trait, impl_trait_projections))] |
| 3 | #![cfg_attr(feature = "nightly", allow(incomplete_features))] | 3 | #![cfg_attr(feature = "nightly", allow(incomplete_features))] |
| 4 | #![warn(missing_docs)] | ||
| 5 | #![doc = include_str!("../README.md")] | ||
| 4 | 6 | ||
| 5 | // This mod MUST go first, so that the others see its macros. | 7 | // This mod MUST go first, so that the others see its macros. |
| 6 | pub(crate) mod fmt; | 8 | pub(crate) mod fmt; |
| 7 | 9 | ||
| 8 | pub use embassy_net_driver as driver; | ||
| 9 | |||
| 10 | mod device; | 10 | mod device; |
| 11 | #[cfg(feature = "dns")] | 11 | #[cfg(feature = "dns")] |
| 12 | pub mod dns; | 12 | pub mod dns; |
| 13 | #[cfg(feature = "tcp")] | 13 | #[cfg(feature = "tcp")] |
| 14 | pub mod tcp; | 14 | pub mod tcp; |
| 15 | mod time; | ||
| 15 | #[cfg(feature = "udp")] | 16 | #[cfg(feature = "udp")] |
| 16 | pub mod udp; | 17 | pub mod udp; |
| 17 | 18 | ||
| @@ -19,33 +20,34 @@ use core::cell::RefCell; | |||
| 19 | use core::future::{poll_fn, Future}; | 20 | use core::future::{poll_fn, Future}; |
| 20 | use core::task::{Context, Poll}; | 21 | use core::task::{Context, Poll}; |
| 21 | 22 | ||
| 23 | pub use embassy_net_driver as driver; | ||
| 22 | use embassy_net_driver::{Driver, LinkState, Medium}; | 24 | use embassy_net_driver::{Driver, LinkState, Medium}; |
| 23 | use embassy_sync::waitqueue::WakerRegistration; | 25 | use embassy_sync::waitqueue::WakerRegistration; |
| 24 | use embassy_time::{Instant, Timer}; | 26 | use embassy_time::{Instant, Timer}; |
| 25 | use futures::pin_mut; | 27 | use futures::pin_mut; |
| 26 | use heapless::Vec; | 28 | use heapless::Vec; |
| 29 | #[cfg(feature = "igmp")] | ||
| 30 | pub use smoltcp::iface::MulticastError; | ||
| 27 | use smoltcp::iface::{Interface, SocketHandle, SocketSet, SocketStorage}; | 31 | use smoltcp::iface::{Interface, SocketHandle, SocketSet, SocketStorage}; |
| 28 | #[cfg(feature = "dhcpv4")] | 32 | #[cfg(feature = "dhcpv4")] |
| 29 | use smoltcp::socket::dhcpv4::{self, RetryConfig}; | 33 | use smoltcp::socket::dhcpv4::{self, RetryConfig}; |
| 30 | #[cfg(feature = "dhcpv4")] | 34 | #[cfg(feature = "udp")] |
| 31 | use smoltcp::time::Duration; | 35 | pub use smoltcp::wire::IpListenEndpoint; |
| 32 | // smoltcp reexports | ||
| 33 | pub use smoltcp::time::{Duration as SmolDuration, Instant as SmolInstant}; | ||
| 34 | #[cfg(feature = "medium-ethernet")] | 36 | #[cfg(feature = "medium-ethernet")] |
| 35 | pub use smoltcp::wire::{EthernetAddress, HardwareAddress}; | 37 | pub use smoltcp::wire::{EthernetAddress, HardwareAddress}; |
| 36 | pub use smoltcp::wire::{IpAddress, IpCidr, Ipv4Address, Ipv4Cidr}; | 38 | pub use smoltcp::wire::{IpAddress, IpCidr, Ipv4Address, Ipv4Cidr}; |
| 37 | #[cfg(feature = "proto-ipv6")] | 39 | #[cfg(feature = "proto-ipv6")] |
| 38 | pub use smoltcp::wire::{Ipv6Address, Ipv6Cidr}; | 40 | pub use smoltcp::wire::{Ipv6Address, Ipv6Cidr}; |
| 39 | #[cfg(feature = "udp")] | ||
| 40 | pub use smoltcp::{socket::udp::PacketMetadata, wire::IpListenEndpoint}; | ||
| 41 | 41 | ||
| 42 | use crate::device::DriverAdapter; | 42 | use crate::device::DriverAdapter; |
| 43 | use crate::time::{instant_from_smoltcp, instant_to_smoltcp}; | ||
| 43 | 44 | ||
| 44 | const LOCAL_PORT_MIN: u16 = 1025; | 45 | const LOCAL_PORT_MIN: u16 = 1025; |
| 45 | const LOCAL_PORT_MAX: u16 = 65535; | 46 | const LOCAL_PORT_MAX: u16 = 65535; |
| 46 | #[cfg(feature = "dns")] | 47 | #[cfg(feature = "dns")] |
| 47 | const MAX_QUERIES: usize = 4; | 48 | const MAX_QUERIES: usize = 4; |
| 48 | 49 | ||
| 50 | /// Memory resources needed for a network stack. | ||
| 49 | pub struct StackResources<const SOCK: usize> { | 51 | pub struct StackResources<const SOCK: usize> { |
| 50 | sockets: [SocketStorage<'static>; SOCK], | 52 | sockets: [SocketStorage<'static>; SOCK], |
| 51 | #[cfg(feature = "dns")] | 53 | #[cfg(feature = "dns")] |
| @@ -53,6 +55,7 @@ pub struct StackResources<const SOCK: usize> { | |||
| 53 | } | 55 | } |
| 54 | 56 | ||
| 55 | impl<const SOCK: usize> StackResources<SOCK> { | 57 | impl<const SOCK: usize> StackResources<SOCK> { |
| 58 | /// Create a new set of stack resources. | ||
| 56 | pub fn new() -> Self { | 59 | pub fn new() -> Self { |
| 57 | #[cfg(feature = "dns")] | 60 | #[cfg(feature = "dns")] |
| 58 | const INIT: Option<dns::DnsQuery> = None; | 61 | const INIT: Option<dns::DnsQuery> = None; |
| @@ -64,23 +67,35 @@ impl<const SOCK: usize> StackResources<SOCK> { | |||
| 64 | } | 67 | } |
| 65 | } | 68 | } |
| 66 | 69 | ||
| 70 | /// Static IP address configuration. | ||
| 67 | #[derive(Debug, Clone, PartialEq, Eq)] | 71 | #[derive(Debug, Clone, PartialEq, Eq)] |
| 68 | pub struct StaticConfig { | 72 | pub struct StaticConfig { |
| 73 | /// IP address and subnet mask. | ||
| 69 | pub address: Ipv4Cidr, | 74 | pub address: Ipv4Cidr, |
| 75 | /// Default gateway. | ||
| 70 | pub gateway: Option<Ipv4Address>, | 76 | pub gateway: Option<Ipv4Address>, |
| 77 | /// DNS servers. | ||
| 71 | pub dns_servers: Vec<Ipv4Address, 3>, | 78 | pub dns_servers: Vec<Ipv4Address, 3>, |
| 72 | } | 79 | } |
| 73 | 80 | ||
| 81 | /// DHCP configuration. | ||
| 74 | #[cfg(feature = "dhcpv4")] | 82 | #[cfg(feature = "dhcpv4")] |
| 75 | #[derive(Debug, Clone, PartialEq, Eq)] | 83 | #[derive(Debug, Clone, PartialEq, Eq)] |
| 76 | pub struct DhcpConfig { | 84 | pub struct DhcpConfig { |
| 77 | pub max_lease_duration: Option<Duration>, | 85 | /// Maximum lease duration. |
| 86 | /// | ||
| 87 | /// If not set, the lease duration specified by the server will be used. | ||
| 88 | /// If set, the lease duration will be capped at this value. | ||
| 89 | pub max_lease_duration: Option<embassy_time::Duration>, | ||
| 90 | /// Retry configuration. | ||
| 78 | pub retry_config: RetryConfig, | 91 | pub retry_config: RetryConfig, |
| 79 | /// Ignore NAKs. | 92 | /// Ignore NAKs from DHCP servers. |
| 93 | /// | ||
| 94 | /// 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. | ||
| 80 | pub ignore_naks: bool, | 95 | pub ignore_naks: bool, |
| 81 | /// Server port config | 96 | /// Server port. This is almost always 67. Do not change unless you know what you're doing. |
| 82 | pub server_port: u16, | 97 | pub server_port: u16, |
| 83 | /// Client port config | 98 | /// Client port. This is almost always 68. Do not change unless you know what you're doing. |
| 84 | pub client_port: u16, | 99 | pub client_port: u16, |
| 85 | } | 100 | } |
| 86 | 101 | ||
| @@ -97,12 +112,18 @@ impl Default for DhcpConfig { | |||
| 97 | } | 112 | } |
| 98 | } | 113 | } |
| 99 | 114 | ||
| 115 | /// Network stack configuration. | ||
| 100 | pub enum Config { | 116 | pub enum Config { |
| 117 | /// Use a static IP address configuration. | ||
| 101 | Static(StaticConfig), | 118 | Static(StaticConfig), |
| 119 | /// Use DHCP to obtain an IP address configuration. | ||
| 102 | #[cfg(feature = "dhcpv4")] | 120 | #[cfg(feature = "dhcpv4")] |
| 103 | Dhcp(DhcpConfig), | 121 | Dhcp(DhcpConfig), |
| 104 | } | 122 | } |
| 105 | 123 | ||
| 124 | /// A network stack. | ||
| 125 | /// | ||
| 126 | /// This is the main entry point for the network stack. | ||
| 106 | pub struct Stack<D: Driver> { | 127 | pub struct Stack<D: Driver> { |
| 107 | pub(crate) socket: RefCell<SocketStack>, | 128 | pub(crate) socket: RefCell<SocketStack>, |
| 108 | inner: RefCell<Inner<D>>, | 129 | inner: RefCell<Inner<D>>, |
| @@ -128,6 +149,7 @@ pub(crate) struct SocketStack { | |||
| 128 | } | 149 | } |
| 129 | 150 | ||
| 130 | impl<D: Driver + 'static> Stack<D> { | 151 | impl<D: Driver + 'static> Stack<D> { |
| 152 | /// Create a new network stack. | ||
| 131 | pub fn new<const SOCK: usize>( | 153 | pub fn new<const SOCK: usize>( |
| 132 | mut device: D, | 154 | mut device: D, |
| 133 | config: Config, | 155 | config: Config, |
| @@ -205,22 +227,30 @@ impl<D: Driver + 'static> Stack<D> { | |||
| 205 | f(&mut *self.socket.borrow_mut(), &mut *self.inner.borrow_mut()) | 227 | f(&mut *self.socket.borrow_mut(), &mut *self.inner.borrow_mut()) |
| 206 | } | 228 | } |
| 207 | 229 | ||
| 230 | /// Get the MAC address of the network interface. | ||
| 208 | pub fn ethernet_address(&self) -> [u8; 6] { | 231 | pub fn ethernet_address(&self) -> [u8; 6] { |
| 209 | self.with(|_s, i| i.device.ethernet_address()) | 232 | self.with(|_s, i| i.device.ethernet_address()) |
| 210 | } | 233 | } |
| 211 | 234 | ||
| 235 | /// Get whether the link is up. | ||
| 212 | pub fn is_link_up(&self) -> bool { | 236 | pub fn is_link_up(&self) -> bool { |
| 213 | self.with(|_s, i| i.link_up) | 237 | self.with(|_s, i| i.link_up) |
| 214 | } | 238 | } |
| 215 | 239 | ||
| 240 | /// Get whether the network stack has a valid IP configuration. | ||
| 241 | /// This is true if the network stack has a static IP configuration or if DHCP has completed | ||
| 216 | pub fn is_config_up(&self) -> bool { | 242 | pub fn is_config_up(&self) -> bool { |
| 217 | self.with(|_s, i| i.config.is_some()) | 243 | self.with(|_s, i| i.config.is_some()) |
| 218 | } | 244 | } |
| 219 | 245 | ||
| 246 | /// Get the current IP configuration. | ||
| 220 | pub fn config(&self) -> Option<StaticConfig> { | 247 | pub fn config(&self) -> Option<StaticConfig> { |
| 221 | self.with(|_s, i| i.config.clone()) | 248 | self.with(|_s, i| i.config.clone()) |
| 222 | } | 249 | } |
| 223 | 250 | ||
| 251 | /// Run the network stack. | ||
| 252 | /// | ||
| 253 | /// You must call this in a background task, to process network events. | ||
| 224 | pub async fn run(&self) -> ! { | 254 | pub async fn run(&self) -> ! { |
| 225 | poll_fn(|cx| { | 255 | poll_fn(|cx| { |
| 226 | self.with_mut(|s, i| i.poll(cx, s)); | 256 | self.with_mut(|s, i| i.poll(cx, s)); |
| @@ -303,7 +333,8 @@ impl<D: Driver + 'static> Stack<D> { | |||
| 303 | 333 | ||
| 304 | #[cfg(feature = "igmp")] | 334 | #[cfg(feature = "igmp")] |
| 305 | impl<D: Driver + smoltcp::phy::Device + 'static> Stack<D> { | 335 | impl<D: Driver + smoltcp::phy::Device + 'static> Stack<D> { |
| 306 | pub fn join_multicast_group<T>(&self, addr: T) -> Result<bool, smoltcp::iface::MulticastError> | 336 | /// Join a multicast group. |
| 337 | pub fn join_multicast_group<T>(&self, addr: T) -> Result<bool, MulticastError> | ||
| 307 | where | 338 | where |
| 308 | T: Into<IpAddress>, | 339 | T: Into<IpAddress>, |
| 309 | { | 340 | { |
| @@ -315,7 +346,8 @@ impl<D: Driver + smoltcp::phy::Device + 'static> Stack<D> { | |||
| 315 | }) | 346 | }) |
| 316 | } | 347 | } |
| 317 | 348 | ||
| 318 | pub fn leave_multicast_group<T>(&self, addr: T) -> Result<bool, smoltcp::iface::MulticastError> | 349 | /// Leave a multicast group. |
| 350 | pub fn leave_multicast_group<T>(&self, addr: T) -> Result<bool, MulticastError> | ||
| 319 | where | 351 | where |
| 320 | T: Into<IpAddress>, | 352 | T: Into<IpAddress>, |
| 321 | { | 353 | { |
| @@ -327,6 +359,7 @@ impl<D: Driver + smoltcp::phy::Device + 'static> Stack<D> { | |||
| 327 | }) | 359 | }) |
| 328 | } | 360 | } |
| 329 | 361 | ||
| 362 | /// Get whether the network stack has joined the given multicast group. | ||
| 330 | pub fn has_multicast_group<T: Into<IpAddress>>(&self, addr: T) -> bool { | 363 | pub fn has_multicast_group<T: Into<IpAddress>>(&self, addr: T) -> bool { |
| 331 | self.socket.borrow().iface.has_multicast_group(addr) | 364 | self.socket.borrow().iface.has_multicast_group(addr) |
| 332 | } | 365 | } |
| @@ -384,7 +417,7 @@ impl<D: Driver + 'static> Inner<D> { | |||
| 384 | #[cfg(feature = "dhcpv4")] | 417 | #[cfg(feature = "dhcpv4")] |
| 385 | fn apply_dhcp_config(&self, socket: &mut smoltcp::socket::dhcpv4::Socket, config: DhcpConfig) { | 418 | fn apply_dhcp_config(&self, socket: &mut smoltcp::socket::dhcpv4::Socket, config: DhcpConfig) { |
| 386 | socket.set_ignore_naks(config.ignore_naks); | 419 | socket.set_ignore_naks(config.ignore_naks); |
| 387 | socket.set_max_lease_duration(config.max_lease_duration); | 420 | socket.set_max_lease_duration(config.max_lease_duration.map(crate::time::duration_to_smoltcp)); |
| 388 | socket.set_ports(config.server_port, config.client_port); | 421 | socket.set_ports(config.server_port, config.client_port); |
| 389 | socket.set_retry_config(config.retry_config); | 422 | socket.set_retry_config(config.retry_config); |
| 390 | } | 423 | } |
| @@ -465,11 +498,3 @@ impl<D: Driver + 'static> Inner<D> { | |||
| 465 | } | 498 | } |
| 466 | } | 499 | } |
| 467 | } | 500 | } |
| 468 | |||
| 469 | fn instant_to_smoltcp(instant: Instant) -> SmolInstant { | ||
| 470 | SmolInstant::from_millis(instant.as_millis() as i64) | ||
| 471 | } | ||
| 472 | |||
| 473 | fn instant_from_smoltcp(instant: SmolInstant) -> Instant { | ||
| 474 | Instant::from_millis(instant.total_millis() as u64) | ||
| 475 | } | ||
diff --git a/embassy-net/src/tcp.rs b/embassy-net/src/tcp.rs index c3d8764b0..732b6d217 100644 --- a/embassy-net/src/tcp.rs +++ b/embassy-net/src/tcp.rs | |||
| @@ -1,22 +1,39 @@ | |||
| 1 | //! TCP sockets. | ||
| 2 | //! | ||
| 3 | //! # Listening | ||
| 4 | //! | ||
| 5 | //! `embassy-net` does not have a `TcpListener`. Instead, individual `TcpSocket`s can be put into | ||
| 6 | //! listening mode by calling [`TcpSocket::accept`]. | ||
| 7 | //! | ||
| 8 | //! Incoming connections when no socket is listening are rejected. To accept many incoming | ||
| 9 | //! connections, create many sockets and put them all into listening mode. | ||
| 10 | |||
| 1 | use core::cell::RefCell; | 11 | use core::cell::RefCell; |
| 2 | use core::future::poll_fn; | 12 | use core::future::poll_fn; |
| 3 | use core::mem; | 13 | use core::mem; |
| 4 | use core::task::Poll; | 14 | use core::task::Poll; |
| 5 | 15 | ||
| 6 | use embassy_net_driver::Driver; | 16 | use embassy_net_driver::Driver; |
| 17 | use embassy_time::Duration; | ||
| 7 | use smoltcp::iface::{Interface, SocketHandle}; | 18 | use smoltcp::iface::{Interface, SocketHandle}; |
| 8 | use smoltcp::socket::tcp; | 19 | use smoltcp::socket::tcp; |
| 9 | use smoltcp::time::Duration; | 20 | pub use smoltcp::socket::tcp::State; |
| 10 | use smoltcp::wire::{IpEndpoint, IpListenEndpoint}; | 21 | use smoltcp::wire::{IpEndpoint, IpListenEndpoint}; |
| 11 | 22 | ||
| 23 | use crate::time::duration_to_smoltcp; | ||
| 12 | use crate::{SocketStack, Stack}; | 24 | use crate::{SocketStack, Stack}; |
| 13 | 25 | ||
| 26 | /// Error returned by TcpSocket read/write functions. | ||
| 14 | #[derive(PartialEq, Eq, Clone, Copy, Debug)] | 27 | #[derive(PartialEq, Eq, Clone, Copy, Debug)] |
| 15 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | 28 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] |
| 16 | pub enum Error { | 29 | pub enum Error { |
| 30 | /// The connection was reset. | ||
| 31 | /// | ||
| 32 | /// This can happen on receiving a RST packet, or on timeout. | ||
| 17 | ConnectionReset, | 33 | ConnectionReset, |
| 18 | } | 34 | } |
| 19 | 35 | ||
| 36 | /// Error returned by [`TcpSocket::connect`]. | ||
| 20 | #[derive(PartialEq, Eq, Clone, Copy, Debug)] | 37 | #[derive(PartialEq, Eq, Clone, Copy, Debug)] |
| 21 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | 38 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] |
| 22 | pub enum ConnectError { | 39 | pub enum ConnectError { |
| @@ -30,6 +47,7 @@ pub enum ConnectError { | |||
| 30 | NoRoute, | 47 | NoRoute, |
| 31 | } | 48 | } |
| 32 | 49 | ||
| 50 | /// Error returned by [`TcpSocket::accept`]. | ||
| 33 | #[derive(PartialEq, Eq, Clone, Copy, Debug)] | 51 | #[derive(PartialEq, Eq, Clone, Copy, Debug)] |
| 34 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | 52 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] |
| 35 | pub enum AcceptError { | 53 | pub enum AcceptError { |
| @@ -41,35 +59,50 @@ pub enum AcceptError { | |||
| 41 | ConnectionReset, | 59 | ConnectionReset, |
| 42 | } | 60 | } |
| 43 | 61 | ||
| 62 | /// A TCP socket. | ||
| 44 | pub struct TcpSocket<'a> { | 63 | pub struct TcpSocket<'a> { |
| 45 | io: TcpIo<'a>, | 64 | io: TcpIo<'a>, |
| 46 | } | 65 | } |
| 47 | 66 | ||
| 67 | /// The reader half of a TCP socket. | ||
| 48 | pub struct TcpReader<'a> { | 68 | pub struct TcpReader<'a> { |
| 49 | io: TcpIo<'a>, | 69 | io: TcpIo<'a>, |
| 50 | } | 70 | } |
| 51 | 71 | ||
| 72 | /// The writer half of a TCP socket. | ||
| 52 | pub struct TcpWriter<'a> { | 73 | pub struct TcpWriter<'a> { |
| 53 | io: TcpIo<'a>, | 74 | io: TcpIo<'a>, |
| 54 | } | 75 | } |
| 55 | 76 | ||
| 56 | impl<'a> TcpReader<'a> { | 77 | impl<'a> TcpReader<'a> { |
| 78 | /// Read data from the socket. | ||
| 79 | /// | ||
| 80 | /// Returns how many bytes were read, or an error. If no data is available, it waits | ||
| 81 | /// until there is at least one byte available. | ||
| 57 | pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> { | 82 | pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> { |
| 58 | self.io.read(buf).await | 83 | self.io.read(buf).await |
| 59 | } | 84 | } |
| 60 | } | 85 | } |
| 61 | 86 | ||
| 62 | impl<'a> TcpWriter<'a> { | 87 | impl<'a> TcpWriter<'a> { |
| 88 | /// Write data to the socket. | ||
| 89 | /// | ||
| 90 | /// Returns how many bytes were written, or an error. If the socket is not ready to | ||
| 91 | /// accept data, it waits until it is. | ||
| 63 | pub async fn write(&mut self, buf: &[u8]) -> Result<usize, Error> { | 92 | pub async fn write(&mut self, buf: &[u8]) -> Result<usize, Error> { |
| 64 | self.io.write(buf).await | 93 | self.io.write(buf).await |
| 65 | } | 94 | } |
| 66 | 95 | ||
| 96 | /// Flushes the written data to the socket. | ||
| 97 | /// | ||
| 98 | /// This waits until all data has been sent, and ACKed by the remote host. | ||
| 67 | pub async fn flush(&mut self) -> Result<(), Error> { | 99 | pub async fn flush(&mut self) -> Result<(), Error> { |
| 68 | self.io.flush().await | 100 | self.io.flush().await |
| 69 | } | 101 | } |
| 70 | } | 102 | } |
| 71 | 103 | ||
| 72 | impl<'a> TcpSocket<'a> { | 104 | impl<'a> TcpSocket<'a> { |
| 105 | /// Create a new TCP socket on the given stack, with the given buffers. | ||
| 73 | pub fn new<D: Driver>(stack: &'a Stack<D>, rx_buffer: &'a mut [u8], tx_buffer: &'a mut [u8]) -> Self { | 106 | pub fn new<D: Driver>(stack: &'a Stack<D>, rx_buffer: &'a mut [u8], tx_buffer: &'a mut [u8]) -> Self { |
| 74 | let s = &mut *stack.socket.borrow_mut(); | 107 | let s = &mut *stack.socket.borrow_mut(); |
| 75 | let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; | 108 | let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; |
| @@ -87,10 +120,12 @@ impl<'a> TcpSocket<'a> { | |||
| 87 | } | 120 | } |
| 88 | } | 121 | } |
| 89 | 122 | ||
| 123 | /// Split the socket into reader and a writer halves. | ||
| 90 | pub fn split(&mut self) -> (TcpReader<'_>, TcpWriter<'_>) { | 124 | pub fn split(&mut self) -> (TcpReader<'_>, TcpWriter<'_>) { |
| 91 | (TcpReader { io: self.io }, TcpWriter { io: self.io }) | 125 | (TcpReader { io: self.io }, TcpWriter { io: self.io }) |
| 92 | } | 126 | } |
| 93 | 127 | ||
| 128 | /// Connect to a remote host. | ||
| 94 | pub async fn connect<T>(&mut self, remote_endpoint: T) -> Result<(), ConnectError> | 129 | pub async fn connect<T>(&mut self, remote_endpoint: T) -> Result<(), ConnectError> |
| 95 | where | 130 | where |
| 96 | T: Into<IpEndpoint>, | 131 | T: Into<IpEndpoint>, |
| @@ -120,6 +155,9 @@ impl<'a> TcpSocket<'a> { | |||
| 120 | .await | 155 | .await |
| 121 | } | 156 | } |
| 122 | 157 | ||
| 158 | /// Accept a connection from a remote host. | ||
| 159 | /// | ||
| 160 | /// This function puts the socket in listening mode, and waits until a connection is received. | ||
| 123 | pub async fn accept<T>(&mut self, local_endpoint: T) -> Result<(), AcceptError> | 161 | pub async fn accept<T>(&mut self, local_endpoint: T) -> Result<(), AcceptError> |
| 124 | where | 162 | where |
| 125 | T: Into<IpListenEndpoint>, | 163 | T: Into<IpListenEndpoint>, |
| @@ -142,54 +180,98 @@ impl<'a> TcpSocket<'a> { | |||
| 142 | .await | 180 | .await |
| 143 | } | 181 | } |
| 144 | 182 | ||
| 183 | /// Read data from the socket. | ||
| 184 | /// | ||
| 185 | /// Returns how many bytes were read, or an error. If no data is available, it waits | ||
| 186 | /// until there is at least one byte available. | ||
| 145 | pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> { | 187 | pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> { |
| 146 | self.io.read(buf).await | 188 | self.io.read(buf).await |
| 147 | } | 189 | } |
| 148 | 190 | ||
| 191 | /// Write data to the socket. | ||
| 192 | /// | ||
| 193 | /// Returns how many bytes were written, or an error. If the socket is not ready to | ||
| 194 | /// accept data, it waits until it is. | ||
| 149 | pub async fn write(&mut self, buf: &[u8]) -> Result<usize, Error> { | 195 | pub async fn write(&mut self, buf: &[u8]) -> Result<usize, Error> { |
| 150 | self.io.write(buf).await | 196 | self.io.write(buf).await |
| 151 | } | 197 | } |
| 152 | 198 | ||
| 199 | /// Flushes the written data to the socket. | ||
| 200 | /// | ||
| 201 | /// This waits until all data has been sent, and ACKed by the remote host. | ||
| 153 | pub async fn flush(&mut self) -> Result<(), Error> { | 202 | pub async fn flush(&mut self) -> Result<(), Error> { |
| 154 | self.io.flush().await | 203 | self.io.flush().await |
| 155 | } | 204 | } |
| 156 | 205 | ||
| 206 | /// Set the timeout for the socket. | ||
| 207 | /// | ||
| 208 | /// If the timeout is set, the socket will be closed if no data is received for the | ||
| 209 | /// specified duration. | ||
| 157 | pub fn set_timeout(&mut self, duration: Option<Duration>) { | 210 | pub fn set_timeout(&mut self, duration: Option<Duration>) { |
| 158 | self.io.with_mut(|s, _| s.set_timeout(duration)) | 211 | self.io |
| 212 | .with_mut(|s, _| s.set_timeout(duration.map(duration_to_smoltcp))) | ||
| 159 | } | 213 | } |
| 160 | 214 | ||
| 215 | /// Set the keep-alive interval for the socket. | ||
| 216 | /// | ||
| 217 | /// If the keep-alive interval is set, the socket will send keep-alive packets after | ||
| 218 | /// the specified duration of inactivity. | ||
| 219 | /// | ||
| 220 | /// If not set, the socket will not send keep-alive packets. | ||
| 161 | pub fn set_keep_alive(&mut self, interval: Option<Duration>) { | 221 | pub fn set_keep_alive(&mut self, interval: Option<Duration>) { |
| 162 | self.io.with_mut(|s, _| s.set_keep_alive(interval)) | 222 | self.io |
| 223 | .with_mut(|s, _| s.set_keep_alive(interval.map(duration_to_smoltcp))) | ||
| 163 | } | 224 | } |
| 164 | 225 | ||
| 226 | /// Set the hop limit field in the IP header of sent packets. | ||
| 165 | pub fn set_hop_limit(&mut self, hop_limit: Option<u8>) { | 227 | pub fn set_hop_limit(&mut self, hop_limit: Option<u8>) { |
| 166 | self.io.with_mut(|s, _| s.set_hop_limit(hop_limit)) | 228 | self.io.with_mut(|s, _| s.set_hop_limit(hop_limit)) |
| 167 | } | 229 | } |
| 168 | 230 | ||
| 231 | /// Get the local endpoint of the socket. | ||
| 232 | /// | ||
| 233 | /// Returns `None` if the socket is not bound (listening) or not connected. | ||
| 169 | pub fn local_endpoint(&self) -> Option<IpEndpoint> { | 234 | pub fn local_endpoint(&self) -> Option<IpEndpoint> { |
| 170 | self.io.with(|s, _| s.local_endpoint()) | 235 | self.io.with(|s, _| s.local_endpoint()) |
| 171 | } | 236 | } |
| 172 | 237 | ||
| 238 | /// Get the remote endpoint of the socket. | ||
| 239 | /// | ||
| 240 | /// Returns `None` if the socket is not connected. | ||
| 173 | pub fn remote_endpoint(&self) -> Option<IpEndpoint> { | 241 | pub fn remote_endpoint(&self) -> Option<IpEndpoint> { |
| 174 | self.io.with(|s, _| s.remote_endpoint()) | 242 | self.io.with(|s, _| s.remote_endpoint()) |
| 175 | } | 243 | } |
| 176 | 244 | ||
| 177 | pub fn state(&self) -> tcp::State { | 245 | /// Get the state of the socket. |
| 246 | pub fn state(&self) -> State { | ||
| 178 | self.io.with(|s, _| s.state()) | 247 | self.io.with(|s, _| s.state()) |
| 179 | } | 248 | } |
| 180 | 249 | ||
| 250 | /// Close the write half of the socket. | ||
| 251 | /// | ||
| 252 | /// This closes only the write half of the socket. The read half side remains open, the | ||
| 253 | /// socket can still receive data. | ||
| 254 | /// | ||
| 255 | /// Data that has been written to the socket and not yet sent (or not yet ACKed) will still | ||
| 256 | /// still sent. The last segment of the pending to send data is sent with the FIN flag set. | ||
| 181 | pub fn close(&mut self) { | 257 | pub fn close(&mut self) { |
| 182 | self.io.with_mut(|s, _| s.close()) | 258 | self.io.with_mut(|s, _| s.close()) |
| 183 | } | 259 | } |
| 184 | 260 | ||
| 261 | /// Forcibly close the socket. | ||
| 262 | /// | ||
| 263 | /// This instantly closes both the read and write halves of the socket. Any pending data | ||
| 264 | /// that has not been sent will be lost. | ||
| 185 | pub fn abort(&mut self) { | 265 | pub fn abort(&mut self) { |
| 186 | self.io.with_mut(|s, _| s.abort()) | 266 | self.io.with_mut(|s, _| s.abort()) |
| 187 | } | 267 | } |
| 188 | 268 | ||
| 269 | /// Get whether the socket is ready to send data, i.e. whether there is space in the send buffer. | ||
| 189 | pub fn may_send(&self) -> bool { | 270 | pub fn may_send(&self) -> bool { |
| 190 | self.io.with(|s, _| s.may_send()) | 271 | self.io.with(|s, _| s.may_send()) |
| 191 | } | 272 | } |
| 192 | 273 | ||
| 274 | /// Get whether the socket is ready to receive data, i.e. whether there is some pending data in the receive buffer. | ||
| 193 | pub fn may_recv(&self) -> bool { | 275 | pub fn may_recv(&self) -> bool { |
| 194 | self.io.with(|s, _| s.may_recv()) | 276 | self.io.with(|s, _| s.may_recv()) |
| 195 | } | 277 | } |
| @@ -341,6 +423,7 @@ mod embedded_io_impls { | |||
| 341 | } | 423 | } |
| 342 | } | 424 | } |
| 343 | 425 | ||
| 426 | /// TCP client compatible with `embedded-nal-async` traits. | ||
| 344 | #[cfg(all(feature = "unstable-traits", feature = "nightly"))] | 427 | #[cfg(all(feature = "unstable-traits", feature = "nightly"))] |
| 345 | pub mod client { | 428 | pub mod client { |
| 346 | use core::cell::UnsafeCell; | 429 | use core::cell::UnsafeCell; |
| @@ -352,14 +435,16 @@ pub mod client { | |||
| 352 | 435 | ||
| 353 | use super::*; | 436 | use super::*; |
| 354 | 437 | ||
| 355 | /// TCP client capable of creating up to N multiple connections with tx and rx buffers according to TX_SZ and RX_SZ. | 438 | /// TCP client connection pool compatible with `embedded-nal-async` traits. |
| 439 | /// | ||
| 440 | /// The pool is capable of managing up to N concurrent connections with tx and rx buffers according to TX_SZ and RX_SZ. | ||
| 356 | pub struct TcpClient<'d, D: Driver, const N: usize, const TX_SZ: usize = 1024, const RX_SZ: usize = 1024> { | 441 | pub struct TcpClient<'d, D: Driver, const N: usize, const TX_SZ: usize = 1024, const RX_SZ: usize = 1024> { |
| 357 | stack: &'d Stack<D>, | 442 | stack: &'d Stack<D>, |
| 358 | state: &'d TcpClientState<N, TX_SZ, RX_SZ>, | 443 | state: &'d TcpClientState<N, TX_SZ, RX_SZ>, |
| 359 | } | 444 | } |
| 360 | 445 | ||
| 361 | impl<'d, D: Driver, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpClient<'d, D, N, TX_SZ, RX_SZ> { | 446 | impl<'d, D: Driver, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpClient<'d, D, N, TX_SZ, RX_SZ> { |
| 362 | /// Create a new TcpClient | 447 | /// Create a new `TcpClient`. |
| 363 | pub fn new(stack: &'d Stack<D>, state: &'d TcpClientState<N, TX_SZ, RX_SZ>) -> Self { | 448 | pub fn new(stack: &'d Stack<D>, state: &'d TcpClientState<N, TX_SZ, RX_SZ>) -> Self { |
| 364 | Self { stack, state } | 449 | Self { stack, state } |
| 365 | } | 450 | } |
| @@ -396,6 +481,7 @@ pub mod client { | |||
| 396 | } | 481 | } |
| 397 | } | 482 | } |
| 398 | 483 | ||
| 484 | /// Opened TCP connection in a [`TcpClient`]. | ||
| 399 | pub struct TcpConnection<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> { | 485 | pub struct TcpConnection<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> { |
| 400 | socket: TcpSocket<'d>, | 486 | socket: TcpSocket<'d>, |
| 401 | state: &'d TcpClientState<N, TX_SZ, RX_SZ>, | 487 | state: &'d TcpClientState<N, TX_SZ, RX_SZ>, |
| @@ -454,6 +540,7 @@ pub mod client { | |||
| 454 | } | 540 | } |
| 455 | 541 | ||
| 456 | impl<const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpClientState<N, TX_SZ, RX_SZ> { | 542 | impl<const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpClientState<N, TX_SZ, RX_SZ> { |
| 543 | /// Create a new `TcpClientState`. | ||
| 457 | pub const fn new() -> Self { | 544 | pub const fn new() -> Self { |
| 458 | Self { pool: Pool::new() } | 545 | Self { pool: Pool::new() } |
| 459 | } | 546 | } |
diff --git a/embassy-net/src/time.rs b/embassy-net/src/time.rs new file mode 100644 index 000000000..b98d40fdc --- /dev/null +++ b/embassy-net/src/time.rs | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | #![allow(unused)] | ||
| 2 | |||
| 3 | use embassy_time::{Duration, Instant}; | ||
| 4 | use smoltcp::time::{Duration as SmolDuration, Instant as SmolInstant}; | ||
| 5 | |||
| 6 | pub(crate) fn instant_to_smoltcp(instant: Instant) -> SmolInstant { | ||
| 7 | SmolInstant::from_micros(instant.as_micros() as i64) | ||
| 8 | } | ||
| 9 | |||
| 10 | pub(crate) fn instant_from_smoltcp(instant: SmolInstant) -> Instant { | ||
| 11 | Instant::from_micros(instant.total_micros() as u64) | ||
| 12 | } | ||
| 13 | |||
| 14 | pub(crate) fn duration_to_smoltcp(duration: Duration) -> SmolDuration { | ||
| 15 | SmolDuration::from_micros(duration.as_micros()) | ||
| 16 | } | ||
| 17 | |||
| 18 | pub(crate) fn duration_from_smoltcp(duration: SmolDuration) -> Duration { | ||
| 19 | Duration::from_micros(duration.total_micros()) | ||
| 20 | } | ||
diff --git a/embassy-net/src/udp.rs b/embassy-net/src/udp.rs index 476aef12f..c9843cfe8 100644 --- a/embassy-net/src/udp.rs +++ b/embassy-net/src/udp.rs | |||
| @@ -1,3 +1,5 @@ | |||
| 1 | //! UDP sockets. | ||
| 2 | |||
| 1 | use core::cell::RefCell; | 3 | use core::cell::RefCell; |
| 2 | use core::future::poll_fn; | 4 | use core::future::poll_fn; |
| 3 | use core::mem; | 5 | use core::mem; |
| @@ -5,11 +7,13 @@ use core::task::Poll; | |||
| 5 | 7 | ||
| 6 | use embassy_net_driver::Driver; | 8 | use embassy_net_driver::Driver; |
| 7 | use smoltcp::iface::{Interface, SocketHandle}; | 9 | use smoltcp::iface::{Interface, SocketHandle}; |
| 8 | use smoltcp::socket::udp::{self, PacketMetadata}; | 10 | use smoltcp::socket::udp; |
| 11 | pub use smoltcp::socket::udp::PacketMetadata; | ||
| 9 | use smoltcp::wire::{IpEndpoint, IpListenEndpoint}; | 12 | use smoltcp::wire::{IpEndpoint, IpListenEndpoint}; |
| 10 | 13 | ||
| 11 | use crate::{SocketStack, Stack}; | 14 | use crate::{SocketStack, Stack}; |
| 12 | 15 | ||
| 16 | /// Error returned by [`UdpSocket::bind`]. | ||
| 13 | #[derive(PartialEq, Eq, Clone, Copy, Debug)] | 17 | #[derive(PartialEq, Eq, Clone, Copy, Debug)] |
| 14 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | 18 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] |
| 15 | pub enum BindError { | 19 | pub enum BindError { |
| @@ -19,6 +23,7 @@ pub enum BindError { | |||
| 19 | NoRoute, | 23 | NoRoute, |
| 20 | } | 24 | } |
| 21 | 25 | ||
| 26 | /// Error returned by [`UdpSocket::recv_from`] and [`UdpSocket::send_to`]. | ||
| 22 | #[derive(PartialEq, Eq, Clone, Copy, Debug)] | 27 | #[derive(PartialEq, Eq, Clone, Copy, Debug)] |
| 23 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | 28 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] |
| 24 | pub enum Error { | 29 | pub enum Error { |
| @@ -26,12 +31,14 @@ pub enum Error { | |||
| 26 | NoRoute, | 31 | NoRoute, |
| 27 | } | 32 | } |
| 28 | 33 | ||
| 34 | /// An UDP socket. | ||
| 29 | pub struct UdpSocket<'a> { | 35 | pub struct UdpSocket<'a> { |
| 30 | stack: &'a RefCell<SocketStack>, | 36 | stack: &'a RefCell<SocketStack>, |
| 31 | handle: SocketHandle, | 37 | handle: SocketHandle, |
| 32 | } | 38 | } |
| 33 | 39 | ||
| 34 | impl<'a> UdpSocket<'a> { | 40 | impl<'a> UdpSocket<'a> { |
| 41 | /// Create a new UDP socket using the provided stack and buffers. | ||
| 35 | pub fn new<D: Driver>( | 42 | pub fn new<D: Driver>( |
| 36 | stack: &'a Stack<D>, | 43 | stack: &'a Stack<D>, |
| 37 | rx_meta: &'a mut [PacketMetadata], | 44 | rx_meta: &'a mut [PacketMetadata], |
| @@ -56,6 +63,7 @@ impl<'a> UdpSocket<'a> { | |||
| 56 | } | 63 | } |
| 57 | } | 64 | } |
| 58 | 65 | ||
| 66 | /// Bind the socket to a local endpoint. | ||
| 59 | pub fn bind<T>(&mut self, endpoint: T) -> Result<(), BindError> | 67 | pub fn bind<T>(&mut self, endpoint: T) -> Result<(), BindError> |
| 60 | where | 68 | where |
| 61 | T: Into<IpListenEndpoint>, | 69 | T: Into<IpListenEndpoint>, |
| @@ -88,6 +96,11 @@ impl<'a> UdpSocket<'a> { | |||
| 88 | res | 96 | res |
| 89 | } | 97 | } |
| 90 | 98 | ||
| 99 | /// Receive a datagram. | ||
| 100 | /// | ||
| 101 | /// This method will wait until a datagram is received. | ||
| 102 | /// | ||
| 103 | /// Returns the number of bytes received and the remote endpoint. | ||
| 91 | pub async fn recv_from(&self, buf: &mut [u8]) -> Result<(usize, IpEndpoint), Error> { | 104 | pub async fn recv_from(&self, buf: &mut [u8]) -> Result<(usize, IpEndpoint), Error> { |
| 92 | poll_fn(move |cx| { | 105 | poll_fn(move |cx| { |
| 93 | self.with_mut(|s, _| match s.recv_slice(buf) { | 106 | self.with_mut(|s, _| match s.recv_slice(buf) { |
| @@ -102,6 +115,7 @@ impl<'a> UdpSocket<'a> { | |||
| 102 | .await | 115 | .await |
| 103 | } | 116 | } |
| 104 | 117 | ||
| 118 | /// Send a datagram to the specified remote endpoint. | ||
| 105 | pub async fn send_to<T>(&self, buf: &[u8], remote_endpoint: T) -> Result<(), Error> | 119 | pub async fn send_to<T>(&self, buf: &[u8], remote_endpoint: T) -> Result<(), Error> |
| 106 | where | 120 | where |
| 107 | T: Into<IpEndpoint>, | 121 | T: Into<IpEndpoint>, |
| @@ -121,22 +135,28 @@ impl<'a> UdpSocket<'a> { | |||
| 121 | .await | 135 | .await |
| 122 | } | 136 | } |
| 123 | 137 | ||
| 138 | /// Returns the local endpoint of the socket. | ||
| 124 | pub fn endpoint(&self) -> IpListenEndpoint { | 139 | pub fn endpoint(&self) -> IpListenEndpoint { |
| 125 | self.with(|s, _| s.endpoint()) | 140 | self.with(|s, _| s.endpoint()) |
| 126 | } | 141 | } |
| 127 | 142 | ||
| 143 | /// Returns whether the socket is open. | ||
| 144 | |||
| 128 | pub fn is_open(&self) -> bool { | 145 | pub fn is_open(&self) -> bool { |
| 129 | self.with(|s, _| s.is_open()) | 146 | self.with(|s, _| s.is_open()) |
| 130 | } | 147 | } |
| 131 | 148 | ||
| 149 | /// Close the socket. | ||
| 132 | pub fn close(&mut self) { | 150 | pub fn close(&mut self) { |
| 133 | self.with_mut(|s, _| s.close()) | 151 | self.with_mut(|s, _| s.close()) |
| 134 | } | 152 | } |
| 135 | 153 | ||
| 154 | /// Returns whether the socket is ready to send data, i.e. it has enough buffer space to hold a packet. | ||
| 136 | pub fn may_send(&self) -> bool { | 155 | pub fn may_send(&self) -> bool { |
| 137 | self.with(|s, _| s.can_send()) | 156 | self.with(|s, _| s.can_send()) |
| 138 | } | 157 | } |
| 139 | 158 | ||
| 159 | /// Returns whether the socket is ready to receive data, i.e. it has received a packet that's now in the buffer. | ||
| 140 | pub fn may_recv(&self) -> bool { | 160 | pub fn may_recv(&self) -> bool { |
| 141 | self.with(|s, _| s.can_recv()) | 161 | self.with(|s, _| s.can_recv()) |
| 142 | } | 162 | } |
diff --git a/examples/nrf52840/src/bin/usb_ethernet.rs b/examples/nrf52840/src/bin/usb_ethernet.rs index b8a72313a..786025c43 100644 --- a/examples/nrf52840/src/bin/usb_ethernet.rs +++ b/examples/nrf52840/src/bin/usb_ethernet.rs | |||
| @@ -132,7 +132,7 @@ async fn main(spawner: Spawner) { | |||
| 132 | 132 | ||
| 133 | loop { | 133 | loop { |
| 134 | let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); | 134 | let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); |
| 135 | socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); | 135 | socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); |
| 136 | 136 | ||
| 137 | info!("Listening on TCP:1234..."); | 137 | info!("Listening on TCP:1234..."); |
| 138 | if let Err(e) = socket.accept(1234).await { | 138 | if let Err(e) = socket.accept(1234).await { |
diff --git a/examples/rp/src/bin/usb_ethernet.rs b/examples/rp/src/bin/usb_ethernet.rs index 66a6ed4d0..431db63eb 100644 --- a/examples/rp/src/bin/usb_ethernet.rs +++ b/examples/rp/src/bin/usb_ethernet.rs | |||
| @@ -114,7 +114,7 @@ async fn main(spawner: Spawner) { | |||
| 114 | 114 | ||
| 115 | loop { | 115 | loop { |
| 116 | let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); | 116 | let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); |
| 117 | socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); | 117 | socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); |
| 118 | 118 | ||
| 119 | info!("Listening on TCP:1234..."); | 119 | info!("Listening on TCP:1234..."); |
| 120 | if let Err(e) = socket.accept(1234).await { | 120 | if let Err(e) = socket.accept(1234).await { |
diff --git a/examples/std/src/bin/net.rs b/examples/std/src/bin/net.rs index e018e18c9..d93616254 100644 --- a/examples/std/src/bin/net.rs +++ b/examples/std/src/bin/net.rs | |||
| @@ -6,6 +6,7 @@ use clap::Parser; | |||
| 6 | use embassy_executor::{Executor, Spawner}; | 6 | use embassy_executor::{Executor, Spawner}; |
| 7 | use embassy_net::tcp::TcpSocket; | 7 | use embassy_net::tcp::TcpSocket; |
| 8 | use embassy_net::{Config, Ipv4Address, Ipv4Cidr, Stack, StackResources}; | 8 | use embassy_net::{Config, Ipv4Address, Ipv4Cidr, Stack, StackResources}; |
| 9 | use embassy_time::Duration; | ||
| 9 | use embedded_io::asynch::Write; | 10 | use embedded_io::asynch::Write; |
| 10 | use heapless::Vec; | 11 | use heapless::Vec; |
| 11 | use log::*; | 12 | use log::*; |
| @@ -75,7 +76,7 @@ async fn main_task(spawner: Spawner) { | |||
| 75 | let mut tx_buffer = [0; 4096]; | 76 | let mut tx_buffer = [0; 4096]; |
| 76 | let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); | 77 | let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); |
| 77 | 78 | ||
| 78 | socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); | 79 | socket.set_timeout(Some(Duration::from_secs(10))); |
| 79 | 80 | ||
| 80 | let remote_endpoint = (Ipv4Address::new(192, 168, 69, 100), 8000); | 81 | let remote_endpoint = (Ipv4Address::new(192, 168, 69, 100), 8000); |
| 81 | info!("connecting to {:?}...", remote_endpoint); | 82 | info!("connecting to {:?}...", remote_endpoint); |
diff --git a/examples/std/src/bin/net_udp.rs b/examples/std/src/bin/net_udp.rs index 328a0536c..4df23edf6 100644 --- a/examples/std/src/bin/net_udp.rs +++ b/examples/std/src/bin/net_udp.rs | |||
| @@ -2,8 +2,8 @@ | |||
| 2 | 2 | ||
| 3 | use clap::Parser; | 3 | use clap::Parser; |
| 4 | use embassy_executor::{Executor, Spawner}; | 4 | use embassy_executor::{Executor, Spawner}; |
| 5 | use embassy_net::udp::UdpSocket; | 5 | use embassy_net::udp::{PacketMetadata, UdpSocket}; |
| 6 | use embassy_net::{Config, Ipv4Address, Ipv4Cidr, PacketMetadata, Stack, StackResources}; | 6 | use embassy_net::{Config, Ipv4Address, Ipv4Cidr, Stack, StackResources}; |
| 7 | use heapless::Vec; | 7 | use heapless::Vec; |
| 8 | use log::*; | 8 | use log::*; |
| 9 | use rand_core::{OsRng, RngCore}; | 9 | use rand_core::{OsRng, RngCore}; |
diff --git a/examples/stm32f4/src/bin/usb_ethernet.rs b/examples/stm32f4/src/bin/usb_ethernet.rs index db9e18393..9131e5896 100644 --- a/examples/stm32f4/src/bin/usb_ethernet.rs +++ b/examples/stm32f4/src/bin/usb_ethernet.rs | |||
| @@ -126,7 +126,7 @@ async fn main(spawner: Spawner) { | |||
| 126 | 126 | ||
| 127 | loop { | 127 | loop { |
| 128 | let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); | 128 | let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); |
| 129 | socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); | 129 | socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); |
| 130 | 130 | ||
| 131 | info!("Listening on TCP:1234..."); | 131 | info!("Listening on TCP:1234..."); |
| 132 | if let Err(e) = socket.accept(1234).await { | 132 | if let Err(e) = socket.accept(1234).await { |
diff --git a/examples/stm32f7/src/bin/eth.rs b/examples/stm32f7/src/bin/eth.rs index 9febb14e6..b947361ac 100644 --- a/examples/stm32f7/src/bin/eth.rs +++ b/examples/stm32f7/src/bin/eth.rs | |||
| @@ -91,7 +91,7 @@ async fn main(spawner: Spawner) -> ! { | |||
| 91 | loop { | 91 | loop { |
| 92 | let mut socket = TcpSocket::new(&stack, &mut rx_buffer, &mut tx_buffer); | 92 | let mut socket = TcpSocket::new(&stack, &mut rx_buffer, &mut tx_buffer); |
| 93 | 93 | ||
| 94 | socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); | 94 | socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); |
| 95 | 95 | ||
| 96 | let remote_endpoint = (Ipv4Address::new(10, 42, 0, 1), 8000); | 96 | let remote_endpoint = (Ipv4Address::new(10, 42, 0, 1), 8000); |
| 97 | info!("connecting..."); | 97 | info!("connecting..."); |
diff --git a/examples/stm32h5/src/bin/eth.rs b/examples/stm32h5/src/bin/eth.rs index 6d650da9e..b2e252fc7 100644 --- a/examples/stm32h5/src/bin/eth.rs +++ b/examples/stm32h5/src/bin/eth.rs | |||
| @@ -110,7 +110,7 @@ async fn main(spawner: Spawner) -> ! { | |||
| 110 | loop { | 110 | loop { |
| 111 | let mut socket = TcpSocket::new(&stack, &mut rx_buffer, &mut tx_buffer); | 111 | let mut socket = TcpSocket::new(&stack, &mut rx_buffer, &mut tx_buffer); |
| 112 | 112 | ||
| 113 | socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); | 113 | socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); |
| 114 | 114 | ||
| 115 | let remote_endpoint = (Ipv4Address::new(10, 42, 0, 1), 8000); | 115 | let remote_endpoint = (Ipv4Address::new(10, 42, 0, 1), 8000); |
| 116 | info!("connecting..."); | 116 | info!("connecting..."); |
diff --git a/examples/stm32h7/src/bin/eth.rs b/examples/stm32h7/src/bin/eth.rs index 541e49762..61bb7e37b 100644 --- a/examples/stm32h7/src/bin/eth.rs +++ b/examples/stm32h7/src/bin/eth.rs | |||
| @@ -92,7 +92,7 @@ async fn main(spawner: Spawner) -> ! { | |||
| 92 | loop { | 92 | loop { |
| 93 | let mut socket = TcpSocket::new(&stack, &mut rx_buffer, &mut tx_buffer); | 93 | let mut socket = TcpSocket::new(&stack, &mut rx_buffer, &mut tx_buffer); |
| 94 | 94 | ||
| 95 | socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); | 95 | socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); |
| 96 | 96 | ||
| 97 | let remote_endpoint = (Ipv4Address::new(10, 42, 0, 1), 8000); | 97 | let remote_endpoint = (Ipv4Address::new(10, 42, 0, 1), 8000); |
| 98 | info!("connecting..."); | 98 | info!("connecting..."); |
diff --git a/examples/stm32l5/src/bin/usb_ethernet.rs b/examples/stm32l5/src/bin/usb_ethernet.rs index 98ec0e836..6c5645a41 100644 --- a/examples/stm32l5/src/bin/usb_ethernet.rs +++ b/examples/stm32l5/src/bin/usb_ethernet.rs | |||
| @@ -121,7 +121,7 @@ async fn main(spawner: Spawner) { | |||
| 121 | 121 | ||
| 122 | loop { | 122 | loop { |
| 123 | let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); | 123 | let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); |
| 124 | socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); | 124 | socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); |
| 125 | 125 | ||
| 126 | info!("Listening on TCP:1234..."); | 126 | info!("Listening on TCP:1234..."); |
| 127 | if let Err(e) = socket.accept(1234).await { | 127 | if let Err(e) = socket.accept(1234).await { |
