From aa6c7286943aa56ddf33e9a95998be08990ffa8c Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Fri, 19 Jan 2024 09:55:26 +0000 Subject: [PATCH] Display and std::error::Error for all errors; DHCP examples --- Cargo.toml | 20 +++++-- edge-captive/Cargo.toml | 4 +- edge-captive/src/io.rs | 16 ++++++ edge-dhcp/Cargo.toml | 3 +- edge-dhcp/README.md | 57 +++++++++++++------- edge-dhcp/src/io.rs | 17 +++++- edge-dhcp/src/lib.rs | 23 +++++++- edge-mdns/Cargo.toml | 4 +- edge-mdns/src/io.rs | 16 ++++++ edge-raw/Cargo.toml | 3 +- edge-raw/src/io.rs | 19 ++++++- edge-raw/src/lib.rs | 18 +++++++ edge-std-nal-async/src/lib.rs | 7 +-- edge-ws/Cargo.toml | 2 + edge-ws/src/lib.rs | 20 +++++++ examples/dhcp_client.rs | 44 +++++++++++++++ examples/dhcp_server.rs | 36 +++++++++++++ examples/http_client.rs | 2 +- examples/{http_server.rs => http_server.rs0} | 0 19 files changed, 271 insertions(+), 40 deletions(-) create mode 100644 examples/dhcp_client.rs create mode 100644 examples/dhcp_server.rs rename examples/{http_server.rs => http_server.rs0} (100%) diff --git a/Cargo.toml b/Cargo.toml index eab58a2..44275de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,8 +16,8 @@ embedded-svc = { git = "https://github.com/esp-rs/embedded-svc" } std-embedded-nal-async = { git = "https://gitlab.com/ivmarkov/std-embedded-nal" } [features] -default = ["io"] -std = ["edge-http/std", "edge-captive/std", "edge-mdns/std", "edge-mqtt", "edge-std-nal-async"] +default = ["std"] +std = ["io", "edge-captive/std", "edge-dhcp/std", "edge-http/std", "edge-mdns/std", "edge-raw/std", "edge-mqtt", "edge-ws/std", "edge-std-nal-async"] io = ["edge-captive/io", "edge-dhcp/io", "edge-http/io", "edge-mdns/io", "edge-raw/io", "edge-ws/io", "embedded-nal-async-xtra"] nightly = [] embedded-svc = ["edge-http/embedded-svc", "edge-mqtt/embedded-svc", "edge-ws/embedded-svc"] @@ -41,6 +41,8 @@ embedded-io-async = "0.6" embedded-nal-async = "0.7" std-embedded-nal-async = "0.1" futures-lite = "1" +rand = "0.8" +embassy-time = { version = "0.3", features = ["std", "generic-queue"] } [[example]] name = "captive_portal" @@ -51,12 +53,20 @@ name = "simple" required-features = ["io", "std"] [[example]] -name = "http_client" +name = "dhcp_client" +required-features = ["io", "std"] + +[[example]] +name = "dhcp_server" required-features = ["io", "std"] [[example]] -name = "http_server" -required-features = ["io", "std", "turnoff"] +name = "http_client" +required-features = ["io", "std"] + +#[[example]] +#name = "http_server" +#required-features = ["io", "std", "turnoff"] [workspace] members = [ diff --git a/edge-captive/Cargo.toml b/edge-captive/Cargo.toml index 428dc82..d41cb18 100644 --- a/edge-captive/Cargo.toml +++ b/edge-captive/Cargo.toml @@ -5,8 +5,8 @@ edition = "2021" rust-version = "1.75" [features] -std = [] # TODO -default = ["io"] +default = ["std"] +std = ["io"] io = ["embedded-nal-async"] [dependencies] diff --git a/edge-captive/src/io.rs b/edge-captive/src/io.rs index e7cfc8a..83c503c 100644 --- a/edge-captive/src/io.rs +++ b/edge-captive/src/io.rs @@ -1,3 +1,4 @@ +use core::fmt; use core::time::Duration; use embedded_nal_async::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpStack, UnconnectedUdp}; @@ -22,6 +23,21 @@ impl From for DnsIoError { } } +impl fmt::Display for DnsIoError +where + E: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::DnsError(err) => write!(f, "DNS error: {}", err), + Self::IoError(err) => write!(f, "IO error: {}", err), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for DnsIoError where E: std::error::Error {} + pub async fn run( stack: &S, socket: SocketAddr, diff --git a/edge-dhcp/Cargo.toml b/edge-dhcp/Cargo.toml index ca85f89..f6ec4fd 100644 --- a/edge-dhcp/Cargo.toml +++ b/edge-dhcp/Cargo.toml @@ -5,7 +5,8 @@ edition = "2021" rust-version = "1.75" [features] -default = ["io"] +default = ["std"] +std = ["io"] io = ["embassy-futures", "embedded-nal-async", "embedded-nal-async-xtra"] [dependencies] diff --git a/edge-dhcp/README.md b/edge-dhcp/README.md index 8abb1e2..13f23b2 100644 --- a/edge-dhcp/README.md +++ b/edge-dhcp/README.md @@ -11,36 +11,47 @@ Async + `no_std` + no-alloc implementation of the DHCP protocol. ### DHCP client ```rust -use edge_raw::{StdRawStack, Udp2RawStack}; -use edge_dhcp::{DEFAULT_CLIENT_PORT, Client, io::Lease, io::Error}; +use edge_raw::io::Udp2RawStack; +use edge_std_nal_async::StdRawStack; -use embedded_nal_async::Ipv4Addr; +use edge_dhcp::client::Client; +use edge_dhcp::io::{self, client::Lease, client::DEFAULT_CLIENT_PORT}; + +use embedded_nal_async::{Ipv4Addr, SocketAddrV4}; + +use log::info; fn main() { - futures_lite::task::block_on(0, run([0; 6]/* Your MAC addr here */)).unwrap(); + env_logger::init_from_env( + env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), + ); + + futures_lite::future::block_on(run( + 2, // The interface index of the interface (e.g. eno0) to use; run `ip addr` to see it + [0x4c, 0xcc, 0x6a, 0xa2, 0x23, 0xf5], // Your MAC addr here; run `ip addr` to see it + )) + .unwrap(); } -async fn run(if_index: u8, if_mac: [u8; 6]) -> Result<(), impl Debug> { - let mut client = Client::new(thread_rng(), if_mac); +async fn run(if_index: u32, if_mac: [u8; 6]) -> Result<(), anyhow::Error> { + let mut client = Client::new(rand::thread_rng(), if_mac); let stack: Udp2RawStack<_> = Udp2RawStack::new(StdRawStack::new(if_index)); let mut buf = [0; 1500]; loop { - let mut socket = bind( + let mut socket = io::bind( &stack, - SocketAddrV4::new( - Ipv4Addr::UNSPECIFIED, - DEFAULT_CLIENT_PORT, - ), + SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, DEFAULT_CLIENT_PORT), ) .await?; - let (mut lease, options) = - Lease::new(&mut client, &mut socket, &mut buf).await?; + let (mut lease, options) = Lease::new(&mut client, &mut socket, &mut buf).await?; info!("Got lease {lease:?} with options {options:?}"); + info!("Entering an endless loop to keep the lease..."); + lease.keep(&mut client, &mut socket, &mut buf).await?; } } @@ -49,16 +60,22 @@ async fn run(if_index: u8, if_mac: [u8; 6]) -> Result<(), impl Debug> { ### DHCP server ```rust -use edge_raw::{StdRawStack, Udp2RawStack}; -use edge_dhcp::{self, DEFAULT_SERVER_PORT, Server, ServerOptions, io::Error}; +use edge_raw::io::Udp2RawStack; +use edge_std_nal_async::StdRawStack; + +use edge_dhcp::io::{self, DEFAULT_SERVER_PORT}; +use edge_dhcp::server::{Server, ServerOptions}; use embedded_nal_async::{Ipv4Addr, SocketAddrV4}; fn main() { - futures_lite::task::block_on(run(0)).unwrap(); + futures_lite::future::block_on(run( + 0, // The interface index of the interface (e.g. eno0) to use; run `ip addr` to see it + )) + .unwrap(); } -async fn run(if_index: u8) -> Result<(), impl Debug> { +async fn run(if_index: u32) -> Result<(), anyhow::Error> { let stack: Udp2RawStack<_> = Udp2RawStack::new(StdRawStack::new(if_index)); let mut buf = [0; 1500]; @@ -69,12 +86,14 @@ async fn run(if_index: u8) -> Result<(), impl Debug> { let mut gw_buf = [Ipv4Addr::UNSPECIFIED]; - io::run( + io::server::run( &mut Server::<64>::new(ip), // Will give IP addresses in the rage 192.168.0.50 - 192.168.0.200, subnet 255.255.255.0 &ServerOptions::new(ip, Some(&mut gw_buf)), &mut socket, &mut buf, ) - .await + .await?; + + Ok(()) } ``` diff --git a/edge-dhcp/src/io.rs b/edge-dhcp/src/io.rs index 97360ee..ba72594 100644 --- a/edge-dhcp/src/io.rs +++ b/edge-dhcp/src/io.rs @@ -1,4 +1,4 @@ -use core::fmt::Debug; +use core::fmt::{self, Debug}; use embedded_nal_async::{SocketAddr, SocketAddrV4, UdpStack, UnconnectedUdp}; use embedded_nal_async_xtra::UnconnectedUdpWithMac; @@ -23,6 +23,21 @@ impl From for Error { } } +impl fmt::Display for Error +where + E: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Io(err) => write!(f, "IO error: {err}"), + Self::Format(err) => write!(f, "Format error: {err}"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error where E: std::error::Error {} + /// A fallback implementation of `UnconnectedUdpWithMac` that does not support MAC addresses. /// Might or might not work depending on the DHCP client. pub struct UnconnectedUdpWithMacFallback(pub T); diff --git a/edge-dhcp/src/lib.rs b/edge-dhcp/src/lib.rs index 8820b0b..42d8e9b 100644 --- a/edge-dhcp/src/lib.rs +++ b/edge-dhcp/src/lib.rs @@ -1,7 +1,7 @@ -#![no_std] +#![cfg_attr(not(feature = "std"), no_std)] -use core::fmt; /// This code is a `no_std` and no-alloc modification of https://github.com/krolaw/dhcp4r +use core::fmt; use core::str::Utf8Error; pub use no_std_net::Ipv4Addr; @@ -37,6 +37,25 @@ impl From for Error { } } +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let str = match self { + Self::DataUnderflow => "Data underflow", + Self::BufferOverflow => "Buffer overflow", + Self::InvalidPacket => "Invalid packet", + Self::InvalidUtf8Str(_) => "Invalid Utf8 string", + Self::InvalidMessageType => "Invalid message type", + Self::MissingCookie => "Missing cookie", + Self::InvalidHlen => "Invalid hlen", + }; + + write!(f, "{}", str) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error {} + /// /// DHCP Message Type. /// diff --git a/edge-mdns/Cargo.toml b/edge-mdns/Cargo.toml index 8fcf54b..922dae4 100644 --- a/edge-mdns/Cargo.toml +++ b/edge-mdns/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2021" [features] -default = ["io"] -std = [] +default = ["std"] +std = ["io"] io = ["embassy-futures", "embassy-sync", "embassy-time", "embedded-nal-async", "embedded-nal-async-xtra"] [dependencies] diff --git a/edge-mdns/src/io.rs b/edge-mdns/src/io.rs index de4f57d..309117f 100644 --- a/edge-mdns/src/io.rs +++ b/edge-mdns/src/io.rs @@ -1,3 +1,4 @@ +use core::fmt; use core::pin::pin; use embassy_futures::select::{select, Either}; @@ -40,6 +41,21 @@ impl From for MdnsIoError { } } +impl fmt::Display for MdnsIoError +where + E: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::MdnsError(err) => write!(f, "mDNS error: {}", err), + Self::IoError(err) => write!(f, "IO error: {}", err), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for MdnsIoError where E: std::error::Error {} + pub struct MdnsRunBuffers { tx_buf: core::mem::MaybeUninit<[u8; MAX_TX_BUF_SIZE]>, rx_buf: core::mem::MaybeUninit<[u8; MAX_RX_BUF_SIZE]>, diff --git a/edge-raw/Cargo.toml b/edge-raw/Cargo.toml index c29629f..352ee2f 100644 --- a/edge-raw/Cargo.toml +++ b/edge-raw/Cargo.toml @@ -5,7 +5,8 @@ edition = "2021" rust-version = "1.75" [features] -default = ["io"] +default = ["std"] +std = ["io"] io = ["embedded-io-async", "embedded-nal-async", "embedded-nal-async-xtra"] [dependencies] diff --git a/edge-raw/src/io.rs b/edge-raw/src/io.rs index 127f28e..41dcc81 100644 --- a/edge-raw/src/io.rs +++ b/edge-raw/src/io.rs @@ -1,4 +1,5 @@ -use core::{fmt::Debug, mem::MaybeUninit}; +use core::fmt::{self, Debug}; +use core::mem::MaybeUninit; use embedded_io_async::ErrorKind; @@ -34,6 +35,22 @@ where } } +impl fmt::Display for Error +where + E: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Io(err) => write!(f, "IO error: {err}"), + Self::UnsupportedProtocol => write!(f, "Unsupported protocol"), + Self::RawError(err) => write!(f, "Raw error: {err}"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error where E: std::error::Error {} + pub struct ConnectedUdp2RawSocket(T, SocketAddrV4, SocketAddrV4); impl ConnectedUdp for ConnectedUdp2RawSocket diff --git a/edge-raw/src/lib.rs b/edge-raw/src/lib.rs index 3dc68ee..c7f93dd 100644 --- a/edge-raw/src/lib.rs +++ b/edge-raw/src/lib.rs @@ -1,6 +1,8 @@ #![cfg_attr(not(feature = "std"), no_std)] #![allow(async_fn_in_trait)] +use core::fmt; + use no_std_net::{Ipv4Addr, SocketAddrV4}; use self::udp::UdpPacketHeader; @@ -32,6 +34,22 @@ impl From for Error { } } +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let str = match self { + Self::DataUnderflow => "Data underflow", + Self::BufferOverflow => "Buffer overflow", + Self::InvalidFormat => "Invalid format", + Self::InvalidChecksum => "Invalid checksum", + }; + + write!(f, "{}", str) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error {} + #[allow(clippy::type_complexity)] pub fn ip_udp_decode( packet: &[u8], diff --git a/edge-std-nal-async/src/lib.rs b/edge-std-nal-async/src/lib.rs index d616b10..acc5ee9 100644 --- a/edge-std-nal-async/src/lib.rs +++ b/edge-std-nal-async/src/lib.rs @@ -29,10 +29,7 @@ impl TcpConnect for StdTcpConnect { type Connection<'a> = StdTcpConnection where Self: 'a; - async fn connect<'a>( - &'a self, - remote: SocketAddr, - ) -> Result, Self::Error> { + async fn connect(&self, remote: SocketAddr) -> Result, Self::Error> { let connection = Async::::connect(to_std_addr(remote)).await?; Ok(StdTcpConnection(connection)) @@ -424,7 +421,7 @@ impl RawStack for StdRawStack { fn as_sockaddr_ll(storage: &libc::sockaddr_storage, len: usize) -> io::Result<&libc::sockaddr_ll> { match storage.ss_family as core::ffi::c_int { libc::AF_PACKET => { - assert!(len as usize >= core::mem::size_of::()); + assert!(len >= core::mem::size_of::()); Ok(unsafe { (storage as *const _ as *const libc::sockaddr_ll).as_ref() }.unwrap()) } _ => Err(io::Error::new(ErrorKind::InvalidInput, "invalid argument")), diff --git a/edge-ws/Cargo.toml b/edge-ws/Cargo.toml index ad00f27..d8b0e47 100644 --- a/edge-ws/Cargo.toml +++ b/edge-ws/Cargo.toml @@ -5,6 +5,8 @@ edition = "2021" rust-version = "1.75" [features] +default = ["std"] +std = ["io"] io = ["embedded-io-async"] [dependencies] diff --git a/edge-ws/src/lib.rs b/edge-ws/src/lib.rs index b3d59dd..6189a73 100644 --- a/edge-ws/src/lib.rs +++ b/edge-ws/src/lib.rs @@ -1,6 +1,8 @@ #![cfg_attr(not(feature = "std"), no_std)] #![allow(async_fn_in_trait)] +use core::fmt; + pub type Fragmented = bool; pub type Final = bool; @@ -60,6 +62,24 @@ impl Error<()> { } } +impl fmt::Display for Error +where + E: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Incomplete(size) => write!(f, "Incomplete: {} bytes missing", size), + Self::Invalid => write!(f, "Invalid"), + Self::BufferOverflow => write!(f, "Buffer overflow"), + Self::InvalidLen => write!(f, "Invalid length"), + Self::Io(err) => write!(f, "IO error: {}", err), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error where E: std::error::Error {} + #[derive(Clone, Debug)] pub struct FrameHeader { pub frame_type: FrameType, diff --git a/examples/dhcp_client.rs b/examples/dhcp_client.rs new file mode 100644 index 0000000..52c48c0 --- /dev/null +++ b/examples/dhcp_client.rs @@ -0,0 +1,44 @@ +use edge_raw::io::Udp2RawStack; +use edge_std_nal_async::StdRawStack; + +use edge_dhcp::client::Client; +use edge_dhcp::io::{self, client::Lease, client::DEFAULT_CLIENT_PORT}; + +use embedded_nal_async::{Ipv4Addr, SocketAddrV4}; + +use log::info; + +fn main() { + env_logger::init_from_env( + env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), + ); + + futures_lite::future::block_on(run( + 2, // The interface index of the interface (e.g. eno0) to use; run `ip addr` to see it + [0x4c, 0xcc, 0x6a, 0xa2, 0x23, 0xf5], // Your MAC addr here; run `ip addr` to see it + )) + .unwrap(); +} + +async fn run(if_index: u32, if_mac: [u8; 6]) -> Result<(), anyhow::Error> { + let mut client = Client::new(rand::thread_rng(), if_mac); + + let stack: Udp2RawStack<_> = Udp2RawStack::new(StdRawStack::new(if_index)); + let mut buf = [0; 1500]; + + loop { + let mut socket = io::bind( + &stack, + SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, DEFAULT_CLIENT_PORT), + ) + .await?; + + let (mut lease, options) = Lease::new(&mut client, &mut socket, &mut buf).await?; + + info!("Got lease {lease:?} with options {options:?}"); + + info!("Entering an endless loop to keep the lease..."); + + lease.keep(&mut client, &mut socket, &mut buf).await?; + } +} diff --git a/examples/dhcp_server.rs b/examples/dhcp_server.rs new file mode 100644 index 0000000..ee9bec2 --- /dev/null +++ b/examples/dhcp_server.rs @@ -0,0 +1,36 @@ +use edge_raw::io::Udp2RawStack; +use edge_std_nal_async::StdRawStack; + +use edge_dhcp::io::{self, DEFAULT_SERVER_PORT}; +use edge_dhcp::server::{Server, ServerOptions}; + +use embedded_nal_async::{Ipv4Addr, SocketAddrV4}; + +fn main() { + futures_lite::future::block_on(run( + 0, // The interface index of the interface (e.g. eno0) to use; run `ip addr` to see it + )) + .unwrap(); +} + +async fn run(if_index: u32) -> Result<(), anyhow::Error> { + let stack: Udp2RawStack<_> = Udp2RawStack::new(StdRawStack::new(if_index)); + + let mut buf = [0; 1500]; + + let ip = Ipv4Addr::new(192, 168, 0, 1); + + let mut socket = io::bind(&stack, SocketAddrV4::new(ip, DEFAULT_SERVER_PORT)).await?; + + let mut gw_buf = [Ipv4Addr::UNSPECIFIED]; + + io::server::run( + &mut Server::<64>::new(ip), // Will give IP addresses in the rage 192.168.0.50 - 192.168.0.200, subnet 255.255.255.0 + &ServerOptions::new(ip, Some(&mut gw_buf)), + &mut socket, + &mut buf, + ) + .await?; + + Ok(()) +} diff --git a/examples/http_client.rs b/examples/http_client.rs index f6a1b35..fb60524 100644 --- a/examples/http_client.rs +++ b/examples/http_client.rs @@ -45,7 +45,7 @@ async fn request<'b, const N: usize, T: TcpConnect>( uri: &str, ) -> Result<(), Error> { connection - .initiate_request(Method::Get, uri, &[("Host", "httpbin.org")]) + .initiate_request(true, Method::Get, uri, &[("Host", "httpbin.org")]) .await?; connection.initiate_response().await?; diff --git a/examples/http_server.rs b/examples/http_server.rs0 similarity index 100% rename from examples/http_server.rs rename to examples/http_server.rs0