diff --git a/.github/workflows/build-deploy-docs.yml b/.github/workflows/build-deploy-docs.yml index 1487d1f62..c00738b4c 100644 --- a/.github/workflows/build-deploy-docs.yml +++ b/.github/workflows/build-deploy-docs.yml @@ -48,7 +48,7 @@ jobs: - name: Build rustdoc docs run: | - cargo doc -p riot-rs --features no-boards,bench,threading,random,csprng,hwrng + cargo doc -p riot-rs --features no-boards,bench,threading,random,csprng,hwrng,coap echo "" > target/doc/index.html mkdir -p ./_site/dev/docs/api && mv target/doc/* ./_site/dev/docs/api diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a2346e9a2..7b0be0c01 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -141,7 +141,7 @@ jobs: args: --verbose --locked --features no-boards -p riot-rs -p riot-rs-boards -p riot-rs-chips -p riot-rs-debug -p riot-rs-embassy -p riot-rs-macros -p riot-rs-random -p riot-rs-rt -p riot-rs-threads -p riot-rs-utils - name: rustdoc - run: RUSTDOCFLAGS='-D warnings' cargo doc -p riot-rs --features no-boards,bench,external-interrupts,threading,random,csprng,hwrng + run: RUSTDOCFLAGS='-D warnings' cargo doc -p riot-rs --features no-boards,bench,external-interrupts,threading,random,csprng,hwrng,coap,net,usb-ethernet - name: rustfmt run: cargo fmt --check --all diff --git a/Cargo.lock b/Cargo.lock index 6341c55f6..c74119844 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -453,20 +453,13 @@ dependencies = [ "coap-scroll-ring-server", "coapcore", "embassy-executor", - "embassy-futures", "embassy-net", "embassy-time", - "embedded-io-async", - "embedded-nal-async", "embedded-nal-coap", "heapless 0.8.0", - "hexlit", - "lakers", - "lakers-crypto-rustcrypto", "riot-rs", "riot-rs-boards", "scroll-ring", - "smoltcp", "static-alloc", ] @@ -601,6 +594,9 @@ dependencies = [ "coap-message-implementations", "coap-message-utils", "coap-numbers", + "embassy-futures", + "embedded-nal-async", + "embedded-nal-coap", "heapless 0.8.0", "hexlit", "lakers", @@ -608,6 +604,7 @@ dependencies = [ "liboscore", "liboscore-msgbackend", "minicbor 0.23.0", + "rand_core", ] [[package]] @@ -3406,6 +3403,7 @@ dependencies = [ "linkme", "riot-rs-bench", "riot-rs-boards", + "riot-rs-coap", "riot-rs-debug", "riot-rs-embassy", "riot-rs-macros", @@ -3455,6 +3453,23 @@ dependencies = [ "stm32f4xx", ] +[[package]] +name = "riot-rs-coap" +version = "0.1.0" +dependencies = [ + "coap-handler", + "coapcore", + "embedded-io-async", + "embedded-nal-async", + "embedded-nal-coap", + "hexlit", + "lakers", + "lakers-crypto-rustcrypto", + "riot-rs-embassy", + "riot-rs-random", + "smoltcp", +] + [[package]] name = "riot-rs-debug" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 21be2bd67..5eab87262 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "src/riot-rs-boards/nrf52", "src/riot-rs-boards/nrf52840dk", "src/riot-rs-chips", + "src/riot-rs-coap", "src/riot-rs-debug", "src/riot-rs-macros", "src/riot-rs-random", diff --git a/examples/coap/Cargo.toml b/examples/coap/Cargo.toml index 51d18f8a0..5b7daef03 100644 --- a/examples/coap/Cargo.toml +++ b/examples/coap/Cargo.toml @@ -13,10 +13,11 @@ workspace = true embassy-executor = { workspace = true, default-features = false } embassy-net = { workspace = true, features = ["udp"] } embassy-time = { workspace = true, default-features = false } -embedded-io-async = "0.6.1" heapless = { workspace = true } riot-rs = { path = "../../src/riot-rs", features = [ "override-network-config", + "coap", + # to be removed later once coap pulls them in "random", "csprng", ] } @@ -24,29 +25,14 @@ riot-rs-boards = { path = "../../src/riot-rs-boards" } coapcore.path = "../../src/lib/coapcore/" -# for the udp_nal mod -embedded-nal-async = "0.7" -# actually patched with https://github.com/smoltcp-rs/smoltcp/pull/904 but -# patch.crates-io takes care of that -smoltcp = { version = "0.11", default-features = false } embedded-nal-coap = "0.1.0-alpha.2" coap-request = "0.2.0-alpha.2" coap-message = "0.3.2" -embassy-futures = "0.1.1" coap-message-demos = { version = "0.4.0", default-features = false } coap-request-implementations = "0.1.0-alpha.4" -lakers = { version = "0.6.0", default-features = false } -lakers-crypto-rustcrypto = "0.6.0" coap-handler = "0.2.0" coap-handler-implementations = "0.5.0" -hexlit = "0.5.5" static-alloc = "0.2.5" coap-scroll-ring-server = "0.2.0" scroll-ring = "0.1.1" - -[features] -default = ["proto-ipv4"] # shame -# actually embedded-nal features, we have to match them here while developing udp_nal in here -proto-ipv4 = [] -proto-ipv6 = [] diff --git a/examples/coap/src/main.rs b/examples/coap/src/main.rs index 1d4c42620..b3efd412c 100644 --- a/examples/coap/src/main.rs +++ b/examples/coap/src/main.rs @@ -3,15 +3,9 @@ #![feature(type_alias_impl_trait)] #![feature(used_with_arg)] -use riot_rs::{debug::log::*, embassy::network}; +use core::fmt::Write; -use embassy_net::udp::{PacketMetadata, UdpSocket}; -use embedded_nal_coap::TransportError; - -// Moving work from https://github.com/embassy-rs/embassy/pull/2519 in here for the time being -mod udp_nal; - -use coapcore::seccontext; +use riot_rs::embassy::embassy_net; // because coapcore depends on it temporarily extern crate alloc; @@ -20,36 +14,6 @@ use static_alloc::Bump; #[global_allocator] static A: Bump<[u8; 1 << 16]> = Bump::uninit(); -#[riot_rs::task(autostart)] -async fn coap_run() { - let stack = network::network_stack().await.unwrap(); - - // FIXME trim to CoAP requirements - let mut rx_meta = [PacketMetadata::EMPTY; 16]; - let mut rx_buffer = [0; 4096]; - let mut tx_meta = [PacketMetadata::EMPTY; 16]; - let mut tx_buffer = [0; 4096]; - - let socket = UdpSocket::new( - stack, - &mut rx_meta, - &mut rx_buffer, - &mut tx_meta, - &mut tx_buffer, - ); - - info!("Starting up CoAP server"); - - // Can't that even bind to the Any address?? - // let local_any = "0.0.0.0:5683".parse().unwrap(); // shame - let local_any = "10.42.0.61:5683".parse().unwrap(); // shame - let unconnected = udp_nal::UnconnectedUdp::bind_multiple(socket, local_any) - .await - .unwrap(); - - run(unconnected).await; -} - // FIXME: So far, this is necessary boiler plate; see ../README.md#networking for details #[riot_rs::config(network)] fn network_config() -> embassy_net::Config { @@ -62,104 +26,88 @@ fn network_config() -> embassy_net::Config { }) } -// Rest is from coap-message-demos/examples/std_embedded_nal_coap.rs +// This is adjusted from coap-message-demos/examples/std_embedded_nal_coap.rs -/// This function works on *any* UdpFullStack, including embedded ones -- only main() is what makes -/// this use POSIX sockets. (It does make use of a std based RNG, but that could be passed in just -/// as well for no_std operation). -async fn run(mut sock: S) -where - S: embedded_nal_async::UnconnectedUdp, -{ +// FIXME: Why doesn't scroll_ring provide that? +#[derive(Clone)] +struct Stdout<'a, const N: usize>(&'a scroll_ring::Buffer); +impl<'a, const N: usize> Write for Stdout<'a, N> { + fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { + self.0.write(s.as_bytes()); + Ok(()) + } +} + +#[riot_rs::task(autostart)] +async fn run() { use coap_handler_implementations::{HandlerBuilder, ReportingHandlerBuilder}; let log = None; let buffer = scroll_ring::Buffer::<512>::default(); - // FIXME: Why doesn't scroll_ring provide that? - struct Stdout<'a>(&'a scroll_ring::Buffer<512>); - impl<'a> core::fmt::Write for Stdout<'a> { - fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { - self.0.write(s.as_bytes()); - Ok(()) - } - } let mut stdout = Stdout(&buffer); - use core::fmt::Write; writeln!(stdout, "We have our own stdout now.").unwrap(); writeln!(stdout, "With rings and atomics.").unwrap(); - use hexlit::hex; - const R: &[u8] = &hex!("72cc4761dbd4c78f758931aa589d348d1ef874a7e303ede2f140dcf3e6aa4aac"); - let own_identity = ( - &lakers::CredentialRPK::new(lakers::EdhocMessageBuffer::new_from_slice(&hex!("A2026008A101A5010202410A2001215820BBC34960526EA4D32E940CAD2A234148DDC21791A12AFBCBAC93622046DD44F02258204519E257236B2A0CE2023F0931F1F386CA7AFDA64FCDE0108C224C51EABF6072")).expect("Credential should be small enough")).expect("Credential should be processable"), - R, - ); - - let mut handler = coap_message_demos::full_application_tree(log) + let handler = coap_message_demos::full_application_tree(log) .at( &["stdout"], coap_scroll_ring_server::BufferHandler::new(&buffer), ) .with_wkc(); - let mut handler = seccontext::OscoreEdhocHandler::new(own_identity, handler, stdout, || { - lakers_crypto_rustcrypto::Crypto::new(riot_rs::random::crypto_rng()) - }); - - info!("Server is ready."); - - let coap = embedded_nal_coap::CoAPShared::<3>::new(); - let (client, server) = coap.split(); - - // going with an embassy_futures join instead of an async_std::task::spawn b/c CoAPShared is not - // Sync, and async_std expects to work in multiple threads - embassy_futures::join::join( - async { - server - .run(&mut sock, &mut handler, &mut riot_rs::random::fast_rng()) - .await - .expect("UDP error") - }, - run_client_operations(client), - ) - .await; + writeln!(stdout, "Server is ready.").unwrap(); + + riot_rs::coap::coap_task(handler, Client(stdout.clone()), &mut stdout).await; } -/// In parallel to server operation, this function performs some operations as a client. -/// -/// This doubles as an experimentation ground for the client side of embedded_nal_coap and -/// coap-request in general. -async fn run_client_operations( - client: embedded_nal_coap::CoAPRuntimeClient<'_, N>, -) { - // shame - let addr = "10.42.0.1:1234"; - let demoserver = addr.clone().parse().unwrap(); - - use coap_request::Stack; - info!("Sending GET to {}...", addr); - let response = client - .to(demoserver) - .request( - coap_request_implementations::Code::get() - .with_path("/other/separate") - .processing_response_payload_through(|p| { - info!("Got payload {:?}", p); - }), - ) - .await; - info!("Response {:?}", response.map_err(|_| "TransportError")); - - let req = coap_request_implementations::Code::post().with_path("/uppercase"); - - info!("Sending POST..."); - let mut response = client.to(demoserver); - let response = response.request( - req.with_request_payload_slice(b"Set time to 1955-11-05") - .processing_response_payload_through(|p| { - info!("Uppercase is {}", core::str::from_utf8(p).unwrap()) - }), - ); - let response = response.await; - info!("Response {:?}", response.map_err(|_| "TransportError")); +struct Client(W); + +impl Client { + async fn run_logging( + mut self, + client: embedded_nal_coap::CoAPRuntimeClient<'_, 3>, + ) -> Result<(), &'static str> { + // shame + let demoserver = "10.42.0.1:1234" + .parse() + .map_err(|_| "Error parsing demo server address")?; + + use coap_request::Stack; + writeln!(self.0, "Sending GET to {}...", demoserver).unwrap(); + + let response = client + .to(demoserver) + .request( + coap_request_implementations::Code::get() + .with_path("/other/separate") + .processing_response_payload_through(|p| { + writeln!( + self.0, + "Got payload {:?} length {}", + &p[..core::cmp::min(10, p.len())], + p.len() + ) + .unwrap(); + }), + ) + .await + .map_err(|_| "Error while trying to GET /other/separate")?; + writeln!(self.0, "Response {:?}", response).unwrap(); + + Ok(()) + } +} + +impl coapcore::ClientRunner<3> for Client { + /// In parallel to server operation, this function performs some operations as a client. + /// + /// This doubles as an experimentation ground for the client side of embedded_nal_coap and + /// coap-request in general. + async fn run(self, client: embedded_nal_coap::CoAPRuntimeClient<'_, 3>) { + let mut stdout = self.0.clone(); + match self.run_logging(client).await { + Ok(_) => writeln!(stdout, "Client process completed").unwrap(), + Err(e) => writeln!(stdout, "Client process erred out: {e}").unwrap(), + } + } } diff --git a/src/lib/coapcore/Cargo.toml b/src/lib/coapcore/Cargo.toml index 939ad7e0e..d6864658b 100644 --- a/src/lib/coapcore/Cargo.toml +++ b/src/lib/coapcore/Cargo.toml @@ -17,7 +17,14 @@ workspace = true # public coap-handler = "0.2.0" coap-message = "0.3.2" +# public because we take a credential lakers = { version = "0.6.0", default-features = false } +# public because the callback provides a type from there +embedded-nal-coap = "0.1.0-alpha.2" +# public because we take a socket +embedded-nal-async = "0.7" +# public because we take a RngCor +rand_core = "0.6.4" # private arrayvec = { version = "0.7.4", default-features = false } @@ -32,3 +39,4 @@ liboscore-msgbackend = { git = "https://gitlab.com/oscore/liboscore/", features ], rev = "e7a4ecd037cbb9c7f085047fec5896f4bdc68d50" } minicbor = "0.23.0" heapless = "0.8.0" +embassy-futures = "0.1.1" diff --git a/src/lib/coapcore/src/lib.rs b/src/lib/coapcore/src/lib.rs index 049a19300..0b0470546 100644 --- a/src/lib/coapcore/src/lib.rs +++ b/src/lib/coapcore/src/lib.rs @@ -5,11 +5,9 @@ //! the application as an `embedded-nal` socket, and processes CoAP along with its security //! components OSCORE and EDHOC before passing on authorized requests to the application. //! -//! The crate is under heavy development: Its API is in flux, and so far it does not yet provide -//! the CoAP server itself, but merely a middleware. (Providing the full CoAP will be a requirement -//! for at least as long as the OSCORE component is tightly coupled to a particular implementation -//! of [`coap-message`]). +//! The crate is under heavy development. #![no_std] +#![feature(lint_reasons)] // Might warrant a standalone crate at some point // @@ -17,3 +15,39 @@ // anyway) pub mod oluru; pub mod seccontext; + +// This is a trait because I haven't managed to work out any lifetimes for a single client +// callback; tried +// pub async fn coap_task( +// sock: &mut impl embedded_nal_async::UnconnectedUdp, +// handler: &mut impl coap_handler::Handler, +// rng: &mut impl rand_core::RngCore, +// run_client_operations: ClientOps, +// ) where +// for<'a> ClientOps: Fn(embedded_nal_coap::CoAPRuntimeClient<'a, 3>) -> ClientFuture, +// ClientFuture: core::future::Future, +// { +// which typechecks on its own, but won't take a static async fn (or closure that returns an async +// block) as input. +pub trait ClientRunner { + #[allow(async_fn_in_trait, reason = "We explicitly expect this to not be send")] + async fn run(self, client: embedded_nal_coap::CoAPRuntimeClient<'_, N>); +} + +pub async fn coap_task( + sock: &mut impl embedded_nal_async::UnconnectedUdp, + handler: &mut impl coap_handler::Handler, + rng: &mut impl rand_core::RngCore, + client_runner: impl ClientRunner, +) { + let coap = embedded_nal_coap::CoAPShared::::new(); + let (client, server) = coap.split(); + + // going with an embassy_futures join instead of an async_std::task::spawn b/c CoAPShared is not + // Sync, and async_std expects to work in multiple threads + embassy_futures::join::join( + async { server.run(sock, handler, rng).await.expect("UDP error") }, + client_runner.run(client), + ) + .await; +} diff --git a/src/riot-rs-coap/Cargo.toml b/src/riot-rs-coap/Cargo.toml new file mode 100644 index 000000000..879925744 --- /dev/null +++ b/src/riot-rs-coap/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "riot-rs-coap" +version.workspace = true +authors.workspace = true +edition.workspace = true +repository.workspace = true + +[dependencies] +coapcore.path = "../lib/coapcore" +riot-rs-random = { path = "../riot-rs-random", features = ["csprng"] } +riot-rs-embassy = { path = "../riot-rs-embassy", features = ["udp"] } + +# actually patched with https://github.com/smoltcp-rs/smoltcp/pull/904 but +# patch.crates-io takes care of that +smoltcp = { version = "0.11", default-features = false } + +embedded-nal-coap = "0.1.0-alpha.2" +coap-handler = "0.2.0" +hexlit = "0.5.5" +lakers = { version = "0.6.0", default-features = false } +lakers-crypto-rustcrypto = "0.6.0" + +# for the udp_nal mod +embedded-nal-async = "0.7" +embedded-io-async = "0.6.1" + +[lints] +workspace = true + +[features] +# all for udp_nal +default = ["proto-ipv4"] # shame +# actually embedded-nal features, we have to match them here while developing udp_nal in here +proto-ipv4 = ["smoltcp/proto-ipv4"] +proto-ipv6 = ["smoltcp/proto-ipv6"] diff --git a/src/riot-rs-coap/src/lib.rs b/src/riot-rs-coap/src/lib.rs new file mode 100644 index 000000000..1bb26a679 --- /dev/null +++ b/src/riot-rs-coap/src/lib.rs @@ -0,0 +1,59 @@ +//! A CoAP stack preconfigured for RIOT-rs +//! ====================================== +//! +//! This crate mainly provides easy-to-use wrappers around the [`coapcore`] crate, with presets +//! tailored towards RIOT-rs: It utilizes [`embassy-net`] to open a network accessible CoAP socket, +//! [`riot-rs-random`] as a source of randomness, and [`lakers-crypto-rustcrypto`] for the +//! cryptographic algorithm implementations. +#![no_std] + +// Moving work from https://github.com/embassy-rs/embassy/pull/2519 in here for the time being +mod udp_nal; + +use riot_rs_embassy::embassy_net::udp::{PacketMetadata, UdpSocket}; + +use coapcore::seccontext; + +pub async fn coap_task( + handler: impl coap_handler::Handler, + client_runner: impl coapcore::ClientRunner, + logger: &mut impl core::fmt::Write, +) { + let stack = riot_rs_embassy::network::network_stack().await.unwrap(); + + // FIXME trim to CoAP requirements + let mut rx_meta = [PacketMetadata::EMPTY; 16]; + let mut rx_buffer = [0; 4096]; + let mut tx_meta = [PacketMetadata::EMPTY; 16]; + let mut tx_buffer = [0; 4096]; + + let socket = UdpSocket::new( + stack, + &mut rx_meta, + &mut rx_buffer, + &mut tx_meta, + &mut tx_buffer, + ); + + // Can't that even bind to the Any address?? + // let local_any = "0.0.0.0:5683".parse().unwrap(); // shame + let local_any = "10.42.0.61:5683".parse().unwrap(); // shame + let mut sock = udp_nal::UnconnectedUdp::bind_multiple(socket, local_any) + .await + .unwrap(); + + let mut rng = riot_rs_random::fast_rng(); + + use hexlit::hex; + const R: &[u8] = &hex!("72cc4761dbd4c78f758931aa589d348d1ef874a7e303ede2f140dcf3e6aa4aac"); + let own_identity = ( + &lakers::CredentialRPK::new(lakers::EdhocMessageBuffer::new_from_slice(&hex!("A2026008A101A5010202410A2001215820BBC34960526EA4D32E940CAD2A234148DDC21791A12AFBCBAC93622046DD44F02258204519E257236B2A0CE2023F0931F1F386CA7AFDA64FCDE0108C224C51EABF6072")).expect("Credential should be small enough")).expect("Credential should be processable"), + R, + ); + + let mut handler = seccontext::OscoreEdhocHandler::new(own_identity, handler, logger, || { + lakers_crypto_rustcrypto::Crypto::new(riot_rs_random::crypto_rng()) + }); + + coapcore::coap_task(&mut sock, &mut handler, &mut rng, client_runner).await; +} diff --git a/examples/coap/src/udp_nal/mod.rs b/src/riot-rs-coap/src/udp_nal/mod.rs similarity index 98% rename from examples/coap/src/udp_nal/mod.rs rename to src/riot-rs-coap/src/udp_nal/mod.rs index bd0ad3871..b468933b3 100644 --- a/examples/coap/src/udp_nal/mod.rs +++ b/src/riot-rs-coap/src/udp_nal/mod.rs @@ -21,7 +21,7 @@ use core::future::poll_fn; use embedded_nal_async as nal; use smoltcp::wire::{IpAddress, IpEndpoint}; -use embassy_net::udp; +use riot_rs_embassy::embassy_net::udp; mod util; pub use util::Error; @@ -78,7 +78,7 @@ impl<'a> ConnectedUdp<'a> { /// unconnected. pub async fn connect(socket: udp::UdpSocket<'a>, /*, ... */) -> Result { // This is really just a copy of the provided `embedded_nal::udp::UdpStack::connect` method - todo!() + todo!("use {:p}", &socket) } } diff --git a/examples/coap/src/udp_nal/util.rs b/src/riot-rs-coap/src/udp_nal/util.rs similarity index 96% rename from examples/coap/src/udp_nal/util.rs rename to src/riot-rs-coap/src/udp_nal/util.rs index fc8b532f4..1048510d3 100644 --- a/examples/coap/src/udp_nal/util.rs +++ b/src/riot-rs-coap/src/udp_nal/util.rs @@ -1,7 +1,7 @@ //! Helpers for udp_nal -- conversion and error types -use embassy_net::udp; use embedded_nal_async as nal; +use riot_rs_embassy::embassy_net::udp; use smoltcp::wire::{IpAddress, IpEndpoint}; pub(super) fn sockaddr_nal2smol(sockaddr: nal::SocketAddr) -> Result { @@ -39,7 +39,8 @@ pub(super) fn sockaddr_smol2nal(endpoint: IpEndpoint) -> nal::SocketAddr { } #[cfg(feature = "proto-ipv6")] IpAddress::Ipv6(addr) => { - embedded_nal_async::SocketAddrV6::new(addr.0.into(), endpoint.port).into() + // FIXME: Where is smoltcp's zone identifier? + embedded_nal_async::SocketAddrV6::new(addr.0.into(), endpoint.port, 0, 0).into() } } } diff --git a/src/riot-rs-embassy/Cargo.toml b/src/riot-rs-embassy/Cargo.toml index 5fe769e15..8d834e4f9 100644 --- a/src/riot-rs-embassy/Cargo.toml +++ b/src/riot-rs-embassy/Cargo.toml @@ -166,3 +166,7 @@ defmt = [ # usb peripheral support. stm32-usb = [] stm32-usb-synopsis = [] + +# Made available so that downsteram crates such as riot-rs-coap don't need to +# pull in embassy-net just to enable a feature they use through a pub-use. +udp = ["net", "embassy-net/udp"] diff --git a/src/riot-rs/Cargo.toml b/src/riot-rs/Cargo.toml index 9fb1cd2f8..7753ad0ac 100644 --- a/src/riot-rs/Cargo.toml +++ b/src/riot-rs/Cargo.toml @@ -13,6 +13,7 @@ document-features = { workspace = true } linkme = { workspace = true } riot-rs-bench = { workspace = true, optional = true } riot-rs-boards = { path = "../riot-rs-boards" } +riot-rs-coap = { path = "../riot-rs-coap", optional = true } riot-rs-debug = { workspace = true } riot-rs-embassy = { path = "../riot-rs-embassy" } riot-rs-macros = { path = "../riot-rs-macros" } @@ -43,6 +44,8 @@ random = ["riot-rs-random"] csprng = ["riot-rs-random/csprng"] ## Enables seeding the random number generator from hardware. hwrng = ["riot-rs-embassy/hwrng"] +## Enables the [`coap`] module +coap = ["riot-rs-coap", "random", "csprng"] #! ## Wired communication ## Enables USB support. diff --git a/src/riot-rs/src/lib.rs b/src/riot-rs/src/lib.rs index a9093d316..564131e1f 100644 --- a/src/riot-rs/src/lib.rs +++ b/src/riot-rs/src/lib.rs @@ -16,6 +16,9 @@ pub mod buildinfo; #[cfg(feature = "bench")] #[doc(inline)] pub use riot_rs_bench as bench; +#[cfg(feature = "coap")] +#[doc(inline)] +pub use riot_rs_coap as coap; #[doc(inline)] pub use riot_rs_debug as debug; #[doc(inline)]