From a2808a3334689083af5dbda1a6b416aa9ca71f88 Mon Sep 17 00:00:00 2001 From: James Munns Date: Thu, 31 Oct 2024 20:03:34 +0100 Subject: [PATCH] Expand the capabilities of various macros (#55) * Hah Hah! I'm doing macros! * Macros overwhelming! * Remove now-incorrect message pre-check * Implement schema querying * Add minimal reference example * Implement logging, fill out docs, format --- example/firmware/.cargo/config.toml | 4 + example/firmware/Cargo.lock | 19 +- example/firmware/Cargo.toml | 2 +- example/firmware/src/bin/logging.rs | 135 ++++++ example/firmware/src/bin/minimal.rs | 116 +++++ example/workbook-host/Cargo.lock | 398 +++++++++++++++- example/workbook-host/Cargo.toml | 1 + example/workbook-host/src/bin/comms-02.rs | 31 +- example/workbook-host/src/bin/logging.rs | 19 + example/workbook-host/src/client.rs | 4 +- example/workbook-icd/Cargo.lock | 436 +++++++++++++++++- example/workbook-icd/Cargo.toml | 3 + example/workbook-icd/src/lib.rs | 32 +- source/postcard-rpc-test/Cargo.lock | 398 +++++++++++++++- source/postcard-rpc-test/Cargo.toml | 4 + source/postcard-rpc-test/tests/basic.rs | 148 +++++- source/postcard-rpc/Cargo.toml | 17 +- source/postcard-rpc/src/host_client/mod.rs | 292 +++++++++++- .../postcard-rpc/src/host_client/raw_nusb.rs | 10 +- source/postcard-rpc/src/lib.rs | 107 ++--- source/postcard-rpc/src/macros.rs | 329 ++++++++++--- .../postcard-rpc/src/server/dispatch_macro.rs | 82 +--- .../src/server/impls/embassy_usb_v0_3.rs | 272 +++++++++-- .../src/server/impls/test_channels.rs | 70 ++- source/postcard-rpc/src/server/mod.rs | 140 +++++- source/postcard-rpc/src/standard_icd.rs | 154 +++++++ source/postcard-rpc/src/uniques.rs | 62 ++- 27 files changed, 2971 insertions(+), 314 deletions(-) create mode 100644 example/firmware/src/bin/logging.rs create mode 100644 example/firmware/src/bin/minimal.rs create mode 100644 example/workbook-host/src/bin/logging.rs create mode 100644 source/postcard-rpc/src/standard_icd.rs diff --git a/example/firmware/.cargo/config.toml b/example/firmware/.cargo/config.toml index 4295fad..75850f3 100644 --- a/example/firmware/.cargo/config.toml +++ b/example/firmware/.cargo/config.toml @@ -6,3 +6,7 @@ target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+ [env] DEFMT_LOG = "debug" + +[unstable] +build-std = ["core"] +build-std-features = ["panic_immediate_abort"] diff --git a/example/firmware/Cargo.lock b/example/firmware/Cargo.lock index 6857ab8..f24483b 100644 --- a/example/firmware/Cargo.lock +++ b/example/firmware/Cargo.lock @@ -333,7 +333,7 @@ name = "embassy-embedded-hal" version = "0.2.0" source = "git+https://github.com/embassy-rs/embassy?rev=32cff6530fdb81066451cc1d3f1fbbb420e985da#32cff6530fdb81066451cc1d3f1fbbb420e985da" dependencies = [ - "embassy-futures", + "embassy-futures 0.1.1 (git+https://github.com/embassy-rs/embassy?rev=32cff6530fdb81066451cc1d3f1fbbb420e985da)", "embassy-sync", "embassy-time", "embedded-hal 0.2.7", @@ -369,6 +369,12 @@ dependencies = [ "syn 2.0.72", ] +[[package]] +name = "embassy-futures" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f878075b9794c1e4ac788c95b728f26aa6366d32eeb10c7051389f898f7d067" + [[package]] name = "embassy-futures" version = "0.1.1" @@ -395,7 +401,7 @@ name = "embassy-net-driver-channel" version = "0.3.0" source = "git+https://github.com/embassy-rs/embassy?rev=32cff6530fdb81066451cc1d3f1fbbb420e985da#32cff6530fdb81066451cc1d3f1fbbb420e985da" dependencies = [ - "embassy-futures", + "embassy-futures 0.1.1 (git+https://github.com/embassy-rs/embassy?rev=32cff6530fdb81066451cc1d3f1fbbb420e985da)", "embassy-net-driver", "embassy-sync", ] @@ -413,7 +419,7 @@ dependencies = [ "defmt", "document-features", "embassy-embedded-hal", - "embassy-futures", + "embassy-futures 0.1.1 (git+https://github.com/embassy-rs/embassy?rev=32cff6530fdb81066451cc1d3f1fbbb420e985da)", "embassy-hal-internal", "embassy-sync", "embassy-time", @@ -487,7 +493,7 @@ version = "0.3.0" source = "git+https://github.com/embassy-rs/embassy?rev=32cff6530fdb81066451cc1d3f1fbbb420e985da#32cff6530fdb81066451cc1d3f1fbbb420e985da" dependencies = [ "defmt", - "embassy-futures", + "embassy-futures 0.1.1 (git+https://github.com/embassy-rs/embassy?rev=32cff6530fdb81066451cc1d3f1fbbb420e985da)", "embassy-net-driver-channel", "embassy-sync", "embassy-usb-driver", @@ -1132,12 +1138,13 @@ name = "postcard-rpc" version = "0.9.0" dependencies = [ "embassy-executor", + "embassy-futures 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "embassy-sync", + "embassy-time", "embassy-usb", "embassy-usb-driver", - "futures-util", "heapless 0.8.0", - "paste", + "portable-atomic", "postcard", "postcard-schema", "serde", diff --git a/example/firmware/Cargo.toml b/example/firmware/Cargo.toml index f1e7a07..cc4aec8 100644 --- a/example/firmware/Cargo.toml +++ b/example/firmware/Cargo.toml @@ -34,7 +34,7 @@ static_cell = "2.1" [profile.release] debug = 2 lto = true -opt-level = 's' +opt-level = 'z' codegen-units = 1 incremental = false diff --git a/example/firmware/src/bin/logging.rs b/example/firmware/src/bin/logging.rs new file mode 100644 index 0000000..7c484a2 --- /dev/null +++ b/example/firmware/src/bin/logging.rs @@ -0,0 +1,135 @@ +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::{peripherals::USB, usb}; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_time::{Duration, Ticker}; +use embassy_usb::{Config, UsbDevice}; +use postcard_rpc::{ + define_dispatch, sender_fmt, + server::{ + impls::embassy_usb_v0_3::{ + dispatch_impl::{WireRxBuf, WireRxImpl, WireSpawnImpl, WireStorage, WireTxImpl}, + PacketBuffers, + }, + Dispatch, Sender, Server, + }, +}; +use static_cell::ConstStaticCell; +use workbook_fw::Irqs; +use workbook_icd::{ENDPOINT_LIST, TOPICS_IN_LIST, TOPICS_OUT_LIST}; +use {defmt_rtt as _, panic_probe as _}; + +pub struct Context {} + +type AppDriver = usb::Driver<'static, USB>; +type AppStorage = WireStorage; +type BufStorage = PacketBuffers<1024, 1024>; +type AppTx = WireTxImpl; +type AppRx = WireRxImpl; +type AppServer = Server; + +static PBUFS: ConstStaticCell = ConstStaticCell::new(BufStorage::new()); +static STORAGE: AppStorage = AppStorage::new(); + +fn usb_config() -> Config<'static> { + let mut config = Config::new(0x16c0, 0x27DD); + config.manufacturer = Some("OneVariable"); + config.product = Some("ov-twin"); + config.serial_number = Some("12345678"); + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + config +} + +define_dispatch! { + app: MyApp; + spawn_fn: spawn_fn; + tx_impl: AppTx; + spawn_impl: WireSpawnImpl; + context: Context; + + endpoints: { + list: ENDPOINT_LIST; + + | EndpointTy | kind | handler | + | ---------- | ---- | ------- | + }; + topics_in: { + list: TOPICS_IN_LIST; + + | TopicTy | kind | handler | + | ---------- | ---- | ------- | + }; + topics_out: { + list: TOPICS_OUT_LIST; + }; +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + // SYSTEM INIT + info!("Start"); + let p = embassy_rp::init(Default::default()); + + // USB/RPC INIT + let driver = usb::Driver::new(p.USB, Irqs); + let pbufs = PBUFS.take(); + let config = usb_config(); + + let context = Context {}; + + let (device, tx_impl, rx_impl) = STORAGE.init(driver, config, pbufs.tx_buf.as_mut_slice()); + let dispatcher = MyApp::new(context, spawner.into()); + let vkk = dispatcher.min_key_len(); + let server: AppServer = Server::new( + &tx_impl, + rx_impl, + pbufs.rx_buf.as_mut_slice(), + dispatcher, + vkk, + ); + let sender = server.sender(); + spawner.must_spawn(usb_task(device)); + spawner.must_spawn(server_task(server)); + spawner.must_spawn(logging_task(sender)); +} + +#[embassy_executor::task] +pub async fn logging_task(sender: Sender) { + let mut ticker = Ticker::every(Duration::from_millis(1000)); + let mut ctr = 0u16; + loop { + ticker.next().await; + defmt::info!("logging"); + if ctr & 0b1 != 0 { + let _ = sender.log_str("Hello world!").await; + } else { + let _ = sender_fmt!(sender, "formatted: {ctr}").await; + } + ctr = ctr.wrapping_add(1); + } +} + +#[embassy_executor::task] +pub async fn server_task(mut server: AppServer) { + loop { + // If the host disconnects, we'll return an error here. + // If this happens, just wait until the host reconnects + let _ = server.run().await; + } +} + +/// This handles the low level USB management +#[embassy_executor::task] +pub async fn usb_task(mut usb: UsbDevice<'static, AppDriver>) { + usb.run().await; +} diff --git a/example/firmware/src/bin/minimal.rs b/example/firmware/src/bin/minimal.rs new file mode 100644 index 0000000..501b47c --- /dev/null +++ b/example/firmware/src/bin/minimal.rs @@ -0,0 +1,116 @@ +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::{peripherals::USB, usb}; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_usb::{Config, UsbDevice}; +use postcard_rpc::{ + define_dispatch, + server::{ + impls::embassy_usb_v0_3::{ + dispatch_impl::{WireRxBuf, WireRxImpl, WireSpawnImpl, WireStorage, WireTxImpl}, + PacketBuffers, + }, + Dispatch, Server, + }, +}; +use static_cell::ConstStaticCell; +use workbook_fw::Irqs; +use workbook_icd::{ENDPOINT_LIST, TOPICS_IN_LIST, TOPICS_OUT_LIST}; +use {defmt_rtt as _, panic_probe as _}; + +pub struct Context {} + +type AppDriver = usb::Driver<'static, USB>; +type AppStorage = WireStorage; +type BufStorage = PacketBuffers<1024, 1024>; +type AppTx = WireTxImpl; +type AppRx = WireRxImpl; +type AppServer = Server; + +static PBUFS: ConstStaticCell = ConstStaticCell::new(BufStorage::new()); +static STORAGE: AppStorage = AppStorage::new(); + +fn usb_config() -> Config<'static> { + let mut config = Config::new(0x16c0, 0x27DD); + config.manufacturer = Some("OneVariable"); + config.product = Some("ov-twin"); + config.serial_number = Some("12345678"); + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + config +} + +define_dispatch! { + app: MyApp; + spawn_fn: spawn_fn; + tx_impl: AppTx; + spawn_impl: WireSpawnImpl; + context: Context; + + endpoints: { + list: ENDPOINT_LIST; + + | EndpointTy | kind | handler | + | ---------- | ---- | ------- | + }; + topics_in: { + list: TOPICS_IN_LIST; + + | TopicTy | kind | handler | + | ---------- | ---- | ------- | + }; + topics_out: { + list: TOPICS_OUT_LIST; + }; +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + // SYSTEM INIT + info!("Start"); + let p = embassy_rp::init(Default::default()); + + // USB/RPC INIT + let driver = usb::Driver::new(p.USB, Irqs); + let pbufs = PBUFS.take(); + let config = usb_config(); + + let context = Context {}; + + let (device, tx_impl, rx_impl) = STORAGE.init(driver, config, pbufs.tx_buf.as_mut_slice()); + let dispatcher = MyApp::new(context, spawner.into()); + let vkk = dispatcher.min_key_len(); + let server: AppServer = Server::new( + &tx_impl, + rx_impl, + pbufs.rx_buf.as_mut_slice(), + dispatcher, + vkk, + ); + spawner.must_spawn(usb_task(device)); + spawner.must_spawn(server_task(server)); +} + +#[embassy_executor::task] +pub async fn server_task(mut server: AppServer) { + loop { + // If the host disconnects, we'll return an error here. + // If this happens, just wait until the host reconnects + let _ = server.run().await; + } +} + +/// This handles the low level USB management +#[embassy_executor::task] +pub async fn usb_task(mut usb: UsbDevice<'static, AppDriver>) { + usb.run().await; +} diff --git a/example/workbook-host/Cargo.lock b/example/workbook-host/Cargo.lock index 8ab90b1..0d64ec2 100644 --- a/example/workbook-host/Cargo.lock +++ b/example/workbook-host/Cargo.lock @@ -17,6 +17,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -62,6 +74,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "bitfield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" + [[package]] name = "bitflags" version = "2.5.0" @@ -130,12 +148,209 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.60", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "document-features" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0" +dependencies = [ + "litrs", +] + +[[package]] +name = "embassy-executor" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fd0a2386252214d31d22400730e28e9c6bc62b346df62802e30a0bb3677e43b" +dependencies = [ + "critical-section", + "document-features", + "embassy-executor-macros", +] + +[[package]] +name = "embassy-executor-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c853e6bcad2d1c0811f4de404cef87363a1fa2535430cf76824c163cf75689ba" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "embassy-futures" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f878075b9794c1e4ac788c95b728f26aa6366d32eeb10c7051389f898f7d067" + +[[package]] +name = "embassy-net-driver" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524eb3c489760508f71360112bca70f6e53173e6fe48fc5f0efd0f5ab217751d" + +[[package]] +name = "embassy-net-driver-channel" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4818c32afec43e3cae234f324bad9a976c9aa7501022d26ff60a4017a1a006b7" +dependencies = [ + "embassy-futures", + "embassy-net-driver", + "embassy-sync", +] + +[[package]] +name = "embassy-sync" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3e0c49ff02ebe324faf3a8653ba91582e2d0a7fdef5bc88f449d5aa1bfcc05c" +dependencies = [ + "cfg-if", + "critical-section", + "embedded-io-async", + "futures-util", + "heapless 0.8.0", +] + +[[package]] +name = "embassy-time" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "158080d48f824fad101d7b2fae2d83ac39e3f7a6fa01811034f7ab8ffc6e7309" +dependencies = [ + "cfg-if", + "critical-section", + "document-features", + "embassy-time-driver", + "embassy-time-queue-driver", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "futures-util", + "heapless 0.8.0", +] + +[[package]] +name = "embassy-time-driver" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e0c214077aaa9206958b16411c157961fb7990d4ea628120a78d1a5a28aed24" +dependencies = [ + "document-features", +] + +[[package]] +name = "embassy-time-queue-driver" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1177859559ebf42cd24ae7ba8fe6ee707489b01d0bf471f8827b7b12dcb0bc0" + +[[package]] +name = "embassy-usb" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d0b882133fa684b9d4652351cd7aac5afe8a2c2bf4a7da59f442ff61087cda2" +dependencies = [ + "embassy-futures", + "embassy-net-driver-channel", + "embassy-sync", + "embassy-usb-driver", + "heapless 0.8.0", + "ssmarshal", + "usbd-hid", +] + +[[package]] +name = "embassy-usb-driver" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fc247028eae04174b6635104a35b1ed336aabef4654f5e87a8f32327d231970" + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "embedded-hal-async" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" +dependencies = [ + "embedded-hal 1.0.0", +] + [[package]] name = "embedded-io" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "embedded-io-async" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" +dependencies = [ + "embedded-io 0.6.1", +] + [[package]] name = "encode_unicode" version = "0.3.6" @@ -152,6 +367,36 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + [[package]] name = "generator" version = "0.7.5" @@ -203,6 +448,15 @@ dependencies = [ "byteorder", ] +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + [[package]] name = "heapless" version = "0.7.17" @@ -233,6 +487,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "io-kit-sys" version = "0.4.1" @@ -261,6 +521,12 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + [[package]] name = "lock_api" version = "0.4.11" @@ -355,6 +621,21 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24e0cc5e2c585acbd15c5ce911dff71e1f4d5313f43345873311c4f5efd741cc" +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -413,12 +694,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "pin-project" version = "1.1.5" @@ -445,6 +720,12 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "portable-atomic" version = "1.6.0" @@ -458,7 +739,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a55c51ee6c0db07e68448e336cf8ea4131a620edefebf9893e759b2d793420f8" dependencies = [ "cobs", - "embedded-io", + "embedded-io 0.4.0", "heapless 0.7.17", "serde", ] @@ -478,14 +759,21 @@ dependencies = [ name = "postcard-rpc" version = "0.9.0" dependencies = [ + "embassy-executor", + "embassy-futures", + "embassy-sync", + "embassy-time", + "embassy-usb", + "embassy-usb-driver", "heapless 0.8.0", "maitake-sync", "nusb", - "paste", + "portable-atomic", "postcard", "postcard-schema", "serde", "ssmarshal", + "static_cell", "thiserror", "tokio", "tracing", @@ -685,6 +973,21 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_cell" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89b0684884a883431282db1e4343f34afc2ff6996fe1f4a1664519b66e14c1e" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "1.0.109" @@ -839,12 +1142,71 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "usb-device" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" +dependencies = [ + "heapless 0.8.0", + "portable-atomic", +] + +[[package]] +name = "usbd-hid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f291ab53d428685cc780f08a2eb9d5d6ff58622db2b36e239a4f715f1e184c" +dependencies = [ + "serde", + "ssmarshal", + "usb-device", + "usbd-hid-macros", +] + +[[package]] +name = "usbd-hid-descriptors" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee54712c5d778d2fb2da43b1ce5a7b5060886ef7b09891baeb4bf36910a3ed" +dependencies = [ + "bitfield", +] + +[[package]] +name = "usbd-hid-macros" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb573c76e7884035ac5e1ab4a81234c187a82b6100140af0ab45757650ccda38" +dependencies = [ + "byteorder", + "hashbrown", + "log", + "proc-macro2", + "quote", + "serde", + "syn 1.0.109", + "usbd-hid-descriptors", +] + [[package]] name = "valuable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + [[package]] name = "winapi" version = "0.3.9" @@ -1062,3 +1424,23 @@ dependencies = [ "postcard-schema", "serde", ] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] diff --git a/example/workbook-host/Cargo.toml b/example/workbook-host/Cargo.toml index 5fa829b..97d4495 100644 --- a/example/workbook-host/Cargo.toml +++ b/example/workbook-host/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies.workbook-icd] path = "../workbook-icd" +features = ["use-std"] [dependencies.postcard-rpc] version = "0.9" diff --git a/example/workbook-host/src/bin/comms-02.rs b/example/workbook-host/src/bin/comms-02.rs index d119888..37806a8 100644 --- a/example/workbook-host/src/bin/comms-02.rs +++ b/example/workbook-host/src/bin/comms-02.rs @@ -7,7 +7,7 @@ use workbook_host_client::{client::WorkbookClient, icd, read_line}; #[tokio::main] async fn main() { - println!("Connecting..."); + println!("Connecting to USB device..."); let client = WorkbookClient::new(); println!("Connected! Pinging 42"); let ping = client.ping(42).await.unwrap(); @@ -103,6 +103,35 @@ async fn main() { let res = client.stop_accelerometer().await.unwrap(); println!("Stopped: {res}"); } + ["schema"] => { + let schema = client.client.get_schema_report().await.unwrap(); + + println!(); + println!("# Endpoints"); + println!(); + for ep in &schema.endpoints { + println!("* '{}'", ep.path); + println!(" * Request: {}", ep.req_ty); + println!(" * Response: {}", ep.resp_ty); + } + + println!(); + println!("# Topics Client -> Server"); + println!(); + for tp in &schema.topics_in { + println!("* '{}'", tp.path); + println!(" * Message: {}", tp.ty); + } + + println!(); + println!("# Topics Client <- Server"); + println!(); + for tp in &schema.topics_out { + println!("* '{}'", tp.path); + println!(" * Message: {}", tp.ty); + } + println!(); + } other => { println!("Error, didn't understand '{other:?};"); } diff --git a/example/workbook-host/src/bin/logging.rs b/example/workbook-host/src/bin/logging.rs new file mode 100644 index 0000000..6143e38 --- /dev/null +++ b/example/workbook-host/src/bin/logging.rs @@ -0,0 +1,19 @@ +use postcard_rpc::standard_icd::LoggingTopic; +use workbook_host_client::client::WorkbookClient; + +#[tokio::main] +async fn main() { + println!("Connecting to USB device..."); + let client = WorkbookClient::new(); + println!("Connected! Pinging 42"); + let ping = client.ping(42).await.unwrap(); + println!("Got: {ping}."); + println!(); + + let mut logsub = client.client.subscribe::(64).await.unwrap(); + + while let Some(msg) = logsub.recv().await { + println!("LOG: {msg}"); + } + println!("Device disconnected"); +} diff --git a/example/workbook-host/src/client.rs b/example/workbook-host/src/client.rs index 0688a7a..f38009d 100644 --- a/example/workbook-host/src/client.rs +++ b/example/workbook-host/src/client.rs @@ -1,11 +1,11 @@ use postcard_rpc::{ header::VarSeqKind, host_client::{HostClient, HostErr}, - standard_icd::{WireError, ERROR_PATH}, + standard_icd::{PingEndpoint, WireError, ERROR_PATH}, }; use std::convert::Infallible; use workbook_icd::{ - AccelRange, BadPositionError, GetUniqueIdEndpoint, PingEndpoint, Rgb8, SetAllLedEndpoint, + AccelRange, BadPositionError, GetUniqueIdEndpoint, Rgb8, SetAllLedEndpoint, SetSingleLedEndpoint, SingleLed, StartAccel, StartAccelerationEndpoint, StopAccelerationEndpoint, }; diff --git a/example/workbook-icd/Cargo.lock b/example/workbook-icd/Cargo.lock index 66aeb14..7b28e1c 100644 --- a/example/workbook-icd/Cargo.lock +++ b/example/workbook-icd/Cargo.lock @@ -2,6 +2,18 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "atomic-polyfill" version = "1.0.3" @@ -17,12 +29,24 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +[[package]] +name = "bitfield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" + [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "cobs" version = "0.2.3" @@ -35,6 +59,239 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.60", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "document-features" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0" +dependencies = [ + "litrs", +] + +[[package]] +name = "embassy-executor" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fd0a2386252214d31d22400730e28e9c6bc62b346df62802e30a0bb3677e43b" +dependencies = [ + "critical-section", + "document-features", + "embassy-executor-macros", +] + +[[package]] +name = "embassy-executor-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c853e6bcad2d1c0811f4de404cef87363a1fa2535430cf76824c163cf75689ba" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "embassy-futures" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f878075b9794c1e4ac788c95b728f26aa6366d32eeb10c7051389f898f7d067" + +[[package]] +name = "embassy-net-driver" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524eb3c489760508f71360112bca70f6e53173e6fe48fc5f0efd0f5ab217751d" + +[[package]] +name = "embassy-net-driver-channel" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4818c32afec43e3cae234f324bad9a976c9aa7501022d26ff60a4017a1a006b7" +dependencies = [ + "embassy-futures", + "embassy-net-driver", + "embassy-sync", +] + +[[package]] +name = "embassy-sync" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3e0c49ff02ebe324faf3a8653ba91582e2d0a7fdef5bc88f449d5aa1bfcc05c" +dependencies = [ + "cfg-if", + "critical-section", + "embedded-io-async", + "futures-util", + "heapless 0.8.0", +] + +[[package]] +name = "embassy-time" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "158080d48f824fad101d7b2fae2d83ac39e3f7a6fa01811034f7ab8ffc6e7309" +dependencies = [ + "cfg-if", + "critical-section", + "document-features", + "embassy-time-driver", + "embassy-time-queue-driver", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "futures-util", + "heapless 0.8.0", +] + +[[package]] +name = "embassy-time-driver" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e0c214077aaa9206958b16411c157961fb7990d4ea628120a78d1a5a28aed24" +dependencies = [ + "document-features", +] + +[[package]] +name = "embassy-time-queue-driver" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1177859559ebf42cd24ae7ba8fe6ee707489b01d0bf471f8827b7b12dcb0bc0" + +[[package]] +name = "embassy-usb" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d0b882133fa684b9d4652351cd7aac5afe8a2c2bf4a7da59f442ff61087cda2" +dependencies = [ + "embassy-futures", + "embassy-net-driver-channel", + "embassy-sync", + "embassy-usb-driver", + "heapless 0.8.0", + "ssmarshal", + "usbd-hid", +] + +[[package]] +name = "embassy-usb-driver" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fc247028eae04174b6635104a35b1ed336aabef4654f5e87a8f32327d231970" + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "embedded-hal-async" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" +dependencies = [ + "embedded-hal 1.0.0", +] + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "embedded-io-async" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" +dependencies = [ + "embedded-io", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + [[package]] name = "hash32" version = "0.2.1" @@ -53,6 +310,15 @@ dependencies = [ "byteorder", ] +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + [[package]] name = "heapless" version = "0.7.17" @@ -77,6 +343,18 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + [[package]] name = "lock_api" version = "0.4.11" @@ -88,10 +366,49 @@ dependencies = [ ] [[package]] -name = "paste" -version = "1.0.15" +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "once_cell" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "portable-atomic" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" [[package]] name = "postcard" @@ -119,11 +436,18 @@ dependencies = [ name = "postcard-rpc" version = "0.9.0" dependencies = [ + "embassy-executor", + "embassy-futures", + "embassy-sync", + "embassy-time", + "embassy-usb", + "embassy-usb-driver", "heapless 0.8.0", - "paste", + "portable-atomic", "postcard", "postcard-schema", "serde", + "static_cell", ] [[package]] @@ -204,12 +528,37 @@ dependencies = [ "lock_api", ] +[[package]] +name = "ssmarshal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e6ad23b128192ed337dfa4f1b8099ced0c2bf30d61e551b65fda5916dbb850" +dependencies = [ + "encode_unicode", + "serde", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_cell" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89b0684884a883431282db1e4343f34afc2ff6996fe1f4a1664519b66e14c1e" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "1.0.109" @@ -238,6 +587,65 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "usb-device" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" +dependencies = [ + "heapless 0.8.0", + "portable-atomic", +] + +[[package]] +name = "usbd-hid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f291ab53d428685cc780f08a2eb9d5d6ff58622db2b36e239a4f715f1e184c" +dependencies = [ + "serde", + "ssmarshal", + "usb-device", + "usbd-hid-macros", +] + +[[package]] +name = "usbd-hid-descriptors" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee54712c5d778d2fb2da43b1ce5a7b5060886ef7b09891baeb4bf36910a3ed" +dependencies = [ + "bitfield", +] + +[[package]] +name = "usbd-hid-macros" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb573c76e7884035ac5e1ab4a81234c187a82b6100140af0ab45757650ccda38" +dependencies = [ + "byteorder", + "hashbrown", + "log", + "proc-macro2", + "quote", + "serde", + "syn 1.0.109", + "usbd-hid-descriptors", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + [[package]] name = "workbook-icd" version = "0.1.0" @@ -246,3 +654,23 @@ dependencies = [ "postcard-schema", "serde", ] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] diff --git a/example/workbook-icd/Cargo.toml b/example/workbook-icd/Cargo.toml index 7b9e0c7..2834f06 100644 --- a/example/workbook-icd/Cargo.toml +++ b/example/workbook-icd/Cargo.toml @@ -17,3 +17,6 @@ features = ["derive"] [patch.crates-io] postcard-rpc = { path = "../../source/postcard-rpc" } + +[features] +use-std = [] diff --git a/example/workbook-icd/src/lib.rs b/example/workbook-icd/src/lib.rs index ecf109c..9222773 100644 --- a/example/workbook-icd/src/lib.rs +++ b/example/workbook-icd/src/lib.rs @@ -1,34 +1,40 @@ -#![no_std] +#![cfg_attr(not(feature = "use-std"), no_std)] -use postcard_rpc::{endpoints, topics}; +use postcard_rpc::{endpoints, topics, TopicDirection}; use postcard_schema::Schema; use serde::{Deserialize, Serialize}; // --- +pub type SingleLedSetResult = Result<(), BadPositionError>; +pub type AllLedArray = [Rgb8; 24]; + endpoints! { list = ENDPOINT_LIST; - | EndpointTy | RequestTy | ResponseTy | Path | - | ---------- | --------- | ---------- | ---- | - | PingEndpoint | u32 | u32 | "ping" | - | GetUniqueIdEndpoint | () | u64 | "unique_id/get" | - | SetSingleLedEndpoint | SingleLed | Result<(), BadPositionError> | "led/set_one" | - | SetAllLedEndpoint | [Rgb8; 24] | () | "led/set_all" | - | StartAccelerationEndpoint | StartAccel | () | "accel/start" | - | StopAccelerationEndpoint | () | bool | "accel/stop" | + omit_std = true; + | EndpointTy | RequestTy | ResponseTy | Path | + | ---------- | --------- | ---------- | ---- | + | PingEndpoint | u32 | u32 | "ping" | + | GetUniqueIdEndpoint | () | u64 | "unique_id/get" | + | SetSingleLedEndpoint | SingleLed | SingleLedSetResult | "led/set_one" | + | SetAllLedEndpoint | AllLedArray | () | "led/set_all" | + | StartAccelerationEndpoint | StartAccel | () | "accel/start" | + | StopAccelerationEndpoint | () | bool | "accel/stop" | } topics! { list = TOPICS_IN_LIST; + direction = TopicDirection::ToServer; | TopicTy | MessageTy | Path | | ------- | --------- | ---- | } topics! { list = TOPICS_OUT_LIST; - | TopicTy | MessageTy | Path | - | ------- | --------- | ---- | - | AccelTopic | Acceleration | "accel/data" | + direction = TopicDirection::ToClient; + | TopicTy | MessageTy | Path | Cfg | + | ------- | --------- | ---- | --- | + | AccelTopic | Acceleration | "accel/data" | | } #[derive(Serialize, Deserialize, Schema, Debug, PartialEq)] diff --git a/source/postcard-rpc-test/Cargo.lock b/source/postcard-rpc-test/Cargo.lock index 517d414..ce07f2c 100644 --- a/source/postcard-rpc-test/Cargo.lock +++ b/source/postcard-rpc-test/Cargo.lock @@ -17,6 +17,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -56,6 +68,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "bitfield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" + [[package]] name = "byteorder" version = "1.5.0" @@ -125,18 +143,245 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.39", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "document-features" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0" +dependencies = [ + "litrs", +] + +[[package]] +name = "embassy-executor" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fd0a2386252214d31d22400730e28e9c6bc62b346df62802e30a0bb3677e43b" +dependencies = [ + "critical-section", + "document-features", + "embassy-executor-macros", +] + +[[package]] +name = "embassy-executor-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c853e6bcad2d1c0811f4de404cef87363a1fa2535430cf76824c163cf75689ba" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "embassy-futures" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f878075b9794c1e4ac788c95b728f26aa6366d32eeb10c7051389f898f7d067" + +[[package]] +name = "embassy-net-driver" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524eb3c489760508f71360112bca70f6e53173e6fe48fc5f0efd0f5ab217751d" + +[[package]] +name = "embassy-net-driver-channel" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4818c32afec43e3cae234f324bad9a976c9aa7501022d26ff60a4017a1a006b7" +dependencies = [ + "embassy-futures", + "embassy-net-driver", + "embassy-sync", +] + +[[package]] +name = "embassy-sync" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3e0c49ff02ebe324faf3a8653ba91582e2d0a7fdef5bc88f449d5aa1bfcc05c" +dependencies = [ + "cfg-if", + "critical-section", + "embedded-io-async", + "futures-util", + "heapless 0.8.0", +] + +[[package]] +name = "embassy-time" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "158080d48f824fad101d7b2fae2d83ac39e3f7a6fa01811034f7ab8ffc6e7309" +dependencies = [ + "cfg-if", + "critical-section", + "document-features", + "embassy-time-driver", + "embassy-time-queue-driver", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "futures-util", + "heapless 0.8.0", +] + +[[package]] +name = "embassy-time-driver" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e0c214077aaa9206958b16411c157961fb7990d4ea628120a78d1a5a28aed24" +dependencies = [ + "document-features", +] + +[[package]] +name = "embassy-time-queue-driver" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1177859559ebf42cd24ae7ba8fe6ee707489b01d0bf471f8827b7b12dcb0bc0" + +[[package]] +name = "embassy-usb" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d0b882133fa684b9d4652351cd7aac5afe8a2c2bf4a7da59f442ff61087cda2" +dependencies = [ + "embassy-futures", + "embassy-net-driver-channel", + "embassy-sync", + "embassy-usb-driver", + "heapless 0.8.0", + "ssmarshal", + "usbd-hid", +] + +[[package]] +name = "embassy-usb-driver" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fc247028eae04174b6635104a35b1ed336aabef4654f5e87a8f32327d231970" + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "embedded-hal-async" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" +dependencies = [ + "embedded-hal 1.0.0", +] + [[package]] name = "embedded-io" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "embedded-io-async" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" +dependencies = [ + "embedded-io 0.6.1", +] + [[package]] name = "encode_unicode" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + [[package]] name = "generator" version = "0.7.5" @@ -174,6 +419,15 @@ dependencies = [ "byteorder", ] +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + [[package]] name = "heapless" version = "0.7.16" @@ -198,6 +452,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "lazy_static" version = "1.4.0" @@ -210,6 +470,12 @@ version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + [[package]] name = "lock_api" version = "0.4.11" @@ -295,6 +561,21 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1178a37e39d26b8c3bbd3197460a59939ca2b8fe002ea92c1d9a1ba87c203d85" +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -326,12 +607,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "pin-project" version = "1.1.3" @@ -358,6 +633,12 @@ version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "portable-atomic" version = "1.5.1" @@ -372,7 +653,7 @@ checksum = "a55c51ee6c0db07e68448e336cf8ea4131a620edefebf9893e759b2d793420f8" dependencies = [ "cobs", "const_format", - "embedded-io", + "embedded-io 0.4.0", "heapless 0.7.16", "postcard-derive 0.1.1", "serde", @@ -404,13 +685,20 @@ dependencies = [ name = "postcard-rpc" version = "0.9.0" dependencies = [ + "embassy-executor", + "embassy-futures", + "embassy-sync", + "embassy-time", + "embassy-usb", + "embassy-usb-driver", "heapless 0.8.0", "maitake-sync", - "paste", + "portable-atomic", "postcard", "postcard-schema", "serde", "ssmarshal", + "static_cell", "thiserror", "tokio", "tracing", @@ -599,6 +887,21 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_cell" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89b0684884a883431282db1e4343f34afc2ff6996fe1f4a1664519b66e14c1e" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "1.0.109" @@ -758,12 +1061,71 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "usb-device" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" +dependencies = [ + "heapless 0.8.0", + "portable-atomic", +] + +[[package]] +name = "usbd-hid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f291ab53d428685cc780f08a2eb9d5d6ff58622db2b36e239a4f715f1e184c" +dependencies = [ + "serde", + "ssmarshal", + "usb-device", + "usbd-hid-macros", +] + +[[package]] +name = "usbd-hid-descriptors" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee54712c5d778d2fb2da43b1ce5a7b5060886ef7b09891baeb4bf36910a3ed" +dependencies = [ + "bitfield", +] + +[[package]] +name = "usbd-hid-macros" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb573c76e7884035ac5e1ab4a81234c187a82b6100140af0ab45757650ccda38" +dependencies = [ + "byteorder", + "hashbrown", + "log", + "proc-macro2", + "quote", + "serde", + "syn 1.0.109", + "usbd-hid-descriptors", +] + [[package]] name = "valuable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + [[package]] name = "winapi" version = "0.3.9" @@ -851,3 +1213,23 @@ name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] diff --git a/source/postcard-rpc-test/Cargo.toml b/source/postcard-rpc-test/Cargo.toml index 7278b73..db358f4 100644 --- a/source/postcard-rpc-test/Cargo.toml +++ b/source/postcard-rpc-test/Cargo.toml @@ -22,3 +22,7 @@ features = ["derive"] [dependencies.tokio] version = "1.34.0" features = ["rt", "macros", "sync", "time"] + +[features] +default = ["alpha"] +alpha = [] diff --git a/source/postcard-rpc-test/tests/basic.rs b/source/postcard-rpc-test/tests/basic.rs index 33ffff7..3eaabfb 100644 --- a/source/postcard-rpc-test/tests/basic.rs +++ b/source/postcard-rpc-test/tests/basic.rs @@ -11,7 +11,7 @@ use tokio::{sync::mpsc, task::yield_now}; use postcard_rpc::{ define_dispatch, endpoints, header::{VarHeader, VarKey, VarKeyKind, VarSeq, VarSeqKind}, - host_client::test_channels as client, + host_client::{test_channels as client, HostClient}, server::{ impls::test_channels::{ dispatch_impl::{new_server, spawn_fn, Settings, WireSpawnImpl, WireTxImpl}, @@ -45,28 +45,56 @@ pub struct EResp; #[derive(Serialize, Deserialize, Schema)] pub struct ZMsg(pub i16); +#[cfg(feature = "alpha")] +#[derive(Serialize, Deserialize, Schema)] +pub struct Message<'a> { + data: &'a str, +} + +#[cfg(not(feature = "alpha"))] +#[derive(Serialize, Deserialize, Schema)] +pub struct Message { + data: String, +} + +#[derive(Serialize, Deserialize, Schema)] +pub struct DoubleMessage<'a, 'b> { + data1: &'a str, + data2: &'b str, +} + endpoints! { list = ENDPOINT_LIST; - | EndpointTy | RequestTy | ResponseTy | Path | - | ---------- | --------- | ---------- | ---- | - | AlphaEndpoint | AReq | AResp | "alpha" | - | BetaEndpoint | BReq | BResp | "beta" | - | GammaEndpoint | GReq | GResp | "gamma" | - | DeltaEndpoint | DReq | DResp | "delta" | - | EpsilonEndpoint | EReq | EResp | "epsilon" | + | EndpointTy | RequestTy | ResponseTy | Path | Cfg | + | ---------- | --------- | ---------- | ---- | --- | + | AlphaEndpoint | AReq | AResp | "alpha" | | + | BetaEndpoint | BReq | BResp | "beta" | | + | GammaEndpoint | GReq | GResp | "gamma" | | + | DeltaEndpoint | DReq | DResp | "delta" | | + | EpsilonEndpoint | EReq | EResp | "epsilon" | | + | BorrowEndpoint1 | Message<'a> | u8 | "borrow1" | cfg(feature = "alpha") | + | BorrowEndpoint2 | () | Message<'a> | "borrow2" | | + | BorrowEndpoint3 | Message<'a> | Message<'b> | "borrow3" | | + | BorrowEndpoint4 | DoubleMessage<'a, 'b> | DoubleMessage<'c, 'd> | "borrow4" | | } topics! { list = TOPICS_IN_LIST; - | TopicTy | MessageTy | Path | - | ---------- | --------- | ---- | - | ZetaTopic1 | ZMsg | "zeta1" | - | ZetaTopic2 | ZMsg | "zeta2" | - | ZetaTopic3 | ZMsg | "zeta3" | + direction = postcard_rpc::TopicDirection::ToServer; + | TopicTy | MessageTy | Path | Cfg | + | ---------- | --------- | ---- | --- | + | ZetaTopic1 | ZMsg | "zeta1" | | + | ZetaTopic2 | ZMsg | "zeta2" | | + | ZetaTopic3 | ZMsg | "zeta3" | | + | BorrowTopic | Message<'a> | "msg1" | cfg(feature = "alpha") | + | BorrowTopic | DoubleMessage<'a, 'b> | "msg1" | cfg(not(feature = "alpha")) | + | BerpTopic1 | u8 | "empty" | | + | BerpTopic2 | () | "empty" | | } topics! { list = TOPICS_OUT_LIST; + direction = postcard_rpc::TopicDirection::ToClient; | TopicTy | MessageTy | Path | | ---------- | --------- | ---- | | ZetaTopic10 | ZMsg | "zeta10" | @@ -75,6 +103,7 @@ topics! { pub struct TestContext { pub ctr: Arc, pub topic_ctr: Arc, + pub msg: String, } pub struct TestSpawnContext { @@ -103,10 +132,12 @@ define_dispatch! { endpoints: { list: ENDPOINT_LIST; - | EndpointTy | kind | handler | - | ---------- | ---- | ------- | - | AlphaEndpoint | async | test_alpha_handler | - | BetaEndpoint | spawn | test_beta_handler | + | EndpointTy | kind | handler | + | ---------- | ---- | ------- | + | AlphaEndpoint | async | test_alpha_handler | + | BetaEndpoint | spawn | test_beta_handler | + | BorrowEndpoint1 | blocking | test_borrowep_blocking | + | BorrowEndpoint2 | blocking | test_borrowep_blocking2 | }; topics_in: { list: TOPICS_IN_LIST; @@ -116,12 +147,31 @@ define_dispatch! { | ZetaTopic1 | blocking | test_zeta_blocking | | ZetaTopic2 | async | test_zeta_async | | ZetaTopic3 | spawn | test_zeta_spawn | + | BorrowTopic | blocking | test_borrow_blocking | }; topics_out: { list: TOPICS_OUT_LIST; }; } +fn test_borrowep_blocking2( + context: &mut TestContext, + _header: VarHeader, + _body: (), +) -> Message<'_> { + Message { + data: context.msg.as_str(), + } +} + +fn test_borrowep_blocking( + _context: &mut TestContext, + _header: VarHeader, + _body: Message<'_>, +) -> u8 { + 0 +} + fn test_zeta_blocking( context: &mut TestContext, _header: VarHeader, @@ -131,6 +181,15 @@ fn test_zeta_blocking( context.topic_ctr.fetch_add(1, Ordering::Relaxed); } +fn test_borrow_blocking( + context: &mut TestContext, + _header: VarHeader, + _body: Message, + _out: &Sender, +) { + context.topic_ctr.fetch_add(1, Ordering::Relaxed); +} + async fn test_zeta_async( context: &mut TestContext, _header: VarHeader, @@ -176,6 +235,7 @@ async fn smoke() { TestContext { ctr: Arc::new(AtomicUsize::new(0)), topic_ctr: topic_ctr.clone(), + msg: String::from("hello"), }, ChannelWireSpawn {}, ); @@ -319,6 +379,7 @@ async fn end_to_end() { TestContext { ctr: Arc::new(AtomicUsize::new(0)), topic_ctr: topic_ctr.clone(), + msg: String::from("hello"), }, ChannelWireSpawn {}, ); @@ -348,6 +409,57 @@ async fn end_to_end() { assert_eq!(resp.0, 1234); } +#[tokio::test] +async fn end_to_end_schema() { + let (client_tx, server_rx) = mpsc::channel(16); + let (server_tx, client_rx) = mpsc::channel(16); + let topic_ctr = Arc::new(AtomicUsize::new(0)); + + let app = SingleDispatcher::new( + TestContext { + ctr: Arc::new(AtomicUsize::new(0)), + topic_ctr: topic_ctr.clone(), + msg: String::from("hello"), + }, + ChannelWireSpawn {}, + ); + + let cwrx = ChannelWireRx::new(server_rx); + let cwtx = ChannelWireTx::new(server_tx); + + let kkind = app.min_key_len(); + let report = app.device_map; + let mut server = new_server( + app, + Settings { + tx: cwtx, + rx: cwrx, + buf: 1024, + kkind, + }, + ); + tokio::task::spawn(async move { + server.run().await; + }); + + let cli: HostClient<_> = client::new_from_channels(client_tx, client_rx, VarSeqKind::Seq1); + let schema = cli.get_schema_report().await.unwrap(); + + for ep in &schema.endpoints { + println!("'{}': {} -> {}", ep.path, ep.req_ty, ep.resp_ty); + } + for tp in &schema.topics_in { + println!("'{}' ---> {}", tp.path, tp.ty); + } + for tp in &schema.topics_out { + println!("'{}' <--- {}", tp.path, tp.ty); + } + + assert_eq!(schema.endpoints.len(), report.endpoints.len()); + assert_eq!(schema.topics_in.len(), report.topics_in.len()); + assert_eq!(schema.topics_out.len(), report.topics_out.len()); +} + #[tokio::test] async fn end_to_end_force8() { let (client_tx, server_rx) = mpsc::channel(16); @@ -358,6 +470,7 @@ async fn end_to_end_force8() { TestContext { ctr: Arc::new(AtomicUsize::new(0)), topic_ctr: topic_ctr.clone(), + msg: String::from("hello"), }, ChannelWireSpawn {}, ); @@ -394,6 +507,7 @@ fn device_map() { TestContext { ctr: Arc::new(AtomicUsize::new(0)), topic_ctr: topic_ctr.clone(), + msg: String::from("hello"), }, ChannelWireSpawn {}, ); diff --git a/source/postcard-rpc/Cargo.toml b/source/postcard-rpc/Cargo.toml index c849c8f..33e9c15 100644 --- a/source/postcard-rpc/Cargo.toml +++ b/source/postcard-rpc/Cargo.toml @@ -30,7 +30,6 @@ heapless = "0.8.0" postcard = { version = "1.0.8" } serde = { version = "1.0.192", default-features = false, features = ["derive"] } postcard-schema = { version = "0.1.0", features = ["derive"] } -paste = "1.0.15" # # std-only features @@ -50,7 +49,7 @@ optional = true [dependencies.tokio] version = "1.33.0" -features = ["sync", "rt", "macros", "io-util"] +features = ["sync", "rt", "macros", "io-util", "time"] optional = true [dependencies.tracing] @@ -128,9 +127,16 @@ optional = true version = "0.6" optional = true -[dependencies.futures-util] +[dependencies.embassy-futures] +version = "0.1" +optional = true + +[dependencies.embassy-time] version = "0.3" optional = true + +[dependencies.portable-atomic] +version = "1.0" default-features = false [dev-dependencies] @@ -146,7 +152,7 @@ features = ["std"] [features] -default = [] +default = ["embassy-usb-0_3-server"] test-utils = ["use-std", "postcard-schema/use-std"] use-std = [ "dep:maitake-sync", @@ -198,7 +204,8 @@ embassy-usb-0_3-server = [ "dep:static_cell", "dep:embassy-usb-driver", "dep:embassy-executor", - "dep:futures-util", + "dep:embassy-time", + "dep:embassy-futures", ] # NOTE: This exists because `embassy-usb` indirectly relies on ssmarshal diff --git a/source/postcard-rpc/src/host_client/mod.rs b/source/postcard-rpc/src/host_client/mod.rs index f3d435e..6bc79f7 100644 --- a/source/postcard-rpc/src/host_client/mod.rs +++ b/source/postcard-rpc/src/host_client/mod.rs @@ -3,7 +3,9 @@ //! This library is meant to be used with the `Dispatch` type and the //! postcard-rpc wire protocol. +use core::time::Duration; use std::{ + collections::HashSet, future::Future, marker::PhantomData, sync::{ @@ -16,8 +18,8 @@ use maitake_sync::{ wait_map::{WaitError, WakeOutcome}, WaitMap, }; -use postcard_schema::Schema; -use serde::{de::DeserializeOwned, Serialize}; +use postcard_schema::{schema::owned::OwnedNamedType, Schema}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use tokio::{ select, sync::{ @@ -29,7 +31,8 @@ use util::Subscriptions; use crate::{ header::{VarHeader, VarKey, VarKeyKind, VarSeq, VarSeqKind}, - Endpoint, Key, Topic, + standard_icd::{GetAllSchemaDataTopic, GetAllSchemasEndpoint, OwnedSchemaData}, + Endpoint, Key, Topic, TopicDirection, }; use self::util::Stopper; @@ -220,11 +223,105 @@ where } } +/// Errors related to retrieving the schema +#[derive(Debug)] +pub enum SchemaError { + /// Some kind of communication error occurred + Comms(HostErr), + /// An error occurred internally. Please open an issue. + TaskError, + /// Invalid report data was received, including endpoints or + /// tasks that referred to unknown types. Please open an issue + InvalidReportData, + /// Data was lost while transmitting. If a retry does not solve + /// this, please open an issue. + LostData, +} + +impl From for SchemaError { + fn from(_: UnableToFindType) -> Self { + Self::InvalidReportData + } +} + /// # Interface Methods impl HostClient where WireErr: DeserializeOwned + Schema, { + /// Obtain a [`SchemaReport`] describing the connected device + pub async fn get_schema_report(&self) -> Result> { + let Ok(mut sub) = self.subscribe::(64).await else { + return Err(SchemaError::Comms(HostErr::Closed)); + }; + + let collect_task = tokio::task::spawn({ + async move { + let mut got = vec![]; + while let Ok(Some(val)) = + tokio::time::timeout(Duration::from_millis(100), sub.recv()).await + { + got.push(val); + } + got + } + }); + let trigger_task = self.send_resp::(&()).await; + let data = collect_task.await; + let (resp, data) = match (trigger_task, data) { + (Ok(a), Ok(b)) => (a, b), + (Ok(_), Err(_)) => return Err(SchemaError::TaskError), + (Err(e), Ok(_)) => return Err(SchemaError::Comms(e)), + (Err(e1), Err(_e2)) => return Err(SchemaError::Comms(e1)), + }; + let mut rpt = SchemaReport::default(); + let mut e_and_t = vec![]; + + for d in data { + match d { + OwnedSchemaData::Type(d) => { + rpt.add_type(d); + } + e @ OwnedSchemaData::Endpoint { .. } => e_and_t.push(e), + t @ OwnedSchemaData::Topic { .. } => e_and_t.push(t), + } + } + + for e in e_and_t { + match e { + OwnedSchemaData::Type(_) => unreachable!(), + OwnedSchemaData::Endpoint { + path, + request_key, + response_key, + } => { + rpt.add_endpoint(path, request_key, response_key)?; + } + OwnedSchemaData::Topic { + path, + key, + direction, + } => match direction { + TopicDirection::ToServer => rpt.add_topic_in(path, key)?, + TopicDirection::ToClient => rpt.add_topic_out(path, key)?, + }, + } + } + + let mut data_matches = true; + data_matches &= resp.endpoints_sent as usize == rpt.endpoints.len(); + data_matches &= resp.topics_in_sent as usize == rpt.topics_in.len(); + data_matches &= resp.topics_out_sent as usize == rpt.topics_out.len(); + data_matches &= resp.errors == 0; + + if data_matches { + // TODO: filter primitive types out? + Ok(rpt) + } else { + Err(SchemaError::LostData) + } + } + /// Send a message of type [Endpoint::Request][Endpoint] to `path`, and await /// a response of type [Endpoint::Response][Endpoint] (or WireErr) to `path`. /// @@ -559,3 +656,192 @@ impl HostContext { } } } + +/// A report describing the schema spoken by the connected device +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Schema)] +pub struct SchemaReport { + /// All custom types spoken by the device (on any endpoint or topic), + /// as well as all primitive types. In the future, primitive types may + /// be removed. + pub types: HashSet, + /// All incoming (client to server) topics reported by the device + pub topics_in: Vec, + /// All outgoing (server to client) topics reported by the device + pub topics_out: Vec, + /// All endpoints reported by the device + pub endpoints: Vec, +} + +impl Default for SchemaReport { + fn default() -> Self { + let mut me = Self { + types: Default::default(), + topics_in: Default::default(), + topics_out: Default::default(), + endpoints: Default::default(), + }; + + // We need to pre-populate all of the types we consider primitives: + // DataModelType::Bool + me.add_type(OwnedNamedType::from(::SCHEMA)); + // DataModelType::I8 + me.add_type(OwnedNamedType::from(::SCHEMA)); + // DataModelType::U8 + me.add_type(OwnedNamedType::from(::SCHEMA)); + // DataModelType::I16 + me.add_type(OwnedNamedType::from(::SCHEMA)); + // DataModelType::I32 + me.add_type(OwnedNamedType::from(::SCHEMA)); + // DataModelType::I64 + me.add_type(OwnedNamedType::from(::SCHEMA)); + // DataModelType::I128 + me.add_type(OwnedNamedType::from(::SCHEMA)); + // DataModelType::U16 + me.add_type(OwnedNamedType::from(::SCHEMA)); + // DataModelType::U32 + me.add_type(OwnedNamedType::from(::SCHEMA)); + // DataModelType::U64 + me.add_type(OwnedNamedType::from(::SCHEMA)); + // DataModelType::U128 + me.add_type(OwnedNamedType::from(::SCHEMA)); + // // DataModelType::Usize + // me.add_type(OwnedNamedType::from(::SCHEMA)); + // // DataModelType::Isize + // me.add_type(OwnedNamedType::from(::SCHEMA)); + // DataModelType::F32 + me.add_type(OwnedNamedType::from(::SCHEMA)); + // DataModelType::F64 + me.add_type(OwnedNamedType::from(::SCHEMA)); + // DataModelType::Char + me.add_type(OwnedNamedType::from(::SCHEMA)); + // DataModelType::String + me.add_type(OwnedNamedType::from(::SCHEMA)); + // DataModelType::ByteArray + me.add_type(OwnedNamedType::from( as Schema>::SCHEMA)); + // DataModelType::Unit + me.add_type(OwnedNamedType::from(<() as Schema>::SCHEMA)); + // DataModelType::Schema + me.add_type(OwnedNamedType::from(::SCHEMA)); + + me + } +} + +/// A description of a single Topic +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Schema)] +pub struct TopicReport { + /// The human readable path of the topic + pub path: String, + /// The Key of the topic (which hashes the path and type) + pub key: Key, + /// The schema of the type of the message + pub ty: OwnedNamedType, +} + +/// A description of a single Endpoint +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Schema)] +pub struct EndpointReport { + /// The human readable path of the endpoint + pub path: String, + /// The Key of the request (which hashes the path and type) + pub req_key: Key, + /// The schema of the request type + pub req_ty: OwnedNamedType, + /// The Key of the response (which hashes the path and type) + pub resp_key: Key, + /// The schema of the response type + pub resp_ty: OwnedNamedType, +} + +/// An error that denotes we were unable to resolve the type used by a given key +#[derive(Debug)] +pub struct UnableToFindType; + +impl SchemaReport { + /// Insert a new type + pub fn add_type(&mut self, t: OwnedNamedType) { + self.types.insert(t); + } + + /// Insert a new incoming (client to server) topic + /// + /// Returns an error if we are unable to find the type used for this topic + pub fn add_topic_in(&mut self, path: String, key: Key) -> Result<(), UnableToFindType> { + // We need to figure out which type goes with this topic + for ty in self.types.iter() { + let calc_key = Key::for_owned_schema_path(&path, ty); + if calc_key == key { + self.topics_in.push(TopicReport { + path, + key, + ty: ty.clone(), + }); + return Ok(()); + } + } + Err(UnableToFindType) + } + + /// Insert a new outgoing (server to client) topic + /// + /// Returns an error if we are unable to find the type used for this topic + pub fn add_topic_out(&mut self, path: String, key: Key) -> Result<(), UnableToFindType> { + // We need to figure out which type goes with this topic + for ty in self.types.iter() { + let calc_key = Key::for_owned_schema_path(&path, ty); + if calc_key == key { + self.topics_out.push(TopicReport { + path, + key, + ty: ty.clone(), + }); + return Ok(()); + } + } + Err(UnableToFindType) + } + + /// Insert a new endpoint + /// + /// Returns an error if we are unable to find the type used for the request/response + pub fn add_endpoint( + &mut self, + path: String, + req_key: Key, + resp_key: Key, + ) -> Result<(), UnableToFindType> { + // We need to figure out which types go with this endpoint + let mut req_ty = None; + for ty in self.types.iter() { + let calc_key = Key::for_owned_schema_path(&path, ty); + if calc_key == req_key { + req_ty = Some(ty.clone()); + break; + } + } + let Some(req_ty) = req_ty else { + return Err(UnableToFindType); + }; + + let mut resp_ty = None; + for ty in self.types.iter() { + let calc_key = Key::for_owned_schema_path(&path, ty); + if calc_key == resp_key { + resp_ty = Some(ty.clone()); + break; + } + } + let Some(resp_ty) = resp_ty else { + return Err(UnableToFindType); + }; + + self.endpoints.push(EndpointReport { + path, + req_key, + req_ty, + resp_key, + resp_ty, + }); + Ok(()) + } +} diff --git a/source/postcard-rpc/src/host_client/raw_nusb.rs b/source/postcard-rpc/src/host_client/raw_nusb.rs index 55752f6..1c42dc0 100644 --- a/source/postcard-rpc/src/host_client/raw_nusb.rs +++ b/source/postcard-rpc/src/host_client/raw_nusb.rs @@ -290,8 +290,8 @@ impl NusbWireRx { if fatal { tracing::error!("Fatal Error, exiting"); - // TODO we should notify sub worker! - // ctxt.map.close(); + // When we close the channel, all pending receivers and subscribers + // will be notified return Err(e.into()); } else { tracing::info!("Potential recovery, resuming NusbWireRx::recv_inner"); @@ -299,12 +299,6 @@ impl NusbWireRx { } } - // TODO: Min size of a header is 9 bytes, 8 for key, 1 for seq_no. - if res.data.len() < 9 { - tracing::warn!("Header decode error!"); - continue; - }; - // If we get a good decode, clear the error flag if self.consecutive_errs != 0 { tracing::info!("Clearing consecutive error counter after good header decode"); diff --git a/source/postcard-rpc/src/lib.rs b/source/postcard-rpc/src/lib.rs index 34bde2b..acf1983 100644 --- a/source/postcard-rpc/src/lib.rs +++ b/source/postcard-rpc/src/lib.rs @@ -119,11 +119,15 @@ use header::{VarKey, VarKeyKind}; use postcard_schema::{schema::NamedType, Schema}; use serde::{Deserialize, Serialize}; -#[cfg(feature = "cobs")] -pub mod accumulator; - pub mod hash; pub mod header; +mod macros; +pub mod server; +pub mod standard_icd; +pub mod uniques; + +#[cfg(feature = "cobs")] +pub mod accumulator; #[cfg(feature = "use-std")] pub mod host_client; @@ -131,12 +135,6 @@ pub mod host_client; #[cfg(any(test, feature = "test-utils"))] pub mod test_utils; -mod macros; - -pub mod server; - -pub mod uniques; - /// The `Key` uniquely identifies what "kind" of message this is. /// /// In order to generate it, `postcard-rpc` takes two pieces of data: @@ -225,7 +223,7 @@ mod key_owned { /// /// * Key8 bytes (`[u8; 8]`): `[a, b, c, d, e, f, g, h]` /// * Key4 bytes (`u8`): `a ^ b ^ c ^ d ^ e ^ f ^ g ^ h` -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct Key1(u8); /// A compacted 2-byte key @@ -234,7 +232,7 @@ pub struct Key1(u8); /// /// * Key8 bytes (`[u8; 8]`): `[a, b, c, d, e, f, g, h]` /// * Key4 bytes (`[u8; 2]`): `[a ^ b ^ c ^ d, e ^ f ^ g ^ h]` -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct Key2([u8; 2]); /// A compacted 4-byte key @@ -243,7 +241,7 @@ pub struct Key2([u8; 2]); /// /// * Key8 bytes (`[u8; 8]`): `[a, b, c, d, e, f, g, h]` /// * Key4 bytes (`[u8; 4]`): `[a ^ b, c ^ d, e ^ f, g ^ h]` -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct Key4([u8; 4]); impl Key1 { @@ -431,8 +429,20 @@ pub trait Endpoint { const PATH: &'static str; /// The unique [Key] identifying the Request const REQ_KEY: Key; + /// The unique [Key4] identifying the Request + const REQ_KEY4: Key4 = Key4::from_key8(Self::REQ_KEY); + /// The unique [Key2] identifying the Request + const REQ_KEY2: Key2 = Key2::from_key8(Self::REQ_KEY); + /// The unique [Key1] identifying the Request + const REQ_KEY1: Key1 = Key1::from_key8(Self::REQ_KEY); /// The unique [Key] identifying the Response const RESP_KEY: Key; + /// The unique [Key4] identifying the Response + const RESP_KEY4: Key4 = Key4::from_key8(Self::RESP_KEY); + /// The unique [Key2] identifying the Response + const RESP_KEY2: Key2 = Key2::from_key8(Self::RESP_KEY); + /// The unique [Key1] identifying the Response + const RESP_KEY1: Key1 = Key1::from_key8(Self::RESP_KEY); } /// A marker trait denoting a single topic @@ -449,66 +459,21 @@ pub trait Topic { const PATH: &'static str; /// The unique [Key] identifying the Message const TOPIC_KEY: Key; + /// The unique [Key4] identifying the Message + const TOPIC_KEY4: Key4 = Key4::from_key8(Self::TOPIC_KEY); + /// The unique [Key2] identifying the Message + const TOPIC_KEY2: Key2 = Key2::from_key8(Self::TOPIC_KEY); + /// The unique [Key2] identifying the Message + const TOPIC_KEY1: Key1 = Key1::from_key8(Self::TOPIC_KEY); } -/// These are items you can use for your error path and error key. -/// -/// This is used by [`define_dispatch!()`] as well. -pub mod standard_icd { - use crate::Key; - use postcard_schema::Schema; - use serde::{Deserialize, Serialize}; - - /// The calculated Key for the type [`WireError`] and the path [`ERROR_PATH`] - pub const ERROR_KEY: Key = Key::for_path::(ERROR_PATH); - - /// The path string used for the error type - pub const ERROR_PATH: &str = "error"; - - /// The given frame was too long - #[derive(Serialize, Deserialize, Schema, Debug, PartialEq)] - pub struct FrameTooLong { - /// The length of the too-long frame - pub len: u32, - /// The maximum frame length supported - pub max: u32, - } - - /// The given frame was too short - #[derive(Serialize, Deserialize, Schema, Debug, PartialEq)] - pub struct FrameTooShort { - /// The length of the too-short frame - pub len: u32, - } - - /// A protocol error that is handled outside of the normal request type, usually - /// indicating a protocol-level error - #[derive(Serialize, Deserialize, Schema, Debug, PartialEq)] - pub enum WireError { - /// The frame exceeded the buffering capabilities of the server - FrameTooLong(FrameTooLong), - /// The frame was shorter than the minimum frame size and was rejected - FrameTooShort(FrameTooShort), - /// Deserialization of a message failed - DeserFailed, - /// Serialization of a message failed, usually due to a lack of space to - /// buffer the serialized form - SerFailed, - /// The key associated with this request was unknown - UnknownKey, - /// The server was unable to spawn the associated handler, typically due - /// to an exhaustion of resources - FailedToSpawn, - /// The provided key is below the minimum key size calculated to avoid hash - /// collisions, and was rejected to avoid potential misunderstanding - KeyTooSmall, - } - - #[cfg(not(feature = "use-std"))] - crate::topic!(Logging, [u8], "logs/formatted"); - - #[cfg(feature = "use-std")] - crate::topic!(Logging, Vec, "logs/formatted"); +/// The direction of topic messages +#[derive(Debug, PartialEq, Clone, Copy, Schema, Serialize, Deserialize)] +pub enum TopicDirection { + /// Topic messages sent TO the SERVER, FROM the CLIENT + ToServer, + /// Topic messages sent TO the CLIENT, FROM the SERVER + ToClient, } /// An overview of all topics (in and out) and endpoints @@ -551,6 +516,8 @@ pub struct EndpointMap { /// by path and key #[derive(Debug)] pub struct TopicMap { + /// The direction of these topic messages + pub direction: TopicDirection, /// The set of unique types used by all topics in this map pub types: &'static [&'static NamedType], /// The list of topics by path string and topic key diff --git a/source/postcard-rpc/src/macros.rs b/source/postcard-rpc/src/macros.rs index 2cd0753..abf6816 100644 --- a/source/postcard-rpc/src/macros.rs +++ b/source/postcard-rpc/src/macros.rs @@ -91,41 +91,107 @@ macro_rules! endpoint { /// ``` #[macro_export] macro_rules! endpoints { + (@ep_tys $([[$($meta:meta)?] $ep_name:ident])*) => { + $crate::endpoints!(@ep_tys omit_std=false; $([[$($meta)?] $ep_name])*) + }; + (@ep_tys omit_std=true; $([[$($meta:meta)?] $ep_name:ident])*) => { + const { + const LISTS: &[&[&'static postcard_schema::schema::NamedType]] = &[ + $( + $(#[$meta])? + $crate::unique_types!(<$ep_name as $crate::Endpoint>::Request), + $(#[$meta])? + $crate::unique_types!(<$ep_name as $crate::Endpoint>::Response), + )* + ]; + + const TTL_COUNT: usize = $crate::uniques::total_len(LISTS); + const BIG_RPT: ([Option<&'static postcard_schema::schema::NamedType>; TTL_COUNT], usize) = $crate::uniques::merge_nty_lists(LISTS); + const SMALL_RPT: [&'static postcard_schema::schema::NamedType; BIG_RPT.1] = $crate::uniques::cruncher(BIG_RPT.0.as_slice()); + SMALL_RPT.as_slice() + } + }; + (@ep_tys omit_std=false; $([[$($meta:meta)?] $ep_name:ident])*) => { + const { + const USER_TYS: &[&'static postcard_schema::schema::NamedType] = + $crate::endpoints!(@ep_tys omit_std=true; $([[$($meta)?] $ep_name])*); + const STD_TYS: &[&'static postcard_schema::schema::NamedType] + = $crate::standard_icd::STANDARD_ICD_ENDPOINTS.types; + + const BOTH: &[&[&'static postcard_schema::schema::NamedType]] = &[ + USER_TYS, STD_TYS, + ]; + const TTL_COUNT: usize = $crate::uniques::total_len(BOTH); + const BIG_RPT: ([Option<&'static postcard_schema::schema::NamedType>; TTL_COUNT], usize) = $crate::uniques::merge_nty_lists(BOTH); + const SMALL_RPT: [&'static postcard_schema::schema::NamedType; BIG_RPT.1] = $crate::uniques::cruncher(BIG_RPT.0.as_slice()); + SMALL_RPT.as_slice() + } + }; + (@ep_eps $([[$($meta:meta)?] $ep_name:ident])*) => { + $crate::endpoints!(@ep_eps omit_std=false; $([[$($meta)?] $ep_name])*) + }; + (@ep_eps omit_std=true; $([[$($meta:meta)?] $ep_name:ident])*) => { + &[ + $( + $(#[$meta])? + ( + <$ep_name as $crate::Endpoint>::PATH, + <$ep_name as $crate::Endpoint>::REQ_KEY, + <$ep_name as $crate::Endpoint>::RESP_KEY, + ), + )* + ] + }; + (@ep_eps omit_std=false; $([[$($meta:meta)?] $ep_name:ident])*) => { + const { + const USER_EPS: &[(&str, $crate::Key, $crate::Key)] = + $crate::endpoints!(@ep_eps omit_std=true; $([[$($meta)?] $ep_name])*); + const NULL_KEY: $crate::Key = unsafe { $crate::Key::from_bytes([0u8; 8]) }; + const STD_EPS: &[(&str, $crate::Key, $crate::Key)] = + $crate::standard_icd::STANDARD_ICD_ENDPOINTS.endpoints; + + $crate::concat_arrays! { + init = ("", NULL_KEY, NULL_KEY); + ty = (&str, $crate::Key, $crate::Key); + [STD_EPS, USER_EPS] + } + } + }; ( list = $list_name:ident; - | EndpointTy | RequestTy | ResponseTy | Path | - | $(-)* | $(-)* | $(-)* | $(-)* | - $( | $ep_name:ident | $req_ty:ty | $resp_ty:ty | $path_str:literal | )* + $(omit_std = $omit:tt;)? + | EndpointTy | RequestTy | ResponseTy | Path | $( Cfg |)? + | $(-)* | $(-)* | $(-)* | $(-)* | $($(-)* |)? + $( | $ep_name:ident | $req_ty:tt $(< $($req_lt:lifetime),+ >)? | $resp_ty:tt $(< $($resp_lt:lifetime),+ >)? | $path_str:literal | $($meta:meta)? $(|)? )* ) => { // struct definitions and trait impls $( - pub struct $ep_name; + /// Macro Generated Marker Type + $(#[$meta])? + pub struct $ep_name < $($($req_lt,)+)? $($($resp_lt,)+)? > { + $( + _plt_req: core::marker::PhantomData<($(& $req_lt (),)+)>, + )? + $( + _plt_resp: core::marker::PhantomData<($(& $resp_lt (),)+)>, + )? + _priv: core::marker::PhantomData<()>, + } - impl $crate::Endpoint for $ep_name { - type Request = $req_ty; - type Response = $resp_ty; + $(#[$meta])? + impl < $($($req_lt,)+)? $($($resp_lt,)+)? > $crate::Endpoint for $ep_name < $($($req_lt,)+)? $($($resp_lt,)+)? > { + type Request = $req_ty $(< $($req_lt,)+ >)?; + type Response = $resp_ty $(< $($resp_lt,)+ >)?; const PATH: &'static str = $path_str; const REQ_KEY: $crate::Key = $crate::Key::for_path::<$req_ty>($path_str); const RESP_KEY: $crate::Key = $crate::Key::for_path::<$resp_ty>($path_str); } )* + /// Macro Generated Endpoint Map pub const $list_name: $crate::EndpointMap = $crate::EndpointMap { - types: &[ - $( - <$ep_name as $crate::Endpoint>::Request::SCHEMA, - <$ep_name as $crate::Endpoint>::Response::SCHEMA, - )* - ], - endpoints: &[ - $( - ( - <$ep_name as $crate::Endpoint>::PATH, - <$ep_name as $crate::Endpoint>::REQ_KEY, - <$ep_name as $crate::Endpoint>::RESP_KEY, - ), - )* - ], + types: $crate::endpoints!(@ep_tys $(omit_std = $omit;)? $([[$($meta)?] $ep_name])*), + endpoints: $crate::endpoints!(@ep_eps $(omit_std = $omit;)? $([[$($meta)?] $ep_name])*), }; }; } @@ -182,7 +248,7 @@ macro_rules! topic { /// ```rust /// # use postcard_schema::Schema; /// # use serde::{Serialize, Deserialize}; -/// use postcard_rpc::topics; +/// use postcard_rpc::{topics, TopicDirection}; /// /// #[derive(Debug, Serialize, Deserialize, Schema)] /// pub struct Message1 { @@ -198,72 +264,174 @@ macro_rules! topic { /// /// topics!{ /// list = TOPIC_LIST_NAME; +/// direction = TopicDirection::ToServer; /// | TopicTy | MessageTy | Path | /// | ------- | --------- | ---- | /// | Topic1 | Message1 | "topics/one" | /// | Topic2 | Message2 | "topics/two" | /// } /// ``` + #[macro_export] macro_rules! topics { + (@tp_tys ( $dir:expr ) $([[$($meta:meta)?] $tp_name:ident])*) => { + $crate::topics!(@tp_tys ( $dir ) omit_std=false; $([[$($meta)?] $tp_name])*) + }; + (@tp_tys ( $dir:expr ) omit_std=true; $([[$($meta:meta)?] $tp_name:ident])*) => { + const { + const LISTS: &[&[&'static postcard_schema::schema::NamedType]] = &[ + $( + $(#[$meta])? + $crate::unique_types!(<$tp_name as $crate::Topic>::Message), + )* + ]; + + const TTL_COUNT: usize = $crate::uniques::total_len(LISTS); + const BIG_RPT: ([Option<&'static postcard_schema::schema::NamedType>; TTL_COUNT], usize) = $crate::uniques::merge_nty_lists(LISTS); + const SMALL_RPT: [&'static postcard_schema::schema::NamedType; BIG_RPT.1] = $crate::uniques::cruncher(BIG_RPT.0.as_slice()); + SMALL_RPT.as_slice() + } + }; + (@tp_tys ( $dir:expr ) omit_std=false; $([[$($meta:meta)?] $tp_name:ident])*) => { + const { + const USER_TYS: &[&'static postcard_schema::schema::NamedType] = + $crate::topics!(@tp_tys ( $dir ) omit_std=true; $([[$($meta)?] $tp_name])*); + const STD_TYS: &[&'static postcard_schema::schema::NamedType] = const { + match $dir { + $crate::TopicDirection::ToServer => $crate::standard_icd::STANDARD_ICD_TOPICS_IN.types, + $crate::TopicDirection::ToClient => $crate::standard_icd::STANDARD_ICD_TOPICS_OUT.types, + } + }; + + const BOTH: &[&[&'static postcard_schema::schema::NamedType]] = &[ + STD_TYS, USER_TYS, + ]; + const TTL_COUNT: usize = $crate::uniques::total_len(BOTH); + const BIG_RPT: ([Option<&'static postcard_schema::schema::NamedType>; TTL_COUNT], usize) = $crate::uniques::merge_nty_lists(BOTH); + const SMALL_RPT: [&'static postcard_schema::schema::NamedType; BIG_RPT.1] = $crate::uniques::cruncher(BIG_RPT.0.as_slice()); + SMALL_RPT.as_slice() + } + }; + (@tp_tps ( $dir:expr ) $([[$($meta:meta)?] $tp_name:ident])*) => { + $crate::topics!(@tp_tps ( $dir ) omit_std=false; $([[$($meta)?] $tp_name])*) + }; + (@tp_tps ( $dir:expr ) omit_std=true; $([[$($meta:meta)?] $tp_name:ident])*) => { + &[ + $( + $(#[$meta])? + ( + <$tp_name as $crate::Topic>::PATH, + <$tp_name as $crate::Topic>::TOPIC_KEY, + ), + )* + ] + }; + (@tp_tps ( $dir:expr ) omit_std=false; $([[$($meta:meta)?] $tp_name:ident])*) => { + const { + const USER_TPS: &[(&str, $crate::Key)] = + $crate::topics!(@tp_tps ( $dir ) omit_std=true; $([[$($meta)?] $tp_name])*); + const NULL_KEY: $crate::Key = unsafe { $crate::Key::from_bytes([0u8; 8]) }; + const STD_TPS: &[(&str, $crate::Key)] = const { + match $dir { + $crate::TopicDirection::ToServer => $crate::standard_icd::STANDARD_ICD_TOPICS_IN.topics, + $crate::TopicDirection::ToClient => $crate::standard_icd::STANDARD_ICD_TOPICS_OUT.topics, + } + }; + + $crate::concat_arrays! { + init = ("", NULL_KEY); + ty = (&str, $crate::Key); + [STD_TPS, USER_TPS] + } + } + }; ( - list = $list_name:ident; - | TopicTy | MessageTy | Path | - | $(-)* | $(-)* | $(-)* | - $( | $tp_name:ident | $msg_ty:ty | $path_str:literal | )* + list = $list_name:ident; + direction = $direction:expr; + $(omit_std = $omit:tt;)? + | TopicTy | MessageTy | Path | $( Cfg |)? + | $(-)* | $(-)* | $(-)* | $($(-)* |)? + $(| $tp_name:ident | $msg_ty:tt $(< $($msg_lt:lifetime),+ >)? | $path_str:literal | $($meta:meta)? $(|)?)* ) => { // struct definitions and trait impls $( /// $tp_name - A Topic definition type /// /// Generated by the `topics!()` macro - pub struct $tp_name; + $(#[$meta])? + pub struct $tp_name $(< $($msg_lt,)+ >)? { + $( + _plt: core::marker::PhantomData<($(& $msg_lt (),)+)>, + )? + _priv: core::marker::PhantomData<()>, + } - impl $crate::Topic for $tp_name { - type Message = $msg_ty; + $(#[$meta])? + impl $(< $($msg_lt),+ >)? $crate::Topic for $tp_name $(< $($msg_lt,)+ >)? { + type Message = $msg_ty $(< $($msg_lt,)+ >)?; const PATH: &'static str = $path_str; const TOPIC_KEY: $crate::Key = $crate::Key::for_path::<$msg_ty>($path_str); } )* + /// Macro Generated Topic Map pub const $list_name: $crate::TopicMap = $crate::TopicMap { - types: &[ - $( - <$tp_name as $crate::Topic>::Message::SCHEMA, - )* - ], - topics: &[ - $( - ( - <$tp_name as $crate::Topic>::PATH, - <$tp_name as $crate::Topic>::TOPIC_KEY, - ), - )* - ], + direction: $direction, + types: $crate::topics!(@tp_tys ( $direction ) $(omit_std = $omit;)? $([[$($meta)?] $tp_name])*), + topics: $crate::topics!(@tp_tps ( $direction ) $(omit_std = $omit;)? $([[$($meta)?] $tp_name])*), }; }; } -// TODO: bring this back when I sort out how to do formatting in the sender! -// This might require WireTx impls -// -// #[cfg(feature = "embassy-usb-0_3-server")] -// #[macro_export] -// macro_rules! sender_log { -// ($sender:ident, $($arg:tt)*) => { -// $sender.fmt_publish::<$crate::standard_icd::Logging>(format_args!($($arg)*)) -// }; -// ($sender:ident, $s:expr) => { -// $sender.str_publish::<$crate::standard_icd::Logging>($s) -// }; -// ($($arg:tt)*) => { -// compile_error!("You must pass the sender to `sender_log`!"); -// } -// } +/// A macro for turning `&[&[T]]` into `&[T]` +#[macro_export] +macro_rules! concat_arrays { + ( + init = $init:expr; + ty = $tyname:ty; + [$($arr:ident),+] + ) => { + const { + const SLI: &[&[$tyname]] = &[ + $($arr,)+ + ]; + const LEN: usize = $crate::uniques::total_len(SLI); + const ARR: [$tyname; LEN] = $crate::uniques::combine_with_copy(SLI, $init); + + ARR.as_slice() + } + }; +} + +#[cfg(test)] +mod concat_test { + #[test] + fn concats() { + const A: &[u32] = &[1, 2, 3]; + const B: &[u32] = &[4, 5, 6]; + const BOTH: &[u32] = concat_arrays!( + init = 0xFFFF_FFFF; + ty = u32; + [A, B] + ); + assert_eq!(BOTH, [1, 2, 3, 4, 5, 6]); + } +} + +/// A helper function for logging with the [Sender][crate::server::Sender] +#[macro_export] +macro_rules! sender_fmt { + ($sender:ident, $($arg:tt)*) => { + $sender.log_fmt(format_args!($($arg)*)) + }; + ($($arg:tt)*) => { + compile_error!("You must pass the sender to `sender_log`!"); + } +} #[cfg(test)] mod endpoints_test { - use postcard_schema::Schema; + use postcard_schema::{schema::owned::OwnedNamedType, Schema}; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Schema)] @@ -284,22 +452,51 @@ mod endpoints_test { topics! { list = TOPICS_IN_LIST; + direction = crate::TopicDirection::ToServer; + | TopicTy | MessageTy | Path | + | ---------- | --------- | ---- | + | BetaTopic1 | BTopic | "test/in/beta1" | + | BetaTopic2 | BTopic | "test/in/beta2" | + | BetaTopic3 | BTopic | "test/in/beta3" | + } + + topics! { + list = TOPICS_OUT_LIST; + direction = crate::TopicDirection::ToClient; | TopicTy | MessageTy | Path | | ---------- | --------- | ---- | - | BetaTopic1 | BTopic | "test/beta1" | - | BetaTopic2 | BTopic | "test/beta2" | - | BetaTopic3 | BTopic | "test/beta3" | + | BetaTopic4 | BTopic | "test/out/beta1" | } #[test] fn eps() { - assert_eq!(ENDPOINT_LIST.types.len(), 6); - assert_eq!(ENDPOINT_LIST.endpoints.len(), 3); + for ep in ENDPOINT_LIST.types { + println!("{}", OwnedNamedType::from(*ep)); + } + assert_eq!(ENDPOINT_LIST.types.len(), 3); + for ep in ENDPOINT_LIST.endpoints { + println!("{}", ep.0); + } + assert_eq!(ENDPOINT_LIST.endpoints.len(), 5); } #[test] fn tps() { - assert_eq!(TOPICS_IN_LIST.types.len(), 3); + for tp in TOPICS_IN_LIST.types { + println!("TY IN: {}", OwnedNamedType::from(*tp)); + } + for tp in TOPICS_IN_LIST.topics { + println!("TP IN: {}", tp.0); + } + for tp in TOPICS_OUT_LIST.types { + println!("TY OUT: {}", OwnedNamedType::from(*tp)); + } + for tp in TOPICS_OUT_LIST.topics { + println!("TP OUT: {}", tp.0); + } + assert_eq!(TOPICS_IN_LIST.types.len(), 1); assert_eq!(TOPICS_IN_LIST.topics.len(), 3); + assert_eq!(TOPICS_OUT_LIST.types.len(), 6); + assert_eq!(TOPICS_OUT_LIST.topics.len(), 3); } } diff --git a/source/postcard-rpc/src/server/dispatch_macro.rs b/source/postcard-rpc/src/server/dispatch_macro.rs index f1b7532..0e6983e 100644 --- a/source/postcard-rpc/src/server/dispatch_macro.rs +++ b/source/postcard-rpc/src/server/dispatch_macro.rs @@ -1,8 +1,3 @@ -#[doc(hidden)] -pub mod export { - pub use paste::paste; -} - /// Define Dispatch Macro /// /// # Example @@ -120,12 +115,15 @@ macro_rules! define_dispatch { } }; + + ////////////////////////////////////////////////////////////////////////////// // Implementation of the dispatch trait for the app, where the Key length // is N, where N is 1, 2, 4, or 8 ////////////////////////////////////////////////////////////////////////////// (@matcher $n:literal $app_name:ident $tx_impl:ty; $spawn_fn:ident $key_ty:ty; $key_kind:expr; + $req_key_name:ident / $topic_key_name:ident = $bytes_ty:ty; ($($endpoint:ty | $ep_flavor:tt | $ep_handler:ident)*) ($($topic_in:ty | $tp_flavor:tt | $tp_handler:ident)*) ) => { @@ -148,11 +146,23 @@ macro_rules! define_dispatch { let err = $crate::standard_icd::WireError::KeyTooSmall; return tx.error(hdr.seq_no, err).await; }; - let keyb = keyb.to_bytes(); - use consts::*; match keyb { + // Standard ICD endpoints + <$crate::standard_icd::PingEndpoint as $crate::Endpoint>::$req_key_name => { + // Can we deserialize the request? + let Ok(req) = postcard::from_bytes::<<$crate::standard_icd::PingEndpoint as $crate::Endpoint>::Request>(body) else { + let err = $crate::standard_icd::WireError::DeserFailed; + return tx.error(hdr.seq_no, err).await; + }; + + tx.reply::<$crate::standard_icd::PingEndpoint>(hdr.seq_no, &req).await + }, + <$crate::standard_icd::GetAllSchemasEndpoint as $crate::Endpoint>::$req_key_name => { + tx.send_all_schemas(hdr, self.device_map).await + } + // end $( - $crate::server::dispatch_macro::export::paste! { [<$endpoint:upper _KEY $n>] } => { + <$endpoint as $crate::Endpoint>::$req_key_name => { // Can we deserialize the request? let Ok(req) = postcard::from_bytes::<<$endpoint as $crate::Endpoint>::Request>(body) else { let err = $crate::standard_icd::WireError::DeserFailed; @@ -173,7 +183,7 @@ macro_rules! define_dispatch { } )* $( - $crate::server::dispatch_macro::export::paste! { [<$topic_in:upper _KEY $n>] } => { + <$topic_in as $crate::Topic>::$topic_key_name => { // Can we deserialize the request? let Ok(msg) = postcard::from_bytes::<<$topic_in as $crate::Topic>::Message>(body) else { // This is a topic, not much to be done @@ -189,7 +199,6 @@ macro_rules! define_dispatch { #[allow(unused)] let spawninfo = &dispatch.spawn; - // (@tp_arm async $handler:ident $context:ident $header:ident $req:ident $outputter:ident) $crate::define_dispatch!(@tp_arm $tp_flavor $tp_handler context hdr msg tx ($spawn_fn) spawninfo); Ok(()) } @@ -365,53 +374,6 @@ macro_rules! define_dispatch { }; } - - // Here, we calculate at const time the keys we need to match against. This is done with - // paste, which is unfortunate, but allows us to match on this correctly later. - mod consts { - use super::*; - $( - $crate::server::dispatch_macro::export::paste! { - pub const [<$endpoint:upper _KEY1>]: u8 = $crate::Key1::from_key8(<$endpoint as $crate::Endpoint>::REQ_KEY).to_bytes(); - } - )* - $( - $crate::server::dispatch_macro::export::paste! { - pub const [<$topic_in:upper _KEY1>]: u8 = $crate::Key1::from_key8(<$topic_in as $crate::Topic>::TOPIC_KEY).to_bytes(); - } - )* - $( - $crate::server::dispatch_macro::export::paste! { - pub const [<$endpoint:upper _KEY2>]: [u8; 2] = $crate::Key2::from_key8(<$endpoint as $crate::Endpoint>::REQ_KEY).to_bytes(); - } - )* - $( - $crate::server::dispatch_macro::export::paste! { - pub const [<$topic_in:upper _KEY2>]: [u8; 2] = $crate::Key2::from_key8(<$topic_in as $crate::Topic>::TOPIC_KEY).to_bytes(); - } - )* - $( - $crate::server::dispatch_macro::export::paste! { - pub const [<$endpoint:upper _KEY4>]: [u8; 4] = $crate::Key4::from_key8(<$endpoint as $crate::Endpoint>::REQ_KEY).to_bytes(); - } - )* - $( - $crate::server::dispatch_macro::export::paste! { - pub const [<$topic_in:upper _KEY4>]: [u8; 4] = $crate::Key4::from_key8(<$topic_in as $crate::Topic>::TOPIC_KEY).to_bytes(); - } - )* - $( - $crate::server::dispatch_macro::export::paste! { - pub const [<$endpoint:upper _KEY8>]: [u8; 8] = <$endpoint as $crate::Endpoint>::REQ_KEY.to_bytes(); - } - )* - $( - $crate::server::dispatch_macro::export::paste! { - pub const [<$topic_in:upper _KEY8>]: [u8; 8] = <$topic_in as $crate::Topic>::TOPIC_KEY.to_bytes(); - } - )* - } - // This is the fun part. // // For... reasons, we need to generate a match function to allow for dispatching @@ -427,8 +389,6 @@ macro_rules! define_dispatch { // same outcome. pub type $app_name = impls::$app_name<{ sizer::NEEDED_SZ }>; - - mod impls { use super::*; @@ -480,21 +440,25 @@ macro_rules! define_dispatch { $crate::define_dispatch! { @matcher 1 $app_name $tx_impl; $spawn_fn $crate::Key1; $crate::header::VarKeyKind::Key1; + REQ_KEY1 / TOPIC_KEY1 = u8; ($($endpoint | $ep_flavor | $ep_handler)*) ($($topic_in | $tp_flavor | $tp_handler)*) } $crate::define_dispatch! { @matcher 2 $app_name $tx_impl; $spawn_fn $crate::Key2; $crate::header::VarKeyKind::Key2; + REQ_KEY2 / TOPIC_KEY2 = [u8; 2]; ($($endpoint | $ep_flavor | $ep_handler)*) ($($topic_in | $tp_flavor | $tp_handler)*) } $crate::define_dispatch! { @matcher 4 $app_name $tx_impl; $spawn_fn $crate::Key4; $crate::header::VarKeyKind::Key4; + REQ_KEY4 / TOPIC_KEY4 = [u8; 4]; ($($endpoint | $ep_flavor | $ep_handler)*) ($($topic_in | $tp_flavor | $tp_handler)*) } $crate::define_dispatch! { @matcher 8 $app_name $tx_impl; $spawn_fn $crate::Key; $crate::header::VarKeyKind::Key8; + REQ_KEY / TOPIC_KEY = [u8; 8]; ($($endpoint | $ep_flavor | $ep_handler)*) ($($topic_in | $tp_flavor | $tp_handler)*) } diff --git a/source/postcard-rpc/src/server/impls/embassy_usb_v0_3.rs b/source/postcard-rpc/src/server/impls/embassy_usb_v0_3.rs index 3135aed..d62abb2 100644 --- a/source/postcard-rpc/src/server/impls/embassy_usb_v0_3.rs +++ b/source/postcard-rpc/src/server/impls/embassy_usb_v0_3.rs @@ -1,14 +1,18 @@ //! Implementation using `embassy-usb` and bulk interfaces +use core::fmt::Arguments; use embassy_executor::{SpawnError, SpawnToken, Spawner}; +use embassy_futures::select::{select, Either}; use embassy_sync::{blocking_mutex::raw::RawMutex, mutex::Mutex}; -use embassy_usb_driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; -use futures_util::FutureExt; +use embassy_time::Timer; +use embassy_usb_driver::{Driver, EndpointError, EndpointIn, EndpointOut}; use serde::Serialize; use crate::{ - header::VarHeader, + header::{VarHeader, VarKey, VarKeyKind, VarSeq}, server::{WireRx, WireRxErrorKind, WireSpawn, WireTx, WireTxErrorKind}, + standard_icd::LoggingTopic, + Topic, }; /// A collection of types and aliases useful for importing the correct types @@ -112,9 +116,9 @@ pub mod dispatch_impl { let wtx = self.cell.init(Mutex::new(EUsbWireTxInner { ep_in, - _log_seq: 0, + log_seq: 0, tx_buf, - _max_log_len: 0, + pending_frame: false, })); // Build the builder. @@ -132,9 +136,9 @@ pub mod dispatch_impl { /// Implementation detail, holding the endpoint and scratch buffer used for sending pub struct EUsbWireTxInner> { ep_in: D::EndpointIn, - _log_seq: u32, + log_seq: u16, tx_buf: &'static mut [u8], - _max_log_len: usize, + pending_frame: bool, } /// A [`WireTx`] implementation for embassy-usb 0.3. @@ -161,9 +165,9 @@ impl + 'static> WireTx for EUsbWireTx< let EUsbWireTxInner { ep_in, - _log_seq: _, + log_seq: _, tx_buf, - _max_log_len: _, + pending_frame, }: &mut EUsbWireTxInner = &mut inner; let (hdr_used, remain) = hdr.write_to_slice(tx_buf).ok_or(WireTxErrorKind::Other)?; @@ -171,7 +175,7 @@ impl + 'static> WireTx for EUsbWireTx< let used_ttl = hdr_used.len() + bdy_used.len(); if let Some(used) = tx_buf.get(..used_ttl) { - send_all::(ep_in, used).await + send_all::(ep_in, used, pending_frame).await } else { Err(WireTxErrorKind::Other) } @@ -179,38 +183,252 @@ impl + 'static> WireTx for EUsbWireTx< async fn send_raw(&self, buf: &[u8]) -> Result<(), Self::Error> { let mut inner = self.inner.lock().await; - send_all::(&mut inner.ep_in, buf).await + let EUsbWireTxInner { + ep_in, + pending_frame, + .. + }: &mut EUsbWireTxInner = &mut inner; + send_all::(ep_in, buf, pending_frame).await + } + + async fn send_log_str(&self, kkind: VarKeyKind, s: &str) -> Result<(), Self::Error> { + let mut inner = self.inner.lock().await; + + let EUsbWireTxInner { + ep_in, + log_seq, + tx_buf, + pending_frame, + }: &mut EUsbWireTxInner = &mut inner; + + let key = match kkind { + VarKeyKind::Key1 => VarKey::Key1(LoggingTopic::TOPIC_KEY1), + VarKeyKind::Key2 => VarKey::Key2(LoggingTopic::TOPIC_KEY2), + VarKeyKind::Key4 => VarKey::Key4(LoggingTopic::TOPIC_KEY4), + VarKeyKind::Key8 => VarKey::Key8(LoggingTopic::TOPIC_KEY), + }; + let ctr = *log_seq; + *log_seq = log_seq.wrapping_add(1); + let wh = VarHeader { + key, + seq_no: VarSeq::Seq2(ctr), + }; + + let (hdr_used, remain) = wh.write_to_slice(tx_buf).ok_or(WireTxErrorKind::Other)?; + let bdy_used = postcard::to_slice::(s, remain).map_err(|_| WireTxErrorKind::Other)?; + let used_ttl = hdr_used.len() + bdy_used.len(); + + if let Some(used) = tx_buf.get(..used_ttl) { + send_all::(ep_in, used, pending_frame).await + } else { + Err(WireTxErrorKind::Other) + } + } + + async fn send_log_fmt<'a>( + &self, + kkind: VarKeyKind, + args: Arguments<'a>, + ) -> Result<(), Self::Error> { + let mut inner = self.inner.lock().await; + + let EUsbWireTxInner { + ep_in, + log_seq, + tx_buf, + pending_frame, + }: &mut EUsbWireTxInner = &mut inner; + let ttl_len = tx_buf.len(); + + let key = match kkind { + VarKeyKind::Key1 => VarKey::Key1(LoggingTopic::TOPIC_KEY1), + VarKeyKind::Key2 => VarKey::Key2(LoggingTopic::TOPIC_KEY2), + VarKeyKind::Key4 => VarKey::Key4(LoggingTopic::TOPIC_KEY4), + VarKeyKind::Key8 => VarKey::Key8(LoggingTopic::TOPIC_KEY), + }; + let ctr = *log_seq; + *log_seq = log_seq.wrapping_add(1); + let wh = VarHeader { + key, + seq_no: VarSeq::Seq2(ctr), + }; + let Some((_hdr, remaining)) = wh.write_to_slice(tx_buf) else { + return Err(WireTxErrorKind::Other); + }; + let max_log_len = actual_varint_max_len(remaining.len()); + + // Then, reserve space for non-canonical length fields + // We also set all but the last bytes to be "continuation" + // bytes + if remaining.len() < max_log_len { + return Err(WireTxErrorKind::Other); + } + + let (len_field, body) = remaining.split_at_mut(max_log_len); + for b in len_field.iter_mut() { + *b = 0x80; + } + if let Some(b) = len_field.last_mut() { + *b = 0x00; + } + + // Then, do the formatting + let body_len = body.len(); + let mut sw = SliceWriter(body); + let res = core::fmt::write(&mut sw, args); + + // Calculate the number of bytes used *for formatting*. + let remain = sw.0.len(); + let used = body_len - remain; + + // If we had an error, that's probably because we ran out + // of room. If we had an error, AND there is at least three + // bytes, then replace those with '.'s like ... + if res.is_err() && (body.len() >= 3) { + let start = body.len() - 3; + body[start..].iter_mut().for_each(|b| *b = b'.'); + } + + // then go back and fill in the len - we write the len + // directly to the reserved bytes, and if we DIDN'T use + // the full space, we mark the end of the real length as + // a continuation field. This will result in a non-canonical + // "extended" length in postcard, and will "spill into" the + // bytes we wrote previously above + let mut len_bytes = [0u8; varint_max::()]; + let len_used = varint_usize(used, &mut len_bytes); + if len_used.len() != len_field.len() { + if let Some(b) = len_used.last_mut() { + *b |= 0x80; + } + } + len_field[..len_used.len()].copy_from_slice(len_used); + + // Calculate the TOTAL amount + let act_used = ttl_len - remain; + + send_all::(ep_in, &tx_buf[..act_used], pending_frame).await } } #[inline] -async fn send_all(ep_in: &mut D::EndpointIn, out: &[u8]) -> Result<(), WireTxErrorKind> +async fn send_all( + ep_in: &mut D::EndpointIn, + out: &[u8], + pending_frame: &mut bool, +) -> Result<(), WireTxErrorKind> where D: Driver<'static>, { if out.is_empty() { return Ok(()); } - // TODO: Timeout? - if ep_in.wait_enabled().now_or_never().is_none() { - return Ok(()); - } - // write in segments of 64. The last chunk may - // be 0 < len <= 64. - for ch in out.chunks(64) { - if ep_in.write(ch).await.is_err() { + // Calculate an estimated timeout based on the number of frames we need to send + // For now, we use 2ms/frame + let frames = out.len() / 64; + let timeout_ms = frames * 2; + + let send_fut = async { + // If we left off a pending frame, send one now so we don't leave an unterminated + // message + if *pending_frame && ep_in.write(&[]).await.is_err() { + return Err(WireTxErrorKind::ConnectionClosed); + } + *pending_frame = true; + + // write in segments of 64. The last chunk may + // be 0 < len <= 64. + for ch in out.chunks(64) { + if ep_in.write(ch).await.is_err() { + return Err(WireTxErrorKind::ConnectionClosed); + } + } + // If the total we sent was a multiple of 64, send an + // empty message to "flush" the transaction. We already checked + // above that the len != 0. + if (out.len() & (64 - 1)) == 0 && ep_in.write(&[]).await.is_err() { return Err(WireTxErrorKind::ConnectionClosed); } + + *pending_frame = false; + Ok(()) + }; + + match select(send_fut, Timer::after_millis(timeout_ms as u64)).await { + Either::First(res) => res, + Either::Second(()) => Err(WireTxErrorKind::Timeout), } - // If the total we sent was a multiple of 64, send an - // empty message to "flush" the transaction. We already checked - // above that the len != 0. - if (out.len() & (64 - 1)) == 0 && ep_in.write(&[]).await.is_err() { - return Err(WireTxErrorKind::ConnectionClosed); +} + +struct SliceWriter<'a>(&'a mut [u8]); + +impl<'a> core::fmt::Write for SliceWriter<'a> { + fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { + let sli = core::mem::take(&mut self.0); + + // If this write would overflow us, note that, but still take + // as much as we possibly can here + let bad = s.len() > sli.len(); + let to_write = s.len().min(sli.len()); + let (now, later) = sli.split_at_mut(to_write); + now.copy_from_slice(s.as_bytes()); + self.0 = later; + + // Now, report whether we overflowed or not + if bad { + Err(core::fmt::Error) + } else { + Ok(()) + } } +} - Ok(()) +/// Returns the maximum number of bytes required to encode T. +const fn varint_max() -> usize { + const BITS_PER_BYTE: usize = 8; + const BITS_PER_VARINT_BYTE: usize = 7; + + // How many data bits do we need for this type? + let bits = core::mem::size_of::() * BITS_PER_BYTE; + + // We add (BITS_PER_VARINT_BYTE - 1), to ensure any integer divisions + // with a remainder will always add exactly one full byte, but + // an evenly divided number of bits will be the same + let roundup_bits = bits + (BITS_PER_VARINT_BYTE - 1); + + // Apply division, using normal "round down" integer division + roundup_bits / BITS_PER_VARINT_BYTE +} + +#[inline] +fn varint_usize(n: usize, out: &mut [u8; varint_max::()]) -> &mut [u8] { + let mut value = n; + for i in 0..varint_max::() { + out[i] = value.to_le_bytes()[0]; + if value < 128 { + return &mut out[..=i]; + } + + out[i] |= 0x80; + value >>= 7; + } + debug_assert_eq!(value, 0); + &mut out[..] +} + +fn actual_varint_max_len(largest: usize) -> usize { + if largest < (2 << 7) { + 1 + } else if largest < (2 << 14) { + 2 + } else if largest < (2 << 21) { + 3 + } else if largest < (2 << 28) { + 4 + } else { + varint_max::() + } } ////////////////////////////////////////////////////////////////////////////// @@ -404,6 +622,7 @@ pub mod fake { topics! { list = TOPICS_IN_LIST; + direction = crate::TopicDirection::ToServer; | TopicTy | MessageTy | Path | | ---------- | --------- | ---- | | ZetaTopic1 | ZMsg | "zeta1" | @@ -413,6 +632,7 @@ pub mod fake { topics! { list = TOPICS_OUT_LIST; + direction = crate::TopicDirection::ToClient; | TopicTy | MessageTy | Path | | ---------- | --------- | ---- | | ZetaTopic10 | ZMsg | "zeta10" | diff --git a/source/postcard-rpc/src/server/impls/test_channels.rs b/source/postcard-rpc/src/server/impls/test_channels.rs index db4aab1..564a442 100644 --- a/source/postcard-rpc/src/server/impls/test_channels.rs +++ b/source/postcard-rpc/src/server/impls/test_channels.rs @@ -1,11 +1,22 @@ //! Implementation that uses channels for local testing -use core::{convert::Infallible, future::Future}; +use core::{ + convert::Infallible, + future::Future, + sync::atomic::{AtomicU32, Ordering}, +}; +use std::sync::Arc; -use crate::server::{ - AsWireRxErrorKind, AsWireTxErrorKind, WireRx, WireRxErrorKind, WireSpawn, WireTx, - WireTxErrorKind, +use crate::{ + header::{VarHeader, VarKey, VarKeyKind, VarSeq}, + server::{ + AsWireRxErrorKind, AsWireTxErrorKind, WireRx, WireRxErrorKind, WireSpawn, WireTx, + WireTxErrorKind, + }, + standard_icd::LoggingTopic, + Topic, }; +use core::fmt::Arguments; use tokio::sync::mpsc; ////////////////////////////////////////////////////////////////////////////// @@ -69,12 +80,16 @@ pub mod dispatch_impl { #[derive(Clone)] pub struct ChannelWireTx { tx: mpsc::Sender>, + log_ctr: Arc, } impl ChannelWireTx { /// Create a new [`ChannelWireTx`] pub fn new(tx: mpsc::Sender>) -> Self { - Self { tx } + Self { + tx, + log_ctr: Arc::new(AtomicU32::new(0)), + } } } @@ -104,6 +119,51 @@ impl WireTx for ChannelWireTx { .map_err(|_| ChannelWireTxError::ChannelClosed)?; Ok(()) } + + async fn send_log_str(&self, kkind: VarKeyKind, s: &str) -> Result<(), Self::Error> { + let ctr = self.log_ctr.fetch_add(1, Ordering::Relaxed); + let key = match kkind { + VarKeyKind::Key1 => VarKey::Key1(LoggingTopic::TOPIC_KEY1), + VarKeyKind::Key2 => VarKey::Key2(LoggingTopic::TOPIC_KEY2), + VarKeyKind::Key4 => VarKey::Key4(LoggingTopic::TOPIC_KEY4), + VarKeyKind::Key8 => VarKey::Key8(LoggingTopic::TOPIC_KEY), + }; + let wh = VarHeader { + key, + seq_no: VarSeq::Seq4(ctr), + }; + let msg = s.to_string(); + + self.send::<::Message>(wh, &msg) + .await + } + + async fn send_log_fmt<'a>( + &self, + kkind: VarKeyKind, + a: Arguments<'a>, + ) -> Result<(), Self::Error> { + let ctr = self.log_ctr.fetch_add(1, Ordering::Relaxed); + let key = match kkind { + VarKeyKind::Key1 => VarKey::Key1(LoggingTopic::TOPIC_KEY1), + VarKeyKind::Key2 => VarKey::Key2(LoggingTopic::TOPIC_KEY2), + VarKeyKind::Key4 => VarKey::Key4(LoggingTopic::TOPIC_KEY4), + VarKeyKind::Key8 => VarKey::Key8(LoggingTopic::TOPIC_KEY), + }; + let wh = VarHeader { + key, + seq_no: VarSeq::Seq4(ctr), + }; + let mut buf = wh.write_to_vec(); + let msg = format!("{a}"); + let msg = postcard::to_stdvec(&msg).unwrap(); + buf.extend_from_slice(&msg); + self.tx + .send(buf) + .await + .map_err(|_| ChannelWireTxError::ChannelClosed)?; + Ok(()) + } } /// A wire tx error diff --git a/source/postcard-rpc/src/server/mod.rs b/source/postcard-rpc/src/server/mod.rs index eb34327..92a630d 100644 --- a/source/postcard-rpc/src/server/mod.rs +++ b/source/postcard-rpc/src/server/mod.rs @@ -27,14 +27,14 @@ pub mod dispatch_macro; pub mod impls; -use core::ops::DerefMut; +use core::{fmt::Arguments, ops::DerefMut}; use postcard_schema::Schema; use serde::Serialize; use crate::{ header::{VarHeader, VarKey, VarKeyKind, VarSeq}, - Key, + DeviceMap, Key, TopicDirection, }; ////////////////////////////////////////////////////////////////////////////// @@ -55,6 +55,20 @@ pub trait WireTx: Clone { /// Send a single frame to the client, without handling serialization async fn send_raw(&self, buf: &[u8]) -> Result<(), Self::Error>; + + /// Send a logging message on the [`LoggingTopic`][crate::standard_icd::LoggingTopic] + /// + /// This message is simpler as it does not do any formatting + async fn send_log_str(&self, kkind: VarKeyKind, s: &str) -> Result<(), Self::Error>; + + /// Send a logging message on the [`LoggingTopic`][crate::standard_icd::LoggingTopic] + /// + /// This version formats to the outgoing buffer + async fn send_log_fmt<'a>( + &self, + kkind: VarKeyKind, + a: Arguments<'a>, + ) -> Result<(), Self::Error>; } /// The base [`WireTx`] Error Kind @@ -67,6 +81,8 @@ pub enum WireTxErrorKind { ConnectionClosed, /// Other unspecified errors Other, + /// Timeout (WireTx impl specific) reached + Timeout, } /// A conversion trait to convert a user error into a base Kind type @@ -193,6 +209,7 @@ impl Sender { #[inline] pub async fn reply_keyed(&self, seq_no: VarSeq, key: Key, resp: &T) -> Result<(), Tx::Error> where + T: ?Sized, T: Serialize + Schema, { let mut key = VarKey::Key8(key); @@ -205,6 +222,7 @@ impl Sender { #[inline] pub async fn publish(&self, seq_no: VarSeq, msg: &T::Message) -> Result<(), Tx::Error> where + T: ?Sized, T: crate::Topic, T::Message: Serialize + Schema, { @@ -214,6 +232,18 @@ impl Sender { self.tx.send::(wh, msg).await } + /// Log a `str` directly to the [`LoggingTopic`][crate::standard_idc::LoggingTopic] + #[inline] + pub async fn log_str(&self, msg: &str) -> Result<(), Tx::Error> { + self.tx.send_log_str(self.kkind, msg).await + } + + /// Format a message to the [`LoggingTopic`][crate::standard_idc::LoggingTopic] + #[inline] + pub async fn log_fmt(&self, msg: Arguments<'_>) -> Result<(), Tx::Error> { + self.tx.send_log_fmt(self.kkind, msg).await + } + /// Send a single error message pub async fn error( &self, @@ -223,6 +253,106 @@ impl Sender { self.reply_keyed(seq_no, crate::standard_icd::ERROR_KEY, &error) .await } + + /// Implements the [`GetAllSchemasEndpoint`][crate::standard_icd::GetAllSchemasEndpoint] endpoint + pub async fn send_all_schemas( + &self, + hdr: &VarHeader, + device_map: &DeviceMap, + ) -> Result<(), Tx::Error> { + #[cfg(feature = "use-std")] + use crate::standard_icd::OwnedSchemaData as SchemaData; + #[cfg(not(feature = "use-std"))] + use crate::standard_icd::SchemaData; + use crate::standard_icd::{GetAllSchemaDataTopic, GetAllSchemasEndpoint, SchemaTotals}; + + let mut msg_ctr = 0; + let mut err_ctr = 0; + + // First, send all types + for ty in device_map.types { + let res = self + .publish::( + VarSeq::Seq2(msg_ctr), + &SchemaData::Type((*ty).into()), + ) + .await; + if res.is_err() { + err_ctr += 1; + }; + msg_ctr += 1; + } + + // Then all endpoints + for ep in device_map.endpoints { + let res = self + .publish::( + VarSeq::Seq2(msg_ctr), + &SchemaData::Endpoint { + path: ep.0.into(), + request_key: ep.1, + response_key: ep.2, + }, + ) + .await; + if res.is_err() { + err_ctr += 1; + } + + msg_ctr += 1; + } + + // Then output topics + for to in device_map.topics_out { + let res = self + .publish::( + VarSeq::Seq2(msg_ctr), + &SchemaData::Topic { + direction: TopicDirection::ToClient, + path: to.0.into(), + key: to.1, + }, + ) + .await; + if res.is_err() { + err_ctr += 1; + } + msg_ctr += 1; + } + + // Then input topics + for ti in device_map.topics_in { + let res = self + .publish::( + VarSeq::Seq2(msg_ctr), + &SchemaData::Topic { + direction: TopicDirection::ToServer, + path: ti.0.into(), + key: ti.1, + }, + ) + .await; + if res.is_err() { + err_ctr += 1; + } + msg_ctr += 1; + } + + // Finally, reply with the totals + self.reply::( + hdr.seq_no, + &SchemaTotals { + types_sent: device_map.types.len() as u32, + endpoints_sent: device_map.endpoints.len() as u32, + topics_in_sent: device_map.topics_in.len() as u32, + topics_out_sent: device_map.topics_out.len() as u32, + errors: err_ctr, + }, + ) + .await?; + + Ok(()) + } } ////////////////////////////////////////////////////////////////////////////// @@ -283,6 +413,11 @@ where } } + /// Get a copy of the [`Sender`] to pass to tasks that need it + pub fn sender(&self) -> Sender { + self.tx.clone() + } + /// Run until a fatal error occurs /// /// The server will receive frames, and dispatch them. When a fatal error occurs, @@ -320,6 +455,7 @@ where match kind { WireTxErrorKind::ConnectionClosed => return ServerError::TxFatal(e), WireTxErrorKind::Other => {} + WireTxErrorKind::Timeout => return ServerError::TxFatal(e), } } } diff --git a/source/postcard-rpc/src/standard_icd.rs b/source/postcard-rpc/src/standard_icd.rs new file mode 100644 index 0000000..dc63d31 --- /dev/null +++ b/source/postcard-rpc/src/standard_icd.rs @@ -0,0 +1,154 @@ +//! These are items you can use for your error path and error key. +//! +//! This is used by [`define_dispatch!()`] as well. + +use crate::{endpoints, topics, Key, TopicDirection}; +use postcard_schema::Schema; +use serde::{Deserialize, Serialize}; + +#[cfg(not(feature = "use-std"))] +use postcard_schema::schema::NamedType; + +#[cfg(feature = "use-std")] +use postcard_schema::schema::owned::OwnedNamedType; + +/// The calculated Key for the type [`WireError`] and the path [`ERROR_PATH`] +pub const ERROR_KEY: Key = Key::for_path::(ERROR_PATH); + +/// The path string used for the error type +pub const ERROR_PATH: &str = "error"; + +/// The given frame was too long +#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)] +pub struct FrameTooLong { + /// The length of the too-long frame + pub len: u32, + /// The maximum frame length supported + pub max: u32, +} + +/// The given frame was too short +#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)] +pub struct FrameTooShort { + /// The length of the too-short frame + pub len: u32, +} + +/// A protocol error that is handled outside of the normal request type, usually +/// indicating a protocol-level error +#[derive(Serialize, Deserialize, Schema, Debug, PartialEq)] +pub enum WireError { + /// The frame exceeded the buffering capabilities of the server + FrameTooLong(FrameTooLong), + /// The frame was shorter than the minimum frame size and was rejected + FrameTooShort(FrameTooShort), + /// Deserialization of a message failed + DeserFailed, + /// Serialization of a message failed, usually due to a lack of space to + /// buffer the serialized form + SerFailed, + /// The key associated with this request was unknown + UnknownKey, + /// The server was unable to spawn the associated handler, typically due + /// to an exhaustion of resources + FailedToSpawn, + /// The provided key is below the minimum key size calculated to avoid hash + /// collisions, and was rejected to avoid potential misunderstanding + KeyTooSmall, +} + +/// A single element of schema information +#[cfg(not(feature = "use-std"))] +#[derive(Serialize, Schema, Debug, PartialEq, Copy, Clone)] +pub enum SchemaData<'a> { + /// A single Type + Type(&'a NamedType), + /// A single Endpoint + Endpoint { + /// The path of the endpoint + path: &'a str, + /// The key of the Request type + path + request_key: Key, + /// The key of the Response type + path + response_key: Key, + }, + /// A single Topic + Topic { + /// The path of the topic + path: &'a str, + /// The key of the Message type + path + key: Key, + /// The direction of the Topic + direction: TopicDirection, + }, +} + +/// A single element of schema information +#[cfg(feature = "use-std")] +#[derive(Serialize, Deserialize, Schema, Debug, PartialEq, Clone)] +pub enum OwnedSchemaData { + /// A single Type + Type(OwnedNamedType), + /// A single Endpoint + Endpoint { + /// The path of the endpoint + path: String, + /// The key of the Request type + path + request_key: Key, + /// The key of the Response type + path + response_key: Key, + }, + /// A single Topic + Topic { + /// The path of the topic + path: String, + /// The key of the Message type + path + key: Key, + /// The direction of the Topic + direction: TopicDirection, + }, +} + +/// A summary of all messages sent when streaming schema data +#[derive(Serialize, Deserialize, Schema, Debug, PartialEq, Copy, Clone)] +pub struct SchemaTotals { + /// A count of the number of (Owned)SchemaData::Type messages sent + pub types_sent: u32, + /// A count of the number of (Owned)SchemaData::Endpoint messages sent + pub endpoints_sent: u32, + /// A count of the number of (Owned)SchemaData::Topic messages sent + pub topics_in_sent: u32, + /// A count of the number of (Owned)SchemaData::Topic messages sent + pub topics_out_sent: u32, + /// A count of the number of messages (any of the above) that failed to send + pub errors: u32, +} + +endpoints! { + list = STANDARD_ICD_ENDPOINTS; + omit_std = true; + | EndpointTy | RequestTy | ResponseTy | Path | + | ---------- | --------- | ---------- | ---- | + | PingEndpoint | u32 | u32 | "postcard-rpc/ping" | + | GetAllSchemasEndpoint | () | SchemaTotals | "postcard-rpc/schemas/get" | +} + +topics! { + list = STANDARD_ICD_TOPICS_OUT; + direction = crate::TopicDirection::ToClient; + omit_std = true; + | TopicTy | MessageTy | Path | Cfg | + | ------- | --------- | ---- | --- | + | GetAllSchemaDataTopic | SchemaData<'a> | "postcard-rpc/schema/data" | cfg(not(feature = "use-std")) | + | GetAllSchemaDataTopic | OwnedSchemaData | "postcard-rpc/schema/data" | cfg(feature = "use-std") | + | LoggingTopic | str | "postcard-rpc/logging" | cfg(not(feature = "use-std")) | + | LoggingTopic | String | "postcard-rpc/logging" | cfg(feature = "use-std") | +} + +topics! { + list = STANDARD_ICD_TOPICS_IN; + direction = crate::TopicDirection::ToServer; + omit_std = true; + | TopicTy | MessageTy | Path | Cfg | + | ------- | --------- | ---- | --- | +} diff --git a/source/postcard-rpc/src/uniques.rs b/source/postcard-rpc/src/uniques.rs index aa7f90f..b9f4c41 100644 --- a/source/postcard-rpc/src/uniques.rs +++ b/source/postcard-rpc/src/uniques.rs @@ -547,7 +547,7 @@ const fn type_chewer_dmt( // For each type in the variant... while i < nvars.len() { match nvars[i].ty { - DataModelVariant::UnitVariant => continue, + DataModelVariant::UnitVariant => {} DataModelVariant::NewtypeVariant(nt) => { let mut k = 0; let mut found = false; @@ -722,6 +722,37 @@ pub const fn merge_nty_lists( // STAGE 6-9 (macro op) ////////////////////////////////////////////////////////////////////////////// +/// Get the sum of the length of all arrays +pub const fn total_len(arrs: &[&[T]]) -> usize { + let mut i = 0; + let mut ct = 0; + while i < arrs.len() { + ct += arrs[i].len(); + i += 1; + } + ct +} + +/// , +pub const fn combine_with_copy(arrs: &[&[T]], init: T) -> [T; N] { + let mut out = [init; N]; + let mut outidx = 0; + let mut i = 0; + while i < arrs.len() { + let mut j = 0; + while j < arrs[i].len() { + out[outidx] = arrs[i][j]; + outidx += 1; + j += 1; + } + i += 1; + } + + assert!(outidx == N); + + out +} + /// `merge_unique_types` collects all unique, non-primitive types contained by /// the given comma separated types. It can be used with any types that implement /// the [`Schema`] trait, and returns a `&'static [&'static NamedType]`. @@ -734,15 +765,7 @@ macro_rules! merge_unique_types { $crate::unique_types!($t), )* ]; - const TTL_COUNT: usize = const { - let mut i = 0; - let mut ct = 0; - while i < LISTS.len() { - ct += LISTS[i].len(); - i += 1; - } - ct - }; + const TTL_COUNT: usize = $crate::uniques::total_len(LISTS); const BIG_RPT: ([Option<&'static postcard_schema::schema::NamedType>; TTL_COUNT], usize) = $crate::uniques::merge_nty_lists(LISTS); const SMALL_RPT: [&'static postcard_schema::schema::NamedType; BIG_RPT.1] = $crate::uniques::cruncher(BIG_RPT.0.as_slice()); SMALL_RPT.as_slice() @@ -792,6 +815,14 @@ mod test { e: Example2, } + #[derive(Schema)] + enum Example4 { + A, + B, + C, + D, + } + #[test] fn subpar_arrs() { const MAXARR: usize = unique_types_nty_upper(<[Example0; 32]>::SCHEMA); @@ -892,6 +923,17 @@ mod test { } } + println!(); + println!("Example4"); + let (arr3, used): ([Option<_>; MAX3], usize) = type_chewer_nty(Example4::SCHEMA); + println!("max: {MAX3} used: {used}"); + for a in arr3 { + match a { + Some(a) => println!("Some({})", OwnedNamedType::from(a)), + None => println!("None"), + } + } + println!(); let rpt0 = unique_types!(Example0); println!("{}", rpt0.len());