diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c4426f..40172fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +* The networks store of the wireless stacks is a type parameter, `S`, after `E` - `WifiMatterStack<'a, B, E, S>`, `ThreadMatterStack<'a, B, E, S>` - by default a `WirelessNetworks` with `MAX_WIRELESS_NETWORKS` networks; it lives in `WirelessBle`, lent to the Network Commissioning handler by `run` +* (Breaking) The `Network` trait loses `Networks`, `NETWORKS` and `init_networks`; `MatterStackInteractionModel` and `MatterStackInteractionModelState` lose their networks and network controller parameters * (Breaking) Ethernet diagnostics: `run_preex`, `PreexistingEthernet::new` and `EthernetTask::run` take an `EthDiag` (`&()` if there is nothing to report) * Thread and Wi-Fi diagnostics: forward the MAC/MLE counters and the other new optional attributes of the operational network controller +* `InterfaceEnabled`: the operational network controller decides whether the interface can be disabled (`NetCtl::set_enabled`); it can't during BLE commissioning ## [0.3.1] - 2026-09-20 * Raise MSRV to 1.89 diff --git a/src/eth.rs b/src/eth.rs index 9a766e6..9ce6133 100644 --- a/src/eth.rs +++ b/src/eth.rs @@ -5,9 +5,7 @@ use embassy_futures::select::select4; use rs_matter::crypto::Crypto; use rs_matter::dm::clusters::eth_diag::EthDiag; use rs_matter::dm::clusters::gen_diag::NetifDiag; -use rs_matter::dm::clusters::net_comm::{DummyNetworks, NetworkType}; use rs_matter::dm::endpoints::eth_net_handler; -use rs_matter::dm::networks::wireless::NoopWirelessNetCtl; use rs_matter::dm::networks::NetChangeNotif; use rs_matter::dm::{DataModel, Endpoint}; use rs_matter::error::Error; @@ -15,7 +13,7 @@ use rs_matter::pairing::DiscoveryCapabilities; use rs_matter::persist::{KvBlobStore, KvBlobStoreAccess}; use rs_matter::root_endpoint; use rs_matter::transport::network::NoNetwork; -use rs_matter::utils::init::{init, init_from_closure, Init}; +use rs_matter::utils::init::{init, Init}; use rs_matter::utils::select::Coalesce; use crate::mdns::Mdns; @@ -51,26 +49,12 @@ where where E: 'a; - // Ethernet does not manage network credentials, so use the no-op networks store. - type Networks = DummyNetworks; - - const NETWORKS: Self::Networks = DummyNetworks; - fn init() -> impl Init { init!(Self { embedding <- E::init(), }) } - fn init_networks() -> impl Init { - unsafe { - init_from_closure(|slot: *mut DummyNetworks| { - slot.write(DummyNetworks); - Ok(()) - }) - } - } - fn discovery_capabilities(&self) -> DiscoveryCapabilities { DiscoveryCapabilities::IP } @@ -208,17 +192,9 @@ where self.matter.factory_reset(&kv)?; - // Reset the events counter (and the - no-op for Ethernet - networks store) - // so we don't carry a stale watermark across a factory reset - // (Matter Core spec R1.5.1, §7.14.1.1). - self.im( - crypto, - handler, - &kv, - NoopWirelessNetCtl::new(NetworkType::Ethernet), - ) - .factory_reset() - .await + // Reset the events counter so we don't carry a stale watermark across a + // factory reset (Matter Core spec R1.5.1, §7.14.1.1). + self.im(crypto, handler, &kv).factory_reset().await } /// Run the startup sequence of the stack: re-hydrate the persisted state and @@ -414,14 +390,9 @@ where &self.handler, self.crypto.weak_rand()?, ); - // Ethernet does not manage networks, so use the inert wireless net-ctl; - // the engine's connection-manager branch then stays dormant. - let im = self.stack.im( - &self.crypto, - (&self.handler, combined), - &self.kv, - NoopWirelessNetCtl::new(NetworkType::Ethernet), - ); + let im = self + .stack + .im(&self.crypto, (&self.handler, combined), &self.kv); let mut net_task = pin_alloc!( self.stack.bump, diff --git a/src/lib.rs b/src/lib.rs index ea4cbc6..95aec9f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,9 +26,7 @@ use rs_matter::crypto::{Crypto, Rng}; use rs_matter::dm::clusters::basic_info::BasicInfoConfig; use rs_matter::dm::clusters::dev_att::DeviceAttestation; use rs_matter::dm::clusters::gen_diag::NetifDiag; -use rs_matter::dm::clusters::net_comm::{NetCtl, NetCtlStatus, Networks}; use rs_matter::dm::clusters::sw_diag::SwDiag; -use rs_matter::dm::clusters::wifi_diag::WirelessDiag; use rs_matter::dm::endpoints::RootHandler; use rs_matter::dm::networks::NetChangeNotif; use rs_matter::dm::{AttrChangeNotifier, AttrId, ClusterId, DataModel, EndptId}; @@ -226,26 +224,22 @@ cfg_if! { const MAX_BUSY_RESPONDERS: usize = 2; -pub type MatterStackInteractionModel<'a, C, H, K, RN, NC> = InteractionModel< +pub type MatterStackInteractionModel<'a, C, H, K> = InteractionModel< 'a, C, MatterBuffers, H, K, - RN, - NC, // The accessory role: inbound `ReportData` is disowned. (), MAX_SUBSCRIPTIONS, EVENTS_RINGBUF_SIZE, >; -/// The `InteractionModelState` specialization owned by `MatterStack`. -/// -/// It owns the subscriptions table, the events queue and the `rs-matter` -/// networks store as a single unit. The KV scratch buffer now lives in `Matter`. -pub type MatterStackInteractionModelState = - InteractionModelState; +/// The `InteractionModelState` specialization owned by `MatterStack`: the +/// subscriptions table and the events queue. +pub type MatterStackInteractionModelState = + InteractionModelState; /// The `MatterStack` struct is the main entry point for the Matter stack. /// @@ -256,13 +250,13 @@ where { matter: Matter<'a>, buffers: MatterBuffers, - /// The interaction-model state: subscriptions table, events queue, the - /// `rs-matter` networks store, and the KV scratch buffer, owned as one unit. - state: MatterStackInteractionModelState, + /// The interaction-model state: subscriptions table and events queue. + state: MatterStackInteractionModelState, bump: Bump, run_lock: IfMutex<()>, - /// Whether the Interaction Model state (events watermark, networks store, - /// persisted subscriptions) has already been re-hydrated from the KV store. + /// Whether the Interaction Model state (events watermark, persisted + /// subscriptions) and the cluster handlers (the networks store among them) + /// have already been re-hydrated from the KV store. /// /// `InteractionModel::startup` cannot be driven from `MatterStack::startup`, /// because it has to run on the very Interaction Model instance that is then @@ -315,7 +309,7 @@ where Self { matter: Matter::new(dev_det, dev_comm, dev_att, MATTER_PORT), buffers: MatterBuffers::new(), - state: MatterStackInteractionModelState::new(N::NETWORKS), + state: MatterStackInteractionModelState::new(), bump: Bump::new(), run_lock: IfMutex::new(()), im_hydrated: Mutex::new(Cell::new(false)), @@ -338,7 +332,7 @@ where MATTER_PORT, ), buffers <- MatterBuffers::init(), - state <- MatterStackInteractionModelState::init(N::init_networks()), + state <- MatterStackInteractionModelState::init(), bump <- Bump::init(), run_lock <- IfMutex::init(()), im_hydrated: Mutex::new(Cell::new(false)), @@ -672,40 +666,30 @@ where } #[inline(always)] - fn im( - &self, - crypto: C, - handler: H, - kv: K, - net_ctl: NC, - ) -> MatterStackInteractionModel<'_, C, H, K, N::Networks, NC> + fn im(&self, crypto: C, handler: H, kv: K) -> MatterStackInteractionModel<'_, C, H, K> where C: Crypto, H: DataModel, K: KvBlobStoreAccess, - NC: NetCtl + NetCtlStatus + WirelessDiag + NetChangeNotif, { - MatterStackInteractionModel::new_with_net_ctl( + MatterStackInteractionModel::new( self.matter(), crypto, &self.buffers, handler, kv, - net_ctl, &self.state, ) } - async fn run_im( + async fn run_im( &self, - im: &MatterStackInteractionModel<'_, C, H, K, RN, NC>, + im: &MatterStackInteractionModel<'_, C, H, K>, ) -> Result<(), Error> where C: Crypto, H: DataModel, K: KvBlobStoreAccess, - RN: Networks, - NC: NetCtl + NetCtlStatus + WirelessDiag + NetChangeNotif, { // TODO // Reset the Matter transport buffers and all sessions first @@ -719,16 +703,14 @@ where select(&mut responder, &mut im_job).coalesce().await } - async fn run_im_with_bump( + async fn run_im_with_bump( &self, - im: &MatterStackInteractionModel<'_, C, H, K, RN, NC>, + im: &MatterStackInteractionModel<'_, C, H, K>, ) -> Result<(), Error> where C: Crypto, H: DataModel, K: KvBlobStoreAccess, - RN: Networks, - NC: NetCtl + NetCtlStatus + WirelessDiag + NetChangeNotif, { // TODO // Reset the Matter transport buffers and all sessions first @@ -742,23 +724,21 @@ where select(&mut responder, &mut im_job).coalesce().await } - /// Re-hydrate the Interaction Model state (events watermark, networks store, - /// persisted subscriptions) and deliver the `Startup` lifecycle op to the - /// cluster handlers - but only for the first Interaction Model instance that - /// this stack runs (see `im_hydrated`). + /// Re-hydrate the Interaction Model state (events watermark, persisted + /// subscriptions) and deliver the `Startup` lifecycle op to the cluster + /// handlers, which load the networks store among others - but only for the + /// first Interaction Model instance that this stack runs (see `im_hydrated`). /// /// `BasicInformation::StartUp` is emitted by `InteractionModel::run` itself, /// once per process lifetime. - async fn startup_im( + async fn startup_im( &self, - im: &MatterStackInteractionModel<'_, C, H, K, RN, NC>, + im: &MatterStackInteractionModel<'_, C, H, K>, ) -> Result<(), Error> where C: Crypto, H: DataModel, K: KvBlobStoreAccess, - RN: Networks, - NC: NetCtl + NetCtlStatus + WirelessDiag + NetChangeNotif, { if self.im_hydrated.lock(|hydrated| hydrated.replace(true)) { return Ok(()); @@ -767,16 +747,14 @@ where im.startup().await } - async fn run_responder( + async fn run_responder( &self, - im: &MatterStackInteractionModel<'_, C, H, K, RN, NC>, + im: &MatterStackInteractionModel<'_, C, H, K>, ) -> Result<(), Error> where C: Crypto, H: DataModel, K: KvBlobStoreAccess, - RN: Networks, - NC: NetCtl + NetCtlStatus + WirelessDiag + NetChangeNotif, { let responder = DefaultResponder::new(im); @@ -787,16 +765,14 @@ where Ok(()) } - async fn run_responder_with_bump( + async fn run_responder_with_bump( &self, - im: &MatterStackInteractionModel<'_, C, H, K, RN, NC>, + im: &MatterStackInteractionModel<'_, C, H, K>, ) -> Result<(), Error> where C: Crypto, H: DataModel, K: KvBlobStoreAccess, - RN: Networks, - NC: NetCtl + NetCtlStatus + WirelessDiag + NetChangeNotif, { let responder = DefaultResponder::new(im); diff --git a/src/network.rs b/src/network.rs index 26777be..c1a9fe1 100644 --- a/src/network.rs +++ b/src/network.rs @@ -1,4 +1,3 @@ -use rs_matter::dm::clusters::net_comm::Networks as RsNetworks; use rs_matter::pairing::DiscoveryCapabilities; use rs_matter::utils::init::{init_from_closure, Init}; @@ -31,24 +30,9 @@ pub trait Network: Sealed { where Self: 'a; - /// The `rs-matter` networks store type owned by the stack's - /// `InteractionModelState` for this network kind. - /// - /// For Ethernet (and other transports where the Matter stack does not manage - /// network credentials) this is a no-op store; for the wireless (BLE+Wifi/Thread) - /// network it is a `WirelessNetworks` store. - type Networks: RsNetworks; - - /// A const initializer for the `rs-matter` networks store (used by the - /// `const` `MatterStack::new` constructor). - const NETWORKS: Self::Networks; - /// Return an in-place initializer for the network type. fn init() -> impl Init; - /// Return an in-place initializer for the `rs-matter` networks store. - fn init_networks() -> impl Init; - /// Return the discovery capabilities of this network when commissioning the device. fn discovery_capabilities(&self) -> DiscoveryCapabilities; diff --git a/src/wireless.rs b/src/wireless.rs index b4e0306..9a4a33b 100644 --- a/src/wireless.rs +++ b/src/wireless.rs @@ -5,25 +5,25 @@ use embassy_futures::select::{select, select3, select4}; use rs_matter::crypto::Crypto; use rs_matter::dm::clusters::gen_diag::NetifDiag; -use rs_matter::dm::clusters::net_comm::{self, NetCtlError, NetworkType, WirelessCreds}; +use rs_matter::dm::clusters::net_comm::{ + self, remove_networks, NetCtlError, NetworkType, Networks, WirelessCreds, +}; use rs_matter::dm::clusters::wifi_diag::WirelessDiag; use rs_matter::dm::clusters::{thread_diag, wifi_diag}; -use rs_matter::dm::networks::wireless::{ - NetCtlState, NoopWirelessNetCtl, WirelessNetwork, WirelessNetworks, -}; +use rs_matter::dm::networks::wireless::{NetCtlState, WirelessNetwork, WirelessNetworks}; use rs_matter::dm::networks::NetChangeNotif; use rs_matter::dm::DataModel; use rs_matter::error::Error; use rs_matter::pairing::DiscoveryCapabilities; -use rs_matter::persist::KvBlobStore; +use rs_matter::persist::{KvBlobStore, NETWORKS_KEY}; use rs_matter::sc::pase::CommWindowState; use rs_matter::transport::network::btp::{AdvData, Btp}; use rs_matter::transport::network::NoNetwork; use rs_matter::utils::cell::RefCell; use rs_matter::utils::init::{init, Init}; use rs_matter::utils::select::Coalesce; -use rs_matter::utils::sync::blocking; use rs_matter::utils::sync::DynBase; +use rs_matter::utils::sync::{blocking, IfMutex}; use crate::ble::GattPeripheral; use crate::mdns::Mdns; @@ -40,10 +40,19 @@ mod gatt; mod thread; mod wifi; +/// The number of networks of the default wireless networks store. pub const MAX_WIRELESS_NETWORKS: usize = 2; /// A type alias for a Matter stack running over either Wifi or Thread (and BLE, during commissioning). -pub type WirelessMatterStack<'a, const B: usize, T, E = ()> = MatterStack<'a, B, WirelessBle>; +/// +/// `S` is the networks store of the Wifi or Thread interface. +pub type WirelessMatterStack< + 'a, + const B: usize, + T, + E = (), + S = WirelessNetworks, +> = MatterStack<'a, B, WirelessBle>; /// An implementation of the `Network` trait for a Matter stack running over /// BLE during commissioning, and then over either WiFi or Thread when operating. @@ -57,22 +66,30 @@ pub type WirelessMatterStack<'a, const B: usize, T, E = ()> = MatterStack<'a, B, /// /// This is done to save memory and to avoid the usage of BLE+Wifi/Thread co-exist drivers on /// devices which share a single wireless radio for both BLE and Wifi/Thread. -pub struct WirelessBle +/// +/// `T` selects the operational network (`wireless::Wifi` or `wireless::Thread`) and +/// `S` is the networks store of its interface - any `Networks` implementation, by +/// default `WirelessNetworks` with `MAX_WIRELESS_NETWORKS` networks. +pub struct WirelessBle> where T: WirelessNetwork, { btp: Btp, net_state: blocking::Mutex>, + /// The networks store of the operational network interface. + /// + /// `run` holds it locked throughout and lends it, as `&mut`, to the Network + /// Commissioning handler of each phase, so that the handler is its only + /// user meanwhile. + networks: IfMutex, embedding: E, - // The wireless network type is no longer stored here (the networks store lives - // in the stack's `InteractionModelState`), but it still parameterizes the - // `Network::Networks` associated type, so keep it as a phantom marker. _network: PhantomData T>, } -impl WirelessBle +impl WirelessBle where T: WirelessNetwork, + S: Embedding, E: Embedding, { /// Creates a new instance of the `WirelessBle` network type. @@ -80,6 +97,7 @@ where Self { btp: Btp::new(), net_state: NetCtlState::new_with_mutex(), + networks: IfMutex::new(S::INIT), embedding: E::INIT, _network: PhantomData, } @@ -90,12 +108,24 @@ where init!(Self { btp <- Btp::init(), net_state <- NetCtlState::init_with_mutex(), + networks <- IfMutex::init(S::init()), embedding <- E::init(), _network: PhantomData, }) } } +impl Embedding for WirelessNetworks +where + T: WirelessNetwork, +{ + const INIT: Self = Self::new(); + + fn init() -> impl Init { + WirelessNetworks::init() + } +} + /// A composite wireless network controller that is the SAME concrete type during /// both the (BLE) commissioning phase and the operational (Thread/Wifi) phase. /// @@ -112,6 +142,7 @@ where /// The `Commissioning` variant reproduces the behavior of the former /// `NoopWirelessNetCtl`: `scan` errors with `InvalidAction`, `connect` only /// checks the creds match the network type, and every diag returns its default. +/// It refuses to disable the interface being commissioned. /// The `Operational` variant delegates to the real controller `Q`. /// /// `Q` is named identically at every phase via the wireless driver's associated @@ -173,6 +204,14 @@ where } } + fn set_enabled(&self, enabled: bool) -> Result<(), Error> { + match self { + Self::Commissioning(_) if enabled => Ok(()), + Self::Commissioning(_) => Err(rs_matter::error::ErrorCode::InvalidAction.into()), + Self::Operational(q) => q.set_enabled(enabled), + } + } + async fn scan(&self, network: Option<&[u8]>, f: F) -> Result<(), NetCtlError> where F: FnMut(&net_comm::NetworkScanInfo) -> Result<(), Error>, @@ -517,9 +556,10 @@ where } } -impl Default for WirelessBle +impl Default for WirelessBle where T: WirelessNetwork, + S: Embedding, E: Embedding, { fn default() -> Self { @@ -527,16 +567,18 @@ where } } -impl Sealed for WirelessBle +impl Sealed for WirelessBle where T: WirelessNetwork, + S: Embedding, E: Embedding, { } -impl Network for WirelessBle +impl Network for WirelessBle where T: WirelessNetwork, + S: Embedding, E: Embedding, { const INIT: Self = Self::new(); @@ -546,19 +588,10 @@ where where Self: 'a; - // The wireless networks store, owned by the stack's `InteractionModelState`. - type Networks = WirelessNetworks; - - const NETWORKS: Self::Networks = WirelessNetworks::new(); - fn init() -> impl Init { WirelessBle::init() } - fn init_networks() -> impl Init { - WirelessNetworks::init() - } - fn discovery_capabilities(&self) -> DiscoveryCapabilities { DiscoveryCapabilities::BLE } @@ -568,9 +601,10 @@ where } } -impl MatterStack<'_, B, WirelessBle> +impl MatterStack<'_, B, WirelessBle> where T: WirelessNetwork, + NW: Networks + Embedding, E: Embedding, { /// Reset the Matter instance to the factory defaults by removing all fabrics and basic info settings @@ -588,20 +622,14 @@ where self.matter.factory_reset(&kv)?; - // Reset the events counter and the wireless networks store so we don't - // carry stale state across a factory reset (Matter Core spec R1.5.1, - // §7.14.1.1 for the events watermark; the networks store holds the - // commissioned Wifi/Thread credentials). - // The net-ctl is inert here - a factory reset never drives the - // wireless connection manager. - self.im( - crypto, - handler, - &kv, - NoopWirelessNetCtl::new(NetworkType::Ethernet), - ) - .factory_reset() - .await + // The stack's Network Commissioning handler is not part of `handler`, + // so the `FactoryReset` broadcast below does not reach its networks + // store (the commissioned Wifi/Thread credentials): reset it here. + remove_networks(self.network.networks.get_mut(), NETWORKS_KEY, &kv)?; + + // Reset the events counter so we don't carry a stale watermark across a + // factory reset (Matter Core spec R1.5.1, §7.14.1.1). + self.im(crypto, handler, &kv).factory_reset().await } /// Run the startup sequence of the stack: re-hydrate the persisted state and @@ -897,12 +925,14 @@ impl PreexistingWireless { } } -pub(crate) struct MatterStackWirelessTask<'a, const B: usize, T, E, C, H, K, U, Q> +pub(crate) struct MatterStackWirelessTask<'a, const B: usize, T, S, E, C, H, K, U, Q> where T: WirelessNetwork, + S: Embedding, E: Embedding, { - stack: &'a MatterStack<'a, B, WirelessBle>, + stack: &'a MatterStack<'a, B, WirelessBle>, + networks: &'a mut S, crypto: C, handler: H, kv: K, diff --git a/src/wireless/thread.rs b/src/wireless/thread.rs index c3dcabd..697831d 100644 --- a/src/wireless/thread.rs +++ b/src/wireless/thread.rs @@ -6,10 +6,12 @@ use embassy_futures::select::{select, select3, select4}; use rs_matter::crypto::Crypto; use rs_matter::dm::clusters::gen_diag::NetifDiag; -use rs_matter::dm::clusters::net_comm::{NetCtl, NetworkType}; +use rs_matter::dm::clusters::net_comm::{NetCtl, NetworkType, Networks}; use rs_matter::dm::clusters::thread_diag::ThreadDiag; use rs_matter::dm::endpoints::thread_net_handler; -use rs_matter::dm::networks::wireless::{self, NetCtlWithStatusImpl, NoopWirelessNetCtl}; +use rs_matter::dm::networks::wireless::{ + self, connect_once, NetCtlWithStatusImpl, NoopWirelessNetCtl, ThreadNetworks, MAX_CREDS_SIZE, +}; use rs_matter::dm::networks::NetChangeNotif; use rs_matter::dm::{DataModel, Endpoint}; use rs_matter::error::Error; @@ -21,17 +23,22 @@ use rs_matter::utils::select::Coalesce; use crate::mdns::Mdns; use crate::nal::NetStack; use crate::network::Embedding; -use crate::wireless::{GattPeripheral, GattTask, MatterStackWirelessTask, WirelessNetCtl}; +use crate::wireless::{ + GattPeripheral, GattTask, MatterStackWirelessTask, WirelessNetCtl, MAX_WIRELESS_NETWORKS, +}; use crate::{pin_alloc, UserTask}; use super::{Gatt, PreexistingWireless, WirelessMatterStack}; /// A type alias for a Matter stack running over Thread (and BLE, during commissioning). -pub type ThreadMatterStack<'a, const B: usize, E = ()> = - WirelessMatterStack<'a, B, wireless::Thread, E>; +/// +/// `S` is the networks store of the Thread interface. +pub type ThreadMatterStack<'a, const B: usize, E = (), S = ThreadNetworks> = + WirelessMatterStack<'a, B, wireless::Thread, E, S>; -impl WirelessMatterStack<'_, B, wireless::Thread, E> +impl WirelessMatterStack<'_, B, wireless::Thread, E, S> where + S: Networks + Embedding, E: Embedding, { /// Run the Matter stack for an already pre-established wireless network where the BLE and the Thread stacks can co-exist. @@ -104,6 +111,9 @@ where { let _lock = self.run_lock.lock().await; + // Lent to the Network Commissioning handler of each phase + let mut networks = self.network.networks.lock().await; + info!("Matter Stack memory: {}b", core::mem::size_of_val(self)); // Since this is the last code executed in the method, resetting the allocator should be safe @@ -116,7 +126,7 @@ where let net_task = pin_alloc!( self.bump, - self.run_thread_coex(&mut thread, crypto, handler, kv, user) + self.run_thread_coex(&mut networks, &mut thread, crypto, handler, kv, user) ); net_task.await @@ -147,6 +157,9 @@ where { let _lock = self.run_lock.lock().await; + // Lent to the Network Commissioning handler of each phase + let mut networks = self.network.networks.lock().await; + info!("Matter Stack memory: {}b", core::mem::size_of_val(self)); // Since this is the last code executed in the method, resetting the allocator should be safe @@ -159,7 +172,7 @@ where let net_task = pin_alloc!( self.bump, - self.run_thread(thread, crypto, handler, kv, user) + self.run_thread(&mut networks, thread, crypto, handler, kv, user) ); net_task.await @@ -167,6 +180,7 @@ where async fn run_thread_coex( &self, + networks: &mut S, thread: &mut W, crypto: C, handler: H, @@ -185,8 +199,9 @@ where // `&kv` is also lent to the driver so it can persist its own state. thread .run( - MatterStackWirelessTask::<'_, _, _, _, _, _, _, _, NoopWirelessNetCtl> { + MatterStackWirelessTask::<'_, _, _, _, _, _, _, _, _, NoopWirelessNetCtl> { stack: self, + networks, crypto, handler, kv: &kv, @@ -199,6 +214,7 @@ where async fn run_thread( &self, + networks: &mut S, mut thread: W, crypto: C, handler: H, @@ -227,8 +243,20 @@ where Gatt::run( &mut thread, - MatterStackWirelessTask::<'_, _, _, _, _, _, _, _, ::NetCtl<'_>> { + MatterStackWirelessTask::< + '_, + _, + _, + _, + _, + _, + _, + _, + _, + ::NetCtl<'_>, + > { stack: self, + networks: &mut *networks, crypto: &crypto, handler: &handler, kv: &kv, @@ -241,8 +269,9 @@ where Thread::run( &mut thread, - MatterStackWirelessTask::<'_, _, _, _, _, _, _, _, ::NetCtl<'_>> { + MatterStackWirelessTask::<'_, _, _, _, _, _, _, _, _, ::NetCtl<'_>> { stack: self, + networks: &mut *networks, crypto: &crypto, handler: &handler, kv: &kv, @@ -453,9 +482,10 @@ where } } -impl<'a, const B: usize, E, C, H, K, X, Q> GattTask - for MatterStackWirelessTask<'a, B, wireless::Thread, E, C, H, K, X, Q> +impl<'a, const B: usize, S, E, C, H, K, X, Q> GattTask + for MatterStackWirelessTask<'a, B, wireless::Thread, S, E, C, H, K, X, Q> where + S: Networks + Embedding, E: Embedding, C: Crypto, H: DataModel, @@ -480,16 +510,16 @@ where &(), &(), &net_ctl, + &mut *self.networks, &net_ctl, &self.handler, self.crypto.weak_rand()?, ); - // The network store comes from the stack's `state`; the (commissioning) - // net-ctl is threaded into the engine, whose `run` keeps its connection - // manager dormant while not commissioned. + // The Network Commissioning handler keeps its connection manager dormant + // while not commissioned, and the commissioning net-ctl defers connects. let im = self .stack - .im(&self.crypto, (&self.handler, combined), &self.kv, &net_ctl); + .im(&self.crypto, (&self.handler, combined), &self.kv); let mut btp_task = pin!(self.stack.run_btp(&self.crypto, peripheral)); @@ -499,9 +529,10 @@ where } } -impl<'a, const B: usize, E, C, H, K, X, Z> ThreadTask - for MatterStackWirelessTask<'a, B, wireless::Thread, E, C, H, K, X, Z> +impl<'a, const B: usize, S, E, C, H, K, X, Z> ThreadTask + for MatterStackWirelessTask<'a, B, wireless::Thread, S, E, C, H, K, X, Z> where + S: Networks + Embedding, E: Embedding, C: Crypto, H: DataModel, @@ -529,37 +560,6 @@ where WirelessNetCtl::Operational(&net_ctl), ); - let combined = thread_net_handler( - &false, - &(), - &netif, - &net_ctl_s, - &net_ctl_s, - &self.handler, - self.crypto.weak_rand()?, - ); - // The operational `net_ctl` is threaded into the engine, which now drives - // the maintenance `WirelessMgr` itself (against the stack's networks store). - let im = self.stack.im( - &self.crypto, - (&self.handler, combined), - &self.kv, - &net_ctl_s, - ); - - let stack = &self.stack; - - let mut net_task = pin!(stack.run_oper_net( - &self.crypto, - &net_stack, - 0, // TODO - core::future::pending(), - Option::<(NoNetwork, NoNetwork)>::None - )); - - let mut mdns_task = - pin!(stack.run_oper_netif_mdns(&self.crypto, &net_stack, &netif, &mut mdns)); - // Non-concurrent commissioning deferred connect. // // In non-concurrent (BLE-only) commissioning the commissioner's @@ -567,15 +567,16 @@ where // network cannot yet run, so the actual connect is deferred. Now that the // operational network is up, replay that connect *before* commissioning // completes - the commissioner re-establishes a CASE session over Thread - // and only then sends `CommissioningComplete`. The engine's maintenance + // and only then sends `CommissioningComplete`. The handler's connection // manager only connects *after* the device is commissioned, so this - // one-shot connect is still performed here. + // one-shot connect is performed here - before the handler borrows the + // networks store. // // The target network is the exact one the commissioner selected: its ID // is remembered in `NetCtlState` by the commissioning-phase `connect` // wrapper. `is_prov_ready()` is true only in this pending non-concurrent // case (on a normal reboot of an already-commissioned device the state is - // empty, so we skip the one-shot connect and let the engine's manager run). + // empty, so we skip the one-shot connect and let the handler's manager run). let deferred_connect_id = self.stack.network.net_state.lock(|state| { let state = state.borrow(); state.is_prov_ready().then(|| state.network_id.clone()) @@ -584,11 +585,40 @@ where if let Some(network_id) = deferred_connect_id { info!("Non-concurrent commissioning: performing the deferred connect"); - // The engine owns the networks + net-ctl; ask it to replay the - // deferred connect (no stack-owned `WirelessMgr`). - im.connect_once(&network_id).await?; + let mut buf = [0u8; MAX_CREDS_SIZE]; + + connect_once(&mut *self.networks, &net_ctl_s, &network_id, &mut buf).await?; } + // The Network Commissioning handler drives the maintenance `WirelessMgr` + // with the operational `net_ctl`. + let combined = thread_net_handler( + &false, + &(), + &netif, + &net_ctl_s, + &mut *self.networks, + &net_ctl_s, + &self.handler, + self.crypto.weak_rand()?, + ); + let im = self + .stack + .im(&self.crypto, (&self.handler, combined), &self.kv); + + let stack = &self.stack; + + let mut net_task = pin!(stack.run_oper_net( + &self.crypto, + &net_stack, + 0, // TODO + core::future::pending(), + Option::<(NoNetwork, NoNetwork)>::None + )); + + let mut mdns_task = + pin!(stack.run_oper_netif_mdns(&self.crypto, &net_stack, &netif, &mut mdns)); + let mut im_task = pin!(self.stack.run_im(&im)); let mut user_task = pin!(self.user_task.run(&net_stack, &netif)); @@ -615,9 +645,10 @@ where } } -impl<'a, const B: usize, E, C, H, K, X, Z> ThreadCoexTask - for MatterStackWirelessTask<'a, B, wireless::Thread, E, C, H, K, X, Z> +impl<'a, const B: usize, S, E, C, H, K, X, Z> ThreadCoexTask + for MatterStackWirelessTask<'a, B, wireless::Thread, S, E, C, H, K, X, Z> where + S: Networks + Embedding, E: Embedding, C: Crypto, H: DataModel, @@ -647,24 +678,22 @@ where WirelessNetCtl::Operational(&net_ctl), ); + // The Network Commissioning handler drives the maintenance `WirelessMgr` + // with the operational `net_ctl`; `run_net_coex` only runs the BTP coex + // transport. let combined = thread_net_handler( &true, &(), &netif, &net_ctl_s, + &mut *self.networks, &net_ctl_s, &self.handler, self.crypto.weak_rand()?, ); - // The operational `net_ctl` is threaded into the engine, which drives the - // maintenance `WirelessMgr` itself; `run_net_coex` only runs the BTP coex - // transport now. - let im = self.stack.im( - &self.crypto, - (&self.handler, combined), - &self.kv, - &net_ctl_s, - ); + let im = self + .stack + .im(&self.crypto, (&self.handler, combined), &self.kv); let stack = &self.stack; let bump = &stack.bump; diff --git a/src/wireless/wifi.rs b/src/wireless/wifi.rs index d8c3e56..1efc4c0 100644 --- a/src/wireless/wifi.rs +++ b/src/wireless/wifi.rs @@ -6,10 +6,12 @@ use embassy_futures::select::{select, select3, select4}; use rs_matter::crypto::Crypto; use rs_matter::dm::clusters::gen_diag::NetifDiag; -use rs_matter::dm::clusters::net_comm::{NetCtl, NetworkType}; +use rs_matter::dm::clusters::net_comm::{NetCtl, NetworkType, Networks}; use rs_matter::dm::clusters::wifi_diag::WifiDiag; use rs_matter::dm::endpoints::wifi_net_handler; -use rs_matter::dm::networks::wireless::{self, NetCtlWithStatusImpl, NoopWirelessNetCtl}; +use rs_matter::dm::networks::wireless::{ + self, connect_once, NetCtlWithStatusImpl, NoopWirelessNetCtl, WifiNetworks, MAX_CREDS_SIZE, +}; use rs_matter::dm::networks::NetChangeNotif; use rs_matter::dm::{DataModel, Endpoint}; use rs_matter::error::Error; @@ -21,17 +23,22 @@ use rs_matter::utils::select::Coalesce; use crate::mdns::Mdns; use crate::nal::NetStack; use crate::network::Embedding; -use crate::wireless::{GattPeripheral, MatterStackWirelessTask, WirelessNetCtl}; +use crate::wireless::{ + GattPeripheral, MatterStackWirelessTask, WirelessNetCtl, MAX_WIRELESS_NETWORKS, +}; use crate::{pin_alloc, UserTask}; use super::{Gatt, GattTask, PreexistingWireless, WirelessMatterStack}; /// A type alias for a Matter stack running over Wifi (and BLE, during commissioning). -pub type WifiMatterStack<'a, const B: usize, E = ()> = - WirelessMatterStack<'a, B, wireless::Wifi, E>; +/// +/// `S` is the networks store of the Wifi interface. +pub type WifiMatterStack<'a, const B: usize, E = (), S = WifiNetworks> = + WirelessMatterStack<'a, B, wireless::Wifi, E, S>; -impl WirelessMatterStack<'_, B, wireless::Wifi, E> +impl WirelessMatterStack<'_, B, wireless::Wifi, E, S> where + S: Networks + Embedding, E: Embedding, { /// Run the Matter stack for an already pre-established wireless network where the BLE and the Wifi stacks can co-exist. @@ -104,6 +111,9 @@ where { let _lock = self.run_lock.lock().await; + // Lent to the Network Commissioning handler of each phase + let mut networks = self.network.networks.lock().await; + info!("Matter Stack memory: {}b", core::mem::size_of_val(self)); // Since this is the last code executed in the method, resetting the allocator should be safe @@ -116,7 +126,7 @@ where let net_task = pin_alloc!( self.bump, - self.run_wifi_coex(&mut wifi, crypto, handler, kv, user) + self.run_wifi_coex(&mut networks, &mut wifi, crypto, handler, kv, user) ); net_task.await @@ -147,6 +157,9 @@ where { let _lock = self.run_lock.lock().await; + // Lent to the Network Commissioning handler of each phase + let mut networks = self.network.networks.lock().await; + info!("Matter Stack memory: {}b", core::mem::size_of_val(self)); // Since this is the last code executed in the method, resetting the allocator should be safe @@ -157,13 +170,17 @@ where self.matter().reset_transport()?; - let net_task = pin_alloc!(self.bump, self.run_wifi(wifi, crypto, handler, kv, user)); + let net_task = pin_alloc!( + self.bump, + self.run_wifi(&mut networks, wifi, crypto, handler, kv, user) + ); net_task.await } async fn run_wifi_coex( &self, + networks: &mut S, wifi: &mut W, crypto: C, handler: H, @@ -181,8 +198,9 @@ where // phantom net-ctl type is an irrelevant placeholder. // `&kv` is also lent to the driver so it can persist its own state. wifi.run( - MatterStackWirelessTask::<'_, _, _, _, _, _, _, _, NoopWirelessNetCtl> { + MatterStackWirelessTask::<'_, _, _, _, _, _, _, _, _, NoopWirelessNetCtl> { stack: self, + networks, crypto, handler, kv, @@ -195,6 +213,7 @@ where async fn run_wifi( &self, + networks: &mut S, mut wifi: W, crypto: C, handler: H, @@ -223,8 +242,9 @@ where Gatt::run( &mut wifi, - MatterStackWirelessTask::<'_, _, _, _, _, _, _, _, ::NetCtl<'_>> { + MatterStackWirelessTask::<'_, _, _, _, _, _, _, _, _, ::NetCtl<'_>> { stack: self, + networks: &mut *networks, crypto: &crypto, handler: &handler, kv: &kv, @@ -237,8 +257,9 @@ where Wifi::run( &mut wifi, - MatterStackWirelessTask::<'_, _, _, _, _, _, _, _, ::NetCtl<'_>> { + MatterStackWirelessTask::<'_, _, _, _, _, _, _, _, _, ::NetCtl<'_>> { stack: self, + networks: &mut *networks, crypto: &crypto, handler: &handler, kv: &kv, @@ -449,9 +470,10 @@ where } } -impl<'a, const B: usize, E, C, H, K, U, Q> GattTask - for MatterStackWirelessTask<'a, B, wireless::Wifi, E, C, H, K, U, Q> +impl<'a, const B: usize, S, E, C, H, K, U, Q> GattTask + for MatterStackWirelessTask<'a, B, wireless::Wifi, S, E, C, H, K, U, Q> where + S: Networks + Embedding, E: Embedding, C: Crypto, H: DataModel, @@ -476,16 +498,16 @@ where &(), &(), &net_ctl, + &mut *self.networks, &net_ctl, &self.handler, self.crypto.weak_rand()?, ); - // The network store comes from the stack's `state`; the (commissioning) - // net-ctl is threaded into the engine, whose `run` keeps its connection - // manager dormant while not commissioned. + // The Network Commissioning handler keeps its connection manager dormant + // while not commissioned, and the commissioning net-ctl defers connects. let im = self .stack - .im(&self.crypto, (&self.handler, combined), &self.kv, &net_ctl); + .im(&self.crypto, (&self.handler, combined), &self.kv); let mut btp_task = pin!(self.stack.run_btp(&self.crypto, peripheral)); @@ -495,9 +517,10 @@ where } } -impl<'a, const B: usize, E, C, H, K, X, Z> WifiTask - for MatterStackWirelessTask<'a, B, wireless::Wifi, E, C, H, K, X, Z> +impl<'a, const B: usize, S, E, C, H, K, X, Z> WifiTask + for MatterStackWirelessTask<'a, B, wireless::Wifi, S, E, C, H, K, X, Z> where + S: Networks + Embedding, E: Embedding, C: Crypto, H: DataModel, @@ -525,23 +548,42 @@ where WirelessNetCtl::Operational(&net_ctl), ); + // Non-concurrent commissioning deferred connect. In BLE-only commissioning + // the commissioner's `ConnectNetwork` is deferred (Wifi can't run during + // BLE) and must be replayed once the operational network is up but *before* + // `CommissioningComplete`. The handler's connection manager only connects + // *after* the device is commissioned, so this one-shot connect is + // performed here - before the handler borrows the networks store. The + // target network ID is the one the commissioner selected, remembered in + // `NetCtlState`. + let deferred_connect_id = self.stack.network.net_state.lock(|state| { + let state = state.borrow(); + state.is_prov_ready().then(|| state.network_id.clone()) + }); + + if let Some(network_id) = deferred_connect_id { + info!("Non-concurrent commissioning: performing the deferred connect"); + + let mut buf = [0u8; MAX_CREDS_SIZE]; + + connect_once(&mut *self.networks, &net_ctl_s, &network_id, &mut buf).await?; + } + + // The Network Commissioning handler drives the maintenance `WirelessMgr` + // with the operational `net_ctl`. let combined = wifi_net_handler( &false, &(), &netif, &net_ctl_s, + &mut *self.networks, &net_ctl_s, &self.handler, self.crypto.weak_rand()?, ); - // The operational `net_ctl` is threaded into the engine, which now drives - // the maintenance `WirelessMgr` itself (against the stack's networks store). - let im = self.stack.im( - &self.crypto, - (&self.handler, combined), - &self.kv, - &net_ctl_s, - ); + let im = self + .stack + .im(&self.crypto, (&self.handler, combined), &self.kv); let stack = &self.stack; @@ -556,26 +598,6 @@ where let mut mdns_task = pin!(stack.run_oper_netif_mdns(&self.crypto, &net_stack, &netif, &mut mdns)); - // Non-concurrent commissioning deferred connect. In BLE-only commissioning - // the commissioner's `ConnectNetwork` is deferred (Wifi can't run during - // BLE) and must be replayed once the operational network is up but *before* - // `CommissioningComplete`. The engine's maintenance manager only connects - // *after* the device is commissioned, so this one-shot connect is still - // performed here. The target network ID is the one the commissioner - // selected, remembered in `NetCtlState`. - let deferred_connect_id = self.stack.network.net_state.lock(|state| { - let state = state.borrow(); - state.is_prov_ready().then(|| state.network_id.clone()) - }); - - if let Some(network_id) = deferred_connect_id { - info!("Non-concurrent commissioning: performing the deferred connect"); - - // The engine owns the networks + net-ctl; ask it to replay the - // deferred connect (no stack-owned `WirelessMgr`). - im.connect_once(&network_id).await?; - } - let mut im_task = pin!(self.stack.run_im(&im)); let mut user_task = pin!(self.user_task.run(&net_stack, &netif)); @@ -602,9 +624,10 @@ where } } -impl<'a, const B: usize, E, C, H, K, X, Z> WifiCoexTask - for MatterStackWirelessTask<'a, B, wireless::Wifi, E, C, H, K, X, Z> +impl<'a, const B: usize, S, E, C, H, K, X, Z> WifiCoexTask + for MatterStackWirelessTask<'a, B, wireless::Wifi, S, E, C, H, K, X, Z> where + S: Networks + Embedding, E: Embedding, C: Crypto, H: DataModel, @@ -634,24 +657,22 @@ where WirelessNetCtl::Operational(&net_ctl), ); + // The Network Commissioning handler drives the maintenance `WirelessMgr` + // with the operational `net_ctl`; `run_net_coex` only runs the BTP coex + // transport. let combined = wifi_net_handler( &true, &(), &netif, &net_ctl_s, + &mut *self.networks, &net_ctl_s, &self.handler, self.crypto.weak_rand()?, ); - // The operational `net_ctl` is threaded into the engine, which drives the - // maintenance `WirelessMgr` itself; `run_net_coex` only runs the BTP coex - // transport now. - let im = self.stack.im( - &self.crypto, - (&self.handler, combined), - &self.kv, - &net_ctl_s, - ); + let im = self + .stack + .im(&self.crypto, (&self.handler, combined), &self.kv); let stack = &self.stack; let bump = &stack.bump;