├── .gitignore ├── README.md ├── Cargo.toml ├── src ├── fmt.rs └── lib.rs └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Non-async Networking primitives for TCP/UDP communication 2 | 3 | This is basically just ripped out of [esp-wifi](https://github.com/esp-rs/esp-hal/blob/v0.21.1/esp-wifi/src/wifi_interface.rs) 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "blocking-network-stack" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | smoltcp = { version = "0.12.0", default-features = false, features = [ 8 | "medium-ethernet", 9 | "socket-raw", 10 | ] } 11 | 12 | heapless = { version = "0.8.0", default-features = false, features = [ 13 | "portable-atomic", 14 | ] } 15 | 16 | embedded-io = { version = "0.6.1", default-features = false } 17 | 18 | defmt = { version = "0.3.9", optional = true } 19 | log = { version = "0.4.22", optional = true } 20 | serde = { version = "1.0.215", default-features = false, features = [ 21 | "derive", 22 | ], optional = true } 23 | 24 | 25 | [features] 26 | # todo remove log 27 | default = ["dhcpv4", "dns", "icmp", "ipv4", "log", "multicast", "tcp", "udp"] 28 | 29 | ## IPv6 support. Includes utils feature 30 | ipv6 = ["smoltcp/proto-ipv6"] 31 | 32 | ## IPv4 support. Includes utils feature 33 | ipv4 = ["smoltcp/proto-ipv4"] 34 | 35 | ## TCP socket support. Includes ipv4 feature 36 | tcp = ["ipv4", "smoltcp/socket-tcp"] 37 | 38 | ## UDP socket support. Includes ipv4 feature 39 | udp = ["ipv4", "smoltcp/socket-udp"] 40 | 41 | ## ICMP socket support. Includes ipv4 feature 42 | icmp = ["ipv4", "smoltcp/socket-icmp"] 43 | 44 | ## DNS support. Includes udp feature 45 | dns = ["smoltcp/proto-dns", "smoltcp/socket-dns", "udp"] 46 | 47 | ## DHCPv4 support, both creating sockets and autoconfiguring network settings. Includes utils feature 48 | dhcpv4 = ["smoltcp/proto-dhcpv4", "smoltcp/socket-dhcpv4"] 49 | 50 | ## Enable support for `defmt` 51 | defmt = ["dep:defmt", "smoltcp/defmt"] 52 | 53 | ## Enable support for the `log` crate 54 | log = ["dep:log", "smoltcp/log"] 55 | 56 | ## Multicast support. 57 | multicast = ["smoltcp/multicast"] 58 | 59 | # Implement serde Serialize / Deserialize 60 | serde = ["dep:serde", "heapless/serde"] 61 | -------------------------------------------------------------------------------- /src/fmt.rs: -------------------------------------------------------------------------------- 1 | #![macro_use] 2 | #![allow(unused_macros)] 3 | 4 | #[cfg(all(feature = "defmt", feature = "log"))] 5 | compile_error!("You may not enable both `defmt` and `log` features."); 6 | 7 | macro_rules! assert { 8 | ($($x:tt)*) => { 9 | { 10 | #[cfg(not(feature = "defmt"))] 11 | ::core::assert!($($x)*); 12 | #[cfg(feature = "defmt")] 13 | ::defmt::assert!($($x)*); 14 | } 15 | }; 16 | } 17 | 18 | macro_rules! assert_eq { 19 | ($($x:tt)*) => { 20 | { 21 | #[cfg(not(feature = "defmt"))] 22 | ::core::assert_eq!($($x)*); 23 | #[cfg(feature = "defmt")] 24 | ::defmt::assert_eq!($($x)*); 25 | } 26 | }; 27 | } 28 | 29 | macro_rules! assert_ne { 30 | ($($x:tt)*) => { 31 | { 32 | #[cfg(not(feature = "defmt"))] 33 | ::core::assert_ne!($($x)*); 34 | #[cfg(feature = "defmt")] 35 | ::defmt::assert_ne!($($x)*); 36 | } 37 | }; 38 | } 39 | 40 | macro_rules! debug_assert { 41 | ($($x:tt)*) => { 42 | { 43 | #[cfg(not(feature = "defmt"))] 44 | ::core::debug_assert!($($x)*); 45 | #[cfg(feature = "defmt")] 46 | ::defmt::debug_assert!($($x)*); 47 | } 48 | }; 49 | } 50 | 51 | macro_rules! debug_assert_eq { 52 | ($($x:tt)*) => { 53 | { 54 | #[cfg(not(feature = "defmt"))] 55 | ::core::debug_assert_eq!($($x)*); 56 | #[cfg(feature = "defmt")] 57 | ::defmt::debug_assert_eq!($($x)*); 58 | } 59 | }; 60 | } 61 | 62 | macro_rules! debug_assert_ne { 63 | ($($x:tt)*) => { 64 | { 65 | #[cfg(not(feature = "defmt"))] 66 | ::core::debug_assert_ne!($($x)*); 67 | #[cfg(feature = "defmt")] 68 | ::defmt::debug_assert_ne!($($x)*); 69 | } 70 | }; 71 | } 72 | 73 | macro_rules! todo { 74 | ($($x:tt)*) => { 75 | { 76 | #[cfg(not(feature = "defmt"))] 77 | ::core::todo!($($x)*); 78 | #[cfg(feature = "defmt")] 79 | ::defmt::todo!($($x)*); 80 | } 81 | }; 82 | } 83 | 84 | macro_rules! unreachable { 85 | ($($x:tt)*) => { 86 | { 87 | #[cfg(not(feature = "defmt"))] 88 | ::core::unreachable!($($x)*); 89 | #[cfg(feature = "defmt")] 90 | ::defmt::unreachable!($($x)*); 91 | } 92 | }; 93 | } 94 | 95 | macro_rules! panic { 96 | ($($x:tt)*) => { 97 | { 98 | #[cfg(not(feature = "defmt"))] 99 | ::core::panic!($($x)*); 100 | #[cfg(feature = "defmt")] 101 | ::defmt::panic!($($x)*); 102 | } 103 | }; 104 | } 105 | 106 | macro_rules! trace { 107 | ($s:literal $(, $x:expr)* $(,)?) => { 108 | { 109 | #[cfg(feature = "log")] 110 | ::log::trace!($s $(, $x)*); 111 | #[cfg(feature = "defmt")] 112 | ::defmt::trace!($s $(, $x)*); 113 | #[cfg(not(any(feature = "log", feature="defmt")))] 114 | let _ = ($( & $x ),*); 115 | } 116 | }; 117 | } 118 | 119 | macro_rules! debug { 120 | ($s:literal $(, $x:expr)* $(,)?) => { 121 | { 122 | #[cfg(feature = "log")] 123 | ::log::debug!($s $(, $x)*); 124 | #[cfg(feature = "defmt")] 125 | ::defmt::debug!($s $(, $x)*); 126 | #[cfg(not(any(feature = "log", feature="defmt")))] 127 | let _ = ($( & $x ),*); 128 | } 129 | }; 130 | } 131 | 132 | macro_rules! info { 133 | ($s:literal $(, $x:expr)* $(,)?) => { 134 | { 135 | #[cfg(feature = "log")] 136 | ::log::info!($s $(, $x)*); 137 | #[cfg(feature = "defmt")] 138 | ::defmt::info!($s $(, $x)*); 139 | #[cfg(not(any(feature = "log", feature="defmt")))] 140 | let _ = ($( & $x ),*); 141 | } 142 | }; 143 | } 144 | 145 | macro_rules! warn { 146 | ($s:literal $(, $x:expr)* $(,)?) => { 147 | { 148 | #[cfg(feature = "log")] 149 | ::log::warn!($s $(, $x)*); 150 | #[cfg(feature = "defmt")] 151 | ::defmt::warn!($s $(, $x)*); 152 | #[cfg(not(any(feature = "log", feature="defmt")))] 153 | let _ = ($( & $x ),*); 154 | } 155 | }; 156 | } 157 | 158 | macro_rules! error { 159 | ($s:literal $(, $x:expr)* $(,)?) => { 160 | { 161 | #[cfg(feature = "log")] 162 | ::log::error!($s $(, $x)*); 163 | #[cfg(feature = "defmt")] 164 | ::defmt::error!($s $(, $x)*); 165 | #[cfg(not(any(feature = "log", feature="defmt")))] 166 | let _ = ($( & $x ),*); 167 | } 168 | }; 169 | } 170 | 171 | #[cfg(feature = "defmt")] 172 | macro_rules! unwrap { 173 | ($($x:tt)*) => { 174 | ::defmt::unwrap!($($x)*) 175 | }; 176 | } 177 | 178 | #[cfg(not(feature = "defmt"))] 179 | macro_rules! unwrap { 180 | ($arg:expr) => { 181 | match $crate::fmt::Try::into_result($arg) { 182 | ::core::result::Result::Ok(t) => t, 183 | ::core::result::Result::Err(e) => { 184 | ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); 185 | } 186 | } 187 | }; 188 | ($arg:expr, $($msg:expr),+ $(,)? ) => { 189 | match $crate::fmt::Try::into_result($arg) { 190 | ::core::result::Result::Ok(t) => t, 191 | ::core::result::Result::Err(e) => { 192 | ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); 193 | } 194 | } 195 | } 196 | } 197 | 198 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 199 | pub struct NoneError; 200 | 201 | pub trait Try { 202 | type Ok; 203 | type Error; 204 | #[allow(unused)] 205 | fn into_result(self) -> Result; 206 | } 207 | 208 | impl Try for Option { 209 | type Ok = T; 210 | type Error = NoneError; 211 | 212 | #[inline] 213 | fn into_result(self) -> Result { 214 | self.ok_or(NoneError) 215 | } 216 | } 217 | 218 | impl Try for Result { 219 | type Ok = T; 220 | type Error = E; 221 | 222 | #[inline] 223 | fn into_result(self) -> Self { 224 | self 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "bitflags" 7 | version = "1.3.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 10 | 11 | [[package]] 12 | name = "blocking-network-stack" 13 | version = "0.1.0" 14 | dependencies = [ 15 | "defmt", 16 | "embedded-io", 17 | "heapless", 18 | "log", 19 | "serde", 20 | "smoltcp", 21 | ] 22 | 23 | [[package]] 24 | name = "byteorder" 25 | version = "1.5.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 28 | 29 | [[package]] 30 | name = "cfg-if" 31 | version = "1.0.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 34 | 35 | [[package]] 36 | name = "defmt" 37 | version = "0.3.9" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "9112096fbf91d9458d3dc1bca22e87e2684cad608c032f80135e2471614ceebe" 40 | dependencies = [ 41 | "bitflags", 42 | "defmt-macros", 43 | ] 44 | 45 | [[package]] 46 | name = "defmt-macros" 47 | version = "0.3.10" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "9e40098d5ffbf1b3c88dff66c3b168a7dc4f3e8ff8604a00441660040a134e20" 50 | dependencies = [ 51 | "defmt-parser", 52 | "proc-macro-error2", 53 | "proc-macro2", 54 | "quote", 55 | "syn", 56 | ] 57 | 58 | [[package]] 59 | name = "defmt-parser" 60 | version = "0.4.1" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "3983b127f13995e68c1e29071e5d115cd96f215ccb5e6812e3728cd6f92653b3" 63 | dependencies = [ 64 | "thiserror", 65 | ] 66 | 67 | [[package]] 68 | name = "embedded-io" 69 | version = "0.6.1" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" 72 | 73 | [[package]] 74 | name = "hash32" 75 | version = "0.3.1" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" 78 | dependencies = [ 79 | "byteorder", 80 | ] 81 | 82 | [[package]] 83 | name = "heapless" 84 | version = "0.8.0" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" 87 | dependencies = [ 88 | "defmt", 89 | "hash32", 90 | "portable-atomic", 91 | "serde", 92 | "stable_deref_trait", 93 | ] 94 | 95 | [[package]] 96 | name = "log" 97 | version = "0.4.22" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 100 | 101 | [[package]] 102 | name = "managed" 103 | version = "0.8.0" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" 106 | 107 | [[package]] 108 | name = "portable-atomic" 109 | version = "1.10.0" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" 112 | 113 | [[package]] 114 | name = "proc-macro-error-attr2" 115 | version = "2.0.0" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" 118 | dependencies = [ 119 | "proc-macro2", 120 | "quote", 121 | ] 122 | 123 | [[package]] 124 | name = "proc-macro-error2" 125 | version = "2.0.1" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" 128 | dependencies = [ 129 | "proc-macro-error-attr2", 130 | "proc-macro2", 131 | "quote", 132 | "syn", 133 | ] 134 | 135 | [[package]] 136 | name = "proc-macro2" 137 | version = "1.0.92" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" 140 | dependencies = [ 141 | "unicode-ident", 142 | ] 143 | 144 | [[package]] 145 | name = "quote" 146 | version = "1.0.37" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 149 | dependencies = [ 150 | "proc-macro2", 151 | ] 152 | 153 | [[package]] 154 | name = "serde" 155 | version = "1.0.215" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" 158 | dependencies = [ 159 | "serde_derive", 160 | ] 161 | 162 | [[package]] 163 | name = "serde_derive" 164 | version = "1.0.215" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" 167 | dependencies = [ 168 | "proc-macro2", 169 | "quote", 170 | "syn", 171 | ] 172 | 173 | [[package]] 174 | name = "smoltcp" 175 | version = "0.12.0" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "dad095989c1533c1c266d9b1e8d70a1329dd3723c3edac6d03bbd67e7bf6f4bb" 178 | dependencies = [ 179 | "bitflags", 180 | "byteorder", 181 | "cfg-if", 182 | "defmt", 183 | "heapless", 184 | "log", 185 | "managed", 186 | ] 187 | 188 | [[package]] 189 | name = "stable_deref_trait" 190 | version = "1.2.0" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 193 | 194 | [[package]] 195 | name = "syn" 196 | version = "2.0.89" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" 199 | dependencies = [ 200 | "proc-macro2", 201 | "quote", 202 | "unicode-ident", 203 | ] 204 | 205 | [[package]] 206 | name = "thiserror" 207 | version = "2.0.3" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" 210 | dependencies = [ 211 | "thiserror-impl", 212 | ] 213 | 214 | [[package]] 215 | name = "thiserror-impl" 216 | version = "2.0.3" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" 219 | dependencies = [ 220 | "proc-macro2", 221 | "quote", 222 | "syn", 223 | ] 224 | 225 | [[package]] 226 | name = "unicode-ident" 227 | version = "1.0.14" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 230 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Non-async Networking primitives for TCP/UDP communication. 2 | 3 | #![no_std] 4 | 5 | // MUST be the first module 6 | mod fmt; 7 | 8 | use core::{borrow::BorrowMut, cell::RefCell, fmt::Display}; 9 | 10 | #[cfg(feature = "dhcpv4")] 11 | use smoltcp::socket::dhcpv4::Socket as Dhcpv4Socket; 12 | #[cfg(feature = "tcp")] 13 | use smoltcp::socket::tcp::Socket as TcpSocket; 14 | #[cfg(feature = "dns")] 15 | use smoltcp::wire::DnsQueryType; 16 | #[cfg(feature = "udp")] 17 | use smoltcp::wire::IpEndpoint; 18 | use smoltcp::{ 19 | iface::{Interface, PollResult, SocketHandle, SocketSet}, 20 | time::Instant, 21 | wire::{IpAddress, IpCidr, Ipv4Address}, 22 | }; 23 | 24 | #[cfg(feature = "tcp")] 25 | const LOCAL_PORT_MIN: u16 = 41000; 26 | #[cfg(feature = "tcp")] 27 | const LOCAL_PORT_MAX: u16 = 65535; 28 | 29 | /// Non-async TCP/IP network stack 30 | /// 31 | /// Mostly a convenience wrapper for `smoltcp` 32 | pub struct Stack<'a, D: smoltcp::phy::Device> { 33 | device: RefCell, 34 | network_interface: RefCell, 35 | sockets: RefCell>, 36 | current_millis_fn: fn() -> u64, 37 | #[cfg(feature = "tcp")] 38 | local_port: RefCell, 39 | pub(crate) network_config: RefCell, 40 | pub(crate) ip_info: RefCell>, 41 | #[cfg(feature = "dhcpv4")] 42 | pub(crate) dhcp_socket_handle: RefCell>, 43 | #[cfg(feature = "dhcpv4")] 44 | pub(crate) reset_dhcp: RefCell, 45 | #[cfg(feature = "dns")] 46 | dns_socket_handle: RefCell>, 47 | } 48 | 49 | impl<'a, D: smoltcp::phy::Device> Stack<'a, D> { 50 | /// Creates new `WifiStack` instance. 51 | /// 52 | /// Handles optional DHCP/DNS features and sets up the 53 | /// configuration for the network interface. 54 | pub fn new( 55 | network_interface: Interface, 56 | device: D, 57 | #[allow(unused_mut)] mut sockets: SocketSet<'a>, 58 | current_millis_fn: fn() -> u64, 59 | random: u32, 60 | ) -> Stack<'a, D> { 61 | #[cfg(feature = "dhcpv4")] 62 | let mut dhcp_socket_handle: Option = None; 63 | #[cfg(feature = "dns")] 64 | let mut dns_socket_handle: Option = None; 65 | 66 | #[cfg(any(feature = "dhcpv4", feature = "dns"))] 67 | for (handle, socket) in sockets.iter_mut() { 68 | match socket { 69 | #[cfg(feature = "dhcpv4")] 70 | smoltcp::socket::Socket::Dhcpv4(_) => dhcp_socket_handle = Some(handle), 71 | #[cfg(feature = "dns")] 72 | smoltcp::socket::Socket::Dns(_) => dns_socket_handle = Some(handle), 73 | _ => {} 74 | } 75 | } 76 | 77 | let this = Self { 78 | device: RefCell::new(device), 79 | network_interface: RefCell::new(network_interface), 80 | network_config: RefCell::new(ipv4::Configuration::Client( 81 | ipv4::ClientConfiguration::DHCP(ipv4::DHCPClientSettings { 82 | // FIXME: smoltcp currently doesn't have a way of giving a hostname through DHCP 83 | hostname: Some(unwrap!("Espressif".try_into().ok())), 84 | }), 85 | )), 86 | ip_info: RefCell::new(None), 87 | #[cfg(feature = "dhcpv4")] 88 | dhcp_socket_handle: RefCell::new(dhcp_socket_handle), 89 | #[cfg(feature = "dhcpv4")] 90 | reset_dhcp: RefCell::new(false), 91 | sockets: RefCell::new(sockets), 92 | current_millis_fn, 93 | #[cfg(feature = "tcp")] 94 | local_port: RefCell::new( 95 | (random % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u32) as u16 + LOCAL_PORT_MIN, 96 | ), 97 | #[cfg(feature = "dns")] 98 | dns_socket_handle: RefCell::new(dns_socket_handle), 99 | }; 100 | 101 | this.reset(); 102 | 103 | this 104 | } 105 | 106 | /// Update the interface configuration 107 | pub fn update_iface_configuration( 108 | &self, 109 | conf: &ipv4::Configuration, 110 | ) -> Result<(), WifiStackError> { 111 | let hw_address = self.network_interface.borrow_mut().hardware_addr(); 112 | self.network_interface 113 | .borrow_mut() 114 | .set_hardware_addr(hw_address); 115 | info!("Set hardware address: {:?}", hw_address); 116 | 117 | self.reset(); // reset IP address 118 | 119 | #[cfg(feature = "dhcpv4")] 120 | { 121 | let mut dhcp_socket_handle_ref = self.dhcp_socket_handle.borrow_mut(); 122 | let mut sockets_ref = self.sockets.borrow_mut(); 123 | 124 | if let Some(dhcp_handle) = *dhcp_socket_handle_ref { 125 | // remove the DHCP client if we use a static IP 126 | if matches!( 127 | conf, 128 | ipv4::Configuration::Client(ipv4::ClientConfiguration::Fixed(_)) 129 | ) { 130 | sockets_ref.remove(dhcp_handle); 131 | *dhcp_socket_handle_ref = None; 132 | } 133 | } 134 | 135 | // re-add the DHCP client if we use DHCP and it has been removed before 136 | if matches!( 137 | conf, 138 | ipv4::Configuration::Client(ipv4::ClientConfiguration::DHCP(_)) 139 | ) && dhcp_socket_handle_ref.is_none() 140 | { 141 | let dhcp_socket = Dhcpv4Socket::new(); 142 | let dhcp_socket_handle = sockets_ref.add(dhcp_socket); 143 | *dhcp_socket_handle_ref = Some(dhcp_socket_handle); 144 | } 145 | 146 | if let Some(dhcp_handle) = *dhcp_socket_handle_ref { 147 | let dhcp_socket = sockets_ref.get_mut::(dhcp_handle); 148 | info!("Reset DHCP client"); 149 | dhcp_socket.reset(); 150 | } 151 | } 152 | 153 | *self.network_config.borrow_mut() = conf.clone(); 154 | Ok(()) 155 | } 156 | 157 | /// Reset DHCP 158 | #[cfg(feature = "dhcpv4")] 159 | pub fn reset_dhcp(&self) { 160 | *self.reset_dhcp.borrow_mut() = true; 161 | } 162 | 163 | /// Reset the stack 164 | pub fn reset(&self) { 165 | debug!("Reset TCP stack"); 166 | 167 | #[cfg(feature = "dhcpv4")] 168 | { 169 | let dhcp_socket_handle_ref = self.dhcp_socket_handle.borrow_mut(); 170 | if let Some(dhcp_handle) = *dhcp_socket_handle_ref { 171 | self.with_mut(|_, _, sockets| { 172 | let dhcp_socket = sockets.get_mut::(dhcp_handle); 173 | debug!("Reset DHCP client"); 174 | dhcp_socket.reset(); 175 | }); 176 | } 177 | } 178 | 179 | self.with_mut(|interface, _, _| { 180 | interface.routes_mut().remove_default_ipv4_route(); 181 | interface.update_ip_addrs(|addrs| { 182 | addrs.clear(); 183 | }); 184 | 185 | #[cfg(feature = "ipv6")] 186 | { 187 | unwrap!(interface.routes_mut().add_default_ipv6_route( 188 | smoltcp::wire::Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0,) 189 | )); 190 | 191 | let mut mac = [0u8; 6]; 192 | match interface.hardware_addr() { 193 | smoltcp::wire::HardwareAddress::Ethernet(hw_address) => { 194 | mac.copy_from_slice(hw_address.as_bytes()); 195 | } 196 | } 197 | 198 | let a4 = ((mac[0] ^ 2) as u16) << 8 | mac[1] as u16; 199 | let a5 = (mac[2] as u16) << 8 | 0xff; 200 | let a6 = 0xfe << 8 | mac[3] as u16; 201 | let a7 = (mac[4] as u16) << 8 | mac[5] as u16; 202 | 203 | info!( 204 | "IPv6 link-local address fe80::{:x}:{:x}:{:x}:{:x}", 205 | a4, a5, a6, a7 206 | ); 207 | 208 | interface.update_ip_addrs(|addrs| { 209 | unwrap!(addrs.push(IpCidr::new( 210 | smoltcp::wire::IpAddress::v6(0xfe80, 0, 0, 0, a4, a5, a6, a7), 211 | 64, 212 | ))); 213 | }); 214 | } 215 | }); 216 | } 217 | 218 | /// Retrieve all current IP addresses 219 | pub fn get_ip_addresses(&self, f: impl FnOnce(&[smoltcp::wire::IpCidr])) { 220 | self.with_mut(|interface, _, _| f(interface.ip_addrs())) 221 | } 222 | 223 | /// Convenience function to poll the DHCP socket. 224 | #[cfg(feature = "dhcpv4")] 225 | pub fn poll_dhcp( 226 | &self, 227 | interface: &mut Interface, 228 | sockets: &mut SocketSet<'a>, 229 | ) -> Result<(), WifiStackError> { 230 | let dhcp_socket_handle_ref = self.dhcp_socket_handle.borrow_mut(); 231 | if let Some(dhcp_handle) = *dhcp_socket_handle_ref { 232 | let dhcp_socket = sockets.get_mut::(dhcp_handle); 233 | 234 | if *self.reset_dhcp.borrow() { 235 | *self.reset_dhcp.borrow_mut() = false; 236 | dhcp_socket.reset(); 237 | } 238 | 239 | let event = dhcp_socket.poll(); 240 | if let Some(event) = event { 241 | match event { 242 | smoltcp::socket::dhcpv4::Event::Deconfigured => { 243 | *self.ip_info.borrow_mut() = None; 244 | interface.routes_mut().remove_default_ipv4_route(); 245 | } 246 | smoltcp::socket::dhcpv4::Event::Configured(config) => { 247 | let dns = config.dns_servers.first(); 248 | *self.ip_info.borrow_mut() = Some(ipv4::IpInfo { 249 | ip: config.address.address().into(), 250 | subnet: ipv4::Subnet { 251 | gateway: unwrap!(config.router).into(), 252 | mask: ipv4::Mask(config.address.prefix_len()), 253 | }, 254 | dns: dns.map(|x| (*x).into()), 255 | secondary_dns: config.dns_servers.get(1).map(|x| (*x).into()), 256 | }); 257 | 258 | let address = config.address; 259 | interface.borrow_mut().update_ip_addrs(|addrs| { 260 | unwrap!(addrs.push(smoltcp::wire::IpCidr::Ipv4(address))); 261 | }); 262 | if let Some(route) = config.router { 263 | unwrap!(interface.routes_mut().add_default_ipv4_route(route)); 264 | } 265 | 266 | #[cfg(feature = "dns")] 267 | if let (Some(&dns), Some(dns_handle)) = 268 | (dns, *self.dns_socket_handle.borrow()) 269 | { 270 | sockets 271 | .get_mut::(dns_handle) 272 | .update_servers(&[dns.into()]); 273 | } 274 | } 275 | } 276 | } 277 | } 278 | 279 | Ok(()) 280 | } 281 | 282 | /// Create a new [Socket] 283 | #[cfg(feature = "tcp")] 284 | pub fn get_socket<'s>( 285 | &'s self, 286 | rx_buffer: &'a mut [u8], 287 | tx_buffer: &'a mut [u8], 288 | ) -> Socket<'s, 'a, D> 289 | where 290 | 'a: 's, 291 | { 292 | let socket = TcpSocket::new( 293 | smoltcp::socket::tcp::SocketBuffer::new(rx_buffer), 294 | smoltcp::socket::tcp::SocketBuffer::new(tx_buffer), 295 | ); 296 | 297 | let socket_handle = 298 | self.with_mut(|_interface, _device, sockets| sockets.borrow_mut().add(socket)); 299 | 300 | Socket { 301 | socket_handle, 302 | network: self, 303 | } 304 | } 305 | 306 | /// Create a new [UdpSocket] 307 | #[cfg(feature = "udp")] 308 | pub fn get_udp_socket<'s>( 309 | &'s self, 310 | rx_meta: &'a mut [smoltcp::socket::udp::PacketMetadata], 311 | rx_buffer: &'a mut [u8], 312 | tx_meta: &'a mut [smoltcp::socket::udp::PacketMetadata], 313 | tx_buffer: &'a mut [u8], 314 | ) -> UdpSocket<'s, 'a, D> 315 | where 316 | 'a: 's, 317 | { 318 | let socket = smoltcp::socket::udp::Socket::new( 319 | smoltcp::socket::udp::PacketBuffer::new(rx_meta, rx_buffer), 320 | smoltcp::socket::udp::PacketBuffer::new(tx_meta, tx_buffer), 321 | ); 322 | 323 | let socket_handle = 324 | self.with_mut(|_interface, _device, sockets| sockets.borrow_mut().add(socket)); 325 | 326 | UdpSocket { 327 | socket_handle, 328 | network: self, 329 | } 330 | } 331 | 332 | /// Check if DNS is configured 333 | #[cfg(feature = "dns")] 334 | pub fn is_dns_configured(&self) -> bool { 335 | self.dns_socket_handle.borrow().is_some() 336 | } 337 | 338 | /// Configure DNS 339 | #[cfg(feature = "dns")] 340 | pub fn configure_dns( 341 | &'a self, 342 | servers: &[IpAddress], 343 | query_storage: &'a mut [Option], 344 | ) { 345 | if let Some(old_handle) = self.dns_socket_handle.take() { 346 | self.with_mut(|_interface, _device, sockets| sockets.remove(old_handle)); 347 | // the returned socket get dropped and frees a slot for the new one 348 | } 349 | 350 | let dns = smoltcp::socket::dns::Socket::new(servers, query_storage); 351 | let handle = self.with_mut(|_interface, _device, sockets| sockets.add(dns)); 352 | self.dns_socket_handle.replace(Some(handle)); 353 | } 354 | 355 | /// Update the DNS servers 356 | #[cfg(feature = "dns")] 357 | pub fn update_dns_servers(&self, servers: &[IpAddress]) { 358 | if let Some(dns_handle) = *self.dns_socket_handle.borrow_mut() { 359 | self.with_mut(|_interface, _device, sockets| { 360 | sockets 361 | .get_mut::(dns_handle) 362 | .update_servers(servers); 363 | }); 364 | } 365 | } 366 | 367 | /// Perform a DNS query 368 | #[cfg(feature = "dns")] 369 | pub fn dns_query( 370 | &self, 371 | name: &str, 372 | query_type: DnsQueryType, 373 | ) -> Result, WifiStackError> 374 | { 375 | use smoltcp::socket::dns; 376 | 377 | match query_type { 378 | // check if name is already an IP 379 | DnsQueryType::A => { 380 | if let Ok(ip) = name.parse::() { 381 | return Ok([ip.into()].into_iter().collect()); 382 | } 383 | } 384 | #[cfg(feature = "ipv6")] 385 | DnsQueryType::Aaaa => { 386 | if let Ok(ip) = name.parse::() { 387 | return Ok([ip.into()].into_iter().collect()); 388 | } 389 | } 390 | _ => {} 391 | } 392 | 393 | let Some(dns_handle) = *self.dns_socket_handle.borrow() else { 394 | return Err(WifiStackError::DnsNotConfigured); 395 | }; 396 | 397 | let query = self.with_mut(|interface, _device, sockets| { 398 | sockets 399 | .get_mut::(dns_handle) 400 | .start_query(interface.context(), name, query_type) 401 | .map_err(WifiStackError::DnsQueryError) 402 | })?; 403 | 404 | loop { 405 | self.work(); 406 | 407 | let result = self.with_mut(|_interface, _device, sockets| { 408 | sockets 409 | .get_mut::(dns_handle) 410 | .get_query_result(query) 411 | }); 412 | 413 | match result { 414 | Ok(addrs) => return Ok(addrs), // query finished 415 | Err(dns::GetQueryResultError::Pending) => {} // query not finished 416 | Err(_) => return Err(WifiStackError::DnsQueryFailed), 417 | } 418 | } 419 | } 420 | 421 | /// Let the stack make progress 422 | /// 423 | /// Make sure to regularly call this function. 424 | pub fn work(&self) { 425 | loop { 426 | let poll_result = self.with_mut(|interface, device, sockets| { 427 | let network_config = self.network_config.borrow().clone(); 428 | if let ipv4::Configuration::Client(ipv4::ClientConfiguration::DHCP(_)) = 429 | network_config 430 | { 431 | #[cfg(feature = "dhcpv4")] 432 | self.poll_dhcp(interface, sockets).ok(); 433 | } else if let ipv4::Configuration::Client(ipv4::ClientConfiguration::Fixed( 434 | settings, 435 | )) = network_config 436 | { 437 | let addr = Ipv4Address::from(settings.ip.octets()); 438 | if !interface.has_ip_addr(addr) { 439 | let gateway = Ipv4Address::from(settings.subnet.gateway.octets()); 440 | interface.routes_mut().add_default_ipv4_route(gateway).ok(); 441 | interface.update_ip_addrs(|addrs| { 442 | unwrap!(addrs.push(IpCidr::new(addr.into(), settings.subnet.mask.0))); 443 | }); 444 | } 445 | } 446 | interface.poll( 447 | Instant::from_millis((self.current_millis_fn)() as i64), 448 | device, 449 | sockets, 450 | ) 451 | }); 452 | 453 | if poll_result == PollResult::None { 454 | break; 455 | } 456 | } 457 | } 458 | 459 | #[cfg(feature = "tcp")] 460 | fn next_local_port(&self) -> u16 { 461 | self.local_port.replace_with(|local_port| { 462 | if *local_port == LOCAL_PORT_MAX { 463 | LOCAL_PORT_MIN 464 | } else { 465 | *local_port + 1 466 | } 467 | }); 468 | *self.local_port.borrow() 469 | } 470 | 471 | #[allow(unused)] 472 | fn with(&self, f: impl FnOnce(&Interface, &D, &SocketSet<'a>) -> R) -> R { 473 | f( 474 | &self.network_interface.borrow(), 475 | &self.device.borrow(), 476 | &self.sockets.borrow(), 477 | ) 478 | } 479 | 480 | fn with_mut(&self, f: impl FnOnce(&mut Interface, &mut D, &mut SocketSet<'a>) -> R) -> R { 481 | f( 482 | &mut self.network_interface.borrow_mut(), 483 | &mut self.device.borrow_mut(), 484 | &mut self.sockets.borrow_mut(), 485 | ) 486 | } 487 | } 488 | 489 | /// Errors returned by functions in this module 490 | #[derive(Debug, Copy, Clone)] 491 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 492 | pub enum WifiStackError { 493 | /// An unknown error occurred, with the associated error code. 494 | Unknown(i32), 495 | 496 | /// An error occurred during Wi-Fi stack initialization. 497 | InitializationError, 498 | 499 | /// A common Wi-Fi error occured. 500 | DeviceError, 501 | 502 | /// Couldn't get the device's IP. 503 | MissingIp, 504 | 505 | /// DNS is not configured. 506 | #[cfg(feature = "dns")] 507 | DnsNotConfigured, 508 | 509 | /// An error occurred when starting a DNS query. 510 | #[cfg(feature = "dns")] 511 | DnsQueryError(smoltcp::socket::dns::StartQueryError), 512 | 513 | /// Cannot get result from a DNS query. 514 | #[cfg(feature = "dns")] 515 | DnsQueryFailed, 516 | } 517 | 518 | impl Display for WifiStackError { 519 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 520 | write!(f, "{:?}", self) 521 | } 522 | } 523 | 524 | impl Stack<'_, D> { 525 | /// Retrieves the current interface configuration. 526 | pub fn get_iface_configuration(&self) -> Result { 527 | Ok(self.network_config.borrow().clone()) 528 | } 529 | 530 | /// Sets a new interface configuration using the provided IPv4 531 | /// configuration. 532 | pub fn set_iface_configuration( 533 | &mut self, 534 | conf: &ipv4::Configuration, 535 | ) -> Result<(), WifiStackError> { 536 | self.update_iface_configuration(conf) 537 | } 538 | 539 | /// Checks if the interface is up (has IP information). 540 | pub fn is_iface_up(&self) -> bool { 541 | self.ip_info.borrow().is_some() 542 | } 543 | 544 | /// Retrieves the current IP information (IP address, subnet info). 545 | pub fn get_ip_info(&self) -> Result { 546 | self.ip_info.borrow().ok_or(WifiStackError::MissingIp) 547 | } 548 | } 549 | 550 | /// A TCP socket 551 | #[cfg(feature = "tcp")] 552 | pub struct Socket<'s, 'n: 's, D: smoltcp::phy::Device> { 553 | socket_handle: SocketHandle, 554 | network: &'s Stack<'n, D>, 555 | } 556 | 557 | #[cfg(feature = "tcp")] 558 | impl<'s, 'n: 's, D: smoltcp::phy::Device> Socket<'s, 'n, D> { 559 | /// Connect the socket 560 | pub fn open<'i>(&'i mut self, addr: IpAddress, port: u16) -> Result<(), IoError> 561 | where 562 | 's: 'i, 563 | { 564 | { 565 | let res = self.network.with_mut(|interface, _device, sockets| { 566 | let sock = sockets.get_mut::(self.socket_handle); 567 | let cx = interface.context(); 568 | let remote_endpoint = (addr, port); 569 | sock.set_ack_delay(Some(smoltcp::time::Duration::from_millis(100))); 570 | sock.connect(cx, remote_endpoint, self.network.next_local_port()) 571 | }); 572 | 573 | res.map_err(IoError::ConnectError)?; 574 | } 575 | 576 | loop { 577 | let can_send = self.network.with_mut(|_interface, _device, sockets| { 578 | let sock = sockets.get_mut::(self.socket_handle); 579 | sock.can_send() 580 | }); 581 | 582 | if can_send { 583 | break; 584 | } 585 | 586 | self.work(); 587 | } 588 | 589 | Ok(()) 590 | } 591 | 592 | /// Listen on the given port. This blocks until there is a peer connected 593 | pub fn listen<'i>(&'i mut self, port: u16) -> Result<(), IoError> 594 | where 595 | 's: 'i, 596 | { 597 | { 598 | let res = self.network.with_mut(|_interface, _device, sockets| { 599 | let sock = sockets.get_mut::(self.socket_handle); 600 | sock.listen(port) 601 | }); 602 | 603 | res.map_err(IoError::ListenError)?; 604 | } 605 | 606 | loop { 607 | let can_send = self.network.with_mut(|_interface, _device, sockets| { 608 | let sock = sockets.get_mut::(self.socket_handle); 609 | sock.can_send() 610 | }); 611 | 612 | if can_send { 613 | break; 614 | } 615 | 616 | self.work(); 617 | } 618 | 619 | Ok(()) 620 | } 621 | 622 | /// Listen on the given port. This doesn't block 623 | pub fn listen_unblocking<'i>(&'i mut self, port: u16) -> Result<(), IoError> 624 | where 625 | 's: 'i, 626 | { 627 | { 628 | let res = self.network.with_mut(|_interface, _device, sockets| { 629 | let sock = sockets.get_mut::(self.socket_handle); 630 | sock.listen(port) 631 | }); 632 | 633 | res.map_err(IoError::ListenError)?; 634 | } 635 | 636 | self.work(); 637 | Ok(()) 638 | } 639 | 640 | /// Closes the socket 641 | pub fn close(&mut self) { 642 | self.network.with_mut(|_interface, _device, sockets| { 643 | sockets.get_mut::(self.socket_handle).close(); 644 | }); 645 | 646 | self.work(); 647 | } 648 | 649 | /// Disconnect the socket 650 | pub fn disconnect(&mut self) { 651 | self.network.with_mut(|_interface, _device, sockets| { 652 | sockets.get_mut::(self.socket_handle).abort(); 653 | }); 654 | 655 | self.work(); 656 | } 657 | 658 | /// Checks if the socket is currently open 659 | pub fn is_open(&mut self) -> bool { 660 | self.network.with_mut(|_interface, _device, sockets| { 661 | sockets.get_mut::(self.socket_handle).is_open() 662 | }) 663 | } 664 | 665 | /// Checks if the socket is currently connected 666 | pub fn is_connected(&mut self) -> bool { 667 | self.network.with_mut(|_interface, _device, sockets| { 668 | let socket = sockets.get_mut::(self.socket_handle); 669 | 670 | socket.may_recv() && socket.may_send() 671 | }) 672 | } 673 | 674 | /// Delegates to [WifiStack::work] 675 | pub fn work(&mut self) { 676 | self.network.work() 677 | } 678 | } 679 | 680 | #[cfg(feature = "tcp")] 681 | impl<'s, 'n: 's, D: smoltcp::phy::Device> Drop for Socket<'s, 'n, D> { 682 | fn drop(&mut self) { 683 | self.network 684 | .with_mut(|_interface, _device, sockets| sockets.remove(self.socket_handle)); 685 | } 686 | } 687 | 688 | /// IO Errors 689 | #[derive(Debug)] 690 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 691 | pub enum IoError { 692 | SocketClosed, 693 | #[cfg(feature = "multicast")] 694 | MultiCastError(smoltcp::iface::MulticastError), 695 | #[cfg(feature = "tcp")] 696 | TcpRecvError, 697 | #[cfg(feature = "udp")] 698 | UdpRecvError(smoltcp::socket::udp::RecvError), 699 | #[cfg(feature = "tcp")] 700 | TcpSendError(smoltcp::socket::tcp::SendError), 701 | #[cfg(feature = "udp")] 702 | UdpSendError(smoltcp::socket::udp::SendError), 703 | #[cfg(feature = "tcp")] 704 | ConnectError(smoltcp::socket::tcp::ConnectError), 705 | #[cfg(feature = "udp")] 706 | BindError(smoltcp::socket::udp::BindError), 707 | #[cfg(feature = "tcp")] 708 | ListenError(smoltcp::socket::tcp::ListenError), 709 | } 710 | 711 | impl embedded_io::Error for IoError { 712 | fn kind(&self) -> embedded_io::ErrorKind { 713 | embedded_io::ErrorKind::Other 714 | } 715 | } 716 | 717 | #[cfg(feature = "tcp")] 718 | impl<'s, 'n: 's, D: smoltcp::phy::Device> embedded_io::ErrorType for Socket<'s, 'n, D> { 719 | type Error = IoError; 720 | } 721 | 722 | #[cfg(feature = "tcp")] 723 | impl<'s, 'n: 's, D: smoltcp::phy::Device> embedded_io::Read for Socket<'s, 'n, D> { 724 | fn read(&mut self, buf: &mut [u8]) -> Result { 725 | let current_millis = self.network.current_millis_fn; 726 | 727 | self.network.with_mut(|interface, device, sockets| { 728 | use smoltcp::socket::tcp::RecvError; 729 | loop { 730 | let now = smoltcp::time::Instant::from_millis((current_millis)() as i64); 731 | interface.poll(now, device, sockets); 732 | let socket = sockets.get_mut::(self.socket_handle); 733 | 734 | match socket.recv_slice(buf) { 735 | Ok(0) => continue, // no data 736 | Ok(n) => return Ok(n), 737 | Err(RecvError::Finished) => return Err(IoError::SocketClosed), // eof 738 | Err(RecvError::InvalidState) => return Err(IoError::TcpRecvError), 739 | } 740 | } 741 | }) 742 | } 743 | } 744 | 745 | #[cfg(feature = "tcp")] 746 | impl<'s, 'n: 's, D: smoltcp::phy::Device> embedded_io::ReadReady for Socket<'s, 'n, D> { 747 | fn read_ready(&mut self) -> Result { 748 | let current_millis = self.network.current_millis_fn; 749 | 750 | self.network.with_mut(|interface, device, sockets| { 751 | use smoltcp::socket::tcp::RecvError; 752 | let now = smoltcp::time::Instant::from_millis((current_millis)() as i64); 753 | interface.poll(now, device, sockets); 754 | let socket = sockets.get_mut::(self.socket_handle); 755 | 756 | match socket.peek(1) { 757 | Ok(s) => Ok(!s.is_empty()), 758 | Err(RecvError::Finished) => Err(IoError::SocketClosed), 759 | Err(RecvError::InvalidState) => Err(IoError::TcpRecvError), 760 | } 761 | }) 762 | } 763 | } 764 | 765 | #[cfg(feature = "tcp")] 766 | impl<'s, 'n: 's, D: smoltcp::phy::Device> embedded_io::Write for Socket<'s, 'n, D> { 767 | fn write(&mut self, buf: &[u8]) -> Result { 768 | loop { 769 | let (may_send, is_open, can_send) = 770 | self.network.with_mut(|interface, device, sockets| { 771 | interface.poll( 772 | Instant::from_millis((self.network.current_millis_fn)() as i64), 773 | device, 774 | sockets, 775 | ); 776 | 777 | let socket = sockets.get_mut::(self.socket_handle); 778 | 779 | (socket.may_send(), socket.is_open(), socket.can_send()) 780 | }); 781 | 782 | if may_send { 783 | break; 784 | } 785 | 786 | if !is_open || !can_send { 787 | return Err(IoError::SocketClosed); 788 | } 789 | } 790 | 791 | let mut written = 0; 792 | loop { 793 | self.flush()?; 794 | 795 | self.network.with_mut(|_interface, _device, sockets| { 796 | sockets 797 | .get_mut::(self.socket_handle) 798 | .send_slice(&buf[written..]) 799 | .map(|len| written += len) 800 | .map_err(IoError::TcpSendError) 801 | })?; 802 | 803 | if written >= buf.len() { 804 | break; 805 | } 806 | } 807 | 808 | Ok(written) 809 | } 810 | 811 | fn flush(&mut self) -> Result<(), Self::Error> { 812 | loop { 813 | let poll_result = self.network.with_mut(|interface, device, sockets| { 814 | interface.poll( 815 | Instant::from_millis((self.network.current_millis_fn)() as i64), 816 | device, 817 | sockets, 818 | ) 819 | }); 820 | 821 | if poll_result == PollResult::None { 822 | break; 823 | } 824 | } 825 | 826 | Ok(()) 827 | } 828 | } 829 | 830 | #[cfg(feature = "tcp")] 831 | impl<'s, 'n: 's, D: smoltcp::phy::Device> embedded_io::WriteReady for Socket<'s, 'n, D> { 832 | fn write_ready(&mut self) -> Result { 833 | let (may_send, is_open, can_send) = self.network.with_mut(|interface, device, sockets| { 834 | interface.poll( 835 | Instant::from_millis((self.network.current_millis_fn)() as i64), 836 | device, 837 | sockets, 838 | ); 839 | 840 | let socket = sockets.get_mut::(self.socket_handle); 841 | 842 | (socket.may_send(), socket.is_open(), socket.can_send()) 843 | }); 844 | 845 | if !is_open || !can_send { 846 | return Err(IoError::SocketClosed); 847 | } 848 | 849 | Ok(may_send) 850 | } 851 | } 852 | 853 | /// A UDP socket 854 | #[cfg(feature = "udp")] 855 | pub struct UdpSocket<'s, 'n: 's, D: smoltcp::phy::Device> { 856 | socket_handle: SocketHandle, 857 | network: &'s Stack<'n, D>, 858 | } 859 | 860 | #[cfg(feature = "udp")] 861 | impl<'s, 'n: 's, D: smoltcp::phy::Device> UdpSocket<'s, 'n, D> { 862 | /// Binds the socket to the given port 863 | pub fn bind<'i>(&'i mut self, port: u16) -> Result<(), IoError> 864 | where 865 | 's: 'i, 866 | { 867 | self.work(); 868 | 869 | { 870 | let res = self.network.with_mut(|_interface, _device, sockets| { 871 | let sock = sockets.get_mut::(self.socket_handle); 872 | sock.bind(port) 873 | }); 874 | 875 | if let Err(err) = res { 876 | return Err(IoError::BindError(err)); 877 | } 878 | } 879 | 880 | loop { 881 | let can_send = self.network.with_mut(|_interface, _device, sockets| { 882 | let sock = sockets.get_mut::(self.socket_handle); 883 | sock.can_send() 884 | }); 885 | 886 | if can_send { 887 | break; 888 | } 889 | 890 | self.work(); 891 | } 892 | 893 | Ok(()) 894 | } 895 | 896 | /// Close the socket 897 | pub fn close(&mut self) { 898 | self.network.with_mut(|_interface, _device, sockets| { 899 | sockets 900 | .get_mut::(self.socket_handle) 901 | .close(); 902 | }); 903 | 904 | self.work(); 905 | } 906 | 907 | /// Sends data on the socket to the given address 908 | pub fn send(&mut self, addr: IpAddress, port: u16, data: &[u8]) -> Result<(), IoError> { 909 | loop { 910 | self.work(); 911 | 912 | let (can_send, packet_capacity, payload_capacity) = 913 | self.network.with_mut(|_interface, _device, sockets| { 914 | let sock = sockets.get_mut::(self.socket_handle); 915 | ( 916 | sock.can_send(), 917 | sock.packet_send_capacity(), 918 | sock.payload_send_capacity(), 919 | ) 920 | }); 921 | 922 | if can_send && packet_capacity > 0 && payload_capacity > data.len() { 923 | break; 924 | } 925 | } 926 | 927 | self.network 928 | .with_mut(|_interface, _device, sockets| { 929 | let endpoint = (addr, port); 930 | let endpoint: IpEndpoint = endpoint.into(); 931 | 932 | sockets 933 | .get_mut::(self.socket_handle) 934 | .send_slice(data, endpoint) 935 | }) 936 | .map_err(IoError::UdpSendError)?; 937 | 938 | self.work(); 939 | 940 | Ok(()) 941 | } 942 | 943 | /// Receives a single datagram message on the socket 944 | pub fn receive(&mut self, data: &mut [u8]) -> Result<(usize, IpAddress, u16), IoError> { 945 | self.work(); 946 | 947 | let res = self.network.with_mut(|_interface, _device, sockets| { 948 | sockets 949 | .get_mut::(self.socket_handle) 950 | .recv_slice(data) 951 | }); 952 | 953 | match res { 954 | Ok((len, endpoint)) => { 955 | let addr = endpoint.endpoint.addr; 956 | Ok((len, addr, endpoint.endpoint.port)) 957 | } 958 | Err(e) => Err(IoError::UdpRecvError(e)), 959 | } 960 | } 961 | 962 | /// This function specifies a new multicast group for this socket to join 963 | #[cfg(feature = "multicast")] 964 | pub fn join_multicast_group(&mut self, addr: IpAddress) -> Result<(), IoError> { 965 | self.work(); 966 | 967 | let res = self 968 | .network 969 | .with_mut(|interface, _, _| interface.join_multicast_group(addr)); 970 | 971 | self.work(); 972 | 973 | res.map_err(IoError::MultiCastError) 974 | } 975 | 976 | /// Delegates to [WifiStack::work] 977 | pub fn work(&mut self) { 978 | self.network.work() 979 | } 980 | } 981 | 982 | #[cfg(feature = "udp")] 983 | impl<'s, 'n: 's, D: smoltcp::phy::Device> Drop for UdpSocket<'s, 'n, D> { 984 | fn drop(&mut self) { 985 | self.network 986 | .with_mut(|_, _, sockets| sockets.borrow_mut().remove(self.socket_handle)); 987 | } 988 | } 989 | 990 | /// IPv4 network configurations. 991 | pub mod ipv4 { 992 | pub use core::net::Ipv4Addr; 993 | use core::{fmt::Display, str::FromStr}; 994 | 995 | #[cfg(feature = "serde")] 996 | use serde::{Deserialize, Serialize}; 997 | 998 | /// Represents a subnet mask. 999 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 1000 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 1001 | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] 1002 | pub struct Mask(pub u8); 1003 | 1004 | impl FromStr for Mask { 1005 | type Err = &'static str; 1006 | 1007 | fn from_str(s: &str) -> Result { 1008 | s.parse::() 1009 | .map_err(|_| "Invalid subnet mask") 1010 | .map_or_else(Err, |mask| { 1011 | if (1..=32).contains(&mask) { 1012 | Ok(Mask(mask)) 1013 | } else { 1014 | Err("Mask should be a number between 1 and 32") 1015 | } 1016 | }) 1017 | } 1018 | } 1019 | 1020 | impl Display for Mask { 1021 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 1022 | write!(f, "{}", self.0) 1023 | } 1024 | } 1025 | 1026 | impl TryFrom for Mask { 1027 | type Error = (); 1028 | 1029 | fn try_from(ip: Ipv4Addr) -> Result { 1030 | let octets = ip.octets(); 1031 | let addr: u32 = ((octets[0] as u32 & 0xff) << 24) 1032 | | ((octets[1] as u32 & 0xff) << 16) 1033 | | ((octets[2] as u32 & 0xff) << 8) 1034 | | (octets[3] as u32 & 0xff); 1035 | 1036 | if addr.leading_ones() + addr.trailing_zeros() == 32 { 1037 | Ok(Mask(addr.leading_ones() as u8)) 1038 | } else { 1039 | Err(()) 1040 | } 1041 | } 1042 | } 1043 | 1044 | impl From for Ipv4Addr { 1045 | fn from(mask: Mask) -> Self { 1046 | let addr: u32 = ((1 << (32 - mask.0)) - 1) ^ 0xffffffffu32; 1047 | 1048 | let (a, b, c, d) = ( 1049 | ((addr >> 24) & 0xff) as u8, 1050 | ((addr >> 16) & 0xff) as u8, 1051 | ((addr >> 8) & 0xff) as u8, 1052 | (addr & 0xff) as u8, 1053 | ); 1054 | 1055 | Ipv4Addr::new(a, b, c, d) 1056 | } 1057 | } 1058 | 1059 | /// Represents a subnet consisting of a gateway and a mask. 1060 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 1061 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 1062 | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] 1063 | pub struct Subnet { 1064 | /// The gateway IP address of the subnet. 1065 | #[cfg_attr(feature = "defmt", defmt(Debug2Format))] 1066 | pub gateway: Ipv4Addr, 1067 | /// The subnet mask associated with the subnet. 1068 | pub mask: Mask, 1069 | } 1070 | 1071 | impl Display for Subnet { 1072 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 1073 | write!(f, "{}/{}", self.gateway, self.mask) 1074 | } 1075 | } 1076 | 1077 | impl FromStr for Subnet { 1078 | type Err = &'static str; 1079 | 1080 | fn from_str(s: &str) -> Result { 1081 | let mut split = s.split('/'); 1082 | if let Some(gateway_str) = split.next() { 1083 | if let Some(mask_str) = split.next() { 1084 | if split.next().is_none() { 1085 | if let Ok(gateway) = gateway_str.parse::() { 1086 | return mask_str.parse::().map(|mask| Self { gateway, mask }); 1087 | } else { 1088 | return Err("Invalid IP address format, expected XXX.XXX.XXX.XXX"); 1089 | } 1090 | } 1091 | } 1092 | } 1093 | 1094 | Err("Expected /") 1095 | } 1096 | } 1097 | 1098 | /// Settings for a client in an IPv4 network. 1099 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 1100 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 1101 | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] 1102 | pub struct ClientSettings { 1103 | /// The client's IPv4 address. 1104 | #[cfg_attr(feature = "defmt", defmt(Debug2Format))] 1105 | pub ip: Ipv4Addr, 1106 | 1107 | /// The subnet associated with the client's IP address. 1108 | pub subnet: Subnet, 1109 | 1110 | /// The primary DNS server for name resolution. 1111 | #[cfg_attr(feature = "defmt", defmt(Debug2Format))] 1112 | pub dns: Option, 1113 | 1114 | /// The secondary DNS server for name resolution. 1115 | #[cfg_attr(feature = "defmt", defmt(Debug2Format))] 1116 | pub secondary_dns: Option, 1117 | } 1118 | 1119 | impl Default for ClientSettings { 1120 | fn default() -> ClientSettings { 1121 | ClientSettings { 1122 | ip: Ipv4Addr::new(192, 168, 71, 200), 1123 | subnet: Subnet { 1124 | gateway: Ipv4Addr::new(192, 168, 71, 1), 1125 | mask: Mask(24), 1126 | }, 1127 | dns: Some(Ipv4Addr::new(8, 8, 8, 8)), 1128 | secondary_dns: Some(Ipv4Addr::new(8, 8, 4, 4)), 1129 | } 1130 | } 1131 | } 1132 | 1133 | /// Settings for the DHCP client. 1134 | /// 1135 | /// This struct contains the configuration for a DHCP client, including a 1136 | /// hostname that can be sent during DHCP negotiations. 1137 | #[derive(Default, Clone, Debug, PartialEq, Eq)] 1138 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 1139 | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] 1140 | pub struct DHCPClientSettings { 1141 | pub hostname: Option>, 1142 | } 1143 | 1144 | /// Configuration for the client in an IPv4 network. 1145 | /// 1146 | /// This enum defines how the client's IP settings are obtained: either 1147 | /// through DHCP, or as a fixed (static) configuration. 1148 | #[derive(Clone, Debug, PartialEq, Eq)] 1149 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 1150 | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] 1151 | pub enum ClientConfiguration { 1152 | /// Use DHCP to obtain network settings. 1153 | DHCP(DHCPClientSettings), 1154 | 1155 | /// Use a fixed configuration for network settings. 1156 | Fixed(ClientSettings), 1157 | } 1158 | 1159 | impl ClientConfiguration { 1160 | /// Returns a reference to the fixed settings if the client is using a 1161 | /// static configuration, `None` otherwise. 1162 | pub fn as_fixed_settings_ref(&self) -> Option<&ClientSettings> { 1163 | match self { 1164 | Self::Fixed(client_settings) => Some(client_settings), 1165 | _ => None, 1166 | } 1167 | } 1168 | 1169 | /// Returns a mutable reference to the fixed settings, creating a 1170 | /// default fixed configuration if necessary. 1171 | pub fn as_fixed_settings_mut(&mut self) -> &mut ClientSettings { 1172 | match self { 1173 | Self::Fixed(client_settings) => client_settings, 1174 | _ => { 1175 | *self = ClientConfiguration::Fixed(Default::default()); 1176 | self.as_fixed_settings_mut() 1177 | } 1178 | } 1179 | } 1180 | } 1181 | 1182 | impl Default for ClientConfiguration { 1183 | fn default() -> ClientConfiguration { 1184 | ClientConfiguration::DHCP(Default::default()) 1185 | } 1186 | } 1187 | 1188 | /// Router configuration in an IPv4 network. 1189 | #[derive(Clone, Debug, Eq, PartialEq)] 1190 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 1191 | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] 1192 | pub struct RouterConfiguration { 1193 | /// The subnet the router is responsible for. 1194 | pub subnet: Subnet, 1195 | 1196 | /// Indicates whether DHCP is enabled on the router. 1197 | pub dhcp_enabled: bool, 1198 | 1199 | /// The primary DNS server for the router. 1200 | #[cfg_attr(feature = "defmt", defmt(Debug2Format))] 1201 | pub dns: Option, 1202 | 1203 | /// The secondary DNS server for the router. 1204 | #[cfg_attr(feature = "defmt", defmt(Debug2Format))] 1205 | pub secondary_dns: Option, 1206 | } 1207 | 1208 | impl Default for RouterConfiguration { 1209 | fn default() -> RouterConfiguration { 1210 | RouterConfiguration { 1211 | subnet: Subnet { 1212 | gateway: Ipv4Addr::new(192, 168, 71, 1), 1213 | mask: Mask(24), 1214 | }, 1215 | dhcp_enabled: true, 1216 | dns: Some(Ipv4Addr::new(8, 8, 8, 8)), 1217 | secondary_dns: Some(Ipv4Addr::new(8, 8, 4, 4)), 1218 | } 1219 | } 1220 | } 1221 | 1222 | /// Represents the network configuration for a device. 1223 | /// 1224 | /// Holds either a client configuration (for devices connecting to a 1225 | /// network) or a router configuration (for devices providing a network 1226 | /// to other clients). 1227 | #[derive(Clone, Debug, Eq, PartialEq)] 1228 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 1229 | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] 1230 | pub enum Configuration { 1231 | /// Configuration for a device acting as a client in the network. 1232 | Client(ClientConfiguration), 1233 | 1234 | /// Configuration for a device acting as a router in the network. 1235 | Router(RouterConfiguration), 1236 | } 1237 | 1238 | impl Default for Configuration { 1239 | fn default() -> Self { 1240 | Self::Client(Default::default()) 1241 | } 1242 | } 1243 | 1244 | /// Represents IPv4 information for a device. 1245 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 1246 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 1247 | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] 1248 | pub struct IpInfo { 1249 | /// The IPv4 address of the device. 1250 | #[cfg_attr(feature = "defmt", defmt(Debug2Format))] 1251 | pub ip: Ipv4Addr, 1252 | 1253 | /// The subnet mask associated with the device's IP address. 1254 | pub subnet: Subnet, 1255 | 1256 | /// The primary DNS server for the device. 1257 | #[cfg_attr(feature = "defmt", defmt(Debug2Format))] 1258 | pub dns: Option, 1259 | 1260 | /// The secondary DNS server for the device. 1261 | #[cfg_attr(feature = "defmt", defmt(Debug2Format))] 1262 | pub secondary_dns: Option, 1263 | } 1264 | } 1265 | --------------------------------------------------------------------------------