#![no_std]
#![allow(async_fn_in_trait)]
#![warn(missing_docs)]
#![doc = include_str!("../README.md")]
//! ## Feature flags
#![doc = document_features::document_features!(feature_label = r#"{feature}"#)]
#[cfg(not(any(feature = "proto-ipv4", feature = "proto-ipv6")))]
compile_error!("You must enable at least one of the following features: proto-ipv4, proto-ipv6");
// This mod MUST go first, so that the others see its macros.
pub(crate) mod fmt;
#[cfg(feature = "dns")]
pub mod dns;
mod driver_util;
#[cfg(feature = "icmp")]
pub mod icmp;
#[cfg(feature = "raw")]
pub mod raw;
#[cfg(feature = "tcp")]
pub mod tcp;
mod time;
#[cfg(feature = "udp")]
pub mod udp;
use core::cell::RefCell;
use core::future::{poll_fn, Future};
use core::mem::MaybeUninit;
use core::pin::pin;
use core::task::{Context, Poll};
pub use embassy_net_driver as driver;
use embassy_net_driver::{Driver, LinkState};
use embassy_sync::waitqueue::WakerRegistration;
use embassy_time::{Instant, Timer};
use heapless::Vec;
#[cfg(feature = "dns")]
pub use smoltcp::config::DNS_MAX_SERVER_COUNT;
#[cfg(feature = "multicast")]
pub use smoltcp::iface::MulticastError;
#[cfg(any(feature = "dns", feature = "dhcpv4"))]
use smoltcp::iface::SocketHandle;
use smoltcp::iface::{Interface, SocketSet, SocketStorage};
use smoltcp::phy::Medium;
#[cfg(feature = "dhcpv4")]
use smoltcp::socket::dhcpv4::{self, RetryConfig};
#[cfg(feature = "medium-ethernet")]
pub use smoltcp::wire::EthernetAddress;
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154", feature = "medium-ip"))]
pub use smoltcp::wire::HardwareAddress;
#[cfg(any(feature = "udp", feature = "tcp"))]
pub use smoltcp::wire::IpListenEndpoint;
#[cfg(feature = "medium-ieee802154")]
pub use smoltcp::wire::{Ieee802154Address, Ieee802154Frame};
pub use smoltcp::wire::{IpAddress, IpCidr, IpEndpoint};
#[cfg(feature = "proto-ipv4")]
pub use smoltcp::wire::{Ipv4Address, Ipv4Cidr};
#[cfg(feature = "proto-ipv6")]
pub use smoltcp::wire::{Ipv6Address, Ipv6Cidr};
use crate::driver_util::DriverAdapter;
use crate::time::{instant_from_smoltcp, instant_to_smoltcp};
const LOCAL_PORT_MIN: u16 = 1025;
const LOCAL_PORT_MAX: u16 = 65535;
#[cfg(feature = "dns")]
const MAX_QUERIES: usize = 4;
#[cfg(feature = "dhcpv4-hostname")]
const MAX_HOSTNAME_LEN: usize = 32;
/// Memory resources needed for a network stack.
pub struct StackResources {
    sockets: MaybeUninit<[SocketStorage<'static>; SOCK]>,
    inner: MaybeUninit>,
    #[cfg(feature = "dns")]
    queries: MaybeUninit<[Option; MAX_QUERIES]>,
    #[cfg(feature = "dhcpv4-hostname")]
    hostname: HostnameResources,
}
#[cfg(feature = "dhcpv4-hostname")]
struct HostnameResources {
    option: MaybeUninit>,
    data: MaybeUninit<[u8; MAX_HOSTNAME_LEN]>,
}
impl StackResources {
    /// Create a new set of stack resources.
    pub const fn new() -> Self {
        Self {
            sockets: MaybeUninit::uninit(),
            inner: MaybeUninit::uninit(),
            #[cfg(feature = "dns")]
            queries: MaybeUninit::uninit(),
            #[cfg(feature = "dhcpv4-hostname")]
            hostname: HostnameResources {
                option: MaybeUninit::uninit(),
                data: MaybeUninit::uninit(),
            },
        }
    }
}
/// Static IP address configuration.
#[cfg(feature = "proto-ipv4")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StaticConfigV4 {
    /// IP address and subnet mask.
    pub address: Ipv4Cidr,
    /// Default gateway.
    pub gateway: Option,
    /// DNS servers.
    pub dns_servers: Vec,
}
/// Static IPv6 address configuration
#[cfg(feature = "proto-ipv6")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StaticConfigV6 {
    /// IP address and subnet mask.
    pub address: Ipv6Cidr,
    /// Default gateway.
    pub gateway: Option,
    /// DNS servers.
    pub dns_servers: Vec,
}
/// DHCP configuration.
#[cfg(feature = "dhcpv4")]
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
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 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. This is almost always 67. Do not change unless you know what you're doing.
    pub server_port: u16,
    /// Client port. This is almost always 68. Do not change unless you know what you're doing.
    pub client_port: u16,
    /// Our hostname. This will be sent to the DHCP server as Option 12.
    #[cfg(feature = "dhcpv4-hostname")]
    pub hostname: Option>,
}
#[cfg(feature = "dhcpv4")]
impl Default for DhcpConfig {
    fn default() -> Self {
        Self {
            max_lease_duration: Default::default(),
            retry_config: Default::default(),
            ignore_naks: Default::default(),
            server_port: smoltcp::wire::DHCP_SERVER_PORT,
            client_port: smoltcp::wire::DHCP_CLIENT_PORT,
            #[cfg(feature = "dhcpv4-hostname")]
            hostname: None,
        }
    }
}
/// Network stack configuration.
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct Config {
    /// IPv4 configuration
    #[cfg(feature = "proto-ipv4")]
    pub ipv4: ConfigV4,
    /// IPv6 configuration
    #[cfg(feature = "proto-ipv6")]
    pub ipv6: ConfigV6,
}
impl Config {
    /// IPv4 configuration with static addressing.
    #[cfg(feature = "proto-ipv4")]
    pub const fn ipv4_static(config: StaticConfigV4) -> Self {
        Self {
            ipv4: ConfigV4::Static(config),
            #[cfg(feature = "proto-ipv6")]
            ipv6: ConfigV6::None,
        }
    }
    /// IPv6 configuration with static addressing.
    #[cfg(feature = "proto-ipv6")]
    pub const fn ipv6_static(config: StaticConfigV6) -> Self {
        Self {
            #[cfg(feature = "proto-ipv4")]
            ipv4: ConfigV4::None,
            ipv6: ConfigV6::Static(config),
        }
    }
    /// IPv4 configuration with dynamic addressing.
    ///
    /// # Example
    /// ```rust
    /// # use embassy_net::Config;
    /// let _cfg = Config::dhcpv4(Default::default());
    /// ```
    #[cfg(feature = "dhcpv4")]
    pub const fn dhcpv4(config: DhcpConfig) -> Self {
        Self {
            ipv4: ConfigV4::Dhcp(config),
            #[cfg(feature = "proto-ipv6")]
            ipv6: ConfigV6::None,
        }
    }
}
/// Network stack IPv4 configuration.
#[cfg(feature = "proto-ipv4")]
#[derive(Debug, Clone, Default)]
pub enum ConfigV4 {
    /// Do not configure IPv4.
    #[default]
    None,
    /// Use a static IPv4 address configuration.
    Static(StaticConfigV4),
    /// Use DHCP to obtain an IP address configuration.
    #[cfg(feature = "dhcpv4")]
    Dhcp(DhcpConfig),
}
/// Network stack IPv6 configuration.
#[cfg(feature = "proto-ipv6")]
#[derive(Debug, Clone, Default)]
pub enum ConfigV6 {
    /// Do not configure IPv6.
    #[default]
    None,
    /// Use a static IPv6 address configuration.
    Static(StaticConfigV6),
}
/// Network stack runner.
///
/// You must call [`Runner::run()`] in a background task for the network stack to work.
pub struct Runner<'d, D: Driver> {
    driver: D,
    stack: Stack<'d>,
}
/// Network stack handle
///
/// Use this to create sockets. It's `Copy`, so you can pass
/// it by value instead of by reference.
#[derive(Copy, Clone)]
pub struct Stack<'d> {
    inner: &'d RefCell,
}
pub(crate) struct Inner {
    pub(crate) sockets: SocketSet<'static>, // Lifetime type-erased.
    pub(crate) iface: Interface,
    /// Waker used for triggering polls.
    pub(crate) waker: WakerRegistration,
    /// Waker used for waiting for link up or config up.
    state_waker: WakerRegistration,
    hardware_address: HardwareAddress,
    next_local_port: u16,
    link_up: bool,
    #[cfg(feature = "proto-ipv4")]
    static_v4: Option,
    #[cfg(feature = "proto-ipv6")]
    static_v6: Option,
    #[cfg(feature = "dhcpv4")]
    dhcp_socket: Option,
    #[cfg(feature = "dns")]
    dns_socket: SocketHandle,
    #[cfg(feature = "dns")]
    dns_waker: WakerRegistration,
    #[cfg(feature = "dhcpv4-hostname")]
    hostname: *mut HostnameResources,
}
fn _assert_covariant<'a, 'b: 'a>(x: Stack<'b>) -> Stack<'a> {
    x
}
/// Create a new network stack.
pub fn new<'d, D: Driver, const SOCK: usize>(
    mut driver: D,
    config: Config,
    resources: &'d mut StackResources,
    random_seed: u64,
) -> (Stack<'d>, Runner<'d, D>) {
    let (hardware_address, medium) = to_smoltcp_hardware_address(driver.hardware_address());
    let mut iface_cfg = smoltcp::iface::Config::new(hardware_address);
    iface_cfg.random_seed = random_seed;
    let iface = Interface::new(
        iface_cfg,
        &mut DriverAdapter {
            inner: &mut driver,
            cx: None,
            medium,
        },
        instant_to_smoltcp(Instant::now()),
    );
    unsafe fn transmute_slice(x: &mut [T]) -> &'static mut [T] {
        core::mem::transmute(x)
    }
    let sockets = resources.sockets.write([SocketStorage::EMPTY; SOCK]);
    #[allow(unused_mut)]
    let mut sockets: SocketSet<'static> = SocketSet::new(unsafe { transmute_slice(sockets) });
    let next_local_port = (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN;
    #[cfg(feature = "dns")]
    let dns_socket = sockets.add(dns::Socket::new(
        &[],
        managed::ManagedSlice::Borrowed(unsafe {
            transmute_slice(resources.queries.write([const { None }; MAX_QUERIES]))
        }),
    ));
    let mut inner = Inner {
        sockets,
        iface,
        waker: WakerRegistration::new(),
        state_waker: WakerRegistration::new(),
        next_local_port,
        hardware_address,
        link_up: false,
        #[cfg(feature = "proto-ipv4")]
        static_v4: None,
        #[cfg(feature = "proto-ipv6")]
        static_v6: None,
        #[cfg(feature = "dhcpv4")]
        dhcp_socket: None,
        #[cfg(feature = "dns")]
        dns_socket,
        #[cfg(feature = "dns")]
        dns_waker: WakerRegistration::new(),
        #[cfg(feature = "dhcpv4-hostname")]
        hostname: &mut resources.hostname,
    };
    #[cfg(feature = "proto-ipv4")]
    inner.set_config_v4(config.ipv4);
    #[cfg(feature = "proto-ipv6")]
    inner.set_config_v6(config.ipv6);
    inner.apply_static_config();
    let inner = &*resources.inner.write(RefCell::new(inner));
    let stack = Stack { inner };
    (stack, Runner { driver, stack })
}
fn to_smoltcp_hardware_address(addr: driver::HardwareAddress) -> (HardwareAddress, Medium) {
    match addr {
        #[cfg(feature = "medium-ethernet")]
        driver::HardwareAddress::Ethernet(eth) => (HardwareAddress::Ethernet(EthernetAddress(eth)), Medium::Ethernet),
        #[cfg(feature = "medium-ieee802154")]
        driver::HardwareAddress::Ieee802154(ieee) => (
            HardwareAddress::Ieee802154(Ieee802154Address::Extended(ieee)),
            Medium::Ieee802154,
        ),
        #[cfg(feature = "medium-ip")]
        driver::HardwareAddress::Ip => (HardwareAddress::Ip, Medium::Ip),
        #[allow(unreachable_patterns)]
        _ => panic!(
            "Unsupported medium {:?}. Make sure to enable the right medium feature in embassy-net's Cargo features.",
            addr
        ),
    }
}
impl<'d> Stack<'d> {
    fn with(&self, f: impl FnOnce(&Inner) -> R) -> R {
        f(&self.inner.borrow())
    }
    fn with_mut(&self, f: impl FnOnce(&mut Inner) -> R) -> R {
        f(&mut self.inner.borrow_mut())
    }
    /// Get the hardware address of the network interface.
    pub fn hardware_address(&self) -> HardwareAddress {
        self.with(|i| i.hardware_address)
    }
    /// Check whether the link is up.
    pub fn is_link_up(&self) -> bool {
        self.with(|i| i.link_up)
    }
    /// Check 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 {
        let v4_up;
        let v6_up;
        #[cfg(feature = "proto-ipv4")]
        {
            v4_up = self.config_v4().is_some();
        }
        #[cfg(not(feature = "proto-ipv4"))]
        {
            v4_up = false;
        }
        #[cfg(feature = "proto-ipv6")]
        {
            v6_up = self.config_v6().is_some();
        }
        #[cfg(not(feature = "proto-ipv6"))]
        {
            v6_up = false;
        }
        v4_up || v6_up
    }
    /// Wait for the network device to obtain a link signal.
    pub async fn wait_link_up(&self) {
        self.wait(|| self.is_link_up()).await
    }
    /// Wait for the network device to lose link signal.
    pub async fn wait_link_down(&self) {
        self.wait(|| !self.is_link_up()).await
    }
    /// Wait for the network stack to obtain a valid IP configuration.
    ///
    /// ## Notes:
    /// - Ensure [`Runner::run`] has been started before using this function.
    ///
    /// - This function may never return (e.g. if no configuration is obtained through DHCP).
    /// The caller is supposed to handle a timeout for this case.
    ///
    /// ## Example
    /// ```ignore
    /// let config = embassy_net::Config::dhcpv4(Default::default());
    /// // Init network stack
    /// // NOTE: DHCP and DNS need one socket slot if enabled. This is why we're
    /// // provisioning space for 3 sockets here: one for DHCP, one for DNS, and one for your code (e.g. TCP).
    /// // If you use more sockets you must increase this. If you don't enable DHCP or DNS you can decrease it.
    /// static RESOURCES: StaticCell> = StaticCell::new();
    /// let (stack, runner) = embassy_net::new(
    ///    driver,
    ///    config,
    ///    RESOURCES.init(embassy_net::StackResources::new()),
    ///    seed
    /// );
    /// // Launch network task that runs `runner.run().await`
    /// spawner.spawn(net_task(runner)).unwrap();
    /// // Wait for DHCP config
    /// stack.wait_config_up().await;
    /// // use the network stack
    /// // ...
    /// ```
    pub async fn wait_config_up(&self) {
        self.wait(|| self.is_config_up()).await
    }
    /// Wait for the network stack to lose a valid IP configuration.
    pub async fn wait_config_down(&self) {
        self.wait(|| !self.is_config_up()).await
    }
    fn wait<'a>(&'a self, mut predicate: impl FnMut() -> bool + 'a) -> impl Future