From faf9b0a79f9c3829e4c24ddc6eb8275796b9a753 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Sat, 3 May 2025 15:01:26 +0000 Subject: [PATCH 1/2] Convert all system cluster handlers to strongly-typed ones using the import macro --- examples/src/bin/onoff_light.rs | 53 +- examples/src/bin/onoff_light_bt.rs | 118 +- examples/src/bin/speaker.rs | 59 +- examples/src/common/comm.rs | 265 ---- examples/src/common/mdns.rs | 8 +- .../benches/client-clusters-criterion.rs | 2 +- .../benches/client-clusters.rs | 2 +- rs-matter-macros-impl/src/idl.rs | 2 +- rs-matter/Cargo.toml | 4 + rs-matter/src/acl.rs | 303 +++-- rs-matter/src/core.rs | 9 +- rs-matter/src/data_model/basic_info.rs | 8 +- rs-matter/src/data_model/clusters.rs | 3 +- rs-matter/src/data_model/core.rs | 3 +- rs-matter/src/data_model/device_types.rs | 11 + rs-matter/src/data_model/mod.rs | 1 + rs-matter/src/data_model/networks.rs | 38 + rs-matter/src/data_model/networks/eth.rs | 130 ++ rs-matter/src/data_model/networks/unix.rs | 138 ++ rs-matter/src/data_model/networks/wireless.rs | 1079 ++++++++++++++++ .../src/data_model/networks/wireless/mgr.rs | 206 +++ .../data_model/networks/wireless/thread.rs | 207 +++ .../src/data_model/networks/wireless/wifi.rs | 135 ++ rs-matter/src/data_model/objects/cluster.rs | 122 +- rs-matter/src/data_model/objects/handler.rs | 2 +- rs-matter/src/data_model/objects/node.rs | 2 +- rs-matter/src/data_model/objects/privilege.rs | 65 +- rs-matter/src/data_model/on_off.rs | 12 +- rs-matter/src/data_model/root_endpoint.rs | 408 +++--- rs-matter/src/data_model/sdm/adm_comm.rs | 148 +++ .../src/data_model/sdm/admin_commissioning.rs | 238 ---- rs-matter/src/data_model/sdm/dev_att.rs | 2 + .../sdm/{eth_nw_diag.rs => eth_diag.rs} | 29 +- rs-matter/src/data_model/sdm/gen_comm.rs | 20 +- rs-matter/src/data_model/sdm/gen_diag.rs | 275 ++++ .../src/data_model/sdm/general_diagnostics.rs | 164 --- .../data_model/sdm/group_key_management.rs | 171 --- rs-matter/src/data_model/sdm/grp_key_mgmt.rs | 137 ++ rs-matter/src/data_model/sdm/mod.rs | 15 +- rs-matter/src/data_model/sdm/net_comm.rs | 1123 +++++++++++++++++ rs-matter/src/data_model/sdm/noc.rs | 894 ++++++------- .../src/data_model/sdm/nw_commissioning.rs | 478 ------- rs-matter/src/data_model/sdm/thread_diag.rs | 665 ++++++++++ .../data_model/sdm/thread_nw_diagnostics.rs | 497 -------- rs-matter/src/data_model/sdm/wifi_diag.rs | 176 +++ .../src/data_model/sdm/wifi_nw_diagnostics.rs | 266 ---- .../{access_control.rs => acl.rs} | 421 +++--- rs-matter/src/data_model/system_model/desc.rs | 243 ++++ .../src/data_model/system_model/descriptor.rs | 257 ---- rs-matter/src/data_model/system_model/mod.rs | 4 +- rs-matter/src/fabric.rs | 125 +- .../src/{data_model/sdm => }/failsafe.rs | 2 + rs-matter/src/lib.rs | 1 + rs-matter/src/mdns.rs | 10 +- rs-matter/src/persist.rs | 82 +- rs-matter/src/secure_channel/pake.rs | 21 +- rs-matter/src/tlv/write.rs | 143 ++- rs-matter/src/transport/network/btp.rs | 8 + .../src/transport/network/btp/context.rs | 47 +- rs-matter/src/transport/network/btp/gatt.rs | 2 + rs-matter/src/transport/session.rs | 5 + rs-matter/tests/common/attributes.rs | 134 -- rs-matter/tests/common/e2e/im/echo_cluster.rs | 16 +- rs-matter/tests/common/e2e/im/handler.rs | 133 +- rs-matter/tests/common/e2e/tlv.rs | 2 +- rs-matter/tests/common/im_engine.rs | 452 ------- rs-matter/tests/data_model/acl_and_dataver.rs | 13 +- rs-matter/tests/data_model/long_reads.rs | 155 ++- 68 files changed, 6661 insertions(+), 4308 deletions(-) delete mode 100644 examples/src/common/comm.rs create mode 100644 rs-matter/src/data_model/networks.rs create mode 100644 rs-matter/src/data_model/networks/eth.rs create mode 100644 rs-matter/src/data_model/networks/unix.rs create mode 100644 rs-matter/src/data_model/networks/wireless.rs create mode 100644 rs-matter/src/data_model/networks/wireless/mgr.rs create mode 100644 rs-matter/src/data_model/networks/wireless/thread.rs create mode 100644 rs-matter/src/data_model/networks/wireless/wifi.rs create mode 100644 rs-matter/src/data_model/sdm/adm_comm.rs delete mode 100644 rs-matter/src/data_model/sdm/admin_commissioning.rs rename rs-matter/src/data_model/sdm/{eth_nw_diag.rs => eth_diag.rs} (69%) create mode 100644 rs-matter/src/data_model/sdm/gen_diag.rs delete mode 100644 rs-matter/src/data_model/sdm/general_diagnostics.rs delete mode 100644 rs-matter/src/data_model/sdm/group_key_management.rs create mode 100644 rs-matter/src/data_model/sdm/grp_key_mgmt.rs create mode 100644 rs-matter/src/data_model/sdm/net_comm.rs delete mode 100644 rs-matter/src/data_model/sdm/nw_commissioning.rs create mode 100644 rs-matter/src/data_model/sdm/thread_diag.rs delete mode 100644 rs-matter/src/data_model/sdm/thread_nw_diagnostics.rs create mode 100644 rs-matter/src/data_model/sdm/wifi_diag.rs delete mode 100644 rs-matter/src/data_model/sdm/wifi_nw_diagnostics.rs rename rs-matter/src/data_model/system_model/{access_control.rs => acl.rs} (55%) create mode 100644 rs-matter/src/data_model/system_model/desc.rs delete mode 100644 rs-matter/src/data_model/system_model/descriptor.rs rename rs-matter/src/{data_model/sdm => }/failsafe.rs (99%) delete mode 100644 rs-matter/tests/common/attributes.rs delete mode 100644 rs-matter/tests/common/im_engine.rs diff --git a/examples/src/bin/onoff_light.rs b/examples/src/bin/onoff_light.rs index 472abfa3..8b273c24 100644 --- a/examples/src/bin/onoff_light.rs +++ b/examples/src/bin/onoff_light.rs @@ -30,21 +30,25 @@ use log::info; use rs_matter::core::{Matter, MATTER_PORT}; use rs_matter::data_model::core::IMBuffer; use rs_matter::data_model::device_types::DEV_TYPE_ON_OFF_LIGHT; -use rs_matter::data_model::objects::*; +use rs_matter::data_model::networks::unix::UnixNetifs; +use rs_matter::data_model::objects::{ + Async, AsyncHandler, AsyncMetadata, Dataver, EmptyHandler, Endpoint, Node, +}; use rs_matter::data_model::on_off::{self, ClusterHandler as _}; use rs_matter::data_model::root_endpoint; +use rs_matter::data_model::sdm::net_comm::NetworkType; use rs_matter::data_model::subscriptions::Subscriptions; -use rs_matter::data_model::system_model::descriptor; +use rs_matter::data_model::system_model::desc::{self, ClusterHandler as _}; use rs_matter::error::Error; use rs_matter::mdns::MdnsService; use rs_matter::pairing::DiscoveryCapabilities; use rs_matter::persist::Psm; use rs_matter::respond::DefaultResponder; -use rs_matter::test_device; use rs_matter::transport::core::MATTER_SOCKET_BIND_ADDR; use rs_matter::utils::init::InitMaybeUninit; use rs_matter::utils::select::Coalesce; use rs_matter::utils::storage::pooled::PooledBuffers; +use rs_matter::{clusters, devices, test_device}; use static_cell::StaticCell; @@ -68,7 +72,7 @@ fn main() -> Result<(), Error> { // e.g., an opt-level of "0" will require a several times' larger stack. // // Optimizing/lowering `rs-matter` memory consumption is an ongoing topic. - .stack_size(45 * 1024) + .stack_size(55 * 1024) .spawn(run) .unwrap(); @@ -120,7 +124,7 @@ fn run() -> Result<(), Error> { let on_off = on_off::OnOffHandler::new(Dataver::new_rand(matter.rand())); // Assemble our Data Model handler by composing the predefined Root Endpoint handler with the On/Off handler - let dm_handler = Async(dm_handler(matter, &on_off)); + let dm_handler = dm_handler(matter, &on_off); // Create a default responder capable of handling up to 3 subscriptions // All other subscription requests will be turned down with "resource exhausted" @@ -192,11 +196,11 @@ fn run() -> Result<(), Error> { const NODE: Node<'static> = Node { id: 0, endpoints: &[ - root_endpoint::endpoint(0, root_endpoint::OperNwType::Ethernet), + root_endpoint::root_endpoint(NetworkType::Ethernet), Endpoint { id: 1, - device_types: &[DEV_TYPE_ON_OFF_LIGHT], - clusters: &[descriptor::CLUSTER, on_off::OnOffHandler::CLUSTER], + device_types: devices!(DEV_TYPE_ON_OFF_LIGHT), + clusters: clusters!(desc::DescHandler::CLUSTER, on_off::OnOffHandler::CLUSTER), }, ], }; @@ -206,21 +210,28 @@ const NODE: Node<'static> = Node { fn dm_handler<'a>( matter: &'a Matter<'a>, on_off: &'a on_off::OnOffHandler, -) -> impl Metadata + NonBlockingHandler + 'a { +) -> impl AsyncMetadata + AsyncHandler + 'a { ( NODE, - root_endpoint::eth_handler(0, matter.rand()) - .chain( - 1, - descriptor::ID, - Async(descriptor::DescriptorCluster::new(Dataver::new_rand( - matter.rand(), - ))), - ) - .chain( - 1, - on_off::OnOffHandler::CLUSTER.id, - Async(on_off::HandlerAdaptor(on_off)), + root_endpoint::with_eth( + &(), + &UnixNetifs, + matter.rand(), + root_endpoint::with_sys( + &false, + matter.rand(), + EmptyHandler + .chain( + 1, + desc::DescHandler::CLUSTER.id, + Async(desc::DescHandler::new(Dataver::new_rand(matter.rand())).adapt()), + ) + .chain( + 1, + on_off::OnOffHandler::CLUSTER.id, + Async(on_off::HandlerAdaptor(on_off)), + ), ), + ), ) } diff --git a/examples/src/bin/onoff_light_bt.rs b/examples/src/bin/onoff_light_bt.rs index 9b49870d..a7b46780 100644 --- a/examples/src/bin/onoff_light_bt.rs +++ b/examples/src/bin/onoff_light_bt.rs @@ -30,38 +30,40 @@ use core::pin::pin; use std::net::UdpSocket; -use comm::WifiNwCommCluster; use embassy_futures::select::{select, select4}; use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_time::{Duration, Timer}; -use log::{info, warn}; +use log::info; use rs_matter::core::Matter; use rs_matter::data_model::device_types::DEV_TYPE_ON_OFF_LIGHT; -use rs_matter::data_model::objects::*; +use rs_matter::data_model::networks::unix::UnixNetifs; +use rs_matter::data_model::networks::wireless::{ + NetCtlState, NetCtlWithStatusImpl, NoopWirelessNetCtl, WifiNetworks, +}; +use rs_matter::data_model::objects::{ + Async, AsyncHandler, AsyncMetadata, Dataver, EmptyHandler, Endpoint, Node, +}; use rs_matter::data_model::on_off::{self, ClusterHandler as _}; use rs_matter::data_model::root_endpoint; -use rs_matter::data_model::sdm::wifi_nw_diagnostics::{ - self, WiFiSecurity, WiFiVersion, WifiNwDiagCluster, WifiNwDiagData, -}; +use rs_matter::data_model::sdm::net_comm::{NetCtl, NetCtlStatus, NetworkType, Networks}; +use rs_matter::data_model::sdm::wifi_diag::WifiDiag; use rs_matter::data_model::subscriptions::Subscriptions; -use rs_matter::data_model::system_model::descriptor; +use rs_matter::data_model::system_model::desc::{self, ClusterHandler as _}; use rs_matter::error::Error; use rs_matter::mdns::MdnsService; use rs_matter::pairing::DiscoveryCapabilities; use rs_matter::persist::Psm; use rs_matter::respond::DefaultResponder; -use rs_matter::test_device; use rs_matter::transport::core::MATTER_SOCKET_BIND_ADDR; use rs_matter::transport::network::btp::{Btp, BtpContext}; use rs_matter::utils::select::Coalesce; use rs_matter::utils::storage::pooled::PooledBuffers; -use rs_matter::utils::sync::{blocking::raw::StdRawMutex, Notification}; -use rs_matter::MATTER_PORT; +use rs_matter::utils::sync::blocking::raw::StdRawMutex; +use rs_matter::{clusters, test_device}; +use rs_matter::{devices, MATTER_PORT}; -#[path = "../common/comm.rs"] -mod comm; #[path = "../common/mdns.rs"] mod mdns; @@ -94,11 +96,17 @@ fn main() -> Result<(), Error> { // Our on-off cluster let on_off = on_off::OnOffHandler::new(Dataver::new_rand(matter.rand())); - // A notification when the Wifi is setup, so that Matter over UDP can start - let wifi_complete = Notification::new(); + // A storage for the Wifi networks + let networks = WifiNetworks::<3, NoopRawMutex>::new(); + + // The network controller + // We would be using a "fake" network controller that does not actually manage any Wifi networks + let net_ctl_state = NetCtlState::new_with_mutex::(); + let net_ctl = + NetCtlWithStatusImpl::new(&net_ctl_state, NoopWirelessNetCtl::new(NetworkType::Wifi)); // Assemble our Data Model handler by composing the predefined Root Endpoint handler with the On/Off handler - let dm_handler = Async(dm_handler(&matter, &on_off, &wifi_complete)); + let dm_handler = dm_handler(&matter, &on_off, &net_ctl, &networks); // Create a default responder capable of handling up to 3 subscriptions // All other subscription requests will be turned down with "resource exhausted" @@ -127,8 +135,9 @@ fn main() -> Result<(), Error> { let dir = std::env::temp_dir().join("rs-matter"); psm.load(&dir, &matter)?; + psm.load_networks(&dir, &networks)?; - let mut persist = pin!(psm.run(dir, &matter)); + let mut persist = pin!(psm.run_with_networks(dir, &matter, Some(&networks))); // Create and run the mDNS responder let mut mdns = pin!(mdns::run_mdns(&matter)); @@ -145,15 +154,8 @@ fn main() -> Result<(), Error> { )); let mut transport = pin!(matter.run(&btp, &btp, DiscoveryCapabilities::BLE)); - - let mut wifi_complete_task = pin!(async { - wifi_complete.wait().await; - warn!( - "Wifi setup complete, giving 4 seconds to BTP to finish any outstanding messages" - ); - - Timer::after(Duration::from_secs(4)).await; - + let mut wifi_prov_task = pin!(async { + NetCtlState::wait_prov_ready(&net_ctl_state, &btp).await; Ok(()) }); @@ -161,7 +163,7 @@ fn main() -> Result<(), Error> { let all = select4( &mut transport, &mut bluetooth, - select(&mut wifi_complete_task, &mut persist).coalesce(), + select(&mut wifi_prov_task, &mut persist).coalesce(), select(&mut respond, &mut device).coalesce(), ); @@ -193,55 +195,49 @@ fn main() -> Result<(), Error> { const NODE: Node<'static> = Node { id: 0, endpoints: &[ - root_endpoint::endpoint(0, root_endpoint::OperNwType::Wifi), + root_endpoint::root_endpoint(NetworkType::Wifi), Endpoint { id: 1, - device_types: &[DEV_TYPE_ON_OFF_LIGHT], - clusters: &[descriptor::CLUSTER, on_off::OnOffHandler::CLUSTER], + device_types: devices!(DEV_TYPE_ON_OFF_LIGHT), + clusters: clusters!(desc::DescHandler::CLUSTER, on_off::OnOffHandler::CLUSTER), }, ], }; /// The Data Model handler + meta-data for our Matter device. /// The handler is the root endpoint 0 handler plus the on-off handler and its descriptor. -fn dm_handler<'a>( +fn dm_handler<'a, N>( matter: &'a Matter<'a>, on_off: &'a on_off::OnOffHandler, - nw_setup_complete: &'a Notification, -) -> impl Metadata + NonBlockingHandler + 'a { + net_ctl: &'a N, + networks: &'a dyn Networks, +) -> impl AsyncMetadata + AsyncHandler + 'a +where + N: NetCtl + NetCtlStatus + WifiDiag, +{ ( NODE, - root_endpoint::handler( - 0, - Async(WifiNwCommCluster::new( - Dataver::new_rand(matter.rand()), - nw_setup_complete, - )), - wifi_nw_diagnostics::ID, - Async(WifiNwDiagCluster::new( - Dataver::new_rand(matter.rand()), - WifiNwDiagData { - bssid: [0; 6], - security_type: WiFiSecurity::Wpa2Personal, - wifi_version: WiFiVersion::B, - channel_number: 20, - rssi: 0, - }, - )), - &false, + root_endpoint::with_wifi( + &(), + &UnixNetifs, + net_ctl, + networks, matter.rand(), - ) - .chain( - 1, - descriptor::ID, - Async(descriptor::DescriptorCluster::new(Dataver::new_rand( + root_endpoint::with_sys( + &false, matter.rand(), - ))), - ) - .chain( - 1, - on_off::OnOffHandler::CLUSTER.id, - Async(on_off::HandlerAdaptor(on_off)), + EmptyHandler + .chain( + 1, + desc::DescHandler::CLUSTER.id, + Async(desc::DescHandler::new(Dataver::new_rand(matter.rand())).adapt()), + ) + .chain( + 1, + on_off::OnOffHandler::CLUSTER.id, + Async(on_off::HandlerAdaptor(on_off)), + ), + ), ), ) } diff --git a/examples/src/bin/speaker.rs b/examples/src/bin/speaker.rs index 5e0c7fa9..b1314830 100644 --- a/examples/src/bin/speaker.rs +++ b/examples/src/bin/speaker.rs @@ -29,27 +29,32 @@ use embassy_sync::blocking_mutex::raw::NoopRawMutex; use log::info; use media_playback::{ ActivateAudioTrackRequest, ActivateTextTrackRequest, ClusterAsyncHandler as _, - FastForwardRequest, PlaybackResponseBuilder, PlaybackStateEnum, RewindRequest, SeekRequest, - SkipBackwardRequest, SkipForwardRequest, StatusEnum, + FastForwardRequest, HandlerAsyncAdaptor, PlaybackResponseBuilder, PlaybackStateEnum, + RewindRequest, SeekRequest, SkipBackwardRequest, SkipForwardRequest, StatusEnum, }; use rs_matter::core::{Matter, MATTER_PORT}; use rs_matter::data_model::device_types::DEV_TYPE_SMART_SPEAKER; -use rs_matter::data_model::objects::{InvokeContext, ReadContext, *}; +use rs_matter::data_model::networks::unix::UnixNetifs; +use rs_matter::data_model::objects::{ + Async, AsyncHandler, AsyncMetadata, Cluster, Dataver, EmptyHandler, Endpoint, InvokeContext, + Node, ReadContext, +}; use rs_matter::data_model::root_endpoint; +use rs_matter::data_model::sdm::net_comm::NetworkType; use rs_matter::data_model::subscriptions::Subscriptions; -use rs_matter::data_model::system_model::descriptor; +use rs_matter::data_model::system_model::desc::{self, ClusterHandler as _}; use rs_matter::error::{Error, ErrorCode}; use rs_matter::mdns::MdnsService; use rs_matter::pairing::DiscoveryCapabilities; use rs_matter::persist::Psm; use rs_matter::respond::DefaultResponder; -use rs_matter::test_device; use rs_matter::tlv::TLVBuilderParent; use rs_matter::transport::core::MATTER_SOCKET_BIND_ADDR; use rs_matter::utils::select::Coalesce; use rs_matter::utils::storage::pooled::PooledBuffers; use rs_matter::with; +use rs_matter::{clusters, test_device}; // Import the MediaPlayback cluster from `rs-matter`. // @@ -124,35 +129,40 @@ fn main() -> Result<(), Error> { const NODE: Node<'static> = Node { id: 0, endpoints: &[ - root_endpoint::endpoint(0, root_endpoint::OperNwType::Ethernet), + root_endpoint::root_endpoint(NetworkType::Ethernet), Endpoint { id: 1, device_types: &[DEV_TYPE_SMART_SPEAKER], - clusters: &[descriptor::CLUSTER, SpeakerHandler::CLUSTER], + clusters: clusters!(desc::DescHandler::CLUSTER, SpeakerHandler::CLUSTER), }, ], }; /// The Data Model handler + meta-data for our Matter device. /// The handler is the root endpoint 0 handler plus the Speaker handler and its descriptor. -fn dm_handler<'a>(matter: &Matter<'_>) -> impl AsyncMetadata + AsyncHandler + 'a { +fn dm_handler(matter: &Matter<'_>) -> impl AsyncMetadata + AsyncHandler + 'static { ( NODE, - root_endpoint::eth_handler(0, matter.rand()) - .chain( - 1, - descriptor::ID, - Async(descriptor::DescriptorCluster::new(Dataver::new_rand( - matter.rand(), - ))), - ) - .chain( - 1, - SpeakerHandler::CLUSTER.id, - media_playback::HandlerAsyncAdaptor(SpeakerHandler::new(Dataver::new_rand( - matter.rand(), - ))), + root_endpoint::with_eth( + &(), + &UnixNetifs, + matter.rand(), + root_endpoint::with_sys( + &false, + matter.rand(), + EmptyHandler + .chain( + 1, + desc::DescHandler::CLUSTER.id, + Async(desc::DescHandler::new(Dataver::new_rand(matter.rand())).adapt()), + ) + .chain( + 1, + SpeakerHandler::CLUSTER.id, + SpeakerHandler::new(Dataver::new_rand(matter.rand())).adapt(), + ), ), + ), ) } @@ -171,6 +181,11 @@ impl SpeakerHandler { } } + /// Adapt the handler instance to the generic `rs-matter` `AsyncHandler` trait + pub const fn adapt(self) -> HandlerAsyncAdaptor { + HandlerAsyncAdaptor(self) + } + /// Update the state of the handler fn set_state(&self, state: PlaybackStateEnum, ctx: &InvokeContext<'_>) { let old_state = self.state.replace(state); diff --git a/examples/src/common/comm.rs b/examples/src/common/comm.rs deleted file mode 100644 index c6976c63..00000000 --- a/examples/src/common/comm.rs +++ /dev/null @@ -1,265 +0,0 @@ -/* - * - * Copyright (c) 2020-2022 Project CHIP Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -//! A module containing a "fake" Wifi Network Commissioning cluster - -use embassy_sync::blocking_mutex::raw::NoopRawMutex; - -use log::{error, info, warn}; - -use rs_matter::data_model::objects::{ - AttrDataEncoder, AttrDataWriter, AttrType, CmdDataEncoder, Dataver, Handler, InvokeContext, - NonBlockingHandler, ReadContext, -}; -use rs_matter::data_model::sdm::nw_commissioning::{ - AddWifiNetworkRequest, Attributes, Commands, ConnectNetworkRequest, ConnectNetworkResponse, - NetworkCommissioningStatus, NetworkConfigResponse, RemoveNetworkRequest, ReorderNetworkRequest, - ResponseCommands, ScanNetworksRequest, WIFI_CLUSTER, -}; -use rs_matter::error::{Error, ErrorCode}; -use rs_matter::interaction_model::core::IMStatusCode; -use rs_matter::interaction_model::messages::ib::Status; -use rs_matter::tlv::{FromTLV, Octets, TLVWrite}; -use rs_matter::transport::exchange::Exchange; -use rs_matter::utils::sync::Notification; - -/// A _fake_ cluster implementing the Matter Network Commissioning Cluster -/// for managing WiFi networks. -/// -/// We only pretend to manage these for the purposes of the BT demo. -pub struct WifiNwCommCluster<'a> { - dataver: Dataver, - nw_setup_complete: &'a Notification, -} - -impl<'a> WifiNwCommCluster<'a> { - /// Create a new instance. - pub const fn new(dataver: Dataver, nw_setup_complete: &'a Notification) -> Self { - Self { - dataver, - nw_setup_complete, - } - } - - /// Read an attribute. - pub fn read( - &self, - ctx: &ReadContext<'_>, - encoder: AttrDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - let Some(mut writer) = encoder.with_dataver(self.dataver.get())? else { - return Ok(()); - }; - - let attr = ctx.attr(); - - if attr.is_system() { - return WIFI_CLUSTER.read(attr.attr_id, writer); - } - - match attr.attr_id.try_into()? { - Attributes::MaxNetworks => AttrType::::new().encode(writer, 1_u8), - Attributes::Networks => { - writer.start_array(&AttrDataWriter::TAG)?; - - writer.end_container()?; - writer.complete() - } - Attributes::ScanMaxTimeSecs => AttrType::new().encode(writer, 30_u8), - Attributes::ConnectMaxTimeSecs => AttrType::new().encode(writer, 60_u8), - Attributes::InterfaceEnabled => AttrType::new().encode(writer, true), - Attributes::LastNetworkingStatus => AttrType::new().encode(writer, 0_u8), - Attributes::LastNetworkID => AttrType::new().encode(writer, Octets("ssid".as_bytes())), - Attributes::LastConnectErrorValue => AttrType::new().encode(writer, 0), - } - } - - /// Invoke a command. - pub fn invoke( - &self, - ctx: &InvokeContext<'_>, - encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - let exchange = ctx.exchange(); - let cmd = ctx.cmd(); - let data = ctx.data(); - - match cmd.cmd_id.try_into()? { - Commands::ScanNetworks => { - info!("ScanNetworks"); - self.scan_networks(exchange, &ScanNetworksRequest::from_tlv(data)?, encoder)?; - } - Commands::AddOrUpdateWifiNetwork => { - info!("AddOrUpdateWifiNetwork"); - self.add_network(exchange, &AddWifiNetworkRequest::from_tlv(data)?, encoder)?; - } - Commands::RemoveNetwork => { - info!("RemoveNetwork"); - self.remove_network(exchange, &RemoveNetworkRequest::from_tlv(data)?, encoder)?; - } - Commands::ConnectNetwork => { - info!("ConnectNetwork"); - self.connect_network(exchange, &ConnectNetworkRequest::from_tlv(data)?, encoder)?; - } - Commands::ReorderNetwork => { - info!("ReorderNetwork"); - self.reorder_network(exchange, &ReorderNetworkRequest::from_tlv(data)?, encoder)?; - } - other => { - error!("{other:?} (not supported)"); - Err(ErrorCode::CommandNotFound)? - } - } - - self.dataver.changed(); - - Ok(()) - } - - fn scan_networks( - &self, - _exchange: &Exchange<'_>, - _req: &ScanNetworksRequest<'_>, - encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - let writer = encoder.with_command(ResponseCommands::ScanNetworksResponse as _)?; - - warn!("Scan network not supported"); - - writer.set(Status::new(IMStatusCode::Busy, 0))?; - - Ok(()) - } - - fn add_network( - &self, - _exchange: &Exchange<'_>, - req: &AddWifiNetworkRequest<'_>, - encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - let writer = encoder.with_command(ResponseCommands::NetworkConfigResponse as _)?; - - info!( - "Updated network with SSID {}", - core::str::from_utf8(req.ssid.0).unwrap() - ); - - writer.set(NetworkConfigResponse { - status: NetworkCommissioningStatus::Success, - debug_text: None, - network_index: Some(0 as _), - })?; - - Ok(()) - } - - fn remove_network( - &self, - _exchange: &Exchange<'_>, - req: &RemoveNetworkRequest<'_>, - encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - let writer = encoder.with_command(ResponseCommands::NetworkConfigResponse as _)?; - - info!( - "Removed network with SSID {}", - core::str::from_utf8(req.network_id.0).unwrap() - ); - - writer.set(NetworkConfigResponse { - status: NetworkCommissioningStatus::Success, - debug_text: None, - network_index: Some(0 as _), - })?; - - Ok(()) - } - - fn connect_network( - &self, - _exchange: &Exchange<'_>, - req: &ConnectNetworkRequest<'_>, - encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - // Non-concurrent commissioning scenario - // (i.e. only BLE is active, and the device BLE+Wifi co-exist - // driver is not running, or does not even exist) - - info!( - "Request to connect to network with SSID {} received", - core::str::from_utf8(req.network_id.0).unwrap(), - ); - - let writer = encoder.with_command(ResponseCommands::ConnectNetworkResponse as _)?; - - // As per spec, return success even though though whether we'll be able to connect to the network - // will become apparent later, once we switch to Wifi - writer.set(ConnectNetworkResponse { - status: NetworkCommissioningStatus::Success, - debug_text: None, - error_value: 0, - })?; - - // Wifi setup is complete, UDP stack can run now - self.nw_setup_complete.notify(); - - Ok(()) - } - - fn reorder_network( - &self, - _exchange: &Exchange<'_>, - req: &ReorderNetworkRequest<'_>, - encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - let writer = encoder.with_command(ResponseCommands::NetworkConfigResponse as _)?; - - info!( - "Network with SSID {} reordered to index {}", - core::str::from_utf8(req.network_id.0).unwrap(), - req.index - ); - - writer.set(NetworkConfigResponse { - status: NetworkCommissioningStatus::Success, - debug_text: None, - network_index: Some(req.index as _), - })?; - - Ok(()) - } -} - -impl Handler for WifiNwCommCluster<'_> { - fn read( - &self, - ctx: &ReadContext<'_>, - encoder: AttrDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - WifiNwCommCluster::read(self, ctx, encoder) - } - - fn invoke( - &self, - ctx: &InvokeContext<'_>, - encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - WifiNwCommCluster::invoke(self, ctx, encoder) - } -} - -impl NonBlockingHandler for WifiNwCommCluster<'_> {} diff --git a/examples/src/common/mdns.rs b/examples/src/common/mdns.rs index 07222332..605dcdd2 100644 --- a/examples/src/common/mdns.rs +++ b/examples/src/common/mdns.rs @@ -17,10 +17,6 @@ //! A module containing the mDNS code used in the examples -use std::net::UdpSocket; - -use log::info; - use rs_matter::core::Matter; use rs_matter::error::Error; @@ -34,6 +30,10 @@ pub async fn run_mdns(_matter: &Matter<'_>) -> Result<(), Error> { #[cfg(not(any(target_os = "macos", all(feature = "zeroconf", target_os = "linux"))))] #[allow(unused)] pub async fn run_mdns(matter: &Matter<'_>) -> Result<(), Error> { + use std::net::UdpSocket; + + use log::info; + use rs_matter::transport::network::{Ipv4Addr, Ipv6Addr}; // NOTE: diff --git a/rs-matter-data-model/benches/client-clusters-criterion.rs b/rs-matter-data-model/benches/client-clusters-criterion.rs index b1b5ba1d..42f1d92a 100644 --- a/rs-matter-data-model/benches/client-clusters-criterion.rs +++ b/rs-matter-data-model/benches/client-clusters-criterion.rs @@ -10,7 +10,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { GraphicalReportHandler::new() .render_report(&mut buf, &e) .unwrap(); - eprintln!("\n{}", buf); + eprintln!("\n{buf}"); } }) }); diff --git a/rs-matter-data-model/benches/client-clusters.rs b/rs-matter-data-model/benches/client-clusters.rs index 7d0b598e..795d3e1e 100644 --- a/rs-matter-data-model/benches/client-clusters.rs +++ b/rs-matter-data-model/benches/client-clusters.rs @@ -15,6 +15,6 @@ fn parse_client_clusters() { GraphicalReportHandler::new() .render_report(&mut buf, &e) .unwrap(); - eprintln!("\n{}", buf); + eprintln!("\n{buf}"); } } diff --git a/rs-matter-macros-impl/src/idl.rs b/rs-matter-macros-impl/src/idl.rs index 5b2184a6..66add160 100644 --- a/rs-matter-macros-impl/src/idl.rs +++ b/rs-matter-macros-impl/src/idl.rs @@ -159,7 +159,7 @@ mod tests { #[test] fn test_unit_testing_cluster() { - let idl = parse_idl(&CSA_STANDARD_CLUSTERS_IDL); + let idl = parse_idl(CSA_STANDARD_CLUSTERS_IDL); let cluster = get_cluster_named(&idl, "UnitTesting").expect("Cluster exists"); let context = IdlGenerateContext::new("rs_matter_crate"); diff --git a/rs-matter/Cargo.toml b/rs-matter/Cargo.toml index bf568303..eec2f6a4 100644 --- a/rs-matter/Cargo.toml +++ b/rs-matter/Cargo.toml @@ -76,6 +76,10 @@ rand = { version = "0.8", optional = true, default-features = false, features = async-io = { version = "2", optional = true, default-features = false } async-compat = { version = "0.2", optional = true, default-features = false } +[target.'cfg(all(unix, not(target_os = "espidf")))'.dependencies] +bitflags = "2" +nix = { version = "0.27", features = ["net"] } + [target.'cfg(target_os = "macos")'.dependencies] astro-dnssd = { version = "0.3" } diff --git a/rs-matter/src/acl.rs b/rs-matter/src/acl.rs index 35bb7b44..d0086fef 100644 --- a/rs-matter/src/acl.rs +++ b/rs-matter/src/acl.rs @@ -15,18 +15,23 @@ * limitations under the License. */ +//! This module contains the implementation of the `rs-matter` Access Control List (ACL) + use core::{fmt::Display, num::NonZeroU8}; use num_derive::FromPrimitive; use crate::data_model::objects::{Access, ClusterId, EndptId, Privilege}; +use crate::data_model::system_model::acl::{ + AccessControlEntryAuthModeEnum, AccessControlEntryStruct, AccessControlEntryStructBuilder, +}; use crate::error::{Error, ErrorCode}; use crate::fabric::FabricMgr; use crate::interaction_model::messages::GenericPath; -use crate::tlv::{EitherIter, FromTLV, Nullable, TLVElement, TLVTag, TLVWrite, ToTLV, TLV}; +use crate::tlv::{FromTLV, Nullable, TLVBuilderParent, TLVElement, TLVTag, TLVWrite, ToTLV, TLV}; use crate::transport::session::{Session, SessionMode, MAX_CAT_IDS_PER_NOC}; use crate::utils::cell::RefCell; -use crate::utils::init::{init, Init}; +use crate::utils::init::{init, Init, IntoFallibleInit}; use crate::utils::storage::Vec; /// Max subjects per ACL entry @@ -41,14 +46,18 @@ pub const TARGETS_PER_ENTRY: usize = 3; // TODO: Make this configurable via a cargo feature pub const ENTRIES_PER_FABRIC: usize = 3; +/// An enum modeling the different authentication modes // TODO: Check if this and the SessionMode can be combined into some generic data structure #[derive(FromPrimitive, Copy, Clone, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] pub enum AuthMode { - Pase = 1, - Case = 2, - Group = 3, - Invalid = 4, + /// PASE authentication + Pase = AccessControlEntryAuthModeEnum::PASE as _, + /// CASE authentication + Case = AccessControlEntryAuthModeEnum::CASE as _, + /// Group authentication + Group = AccessControlEntryAuthModeEnum::Group as _, } impl FromTLV<'_> for AuthMode { @@ -56,24 +65,36 @@ impl FromTLV<'_> for AuthMode { where Self: Sized, { - num::FromPrimitive::from_u32(t.u32()?) - .filter(|a| *a != AuthMode::Invalid) - .ok_or_else(|| ErrorCode::Invalid.into()) + Ok(AccessControlEntryAuthModeEnum::from_tlv(t)?.into()) } } impl ToTLV for AuthMode { fn to_tlv(&self, tag: &TLVTag, mut tw: W) -> Result<(), Error> { - match self { - AuthMode::Invalid => Ok(()), - _ => tw.u8(tag, *self as u8), - } + AccessControlEntryAuthModeEnum::from(*self).to_tlv(tag, &mut tw) } fn tlv_iter(&self, tag: TLVTag) -> impl Iterator> { - match self { - AuthMode::Invalid => EitherIter::First(core::iter::empty()), - _ => EitherIter::Second(TLV::u8(tag, *self as u8).into_tlv_iter()), + TLV::u8(tag, AccessControlEntryAuthModeEnum::from(*self) as _).into_tlv_iter() + } +} + +impl From for AccessControlEntryAuthModeEnum { + fn from(value: AuthMode) -> Self { + match value { + AuthMode::Pase => AccessControlEntryAuthModeEnum::PASE, + AuthMode::Case => AccessControlEntryAuthModeEnum::CASE, + AuthMode::Group => AccessControlEntryAuthModeEnum::Group, + } + } +} + +impl From for AuthMode { + fn from(value: AccessControlEntryAuthModeEnum) -> Self { + match value { + AccessControlEntryAuthModeEnum::PASE => AuthMode::Pase, + AccessControlEntryAuthModeEnum::CASE => AuthMode::Case, + AccessControlEntryAuthModeEnum::Group => AuthMode::Group, } } } @@ -112,12 +133,15 @@ pub fn gen_noc_cat(id: u16, version: u16) -> u32 { pub struct AccessorSubjects([u64; MAX_ACCESSOR_SUBJECTS]); impl AccessorSubjects { + /// Create a new AccessorSubjects object + /// The first subject is the node id pub fn new(id: u64) -> Self { let mut a = Self(Default::default()); a.0[0] = id; a } + /// Add a CAT id to the AccessorSubjects pub fn add_catid(&mut self, subject: u32) -> Result<(), Error> { for (i, val) in self.0.iter().enumerate() { if *val == 0 { @@ -128,7 +152,7 @@ impl AccessorSubjects { Err(ErrorCode::NoSpace.into()) } - /// Match the match_subject with any of the current subjects + /// Match the acl_subject with any of the current subjects /// If a NOC CAT is specified, CAT aware matching is also performed pub fn matches(&self, acl_subject: u64) -> bool { for v in self.0.iter() { @@ -189,13 +213,14 @@ pub struct Accessor<'a> { pub fab_idx: u8, /// Accessor's subject: could be node-id, NoC CAT, group id subjects: AccessorSubjects, - /// The Authmode of this session - auth_mode: AuthMode, + /// The auth mode of this session. Might be `None` for plain-text sessions + auth_mode: Option, // TODO: Is this the right place for this though, or should we just use a global-acl-handle-get fabric_mgr: &'a RefCell, } impl<'a> Accessor<'a> { + /// Create a new Accessor object for the given session pub fn for_session(session: &Session, fabric_mgr: &'a RefCell) -> Self { match session.get_session_mode() { SessionMode::Case { @@ -208,25 +233,29 @@ impl<'a> Accessor<'a> { let _ = subject.add_catid(i); } } - Accessor::new(fab_idx.get(), subject, AuthMode::Case, fabric_mgr) + Accessor::new(fab_idx.get(), subject, Some(AuthMode::Case), fabric_mgr) } SessionMode::Pase { fab_idx } => Accessor::new( *fab_idx, AccessorSubjects::new(1), - AuthMode::Pase, + Some(AuthMode::Pase), fabric_mgr, ), - - SessionMode::PlainText => { - Accessor::new(0, AccessorSubjects::new(1), AuthMode::Invalid, fabric_mgr) - } + SessionMode::PlainText => Accessor::new(0, AccessorSubjects::new(1), None, fabric_mgr), } } + /// Create a new Accessor object + /// + /// # Arguments + /// - `fab_idx`: The fabric index of the accessor (0 means no fabric index) + /// - `subjects`: The subjects of the accessor + /// - `auth_mode`: The auth mode of the accessor + /// - `fabric_mgr`: The fabric manager pub const fn new( fab_idx: u8, subjects: AccessorSubjects, - auth_mode: AuthMode, + auth_mode: Option, fabric_mgr: &'a RefCell, ) -> Self { Self { @@ -237,15 +266,18 @@ impl<'a> Accessor<'a> { } } + /// Return the subjects of the accessor pub fn subjects(&self) -> &AccessorSubjects { &self.subjects } - pub fn auth_mode(&self) -> AuthMode { + /// Return the auth mode of the accessor + pub fn auth_mode(&self) -> Option { self.auth_mode } } +/// Access Descriptor Object #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct AccessDesc { @@ -260,12 +292,14 @@ pub struct AccessDesc { /// Access Request Object pub struct AccessReq<'a> { + /// The accessor requesting access accessor: &'a Accessor<'a>, + /// The object being accessed object: AccessDesc, } impl<'a> AccessReq<'a> { - /// Creates an access request object + /// Create an access request object /// /// An access request specifies the _accessor_ attempting to access _path_ /// with _operation_ @@ -280,10 +314,12 @@ impl<'a> AccessReq<'a> { } } + /// Return the accessor of the request pub fn accessor(&self) -> &Accessor { self.accessor } + /// Return the operation of the request pub fn operation(&self) -> Access { self.object.operation } @@ -296,7 +332,7 @@ impl<'a> AccessReq<'a> { self.object.target_perms = Some(perms); } - /// Checks if access is allowed + /// Check if access is allowed /// /// This checks all the ACL list to identify if any of the ACLs provides the /// _accessor_ the necessary privileges to access the target as per its @@ -306,16 +342,18 @@ impl<'a> AccessReq<'a> { } } +/// The target object #[derive(FromTLV, ToTLV, Clone, Debug, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Target { - cluster: Option, - endpoint: Option, - device_type: Option, + pub cluster: Option, + pub endpoint: Option, + pub device_type: Option, } impl Target { - pub fn new( + /// Create a new target object + pub const fn new( endpoint: Option, cluster: Option, device_type: Option, @@ -328,13 +366,18 @@ impl Target { } } +/// The ACL entry object #[derive(ToTLV, FromTLV, Clone, Debug, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[tlvargs(start = 1)] pub struct AclEntry { + /// The privilege of the entry privilege: Privilege, + /// The auth mode of the entry auth_mode: AuthMode, - subjects: Vec, + /// The subjects of the entry + subjects: Nullable>, + /// The targets of the entry targets: Nullable>, // TODO: Instead of the direct value, we should consider GlobalElements::FabricIndex // Note that this field will always be `Some(NN)` when the entry is persisted in storage, @@ -344,16 +387,23 @@ pub struct AclEntry { } impl AclEntry { - pub fn new(fab_idx: Option, privilege: Privilege, auth_mode: AuthMode) -> Self { + /// Create a new ACL entry object + pub const fn new( + fab_idx: Option, + privilege: Privilege, + auth_mode: AuthMode, + ) -> Self { Self { fab_idx, privilege, auth_mode, - subjects: Vec::new(), + subjects: Nullable::some(Vec::new()), targets: Nullable::some(Vec::new()), } } + /// Return an initializer for an ACL entry object + /// using the given fabric index, privilege and auth mode as input pub fn init( fab_idx: Option, privilege: Privilege, @@ -363,21 +413,127 @@ impl AclEntry { fab_idx, privilege, auth_mode, - subjects <- Vec::init(), + subjects <- Nullable::init_some(Vec::init()), targets <- Nullable::init_some(Vec::init()), }) } + /// Return an initializer for an ACL entry object + /// using the given fabric index and TLV entry struct as input + pub fn init_with<'a>( + fab_idx: NonZeroU8, + entry: &'a AccessControlEntryStruct<'a>, + ) -> impl Init + 'a { + Self::init(Some(fab_idx), Privilege::empty(), AuthMode::Pase) + .into_fallible() + .chain(|e| { + e.privilege = entry.privilege()?.into(); + e.auth_mode = entry.auth_mode()?.into(); + + if let Some(subjects) = entry.subjects()?.into_option() { + let esubjects = unwrap!(e.subjects.as_opt_mut()); + for subject in subjects { + let subject = subject?; + + esubjects.push(subject).map_err(|_| ErrorCode::NoSpace)?; + } + } else { + e.subjects.clear(); + } + + if let Some(targets) = entry.targets()?.into_option() { + let etargets = unwrap!(e.targets.as_opt_mut()); + for target in targets { + let target = target?; + + etargets + .push(Target::new( + target.endpoint()?.into_option(), + target.cluster()?.into_option(), + target.device_type()?.into_option(), + )) + .map_err(|_| ErrorCode::NoSpace)?; + } + } else { + e.targets.clear(); + } + + Ok(()) + }) + } + + /// Return the data of the ACL entry object + /// into the provided TLV builder + pub fn read_into( + &self, + fab_idx: NonZeroU8, + builder: AccessControlEntryStructBuilder

, + ) -> Result { + builder + .privilege(self.privilege.into())? + .auth_mode(self.auth_mode.into())? + .subjects()? + .with_non_null(self.subjects(), |subjects, mut builder| { + for subject in *subjects { + builder = builder.push(subject)?; + } + + builder.end() + })? + .targets()? + .with_non_null(self.targets(), |targets, mut builder| { + for target in *targets { + builder = builder + .push()? + .cluster(Nullable::new(target.cluster))? + .endpoint(Nullable::new(target.endpoint))? + .device_type(Nullable::new(target.device_type))? + .end()?; + } + + builder.end() + })? + .fabric_index(fab_idx.get())? + .end() + } + + /// Return the auth mode of the ACL entry + pub fn auth_mode(&self) -> AuthMode { + self.auth_mode + } + + /// Return the subjects of the ACL entry + pub fn subjects(&self) -> Nullable<&[u64]> { + Nullable::new(self.subjects.as_opt_ref().map(|v| v.as_slice())) + } + + /// Return the targets of the ACL entry + pub fn targets(&self) -> Nullable<&[Target]> { + Nullable::new(self.targets.as_opt_ref().map(|v| v.as_slice())) + } + + /// Check if the ACL entry allows access to the given accessor and object + pub fn allow(&self, req: &AccessReq) -> bool { + self.match_accessor(req.accessor) && self.match_access_desc(&req.object) + } + + /// Add a subject to the ACL entry pub fn add_subject(&mut self, subject: u64) -> Result<(), Error> { - self.subjects + if self.subjects.is_none() { + self.subjects.reinit(Nullable::init_some(Vec::init())); + } + + unwrap!(self.subjects.as_opt_mut()) .push(subject) .map_err(|_| ErrorCode::NoSpace.into()) } + /// Add a CAT id to the ACL entry pub fn add_subject_catid(&mut self, cat_id: u32) -> Result<(), Error> { self.add_subject(NOC_CAT_SUBJECT_PREFIX | cat_id as u64) } + /// Add a target to the ACL entry pub fn add_target(&mut self, target: Target) -> Result<(), Error> { if self.targets.is_none() { self.targets.reinit(Nullable::init_some(Vec::init())); @@ -388,27 +544,16 @@ impl AclEntry { .map_err(|_| ErrorCode::NoSpace.into()) } - pub fn auth_mode(&self) -> AuthMode { - self.auth_mode - } - fn match_accessor(&self, accessor: &Accessor) -> bool { - if self.auth_mode != accessor.auth_mode { + if Some(self.auth_mode) != accessor.auth_mode { return false; } - let mut allow = false; - let mut entries_exist = false; - for s in &self.subjects { - entries_exist = true; - if accessor.subjects.matches(*s) { - allow = true; - } - } - if !entries_exist { - // Subjects array empty implies allow for all subjects - allow = true; - } + let allow = self.subjects().as_opt_ref().is_none_or(|subjects| { + // Subjects array null or empty implies allow for all subjects + // Otherwise, check if the accessor's subject matches any of the ACL entry's subjects + subjects.is_empty() || subjects.iter().any(|s| accessor.subjects.matches(*s)) + }); // true if both are true allow @@ -419,25 +564,15 @@ impl AclEntry { } fn match_access_desc(&self, object: &AccessDesc) -> bool { - let mut allow = false; - let mut entries_exist = false; - match self.targets.as_opt_ref() { - None => allow = true, // Allow if targets are NULL - Some(targets) => { - for t in targets { - entries_exist = true; - if (t.endpoint.is_none() || t.endpoint == object.path.endpoint) + let allow = self.targets.as_opt_ref().is_none_or(|targets| { + // Targets array null or empty implies allow for all targets + // Otherwise, check if the target matches any of the ACL entry's targets + targets.is_empty() + || targets.iter().any(|t| { + (t.endpoint.is_none() || t.endpoint == object.path.endpoint) && (t.cluster.is_none() || t.cluster == object.path.cluster) - { - allow = true - } - } - } - } - if !entries_exist { - // Targets array empty implies allow for all targets - allow = true; - } + }) + }); if allow { // Check that the object's access allows this operation with this privilege @@ -450,10 +585,6 @@ impl AclEntry { false } } - - pub fn allow(&self, req: &AccessReq) -> bool { - self.match_accessor(req.accessor) && self.match_access_desc(&req.object) - } } #[cfg(test)] @@ -485,7 +616,7 @@ pub(crate) mod tests { fn test_basic_empty_subject_target() { let fm = RefCell::new(FabricMgr::new()); - let accessor = Accessor::new(0, AccessorSubjects::new(112233), AuthMode::Pase, &fm); + let accessor = Accessor::new(0, AccessorSubjects::new(112233), Some(AuthMode::Pase), &fm); let path = GenericPath::new(Some(1), Some(1234), None); let mut req_pase = AccessReq::new(&accessor, path, Access::READ); req_pase.set_target_perms(Access::RWVA); @@ -493,7 +624,7 @@ pub(crate) mod tests { // Always allow for PASE sessions assert!(req_pase.allow()); - let accessor = Accessor::new(2, AccessorSubjects::new(112233), AuthMode::Case, &fm); + let accessor = Accessor::new(2, AccessorSubjects::new(112233), Some(AuthMode::Case), &fm); let path = GenericPath::new(Some(1), Some(1234), None); let mut req = AccessReq::new(&accessor, path, Access::READ); req.set_target_perms(Access::RWVA); @@ -538,7 +669,7 @@ pub(crate) mod tests { .add_with_post_init(KeyPair::new(dummy_rand).unwrap(), |_| Ok(())) .unwrap(); - let accessor = Accessor::new(1, AccessorSubjects::new(112233), AuthMode::Case, &fm); + let accessor = Accessor::new(1, AccessorSubjects::new(112233), Some(AuthMode::Case), &fm); let path = GenericPath::new(Some(1), Some(1234), None); let mut req = AccessReq::new(&accessor, path, Access::READ); req.set_target_perms(Access::RWVA); @@ -573,7 +704,7 @@ pub(crate) mod tests { let mut subjects = AccessorSubjects::new(112233); subjects.add_catid(gen_noc_cat(allow_cat, v2)).unwrap(); - let accessor = Accessor::new(1, subjects, AuthMode::Case, &fm); + let accessor = Accessor::new(1, subjects, Some(AuthMode::Case), &fm); let path = GenericPath::new(Some(1), Some(1234), None); let mut req = AccessReq::new(&accessor, path, Access::READ); req.set_target_perms(Access::RWVA); @@ -615,7 +746,7 @@ pub(crate) mod tests { let mut subjects = AccessorSubjects::new(112233); subjects.add_catid(gen_noc_cat(allow_cat, v3)).unwrap(); - let accessor = Accessor::new(1, subjects, AuthMode::Case, &fm); + let accessor = Accessor::new(1, subjects, Some(AuthMode::Case), &fm); let path = GenericPath::new(Some(1), Some(1234), None); let mut req = AccessReq::new(&accessor, path, Access::READ); req.set_target_perms(Access::RWVA); @@ -643,7 +774,7 @@ pub(crate) mod tests { .add_with_post_init(KeyPair::new(dummy_rand).unwrap(), |_| Ok(())) .unwrap(); - let accessor = Accessor::new(1, AccessorSubjects::new(112233), AuthMode::Case, &fm); + let accessor = Accessor::new(1, AccessorSubjects::new(112233), Some(AuthMode::Case), &fm); let path = GenericPath::new(Some(1), Some(1234), None); let mut req = AccessReq::new(&accessor, path, Access::READ); req.set_target_perms(Access::RWVA); @@ -709,7 +840,7 @@ pub(crate) mod tests { .add_with_post_init(KeyPair::new(dummy_rand).unwrap(), |_| Ok(())) .unwrap(); - let accessor = Accessor::new(1, AccessorSubjects::new(112233), AuthMode::Case, &fm); + let accessor = Accessor::new(1, AccessorSubjects::new(112233), Some(AuthMode::Case), &fm); let path = GenericPath::new(Some(1), Some(1234), None); // Create an Exact Match ACL with View privilege @@ -760,10 +891,10 @@ pub(crate) mod tests { .unwrap(); let path = GenericPath::new(Some(1), Some(1234), None); - let accessor2 = Accessor::new(1, AccessorSubjects::new(112233), AuthMode::Case, &fm); + let accessor2 = Accessor::new(1, AccessorSubjects::new(112233), Some(AuthMode::Case), &fm); let mut req1 = AccessReq::new(&accessor2, path.clone(), Access::READ); req1.set_target_perms(Access::RWVA); - let accessor3 = Accessor::new(2, AccessorSubjects::new(112233), AuthMode::Case, &fm); + let accessor3 = Accessor::new(2, AccessorSubjects::new(112233), Some(AuthMode::Case), &fm); let mut req2 = AccessReq::new(&accessor3, path, Access::READ); req2.set_target_perms(Access::RWVA); diff --git a/rs-matter/src/core.rs b/rs-matter/src/core.rs index edb2667d..f6bfb14b 100644 --- a/rs-matter/src/core.rs +++ b/rs-matter/src/core.rs @@ -17,12 +17,11 @@ use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use crate::data_model::{ - basic_info::{BasicInfoConfig, BasicInfoSettings}, - sdm::{dev_att::DevAttDataFetcher, failsafe::FailSafe}, -}; -use crate::error::*; +use crate::data_model::basic_info::{BasicInfoConfig, BasicInfoSettings}; +use crate::data_model::sdm::dev_att::DevAttDataFetcher; +use crate::error::{Error, ErrorCode}; use crate::fabric::FabricMgr; +use crate::failsafe::FailSafe; use crate::mdns::MdnsService; use crate::pairing::{print_pairing_code_and_qr, DiscoveryCapabilities}; use crate::secure_channel::pake::PaseMgr; diff --git a/rs-matter/src/data_model/basic_info.rs b/rs-matter/src/data_model/basic_info.rs index 8867dc70..e88397c4 100644 --- a/rs-matter/src/data_model/basic_info.rs +++ b/rs-matter/src/data_model/basic_info.rs @@ -15,6 +15,8 @@ * limitations under the License. */ +//! This module contains the implementation of the Basic Information cluster and its handler. + use core::str::FromStr; use crate::error::{Error, ErrorCode}; @@ -126,14 +128,18 @@ impl Default for BasicInfoSettings { } } -#[derive(Clone)] +/// The system implementation of a handler for the Basic Information Matter cluster. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct BasicInfoHandler(Dataver); impl BasicInfoHandler { + /// Create a new instance of `BasicInfoHandler` with the given `Dataver` pub fn new(dataver: Dataver) -> Self { Self(dataver) } + /// Adapt the handler instance to the generic `rs-matter` `Handler` trait pub const fn adapt(self) -> HandlerAdaptor { HandlerAdaptor(self) } diff --git a/rs-matter/src/data_model/clusters.rs b/rs-matter/src/data_model/clusters.rs index aab0f3a5..1b693599 100644 --- a/rs-matter/src/data_model/clusters.rs +++ b/rs-matter/src/data_model/clusters.rs @@ -27,6 +27,7 @@ crate::import!( BasicInformation, Descriptor, EthernetNetworkDiagnostics, + GeneralDiagnostics, GeneralCommissioning, GroupKeyManagement, NetworkCommissioning, @@ -34,5 +35,5 @@ crate::import!( OperationalCredentials, ThreadNetworkDiagnostics, UnitTesting, - WifiNetworkDiagnostics, + WiFiNetworkDiagnostics, ); diff --git a/rs-matter/src/data_model/core.rs b/rs-matter/src/data_model/core.rs index 466b51c5..0a1f7f45 100644 --- a/rs-matter/src/data_model/core.rs +++ b/rs-matter/src/data_model/core.rs @@ -82,7 +82,8 @@ where /// * `subscriptions` - a reference to a `Subscriptions` struct which is used for managing subscriptions. `N` designates the maximum /// number of subscriptions that can be managed by this handler. /// * `handler` - an instance of type `T` which implements the `DataModelHandler` trait. This instance is used for interacting with the underlying - /// clusters of the data model. + /// clusters of the data model. Note that the expectations is for the user to provide a handler that handles the Matter system clusters + /// as well (Endpoint 0), possibly by decorating her own clusters with the `rs_matter::data_model::root_endpoint::with_` methods #[inline(always)] pub const fn new(buffers: &'a B, subscriptions: &'a Subscriptions, handler: T) -> Self { Self { diff --git a/rs-matter/src/data_model/device_types.rs b/rs-matter/src/data_model/device_types.rs index 9fd56586..14023738 100644 --- a/rs-matter/src/data_model/device_types.rs +++ b/rs-matter/src/data_model/device_types.rs @@ -31,3 +31,14 @@ pub const DEV_TYPE_SMART_SPEAKER: DeviceType = DeviceType { dtype: 0x0022, drev: 2, }; + +/// A macro to generate the devices for an endpoint. +#[allow(unused_macros)] +#[macro_export] +macro_rules! devices { + ($($device:expr $(,)?)*) => { + &[ + $($device,)* + ] + } +} diff --git a/rs-matter/src/data_model/mod.rs b/rs-matter/src/data_model/mod.rs index c9f2d27a..530357da 100644 --- a/rs-matter/src/data_model/mod.rs +++ b/rs-matter/src/data_model/mod.rs @@ -19,6 +19,7 @@ pub mod basic_info; mod clusters; pub mod core; pub mod device_types; +pub mod networks; pub mod objects; pub mod on_off; pub mod root_endpoint; diff --git a/rs-matter/src/data_model/networks.rs b/rs-matter/src/data_model/networks.rs new file mode 100644 index 00000000..09f9d48f --- /dev/null +++ b/rs-matter/src/data_model/networks.rs @@ -0,0 +1,38 @@ +/* + * + * Copyright (c) 2020-2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! A module containing various types for managing Ethernet, Thread and Wifi networks. + +pub mod eth; +#[cfg(all(unix, feature = "os", not(target_os = "espidf")))] +pub mod unix; +pub mod wireless; + +/// A generic trait for network change notifications. +pub trait NetChangeNotif { + /// Wait until a change occurs. + async fn wait_changed(&self); +} + +impl NetChangeNotif for &T +where + T: NetChangeNotif, +{ + async fn wait_changed(&self) { + (*self).wait_changed().await + } +} diff --git a/rs-matter/src/data_model/networks/eth.rs b/rs-matter/src/data_model/networks/eth.rs new file mode 100644 index 00000000..7e6b74de --- /dev/null +++ b/rs-matter/src/data_model/networks/eth.rs @@ -0,0 +1,130 @@ +/* + * + * Copyright (c) 2020-2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! This module contains the `Networks` trait implementation and the `NetCtl` trait implementation for Ethernet. + +use crate::data_model::sdm::net_comm::{ + self, NetCtl, NetCtlError, NetCtlStatus, NetworkCommissioningStatusEnum, NetworkScanInfo, + NetworkType, NetworksError, +}; +use crate::error::{Error, ErrorCode}; + +use crate::data_model::sdm::net_comm::WirelessCreds; + +/// A fixed `Networks` trait implementation for Ethernet. +/// +/// Ethernet does not need to manage networks, so it always reports 1 network +/// and returns an error when trying to add or update networks. +pub struct EthNetwork<'a> { + network_id: &'a str, +} + +impl<'a> EthNetwork<'a> { + /// Creates a new `EthNetwork` instance. + pub const fn new(network_id: &'a str) -> Self { + Self { network_id } + } +} + +impl net_comm::Networks for EthNetwork<'_> { + fn max_networks(&self) -> Result { + Ok(1) + } + + fn networks( + &self, + f: &mut dyn FnMut(&net_comm::NetworkInfo) -> Result<(), Error>, + ) -> Result<(), Error> { + f(&net_comm::NetworkInfo { + network_id: self.network_id.as_bytes(), + connected: false, // TODO + }) + } + + fn creds( + &self, + _network_id: &[u8], + _f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>, + ) -> Result { + Err(NetworksError::Other(ErrorCode::InvalidAction.into())) + } + + fn next_creds( + &self, + _last_network_id: Option<&[u8]>, + _f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>, + ) -> Result { + Err(ErrorCode::InvalidAction.into()) + } + + fn enabled(&self) -> Result { + Ok(true) + } + + fn set_enabled(&self, _enabled: bool) -> Result<(), Error> { + Ok(()) + } + + fn add_or_update(&self, _creds: &WirelessCreds<'_>) -> Result { + Err(NetworksError::Other(ErrorCode::InvalidAction.into())) + } + + fn reorder(&self, _index: u8, _network_id: &[u8]) -> Result { + Err(NetworksError::Other(ErrorCode::InvalidAction.into())) + } + + fn remove(&self, _network_id: &[u8]) -> Result { + Err(NetworksError::Other(ErrorCode::InvalidAction.into())) + } +} + +/// A `net_comm::NetCtl` implementation for Ethernet that errors out on all methods. +pub struct EthNetCtl; + +impl NetCtl for EthNetCtl { + fn net_type(&self) -> NetworkType { + NetworkType::Ethernet + } + + async fn scan(&self, _network: Option<&[u8]>, _f: F) -> Result<(), NetCtlError> + where + F: FnMut(&NetworkScanInfo) -> Result<(), Error>, + { + Err(NetCtlError::Other(ErrorCode::InvalidAction.into())) + } + + async fn connect(&self, _creds: &WirelessCreds<'_>) -> Result<(), NetCtlError> { + Err(NetCtlError::Other(ErrorCode::InvalidAction.into())) + } +} + +impl NetCtlStatus for EthNetCtl { + fn last_networking_status(&self) -> Result, Error> { + Ok(None) + } + + fn last_network_id(&self, f: F) -> Result + where + F: FnOnce(Option<&[u8]>) -> Result, + { + f(None) + } + + fn last_connect_error_value(&self) -> Result, Error> { + Ok(None) + } +} diff --git a/rs-matter/src/data_model/networks/unix.rs b/rs-matter/src/data_model/networks/unix.rs new file mode 100644 index 00000000..ef0e1efa --- /dev/null +++ b/rs-matter/src/data_model/networks/unix.rs @@ -0,0 +1,138 @@ +/* + * + * Copyright (c) 2020-2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! A module containing `NetifDiag` and `NetChangeNotif` implementations for Unix-like systems. + +use core::net::{Ipv4Addr, Ipv6Addr}; + +use alloc::string::String; +use alloc::vec::Vec; + +use nix::ifaddrs::InterfaceAddress; +use nix::net::if_::InterfaceFlags; + +use crate::data_model::sdm::gen_diag::{InterfaceTypeEnum, NetifDiag, NetifInfo}; +use crate::error::{Error, ErrorCode}; + +use super::NetChangeNotif; + +/// UnixNetifs is a type for getting all network interfaces +/// +/// A simple implementation of the `NetifDiag` trait. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct UnixNetifs; + +impl UnixNetifs { + /// Get all network interfaces + pub fn get(&self) -> Result, Error> { + let mut netifs: Vec = Vec::new(); + + for ia in nix::ifaddrs::getifaddrs().map_err(|_| ErrorCode::NoNetworkInterface)? { + if let Some(netif) = netifs + .iter_mut() + .find(|netif| netif.name == ia.interface_name) + { + netif.load(&ia)?; + } else { + let mut netif = UnixNetif { + name: String::new(), + hw_addr: [0; 8], + ipv4addrs: Vec::new(), + ipv6addrs: Vec::new(), + operational: false, + }; + + netif.load(&ia)?; + + netifs.push(netif); + } + } + + Ok(netifs) + } +} + +impl NetifDiag for UnixNetifs { + fn netifs(&self, f: &mut dyn FnMut(&NetifInfo) -> Result<(), Error>) -> Result<(), Error> { + for netif in self.get()? { + f(&netif.to_netif_info())?; + } + + Ok(()) + } +} + +impl NetChangeNotif for UnixNetifs { + async fn wait_changed(&self) { + core::future::pending().await // TODO + } +} + +/// A type for representing one network interface +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct UnixNetif { + /// Interface name + pub name: String, + /// Hardware address + pub hw_addr: [u8; 8], + /// IPv4 addresses + pub ipv4addrs: Vec, + /// IPv6 addresses + pub ipv6addrs: Vec, + /// Operational status + pub operational: bool, +} + +impl UnixNetif { + /// Convert to `NetifInfo` + pub fn to_netif_info(&self) -> NetifInfo<'_> { + NetifInfo { + name: &self.name, + operational: self.operational, + offprem_svc_reachable_ipv4: None, + offprem_svc_reachable_ipv6: None, + hw_addr: &self.hw_addr, + ipv4_addrs: &self.ipv4addrs, + ipv6_addrs: &self.ipv6addrs, + netif_type: InterfaceTypeEnum::Unspecified, // TODO + } + } + + /// Augment the information of the network interface with + /// the provided `InterfaceAddress`. + fn load(&mut self, ia: &InterfaceAddress) -> Result<(), Error> { + self.name = ia.interface_name.clone(); + self.operational |= ia.flags.contains(InterfaceFlags::IFF_RUNNING); + + if let Some(address) = ia.address.as_ref() { + if let Some(link_addr) = address.as_link_addr() { + if let Some(addr) = link_addr.addr() { + self.hw_addr[..6].copy_from_slice(&addr); + self.hw_addr[6..].fill(0); + } + } else if let Some(ipv6_addr) = address.as_sockaddr_in6() { + self.ipv6addrs.push(ipv6_addr.ip()); + } else if let Some(ipv4_addr) = address.as_sockaddr_in() { + self.ipv4addrs.push(ipv4_addr.ip().into()); + } + } + + Ok(()) + } +} diff --git a/rs-matter/src/data_model/networks/wireless.rs b/rs-matter/src/data_model/networks/wireless.rs new file mode 100644 index 00000000..14c1ea43 --- /dev/null +++ b/rs-matter/src/data_model/networks/wireless.rs @@ -0,0 +1,1079 @@ +/* + * + * Copyright (c) 2020-2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! A module containing various types for managing Thread and Wifi networks. + +use core::borrow::Borrow; +use core::fmt::{Debug, Display}; + +use embassy_sync::blocking_mutex::raw::RawMutex; + +use crate::data_model::sdm::net_comm::{ + self, NetCtlError, NetworkCommissioningStatusEnum, NetworkType, NetworksError, + ThreadCapabilitiesBitmap, WirelessCreds, +}; +use crate::data_model::sdm::{thread_diag, wifi_diag}; +use crate::error::{Error, ErrorCode}; +use crate::fmt::Bytes; +use crate::tlv::{FromTLV, TLVElement, TLVTag, ToTLV}; +use crate::transport::network::btp::{Btp, BtpContext, GattPeripheral}; +use crate::utils::cell::RefCell; +use crate::utils::init::{init, Init}; +use crate::utils::storage::{Vec, WriteBuf}; +use crate::utils::sync::blocking::{self, Mutex}; +use crate::utils::sync::Notification; + +use super::NetChangeNotif; + +pub use mgr::*; +pub use thread::*; +pub use wifi::*; + +mod mgr; +mod thread; +mod wifi; + +/// The maximum length of a wireless network ID. +/// Coincides with the SSID maximum length because +pub const MAX_WIRELESS_NETWORK_ID_LEN: usize = 32; + +/// A type alias for representing an owned ID of a wireless (Thread or Wifi) network. +/// Both Thread and Wifi networks use the same ID type which is just an octet string. +/// +/// For Thread networks, this is the Extended PAN ID (`u64` as 8 bytes, network order). +/// For Wifi networks, this is the SSID (`u8` array of max length 32 bytes). +pub type OwnedWirelessNetworkId = Vec; + +/// A trait representing the credentials of a wireless network (Wifi or Thread). +/// +/// The trait has only two implementations: `Wifi` and `Thread`. +pub trait WirelessNetwork: for<'a> FromTLV<'a> + ToTLV { + /// Return the network ID + /// + /// For Wifi networks, this is the SSID + /// For Thread networks, this is the Extended PAN ID (`u64` as 8 bytes, network order) + fn id(&self) -> &[u8]; + + /// Return an in-place initializer for the type + /// + /// # Arguments + /// - `creds`: The credentials of the network with which to initialize the type + fn init_from<'a>(creds: &'a WirelessCreds<'a>) -> impl Init + 'a; + + /// Update the credentials of the network + /// + /// # Arguments + /// - `creds`: The new credentials to set + fn update(&mut self, creds: &WirelessCreds<'_>) -> Result<(), Error>; + + /// Return the credentials of the network + fn creds(&self) -> WirelessCreds<'_>; + + /// Return a displayable representation of the network + #[cfg(not(feature = "defmt"))] + fn display(&self) -> impl Display { + Self::display_id(self.id()) + } + + /// Return a displayable representation of the network + #[cfg(feature = "defmt")] + fn display(&self) -> impl Display + defmt::Format { + Self::display_id(self.id()) + } + + /// Return a displayable representation of the provided network ID + #[cfg(not(feature = "defmt"))] + fn display_id(id: &[u8]) -> impl Display; + + /// Return a displayable representation of the provided network ID + #[cfg(feature = "defmt")] + fn display_id(id: &[u8]) -> impl Display + defmt::Format; +} + +/// A fixed-size storage for wireless networks credentials. +pub struct WirelessNetworks +where + M: RawMutex, +{ + state: Mutex>>, + state_changed: Notification, + persist_state_changed: Notification, +} + +impl WirelessNetworks +where + M: RawMutex, + T: WirelessNetwork, +{ + /// Create a new instance. + pub const fn new() -> Self { + Self { + state: Mutex::new(RefCell::new(WirelessNetworksStore::new())), + state_changed: Notification::new(), + persist_state_changed: Notification::new(), + } + } + + /// Return an in-place initializer for the struct. + pub fn init() -> impl Init { + init!(Self { + state <- Mutex::init(RefCell::init(WirelessNetworksStore::init())), + state_changed: Notification::new(), + persist_state_changed: Notification::new(), + }) + } + + /// Reset the state. + pub fn reset(&self) { + self.state.lock(|state| state.borrow_mut().reset()); + } + + /// Load the state from a byte slice. + /// + /// # Arguments + /// - `data`: The byte slice to load the state from + pub fn load(&self, data: &[u8]) -> Result<(), Error> { + self.state.lock(|state| state.borrow_mut().load(data)) + } + + /// Store the state into a byte slice. + /// + /// # Arguments + /// - `buf`: The byte slice to store the state into + /// + /// Returns `Ok(None)` if the state has not changed, `Ok(Some(data))` if the state has changed + /// where `data` is the sub-slice of the buffer that contains the data to be persisted + pub fn store<'a>(&self, buf: &'a mut [u8]) -> Result, Error> { + self.state.lock(|state| state.borrow_mut().store(buf)) + } + + /// Return `true` if the state has changed. + pub fn changed(&self) -> bool { + self.state.lock(|state| state.borrow().changed) + } + + /// Wait for the state to change. + pub async fn wait_state_changed(&self) { + loop { + if self.state.lock(|state| state.borrow().changed) { + break; + } + + self.state_changed.wait().await; + } + } + + /// Wait for the state to be changed in a way that requires persisting. + pub async fn wait_persist(&self) { + loop { + if self.state.lock(|state| state.borrow().changed) { + break; + } + + self.persist_state_changed.wait().await; + } + } + + /// Iterate over the registered network credentials + /// + /// # Arguments + /// - `f`: A closure that will be called for each network registered in the storage + pub fn networks(&self, f: F) -> Result<(), Error> + where + F: FnMut(&T) -> Result<(), Error>, + { + self.state.lock(|state| state.borrow().networks(f)) + } + + /// Get the credentials of a network by its ID + /// + /// # Arguments + /// - `network_id`: The ID of the network to get + /// - `f`: A closure that will be called with the credentials of the network, if the network exists + /// + /// Returns the index of the network in the storage if the network exists, `NetworkError::NetworkIdNotFound` otherwise + pub fn network(&self, network_id: &[u8], f: F) -> Result + where + F: FnOnce(&T) -> Result<(), Error>, + { + self.state + .lock(|state| state.borrow().network(network_id, f)) + } + + /// Get the next network credentials after the one with the given ID + /// + /// # Arguments + /// - `after_network_id`: The ID of the network to get the next one after. + /// If no network with the provided network ID exists, the first network in the storage will be returned. + pub fn next_network(&self, after_network_id: Option<&[u8]>, f: F) -> Result + where + F: FnOnce(&T) -> Result<(), Error>, + { + self.state + .lock(|state| state.borrow_mut().next_network(after_network_id, f)) + } + + /// Add or update a network in the storage + /// + /// # Arguments + /// - `network_id`: The ID of the network to add or update + /// - `add`: An in-place initializer for the network to add. The initializer will be used only if a network with the provided + /// network ID does not exist in the storage + /// - `update`: A closure that will be called with the network to update. The closure will be called only if a network with the provided + /// network ID exists in the storage + pub fn add_or_update( + &self, + network_id: &[u8], + add: A, + update: U, + ) -> Result + where + A: Init, + U: FnOnce(&mut T) -> Result<(), Error>, + { + self.state.lock(|state| { + let index = state.borrow_mut().add_or_update(network_id, add, update)?; + + self.state_changed.notify(); + self.persist_state_changed.notify(); + + Ok(index) + }) + } + + /// Reorder a network in the storage + /// + /// # Arguments + /// - `index`: The new index of the network + /// - `network_id`: The ID of the network to reorder + /// + /// Returns the new index of the network in the storage, if a network with the provided ID exists + /// or `NetworkError::NetworkIdNotFound` otherwise + pub fn reorder(&self, index: u8, network_id: &[u8]) -> Result { + self.state.lock(|state| { + let index = state.borrow_mut().reorder(index, network_id)?; + + self.state_changed.notify(); + self.persist_state_changed.notify(); + + Ok(index) + }) + } + + /// Remove a network from the storage + /// + /// # Arguments + /// - `network_id`: The ID of the network to remove + /// + /// Returns the index of the network in the storage if the network exists and was removed, `NetworkError::NetworkIdNotFound` otherwise + pub fn remove(&self, network_id: &[u8]) -> Result { + self.state.lock(|state| { + let index = state.borrow_mut().remove(network_id)?; + + self.state_changed.notify(); + self.persist_state_changed.notify(); + + Ok(index) + }) + } +} + +impl Default for WirelessNetworks +where + M: RawMutex, + T: WirelessNetwork + Clone, +{ + fn default() -> Self { + Self::new() + } +} + +impl net_comm::Networks for WirelessNetworks +where + M: RawMutex, + T: WirelessNetwork, +{ + fn max_networks(&self) -> Result { + Ok(N as _) + } + + fn networks( + &self, + f: &mut dyn FnMut(&net_comm::NetworkInfo) -> Result<(), Error>, + ) -> Result<(), Error> { + WirelessNetworks::networks(self, |network| { + let network_id = network.id(); + + let network_info = net_comm::NetworkInfo { + network_id, + connected: false, // TODO + }; + + f(&network_info) + }) + } + + fn creds( + &self, + network_id: &[u8], + f: &mut dyn FnMut(&net_comm::WirelessCreds) -> Result<(), Error>, + ) -> Result { + WirelessNetworks::network(self, network_id, |network| f(&network.creds())) + } + + fn next_creds( + &self, + last_network_id: Option<&[u8]>, + f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>, + ) -> Result { + WirelessNetworks::next_network(self, last_network_id, |network| f(&network.creds())) + } + + fn enabled(&self) -> Result { + Ok(true) + } + + fn set_enabled(&self, _enabled: bool) -> Result<(), Error> { + Ok(()) + } + + fn add_or_update( + &self, + creds: &net_comm::WirelessCreds<'_>, + ) -> Result { + WirelessNetworks::add_or_update(self, creds.id()?, T::init_from(creds), |network| { + network.update(creds) + }) + } + + fn reorder(&self, index: u8, network_id: &[u8]) -> Result { + WirelessNetworks::reorder(self, index, network_id) + } + + fn remove(&self, network_id: &[u8]) -> Result { + WirelessNetworks::remove(self, network_id) + } +} + +impl NetChangeNotif for WirelessNetworks +where + M: RawMutex, + T: WirelessNetwork, +{ + async fn wait_changed(&self) { + self.state_changed.wait().await; + } +} + +/// The internal unsychronized storage for network credentials. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct WirelessNetworksStore { + networks: crate::utils::storage::Vec, + changed: bool, +} + +impl WirelessNetworksStore +where + T: WirelessNetwork, +{ + const fn new() -> Self { + Self { + networks: crate::utils::storage::Vec::new(), + changed: false, + } + } + + fn init() -> impl Init { + init!(Self { + networks <- crate::utils::storage::Vec::init(), + changed: false, + }) + } + + fn reset(&mut self) { + self.networks.clear(); + self.changed = false; + } + + fn load(&mut self, data: &[u8]) -> Result<(), Error> { + let root = TLVElement::new(data); + + let iter = root.array()?.iter(); + + self.networks.clear(); + + for network in iter { + let network = network?; + + self.networks + .push_init(T::init_from_tlv(network), || ErrorCode::NoSpace.into())?; + } + + self.changed = false; + + Ok(()) + } + + fn store<'a>(&mut self, buf: &'a mut [u8]) -> Result, Error> { + if self.changed { + let mut wb = WriteBuf::new(buf); + + self.networks.to_tlv(&TLVTag::Anonymous, &mut wb)?; + + self.changed = false; + + let tail = wb.get_tail(); + + Ok(Some(&buf[..tail])) + } else { + Ok(None) + } + } + + fn networks(&self, mut f: F) -> Result<(), Error> + where + F: FnMut(&T) -> Result<(), Error>, + { + for network in self.networks.iter() { + f(network)?; + } + + Ok(()) + } + + fn network(&self, network_id: &[u8], f: F) -> Result + where + F: FnOnce(&T) -> Result<(), Error>, + { + let networks = self + .networks + .iter() + .enumerate() + .find(|(_, network)| network.id() == network_id); + + if let Some((index, network)) = networks { + f(network)?; + + Ok(index as _) + } else { + Err(NetworksError::NetworkIdNotFound) + } + } + + fn next_network(&mut self, last_network_id: Option<&[u8]>, f: F) -> Result + where + F: FnOnce(&T) -> Result<(), Error>, + { + if let Some(last_network_id) = last_network_id { + info!( + "Looking for network after the one with ID: {}", + T::display_id(last_network_id) + ); + + // Return the network positioned after the last one used + + let mut networks = self.networks.iter(); + + for network in &mut networks { + if network.id() == last_network_id { + break; + } + } + + let network = networks.next(); + if let Some(network) = network { + info!("Trying with next network - ID: {}", network.display()); + + f(network)?; + return Ok(true); + } + } + + // Wrap over + info!("Wrapping over"); + + if let Some(network) = self.networks.first() { + info!("Trying with first network - ID: {}", network.display()); + + f(network)?; + Ok(true) + } else { + info!("No networks available"); + Ok(false) + } + } + + fn add_or_update( + &mut self, + network_id: &[u8], + add: A, + update: U, + ) -> Result + where + A: Init, + U: FnOnce(&mut T) -> Result<(), Error>, + { + let unetwork = self + .networks + .iter_mut() + .enumerate() + .find(|(_, unetwork)| unetwork.id() == network_id); + + if let Some((index, unetwork)) = unetwork { + // Update + update(unetwork)?; + + self.changed = true; + + info!("Updated network with ID {}", unetwork.display()); + + Ok(index as _) + } else if self.networks.len() >= N { + warn!( + "Adding network with ID {} failed: too many", + T::display_id(network_id) + ); + + Err(NetworksError::BoundsExceeded) + } else { + // Add + self.networks.push_init(add, || ErrorCode::NoSpace.into())?; + + self.changed = true; + + info!("Added network with ID {}", T::display_id(network_id)); + + Ok((self.networks.len() - 1) as _) + } + } + + fn reorder(&mut self, index: u8, network_id: &[u8]) -> Result { + let cur_index = self + .networks + .iter() + .position(|conf| conf.id() == network_id); + + if let Some(cur_index) = cur_index { + // Found + + if index < self.networks.len() as u8 { + let conf = self.networks.remove(cur_index); + unwrap!(self.networks.insert(index as usize, conf).map_err(|_| ())); + + self.changed = true; + + info!( + "Network with ID {} reordered to index {}", + T::display_id(network_id), + index + ); + } else { + warn!( + "Reordering network with ID {} to index {} failed: out of range", + T::display_id(network_id), + index + ); + + Err(NetworksError::OutOfRange)?; + } + } else { + warn!("Network with ID {} not found", T::display_id(network_id)); + Err(NetworksError::NetworkIdNotFound)?; + } + + Ok(index) + } + + fn remove(&mut self, network_id: &[u8]) -> Result { + let index = self + .networks + .iter() + .position(|conf| conf.id() == network_id); + + if let Some(index) = index { + // Found + self.networks.remove(index); + + self.changed = true; + + info!("Removed network with ID {}", T::display_id(network_id)); + + Ok(index as _) + } else { + warn!("Network with ID {} not found", T::display_id(network_id)); + + Err(NetworksError::NetworkIdNotFound) + } + } +} + +/// An enum capable of displaying a network ID in a human-readable format. +#[derive(Debug)] +enum DisplayId<'a> { + Wifi(&'a [u8]), + Thread(&'a [u8]), +} + +impl Display for DisplayId<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + DisplayId::Wifi(id) => { + if let Ok(str) = core::str::from_utf8(id) { + write!(f, "Wifi SSID({})", str) + } else { + write!(f, "Wifi SSID({:?})", Bytes(id)) + } + } + DisplayId::Thread(id) => write!(f, "Thread ExtPanID({:?})", Bytes(id)), + } + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for DisplayId<'_> { + fn format(&self, fmt: defmt::Formatter) { + match self { + DisplayId::Wifi(id) => { + if let Ok(str) = core::str::from_utf8(id) { + defmt::write!(fmt, "Wifi SSID({})", str) + } else { + defmt::write!(fmt, "Wifi SSID({:?})", Bytes(id)) + } + } + DisplayId::Thread(id) => defmt::write!(fmt, "Thread ExtPanID({:?})", Bytes(id)), + } + } +} + +/// A no-op implementation of the `net_comm::NetCtl` trait suitable when non-concurrent provisioning over BTP is used. +/// +/// This implementation will throw `NetworkError::Other(ErrorCode::InvalidAction)` for the `scan` method +/// and will silently return `Ok(())` for the `connect` method, which is meeting the non-concurrent provisioning expectations. +pub struct NoopWirelessNetCtl(NetworkType); + +impl NoopWirelessNetCtl { + /// Create a new instance of `NoopWirelessNetCtl` for the provided network type. + /// + /// Note that it does not make any sense to use `NetworkType::Ethernet` here, as the Ethernet + /// network controller should return errors for both `scan` and `connect` methods. + /// + /// For Ethernet networks, use `EthNetctl` instead. + pub const fn new(net_type: NetworkType) -> Self { + Self(net_type) + } +} + +impl net_comm::NetCtl for NoopWirelessNetCtl { + fn net_type(&self) -> NetworkType { + self.0 + } + + async fn scan(&self, _network: Option<&[u8]>, _f: F) -> Result<(), NetCtlError> + where + F: FnOnce(&net_comm::NetworkScanInfo) -> Result<(), Error>, + { + Err(NetCtlError::Other(ErrorCode::InvalidAction.into())) + } + + async fn connect(&self, creds: &WirelessCreds<'_>) -> Result<(), NetCtlError> { + Ok(creds.check_match(self.0)?) + } +} + +impl NetChangeNotif for NoopWirelessNetCtl { + async fn wait_changed(&self) { + core::future::pending().await + } +} + +impl wifi_diag::WirelessDiag for NoopWirelessNetCtl {} + +impl wifi_diag::WifiDiag for NoopWirelessNetCtl {} + +impl thread_diag::ThreadDiag for NoopWirelessNetCtl {} + +/// A type holding the status of the last `connect` or `scan` operation for the `NetCtlWithStatus` `NetCtl` + `NetCtlStatus` implementation. +pub struct NetCtlState { + /// The network ID used in the last scan or connect operation + pub network_id: OwnedWirelessNetworkId, + /// The status of the last scan or connect operation + pub networking_status: Option, + /// The error code of the last scan or connect operation. + /// If the last operation was scan, this value is `None`. + pub connect_error_value: Option, +} + +impl NetCtlState { + /// Create a new, empty instance of `NetCtlState`. + pub const fn new() -> Self { + Self { + network_id: OwnedWirelessNetworkId::new(), + networking_status: None, + connect_error_value: None, + } + } + + /// Return an in-place initializer for a new, empty `NetCtlState`. + pub fn init() -> impl Init { + init!(Self { + network_id <- OwnedWirelessNetworkId::init(), + networking_status: None, + connect_error_value: None, + }) + } + + /// Create a new, empty instance of `NetCtlState` wrapped in a mutex. + pub const fn new_with_mutex() -> NetCtlStateMutex + where + M: RawMutex, + { + blocking::Mutex::new(RefCell::new(Self::new())) + } + + /// Return an in-place initializer for a new, empty `NetCtlState` wrapped in a mutex. + pub fn init_with_mutex() -> impl Init> + where + M: RawMutex, + { + blocking::Mutex::init(RefCell::init(init!(Self { + network_id <- OwnedWirelessNetworkId::init(), + networking_status: None, + connect_error_value: None, + }))) + } + + /// Return `true` if the network ID is set and the last operation was successful. + pub fn is_prov_ready(&self) -> bool { + !self.network_id.is_empty() + && matches!( + self.networking_status, + Some(NetworkCommissioningStatusEnum::Success) + ) + && self.connect_error_value.is_none() + } + + /// Update the state with the provided network ID and result of the last operation. + /// + /// Return the result of the last operation. + pub fn update( + &mut self, + network_id: Option<&[u8]>, + result: Result, + ) -> Result { + self.network_id.clear(); + + if let Some(network_id) = network_id { + unwrap!(self.network_id.extend_from_slice(network_id)); + } + + if let Some((status, err_code)) = NetworkCommissioningStatusEnum::map_ctl_status(&result) { + self.networking_status = Some(status); + self.connect_error_value = err_code; + } else { + self.networking_status = None; + self.connect_error_value = None; + } + + result + } + + /// Update the state with the provided network ID and result of the last operation. + /// + /// Return the result of the last operation. + pub fn update_with_mutex( + state: &NetCtlStateMutex, + network_id: Option<&[u8]>, + result: Result, + ) -> Result + where + M: RawMutex, + { + state.lock(|state| state.borrow_mut().update(network_id, result)) + } + + /// A utility to wait for provisioning over BTP to be ready. + /// Provisioning over BTP is considered complete when there is no longer an active connection + /// and the network ID is set (i.e. method `NetCtl::connect` was called successfully). + /// + /// This method is only useful for non-concurrent commisioning using wireless networks and BLE, + /// and is likely to be used together with `NoopWirelessNetCtl`. + pub async fn wait_prov_ready(state: &NetCtlStateMutex, btp: &Btp) + where + M: RawMutex, + C: Borrow> + Clone + Send + Sync + 'static, + M2: RawMutex + Send + Sync, + G: GattPeripheral, + { + while !state.lock(|state| state.borrow().is_prov_ready() && btp.conn_ct() == 0) { + // Provisioning over BTP is considered complete when there is no longer an active connection + // and the network ID is set (i.e. method `NetCtl::connect` was called successfully) + + btp.wait_changed().await; + } + } +} + +impl Default for NetCtlState { + fn default() -> Self { + Self::new() + } +} + +/// A type alias for a `NetCtlState` instance wrapped in a mutex. +pub type NetCtlStateMutex = blocking::Mutex>; + +/// A wrapper around a `NetCtl` network controller that additionally implements the `NetCtlStatus`trait. +pub struct NetCtlWithStatusImpl<'a, M, T> +where + M: RawMutex, +{ + state: &'a NetCtlStateMutex, + net_ctl: T, +} + +impl<'a, M, T> NetCtlWithStatusImpl<'a, M, T> +where + M: RawMutex, +{ + /// Create a new instance of `NetCtlWithStatusImpl`. + /// + /// # Arguments + /// - `state`: A reference to a `NetCtlState` instance wrapped in a mutex + /// - `net_ctl`: A network controller that implements the `NetCtl` trait + pub const fn new(state: &'a NetCtlStateMutex, net_ctl: T) -> Self { + Self { state, net_ctl } + } +} + +impl net_comm::NetCtl for NetCtlWithStatusImpl<'_, M, T> +where + M: RawMutex, + T: net_comm::NetCtl, +{ + fn net_type(&self) -> NetworkType { + self.net_ctl.net_type() + } + + fn connect_max_time_seconds(&self) -> u8 { + self.net_ctl.connect_max_time_seconds() + } + + fn scan_max_time_seconds(&self) -> u8 { + self.net_ctl.scan_max_time_seconds() + } + + fn supported_wifi_bands(&self, f: F) -> Result<(), Error> + where + F: FnMut(net_comm::WiFiBandEnum) -> Result<(), Error>, + { + self.net_ctl.supported_wifi_bands(f) + } + + fn supported_thread_features(&self) -> ThreadCapabilitiesBitmap { + self.net_ctl.supported_thread_features() + } + + fn thread_version(&self) -> u16 { + self.net_ctl.thread_version() + } + + async fn scan(&self, network: Option<&[u8]>, f: F) -> Result<(), NetCtlError> + where + F: FnMut(&net_comm::NetworkScanInfo) -> Result<(), Error>, + { + NetCtlState::update_with_mutex(self.state, network, self.net_ctl.scan(network, f).await) + } + + async fn connect(&self, creds: &WirelessCreds<'_>) -> Result<(), NetCtlError> { + NetCtlState::update_with_mutex( + self.state, + Some(creds.id()?), + self.net_ctl.connect(creds).await, + ) + } +} + +impl net_comm::NetCtlStatus for NetCtlWithStatusImpl<'_, M, T> +where + M: RawMutex, + T: net_comm::NetCtl, +{ + fn last_networking_status(&self) -> Result, Error> { + Ok(self.state.lock(|state| state.borrow().networking_status)) + } + + fn last_network_id(&self, f: F) -> Result + where + F: FnOnce(Option<&[u8]>) -> Result, + { + self.state.lock(|state| { + let state = state.borrow(); + + if state.network_id.is_empty() { + f(None) + } else { + f(Some(&state.network_id)) + } + }) + } + + fn last_connect_error_value(&self) -> Result, Error> { + Ok(self.state.lock(|state| state.borrow().connect_error_value)) + } +} + +impl NetChangeNotif for NetCtlWithStatusImpl<'_, M, T> +where + M: RawMutex, + T: NetChangeNotif, +{ + async fn wait_changed(&self) { + self.net_ctl.wait_changed().await + } +} + +impl wifi_diag::WirelessDiag for NetCtlWithStatusImpl<'_, M, T> +where + M: RawMutex, + T: wifi_diag::WirelessDiag, +{ + fn connected(&self) -> Result { + self.net_ctl.connected() + } +} + +impl wifi_diag::WifiDiag for NetCtlWithStatusImpl<'_, M, T> +where + M: RawMutex, + T: wifi_diag::WifiDiag, +{ + fn bssid(&self, f: &mut dyn FnMut(Option<&[u8]>) -> Result<(), Error>) -> Result<(), Error> { + self.net_ctl.bssid(f) + } + + fn security_type(&self) -> Result, Error> { + self.net_ctl.security_type() + } + + fn wi_fi_version(&self) -> Result, Error> { + self.net_ctl.wi_fi_version() + } + + fn channel_number(&self) -> Result, Error> { + self.net_ctl.channel_number() + } + + fn rssi(&self) -> Result, Error> { + self.net_ctl.rssi() + } +} + +impl thread_diag::ThreadDiag for NetCtlWithStatusImpl<'_, M, T> +where + M: RawMutex, + T: thread_diag::ThreadDiag, +{ + fn channel(&self) -> Result, Error> { + self.net_ctl.channel() + } + + fn routing_role(&self) -> Result, Error> { + self.net_ctl.routing_role() + } + + fn network_name( + &self, + f: &mut dyn FnMut(Option<&str>) -> Result<(), Error>, + ) -> Result<(), Error> { + self.net_ctl.network_name(f) + } + + fn pan_id(&self) -> Result, Error> { + self.net_ctl.pan_id() + } + + fn extended_pan_id(&self) -> Result, Error> { + self.net_ctl.extended_pan_id() + } + + fn mesh_local_prefix( + &self, + f: &mut dyn FnMut(Option<&[u8]>) -> Result<(), Error>, + ) -> Result<(), Error> { + self.net_ctl.mesh_local_prefix(f) + } + + fn neightbor_table( + &self, + f: &mut dyn FnMut(&thread_diag::NeighborTable) -> Result<(), Error>, + ) -> Result<(), Error> { + self.net_ctl.neightbor_table(f) + } + + fn route_table( + &self, + f: &mut dyn FnMut(&thread_diag::RouteTable) -> Result<(), Error>, + ) -> Result<(), Error> { + self.net_ctl.route_table(f) + } + + fn partition_id(&self) -> Result, Error> { + self.net_ctl.partition_id() + } + + fn weighting(&self) -> Result, Error> { + self.net_ctl.weighting() + } + + fn data_version(&self) -> Result, Error> { + self.net_ctl.data_version() + } + + fn stable_data_version(&self) -> Result, Error> { + self.net_ctl.stable_data_version() + } + + fn leader_router_id(&self) -> Result, Error> { + self.net_ctl.leader_router_id() + } + + fn security_policy(&self) -> Result, Error> { + self.net_ctl.security_policy() + } + + fn channel_page0_mask( + &self, + f: &mut dyn FnMut(Option<&[u8]>) -> Result<(), Error>, + ) -> Result<(), Error> { + self.net_ctl.channel_page0_mask(f) + } + + fn operational_dataset_components( + &self, + f: &mut dyn FnMut(Option<&thread_diag::OperationalDatasetComponents>) -> Result<(), Error>, + ) -> Result<(), Error> { + self.net_ctl.operational_dataset_components(f) + } + + fn active_network_faults_list( + &self, + f: &mut dyn FnMut(thread_diag::NetworkFaultEnum) -> Result<(), Error>, + ) -> Result<(), Error> { + self.net_ctl.active_network_faults_list(f) + } +} diff --git a/rs-matter/src/data_model/networks/wireless/mgr.rs b/rs-matter/src/data_model/networks/wireless/mgr.rs new file mode 100644 index 00000000..3432b74a --- /dev/null +++ b/rs-matter/src/data_model/networks/wireless/mgr.rs @@ -0,0 +1,206 @@ +/* + * + * Copyright (c) 2020-2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! This module contains a wireless manager that can be used post-commissioning +//! for re-establishing wireless network connection upon loss of connectivity. + +use embassy_time::{Duration, Timer}; + +use crate::data_model::sdm::net_comm::{self, NetCtlError}; +use crate::error::{Error, ErrorCode}; + +use crate::data_model::sdm::net_comm::WirelessCreds; +use crate::data_model::sdm::wifi_diag; +use crate::data_model::sdm::wifi_diag::WirelessDiag; + +use super::thread::Thread; +use super::{NetChangeNotif, OwnedWirelessNetworkId}; + +/// The maximum size of one network credentials +pub const MAX_CREDS_SIZE: usize = 256; + +/// A wireless manager that can be used post-commissioning +/// for re-establishing wireless network connection upon loss of connectivity. +pub struct WirelessMgr<'a, W, T> { + networks: W, + net_ctl: T, + buf: &'a mut [u8; MAX_CREDS_SIZE], +} + +impl<'a, W, T> WirelessMgr<'a, W, T> +where + W: net_comm::Networks + NetChangeNotif, + T: net_comm::NetCtl + wifi_diag::WirelessDiag + NetChangeNotif, +{ + /// Creates a new `WirelessMgr` instance. + /// + /// # Arguments + /// - `networks`: A reference to the networks storage. + /// - `net_ctl`: A reference to the network controller. + /// - `buf`: A mutable buffer used as temp credentials storage. + pub const fn new(networks: W, net_ctl: T, buf: &'a mut [u8; MAX_CREDS_SIZE]) -> Self { + Self { + networks, + net_ctl, + buf, + } + } + + /// Runs the wireless manager. + /// + /// This function will try to connect to the networks in a round-robin fashion + /// and will retry multiple times the current network in case of a failure, prior to + /// moving to the next network. + pub async fn run(&mut self) -> Result<(), Error> { + loop { + Self::run_connect(&self.networks, &self.net_ctl, self.buf).await?; + } + } + + async fn run_connect(networks: &W, net_ctl: &T, buf: &mut [u8]) -> Result<(), Error> { + loop { + Self::wait_connect_while(&net_ctl, true).await?; + + let mut network_id = OwnedWirelessNetworkId::new(); + + let mut c = None; + + networks.next_creds( + (!network_id.is_empty()).then_some(&network_id), + &mut |creds| { + match creds { + WirelessCreds::Wifi { ssid, pass } => { + if ssid.len() + pass.len() > buf.len() { + error!("SSID and password too large"); + return Err(ErrorCode::InvalidData.into()); + } + + buf[..ssid.len()].copy_from_slice(ssid); + buf[ssid.len()..][..pass.len()].copy_from_slice(pass); + + c = Some((ssid.len(), Some(pass.len()))) + } + WirelessCreds::Thread { dataset_tlv } => { + if dataset_tlv.len() > buf.len() { + error!("Dataset TLV too large"); + return Err(ErrorCode::InvalidData.into()); + } + + buf[..dataset_tlv.len()].copy_from_slice(dataset_tlv); + + c = Some((dataset_tlv.len(), None)) + } + } + + Ok(()) + }, + )?; + + if let Some((len1, len2)) = c { + let creds = if let Some(len2) = len2 { + WirelessCreds::Wifi { + ssid: &buf[..len1], + pass: &buf[len1..][..len2], + } + } else { + WirelessCreds::Thread { + dataset_tlv: &buf[..len1], + } + }; + + network_id.clear(); + match creds { + WirelessCreds::Wifi { ssid, .. } => { + network_id + .extend_from_slice(ssid) + .map_err(|_| ErrorCode::InvalidData)?; + } + WirelessCreds::Thread { dataset_tlv } => { + network_id + .extend_from_slice(Thread::dataset_ext_pan_id(dataset_tlv)?) + .map_err(|_| ErrorCode::InvalidData)?; + } + } + + match Self::connect_with_retries(net_ctl, &creds).await { + Ok(_) => unreachable!(), + Err(NetCtlError::Other(e)) => { + error!( + "General failure when connecting to network with ID {}: {:?}", + creds, e + ); + return Err(e); + } + _ => continue, + } + } else { + networks.wait_changed().await; + } + } + } + + async fn connect_with_retries( + net_ctl: &T, + creds: &WirelessCreds<'_>, + ) -> Result<(), NetCtlError> { + loop { + let mut result = Ok(()); + + for delay in [2, 5, 10].iter().copied() { + info!("Connecting to network with ID {}", creds); + + result = net_ctl.connect(creds).await; + + if result.is_ok() { + break; + } else { + warn!( + "Connection to network with ID {} failed: {:?}, retrying in {}s", + creds, result, delay + ); + } + + Timer::after(Duration::from_secs(delay)).await; + } + + if let Err(e) = result { + error!("Failed to connect to network with ID {}: {:?}", creds, e); + + break Err(e); + } else { + info!("Connected to network with ID {}", creds); + + Self::wait_connect_while(&net_ctl, true).await?; + } + } + } + + async fn wait_connect_while(net_ctl: N, connected: bool) -> Result<(), Error> + where + N: WirelessDiag + NetChangeNotif, + { + loop { + if net_ctl.connected()? != connected { + break; + } + + net_ctl.wait_changed().await; + } + + Ok(()) + } +} diff --git a/rs-matter/src/data_model/networks/wireless/thread.rs b/rs-matter/src/data_model/networks/wireless/thread.rs new file mode 100644 index 00000000..8a437b75 --- /dev/null +++ b/rs-matter/src/data_model/networks/wireless/thread.rs @@ -0,0 +1,207 @@ +/* + * + * Copyright (c) 2020-2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! This module contains Thread-specific types. + +use core::fmt::{Debug, Display}; + +use crate::error::{Error, ErrorCode}; +use crate::tlv::{FromTLV, OctetsOwned, ToTLV}; +use crate::utils::init::{init, Init, IntoFallibleInit}; +use crate::utils::storage::Vec; + +use crate::data_model::sdm::net_comm::WirelessCreds; + +use super::{WirelessNetwork, WirelessNetworks}; + +pub type ThreadNetworks = WirelessNetworks; + +/// A struct implementing the `WirelessNetwork` trait for Thread networks. +#[derive(Debug, Clone, Eq, PartialEq, Hash, ToTLV, FromTLV)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Thread { + /// Thread dataset in TLV format + pub dataset: OctetsOwned<256>, +} + +impl Default for Thread { + fn default() -> Self { + Self::new() + } +} + +impl Thread { + /// Create a new, empty instance of `Thread`. + pub const fn new() -> Self { + Self { + dataset: OctetsOwned { vec: Vec::new() }, + } + } + + /// Return an in-place initializer for an empty `Thread` insrtance. + pub fn init() -> impl Init { + init!(Self { + dataset <- OctetsOwned::init(), + }) + } + + /// Get the Extended PAN ID from the operational dataset + pub fn dataset_ext_pan_id(dataset_tlv: &[u8]) -> Result<&[u8], Error> { + ThreadTLV::new(dataset_tlv).ext_pan_id() + } + + /// Get the Extended PAN ID from the operational dataset + pub fn ext_pan_id(&self) -> &[u8] { + unwrap!(Self::dataset_ext_pan_id(&self.dataset.vec)) + } +} + +impl WirelessNetwork for Thread { + fn id(&self) -> &[u8] { + self.ext_pan_id() + } + + #[cfg(not(feature = "defmt"))] + fn display_id(id: &[u8]) -> impl Display { + use super::DisplayId; + + DisplayId::Thread(id) + } + + #[cfg(feature = "defmt")] + fn display_id(id: &[u8]) -> impl Display + defmt::Format { + use super::DisplayId; + + DisplayId::Thread(id) + } + + fn init_from<'a>(creds: &'a WirelessCreds<'a>) -> impl Init + 'a { + Self::init().into_fallible().chain(move |network| { + let WirelessCreds::Thread { dataset_tlv } = creds else { + return Err(ErrorCode::InvalidData.into()); + }; + + network + .dataset + .vec + .extend_from_slice(dataset_tlv) + .map_err(|_| ErrorCode::InvalidData)?; + + Ok(()) + }) + } + + fn update(&mut self, creds: &WirelessCreds<'_>) -> Result<(), Error> { + let WirelessCreds::Thread { dataset_tlv } = creds else { + return Err(ErrorCode::InvalidData.into()); + }; + + if dataset_tlv.len() > self.dataset.vec.capacity() { + return Err(ErrorCode::InvalidData.into()); + } + + self.dataset.vec.clear(); + + unwrap!(self.dataset.vec.extend_from_slice(dataset_tlv)); + + Ok(()) + } + + fn creds(&self) -> WirelessCreds<'_> { + WirelessCreds::Thread { + dataset_tlv: &self.dataset.vec, + } + } +} + +/// A simple Thread TLV reader +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ThreadTLV<'a>(&'a [u8]); + +impl<'a> ThreadTLV<'a> { + /// Create a new `ThreadTLV` instance with the given TLV data + pub const fn new(tlv: &'a [u8]) -> Self { + Self(tlv) + } + + /// Get the Extended PAN ID from the operational dataset + pub fn ext_pan_id(&mut self) -> Result<&'a [u8], Error> { + const EXT_PAN_ID: u8 = 2; + + let ext_pan_id = + self.find_map(|(tlv_type, tlv_value)| (tlv_type == EXT_PAN_ID).then_some(tlv_value)); + + let Some(ext_pan_id) = ext_pan_id else { + return Err(ErrorCode::InvalidData.into()); + }; + + Ok(ext_pan_id) + } + + /// Get the next TLV from the data + /// + /// Returns `Some` with the TLV type and value if there is a TLV available, + /// otherwise returns `None`. + pub fn next_tlv(&mut self) -> Option<(u8, &'a [u8])> { + const LONG_VALUE_ID: u8 = 255; + + // Adopted from here: + // https://github.com/openthread/openthread/blob/main/tools/tcat_ble_client/tlv/tlv.py + + let mut slice = self.0; + + (slice.len() >= 2).then_some(())?; + + let tlv_type = slice[0]; + slice = &slice[1..]; + + let tlv_len_size = if slice[0] == LONG_VALUE_ID { + slice = &slice[1..]; + 3 + } else { + 1 + }; + + (slice.len() >= tlv_len_size).then_some(())?; + + let tlv_len = if tlv_len_size == 1 { + slice[0] as usize + } else { + u32::from_be_bytes([0, slice[0], slice[1], slice[2]]) as usize + }; + + slice = &slice[tlv_len_size..]; + (slice.len() >= tlv_len).then_some(())?; + + let tlv_value = &slice[..tlv_len]; + + slice = &slice[tlv_len..]; + + self.0 = slice; + + Some((tlv_type, tlv_value)) + } +} + +impl<'a> Iterator for ThreadTLV<'a> { + type Item = (u8, &'a [u8]); + + fn next(&mut self) -> Option { + self.next_tlv() + } +} diff --git a/rs-matter/src/data_model/networks/wireless/wifi.rs b/rs-matter/src/data_model/networks/wireless/wifi.rs new file mode 100644 index 00000000..15bd3f34 --- /dev/null +++ b/rs-matter/src/data_model/networks/wireless/wifi.rs @@ -0,0 +1,135 @@ +/* + * + * Copyright (c) 2020-2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! This module contains Wifi-specific types. + +use core::fmt::{Debug, Display}; + +use crate::error::{Error, ErrorCode}; +use crate::tlv::{FromTLV, OctetsOwned, ToTLV}; +use crate::utils::init::{init, Init, IntoFallibleInit}; +use crate::utils::storage::Vec; + +use crate::data_model::sdm::net_comm::WirelessCreds; + +use super::{WirelessNetwork, WirelessNetworks}; + +pub type WifiNetworks = WirelessNetworks; + +/// A struct implementing the `WirelessNetwork` trait for Wifi networks. +#[derive(Debug, Clone, Eq, PartialEq, Hash, ToTLV, FromTLV)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Wifi { + /// Wifi SSID + pub ssid: OctetsOwned<32>, + /// Wifi password + pub password: OctetsOwned<64>, +} + +impl Default for Wifi { + fn default() -> Self { + Self::new() + } +} + +impl Wifi { + /// Create a new, empty instance of `Wifi`. + pub const fn new() -> Self { + Self { + ssid: OctetsOwned { vec: Vec::new() }, + password: OctetsOwned { vec: Vec::new() }, + } + } + + /// Return an in-place initializer for an empty `Wifi` instance. + pub fn init() -> impl Init { + init!(Self { + ssid <- OctetsOwned::init(), + password <- OctetsOwned::init(), + }) + } +} + +impl WirelessNetwork for Wifi { + fn id(&self) -> &[u8] { + &self.ssid + } + + #[cfg(not(feature = "defmt"))] + fn display_id(id: &[u8]) -> impl Display { + use super::DisplayId; + + DisplayId::Wifi(id) + } + + #[cfg(feature = "defmt")] + fn display_id(id: &[u8]) -> impl Display + defmt::Format { + use super::DisplayId; + + DisplayId::Wifi(id) + } + + fn init_from<'a>(creds: &'a WirelessCreds<'a>) -> impl Init + 'a { + Self::init().into_fallible().chain(move |network| { + let WirelessCreds::Wifi { ssid, pass } = creds else { + return Err(ErrorCode::InvalidData.into()); + }; + + network + .ssid + .vec + .extend_from_slice(ssid) + .map_err(|_| ErrorCode::InvalidData)?; + network + .password + .vec + .extend_from_slice(pass) + .map_err(|_| ErrorCode::InvalidData)?; + + Ok(()) + }) + } + + fn update(&mut self, creds: &WirelessCreds<'_>) -> Result<(), Error> { + let WirelessCreds::Wifi { ssid, pass } = creds else { + return Err(ErrorCode::InvalidData.into()); + }; + + if ssid.len() > self.ssid.vec.capacity() { + return Err(ErrorCode::InvalidData.into()); + } + + if pass.len() > self.password.vec.capacity() { + return Err(ErrorCode::InvalidData.into()); + } + + self.ssid.vec.clear(); + self.password.vec.clear(); + + unwrap!(self.ssid.vec.extend_from_slice(ssid)); + unwrap!(self.password.vec.extend_from_slice(pass)); + + Ok(()) + } + + fn creds(&self) -> WirelessCreds<'_> { + WirelessCreds::Wifi { + ssid: &self.ssid.vec, + pass: &self.password.vec, + } + } +} diff --git a/rs-matter/src/data_model/objects/cluster.rs b/rs-matter/src/data_model/objects/cluster.rs index 331341bf..4a9859c8 100644 --- a/rs-matter/src/data_model/objects/cluster.rs +++ b/rs-matter/src/data_model/objects/cluster.rs @@ -217,8 +217,20 @@ impl<'a> Cluster<'a> { self.encode_attribute_ids(&AttrDataWriter::TAG, &mut *writer)?; writer.complete() } - GlobalElements::FeatureMap => writer.set(self.feature_map), - GlobalElements::ClusterRevision => writer.set(self.revision), + GlobalElements::FeatureMap => { + debug!( + "Endpt(0x??)::Cluster(0x{:04x})::Attr::FeatureMap(0xfffc)::Read -> Ok({:08x})", + self.id, self.feature_map + ); + writer.set(self.feature_map) + } + GlobalElements::ClusterRevision => { + debug!( + "Endpt(0x??)::Cluster(0x{:04x})::Attr::ClusterRevision(0xfffd)::Read -> Ok({})", + self.id, self.revision + ); + writer.set(self.revision) + } other => { error!("Attribute {:?} not supported", other); Err(ErrorCode::AttributeNotFound.into()) @@ -227,26 +239,50 @@ impl<'a> Cluster<'a> { } fn encode_attribute_ids(&self, tag: &TLVTag, mut tw: W) -> Result<(), Error> { + debug!("Endpt(0x??)::Cluster(:04x)::Attr::AttributeIDs(0xNN)::Read -> Ok(["); + tw.start_array(tag)?; - for a in self.attributes() { - tw.u32(&TLVTag::Anonymous, a.id)?; + for attr in self.attributes() { + tw.u32(&TLVTag::Anonymous, attr.id)?; + debug!(" Attr: 0x{:02x},", attr.id); } - tw.end_container() + tw.end_container()?; + + debug!("])"); + + Ok(()) } fn encode_accepted_command_ids(&self, tag: &TLVTag, tw: W) -> Result<(), Error> { + debug!( + "Endpt(0x??)::Cluster(0x{:04x})::Attr::AcceptedCmdIDs(0xfff9)::Read -> Ok([", + self.id + ); Self::encode_command_ids(tag, tw, self.commands().map(|cmd| cmd.id)) } fn encode_generated_command_ids(&self, tag: &TLVTag, tw: W) -> Result<(), Error> { + debug!( + "Endpt(0x??)::Cluster(0x{:04x})::Attr::GeneratedCmdIDs(0xfff8)::Read -> Ok([", + self.id + ); Self::encode_command_ids(tag, tw, self.commands().filter_map(|cmd| cmd.resp_id)) } fn encode_event_ids(&self, tag: &TLVTag, mut tw: W) -> Result<(), Error> { + debug!( + "Endpt(0x??)::Cluster(0x{:04x})::Attr::EventIDs(0xfffa)::Read -> Ok([", + self.id + ); + // No events for now tw.start_array(tag)?; - tw.end_container() + tw.end_container()?; + + debug!("])"); + + Ok(()) } fn encode_command_ids( @@ -257,9 +293,14 @@ impl<'a> Cluster<'a> { tw.start_array(tag)?; for cmd in cmds { tw.u32(&TLVTag::Anonymous, cmd)?; + debug!(" Cmd: 0x{:02x}, ", cmd); } - tw.end_container() + tw.end_container()?; + + debug!("])"); + + Ok(()) } } @@ -316,6 +357,54 @@ impl defmt::Format for Cluster<'_> { } } +/// A macro to generate the clusters for an endpoint. +#[allow(unused_macros)] +#[macro_export] +macro_rules! clusters { + (sys; $($cluster:expr $(,)?)*) => { + $crate::clusters!( + <$crate::data_model::system_model::desc::DescHandler as $crate::data_model::system_model::desc::ClusterHandler>::CLUSTER, + <$crate::data_model::system_model::acl::AclHandler as $crate::data_model::system_model::acl::ClusterHandler>::CLUSTER, + <$crate::data_model::basic_info::BasicInfoHandler as $crate::data_model::basic_info::ClusterHandler>::CLUSTER, + <$crate::data_model::sdm::gen_comm::GenCommHandler as $crate::data_model::sdm::gen_comm::ClusterHandler>::CLUSTER, + <$crate::data_model::sdm::gen_diag::GenDiagHandler as $crate::data_model::sdm::gen_diag::ClusterHandler>::CLUSTER, + <$crate::data_model::sdm::adm_comm::AdminCommHandler as $crate::data_model::sdm::adm_comm::ClusterHandler>::CLUSTER, + <$crate::data_model::sdm::noc::NocHandler as $crate::data_model::sdm::noc::ClusterHandler>::CLUSTER, + <$crate::data_model::sdm::grp_key_mgmt::GrpKeyMgmtHandler as $crate::data_model::sdm::grp_key_mgmt::ClusterHandler>::CLUSTER, + $($cluster,)* + ) + }; + (eth; $($cluster:expr $(,)?)*) => { + $crate::clusters!( + sys; + $crate::data_model::sdm::net_comm::NetworkType::Ethernet.cluster(), + <$crate::data_model::sdm::eth_diag::EthDiagHandler as $crate::data_model::sdm::eth_diag::ClusterHandler>::CLUSTER, + $($cluster,)* + ) + }; + (thread; $($cluster:expr $(,)?)*) => { + $crate::clusters!( + sys; + $crate::data_model::sdm::net_comm::NetworkType::Thread.cluster(), + <$crate::data_model::sdm::thread_diag::ThreadDiagHandler as $crate::data_model::sdm::thread_diag::ClusterHandler>::CLUSTER, + $($cluster,)* + ) + }; + (wifi; $($cluster:expr $(,)?)*) => { + $crate::clusters!( + sys; + $crate::data_model::sdm::net_comm::NetworkType::Wifi.cluster(), + <$crate::data_model::sdm::wifi_diag::WifiDiagHandler as $crate::data_model::sdm::wifi_diag::ClusterHandler>::CLUSTER, + $($cluster,)* + ) + }; + ($($cluster:expr $(,)?)*) => { + &[ + $($cluster,)* + ] + } +} + /// A macro that generates a "with" fn for matching attributes and commands /// /// Usage: @@ -350,6 +439,25 @@ macro_rules! with { } } }; + (system) => { + |attr, _, _| attr.is_system() + }; + (system; $($id:path $(|)?)*) => { + #[allow(clippy::collapsible_match)] + |attr, _, _| { + if attr.is_system() { + true + } else if let Ok(l) = attr.id.try_into() { + #[allow(unreachable_patterns)] + match l { + $($id => true,)* + _ => false, + } + } else { + false + } + } + }; ($id0:path $(| $id:path $(|)?)*) => { #[allow(clippy::collapsible_match)] |leaf, _, _| { diff --git a/rs-matter/src/data_model/objects/handler.rs b/rs-matter/src/data_model/objects/handler.rs index cf2b0f0a..ed3f1ad5 100644 --- a/rs-matter/src/data_model/objects/handler.rs +++ b/rs-matter/src/data_model/objects/handler.rs @@ -75,7 +75,7 @@ pub struct WriteContext<'a> { exchange: &'a Exchange<'a>, attr: &'a AttrDetails<'a>, data: &'a TLVElement<'a>, - notify: &'a dyn ChangeNotify, + pub(crate) notify: &'a dyn ChangeNotify, } impl<'a> WriteContext<'a> { diff --git a/rs-matter/src/data_model/objects/node.rs b/rs-matter/src/data_model/objects/node.rs index f014b6f2..1480369f 100644 --- a/rs-matter/src/data_model/objects/node.rs +++ b/rs-matter/src/data_model/objects/node.rs @@ -674,7 +674,7 @@ mod test { expected: &[Result, ErrorCode>], ) { let fab_mgr = RefCell::new(FabricMgr::new()); - let accessor = Accessor::new(0, AccessorSubjects::new(0), AuthMode::Pase, &fab_mgr); + let accessor = Accessor::new(0, AccessorSubjects::new(0), Some(AuthMode::Pase), &fab_mgr); let expander = PathExpander::new(node, &accessor, Some(input.iter().cloned().map(Ok))); diff --git a/rs-matter/src/data_model/objects/privilege.rs b/rs-matter/src/data_model/objects/privilege.rs index e670f15d..07b72e92 100644 --- a/rs-matter/src/data_model/objects/privilege.rs +++ b/rs-matter/src/data_model/objects/privilege.rs @@ -15,7 +15,8 @@ * limitations under the License. */ -use crate::error::{Error, ErrorCode}; +use crate::data_model::system_model::acl::AccessControlEntryPrivilegeEnum; +use crate::error::Error; use crate::tlv::{FromTLV, TLVElement, TLVTag, TLVWrite, ToTLV, TLV}; use crate::utils::bitflags::bitflags; @@ -36,48 +37,50 @@ bitflags! { } } -impl Privilege { - pub fn raw_value(&self) -> u8 { - if self.contains(Privilege::ADMIN) { - 5 - } else if self.contains(Privilege::OPERATE) { - 4 - } else if self.contains(Privilege::MANAGE) { - 3 - } else if self.contains(Privilege::VIEW) { - 1 - } else { - 0 - } - } -} - impl FromTLV<'_> for Privilege { fn from_tlv(t: &TLVElement) -> Result where Self: Sized, { - match t.u32()? { - 1 => Ok(Privilege::VIEW), - 2 => { - error!("ProxyView privilege not yet supporteds"); - Err(ErrorCode::Invalid.into()) - } - 3 => Ok(Privilege::OPERATE), - 4 => Ok(Privilege::MANAGE), - 5 => Ok(Privilege::ADMIN), - _ => Err(ErrorCode::Invalid.into()), - } + Ok(AccessControlEntryPrivilegeEnum::from_tlv(t)?.into()) } } impl ToTLV for Privilege { - fn to_tlv(&self, tag: &TLVTag, mut tw: W) -> Result<(), Error> { - tw.u8(tag, self.raw_value()) + fn to_tlv(&self, tag: &TLVTag, tw: W) -> Result<(), Error> { + AccessControlEntryPrivilegeEnum::from(*self).to_tlv(tag, tw) } fn tlv_iter(&self, tag: TLVTag) -> impl Iterator> { - TLV::u8(tag, self.raw_value()).into_tlv_iter() + TLV::u8(tag, AccessControlEntryPrivilegeEnum::from(*self) as _).into_tlv_iter() + } +} + +impl From for AccessControlEntryPrivilegeEnum { + fn from(value: Privilege) -> Self { + if value.contains(Privilege::A) { + AccessControlEntryPrivilegeEnum::Administer + } else if value.contains(Privilege::M) { + AccessControlEntryPrivilegeEnum::Manage + } else if value.contains(Privilege::O) { + AccessControlEntryPrivilegeEnum::Operate + } else if value.contains(Privilege::V) { + AccessControlEntryPrivilegeEnum::View + } else { + unreachable!() + } + } +} + +impl From for Privilege { + fn from(value: AccessControlEntryPrivilegeEnum) -> Self { + match value { + AccessControlEntryPrivilegeEnum::View => Privilege::VIEW, + AccessControlEntryPrivilegeEnum::Manage => Privilege::MANAGE, + AccessControlEntryPrivilegeEnum::Operate => Privilege::OPERATE, + AccessControlEntryPrivilegeEnum::Administer => Privilege::ADMIN, + _ => Privilege::empty(), + } } } diff --git a/rs-matter/src/data_model/on_off.rs b/rs-matter/src/data_model/on_off.rs index 548a9190..cecfbd0a 100644 --- a/rs-matter/src/data_model/on_off.rs +++ b/rs-matter/src/data_model/on_off.rs @@ -15,6 +15,11 @@ * limitations under the License. */ +//! This module contains the implementation of the On/Off cluster and its handler. +//! +//! While this cluster is not necessary for the operation of `rs-matter`, this +//! implementation is useful in examples and tests. + use core::cell::Cell; use crate::error::{Error, ErrorCode}; @@ -24,13 +29,15 @@ use super::objects::{Cluster, Dataver, InvokeContext, ReadContext}; pub use crate::data_model::clusters::on_off::*; -#[derive(Clone)] +/// A sample implementation of a handler for the On/Off Matter cluster. +#[derive(Clone, Debug)] pub struct OnOffHandler { dataver: Dataver, on: Cell, } impl OnOffHandler { + /// Creates a new instance of `OnOffHandler` with the given `Dataver`. pub const fn new(dataver: Dataver) -> Self { Self { dataver, @@ -38,14 +45,17 @@ impl OnOffHandler { } } + /// Adapt the handler instance to the generic `rs-matter` `Handler` trait pub const fn adapt(self) -> HandlerAdaptor { HandlerAdaptor(self) } + /// Return the current state of the On/Off attribute. pub fn get(&self) -> bool { self.on.get() } + /// Set the On/Off attribute to the given value and notify potential subscribers. pub fn set(&self, on: bool) { if self.on.get() != on { self.on.set(on); diff --git a/rs-matter/src/data_model/root_endpoint.rs b/rs-matter/src/data_model/root_endpoint.rs index 0a177f48..079cbbc9 100644 --- a/rs-matter/src/data_model/root_endpoint.rs +++ b/rs-matter/src/data_model/root_endpoint.rs @@ -1,190 +1,270 @@ +/* + * + * Copyright (c) 2020-2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + use crate::handler_chain_type; use crate::utils::rand::Rand; use super::basic_info::{self, BasicInfoHandler, ClusterHandler as _}; -use super::objects::{Async, Cluster, Dataver, EmptyHandler, Endpoint, EndptId}; -use super::sdm::admin_commissioning::{self, AdminCommCluster}; -use super::sdm::eth_nw_diag::{self, ClusterHandler as _, EthNwDiagHandler}; -use super::sdm::gen_comm::{self, ClusterHandler as _, CommissioningPolicy, GenCommHandler}; -use super::sdm::general_diagnostics::{self, GenDiagCluster}; -use super::sdm::group_key_management::{self, GrpKeyMgmtCluster}; -use super::sdm::noc::{self, NocCluster}; -use super::sdm::nw_commissioning::{self, EthNwCommCluster}; -use super::sdm::wifi_nw_diagnostics; -use super::system_model::access_control::{self, AccessControlCluster}; -use super::system_model::descriptor::{self, DescriptorCluster}; - -const ETH_NW_CLUSTERS: [Cluster<'static>; 10] = [ - descriptor::CLUSTER, - BasicInfoHandler::CLUSTER, - GenCommHandler::CLUSTER, - nw_commissioning::ETH_CLUSTER, - admin_commissioning::CLUSTER, - noc::CLUSTER, - access_control::CLUSTER, - general_diagnostics::CLUSTER, - EthNwDiagHandler::CLUSTER, - group_key_management::CLUSTER, -]; - -const WIFI_NW_CLUSTERS: [Cluster<'static>; 10] = [ - descriptor::CLUSTER, - BasicInfoHandler::CLUSTER, - GenCommHandler::CLUSTER, - nw_commissioning::WIFI_CLUSTER, - admin_commissioning::CLUSTER, - noc::CLUSTER, - access_control::CLUSTER, - general_diagnostics::CLUSTER, - wifi_nw_diagnostics::CLUSTER, - group_key_management::CLUSTER, -]; - -const THREAD_NW_CLUSTERS: [Cluster<'static>; 10] = [ - descriptor::CLUSTER, - BasicInfoHandler::CLUSTER, - GenCommHandler::CLUSTER, - nw_commissioning::THR_CLUSTER, - admin_commissioning::CLUSTER, - noc::CLUSTER, - access_control::CLUSTER, - general_diagnostics::CLUSTER, - wifi_nw_diagnostics::CLUSTER, - group_key_management::CLUSTER, -]; - -/// The type of operational network (Ethernet, Wifi or Thread) -/// for which root endpoint meta-data is being requested -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum OperNwType { - Ethernet, - Wifi, - Thread, -} +use super::networks::eth::{EthNetCtl, EthNetwork}; +use super::objects::{Async, ChainedHandler, Dataver, Endpoint, EndptId}; +use super::sdm::adm_comm::{self, AdminCommHandler, ClusterHandler as _}; +use super::sdm::eth_diag::{self, ClusterHandler as _, EthDiagHandler}; +use super::sdm::gen_comm::{self, ClusterHandler as _, CommPolicy, GenCommHandler}; +use super::sdm::gen_diag::{self, ClusterHandler as _, GenDiag, GenDiagHandler, NetifDiag}; +use super::sdm::grp_key_mgmt::{self, ClusterHandler as _, GrpKeyMgmtHandler}; +use super::sdm::net_comm::{ + self, ClusterAsyncHandler as _, NetCommHandler, NetCtl, NetCtlStatus, NetworkType, Networks, +}; +use super::sdm::noc::{self, ClusterHandler as _, NocHandler}; +use super::sdm::thread_diag::{self, ClusterHandler as _, ThreadDiag, ThreadDiagHandler}; +use super::sdm::wifi_diag::{self, ClusterHandler as _, WifiDiag, WifiDiagHandler}; +use super::system_model::acl::{self, AclHandler, ClusterHandler as _}; +use super::system_model::desc::{self, ClusterHandler as _, DescHandler}; /// A utility function to create a root (Endpoint 0) object using the requested operational network type. -pub const fn endpoint(id: EndptId, op_nw_type: OperNwType) -> Endpoint<'static> { +pub const fn root_endpoint(net_type: NetworkType) -> Endpoint<'static> { Endpoint { - id, + id: ROOT_ENDPOINT_ID, device_types: &[super::device_types::DEV_TYPE_ROOT_NODE], - clusters: clusters(op_nw_type), + clusters: net_type.root_clusters(), } } -/// A utility function to return the clusters for a root (Endpoint 0) object using the requested operational network type. -pub const fn clusters(op_nw_type: OperNwType) -> &'static [Cluster<'static>] { - match op_nw_type { - OperNwType::Ethernet => Ð_NW_CLUSTERS, - OperNwType::Wifi => &WIFI_NW_CLUSTERS, - OperNwType::Thread => &THREAD_NW_CLUSTERS, - } -} +/// A type alias for the handler chain returned by `with_eth()`. +pub type EthHandler<'a, H> = NetHandler< + 'a, + net_comm::HandlerAsyncAdaptor>, + Async>, + H, +>; -/// A type alias for a root (Endpoint 0) handler using Ethernet as an operational network -pub type EthRootEndpointHandler<'a> = - RootEndpointHandler<'a, EthNwCommCluster, eth_nw_diag::HandlerAdaptor>; +/// A type alias for the handler chain returned by `with_wifi()`. +pub type WifiHandler<'a, T, H> = NetHandler< + 'a, + net_comm::HandlerAsyncAdaptor>, + Async>>, + H, +>; -/// A type representing the type of the root (Endpoint 0) handler -/// which is generic over the operational transport clusters (i.e. Ethernet, Wifi or Thread) -pub type RootEndpointHandler<'a, NWCOMM, NWDIAG> = handler_chain_type!( - Async, - Async, - Async>, +/// A type alias for the handler chain returned by `with_thread()`. +pub type ThreadHandler<'a, T, H> = NetHandler< + 'a, + net_comm::HandlerAsyncAdaptor>, + Async>>, + H, +>; + +pub type NetHandler<'a, NETCOMM, NETDIAG, H> = handler_chain_type!( + NETCOMM, + NETDIAG, + Async>> + | H +); + +/// A type alias for the handler chain returned by `with_sys()`. +pub type SysHandler<'a, H> = handler_chain_type!( + Async>>, Async>, Async>>, - Async, - Async, - Async, - Async, - Async + Async>, + Async>, + Async>, + Async> + | H ); -/// A utility function to instantiate the root (Endpoint 0) handler using Ethernet as the operational network. -pub fn eth_handler(endpoint_id: u16, rand: Rand) -> EthRootEndpointHandler<'static> { - handler( - endpoint_id, - EthNwCommCluster::new(Dataver::new_rand(rand)), - EthNwDiagHandler::CLUSTER.id, - EthNwDiagHandler::new(Dataver::new_rand(rand)).adapt(), - &true, - rand, +/// The ID of the root endpoint (Endpoint 0) +pub const ROOT_ENDPOINT_ID: EndptId = 0; + +/// Decorates the provided `handler` with the system model networking handlers necessary for operating +/// with Ethernet networks, installed on the root endpoint (0). +/// +/// The following handlers are added: +/// - `GenDiagHandler` +/// - `EthDiagHandler` +/// - `NetCommHandler` +/// +/// # Arguments: +/// - `gen_diag`: The `GenDiag` implementation. +/// - `netif_diag`: The `NetifDiag` implementation. +/// - `net_ctl`: The `NetCtl` implementation. +/// - `rand`: A random number generator. +/// - `handler`: The handler to be decorated. +pub fn with_eth<'a, H>( + gen_diag: &'a dyn GenDiag, + netif_diag: &'a dyn NetifDiag, + rand: Rand, + handler: H, +) -> EthHandler<'a, H> { + const NETWORK: EthNetwork<'static> = EthNetwork::new("eth"); + + ChainedHandler::new( + ROOT_ENDPOINT_ID, + GenDiagHandler::CLUSTER.id, + Async(GenDiagHandler::new(Dataver::new_rand(rand), gen_diag, netif_diag).adapt()), + handler, + ) + .chain( + ROOT_ENDPOINT_ID, + EthDiagHandler::CLUSTER.id, + Async(EthDiagHandler::new(Dataver::new_rand(rand)).adapt()), + ) + .chain( + ROOT_ENDPOINT_ID, + NetCommHandler::::CLUSTER.id, + NetCommHandler::new(Dataver::new_rand(rand), &NETWORK, EthNetCtl).adapt(), ) } -/// A utility function to instantiate the root (Endpoint 0) handler. -/// Besides a `Rand` function, this function -/// needs user-supplied implementations of the network commissioning -/// and network diagnostics clusters. -pub fn handler( - endpoint_id: u16, - nwcomm: NWCOMM, - nwdiag_id: u32, - nwdiag: NWDIAG, - concurrent_connection_policy: &dyn CommissioningPolicy, +/// Decorates the provided `handler` with the system model networking handlers necessary for operating +/// with Wifi networks, installed on the root endpoint (0). +/// +/// The following handlers are added: +/// - `GenDiagHandler` +/// - `WifiDiagHandler` +/// - `NetCommHandler` +/// +/// # Arguments: +/// - `gen_diag`: The `GenDiag` implementation. +/// - `net_ctl`: The `NetCtl` implementation. +/// - `networks`: The `Networks` implementation. +/// - `rand`: A random number generator. +/// - `handler`: The handler to be decorated. +pub fn with_wifi<'a, T, H>( + gen_diag: &'a dyn GenDiag, + netif_diag: &'a dyn NetifDiag, + net_ctl: &'a T, + networks: &'a dyn Networks, rand: Rand, -) -> RootEndpointHandler<'_, NWCOMM, NWDIAG> { - wrap( - endpoint_id, - nwcomm, - nwdiag_id, - nwdiag, - concurrent_connection_policy, - rand, + handler: H, +) -> WifiHandler<'a, &'a T, H> +where + T: NetCtl + NetCtlStatus + WifiDiag, +{ + ChainedHandler::new( + ROOT_ENDPOINT_ID, + GenDiagHandler::CLUSTER.id, + Async(GenDiagHandler::new(Dataver::new_rand(rand), gen_diag, netif_diag).adapt()), + handler, + ) + .chain( + ROOT_ENDPOINT_ID, + WifiDiagHandler::CLUSTER.id, + Async(WifiDiagHandler::new(Dataver::new_rand(rand), net_ctl).adapt()), + ) + .chain( + ROOT_ENDPOINT_ID, + NetCommHandler::::CLUSTER.id, + NetCommHandler::new(Dataver::new_rand(rand), networks, net_ctl).adapt(), + ) +} + +/// Decorates the provided `handler` with the system model networking handlers necessary for operating +/// with Thread networks, installed on the root endpoint (0). +/// +/// The following handlers are added: +/// - `GenDiagHandler` +/// - `ThreadDiagHandler` +/// - `NetCommHandler` +/// +/// # Arguments: +/// - `gen_diag`: The `GenDiag` implementation. +/// - `net_ctl`: The `NetCtl` implementation. +/// - `networks`: The `Networks` implementation. +/// - `rand`: A random number generator. +pub fn with_thread<'a, T, H>( + gen_diag: &'a dyn GenDiag, + netif_diag: &'a dyn NetifDiag, + net_ctl: &'a T, + networks: &'a dyn Networks, + rand: Rand, + handler: H, +) -> ThreadHandler<'a, &'a T, H> +where + T: NetCtl + NetCtlStatus + ThreadDiag, +{ + ChainedHandler::new( + ROOT_ENDPOINT_ID, + GenDiagHandler::CLUSTER.id, + Async(GenDiagHandler::new(Dataver::new_rand(rand), gen_diag, netif_diag).adapt()), + handler, + ) + .chain( + ROOT_ENDPOINT_ID, + ThreadDiagHandler::CLUSTER.id, + Async(ThreadDiagHandler::new(Dataver::new_rand(rand), net_ctl).adapt()), + ) + .chain( + ROOT_ENDPOINT_ID, + NetCommHandler::::CLUSTER.id, + NetCommHandler::new(Dataver::new_rand(rand), networks, net_ctl).adapt(), ) } -fn wrap( - endpoint_id: u16, - nwcomm: NWCOMM, - nwdiag_id: u32, - nwdiag: NWDIAG, - concurrent_connection_policy: &dyn CommissioningPolicy, +/// Decorates the provided `handler` with the system model handlers installed on the root endpoint (0). +/// +/// All system model handlers are added except for the following ones, which are network-specific: +/// - `GenDiagHandler` +/// - `EthDiagHandler`/`WifiDiagHandler`/`ThreadDiagHandler` +/// - `NetCommHandler` +/// +/// # Arguments: +/// - `comm_policy`: The commissioning policy to be used for the `GenCommHandler`. +/// - `rand`: A random number generator. +/// - `handler`: The handler to be decorated. +pub fn with_sys<'a, H>( + comm_policy: &'a dyn CommPolicy, rand: Rand, -) -> RootEndpointHandler<'_, NWCOMM, NWDIAG> { - EmptyHandler - .chain( - endpoint_id, - group_key_management::ID, - Async(GrpKeyMgmtCluster::new(Dataver::new_rand(rand))), - ) - .chain( - endpoint_id, - general_diagnostics::ID, - Async(GenDiagCluster::new(Dataver::new_rand(rand))), - ) - .chain( - endpoint_id, - access_control::ID, - Async(AccessControlCluster::new(Dataver::new_rand(rand))), - ) - .chain( - endpoint_id, - noc::ID, - Async(NocCluster::new(Dataver::new_rand(rand))), - ) - .chain( - endpoint_id, - admin_commissioning::ID, - Async(AdminCommCluster::new(Dataver::new_rand(rand))), - ) - .chain( - endpoint_id, - GenCommHandler::CLUSTER.id, - Async( - GenCommHandler::new(Dataver::new_rand(rand), concurrent_connection_policy).adapt(), - ), - ) - .chain( - endpoint_id, - BasicInfoHandler::CLUSTER.id, - Async(BasicInfoHandler::new(Dataver::new_rand(rand)).adapt()), - ) - .chain( - endpoint_id, - descriptor::ID, - Async(DescriptorCluster::new(Dataver::new_rand(rand))), - ) - .chain(endpoint_id, nwdiag_id, Async(nwdiag)) - .chain(endpoint_id, nw_commissioning::ID, Async(nwcomm)) + handler: H, +) -> SysHandler<'a, H> { + ChainedHandler::new( + ROOT_ENDPOINT_ID, + GrpKeyMgmtHandler::CLUSTER.id, + Async(GrpKeyMgmtHandler::new(Dataver::new_rand(rand)).adapt()), + handler, + ) + .chain( + ROOT_ENDPOINT_ID, + AclHandler::CLUSTER.id, + Async(AclHandler::new(Dataver::new_rand(rand)).adapt()), + ) + .chain( + ROOT_ENDPOINT_ID, + NocHandler::CLUSTER.id, + Async(NocHandler::new(Dataver::new_rand(rand)).adapt()), + ) + .chain( + ROOT_ENDPOINT_ID, + AdminCommHandler::CLUSTER.id, + Async(AdminCommHandler::new(Dataver::new_rand(rand)).adapt()), + ) + .chain( + ROOT_ENDPOINT_ID, + GenCommHandler::CLUSTER.id, + Async(GenCommHandler::new(Dataver::new_rand(rand), comm_policy).adapt()), + ) + .chain( + ROOT_ENDPOINT_ID, + BasicInfoHandler::CLUSTER.id, + Async(BasicInfoHandler::new(Dataver::new_rand(rand)).adapt()), + ) + .chain( + ROOT_ENDPOINT_ID, + DescHandler::CLUSTER.id, + Async(DescHandler::new(Dataver::new_rand(rand)).adapt()), + ) } diff --git a/rs-matter/src/data_model/sdm/adm_comm.rs b/rs-matter/src/data_model/sdm/adm_comm.rs new file mode 100644 index 00000000..0cdd4b64 --- /dev/null +++ b/rs-matter/src/data_model/sdm/adm_comm.rs @@ -0,0 +1,148 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! This module contains the implementation of the Administrative Commissioning cluster and its handler. + +use core::num::NonZeroU8; + +use crate::data_model::objects::{Cluster, Dataver, InvokeContext, ReadContext}; +use crate::error::Error; +use crate::secure_channel::pake::PaseSessionType; +use crate::tlv::Nullable; + +pub use crate::data_model::clusters::administrator_commissioning::*; +use crate::transport::session::SessionMode; + +/// The system implementation of a handler for the Administrative Commissioning Matter cluster. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AdminCommHandler { + dataver: Dataver, +} + +impl AdminCommHandler { + /// Create a new instance of `AdminCommHandler` with the given `Dataver`. + pub const fn new(dataver: Dataver) -> Self { + Self { dataver } + } + + /// Adapt the handler instance to the generic `rs-matter` `Handler` trait + pub const fn adapt(self) -> HandlerAdaptor { + HandlerAdaptor(self) + } +} + +impl ClusterHandler for AdminCommHandler { + const CLUSTER: Cluster<'static> = FULL_CLUSTER.with_features(Feature::BASIC.bits()); + + fn dataver(&self) -> u32 { + self.dataver.get() + } + + fn dataver_changed(&self) { + self.dataver.changed(); + } + + fn window_status(&self, ctx: &ReadContext<'_>) -> Result { + let session_type = ctx.exchange().matter().pase_mgr.borrow().session_type(); + + Ok(match session_type { + Some(PaseSessionType::Basic) => CommissioningWindowStatusEnum::BasicWindowOpen, + Some(PaseSessionType::Enhanced) => CommissioningWindowStatusEnum::EnhancedWindowOpen, + None => CommissioningWindowStatusEnum::WindowNotOpen, + }) + } + + fn admin_fabric_index(&self, ctx: &ReadContext<'_>) -> Result, Error> { + let session_mgr = ctx.exchange().matter().transport_mgr.session_mgr.borrow(); + + let fab_idx = session_mgr.iter().find_map(|session| { + if let SessionMode::Pase { fab_idx } = session.get_session_mode() { + Some(*fab_idx) + } else { + None + } + }); + + Ok(Nullable::new(fab_idx)) + } + + fn admin_vendor_id(&self, ctx: &ReadContext<'_>) -> Result, Error> { + let session_mgr = ctx.exchange().matter().transport_mgr.session_mgr.borrow(); + let fabric_mgr = ctx.exchange().matter().fabric_mgr.borrow(); + + let fab_idx = session_mgr.iter().find_map(|session| { + if let SessionMode::Pase { fab_idx } = session.get_session_mode() { + Some(*fab_idx) + } else { + None + } + }); + + let vendor_id = fab_idx + .and_then(NonZeroU8::new) + .and_then(|idx| fabric_mgr.get(idx)) + .map(|fabric| fabric.vendor_id()); + + Ok(Nullable::new(vendor_id)) + } + + fn handle_open_commissioning_window( + &self, + ctx: &InvokeContext<'_>, + request: OpenCommissioningWindowRequest<'_>, + ) -> Result<(), Error> { + let matter = ctx.exchange().matter(); + + matter.pase_mgr.borrow_mut().enable_pase_session( + request.pake_passcode_verifier()?.0, + request.salt()?.0, + request.iterations()?, + request.discriminator()?, + request.commissioning_timeout()?, + &matter.transport_mgr.mdns, + ) + } + + fn handle_open_basic_commissioning_window( + &self, + ctx: &InvokeContext<'_>, + request: OpenBasicCommissioningWindowRequest<'_>, + ) -> Result<(), Error> { + let matter = ctx.exchange().matter(); + + matter.pase_mgr.borrow_mut().enable_basic_pase_session( + matter.dev_comm().password, + matter.dev_comm().discriminator, + request.commissioning_timeout()?, + &matter.transport_mgr.mdns, + ) + } + + fn handle_revoke_commissioning(&self, ctx: &InvokeContext<'_>) -> Result<(), Error> { + let matter = ctx.exchange().matter(); + + matter + .pase_mgr + .borrow_mut() + .disable_pase_session(&matter.transport_mgr.mdns)?; + + // TODO: Send status code if no commissioning window is open? + + Ok(()) + } +} diff --git a/rs-matter/src/data_model/sdm/admin_commissioning.rs b/rs-matter/src/data_model/sdm/admin_commissioning.rs deleted file mode 100644 index 1dbc8d39..00000000 --- a/rs-matter/src/data_model/sdm/admin_commissioning.rs +++ /dev/null @@ -1,238 +0,0 @@ -/* - * - * Copyright (c) 2023 Project CHIP Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -use num_derive::FromPrimitive; - -use strum::{EnumDiscriminants, FromRepr}; - -use crate::data_model::objects::{ - Access, AttrDataEncoder, AttrType, Attribute, Cluster, CmdDataEncoder, Command, Dataver, - Handler, InvokeContext, NonBlockingHandler, Quality, ReadContext, -}; -use crate::error::Error; -use crate::tlv::{FromTLV, Nullable, OctetStr, TLVElement}; -use crate::transport::exchange::Exchange; -use crate::{attribute_enum, attributes, cmd_enter, command_enum, commands, with}; - -pub const ID: u32 = 0x003C; - -#[derive(FromPrimitive, Debug, Copy, Clone, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum WindowStatus { - WindowNotOpen = 0, - EnhancedWindowOpen = 1, - BasicWindowOpen = 2, -} - -#[derive(Clone, Debug, FromRepr, EnumDiscriminants)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u32)] -pub enum Attributes { - WindowStatus(AttrType) = 0, - AdminFabricIndex(AttrType>) = 1, - AdminVendorId(AttrType>) = 2, -} - -attribute_enum!(Attributes); - -#[derive(FromRepr)] -#[repr(u32)] -pub enum Commands { - OpenCommWindow = 0x00, - OpenBasicCommWindow = 0x01, - RevokeComm = 0x02, -} - -command_enum!(Commands); - -pub const CLUSTER: Cluster<'static> = Cluster { - id: ID as _, - revision: 1, - feature_map: 0, - attributes: attributes!( - Attribute::new( - AttributesDiscriminants::WindowStatus as _, - Access::RV, - Quality::NONE, - ), - Attribute::new( - AttributesDiscriminants::AdminFabricIndex as _, - Access::RV, - Quality::NULLABLE, - ), - Attribute::new( - AttributesDiscriminants::AdminVendorId as _, - Access::RV, - Quality::NULLABLE, - ), - ), - commands: commands!( - Command::new(Commands::OpenCommWindow as _, None, Access::WA), - Command::new(Commands::OpenBasicCommWindow as _, None, Access::WA), - Command::new(Commands::RevokeComm as _, None, Access::WA), - ), - with_attrs: with!(all), - with_cmds: with!(all), -}; - -#[derive(FromTLV, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[tlvargs(lifetime = "'a")] -pub struct OpenCommWindowReq<'a> { - timeout: u16, - verifier: OctetStr<'a>, - discriminator: u16, - iterations: u32, - salt: OctetStr<'a>, -} - -#[derive(FromTLV, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct OpenBasicCommWindowReq { - timeout: u16, -} - -#[derive(Debug, Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct AdminCommCluster { - data_ver: Dataver, -} - -impl AdminCommCluster { - pub const fn new(data_ver: Dataver) -> Self { - Self { data_ver } - } - - pub fn read( - &self, - ctx: &ReadContext<'_>, - encoder: AttrDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - let attr = ctx.attr(); - - if let Some(writer) = encoder.with_dataver(self.data_ver.get())? { - if attr.is_system() { - CLUSTER.read(attr.attr_id, writer) - } else { - match attr.attr_id.try_into()? { - Attributes::WindowStatus(codec) => codec.encode(writer, 1), - Attributes::AdminVendorId(codec) => codec.encode(writer, Nullable::some(1)), - Attributes::AdminFabricIndex(codec) => codec.encode(writer, Nullable::some(1)), - } - } - } else { - Ok(()) - } - } - - pub fn invoke( - &self, - ctx: &InvokeContext<'_>, - _encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - let exchange = ctx.exchange(); - let cmd = ctx.cmd(); - let data = ctx.data(); - - match cmd.cmd_id.try_into()? { - Commands::OpenCommWindow => self.handle_command_opencomm_win(exchange, data)?, - Commands::OpenBasicCommWindow => { - self.handle_command_open_basic_comm_win(exchange, data)? - } - Commands::RevokeComm => self.handle_command_revokecomm_win(exchange, data)?, - } - - self.data_ver.changed(); - - Ok(()) - } - - fn handle_command_opencomm_win( - &self, - exchange: &Exchange, - data: &TLVElement, - ) -> Result<(), Error> { - cmd_enter!("Open Commissioning Window"); - let req = OpenCommWindowReq::from_tlv(data)?; - - exchange.matter().pase_mgr.borrow_mut().enable_pase_session( - req.verifier.0, - req.salt.0, - req.iterations, - req.discriminator, - req.timeout, - &exchange.matter().transport_mgr.mdns, - ) - } - - fn handle_command_open_basic_comm_win( - &self, - exchange: &Exchange, - data: &TLVElement, - ) -> Result<(), Error> { - cmd_enter!("Open Basic Commissioning Window"); - let req = OpenBasicCommWindowReq::from_tlv(data)?; - - exchange - .matter() - .pase_mgr - .borrow_mut() - .enable_basic_pase_session( - exchange.matter().dev_comm().password, - exchange.matter().dev_comm().discriminator, - req.timeout, - &exchange.matter().transport_mgr.mdns, - ) - } - - fn handle_command_revokecomm_win( - &self, - exchange: &Exchange, - _data: &TLVElement, - ) -> Result<(), Error> { - cmd_enter!("Revoke Commissioning Window"); - exchange - .matter() - .pase_mgr - .borrow_mut() - .disable_pase_session(&exchange.matter().transport_mgr.mdns)?; - - // TODO: Send status code if no commissioning window is open - - Ok(()) - } -} - -impl Handler for AdminCommCluster { - fn read( - &self, - ctx: &ReadContext<'_>, - encoder: AttrDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - AdminCommCluster::read(self, ctx, encoder) - } - - fn invoke( - &self, - ctx: &InvokeContext<'_>, - encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - AdminCommCluster::invoke(self, ctx, encoder) - } -} - -impl NonBlockingHandler for AdminCommCluster {} diff --git a/rs-matter/src/data_model/sdm/dev_att.rs b/rs-matter/src/data_model/sdm/dev_att.rs index 87dca3cc..b818db10 100644 --- a/rs-matter/src/data_model/sdm/dev_att.rs +++ b/rs-matter/src/data_model/sdm/dev_att.rs @@ -18,6 +18,8 @@ use crate::error::Error; /// Device Attestation Data Type +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum DataType { /// Certificate Declaration CertDeclaration, diff --git a/rs-matter/src/data_model/sdm/eth_nw_diag.rs b/rs-matter/src/data_model/sdm/eth_diag.rs similarity index 69% rename from rs-matter/src/data_model/sdm/eth_nw_diag.rs rename to rs-matter/src/data_model/sdm/eth_diag.rs index d21cc250..df2dd09f 100644 --- a/rs-matter/src/data_model/sdm/eth_nw_diag.rs +++ b/rs-matter/src/data_model/sdm/eth_diag.rs @@ -15,33 +15,38 @@ * limitations under the License. */ -use crate::data_model::objects::{Cluster, Dataver, InvokeContext, ReadContext}; -use crate::error::Error; +//! This module contains the implementation of the Ethernet Network Diagnostics cluster and its handler. + +use crate::data_model::objects::{Cluster, Dataver, InvokeContext}; +use crate::error::{Error, ErrorCode}; use crate::with; pub use crate::data_model::clusters::ethernet_network_diagnostics::*; +/// The system implementation of a handler for the Ethernet Network Diagnostics Matter cluster. #[derive(Debug, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct EthNwDiagHandler { +pub struct EthDiagHandler { dataver: Dataver, } -impl EthNwDiagHandler { +impl EthDiagHandler { + /// Create a new instance of `EthDiagHandler` with the given `Dataver`. pub const fn new(dataver: Dataver) -> Self { Self { dataver } } + /// Adapt the handler instance to the generic `rs-matter` `Handler` trait pub const fn adapt(self) -> HandlerAdaptor { HandlerAdaptor(self) } } -impl ClusterHandler for EthNwDiagHandler { +impl ClusterHandler for EthDiagHandler { const CLUSTER: Cluster<'static> = FULL_CLUSTER .with_revision(1) - .with_attrs(with!(required; AttributeId::PacketRxCount | AttributeId::PacketTxCount)) - .with_cmds(with!(CommandId::ResetCounts)); + .with_attrs(with!(required)) + .with_cmds(with!()); fn dataver(&self) -> u32 { self.dataver.get() @@ -51,15 +56,7 @@ impl ClusterHandler for EthNwDiagHandler { self.dataver.changed(); } - fn packet_rx_count(&self, _ctx: &ReadContext) -> Result { - Ok(1) // TODO - } - - fn packet_tx_count(&self, _ctx: &ReadContext) -> Result { - Ok(1) // TODO - } - fn handle_reset_counts(&self, _ctx: &InvokeContext) -> Result<(), Error> { - Ok(()) // TODO + Err(ErrorCode::InvalidAction.into()) } } diff --git a/rs-matter/src/data_model/sdm/gen_comm.rs b/rs-matter/src/data_model/sdm/gen_comm.rs index 39476ccf..5fa5ad85 100644 --- a/rs-matter/src/data_model/sdm/gen_comm.rs +++ b/rs-matter/src/data_model/sdm/gen_comm.rs @@ -15,11 +15,14 @@ * limitations under the License. */ +//! This module contains the implementation of the General Commissioning cluster and its handler. + use crate::data_model::objects::{Cluster, Dataver, InvokeContext, ReadContext, WriteContext}; use crate::error::{Error, ErrorCode}; use crate::tlv::TLVBuilderParent; pub use crate::data_model::clusters::general_commissioning::*; +use crate::with; impl CommissioningErrorEnum { fn map(result: Result<(), Error>) -> Result { @@ -37,7 +40,7 @@ impl CommissioningErrorEnum { /// A trait indicating the commissioning policy supported by `rs-matter`. /// (i.e. co-existence of the BLE/BTP network and the operational network during commissioning). -pub trait CommissioningPolicy { +pub trait CommPolicy { /// Return true if the device supports concurrent connection /// (i.e. co-existence of the BLE/BTP network and the operational network during commissioning). fn concurrent_connection_supported(&self) -> bool; @@ -56,9 +59,9 @@ pub trait CommissioningPolicy { fn location_cap(&self) -> RegulatoryLocationTypeEnum; } -impl CommissioningPolicy for &T +impl CommPolicy for &T where - T: CommissioningPolicy, + T: CommPolicy, { fn concurrent_connection_supported(&self) -> bool { (*self).concurrent_connection_supported() @@ -81,7 +84,7 @@ where } } -impl CommissioningPolicy for bool { +impl CommPolicy for bool { fn concurrent_connection_supported(&self) -> bool { *self } @@ -103,27 +106,30 @@ impl CommissioningPolicy for bool { } } +/// The system implementation of a handler for the General Commissioning Matter cluster. #[derive(Clone)] pub struct GenCommHandler<'a> { dataver: Dataver, - commissioning_policy: &'a dyn CommissioningPolicy, + commissioning_policy: &'a dyn CommPolicy, } impl<'a> GenCommHandler<'a> { - pub const fn new(dataver: Dataver, commissioning_policy: &'a dyn CommissioningPolicy) -> Self { + /// Create a new instance of `GenCommHandler` with the given `Dataver` and `CommissioningPolicy`. + pub const fn new(dataver: Dataver, commissioning_policy: &'a dyn CommPolicy) -> Self { Self { dataver, commissioning_policy, } } + /// Adapt the handler instance to the generic `rs-matter` `Handler` trait pub const fn adapt(self) -> HandlerAdaptor { HandlerAdaptor(self) } } impl ClusterHandler for GenCommHandler<'_> { - const CLUSTER: Cluster<'static> = FULL_CLUSTER; + const CLUSTER: Cluster<'static> = FULL_CLUSTER.with_revision(1).with_attrs(with!(required)); fn dataver(&self) -> u32 { self.dataver.get() diff --git a/rs-matter/src/data_model/sdm/gen_diag.rs b/rs-matter/src/data_model/sdm/gen_diag.rs new file mode 100644 index 00000000..465dc68e --- /dev/null +++ b/rs-matter/src/data_model/sdm/gen_diag.rs @@ -0,0 +1,275 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! This module contains the implementation of the General Diagnostics cluster and its handler. + +use core::net::{Ipv4Addr, Ipv6Addr}; + +use crate::data_model::objects::{ + ArrayAttributeRead, Cluster, Dataver, InvokeContext, ReadContext, +}; +use crate::error::{Error, ErrorCode}; +use crate::tlv::{Nullable, Octets, TLVBuilder, TLVBuilderParent}; +use crate::with; + +pub use crate::data_model::clusters::general_diagnostics::*; + +/// A structure describing the network interface info as returned by the `GenDiag` trait. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct NetifInfo<'a> { + /// The name of the network interface. + pub name: &'a str, + /// Whether the network interface is operational from the POV of Matter. + /// I.e. the node is advertising itself operationally on this interface. + pub operational: bool, + /// Whether any Ipv4 off-premise services used by the node are reachable on this interface. + /// `None` if the node does not use such services or it is unclear if these are reachable. + pub offprem_svc_reachable_ipv4: Option, + /// Whether any Ipv6 off-premise services used by the node are reachable on this interface. + /// `None` if the node does not use such services or it is unclear if these are reachable. + pub offprem_svc_reachable_ipv6: Option, + /// 0-padded hardware address of the network interface. + /// (i.e. either 6 bytes for Eth and Wifi (prefixed with two zero bytes) or 8 bytes for Thread) + pub hw_addr: &'a [u8; 8], + /// The IPv4 addresses assigned to the network interface. + pub ipv4_addrs: &'a [Ipv4Addr], + /// The IPv6 addresses assigned to the network interface. + pub ipv6_addrs: &'a [Ipv6Addr], + /// The type of the network interface. + pub netif_type: InterfaceTypeEnum, +} + +impl NetifInfo<'_> { + /// Read the network interface info into the given `NetworkInterfaceBuilder`. + fn read_into( + &self, + builder: NetworkInterfaceBuilder

, + ) -> Result { + builder + .name(self.name)? + .is_operational(self.operational)? + .off_premise_services_reachable_i_pv_4(Nullable::new(self.offprem_svc_reachable_ipv4))? + .off_premise_services_reachable_i_pv_6(Nullable::new(self.offprem_svc_reachable_ipv6))? + .hardware_address(Octets::new(self.hw_addr))? + .i_pv_4_addresses()? + .with(|mut builder| { + for addr in self.ipv4_addrs { + builder = builder.push(Octets::new(&addr.octets()))?; + } + + builder.end() + })? + .i_pv_6_addresses()? + .with(|mut builder| { + for addr in self.ipv6_addrs { + builder = builder.push(Octets::new(&addr.octets()))?; + } + + builder.end() + })? + .r#type(self.netif_type)? + .end() + } +} + +/// A trait to which the system implementation of the General Diagnostics Matter cluster +/// delegates for information. +pub trait GenDiag { + /// Get the reboot count of the node. + fn reboot_count(&self) -> Result; + + /// Get the uptime of the node in seconds. + fn uptime_secs(&self) -> Result; + + /// Whether the test event triggers are enabled. + /// Check the Matter Core spec for more info. + fn test_event_triggers_enabled(&self) -> Result; + + /// Trigger a test event. + /// Check the Matter Core spec for more info. + fn test_event_trigger(&self, key: &[u8], trigger: u64) -> Result<(), Error>; +} + +impl GenDiag for &T +where + T: GenDiag, +{ + fn reboot_count(&self) -> Result { + (**self).reboot_count() + } + + fn uptime_secs(&self) -> Result { + (**self).uptime_secs() + } + + fn test_event_triggers_enabled(&self) -> Result { + (**self).test_event_triggers_enabled() + } + + fn test_event_trigger(&self, key: &[u8], trigger: u64) -> Result<(), Error> { + (**self).test_event_trigger(key, trigger) + } +} + +/// A dummy implementation of the `GenDiag` trait. +impl GenDiag for () { + fn reboot_count(&self) -> Result { + Ok(0) + } + + fn uptime_secs(&self) -> Result { + Ok(u32::MAX as _) + } + + fn test_event_triggers_enabled(&self) -> Result { + Ok(false) + } + + fn test_event_trigger(&self, _key: &[u8], _trigger: u64) -> Result<(), Error> { + Err(ErrorCode::ConstraintError.into()) + } +} + +pub trait NetifDiag { + /// Iterate over the network interfaces and call the given function for each of them. + /// Call the function with `None` at the end. + fn netifs(&self, f: &mut dyn FnMut(&NetifInfo) -> Result<(), Error>) -> Result<(), Error>; +} + +impl NetifDiag for &T +where + T: NetifDiag, +{ + fn netifs(&self, f: &mut dyn FnMut(&NetifInfo) -> Result<(), Error>) -> Result<(), Error> { + (*self).netifs(f) + } +} + +/// A dummy implementation of the `NetifDiag` trait. +impl NetifDiag for () { + fn netifs(&self, _f: &mut dyn FnMut(&NetifInfo) -> Result<(), Error>) -> Result<(), Error> { + Ok(()) + } +} + +/// The system implementation of a handler for the General Diagnostics Matter cluster. +#[derive(Clone)] +pub struct GenDiagHandler<'a> { + dataver: Dataver, + diag: &'a dyn GenDiag, + netif_diag: &'a dyn NetifDiag, +} + +impl<'a> GenDiagHandler<'a> { + /// Create a new instance of `GenDiagHandler` with the given `Dataver`. + pub const fn new( + dataver: Dataver, + diag: &'a dyn GenDiag, + netif_diag: &'a dyn NetifDiag, + ) -> Self { + Self { + dataver, + diag, + netif_diag, + } + } + + /// Adapt the handler instance to the generic `rs-matter` `Handler` trait + pub const fn adapt(self) -> HandlerAdaptor { + HandlerAdaptor(self) + } +} + +impl ClusterHandler for GenDiagHandler<'_> { + const CLUSTER: Cluster<'static> = FULL_CLUSTER + .with_attrs(with!(required)) + .with_cmds(with!(CommandId::TestEventTrigger)); + + fn dataver(&self) -> u32 { + self.dataver.get() + } + + fn dataver_changed(&self) { + self.dataver.changed(); + } + + fn network_interfaces( + &self, + _ctx: &ReadContext<'_>, + builder: ArrayAttributeRead, NetworkInterfaceBuilder

>, + ) -> Result { + match builder { + ArrayAttributeRead::ReadAll(builder) => { + let mut builder = Some(builder); + self.netif_diag.netifs(&mut |netif| { + builder = Some(netif.read_into(unwrap!(builder.take()).push()?)?); + + Ok(()) + })?; + + builder.take().unwrap().end() + } + ArrayAttributeRead::ReadOne(index, builder) => { + let mut builder = Some(builder); + let mut parent_builder = None; + let mut current = 0; + + self.netif_diag.netifs(&mut |netif| { + if current == index as usize { + parent_builder = Some(netif.read_into(unwrap!(builder.take()))?); + } + + current += 1; + + Ok(()) + })?; + + parent_builder.take().ok_or_else(|| { + ErrorCode::InvalidAction.into() // TODO + }) + } + } + } + + fn reboot_count(&self, _ctx: &ReadContext<'_>) -> Result { + self.diag.reboot_count() + } + + fn test_event_triggers_enabled(&self, _ctx: &ReadContext<'_>) -> Result { + self.diag.test_event_triggers_enabled() + } + + fn handle_test_event_trigger( + &self, + _ctx: &InvokeContext<'_>, + request: TestEventTriggerRequest<'_>, + ) -> Result<(), Error> { + let key = request.enable_key()?.0; + let trigger = request.event_trigger()?; + + self.diag.test_event_trigger(key, trigger) + } + + fn handle_time_snapshot( + &self, + _ctx: &InvokeContext<'_>, + _response: TimeSnapshotResponseBuilder

, + ) -> Result { + Err(ErrorCode::CommandNotFound.into()) + } +} diff --git a/rs-matter/src/data_model/sdm/general_diagnostics.rs b/rs-matter/src/data_model/sdm/general_diagnostics.rs deleted file mode 100644 index 37729283..00000000 --- a/rs-matter/src/data_model/sdm/general_diagnostics.rs +++ /dev/null @@ -1,164 +0,0 @@ -/* - * - * Copyright (c) 2023 Project CHIP Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -use strum::{EnumDiscriminants, FromRepr}; - -use crate::data_model::objects::{ - Access, AttrDataEncoder, AttrType, Attribute, Cluster, CmdDataEncoder, Command, Dataver, - Handler, InvokeContext, NonBlockingHandler, Quality, ReadContext, WriteContext, -}; -use crate::error::{Error, ErrorCode}; -use crate::{attribute_enum, attributes, cmd_enter, command_enum, commands, with}; - -pub const ID: u32 = 0x0033; - -#[derive(FromRepr, EnumDiscriminants, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u32)] -pub enum Attributes { - NetworkInterfaces(()) = 0x00, - RebootCount(AttrType) = 0x01, - TestEventTriggersEnabled(AttrType) = 0x08, -} - -attribute_enum!(Attributes); - -#[derive(FromRepr, EnumDiscriminants, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u32)] -pub enum Commands { - TestEventTrigger = 0x0, -} - -command_enum!(Commands); - -pub const CLUSTER: Cluster<'static> = Cluster { - id: ID as _, - revision: 1, - feature_map: 0, - attributes: attributes!( - Attribute::new( - AttributesDiscriminants::NetworkInterfaces as _, - Access::RV, - Quality::NONE, - ), - Attribute::new( - AttributesDiscriminants::RebootCount as _, - Access::RV, - Quality::PERSISTENT, - ), - Attribute::new( - AttributesDiscriminants::TestEventTriggersEnabled as _, - Access::RV, - Quality::NONE, - ), - ), - commands: commands!(Command::new( - CommandsDiscriminants::TestEventTrigger as _, - None, - Access::WA, - )), - with_attrs: with!(all), - with_cmds: with!(all), -}; - -#[derive(Debug, Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct GenDiagCluster { - data_ver: Dataver, -} - -impl GenDiagCluster { - pub const fn new(data_ver: Dataver) -> Self { - Self { data_ver } - } - - pub fn read( - &self, - ctx: &ReadContext<'_>, - encoder: AttrDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - let attr = ctx.attr(); - - if let Some(writer) = encoder.with_dataver(self.data_ver.get())? { - if attr.is_system() { - CLUSTER.read(attr.attr_id, writer) - } else { - match attr.attr_id.try_into()? { - Attributes::RebootCount(codec) => codec.encode(writer, 1), - other => { - error!("Attribute {:?} not supported", other); - Err(ErrorCode::AttributeNotFound.into()) - } - } - } - } else { - Ok(()) - } - } - - pub fn write(&self, ctx: &WriteContext<'_>) -> Result<(), Error> { - ctx.attr().check_dataver(self.data_ver.get())?; - - self.data_ver.changed(); - - Ok(()) - } - - pub fn invoke( - &self, - ctx: &InvokeContext<'_>, - _encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - let cmd = ctx.cmd(); - - match cmd.cmd_id.try_into()? { - Commands::TestEventTrigger => { - cmd_enter!("TestEventTrigger: Not yet supported"); - } - } - - self.data_ver.changed(); - - Ok(()) - } -} - -impl Handler for GenDiagCluster { - fn read( - &self, - ctx: &ReadContext<'_>, - encoder: AttrDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - GenDiagCluster::read(self, ctx, encoder) - } - - fn write(&self, ctx: &WriteContext<'_>) -> Result<(), Error> { - GenDiagCluster::write(self, ctx) - } - - fn invoke( - &self, - ctx: &InvokeContext<'_>, - encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - GenDiagCluster::invoke(self, ctx, encoder) - } -} - -// TODO: Might be removed once the `on` member is externalized -impl NonBlockingHandler for GenDiagCluster {} diff --git a/rs-matter/src/data_model/sdm/group_key_management.rs b/rs-matter/src/data_model/sdm/group_key_management.rs deleted file mode 100644 index d72b290c..00000000 --- a/rs-matter/src/data_model/sdm/group_key_management.rs +++ /dev/null @@ -1,171 +0,0 @@ -/* - * - * Copyright (c) 2023 Project CHIP Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -use strum::{EnumDiscriminants, FromRepr}; - -use crate::data_model::objects::{ - Access, AttrDataEncoder, AttrType, Attribute, Cluster, CmdDataEncoder, Command, Dataver, - Handler, InvokeContext, NonBlockingHandler, Quality, ReadContext, WriteContext, -}; -use crate::error::{Error, ErrorCode}; -use crate::{attribute_enum, attributes, cmd_enter, command_enum, commands, with}; - -pub const ID: u32 = 0x003F; - -#[derive(FromRepr, EnumDiscriminants, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u32)] -pub enum Attributes { - GroupKeyMap(()) = 0x00, - GroupTable(()) = 0x01, - MaxGroupsPerFabric(AttrType) = 0x02, - MaxGroupKeysPerFabric(AttrType) = 0x03, -} - -attribute_enum!(Attributes); - -#[derive(FromRepr, EnumDiscriminants, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u32)] -pub enum Commands { - KeySetWrite = 0x0, -} - -command_enum!(Commands); - -pub const CLUSTER: Cluster<'static> = Cluster { - id: ID as _, - revision: 1, - feature_map: 0, - attributes: attributes!( - Attribute::new( - AttributesDiscriminants::GroupKeyMap as _, - Access::RWFVM, - Quality::PERSISTENT, - ), - Attribute::new( - AttributesDiscriminants::GroupTable as _, - Access::RF.union(Access::NEED_VIEW), - Quality::NONE, - ), - Attribute::new( - AttributesDiscriminants::MaxGroupsPerFabric as _, - Access::RV, - Quality::FIXED, - ), - Attribute::new( - AttributesDiscriminants::MaxGroupKeysPerFabric as _, - Access::RV, - Quality::FIXED, - ), - ), - commands: commands!(Command::new( - CommandsDiscriminants::KeySetWrite as _, - None, - Access::WA, - ),), - with_attrs: with!(all), - with_cmds: with!(all), -}; - -#[derive(Debug, Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct GrpKeyMgmtCluster { - data_ver: Dataver, -} - -impl GrpKeyMgmtCluster { - pub const fn new(data_ver: Dataver) -> Self { - Self { data_ver } - } - - pub fn read( - &self, - ctx: &ReadContext<'_>, - encoder: AttrDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - let attr = ctx.attr(); - - if let Some(writer) = encoder.with_dataver(self.data_ver.get())? { - if attr.is_system() { - CLUSTER.read(attr.attr_id, writer) - } else { - match attr.attr_id.try_into()? { - Attributes::MaxGroupsPerFabric(codec) => codec.encode(writer, 1), - Attributes::MaxGroupKeysPerFabric(codec) => codec.encode(writer, 1), - other => { - error!("Attribute {:?} not supported", other); - Err(ErrorCode::AttributeNotFound.into()) - } - } - } - } else { - Ok(()) - } - } - - pub fn write(&self, ctx: &WriteContext<'_>) -> Result<(), Error> { - ctx.attr().check_dataver(self.data_ver.get())?; - - self.data_ver.changed(); - - Ok(()) - } - - pub fn invoke( - &self, - ctx: &InvokeContext<'_>, - _encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - let cmd = ctx.cmd(); - - match cmd.cmd_id.try_into()? { - Commands::KeySetWrite => { - cmd_enter!("KeySetWrite: Not yet supported"); - } - } - - self.data_ver.changed(); - - Ok(()) - } -} - -impl Handler for GrpKeyMgmtCluster { - fn read( - &self, - ctx: &ReadContext<'_>, - encoder: AttrDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - GrpKeyMgmtCluster::read(self, ctx, encoder) - } - - fn write(&self, ctx: &WriteContext<'_>) -> Result<(), Error> { - GrpKeyMgmtCluster::write(self, ctx) - } - - fn invoke( - &self, - ctx: &InvokeContext<'_>, - encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - GrpKeyMgmtCluster::invoke(self, ctx, encoder) - } -} - -// TODO: Might be removed once the `on` member is externalized -impl NonBlockingHandler for GrpKeyMgmtCluster {} diff --git a/rs-matter/src/data_model/sdm/grp_key_mgmt.rs b/rs-matter/src/data_model/sdm/grp_key_mgmt.rs new file mode 100644 index 00000000..c01cd1f8 --- /dev/null +++ b/rs-matter/src/data_model/sdm/grp_key_mgmt.rs @@ -0,0 +1,137 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! This module contains the implementation of the Group Key Management cluster and its handler. +//! Note that the implementation is currently stubbed out, as `rs-matter` does not support Groups yet. + +use crate::data_model::objects::{ + ArrayAttributeRead, ArrayAttributeWrite, Cluster, Dataver, InvokeContext, ReadContext, +}; +use crate::error::{Error, ErrorCode}; +use crate::tlv::TLVBuilderParent; +use crate::with; + +pub use crate::data_model::clusters::group_key_management::*; + +/// The system implementation of a handler for the Group Key Management Matter cluster. +/// This is a stub implementation, as `rs-matter` does not support Groups yet. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct GrpKeyMgmtHandler { + dataver: Dataver, +} + +impl GrpKeyMgmtHandler { + /// Creates a new instance of the `GrpKeyMgmtHandler` with the given `Dataver`. + pub const fn new(dataver: Dataver) -> Self { + Self { dataver } + } + + /// Adapt the handler instance to the generic `rs-matter` `Handler` trait + pub const fn adapt(self) -> HandlerAdaptor { + HandlerAdaptor(self) + } +} + +impl ClusterHandler for GrpKeyMgmtHandler { + const CLUSTER: Cluster<'static> = FULL_CLUSTER.with_attrs(with!(required)); + + fn dataver(&self) -> u32 { + self.dataver.get() + } + + fn dataver_changed(&self) { + self.dataver.changed(); + } + + fn group_key_map( + &self, + _ctx: &ReadContext<'_>, + builder: ArrayAttributeRead, GroupKeyMapStructBuilder

>, + ) -> Result { + match builder { + ArrayAttributeRead::ReadAll(builder) => builder.end(), + ArrayAttributeRead::ReadOne(_, _) => Err(ErrorCode::InvalidAction.into()), // TODO + } + } + + fn group_table( + &self, + _ctx: &ReadContext<'_>, + builder: ArrayAttributeRead< + GroupInfoMapStructArrayBuilder

, + GroupInfoMapStructBuilder

, + >, + ) -> Result { + match builder { + ArrayAttributeRead::ReadAll(builder) => builder.end(), + ArrayAttributeRead::ReadOne(_, _) => Err(ErrorCode::InvalidAction.into()), // TODO + } + } + + fn max_groups_per_fabric(&self, _ctx: &ReadContext<'_>) -> Result { + Ok(1) + } + + fn max_group_keys_per_fabric(&self, _ctx: &ReadContext<'_>) -> Result { + Ok(1) + } + + fn set_group_key_map( + &self, + _ctx: &crate::data_model::objects::WriteContext<'_>, + _value: ArrayAttributeWrite< + crate::tlv::TLVArray<'_, GroupKeyMapStruct<'_>>, + GroupKeyMapStruct<'_>, + >, + ) -> Result<(), Error> { + Ok(()) + } + + fn handle_key_set_write( + &self, + _ctx: &InvokeContext<'_>, + _request: KeySetWriteRequest<'_>, + ) -> Result<(), Error> { + Ok(()) + } + + fn handle_key_set_read( + &self, + _ctx: &InvokeContext<'_>, + _request: KeySetReadRequest<'_>, + _response: KeySetReadResponseBuilder

, + ) -> Result { + Err(ErrorCode::NotFound.into()) + } + + fn handle_key_set_remove( + &self, + _ctx: &InvokeContext<'_>, + _request: KeySetRemoveRequest<'_>, + ) -> Result<(), Error> { + Ok(()) + } + + fn handle_key_set_read_all_indices( + &self, + _ctx: &InvokeContext<'_>, + response: KeySetReadAllIndicesResponseBuilder

, + ) -> Result { + response.group_key_set_i_ds()?.end()?.end() + } +} diff --git a/rs-matter/src/data_model/sdm/mod.rs b/rs-matter/src/data_model/sdm/mod.rs index bb17f81f..51a48620 100644 --- a/rs-matter/src/data_model/sdm/mod.rs +++ b/rs-matter/src/data_model/sdm/mod.rs @@ -15,14 +15,13 @@ * limitations under the License. */ -pub mod admin_commissioning; +pub mod adm_comm; pub mod dev_att; -pub mod eth_nw_diag; -pub mod failsafe; +pub mod eth_diag; pub mod gen_comm; -pub mod general_diagnostics; -pub mod group_key_management; +pub mod gen_diag; +pub mod grp_key_mgmt; +pub mod net_comm; pub mod noc; -pub mod nw_commissioning; -pub mod thread_nw_diagnostics; -pub mod wifi_nw_diagnostics; +pub mod thread_diag; +pub mod wifi_diag; diff --git a/rs-matter/src/data_model/sdm/net_comm.rs b/rs-matter/src/data_model/sdm/net_comm.rs new file mode 100644 index 00000000..63ddacf9 --- /dev/null +++ b/rs-matter/src/data_model/sdm/net_comm.rs @@ -0,0 +1,1123 @@ +/* + * + * Copyright (c) 2020-2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! This module contains the implementation of the Network Commissioning cluster and its handler. + +use core::fmt; + +use crate::data_model::networks::wireless::{Thread, ThreadTLV, MAX_WIRELESS_NETWORK_ID_LEN}; +use crate::data_model::objects::{ + ArrayAttributeRead, Cluster, Dataver, InvokeContext, ReadContext, WriteContext, +}; +use crate::error::{Error, ErrorCode}; +use crate::tlv::{ + Nullable, NullableBuilder, Octets, OctetsBuilder, TLVBuilder, TLVBuilderParent, TLVWrite, + ToTLVArrayBuilder, ToTLVBuilder, +}; +use crate::{clusters, with}; + +pub use crate::data_model::clusters::network_commissioning::*; + +/// Network type supported by the `NetCtl` implementations +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum NetworkType { + Ethernet, + Wifi, + Thread, +} + +impl NetworkType { + /// Return an instance of the Network Commissioning cluster meta-data for the given network type. + pub const fn cluster(&self) -> Cluster<'static> { + match self { + Self::Ethernet => FULL_CLUSTER + .with_revision(1) + .with_features(Feature::ETHERNET_NETWORK_INTERFACE.bits()) + .with_attrs(with!(required)) + .with_cmds(with!()), + Self::Wifi => FULL_CLUSTER + .with_revision(1) + .with_features(Feature::WI_FI_NETWORK_INTERFACE.bits()) + .with_attrs(with!(required; AttributeId::ScanMaxTimeSeconds | AttributeId::ConnectMaxTimeSeconds | AttributeId::SupportedWiFiBands)) + .with_cmds(with!(CommandId::AddOrUpdateWiFiNetwork | CommandId::ScanNetworks | CommandId::RemoveNetwork | CommandId::ConnectNetwork | CommandId::ReorderNetwork)), + Self::Thread => FULL_CLUSTER + .with_revision(1) + .with_features(Feature::THREAD_NETWORK_INTERFACE.bits()) + .with_attrs(with!(required; AttributeId::ScanMaxTimeSeconds | AttributeId::ConnectMaxTimeSeconds | AttributeId::ThreadVersion | AttributeId::SupportedThreadFeatures)) + .with_cmds(with!(CommandId::AddOrUpdateThreadNetwork | CommandId::ScanNetworks | CommandId::RemoveNetwork | CommandId::ConnectNetwork | CommandId::ReorderNetwork)), + } + } + + /// Return the root clusters necessary for the given network type + /// These are the Network Commissioning cluster tailored with attributes suitable for the + /// concrete nework type as well as one of the Ethernet Network Diagnostics, WiFi Network + /// Diagnostics or Thread Network Diagnostics clusters. + pub const fn root_clusters(&self) -> &'static [Cluster<'static>] { + static ETH: &[Cluster<'static>] = clusters!(eth;); + static WIFI: &[Cluster<'static>] = clusters!(wifi;); + static THREAD: &[Cluster<'static>] = clusters!(thread;); + + match self { + Self::Ethernet => ETH, + Self::Wifi => WIFI, + Self::Thread => THREAD, + } + } +} + +/// Network information as returned by the `Networks` trait +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct NetworkInfo<'a> { + /// The network ID of the network + pub network_id: &'a [u8], + /// Whether this network is currently connected + pub connected: bool, +} + +impl NetworkInfo<'_> { + /// Read the network information into the given `NetworkInfoStructBuilder`. + fn read_into( + &self, + builder: NetworkInfoStructBuilder

, + ) -> Result { + builder + .network_id(Octets::new(self.network_id))? + .connected(self.connected)? + .network_identifier(None)? + .client_identifier(None)? + .end() + } +} + +/// Network scan information as returned by the `NetCtl::scan` method +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum NetworkScanInfo<'a> { + /// WiFi network scan information when the network type is `NetworkType::Wifi` + Wifi { + security: WiFiSecurityBitmap, + ssid: &'a [u8], + bssid: &'a [u8], + channel: u16, + band: WiFiBandEnum, + rssi: i8, + }, + /// Thread network scan information when the network type is `NetworkType::Thread` + Thread { + pan_id: u16, + ext_pan_id: u64, + network_name: &'a str, + channel: u16, + version: u8, + ext_addr: &'a [u8], + rssi: i8, + lqi: u8, + }, +} + +impl NetworkScanInfo<'_> { + /// Read the network scan information into the given `NetworkScanInfoStructBuilder`. + /// If the network type is not `NetworkType::Wifi`, this method will panic. + pub fn wifi_read_into( + &self, + builder: WiFiInterfaceScanResultStructBuilder

, + ) -> Result { + let NetworkScanInfo::Wifi { + security, + ssid, + bssid, + channel, + band, + rssi, + } = self + else { + panic!("Wifi scan info expected"); + }; + + builder + .security(*security)? + .ssid(Octets::new(ssid))? + .bssid(Octets::new(bssid))? + .channel(*channel)? + .wi_fi_band(*band)? + .rssi(*rssi)? + .end() + } + + /// Read the network scan information into the given `ThreadInterfaceScanResultStructBuilder`. + /// If the network type is not `NetworkType::Thread`, this method will panic. + pub fn thread_read_into( + &self, + builder: ThreadInterfaceScanResultStructBuilder

, + ) -> Result { + let NetworkScanInfo::Thread { + pan_id, + ext_pan_id: extended_pan_id, + network_name, + channel, + version, + ext_addr, + rssi, + lqi, + } = self + else { + panic!("Thread scan info expected"); + }; + + builder + .pan_id(*pan_id)? + .extended_pan_id(*extended_pan_id)? + .network_name(network_name)? + .channel(*channel)? + .version(*version)? + .extended_address(Octets::new(ext_addr))? + .rssi(*rssi)? + .lqi(*lqi)? + .end() + } +} + +/// Wireless credentials used for connecting to a network +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub enum WirelessCreds<'a> { + /// WiFi credentials + Wifi { ssid: &'a [u8], pass: &'a [u8] }, + /// Thread credentials + Thread { dataset_tlv: &'a [u8] }, +} + +impl WirelessCreds<'_> { + /// Return the network ID of the credentials + /// For Wifi networks, this is the SSID + /// For Thread networks, this is the extended PAN ID + pub fn id(&self) -> Result<&[u8], Error> { + match self { + WirelessCreds::Wifi { ssid, .. } => Ok(ssid), + WirelessCreds::Thread { dataset_tlv } => Thread::dataset_ext_pan_id(dataset_tlv), + } + } + + /// Check if the credentials match the given network type + pub fn check_match(&self, net_type: NetworkType) -> Result<(), Error> { + match self { + WirelessCreds::Wifi { .. } if matches!(net_type, NetworkType::Wifi) => Ok(()), + WirelessCreds::Thread { .. } if matches!(net_type, NetworkType::Thread) => Ok(()), + _ => Err(ErrorCode::InvalidAction.into()), + } + } +} + +impl fmt::Display for WirelessCreds<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + WirelessCreds::Wifi { ssid, .. } => write!( + f, + "SSID({})", + core::str::from_utf8(ssid).ok().unwrap_or("???") + ), + WirelessCreds::Thread { dataset_tlv } => write!( + f, + "ExtEpanId({:?})", + ThreadTLV::new(dataset_tlv).ext_pan_id().ok().unwrap_or(&[]) + ), + } + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for WirelessCreds<'_> { + fn format(&self, fmt: defmt::Formatter) { + match self { + WirelessCreds::Wifi { ssid, .. } => defmt::write!( + fmt, + "SSID({})", + core::str::from_utf8(ssid).ok().unwrap_or("???") + ), + WirelessCreds::Thread { dataset_tlv } => defmt::write!( + fmt, + "ExtEpanId({:?})", + ThreadTLV::new(dataset_tlv).ext_pan_id().ok().unwrap_or(&[]) + ), + } + } +} + +/// Network error type for the `Networks` trait +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum NetworksError { + NetworkIdNotFound, + DuplicateNetworkId, + OutOfRange, + BoundsExceeded, + Other(Error), +} + +impl From for NetworksError { + fn from(err: Error) -> Self { + NetworksError::Other(err) + } +} + +/// Network error type for the `NetCtl` trait +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum NetCtlError { + NetworkNotFound, + UnsupportedSecurity, + AuthFailure, + OtherConnectionFailure, + IpBindFailed, + IpV6Failed, + Other(Error), +} + +impl From for NetCtlError { + fn from(err: Error) -> Self { + NetCtlError::Other(err) + } +} + +impl NetworkCommissioningStatusEnum { + /// Map the result of a network storage operation to a `NetworkCommissioningStatusEnum` if the operation + /// failed, or return the index of the network if it succeeded. + pub fn map( + result: Result, + ) -> Result<(NetworkCommissioningStatusEnum, Option, Option), Error> { + if let Some((status, err_code)) = NetworkCommissioningStatusEnum::map_status(&result) { + Ok((status, err_code, result.ok())) + } else { + match result { + Err(NetworksError::Other(e)) => Err(e), + _ => unreachable!(), + } + } + } + + /// Map the result of a network storage operation to a `NetworkCommissioningStatusEnum` and error code if the operation + /// failed, or return the index of the network if it succeeded. + pub fn map_status( + result: &Result, + ) -> Option<(NetworkCommissioningStatusEnum, Option)> { + match result { + Ok(_) => Some((NetworkCommissioningStatusEnum::Success, None)), + Err(NetworksError::NetworkIdNotFound) => { + Some((NetworkCommissioningStatusEnum::NetworkIDNotFound, None)) + } + Err(NetworksError::DuplicateNetworkId) => { + Some((NetworkCommissioningStatusEnum::DuplicateNetworkID, None)) + } + Err(NetworksError::OutOfRange) => { + Some((NetworkCommissioningStatusEnum::OutOfRange, None)) + } + Err(NetworksError::BoundsExceeded) => { + Some((NetworkCommissioningStatusEnum::BoundsExceeded, None)) + } + Err(NetworksError::Other(_)) => None, + } + } + + /// Map the result of a network IO operation to a `NetworkCommissioningStatusEnum` if the operation + /// failed, or return the index of the network if it succeeded. + pub fn map_ctl( + result: Result, + ) -> Result<(NetworkCommissioningStatusEnum, Option, Option), Error> { + if let Some((status, err_code)) = NetworkCommissioningStatusEnum::map_ctl_status(&result) { + Ok((status, err_code, result.ok())) + } else { + match result { + Err(NetCtlError::Other(e)) => Err(e), + _ => unreachable!(), + } + } + } + + /// Map the result of a network IO operation to a `NetworkCommissioningStatusEnum` and error code if the operation + /// failed, or return the index of the network if it succeeded. + pub fn map_ctl_status( + result: &Result, + ) -> Option<(NetworkCommissioningStatusEnum, Option)> { + match result { + Ok(_) => Some((NetworkCommissioningStatusEnum::Success, None)), + Err(NetCtlError::UnsupportedSecurity) => { + Some((NetworkCommissioningStatusEnum::UnsupportedSecurity, None)) + } + Err(NetCtlError::AuthFailure) => { + Some((NetworkCommissioningStatusEnum::AuthFailure, None)) + } + Err(NetCtlError::IpBindFailed) => { + Some((NetworkCommissioningStatusEnum::IPBindFailed, None)) + } + Err(NetCtlError::IpV6Failed) => { + Some((NetworkCommissioningStatusEnum::IPV6Failed, None)) + } + Err(NetCtlError::OtherConnectionFailure) => { + Some((NetworkCommissioningStatusEnum::OtherConnectionFailure, None)) + } + Err(NetCtlError::NetworkNotFound) => { + Some((NetworkCommissioningStatusEnum::NetworkNotFound, None)) + } + Err(NetCtlError::Other(_)) => None, + } + } + + /// Read the networking status and the provided optional index into the given `NetworkConfigResponseBuilder`. + pub fn read_into( + &self, + index: Option, + builder: NetworkConfigResponseBuilder

, + ) -> Result { + builder + .networking_status(*self)? + .debug_text(None)? + .network_index(index)? + .client_identity(None)? + .possession_signature(None)? + .end() + } +} + +/// Trait for managing networks' credentials storage +pub trait Networks { + /// Return the maximum number of networks supported by the implementation + /// + /// For `NetworkType::Ethernet` this method should always return 1 + fn max_networks(&self) -> Result; + + /// Iterate over the networks recorded in the implementation and call the provided function for each network + fn networks(&self, f: &mut dyn FnMut(&NetworkInfo) -> Result<(), Error>) -> Result<(), Error>; + + /// Get the credentials for the given network ID by calling the provided function + /// + /// For `NetworkType::Ethernet` this method should always fail with an error. + /// + /// The function will be called with the credentials for the network ID, or an error if the network ID is not found. + /// + /// Return the index of the network ID if found, or an error if not found. + fn creds( + &self, + network_id: &[u8], + f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>, + ) -> Result; + + /// Return the next credentials after the ones corresponding to the given network ID by calling the provided function + /// + /// For `NetworkType::Ethernet` this method should always fail with an error. + /// + /// If the network ID is `None` or credentials with the provided network ID cannot be found, + /// the first credentials will be returned. + /// + /// If the credentials corresponding to the network ID are the last ones recorded in the `Netwrks` trait implementation, + /// the method will wrap-over and will return the first credentials or even the same credentials again if there is only one + /// recorded network. + /// + /// Return `true` if the credentials were found, `false` otherwise. + fn next_creds( + &self, + last_network_id: Option<&[u8]>, + f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>, + ) -> Result; + + /// Return whether the network interface is enabled + fn enabled(&self) -> Result; + + /// Set the network interface enabled or disabled + fn set_enabled(&self, enabled: bool) -> Result<(), Error>; + + /// Add or update the credentials for the given network ID + /// + /// For `NetworkType::Ethernet` this method should always fail with an error. + /// + /// The network ID is derived from the credentials. + /// + /// Return the index of the network ID if it was added or updated, or an error if the operation failed. + fn add_or_update(&self, creds: &WirelessCreds<'_>) -> Result; + + /// Reorder the network with the given index + /// + /// For `NetworkType::Ethernet` this method should always fail with an error. + /// + /// The index is the new index of the network ID. + /// + /// Return the index of the network ID if it was reordered, or an error if the operation failed. + fn reorder(&self, index: u8, network_id: &[u8]) -> Result; + + /// Remove the network with the given network ID + /// + /// For `NetworkType::Ethernet` this method should always fail with an error. + /// + /// Return the index of the network ID if it was removed, or an error if the operation failed. + fn remove(&self, network_id: &[u8]) -> Result; +} + +impl Networks for &T +where + T: Networks, +{ + fn max_networks(&self) -> Result { + (*self).max_networks() + } + + fn networks(&self, f: &mut dyn FnMut(&NetworkInfo) -> Result<(), Error>) -> Result<(), Error> { + (*self).networks(f) + } + + fn creds( + &self, + network_id: &[u8], + f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>, + ) -> Result { + (*self).creds(network_id, f) + } + + fn next_creds( + &self, + last_network_id: Option<&[u8]>, + f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>, + ) -> Result { + (*self).next_creds(last_network_id, f) + } + + fn enabled(&self) -> Result { + (*self).enabled() + } + + fn set_enabled(&self, enabled: bool) -> Result<(), Error> { + (*self).set_enabled(enabled) + } + + fn add_or_update(&self, creds: &WirelessCreds<'_>) -> Result { + (*self).add_or_update(creds) + } + + fn reorder(&self, index: u8, network_id: &[u8]) -> Result { + (*self).reorder(index, network_id) + } + + fn remove(&self, network_id: &[u8]) -> Result { + (*self).remove(network_id) + } +} + +/// Trait for managing network connectivity +pub trait NetCtl { + /// Return the network type of the implementation + fn net_type(&self) -> NetworkType; + + /// Return the maximum time in seconds for connecting to a network + /// + /// Default implementation returns 30 seconds + fn connect_max_time_seconds(&self) -> u8 { + 30 + } + + /// Return the maximum time in seconds for scanning for networks + /// + /// Default implementation returns 30 seconds + fn scan_max_time_seconds(&self) -> u8 { + 30 + } + + /// Return the supported WiFi bands for the implementation in the provided closure + /// + /// Default implementation returns 2.4GHz band only + /// + /// NOTE: This method is only relevant when `net_type` is `NetworkType::Wifi` + fn supported_wifi_bands(&self, mut f: F) -> Result<(), Error> + where + F: FnMut(WiFiBandEnum) -> Result<(), Error>, + { + f(WiFiBandEnum::V2G4) + } + + /// Return the supported Thread features for the implementation + /// + /// Default implementation returns an empty bitmap + /// + /// NOTE: This method is only relevant when `net_type` is `NetworkType::Thread` + fn supported_thread_features(&self) -> ThreadCapabilitiesBitmap { + ThreadCapabilitiesBitmap::empty() + } + + /// Return the Thread version for the implementation + /// + /// Default implementation returns 4 (Thread 1.3.0) + /// + /// NOTE: This method is only relevant when `net_type` is `NetworkType::Thread` + fn thread_version(&self) -> u16 { + 4 + } + + /// Scan for networks and call the provided function for each network found + /// + /// For `NetworkType::Ethernet` this method should always fail with an error. + async fn scan(&self, network: Option<&[u8]>, f: F) -> Result<(), NetCtlError> + where + F: FnMut(&NetworkScanInfo) -> Result<(), Error>; + + /// Connect to the network with the given credentials + /// + /// For `NetworkType::Ethernet` this method should always fail with an error. + async fn connect(&self, creds: &WirelessCreds) -> Result<(), NetCtlError>; +} + +impl NetCtl for &T +where + T: NetCtl, +{ + fn net_type(&self) -> NetworkType { + (*self).net_type() + } + + fn connect_max_time_seconds(&self) -> u8 { + (*self).connect_max_time_seconds() + } + + fn scan_max_time_seconds(&self) -> u8 { + (*self).scan_max_time_seconds() + } + + fn supported_wifi_bands(&self, f: F) -> Result<(), Error> + where + F: FnMut(WiFiBandEnum) -> Result<(), Error>, + { + (*self).supported_wifi_bands(f) + } + + fn supported_thread_features(&self) -> ThreadCapabilitiesBitmap { + (*self).supported_thread_features() + } + + fn thread_version(&self) -> u16 { + (*self).thread_version() + } + + async fn scan(&self, network: Option<&[u8]>, f: F) -> Result<(), NetCtlError> + where + F: FnMut(&NetworkScanInfo) -> Result<(), Error>, + { + (*self).scan(network, f).await + } + + async fn connect(&self, creds: &WirelessCreds<'_>) -> Result<(), NetCtlError> { + (*self).connect(creds).await + } +} + +/// Trait for providing the status of the last `scan` / `connect` operation +pub trait NetCtlStatus { + /// Return the networking status of the last scan or connect operation + /// + /// For `NetworkType::Ethernet` this method should always return `Ok(None)` + fn last_networking_status(&self) -> Result, Error>; + + /// Return the network ID of the last connect operation + /// + /// For `NetworkType::Ethernet` this method should always return `Ok(None)` + fn last_network_id(&self, f: F) -> Result + where + F: FnOnce(Option<&[u8]>) -> Result; + + /// Return the error value of the last connect operation + /// + /// For `NetworkType::Ethernet` this method should always return `Ok(None)` + fn last_connect_error_value(&self) -> Result, Error>; +} + +impl NetCtlStatus for &T +where + T: NetCtlStatus, +{ + fn last_networking_status(&self) -> Result, Error> { + (*self).last_networking_status() + } + + fn last_network_id(&self, f: F) -> Result + where + F: FnOnce(Option<&[u8]>) -> Result, + { + (*self).last_network_id(f) + } + + fn last_connect_error_value(&self) -> Result, Error> { + (*self).last_connect_error_value() + } +} + +/// The system implementation of a handler for the Network Commissioning Matter cluster. +pub struct NetCommHandler<'a, T> { + dataver: Dataver, + networks: &'a dyn Networks, + net_ctl: T, +} + +impl<'a, T> NetCommHandler<'a, T> { + /// Create a new instance of `NetCommHandler` with the given `Dataver`, `Networks` and `NetCtl`. + pub const fn new(dataver: Dataver, networks: &'a dyn Networks, net_ctl: T) -> Self { + Self { + dataver, + networks, + net_ctl, + } + } + + /// Adapt the handler instance to the generic `rs-matter` `AsyncHandler` trait + pub const fn adapt(self) -> HandlerAsyncAdaptor { + HandlerAsyncAdaptor(self) + } +} + +impl ClusterAsyncHandler for NetCommHandler<'_, T> +where + T: NetCtl + NetCtlStatus, +{ + const CLUSTER: Cluster<'static> = NetworkType::Ethernet.cluster(); // TODO + + fn dataver(&self) -> u32 { + self.dataver.get() + } + + fn dataver_changed(&self) { + self.dataver.changed(); + } + + async fn max_networks(&self, _ctx: &ReadContext<'_>) -> Result { + self.networks.max_networks() + } + + async fn connect_max_time_seconds(&self, _ctx: &ReadContext<'_>) -> Result { + Ok(self.net_ctl.connect_max_time_seconds()) + } + + async fn scan_max_time_seconds(&self, _ctx: &ReadContext<'_>) -> Result { + Ok(self.net_ctl.scan_max_time_seconds()) + } + + async fn supported_wi_fi_bands( + &self, + _ctx: &ReadContext<'_>, + builder: ArrayAttributeRead< + ToTLVArrayBuilder, + ToTLVBuilder, + >, + ) -> Result { + match builder { + ArrayAttributeRead::ReadAll(builder) => builder.with(|builder| { + let mut builder = Some(builder); + + self.net_ctl.supported_wifi_bands(|band| { + builder = Some(unwrap!(builder.take()).push(&band)?); + + Ok(()) + })?; + + unwrap!(builder.take()).end() + }), + ArrayAttributeRead::ReadOne(index, builder) => { + let mut current = 0; + let mut builder = Some(builder); + let mut parent = None; + + self.net_ctl.supported_wifi_bands(|band| { + if current == index { + parent = Some(unwrap!(builder.take()).set(&band)?); + } + + current += 1; + + Ok(()) + })?; + + if let Some(parent) = parent { + Ok(parent) + } else { + Err(ErrorCode::ConstraintError.into()) + } + } + } + } + + async fn supported_thread_features( + &self, + _ctx: &ReadContext<'_>, + ) -> Result { + Ok(self.net_ctl.supported_thread_features()) + } + + async fn thread_version(&self, _ctx: &ReadContext<'_>) -> Result { + Ok(self.net_ctl.thread_version()) + } + + async fn networks( + &self, + _ctx: &ReadContext<'_>, + builder: ArrayAttributeRead, NetworkInfoStructBuilder

>, + ) -> Result { + match builder { + ArrayAttributeRead::ReadAll(builder) => builder.with(|builder| { + let mut builder = Some(builder); + + self.networks.networks(&mut |ni| { + builder = Some(ni.read_into(unwrap!(builder.take()).push()?)?); + + Ok(()) + })?; + + unwrap!(builder.take()).end() + }), + ArrayAttributeRead::ReadOne(index, builder) => { + let mut current = 0; + let mut builder = Some(builder); + let mut parent = None; + + self.networks.networks(&mut |ni| { + if current == index { + parent = Some(ni.read_into(unwrap!(builder.take()))?); + } + + current += 1; + + Ok(()) + })?; + + if let Some(parent) = parent { + Ok(parent) + } else { + Err(ErrorCode::ConstraintError.into()) + } + } + } + } + + async fn interface_enabled(&self, _ctx: &ReadContext<'_>) -> Result { + self.networks.enabled() + } + + async fn last_networking_status( + &self, + _ctx: &ReadContext<'_>, + ) -> Result, Error> { + Ok(Nullable::new(self.net_ctl.last_networking_status()?)) + } + + async fn last_network_id( + &self, + _ctx: &ReadContext<'_>, + builder: NullableBuilder>, + ) -> Result { + self.net_ctl.last_network_id(|network_id| { + if let Some(network_id) = network_id { + builder.non_null()?.set(Octets::new(network_id)) + } else { + builder.null() + } + }) + } + + async fn last_connect_error_value( + &self, + _ctx: &ReadContext<'_>, + ) -> Result, Error> { + Ok(Nullable::new(self.net_ctl.last_connect_error_value()?)) + } + + async fn set_interface_enabled( + &self, + _ctx: &WriteContext<'_>, + value: bool, + ) -> Result<(), Error> { + self.networks.set_enabled(value) + } + + async fn handle_scan_networks( + &self, + _ctx: &InvokeContext<'_>, + request: ScanNetworksRequest<'_>, + response: ScanNetworksResponseBuilder

, + ) -> Result { + match self.net_ctl.net_type() { + NetworkType::Thread => { + let mut builder = Some(response); + let mut array_builder = None; + + let (status, _, _) = NetworkCommissioningStatusEnum::map_ctl( + self.net_ctl + .scan( + request + .ssid()? + .as_ref() + .and_then(|ssid| ssid.as_opt_ref()) + .map(|ssid| ssid.0), + |network| { + let abuilder = if let Some(builder) = builder.take() { + builder + .networking_status(NetworkCommissioningStatusEnum::Success)? + .debug_text(None)? + .wi_fi_scan_results()? + .none() + .thread_scan_results()? + .some()? + } else { + unwrap!(array_builder.take()) + }; + + array_builder = Some(network.thread_read_into(abuilder.push()?)?); + + Ok(()) + }, + ) + .await + .map(|_| 0), + )?; + + if let Some(builder) = builder { + builder + .networking_status(status)? + .debug_text(None)? + .wi_fi_scan_results()? + .none() + .thread_scan_results()? + .none() + .end() + } else { + unwrap!(array_builder.take()).end()?.end() + } + } + NetworkType::Wifi => { + let mut builder = Some(response); + let mut array_builder = None; + + let (status, _, _) = NetworkCommissioningStatusEnum::map_ctl( + self.net_ctl + .scan( + request + .ssid()? + .as_ref() + .and_then(|ssid| ssid.as_opt_ref()) + .map(|ssid| ssid.0), + |network| { + let abuilder = if let Some(builder) = builder.take() { + builder + .networking_status(NetworkCommissioningStatusEnum::Success)? + .debug_text(None)? + .wi_fi_scan_results()? + .some()? + } else { + unwrap!(array_builder.take()) + }; + + array_builder = Some(network.wifi_read_into(abuilder.push()?)?); + + Ok(()) + }, + ) + .await + .map(|_| 0), + )?; + + if let Some(builder) = builder { + builder + .networking_status(status)? + .debug_text(None)? + .wi_fi_scan_results()? + .none() + .thread_scan_results()? + .none() + .end() + } else { + unwrap!(array_builder.take()) + .end()? + .thread_scan_results()? + .none() + .end() + } + } + NetworkType::Ethernet => Err(ErrorCode::InvalidAction.into()), // TODO + } + } + + async fn handle_add_or_update_wi_fi_network( + &self, + _ctx: &InvokeContext<'_>, + request: AddOrUpdateWiFiNetworkRequest<'_>, + response: NetworkConfigResponseBuilder

, + ) -> Result { + let (status, _, index) = NetworkCommissioningStatusEnum::map(self.networks.add_or_update( + &WirelessCreds::Wifi { + ssid: request.ssid()?.0, + pass: request.credentials()?.0, + }, + ))?; + + status.read_into(index, response) + } + + async fn handle_add_or_update_thread_network( + &self, + _ctx: &InvokeContext<'_>, + request: AddOrUpdateThreadNetworkRequest<'_>, + response: NetworkConfigResponseBuilder

, + ) -> Result { + let (status, _, index) = NetworkCommissioningStatusEnum::map(self.networks.add_or_update( + &WirelessCreds::Thread { + dataset_tlv: request.operational_dataset()?.0, + }, + ))?; + + status.read_into(index, response) + } + + async fn handle_remove_network( + &self, + _ctx: &InvokeContext<'_>, + request: RemoveNetworkRequest<'_>, + response: NetworkConfigResponseBuilder

, + ) -> Result { + let (status, _, index) = + NetworkCommissioningStatusEnum::map(self.networks.remove(request.network_id()?.0))?; + + status.read_into(index, response) + } + + async fn handle_connect_network( + &self, + _ctx: &InvokeContext<'_>, + request: ConnectNetworkRequest<'_>, + mut response: ConnectNetworkResponseBuilder

, + ) -> Result { + if request.network_id()?.0.len() > MAX_WIRELESS_NETWORK_ID_LEN { + return Err(ErrorCode::InvalidAction.into()); + } + + let (status, err_code) = match self.net_ctl.net_type() { + NetworkType::Thread => { + let dataset_buf = response.writer().available_space(); + let mut dataset_len = 0; + + let (mut status, mut err_code, _) = NetworkCommissioningStatusEnum::map( + self.networks.creds(request.network_id()?.0, &mut |creds| { + let WirelessCreds::Thread { dataset_tlv } = creds else { + error!("Thread creds expected"); + return Err(ErrorCode::InvalidAction.into()); + }; + + if dataset_tlv.len() > dataset_buf.len() { + error!("Dataset too large"); + return Err(ErrorCode::ConstraintError.into()); + } + + dataset_buf[..dataset_tlv.len()].copy_from_slice(dataset_tlv); + dataset_len = dataset_tlv.len(); + + Ok(()) + }), + )?; + + if matches!(status, NetworkCommissioningStatusEnum::Success) { + (status, err_code, _) = NetworkCommissioningStatusEnum::map_ctl( + self.net_ctl + .connect(&WirelessCreds::Thread { + dataset_tlv: &dataset_buf[..dataset_len], + }) + .await, + )?; + } + + (status, err_code) + } + NetworkType::Wifi => { + let buf = response.writer().available_space(); + let (ssid_buf, pass_buf) = buf.split_at_mut(buf.len() / 2); + let mut ssid_len = 0; + let mut pass_len = 0; + + let (mut status, mut err_code, _) = NetworkCommissioningStatusEnum::map( + self.networks.creds(request.network_id()?.0, &mut |creds| { + let WirelessCreds::Wifi { ssid, pass } = creds else { + error!("Wifi creds expected"); + return Err(ErrorCode::InvalidAction.into()); + }; + + if ssid.len() > ssid_buf.len() { + error!("SSID too large"); + return Err(ErrorCode::ConstraintError.into()); + } + + if pass.len() > pass_buf.len() { + error!("Password too large"); + return Err(ErrorCode::ConstraintError.into()); + } + + ssid_buf[..ssid.len()].copy_from_slice(ssid); + ssid_len = ssid.len(); + pass_buf[..pass.len()].copy_from_slice(pass); + pass_len = pass.len(); + + Ok(()) + }), + )?; + + if matches!(status, NetworkCommissioningStatusEnum::Success) { + (status, err_code, _) = NetworkCommissioningStatusEnum::map_ctl( + self.net_ctl + .connect(&WirelessCreds::Wifi { + ssid: &ssid_buf[..ssid_len], + pass: &pass_buf[..pass_len], + }) + .await, + )?; + } + + (status, err_code) + } + NetworkType::Ethernet => { + return Err(ErrorCode::InvalidAction.into()); + } + }; + + response + .networking_status(status)? + .debug_text(None)? + .error_value(Nullable::new(err_code))? + .end() + } + + async fn handle_reorder_network( + &self, + _ctx: &InvokeContext<'_>, + request: ReorderNetworkRequest<'_>, + response: NetworkConfigResponseBuilder

, + ) -> Result { + let (status, _, index) = NetworkCommissioningStatusEnum::map( + self.networks + .reorder(request.network_index()? as _, request.network_id()?.0), + )?; + + status.read_into(index, response) + } + + async fn handle_query_identity( + &self, + _ctx: &InvokeContext<'_>, + _request: QueryIdentityRequest<'_>, + _response: QueryIdentityResponseBuilder

, + ) -> Result { + Err(ErrorCode::InvalidAction.into()) + } +} diff --git a/rs-matter/src/data_model/sdm/noc.rs b/rs-matter/src/data_model/sdm/noc.rs index 9740b334..c0d10f0e 100644 --- a/rs-matter/src/data_model/sdm/noc.rs +++ b/rs-matter/src/data_model/sdm/noc.rs @@ -15,485 +15,278 @@ * limitations under the License. */ +//! This module contains the implementation of the Node Operational Credentials cluster and its handler. + use core::cell::Cell; use core::mem::MaybeUninit; use core::num::NonZeroU8; -use strum::{EnumDiscriminants, FromRepr}; - use crate::cert::CertRef; use crate::crypto::{self, KeyPair}; use crate::data_model::objects::{ - Access, AttrDataEncoder, AttrDataWriter, AttrType, Attribute, Cluster, CmdDataEncoder, - CmdDataWriter, Command, Dataver, Handler, InvokeContext, NonBlockingHandler, Quality, - ReadContext, + ArrayAttributeRead, Cluster, Dataver, InvokeContext, ReadContext, }; use crate::data_model::sdm::dev_att; use crate::error::{Error, ErrorCode}; -use crate::fabric::MAX_SUPPORTED_FABRICS; -use crate::fmt::Bytes; -use crate::tlv::{FromTLV, OctetStr, TLVElement, TLVTag, TLVWrite, ToTLV, UtfStr}; -use crate::transport::exchange::Exchange; +use crate::fabric::{Fabric, MAX_SUPPORTED_FABRICS}; +use crate::tlv::{ + Nullable, Octets, OctetsArrayBuilder, OctetsBuilder, TLVBuilder, TLVBuilderParent, TLVElement, + TLVTag, TLVWrite, +}; use crate::transport::session::SessionMode; use crate::utils::init::InitMaybeUninit; use crate::utils::storage::WriteBuf; -use crate::{alloc, attribute_enum, attributes, cmd_enter, command_enum, commands, with}; use super::dev_att::{DataType, DevAttDataFetcher}; -// Node Operational Credentials Cluster - -#[derive(Clone, Copy)] -#[allow(dead_code)] -pub enum NocStatus { - Ok = 0, - InvalidPublicKey = 1, - InvalidNodeOpId = 2, - InvalidNOC = 3, - MissingCsr = 4, - TableFull = 5, - InvalidAdminSubject = 6, - Reserved1 = 7, - Reserved2 = 8, - FabricConflict = 9, - LabelConflict = 10, - InvalidFabricIndex = 11, -} - -pub const ID: u32 = 0x003E; - -#[derive(FromRepr)] -#[repr(u32)] -pub enum Commands { - AttReq = 0x00, - CertChainReq = 0x02, - CSRReq = 0x04, - AddNOC = 0x06, - UpdateFabricLabel = 0x09, - RemoveFabric = 0x0a, - AddTrustedRootCert = 0x0b, -} - -command_enum!(Commands); - -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u32)] -pub enum RespCommands { - AttReqResp = 0x01, - CertChainResp = 0x03, - CSRResp = 0x05, - NOCResp = 0x08, -} - -#[derive(FromRepr, EnumDiscriminants, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u32)] -pub enum Attributes { - NOCs = 0, - Fabrics(()) = 1, - SupportedFabrics(AttrType) = 2, - CommissionedFabrics(AttrType) = 3, - TrustedRootCerts = 4, - CurrentFabricIndex(AttrType) = 5, -} - -attribute_enum!(Attributes); - -#[derive(Debug, Clone, FromTLV, ToTLV, Eq, PartialEq, Hash)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[tlvargs(lifetime = "'a")] -struct NocResp<'a> { - status_code: u8, - fab_idx: u8, - debug_txt: UtfStr<'a>, -} - -#[derive(Debug, Clone, FromTLV, ToTLV, Eq, PartialEq, Hash)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[tlvargs(lifetime = "'a")] -struct AddNocReq<'a> { - noc_value: OctetStr<'a>, - icac_value: Option>, - ipk_value: OctetStr<'a>, - case_admin_subject: u64, - vendor_id: u16, -} - -#[derive(Debug, Clone, FromTLV, ToTLV, Eq, PartialEq, Hash)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[tlvargs(lifetime = "'a")] -struct CsrReq<'a> { - nonce: OctetStr<'a>, - for_update_noc: Option, -} - -#[derive(Debug, Clone, FromTLV, ToTLV, Eq, PartialEq, Hash)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[tlvargs(lifetime = "'a")] -struct CommonReq<'a> { - str: OctetStr<'a>, -} - -#[derive(Debug, Clone, FromTLV, ToTLV, Eq, PartialEq, Hash)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[tlvargs(lifetime = "'a")] -struct UpdateFabricLabelReq<'a> { - label: UtfStr<'a>, -} +pub use crate::data_model::clusters::operational_credentials::*; -#[derive(Debug, Clone, FromTLV, ToTLV, Eq, PartialEq, Hash)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -struct CertChainReq { - cert_type: u8, -} - -#[derive(Debug, Clone, FromTLV, ToTLV, Eq, PartialEq, Hash)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -struct RemoveFabricReq { - fab_idx: NonZeroU8, -} - -impl NocStatus { +impl NodeOperationalCertStatusEnum { fn map(result: Result<(), Error>) -> Result { match result { - Ok(()) => Ok(NocStatus::Ok), + Ok(()) => Ok(Self::OK), Err(err) => match err.code() { - ErrorCode::NocFabricTableFull => Ok(NocStatus::TableFull), - ErrorCode::NocInvalidFabricIndex => Ok(NocStatus::InvalidFabricIndex), - ErrorCode::ConstraintError => Ok(NocStatus::MissingCsr), + ErrorCode::NocFabricTableFull => Ok(Self::TableFull), + ErrorCode::NocInvalidFabricIndex => Ok(Self::InvalidFabricIndex), + ErrorCode::ConstraintError => Ok(Self::MissingCsr), _ => Err(err), }, } } } -pub const CLUSTER: Cluster<'static> = Cluster { - id: ID as _, - revision: 1, - feature_map: 0, - attributes: attributes!( - Attribute::new( - AttributesDiscriminants::CurrentFabricIndex as _, - Access::RV, - Quality::NONE, - ), - Attribute::new( - AttributesDiscriminants::Fabrics as _, - Access::RV.union(Access::FAB_SCOPED), - Quality::NONE, - ), - Attribute::new( - AttributesDiscriminants::SupportedFabrics as _, - Access::RV, - Quality::FIXED, - ), - Attribute::new( - AttributesDiscriminants::CommissionedFabrics as _, - Access::RV, - Quality::NONE, - ), - ), - commands: commands!( - Command::new( - Commands::AttReq as _, - Some(RespCommands::AttReqResp as _), - Access::WA - ), - Command::new( - Commands::CertChainReq as _, - Some(RespCommands::CertChainResp as _), - Access::WA - ), - Command::new( - Commands::CSRReq as _, - Some(RespCommands::CSRResp as _), - Access::WA - ), - Command::new( - Commands::AddNOC as _, - Some(RespCommands::NOCResp as _), - Access::WA - ), - Command::new(Commands::UpdateFabricLabel as _, None, Access::WA), - Command::new(Commands::RemoveFabric as _, None, Access::WA), - Command::new(Commands::AddTrustedRootCert as _, None, Access::WA), - ), - with_attrs: with!(all), - with_cmds: with!(all), -}; - +/// The system implementation of a handler for the Node Operational Credentials Matter cluster. #[derive(Debug, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct NocCluster { - data_ver: Dataver, +pub struct NocHandler { + dataver: Dataver, } -impl NocCluster { - pub const fn new(data_ver: Dataver) -> Self { - Self { data_ver } +impl NocHandler { + /// Creates a new instance of the `NocHandler` with the given `Dataver`. + pub const fn new(dataver: Dataver) -> Self { + Self { dataver } } - pub fn read( - &self, - ctx: &ReadContext<'_>, - encoder: AttrDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - let exchange = ctx.exchange(); - let attr = ctx.attr(); - - if let Some(mut writer) = encoder.with_dataver(self.data_ver.get())? { - if attr.is_system() { - CLUSTER.read(attr.attr_id, writer) - } else { - match attr.attr_id.try_into()? { - Attributes::SupportedFabrics(codec) => { - codec.encode(writer, MAX_SUPPORTED_FABRICS as _) - } - Attributes::CurrentFabricIndex(codec) => codec.encode(writer, attr.fab_idx), - Attributes::Fabrics(_) => { - writer.start_array(&AttrDataWriter::TAG)?; - for fabric in exchange.matter().fabric_mgr.borrow().iter() { - if (!attr.fab_filter || attr.fab_idx == fabric.fab_idx().get()) - && !fabric.root_ca().is_empty() - { - // Empty `root_ca` might happen in the E2E tests - let root_ca_cert = CertRef::new(TLVElement::new(fabric.root_ca())); - - fabric - .descriptor(&root_ca_cert)? - .to_tlv(&TLVTag::Anonymous, &mut *writer)?; - } - } - - writer.end_container()?; - - writer.complete() - } - Attributes::CommissionedFabrics(codec) => codec.encode( - writer, - exchange.matter().fabric_mgr.borrow().iter().count() as _, - ), - other => { - error!("Attribute {:?} not supported", other); - Err(ErrorCode::AttributeNotFound.into()) - } - } - } - } else { - Ok(()) - } + /// Adapt the handler instance to the generic `rs-matter` `Handler` trait + pub const fn adapt(self) -> HandlerAdaptor { + HandlerAdaptor(self) } - pub fn invoke( - &self, - ctx: &InvokeContext<'_>, - encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - let exchange = ctx.exchange(); - let cmd = ctx.cmd(); - let data = ctx.data(); - - match cmd.cmd_id.try_into()? { - Commands::AddNOC => self.handle_command_addnoc(exchange, data, encoder)?, - Commands::CSRReq => self.handle_command_csrrequest(exchange, data, encoder)?, - Commands::AddTrustedRootCert => { - self.handle_command_addtrustedrootcert(exchange, data)? - } - Commands::AttReq => self.handle_command_attrequest(exchange, data, encoder)?, - Commands::CertChainReq => { - self.handle_command_certchainrequest(exchange, data, encoder)? - } - Commands::UpdateFabricLabel => { - self.handle_command_updatefablabel(exchange, data, encoder)?; - } - Commands::RemoveFabric => self.handle_command_rmfabric(exchange, data, encoder)?, - } + /// Computes the attestation signature using the provided `DevAttDataFetcher` + fn compute_attestation_signature<'a>( + dev_att: &dyn DevAttDataFetcher, + attest_element: &mut WriteBuf, + attest_challenge: &[u8], + signature_buf: &'a mut [u8], + ) -> Result<&'a [u8], Error> { + let dac_key = { + let mut pubkey_buf = MaybeUninit::<[u8; crypto::EC_POINT_LEN_BYTES]>::uninit(); // TODO MEDIUM BUFFER + let pubkey_buf = pubkey_buf.init_zeroed(); - self.data_ver.changed(); + let mut privkey_buf = MaybeUninit::<[u8; crypto::BIGNUM_LEN_BYTES]>::uninit(); // TODO MEDIUM BUFFER + let privkey_buf = privkey_buf.init_zeroed(); - Ok(()) - } + let pubkey_len = dev_att.get_devatt_data(dev_att::DataType::DACPubKey, pubkey_buf)?; + let privkey_len = + dev_att.get_devatt_data(dev_att::DataType::DACPrivKey, privkey_buf)?; - fn handle_command_updatefablabel( - &self, - exchange: &Exchange, - data: &TLVElement, - encoder: CmdDataEncoder, - ) -> Result<(), Error> { - cmd_enter!("Update Fabric Label"); - let req = UpdateFabricLabelReq::from_tlv(data).map_err(Error::map_invalid_command)?; - debug!("Received Fabric Label: {:?}", req); + KeyPair::new_from_components(&pubkey_buf[..pubkey_len], &privkey_buf[..privkey_len]) + }?; - let mut updated_fab_idx = 0; + attest_element.copy_from_slice(attest_challenge)?; + let len = dac_key.sign_msg(attest_element.as_slice(), signature_buf)?; - let status = NocStatus::map(exchange.with_session(|sess| { - let SessionMode::Case { fab_idx, .. } = sess.get_session_mode() else { - return Err(ErrorCode::GennCommInvalidAuthentication.into()); - }; + Ok(&signature_buf[..len]) + } +} - updated_fab_idx = fab_idx.get(); +impl ClusterHandler for NocHandler { + const CLUSTER: Cluster<'static> = FULL_CLUSTER; - exchange - .matter() - .fabric_mgr - .borrow_mut() - .update_label(*fab_idx, req.label) - .map_err(|e| { - if e.code() == ErrorCode::Invalid { - ErrorCode::NocLabelConflict.into() - } else { - e - } - }) - }))?; + fn dataver(&self) -> u32 { + self.dataver.get() + } - Self::create_nocresponse(encoder, status as _, updated_fab_idx, "") + fn dataver_changed(&self) { + self.dataver.changed(); } - fn handle_command_rmfabric( + fn no_cs( &self, - exchange: &Exchange, - data: &TLVElement, - encoder: CmdDataEncoder, - ) -> Result<(), Error> { - cmd_enter!("Remove Fabric"); - let req = RemoveFabricReq::from_tlv(data).map_err(Error::map_invalid_command)?; - debug!("Received Fabric Index: {:?}", req); + ctx: &ReadContext<'_>, + builder: ArrayAttributeRead, NOCStructBuilder

>, + ) -> Result { + fn read_into( + fabric: &Fabric, + builder: NOCStructBuilder

, + ) -> Result { + builder + .noc(Octets::new(fabric.noc()))? + .icac(Nullable::new( + (!fabric.icac().is_empty()).then(|| Octets::new(fabric.icac())), + ))? + .fabric_index(fabric.fab_idx().get())? + .end() + } - if exchange - .matter() - .fabric_mgr - .borrow_mut() - .remove(req.fab_idx, &exchange.matter().transport_mgr.mdns) - .is_ok() - { - exchange - .matter() - .transport_mgr - .session_mgr - .borrow_mut() - .remove_for_fabric(req.fab_idx); - exchange.matter().transport_mgr.session_removed.notify(); + let attr = ctx.attr(); + let fabric_mgr = ctx.exchange().matter().fabric_mgr.borrow(); + let mut fabrics = fabric_mgr.iter().filter(|fabric| { + (!attr.fab_filter || attr.fab_idx == fabric.fab_idx().get()) + && !fabric.root_ca().is_empty() + }); + + match builder { + ArrayAttributeRead::ReadAll(mut builder) => { + for fabric in fabrics { + builder = read_into(fabric, builder.push()?)?; + } - // Note that since we might have removed our own session, the exchange - // will terminate with a "NoSession" error, but that's OK and handled properly + builder.end() + } + ArrayAttributeRead::ReadOne(index, builder) => { + let fabric = fabrics.nth(index as _); - Ok(()) - } else { - Self::create_nocresponse( - encoder, - NocStatus::InvalidFabricIndex, - req.fab_idx.get(), - "", - ) + if let Some(fabric) = fabric { + read_into(fabric, builder) + } else { + Err(ErrorCode::InvalidAction.into()) // TODO + } + } } } - fn handle_command_addnoc( + fn fabrics( &self, - exchange: &Exchange, - data: &TLVElement, - encoder: CmdDataEncoder, - ) -> Result<(), Error> { - cmd_enter!("AddNOC"); - - let r = AddNocReq::from_tlv(data).map_err(Error::map_invalid_command)?; - - debug!( - "Received NOC as: {}", - CertRef::new(TLVElement::new(r.noc_value.0)) - ); - - let icac = r - .icac_value - .as_ref() - .map(|icac| icac.0) - .filter(|icac| !icac.is_empty()); - if let Some(icac) = icac { - debug!("Received ICAC as: {}", CertRef::new(TLVElement::new(icac))); + ctx: &ReadContext<'_>, + builder: ArrayAttributeRead< + FabricDescriptorStructArrayBuilder

, + FabricDescriptorStructBuilder

, + >, + ) -> Result { + fn read_into( + fabric: &Fabric, + builder: FabricDescriptorStructBuilder

, + ) -> Result { + // Empty `root_ca` might happen in the E2E tests + let root_ca_cert = CertRef::new(TLVElement::new(fabric.root_ca())); + + builder + .root_public_key(Octets::new(root_ca_cert.pubkey()?))? + .vendor_id(fabric.vendor_id())? + .fabric_id(fabric.fabric_id())? + .node_id(fabric.node_id())? + .label(fabric.label())? + .fabric_index(fabric.fab_idx().get())? + .end() } - let mut added_fab_idx = 0; + let attr = ctx.attr(); + let fabric_mgr = ctx.exchange().matter().fabric_mgr.borrow(); + let mut fabrics = fabric_mgr.iter().filter(|fabric| { + (!attr.fab_filter || attr.fab_idx == fabric.fab_idx().get()) + && !fabric.root_ca().is_empty() + }); + + match builder { + ArrayAttributeRead::ReadAll(mut builder) => { + for fabric in fabrics { + builder = read_into(fabric, builder.push()?)?; + } - let mut buf = alloc!([0; 800]); // TODO LARGE BUFFER - let buf = &mut buf[..]; + builder.end() + } + ArrayAttributeRead::ReadOne(index, builder) => { + let fabric = fabrics.nth(index as _); - let status = NocStatus::map(exchange.with_session(|sess| { - let fab_idx = exchange.matter().failsafe.borrow_mut().add_noc( - &exchange.matter().fabric_mgr, - sess.get_session_mode(), - r.vendor_id, - icac, - r.noc_value.0, - r.ipk_value.0, - r.case_admin_subject, - buf, - &exchange.matter().transport_mgr.mdns, - )?; + if let Some(fabric) = fabric { + read_into(fabric, builder) + } else { + Err(ErrorCode::InvalidAction.into()) // TODO + } + } + } + } - let succeeded = Cell::new(false); + fn supported_fabrics(&self, _ctx: &ReadContext<'_>) -> Result { + Ok(MAX_SUPPORTED_FABRICS as u8) + } - let _fab_guard = scopeguard::guard(fab_idx, |fab_idx| { - if !succeeded.get() { - // Remove the fabric if we fail further down this function - warn!("Removing fabric {} due to failure", fab_idx.get()); + fn commissioned_fabrics(&self, ctx: &ReadContext<'_>) -> Result { + Ok(ctx.exchange().matter().fabric_mgr.borrow().iter().count() as _) + } - unwrap!(exchange - .matter() - .fabric_mgr - .borrow_mut() - .remove(fab_idx, &exchange.matter().transport_mgr.mdns)); + fn trusted_root_certificates( + &self, + ctx: &ReadContext<'_>, + builder: ArrayAttributeRead, OctetsBuilder

>, + ) -> Result { + let attr = ctx.attr(); + let fabric_mgr = ctx.exchange().matter().fabric_mgr.borrow(); + let mut fabrics = fabric_mgr.iter().filter(|fabric| { + (!attr.fab_filter || attr.fab_idx == fabric.fab_idx().get()) + && !fabric.root_ca().is_empty() + }); + + match builder { + ArrayAttributeRead::ReadAll(mut builder) => { + for fabric in fabrics { + builder = builder.push(Octets::new(fabric.root_ca()))?; } - }); - if matches!(sess.get_session_mode(), SessionMode::Pase { .. }) { - sess.upgrade_fabric_idx(fab_idx)?; + builder.end() } + ArrayAttributeRead::ReadOne(index, builder) => { + let fabric = fabrics.nth(index as _); - succeeded.set(true); - - added_fab_idx = fab_idx.get(); - - Ok(()) - }))?; - - Self::create_nocresponse(encoder, status, added_fab_idx, "")?; + if let Some(fabric) = fabric { + builder.set(Octets::new(fabric.root_ca())) + } else { + Err(ErrorCode::InvalidAction.into()) // TODO + } + } + } + } - Ok(()) + fn current_fabric_index(&self, ctx: &ReadContext<'_>) -> Result { + let attr = ctx.attr(); + Ok(attr.fab_idx) } - fn handle_command_attrequest( + fn handle_attestation_request( &self, - exchange: &Exchange, - data: &TLVElement, - encoder: CmdDataEncoder, - ) -> Result<(), Error> { - cmd_enter!("AttestationRequest"); - - let req = CommonReq::from_tlv(data).map_err(Error::map_invalid_command)?; - debug!("Received Attestation Nonce:{:?}", req.str); + ctx: &InvokeContext<'_>, + request: AttestationRequestRequest<'_>, + response: AttestationResponseBuilder

, + ) -> Result { + info!("Got Attestation Request"); - exchange.with_session(|sess| { - let mut writer = encoder.with_command(RespCommands::AttReqResp as _)?; + ctx.exchange().with_session(|sess| { + // Switch to raw writer for the response + // Necessary, as we want to take advantage of the `TLVWrite::str_cb` method + // to in-place compute and write the attestation response and the signature as an octet string + let mut parent = response.unchecked_into_parent(); + let writer = parent.writer(); - writer.start_struct(&CmdDataWriter::TAG)?; + // Struct is already started + // writer.start_struct(&CmdDataWriter::TAG)?; - let epoch = (exchange.matter().epoch())().as_secs() as u32; + let epoch = (ctx.exchange().matter().epoch())().as_secs() as u32; let mut signature_buf = MaybeUninit::<[u8; crypto::EC_SIGNATURE_LEN_BYTES]>::uninit(); // TODO MEDIUM BUFFER let signature_buf = signature_buf.init_zeroed(); let mut signature_len = 0; writer.str_cb(&TLVTag::Context(0), |buf| { - let dev_att = exchange.matter().dev_att(); + let dev_att = ctx.exchange().matter().dev_att(); let mut wb = WriteBuf::new(buf); wb.start_struct(&TLVTag::Anonymous)?; wb.str_cb(&TLVTag::Context(1), |buf| { dev_att.get_devatt_data(dev_att::DataType::CertDeclaration, buf) })?; - wb.str(&TLVTag::Context(2), req.str.0)?; + wb.str(&TLVTag::Context(2), request.attestation_nonce()?.0)?; wb.u32(&TLVTag::Context(3), epoch)?; wb.end_container()?; @@ -509,60 +302,72 @@ impl NocCluster { Ok(len) })?; + writer.str(&TLVTag::Context(1), &signature_buf[..signature_len])?; writer.end_container()?; - writer.complete() + Ok(parent) }) } - fn handle_command_certchainrequest( + fn handle_certificate_chain_request( &self, - exchange: &Exchange, - data: &TLVElement, - encoder: CmdDataEncoder, - ) -> Result<(), Error> { - cmd_enter!("CertChainRequest"); + ctx: &InvokeContext<'_>, + request: CertificateChainRequestRequest<'_>, + response: CertificateChainResponseBuilder

, + ) -> Result { + info!("Got Cert Chain Request"); - debug!("Received data: {}", data); - let cert_type = - Self::get_certchainrequest_params(data).map_err(Error::map_invalid_command)?; + // Switch to raw writer for the response + // Necessary, as we want to take advantage of the `TLVWrite::str_cb` method + // to emplace the attestation certificate as an octet string + let mut parent = response.unchecked_into_parent(); + let writer = parent.writer(); - let mut writer = encoder.with_command(RespCommands::CertChainResp as _)?; + // Struct is already started + // writer.start_struct(&CmdDataWriter::TAG)?; - writer.start_struct(&CmdDataWriter::TAG)?; writer.str_cb(&TLVTag::Context(0), |buf| { - exchange.matter().dev_att().get_devatt_data(cert_type, buf) + ctx.exchange().matter().dev_att().get_devatt_data( + match request.certificate_type()? { + CertificateChainTypeEnum::DACCertificate => DataType::DAC, + CertificateChainTypeEnum::PAICertificate => DataType::PAI, + }, + buf, + ) })?; + writer.end_container()?; - writer.complete() + Ok(parent) } - fn handle_command_csrrequest( + fn handle_csr_request( &self, - exchange: &Exchange, - data: &TLVElement, - encoder: CmdDataEncoder, - ) -> Result<(), Error> { - cmd_enter!("CSRRequest"); - - let req = CsrReq::from_tlv(data).map_err(Error::map_invalid_command)?; - debug!("Received CSR: {:?}", req); + ctx: &InvokeContext<'_>, + request: CSRRequestRequest<'_>, + response: CSRResponseBuilder

, + ) -> Result { + info!("Got CSR Request"); - exchange.with_session(|sess| { - let mut failsafe = exchange.matter().failsafe.borrow_mut(); + ctx.exchange().with_session(|sess| { + let mut failsafe = ctx.exchange().matter().failsafe.borrow_mut(); - let key_pair = if req.for_update_noc.unwrap_or(false) { + let key_pair = if request.is_for_update_noc()?.unwrap_or(false) { failsafe.update_csr_req(sess.get_session_mode()) } else { failsafe.add_csr_req(sess.get_session_mode()) }?; - let mut writer = encoder.with_command(RespCommands::CSRResp as _)?; + // Switch to raw writer for the response + // Necessary, as we want to take advantage of the `TLVWrite::str_cb` method + // to in-place compute and write the CSR response and the signature as an octet string + let mut parent = response.unchecked_into_parent(); + let writer = parent.writer(); - writer.start_struct(&CmdDataWriter::TAG)?; + // Struct is already started + // writer.start_struct(&CmdDataWriter::TAG)?; let mut signature_buf = MaybeUninit::<[u8; crypto::EC_SIGNATURE_LEN_BYTES]>::uninit(); // TODO MEDIUM BUFFER let signature_buf = signature_buf.init_zeroed(); @@ -573,13 +378,13 @@ impl NocCluster { wb.start_struct(&TLVTag::Anonymous)?; wb.str_cb(&TLVTag::Context(1), |buf| Ok(key_pair.get_csr(buf)?.len()))?; - wb.str(&TLVTag::Context(2), req.nonce.0)?; + wb.str(&TLVTag::Context(2), request.csr_nonce()?.0)?; wb.end_container()?; let len = wb.get_tail(); signature_len = Self::compute_attestation_signature( - exchange.matter().dev_att(), + ctx.exchange().matter().dev_att(), &mut wb, sess.get_att_challenge(), signature_buf, @@ -588,106 +393,197 @@ impl NocCluster { Ok(len) })?; + writer.str(&TLVTag::Context(1), &signature_buf[..signature_len])?; writer.end_container()?; - writer.complete() + Ok(parent) }) } - fn handle_command_addtrustedrootcert( + fn handle_add_noc( &self, - exchange: &Exchange, - data: &TLVElement, - ) -> Result<(), Error> { - cmd_enter!("AddTrustedRootCert"); + ctx: &InvokeContext<'_>, + request: AddNOCRequest<'_>, + mut response: NOCResponseBuilder

, + ) -> Result { + info!("Got Add NOC Request"); - let req = CommonReq::from_tlv(data).map_err(Error::map_invalid_command)?; - debug!("Received Trusted Cert: {}", Bytes(&req.str)); + let icac = request + .icac_value()? + .as_ref() + .map(|icac| icac.0) + .filter(|icac| !icac.is_empty()); - exchange.with_session(|sess| { - exchange - .matter() - .failsafe - .borrow_mut() - .add_trusted_root_cert(sess.get_session_mode(), req.str.0) - }) - } + let mut added_fab_idx = 0; - fn create_nocresponse( - encoder: CmdDataEncoder, - status_code: NocStatus, - fab_idx: u8, - debug_txt: &str, - ) -> Result<(), Error> { - let cmd_data = NocResp { - status_code: status_code as u8, - fab_idx, - debug_txt, - }; + let buf = response.writer().available_space(); - encoder - .with_command(RespCommands::NOCResp as _)? - .set(cmd_data) - } + let status = NodeOperationalCertStatusEnum::map(ctx.exchange().with_session(|sess| { + let fab_idx = ctx.exchange().matter().failsafe.borrow_mut().add_noc( + &ctx.exchange().matter().fabric_mgr, + sess.get_session_mode(), + request.admin_vendor_id()?, + icac, + request.noc_value()?.0, + request.ipk_value()?.0, + request.case_admin_subject()?, + buf, + &ctx.exchange().matter().transport_mgr.mdns, + )?; - fn compute_attestation_signature<'a>( - dev_att: &dyn DevAttDataFetcher, - attest_element: &mut WriteBuf, - attest_challenge: &[u8], - signature_buf: &'a mut [u8], - ) -> Result<&'a [u8], Error> { - let dac_key = { - let mut pubkey_buf = MaybeUninit::<[u8; crypto::EC_POINT_LEN_BYTES]>::uninit(); // TODO MEDIUM BUFFER - let pubkey_buf = pubkey_buf.init_zeroed(); + let succeeded = Cell::new(false); - let mut privkey_buf = MaybeUninit::<[u8; crypto::BIGNUM_LEN_BYTES]>::uninit(); // TODO MEDIUM BUFFER - let privkey_buf = privkey_buf.init_zeroed(); + let _fab_guard = scopeguard::guard(fab_idx, |fab_idx| { + if !succeeded.get() { + // Remove the fabric if we fail further down this function + warn!("Removing fabric {} due to failure", fab_idx.get()); - let pubkey_len = dev_att.get_devatt_data(dev_att::DataType::DACPubKey, pubkey_buf)?; - let privkey_len = - dev_att.get_devatt_data(dev_att::DataType::DACPrivKey, privkey_buf)?; + unwrap!(ctx + .exchange() + .matter() + .fabric_mgr + .borrow_mut() + .remove(fab_idx, &ctx.exchange().matter().transport_mgr.mdns)); + } + }); - KeyPair::new_from_components(&pubkey_buf[..pubkey_len], &privkey_buf[..privkey_len]) - }?; + if matches!(sess.get_session_mode(), SessionMode::Pase { .. }) { + sess.upgrade_fabric_idx(fab_idx)?; + } - attest_element.copy_from_slice(attest_challenge)?; - let len = dac_key.sign_msg(attest_element.as_slice(), signature_buf)?; + succeeded.set(true); - Ok(&signature_buf[..len]) + added_fab_idx = fab_idx.get(); + + Ok(()) + }))?; + + response + .status_code(status)? + .fabric_index(Some(added_fab_idx))? + .debug_text(None)? + .end() } - fn get_certchainrequest_params(data: &TLVElement) -> Result { - let cert_type = CertChainReq::from_tlv(data)?.cert_type; + fn handle_update_noc( + &self, + _ctx: &InvokeContext<'_>, + _request: UpdateNOCRequest<'_>, + _response: NOCResponseBuilder

, + ) -> Result { + info!("Got Update NOC Request"); - const CERT_TYPE_DAC: u8 = 1; - const CERT_TYPE_PAI: u8 = 2; - debug!("Received Cert Type:{:?}", cert_type); - match cert_type { - CERT_TYPE_DAC => Ok(dev_att::DataType::DAC), - CERT_TYPE_PAI => Ok(dev_att::DataType::PAI), - _ => Err(ErrorCode::Invalid.into()), - } + Err(ErrorCode::InvalidAction.into()) // TODO: Implement this } -} -impl Handler for NocCluster { - fn read( + fn handle_update_fabric_label( &self, - ctx: &ReadContext<'_>, - encoder: AttrDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - NocCluster::read(self, ctx, encoder) + ctx: &InvokeContext<'_>, + request: UpdateFabricLabelRequest<'_>, + response: NOCResponseBuilder

, + ) -> Result { + info!("Got Update Fabric Label Request: {:?}", request.label()); + + let mut updated_fab_idx = 0; + + let status = NodeOperationalCertStatusEnum::map(ctx.exchange().with_session(|sess| { + let SessionMode::Case { fab_idx, .. } = sess.get_session_mode() else { + return Err(ErrorCode::GennCommInvalidAuthentication.into()); + }; + + updated_fab_idx = fab_idx.get(); + + ctx.exchange() + .matter() + .fabric_mgr + .borrow_mut() + .update_label(*fab_idx, request.label()?) + .map_err(|e| { + if e.code() == ErrorCode::Invalid { + ErrorCode::NocLabelConflict.into() + } else { + e + } + }) + }))?; + + response + .status_code(status)? + .fabric_index(Some(updated_fab_idx))? + .debug_text(None)? + .end() } - fn invoke( + fn handle_remove_fabric( &self, ctx: &InvokeContext<'_>, - encoder: CmdDataEncoder<'_, '_, '_>, + request: RemoveFabricRequest<'_>, + response: NOCResponseBuilder

, + ) -> Result { + info!("Got Remove Fabric Request"); + + let fab_idx = NonZeroU8::new(request.fabric_index()?).ok_or(ErrorCode::InvalidAction)?; + + let status = if ctx + .exchange() + .matter() + .fabric_mgr + .borrow_mut() + .remove(fab_idx, &ctx.exchange().matter().transport_mgr.mdns) + .is_ok() + { + ctx.exchange() + .matter() + .transport_mgr + .session_mgr + .borrow_mut() + .remove_for_fabric(fab_idx); + + // Notify that the fabrics need to be persisted + // We need to explicitly do this because if the fabric being removed + // is the one on which the session is running, the session will be removed + // and the response will fail + ctx.exchange().matter().notify_persist(); + + // Notify that a session was removed + ctx.exchange() + .matter() + .transport_mgr + .session_removed + .notify(); + + // Note that since we might have removed our own session, the exchange + // will terminate with a "NoSession" error, but that's OK and handled properly + + info!("Removed operational fabric with local index {}", fab_idx); + + NodeOperationalCertStatusEnum::OK + } else { + NodeOperationalCertStatusEnum::InvalidFabricIndex + }; + + response + .status_code(status)? + .fabric_index(Some(fab_idx.get()))? + .debug_text(None)? + .end() + } + + fn handle_add_trusted_root_certificate( + &self, + ctx: &InvokeContext<'_>, + request: AddTrustedRootCertificateRequest<'_>, ) -> Result<(), Error> { - NocCluster::invoke(self, ctx, encoder) + info!("Got Add Trusted Root Cert Request"); + + ctx.exchange().with_session(|sess| { + ctx.exchange() + .matter() + .failsafe + .borrow_mut() + .add_trusted_root_cert(sess.get_session_mode(), request.root_ca_certificate()?.0) + }) } } - -impl NonBlockingHandler for NocCluster {} diff --git a/rs-matter/src/data_model/sdm/nw_commissioning.rs b/rs-matter/src/data_model/sdm/nw_commissioning.rs deleted file mode 100644 index 6f57bbca..00000000 --- a/rs-matter/src/data_model/sdm/nw_commissioning.rs +++ /dev/null @@ -1,478 +0,0 @@ -/* - * - * Copyright (c) 2020-2022 Project CHIP Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -use strum::FromRepr; - -use crate::data_model::objects::{ - Access, AttrDataEncoder, AttrDataWriter, AttrType, Attribute, Cluster, Command, Dataver, - Handler, NonBlockingHandler, Quality, ReadContext, -}; -use crate::error::{Error, ErrorCode}; -use crate::tlv::{FromTLV, OctetStr, TLVArray, TLVTag, TLVWrite, ToTLV}; -use crate::utils::bitflags::bitflags; -use crate::{attribute_enum, attributes, bitflags_tlv, command_enum, commands}; - -pub const ID: u32 = 0x0031; - -#[derive(FromRepr, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u32)] -pub enum Attributes { - MaxNetworks = 0x00, - Networks = 0x01, - ScanMaxTimeSecs = 0x02, - ConnectMaxTimeSecs = 0x03, - InterfaceEnabled = 0x04, - LastNetworkingStatus = 0x05, - LastNetworkID = 0x06, - LastConnectErrorValue = 0x07, -} - -attribute_enum!(Attributes); - -#[derive(Debug, FromRepr)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u32)] -pub enum Commands { - ScanNetworks = 0x00, - AddOrUpdateWifiNetwork = 0x02, - AddOrUpdateThreadNetwork = 0x03, - RemoveNetwork = 0x04, - ConnectNetwork = 0x06, - ReorderNetwork = 0x08, -} - -#[derive(FromRepr)] -#[repr(u32)] -pub enum ResponseCommands { - ScanNetworksResponse = 0x01, - NetworkConfigResponse = 0x05, - ConnectNetworkResponse = 0x07, -} - -command_enum!(Commands); - -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub enum FeatureMap { - Wifi = 0x01, - Thread = 0x02, - Ethernet = 0x04, -} - -impl TryFrom for FeatureMap { - type Error = Error; - - fn try_from(value: u32) -> Result { - match value { - 0x01 => Ok(FeatureMap::Wifi), - 0x02 => Ok(FeatureMap::Thread), - 0x04 => Ok(FeatureMap::Ethernet), - _ => Err(ErrorCode::Invalid.into()), - } - } -} - -const fn cluster(feature_map: FeatureMap) -> Cluster<'static> { - static ATTRIBUTES: &[Attribute] = attributes!( - Attribute::new(Attributes::MaxNetworks as _, Access::RA, Quality::F), - Attribute::new(Attributes::Networks as _, Access::RA, Quality::NONE), - Attribute::new(Attributes::ScanMaxTimeSecs as _, Access::RV, Quality::F), - Attribute::new(Attributes::ConnectMaxTimeSecs as _, Access::RV, Quality::F), - Attribute::new(Attributes::InterfaceEnabled as _, Access::RWVA, Quality::N), - Attribute::new( - Attributes::LastNetworkingStatus as _, - Access::RA, - Quality::X, - ), - Attribute::new(Attributes::LastNetworkID as _, Access::RA, Quality::X), - Attribute::new( - Attributes::LastConnectErrorValue as _, - Access::RA, - Quality::X, - ), - ); - - static COMMANDS: &[Command] = commands!( - Command::new( - Commands::ScanNetworks as _, - Some(ResponseCommands::ScanNetworksResponse as _), - Access::WA - ), - Command::new( - Commands::AddOrUpdateWifiNetwork as _, - Some(ResponseCommands::NetworkConfigResponse as _), - Access::WA - ), - Command::new( - Commands::AddOrUpdateThreadNetwork as _, - Some(ResponseCommands::NetworkConfigResponse as _), - Access::WA - ), - Command::new( - Commands::RemoveNetwork as _, - Some(ResponseCommands::NetworkConfigResponse as _), - Access::WA - ), - Command::new( - Commands::ConnectNetwork as _, - Some(ResponseCommands::ConnectNetworkResponse as _), - Access::WA - ), - Command::new( - Commands::ReorderNetwork as _, - Some(ResponseCommands::NetworkConfigResponse as _), - Access::WA - ), - ); - - Cluster { - id: ID as _, - revision: 1, - feature_map: feature_map as _, - attributes: ATTRIBUTES, - commands: COMMANDS, - with_attrs: |attr, _, feature_map| { - if attr.is_system() { - return true; - } - - let Ok(feature_map) = feature_map.try_into() else { - return false; - }; - - let Ok(id) = attr.id.try_into() else { - return false; - }; - - match feature_map { - FeatureMap::Wifi | FeatureMap::Thread => matches!( - id, - Attributes::MaxNetworks - | Attributes::Networks - | Attributes::ScanMaxTimeSecs - | Attributes::ConnectMaxTimeSecs - | Attributes::InterfaceEnabled - | Attributes::LastNetworkingStatus - | Attributes::LastNetworkID - | Attributes::LastConnectErrorValue - ), - FeatureMap::Ethernet => matches!( - id, - Attributes::MaxNetworks - | Attributes::Networks - | Attributes::ConnectMaxTimeSecs - | Attributes::InterfaceEnabled - | Attributes::LastNetworkingStatus - | Attributes::LastNetworkID - | Attributes::LastConnectErrorValue - ), - } - }, - with_cmds: |cmd, _, feature_map| { - let Ok(feature_map) = feature_map.try_into() else { - return false; - }; - - let Ok(id) = cmd.id.try_into() else { - return false; - }; - - match feature_map { - FeatureMap::Wifi => { - matches!( - id, - Commands::ScanNetworks - | Commands::AddOrUpdateWifiNetwork - | Commands::RemoveNetwork - | Commands::ConnectNetwork - | Commands::ReorderNetwork - ) - } - FeatureMap::Thread => { - matches!( - id, - Commands::ScanNetworks - | Commands::AddOrUpdateThreadNetwork - | Commands::RemoveNetwork - | Commands::ConnectNetwork - | Commands::ReorderNetwork - ) - } - FeatureMap::Ethernet => false, - } - }, - } -} - -pub const ETH_CLUSTER: Cluster<'static> = cluster(FeatureMap::Ethernet); -pub const WIFI_CLUSTER: Cluster<'static> = cluster(FeatureMap::Wifi); -pub const THR_CLUSTER: Cluster<'static> = cluster(FeatureMap::Thread); - -bitflags! { - #[repr(transparent)] - #[derive(Default)] - #[cfg_attr(not(feature = "defmt"), derive(Debug, Copy, Clone, Eq, PartialEq, Hash))] - pub struct WiFiSecurity: u8 { - const UNENCRYPTED = 0x01; - const WEP = 0x02; - const WPA_PERSONAL = 0x04; - const WPA2_PERSONAL = 0x08; - const WPA3_PERSONAL = 0x10; - } -} - -bitflags_tlv!(WiFiSecurity, u8); - -#[derive(Debug, Copy, Clone, Eq, PartialEq, FromTLV, ToTLV, FromRepr)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum WifiBand { - B2G4 = 0, - B3G65 = 1, - B5G = 2, - B6G = 3, - B60G = 4, - B1G = 5, -} - -#[derive(Debug, Clone, FromTLV, ToTLV)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[tlvargs(lifetime = "'a")] -pub struct ScanNetworksRequest<'a> { - pub ssid: Option>, - pub breadcrumb: Option, -} - -#[derive(Debug, Clone, FromTLV, ToTLV)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[tlvargs(lifetime = "'a")] -pub struct ScanNetworksResponse<'a> { - pub status: NetworkCommissioningStatus, - pub debug_text: Option>, - pub wifi_scan_results: Option>>, - pub thread_scan_results: Option>>, -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum ScanNetworksResponseTag { - Status = 0, - DebugText = 1, - WifiScanResults = 2, - ThreadScanResults = 3, -} - -#[derive(Debug, Clone, FromTLV, ToTLV)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[tlvargs(lifetime = "'a")] -pub struct AddWifiNetworkRequest<'a> { - pub ssid: OctetStr<'a>, - pub credentials: OctetStr<'a>, - pub breadcrumb: Option, -} - -#[derive(Debug, Clone, FromTLV, ToTLV)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[tlvargs(lifetime = "'a")] -pub struct AddThreadNetworkRequest<'a> { - pub op_dataset: OctetStr<'a>, - pub breadcrumb: Option, -} - -#[derive(Debug, Clone, FromTLV, ToTLV)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[tlvargs(lifetime = "'a")] -pub struct RemoveNetworkRequest<'a> { - pub network_id: OctetStr<'a>, - pub breadcrumb: Option, -} - -#[derive(Debug, Clone, FromTLV, ToTLV)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[tlvargs(lifetime = "'a")] -pub struct NetworkConfigResponse<'a> { - pub status: NetworkCommissioningStatus, - pub debug_text: Option>, - pub network_index: Option, -} - -pub type ConnectNetworkRequest<'a> = RemoveNetworkRequest<'a>; - -#[derive(Debug, Clone, FromTLV, ToTLV)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[tlvargs(lifetime = "'a")] -pub struct ReorderNetworkRequest<'a> { - pub network_id: OctetStr<'a>, - pub index: u8, - pub breadcrumb: Option, -} - -#[derive(Debug, Clone, FromTLV, ToTLV)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[tlvargs(lifetime = "'a")] -pub struct ConnectNetworkResponse<'a> { - pub status: NetworkCommissioningStatus, - pub debug_text: Option>, - pub error_value: i32, -} - -#[derive(Debug, Clone, FromTLV, ToTLV)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[tlvargs(lifetime = "'a")] -pub struct WiFiInterfaceScanResult<'a> { - pub security: WiFiSecurity, - pub ssid: OctetStr<'a>, - pub bssid: OctetStr<'a>, - pub channel: u16, - pub band: Option, - pub rssi: Option, -} - -#[derive(Debug, Clone, FromTLV, ToTLV)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[tlvargs(lifetime = "'a")] -pub struct ThreadInterfaceScanResult<'a> { - pub pan_id: u16, - pub extended_pan_id: u64, - pub network_name: OctetStr<'a>, - pub channel: u16, - pub version: u8, - pub extended_address: OctetStr<'a>, - pub rssi: i8, - pub lqi: u8, -} - -#[derive(Debug, Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct EthNwCommCluster { - data_ver: Dataver, -} - -impl EthNwCommCluster { - pub const fn new(data_ver: Dataver) -> Self { - Self { data_ver } - } - - pub fn read( - &self, - ctx: &ReadContext<'_>, - encoder: AttrDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - let attr = ctx.attr(); - - let info = self.get_network_info(); - if let Some(mut writer) = encoder.with_dataver(self.data_ver.get())? { - if attr.is_system() { - ETH_CLUSTER.read(attr.attr_id, writer) - } else { - match attr.attr_id.try_into()? { - Attributes::MaxNetworks => AttrType::::new().encode(writer, 1), - Attributes::Networks => { - writer.start_array(&AttrDataWriter::TAG)?; - info.nw_info.to_tlv(&TLVTag::Anonymous, &mut *writer)?; - writer.end_container()?; - writer.complete() - } - Attributes::ConnectMaxTimeSecs => { - AttrType::::new().encode(writer, info.connect_max_time_secs) - } - Attributes::InterfaceEnabled => { - AttrType::::new().encode(writer, info.interface_enabled) - } - Attributes::LastNetworkingStatus => { - AttrType::::new().encode(writer, info.last_nw_status as u8) - } - Attributes::LastNetworkID => { - info.nw_info - .network_id - .to_tlv(&AttrDataWriter::TAG, &mut *writer)?; - writer.complete() - } - Attributes::LastConnectErrorValue => { - writer.null(&AttrDataWriter::TAG)?; - writer.complete() - } - other => { - error!("Attribute {:?} not supported", other); - Err(ErrorCode::AttributeNotFound.into()) - } - } - } - } else { - Ok(()) - } - } - - fn get_network_info(&self) -> NwMetaInfo<'static> { - // Only single, Ethernet, supported for now - let nw_info = NwInfo { - network_id: OctetStr::new(b"en0"), - connected: true, - }; - NwMetaInfo { - nw_info, - connect_max_time_secs: 60, - interface_enabled: true, - last_nw_status: NetworkCommissioningStatus::Success, - } - } -} - -#[derive(Debug, FromTLV, ToTLV)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[tlvargs(lifetime = "'a")] -pub struct NwInfo<'a> { - pub network_id: OctetStr<'a>, - pub connected: bool, -} - -struct NwMetaInfo<'a> { - nw_info: NwInfo<'a>, - connect_max_time_secs: u8, - interface_enabled: bool, - last_nw_status: NetworkCommissioningStatus, -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq, FromRepr, FromTLV, ToTLV)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum NetworkCommissioningStatus { - Success = 0, - OutOfRange = 1, - BoundsExceeded = 2, - NetworkIdNotFound = 3, - DuplicateNetworkId = 4, - NetworkNotFound = 5, - RegulatoryError = 6, - AuthFailure = 7, - UnsupportedSecurity = 8, - OtherConnectionFailure = 9, - IPV6Failed = 10, - IPBindFailed = 11, - UnknownError = 12, -} - -impl Handler for EthNwCommCluster { - fn read( - &self, - ctx: &ReadContext<'_>, - encoder: AttrDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - EthNwCommCluster::read(self, ctx, encoder) - } -} - -impl NonBlockingHandler for EthNwCommCluster {} diff --git a/rs-matter/src/data_model/sdm/thread_diag.rs b/rs-matter/src/data_model/sdm/thread_diag.rs new file mode 100644 index 00000000..740116f0 --- /dev/null +++ b/rs-matter/src/data_model/sdm/thread_diag.rs @@ -0,0 +1,665 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! This module contains the implementation of the Thread Network Diagnostics cluster and its handler. + +use rs_matter_macros::{FromTLV, ToTLV}; + +use crate::data_model::objects::{ArrayAttributeRead, Dataver, InvokeContext, ReadContext}; +use crate::error::{Error, ErrorCode}; +use crate::tlv::{ + Nullable, NullableBuilder, Octets, OctetsBuilder, TLVBuilderParent, ToTLVArrayBuilder, + ToTLVBuilder, Utf8StrBuilder, +}; +use crate::with; + +pub use crate::data_model::clusters::thread_network_diagnostics::*; + +use super::wifi_diag::WirelessDiag; + +/// Thread Neighbor Table as returned by the `ThreadDiag` trait +#[derive(Debug, Clone, Eq, PartialEq, Hash, FromTLV, ToTLV)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct NeighborTable { + pub ext_address: u64, + pub age: u32, + pub rloc16: u16, + pub link_frame_counter: u32, + pub mle_frame_counter: u32, + pub lqi: u8, + pub average_rssi: Option, + pub last_rssi: Option, + pub frame_error_rate: u8, + pub message_error_rate: u8, + pub rx_on_when_idle: bool, + pub full_thread_device: bool, + pub full_network_data: bool, + pub is_child: bool, +} + +impl NeighborTable { + /// Reads the `NeighborTable` into the provided `NeighborTableStructBuilder`. + fn read_into( + &self, + builder: NeighborTableStructBuilder

, + ) -> Result { + builder + .ext_address(self.ext_address)? + .age(self.age)? + .rloc_16(self.rloc16)? + .link_frame_counter(self.link_frame_counter)? + .mle_frame_counter(self.mle_frame_counter)? + .lqi(self.lqi)? + .average_rssi(Nullable::new(self.average_rssi))? + .last_rssi(Nullable::new(self.last_rssi))? + .frame_error_rate(self.frame_error_rate)? + .message_error_rate(self.message_error_rate)? + .rx_on_when_idle(self.rx_on_when_idle)? + .full_thread_device(self.full_thread_device)? + .full_network_data(self.full_network_data)? + .is_child(self.is_child)? + .end() + } +} + +/// Thread Route Table as returned by the `ThreadDiag` trait +#[derive(Debug, Clone, Eq, PartialEq, Hash, FromTLV, ToTLV)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct RouteTable { + pub ext_address: u64, + pub rloc16: u16, + pub router_id: u8, + pub next_hop: u8, + pub path_cost: u8, + pub lqi_in: u8, + pub lqi_out: u8, + pub age: u8, + pub allocated: bool, + pub link_established: bool, +} + +impl RouteTable { + /// Reads the `RouteTable` into the provided `RouteTableStructBuilder`. + fn read_into( + &self, + builder: RouteTableStructBuilder

, + ) -> Result { + builder + .ext_address(self.ext_address)? + .rloc_16(self.rloc16)? + .router_id(self.router_id)? + .next_hop(self.next_hop)? + .path_cost(self.path_cost)? + .lqi_in(self.lqi_in)? + .lqi_out(self.lqi_out)? + .age(self.age)? + .allocated(self.allocated)? + .link_established(self.link_established)? + .end() + } +} + +/// Thread Routing Role as returned by the `ThreadDiag` trait +#[derive(Debug, Clone, Eq, PartialEq, Hash, FromTLV, ToTLV)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SecurityPolicy { + pub rotation_time: u16, + pub flags: u16, +} + +/// Thread Operational Dataset Components as returned by the `ThreadDiag` trait +#[derive(Debug, Clone, Eq, PartialEq, Hash, FromTLV, ToTLV)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct OperationalDatasetComponents { + pub active_timestamp_present: bool, + pub pending_timestamp_present: bool, + pub master_key_present: bool, + pub network_name_present: bool, + pub extended_pan_id_present: bool, + pub mesh_local_prefix_present: bool, + pub delay_present: bool, + pub pan_id_present: bool, + pub channel_present: bool, + pub pskc_present: bool, + pub security_policy_present: bool, + pub channel_mask_present: bool, +} + +impl OperationalDatasetComponents { + /// Reads the `OperationalDatasetComponents` into the provided `OperationalDatasetComponentsBuilder`. + fn read_into( + &self, + builder: OperationalDatasetComponentsBuilder

, + ) -> Result { + builder + .active_timestamp_present(self.active_timestamp_present)? + .pending_timestamp_present(self.pending_timestamp_present)? + .master_key_present(self.master_key_present)? + .network_name_present(self.network_name_present)? + .extended_pan_id_present(self.extended_pan_id_present)? + .mesh_local_prefix_present(self.mesh_local_prefix_present)? + .delay_present(self.delay_present)? + .pan_id_present(self.pan_id_present)? + .channel_present(self.channel_present)? + .pskc_present(self.pskc_present)? + .security_policy_present(self.security_policy_present)? + .channel_mask_present(self.channel_mask_present)? + .end() + } +} + +/// The minimal set of data required to implement the Thread Network Diagnostics Cluster +/// +/// The names of the methods in this trait are matching 1:1 the mandatory attributes of the +/// Thread Network Diagnostics Cluster. +pub trait ThreadDiag: WirelessDiag { + fn channel(&self) -> Result, Error> { + Ok(None) + } + + fn routing_role(&self) -> Result, Error> { + Ok(None) + } + + fn network_name( + &self, + f: &mut dyn FnMut(Option<&str>) -> Result<(), Error>, + ) -> Result<(), Error> { + f(None) + } + + fn pan_id(&self) -> Result, Error> { + Ok(None) + } + + fn extended_pan_id(&self) -> Result, Error> { + Ok(None) + } + + #[allow(clippy::type_complexity)] + fn mesh_local_prefix( + &self, + f: &mut dyn FnMut(Option<&[u8]>) -> Result<(), Error>, + ) -> Result<(), Error> { + f(None) + } + + fn neightbor_table( + &self, + _f: &mut dyn FnMut(&NeighborTable) -> Result<(), Error>, + ) -> Result<(), Error> { + Ok(()) + } + + fn route_table( + &self, + _f: &mut dyn FnMut(&RouteTable) -> Result<(), Error>, + ) -> Result<(), Error> { + Ok(()) + } + + fn partition_id(&self) -> Result, Error> { + Ok(None) + } + + fn weighting(&self) -> Result, Error> { + Ok(None) + } + + fn data_version(&self) -> Result, Error> { + Ok(None) + } + + fn stable_data_version(&self) -> Result, Error> { + Ok(None) + } + + fn leader_router_id(&self) -> Result, Error> { + Ok(None) + } + + fn security_policy(&self) -> Result, Error> { + Ok(None) + } + + #[allow(clippy::type_complexity)] + fn channel_page0_mask( + &self, + f: &mut dyn FnMut(Option<&[u8]>) -> Result<(), Error>, + ) -> Result<(), Error> { + f(None) + } + + #[allow(clippy::type_complexity)] + fn operational_dataset_components( + &self, + f: &mut dyn FnMut(Option<&OperationalDatasetComponents>) -> Result<(), Error>, + ) -> Result<(), Error> { + f(None) + } + + #[allow(clippy::type_complexity)] + fn active_network_faults_list( + &self, + _f: &mut dyn FnMut(NetworkFaultEnum) -> Result<(), Error>, + ) -> Result<(), Error> { + Ok(()) + } +} + +impl ThreadDiag for &T +where + T: ThreadDiag, +{ + fn channel(&self) -> Result, Error> { + (*self).channel() + } + + fn routing_role(&self) -> Result, Error> { + (*self).routing_role() + } + + fn network_name( + &self, + f: &mut dyn FnMut(Option<&str>) -> Result<(), Error>, + ) -> Result<(), Error> { + (*self).network_name(f) + } + + fn pan_id(&self) -> Result, Error> { + (*self).pan_id() + } + + fn extended_pan_id(&self) -> Result, Error> { + (*self).extended_pan_id() + } + + fn mesh_local_prefix( + &self, + f: &mut dyn FnMut(Option<&[u8]>) -> Result<(), Error>, + ) -> Result<(), Error> { + (*self).mesh_local_prefix(f) + } + + fn neightbor_table( + &self, + f: &mut dyn FnMut(&NeighborTable) -> Result<(), Error>, + ) -> Result<(), Error> { + (*self).neightbor_table(f) + } + + fn route_table( + &self, + f: &mut dyn FnMut(&RouteTable) -> Result<(), Error>, + ) -> Result<(), Error> { + (*self).route_table(f) + } + + fn partition_id(&self) -> Result, Error> { + (*self).partition_id() + } + + fn weighting(&self) -> Result, Error> { + (*self).weighting() + } + + fn data_version(&self) -> Result, Error> { + (*self).data_version() + } + + fn stable_data_version(&self) -> Result, Error> { + (*self).stable_data_version() + } + + fn leader_router_id(&self) -> Result, Error> { + (*self).leader_router_id() + } + + fn security_policy(&self) -> Result, Error> { + (*self).security_policy() + } + + fn channel_page0_mask( + &self, + f: &mut dyn FnMut(Option<&[u8]>) -> Result<(), Error>, + ) -> Result<(), Error> { + (*self).channel_page0_mask(f) + } + + fn operational_dataset_components( + &self, + f: &mut dyn FnMut(Option<&OperationalDatasetComponents>) -> Result<(), Error>, + ) -> Result<(), Error> { + (*self).operational_dataset_components(f) + } + + fn active_network_faults_list( + &self, + f: &mut dyn FnMut(NetworkFaultEnum) -> Result<(), Error>, + ) -> Result<(), Error> { + (*self).active_network_faults_list(f) + } +} + +impl ThreadDiag for () {} + +/// A cluster implementing the Matter Thread Diagnostics Cluster. +pub struct ThreadDiagHandler<'a> { + dataver: Dataver, + diag: &'a dyn ThreadDiag, +} + +impl<'a> ThreadDiagHandler<'a> { + /// Create a new instance. + pub const fn new(dataver: Dataver, diag: &'a dyn ThreadDiag) -> Self { + Self { dataver, diag } + } + + /// Adapt the handler instance to the generic `rs-matter` `Handler` trait + pub const fn adapt(self) -> HandlerAdaptor { + HandlerAdaptor(self) + } +} + +impl ClusterHandler for ThreadDiagHandler<'_> { + const CLUSTER: crate::data_model::objects::Cluster<'static> = FULL_CLUSTER + .with_revision(1) + .with_attrs(with!(required)) + .with_cmds(with!()); + + fn dataver(&self) -> u32 { + self.dataver.get() + } + + fn dataver_changed(&self) { + self.dataver.changed(); + } + + fn channel(&self, _ctx: &ReadContext<'_>) -> Result, Error> { + Ok(Nullable::new(self.diag.channel()?)) + } + + fn routing_role(&self, _ctx: &ReadContext<'_>) -> Result, Error> { + Ok(Nullable::new(self.diag.routing_role()?)) + } + + fn network_name( + &self, + _ctx: &ReadContext<'_>, + builder: NullableBuilder>, + ) -> Result { + let mut builder = Some(builder); + let mut parent = None; + + self.diag.network_name(&mut |name| { + if let Some(name) = name { + parent = Some(unwrap!(builder.take()).non_null()?.set(name)?); + } else { + parent = Some(unwrap!(builder.take()).null()?); + } + + Ok(()) + })?; + + Ok(unwrap!(parent)) + } + + fn pan_id(&self, _ctx: &ReadContext<'_>) -> Result, Error> { + Ok(Nullable::new(self.diag.pan_id()?)) + } + + fn extended_pan_id(&self, _ctx: &ReadContext<'_>) -> Result, Error> { + Ok(Nullable::new(self.diag.extended_pan_id()?)) + } + + fn mesh_local_prefix( + &self, + _ctx: &ReadContext<'_>, + builder: NullableBuilder>, + ) -> Result { + let mut builder = Some(builder); + let mut parent = None; + + self.diag.mesh_local_prefix(&mut |prefix| { + if let Some(prefix) = prefix { + parent = Some( + unwrap!(builder.take()) + .non_null()? + .set(Octets::new(prefix))?, + ); + } else { + parent = Some(unwrap!(builder.take()).null()?); + } + + Ok(()) + })?; + + Ok(unwrap!(parent)) + } + + fn neighbor_table( + &self, + _ctx: &ReadContext<'_>, + builder: ArrayAttributeRead< + NeighborTableStructArrayBuilder

, + NeighborTableStructBuilder

, + >, + ) -> Result { + match builder { + ArrayAttributeRead::ReadAll(builder) => { + let mut builder = Some(builder); + + self.diag.neightbor_table(&mut |item| { + builder = Some(item.read_into(unwrap!(builder.take()).push()?)?); + + Ok(()) + })?; + + unwrap!(builder).end() + } + ArrayAttributeRead::ReadOne(index, builder) => { + let mut builder = Some(builder); + let mut parent = None; + let mut current = 0; + + self.diag.neightbor_table(&mut |item| { + if index == current { + parent = Some(item.read_into(unwrap!(builder.take()))?); + } + + current += 1; + + Ok(()) + })?; + + if let Some(parent) = parent { + Ok(parent) + } else { + Err(ErrorCode::InvalidAction.into()) + } + } + } + } + + fn route_table( + &self, + _ctx: &ReadContext<'_>, + builder: ArrayAttributeRead, RouteTableStructBuilder

>, + ) -> Result { + match builder { + ArrayAttributeRead::ReadAll(builder) => { + let mut builder = Some(builder); + + self.diag.route_table(&mut |item| { + builder = Some(item.read_into(unwrap!(builder.take()).push()?)?); + + Ok(()) + })?; + + unwrap!(builder).end() + } + ArrayAttributeRead::ReadOne(index, builder) => { + let mut builder = Some(builder); + let mut parent = None; + let mut current = 0; + + self.diag.route_table(&mut |item| { + if index == current { + parent = Some(item.read_into(unwrap!(builder.take()))?); + } + + current += 1; + + Ok(()) + })?; + + if let Some(parent) = parent { + Ok(parent) + } else { + Err(ErrorCode::InvalidAction.into()) + } + } + } + } + + fn partition_id(&self, _ctx: &ReadContext<'_>) -> Result, Error> { + Ok(Nullable::new(self.diag.partition_id()?)) + } + + fn weighting(&self, _ctx: &ReadContext<'_>) -> Result, Error> { + Ok(Nullable::new(self.diag.weighting()?)) + } + + fn data_version(&self, _ctx: &ReadContext<'_>) -> Result, Error> { + Ok(Nullable::new(self.diag.data_version()?)) + } + + fn stable_data_version(&self, _ctx: &ReadContext<'_>) -> Result, Error> { + Ok(Nullable::new(self.diag.stable_data_version()?)) + } + + fn leader_router_id(&self, _ctx: &ReadContext<'_>) -> Result, Error> { + Ok(Nullable::new(self.diag.leader_router_id()?)) + } + + fn security_policy( + &self, + _ctx: &ReadContext<'_>, + builder: NullableBuilder>, + ) -> Result { + let security_policy = self.diag.security_policy()?; + if let Some(security_policy) = security_policy { + builder + .non_null()? + .rotation_time(security_policy.rotation_time)? + .flags(security_policy.flags)? + .end() + } else { + builder.null() + } + } + + fn channel_page_0_mask( + &self, + _ctx: &ReadContext<'_>, + builder: NullableBuilder>, + ) -> Result { + let mut builder = Some(builder); + let mut parent = None; + + self.diag.channel_page0_mask(&mut |mask| { + if let Some(mask) = mask { + parent = Some(unwrap!(builder.take()).non_null()?.set(Octets::new(mask))?); + } else { + parent = Some(unwrap!(builder.take()).null()?); + } + + Ok(()) + })?; + + Ok(unwrap!(parent.take())) + } + + fn operational_dataset_components( + &self, + _ctx: &ReadContext<'_>, + builder: NullableBuilder>, + ) -> Result { + let mut builder = Some(builder); + let mut parent = None; + + self.diag.operational_dataset_components(&mut |dsc| { + if let Some(dsc) = dsc { + parent = Some(dsc.read_into(unwrap!(builder.take()).non_null()?)?); + } else { + parent = Some(unwrap!(builder.take()).null()?); + } + + Ok(()) + })?; + + Ok(unwrap!(parent)) + } + + fn active_network_faults_list( + &self, + _ctx: &ReadContext<'_>, + builder: ArrayAttributeRead< + ToTLVArrayBuilder, + ToTLVBuilder, + >, + ) -> Result { + match builder { + ArrayAttributeRead::ReadAll(builder) => { + let mut builder = Some(builder); + + self.diag.active_network_faults_list(&mut |fault| { + builder = Some(unwrap!(builder.take()).push(&fault)?); + + Ok(()) + })?; + + unwrap!(builder.take()).end() + } + ArrayAttributeRead::ReadOne(index, builder) => { + let mut builder = Some(builder); + let mut parent = None; + let mut current = 0; + + self.diag.active_network_faults_list(&mut |fault| { + if index == current { + parent = Some(unwrap!(builder.take()).set(&fault)?); + } + + current += 1; + + Ok(()) + })?; + + if let Some(parent) = parent { + Ok(parent) + } else { + Err(ErrorCode::InvalidAction.into()) + } + } + } + } + + fn handle_reset_counts(&self, _ctx: &InvokeContext<'_>) -> Result<(), Error> { + Err(ErrorCode::InvalidAction.into()) + } +} diff --git a/rs-matter/src/data_model/sdm/thread_nw_diagnostics.rs b/rs-matter/src/data_model/sdm/thread_nw_diagnostics.rs deleted file mode 100644 index c1e183ec..00000000 --- a/rs-matter/src/data_model/sdm/thread_nw_diagnostics.rs +++ /dev/null @@ -1,497 +0,0 @@ -/* - * - * Copyright (c) 2023 Project CHIP Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -use core::net::Ipv6Addr; - -use strum::{EnumDiscriminants, FromRepr}; - -use crate::data_model::objects::{ - Access, AttrDataEncoder, AttrDataWriter, AttrType, Attribute, Cluster, CmdDataEncoder, Command, - Dataver, Handler, InvokeContext, NonBlockingHandler, Quality, ReadContext, WriteContext, -}; -use crate::error::{Error, ErrorCode}; -use crate::tlv::{FromTLV, TLVTag, TLVWrite, ToTLV}; -use crate::{attribute_enum, attributes, command_enum, commands, with}; - -pub const ID: u32 = 0x0036; - -#[derive(FromRepr, EnumDiscriminants, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u32)] -pub enum Attributes { - Channel(AttrType) = 0x00, - RoutingRole(AttrType) = 0x01, - NetworkName = 0x02, - PanId(AttrType) = 0x03, - ExtendedPanId(AttrType) = 0x04, - MeshLocalPrefix = 0x05, - OverrunCount(AttrType) = 0x06, - NeightborTable = 0x07, - RouteTable = 0x08, - PartitionId(AttrType) = 0x09, - Weighting(AttrType) = 0x0a, - DataVersion(AttrType) = 0x0b, - StableDataVersion(AttrType) = 0x0c, - LeaderRouterId(AttrType) = 0x0d, - DetachedRoleCount(AttrType) = 0x0e, - ChildRoleCount(AttrType) = 0x0f, - RouterRoleCount(AttrType) = 0x10, - LeaderRoleCount(AttrType) = 0x11, - AttachAttemptCount(AttrType) = 0x12, - PartitionIdChangeCount(AttrType) = 0x13, - BetterPartitionAttachChangeCount(AttrType) = 0x14, - ParentChangeCount(AttrType) = 0x15, - TxTotalCount(AttrType) = 0x16, - TxUnicastCount(AttrType) = 0x17, - TxBroadcastCount(AttrType) = 0x18, - TxAckRequestedCount(AttrType) = 0x19, - TxAckedCount(AttrType) = 0x1a, - TxNoAckRequestedCount(AttrType) = 0x1b, - TxDataCount(AttrType) = 0x1c, - TxDataPollCount(AttrType) = 0x1d, - TxBeaconCount(AttrType) = 0x1e, - TxBeaconRequestCount(AttrType) = 0x1f, - TxOtherCount(AttrType) = 0x20, - TxRetryCount(AttrType) = 0x21, - TxDirectMaxRetryExpiryCount(AttrType) = 0x22, - TxIndirectMaxRetryExpiryCount(AttrType) = 0x23, - TxErrCcaCount(AttrType) = 0x24, - TxErrAbortCount(AttrType) = 0x25, - TxErrBusyChannelCount(AttrType) = 0x26, - RxTotalCount(AttrType) = 0x27, - RxUnicastCount(AttrType) = 0x28, - RxBroadcastCount(AttrType) = 0x29, - RxDataCount(AttrType) = 0x2a, - RxDataPollCount(AttrType) = 0x2b, - RxBeaconCount(AttrType) = 0x2c, - RxBeaconRequestCount(AttrType) = 0x2d, - RxOtherCount(AttrType) = 0x2e, - RxAddressFilteredCount(AttrType) = 0x2f, - RxDestAddressFilteredCount(AttrType) = 0x30, - RxDuplicatedCount(AttrType) = 0x31, - RxErrNoFrameCount(AttrType) = 0x32, - RxErrUnknownNeightborCount(AttrType) = 0x33, - RxErrInvalidSrcAddrCount(AttrType) = 0x34, - RxErrSecCount(AttrType) = 0x35, - RxErrFcsCount(AttrType) = 0x36, - RxErrOtherCount(AttrType) = 0x37, - ActiveTimestamp(AttrType) = 0x38, - PendingTimestamp(AttrType) = 0x39, - Delay(AttrType) = 0x3a, - SecurityPolicy = 0x3b, - ChannelPage0Mask = 0x3c, - OperationalDatasetComponents = 0x3d, - ActiveNetworkFaultsList = 0x3e, -} - -attribute_enum!(Attributes); - -#[derive(FromRepr, EnumDiscriminants, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u32)] -pub enum Commands { - ResetCounts = 0x0, -} - -command_enum!(Commands); - -pub const CLUSTER: Cluster<'static> = Cluster { - id: ID as _, - revision: 1, - feature_map: 0, - attributes: attributes!( - Attribute::new( - AttributesDiscriminants::Channel as _, - Access::RV, - Quality::NONE, - ), - Attribute::new( - AttributesDiscriminants::RoutingRole as _, - Access::RV, - Quality::FIXED, - ), - Attribute::new( - AttributesDiscriminants::NetworkName as _, - Access::RV, - Quality::FIXED, - ), - Attribute::new( - AttributesDiscriminants::PanId as _, - Access::RV, - Quality::FIXED, - ), - Attribute::new( - AttributesDiscriminants::ExtendedPanId as _, - Access::RV, - Quality::FIXED, - ), - Attribute::new( - AttributesDiscriminants::MeshLocalPrefix as _, - Access::RV, - Quality::FIXED, - ), - Attribute::new( - AttributesDiscriminants::NeightborTable as _, - Access::RV, - Quality::FIXED, - ), - Attribute::new( - AttributesDiscriminants::RouteTable as _, - Access::RV, - Quality::FIXED, - ), - Attribute::new( - AttributesDiscriminants::PartitionId as _, - Access::RV, - Quality::FIXED, - ), - Attribute::new( - AttributesDiscriminants::Weighting as _, - Access::RV, - Quality::FIXED, - ), - Attribute::new( - AttributesDiscriminants::DataVersion as _, - Access::RV, - Quality::FIXED, - ), - Attribute::new( - AttributesDiscriminants::StableDataVersion as _, - Access::RV, - Quality::FIXED, - ), - Attribute::new( - AttributesDiscriminants::LeaderRouterId as _, - Access::RV, - Quality::FIXED, - ), - Attribute::new( - AttributesDiscriminants::SecurityPolicy as _, - Access::RV, - Quality::FIXED, - ), - Attribute::new( - AttributesDiscriminants::ChannelPage0Mask as _, - Access::RV, - Quality::FIXED, - ), - Attribute::new( - AttributesDiscriminants::OperationalDatasetComponents as _, - Access::RV, - Quality::FIXED, - ), - Attribute::new( - AttributesDiscriminants::ActiveNetworkFaultsList as _, - Access::RV, - Quality::FIXED, - ), - ), - commands: commands!(Command::new( - CommandsDiscriminants::ResetCounts as _, - None, - Access::WA, - ),), - with_attrs: with!(all), - with_cmds: with!(all), -}; - -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, FromTLV, ToTLV, FromRepr)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum RoutingRole { - Unspecified = 0, - Unassigned = 1, - SleepyEndDevice = 2, - EndDevice = 3, - REED = 4, - Router = 5, - Leader = 6, -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, FromTLV, ToTLV, FromRepr)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum NetworkFault { - Unspecified = 0, - Linkdown = 1, - HardwareFailure = 2, - NetworkJammed = 3, -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, FromTLV, ToTLV, FromRepr)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum ConnectionStatus { - Connected = 0, - NotConnected = 1, -} - -#[derive(Debug, Clone, Eq, PartialEq, Hash, FromTLV, ToTLV)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct NeightborTable { - pub ext_address: u64, - pub age: u32, - pub rloc16: u16, - pub link_frame_counter: u32, - pub mle_frame_counter: u32, - pub lqi: u8, - pub average_rssi: i8, - pub last_rssi: i8, - pub frame_error_rate: u8, - pub message_error_rate: u8, - pub rx_on_when_idle: bool, - pub full_thread_device: bool, - pub full_network_data: bool, - pub is_child: bool, -} - -#[derive(Debug, Clone, Eq, PartialEq, Hash, FromTLV, ToTLV)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct RouteTable { - pub ext_address: u64, - pub rloc16: u16, - pub router_id: u8, - pub next_hop: u8, - pub path_cost: u8, - pub lqi_in: u8, - pub lqi_out: u8, - pub age: u8, - pub allocated: bool, - pub established: bool, -} - -#[derive(Debug, Clone, Eq, PartialEq, Hash, FromTLV, ToTLV)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct SecurityPolicy { - pub rotation_time: u16, - pub flags: u16, -} - -#[derive(Debug, Clone, Eq, PartialEq, Hash, FromTLV, ToTLV)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct OperationalDatasetComponents { - pub active_timestamp_present: bool, - pub pending_timestamp_present: bool, - pub master_key_present: bool, - pub network_name_present: bool, - pub extended_pan_id_present: bool, - pub mesh_local_prefix_present: bool, - pub delay_present: bool, - pub pan_id_present: bool, - pub channel_present: bool, - pub pskc_present: bool, - pub security_policy_present: bool, - pub channel_mask_present: bool, -} - -/// The minimal set of data required to implement the Thread Network Diagnostics Cluster. -pub trait ThreadNwDiagData { - fn channel(&self) -> u16; - - fn routing_role(&self) -> RoutingRole; - - fn network_name(&self) -> &str; - - fn pan_id(&self) -> u16; - - fn extended_pan_id(&self) -> u64; - - fn mesh_local_prefix(&self) -> (Ipv6Addr, u8); - - fn overrun_count(&self) -> u64; - - fn neightbor_table(&self) -> &[NeightborTable]; - - fn route_table(&self) -> &[RouteTable]; - - fn partition_id(&self) -> u32; - - fn weighting(&self) -> u16; - - fn data_version(&self) -> u16; - - fn stable_data_version(&self) -> u16; - - fn leader_router_id(&self) -> u8; - - fn security_policy(&self) -> &SecurityPolicy; - - fn channel_page0_mask(&self) -> &[u8]; - - fn operational_dataset_components(&self) -> &OperationalDatasetComponents; - - fn active_network_faults_list(&self) -> &[NetworkFault]; -} - -/// A cluster implementing the Matter Thread Diagnostics Cluster. -pub struct ThreadNwDiagCluster<'a> { - data_ver: Dataver, - data: &'a dyn ThreadNwDiagData, -} - -impl<'a> ThreadNwDiagCluster<'a> { - /// Create a new instance. - pub const fn new(data_ver: Dataver, data: &'a dyn ThreadNwDiagData) -> Self { - Self { data_ver, data } - } - - /// Read the value of an attribute. - pub fn read( - &self, - ctx: &ReadContext<'_>, - encoder: AttrDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - let attr = ctx.attr(); - - if let Some(mut writer) = encoder.with_dataver(self.data_ver.get())? { - if attr.is_system() { - CLUSTER.read(attr.attr_id, writer) - } else { - match attr.attr_id.try_into()? { - Attributes::Channel(codec) => codec.encode(writer, self.data.channel()), - Attributes::RoutingRole(codec) => { - codec.encode(writer, self.data.routing_role()) - } - Attributes::NetworkName => writer.set(self.data.network_name()), - Attributes::PanId(codec) => codec.encode(writer, self.data.pan_id()), - Attributes::ExtendedPanId(codec) => { - codec.encode(writer, self.data.extended_pan_id()) - } - Attributes::MeshLocalPrefix => { - let (ip, mask) = self.data.mesh_local_prefix(); - - let ip_size = (mask / 8 + if mask % 8 > 0 { 1 } else { 0 }) as usize; - - writer.stri( - &AttrDataWriter::TAG, - ip_size + 1, - core::iter::once(mask).chain(ip.octets().into_iter().take(ip_size)), - ) - } - Attributes::OverrunCount(codec) => { - codec.encode(writer, self.data.overrun_count()) - } - Attributes::NeightborTable => { - writer.start_array(&AttrDataWriter::TAG)?; - - for table in self.data.neightbor_table() { - table.to_tlv(&TLVTag::Anonymous, &mut *writer)?; - } - - writer.end_container() - } - Attributes::RouteTable => { - writer.start_array(&AttrDataWriter::TAG)?; - - for table in self.data.route_table() { - table.to_tlv(&TLVTag::Anonymous, &mut *writer)?; - } - - writer.end_container() - } - Attributes::PartitionId(codec) => { - codec.encode(writer, self.data.partition_id()) - } - Attributes::Weighting(codec) => codec.encode(writer, self.data.weighting()), - Attributes::DataVersion(codec) => { - codec.encode(writer, self.data.data_version()) - } - Attributes::StableDataVersion(codec) => { - codec.encode(writer, self.data.stable_data_version()) - } - Attributes::LeaderRouterId(codec) => { - codec.encode(writer, self.data.leader_router_id()) - } - Attributes::SecurityPolicy => writer.set(self.data.security_policy()), - Attributes::ChannelPage0Mask => { - writer.str(&AttrDataWriter::TAG, self.data.channel_page0_mask()) - } - Attributes::OperationalDatasetComponents => { - writer.set(self.data.operational_dataset_components()) - } - Attributes::ActiveNetworkFaultsList => { - writer.start_array(&AttrDataWriter::TAG)?; - - for table in self.data.active_network_faults_list() { - table.to_tlv(&TLVTag::Anonymous, &mut *writer)?; - } - - writer.end_container() - } - other => { - error!("Attribute {:?} not supported", other); - Err(ErrorCode::AttributeNotFound.into()) - } - } - } - } else { - Ok(()) - } - } - - /// Write the value of an attribute. - pub fn write(&self, ctx: &WriteContext<'_>) -> Result<(), Error> { - ctx.attr().check_dataver(self.data_ver.get())?; - - self.data_ver.changed(); - - Ok(()) - } - - /// Invoke a command. - pub fn invoke( - &self, - ctx: &InvokeContext<'_>, - _encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - let cmd = ctx.cmd(); - - match cmd.cmd_id.try_into()? { - Commands::ResetCounts => { - debug!("ResetCounts: Not yet supported"); - } - } - - self.data_ver.changed(); - - Ok(()) - } -} - -impl Handler for ThreadNwDiagCluster<'_> { - fn read( - &self, - ctx: &ReadContext<'_>, - encoder: AttrDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - ThreadNwDiagCluster::read(self, ctx, encoder) - } - - fn write(&self, ctx: &WriteContext<'_>) -> Result<(), Error> { - ThreadNwDiagCluster::write(self, ctx) - } - - fn invoke( - &self, - ctx: &InvokeContext<'_>, - encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - ThreadNwDiagCluster::invoke(self, ctx, encoder) - } -} - -impl NonBlockingHandler for ThreadNwDiagCluster<'_> {} diff --git a/rs-matter/src/data_model/sdm/wifi_diag.rs b/rs-matter/src/data_model/sdm/wifi_diag.rs new file mode 100644 index 00000000..a39c69ee --- /dev/null +++ b/rs-matter/src/data_model/sdm/wifi_diag.rs @@ -0,0 +1,176 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! This module contains the implementation of the Wifi Network Diagnostics cluster and its handler. + +use crate::data_model::objects::{Cluster, Dataver, InvokeContext, ReadContext}; +use crate::error::{Error, ErrorCode}; +use crate::tlv::{Nullable, NullableBuilder, Octets, OctetsBuilder, TLVBuilderParent}; +use crate::with; + +pub use crate::data_model::clusters::wi_fi_network_diagnostics::*; + +/// A trait required by `WifiDiag` and `ThreadDiag` that provides information whether the +/// devicde is connected to a wireless network +pub trait WirelessDiag { + /// Returns true if the device is connected to a wireless network + fn connected(&self) -> Result { + Ok(false) + } +} + +impl WirelessDiag for &T +where + T: WirelessDiag, +{ + fn connected(&self) -> Result { + (*self).connected() + } +} + +impl WirelessDiag for () {} + +/// A trait for the Wifi Diagnostics cluster. +/// +/// The names of the methods in this trait are matching 1:1 the mandatory attributes of the +/// Wifi Diagnostics cluster. +pub trait WifiDiag: WirelessDiag { + #[allow(clippy::type_complexity)] + fn bssid(&self, f: &mut dyn FnMut(Option<&[u8]>) -> Result<(), Error>) -> Result<(), Error> { + f(None) + } + + fn security_type(&self) -> Result, Error> { + Ok(Nullable::none()) + } + + fn wi_fi_version(&self) -> Result, Error> { + Ok(Nullable::none()) + } + + fn channel_number(&self) -> Result, Error> { + Ok(Nullable::none()) + } + + fn rssi(&self) -> Result, Error> { + Ok(Nullable::none()) + } +} + +impl WifiDiag for &T +where + T: WifiDiag, +{ + fn bssid(&self, f: &mut dyn FnMut(Option<&[u8]>) -> Result<(), Error>) -> Result<(), Error> { + (*self).bssid(f) + } + + fn security_type(&self) -> Result, Error> { + (*self).security_type() + } + + fn wi_fi_version(&self) -> Result, Error> { + (*self).wi_fi_version() + } + + fn channel_number(&self) -> Result, Error> { + (*self).channel_number() + } + + fn rssi(&self) -> Result, Error> { + (*self).rssi() + } +} + +impl WifiDiag for () {} + +/// A cluster implementing the Matter Wifi Diagnostics Cluster. +#[derive(Clone)] +pub struct WifiDiagHandler<'a> { + dataver: Dataver, + diag: &'a dyn WifiDiag, +} + +impl<'a> WifiDiagHandler<'a> { + /// Create a new instance. + pub const fn new(dataver: Dataver, diag: &'a dyn WifiDiag) -> Self { + Self { dataver, diag } + } + + /// Adapt the handler instance to the generic `rs-matter` `Handler` trait + pub const fn adapt(self) -> HandlerAdaptor { + HandlerAdaptor(self) + } +} + +impl ClusterHandler for WifiDiagHandler<'_> { + const CLUSTER: Cluster<'static> = FULL_CLUSTER + .with_revision(1) + .with_attrs(with!(required)) + .with_cmds(with!()); + + fn dataver(&self) -> u32 { + self.dataver.get() + } + + fn dataver_changed(&self) { + self.dataver.changed(); + } + + fn bssid( + &self, + _ctx: &ReadContext<'_>, + builder: NullableBuilder>, + ) -> Result { + let mut builder = Some(builder); + let mut parent = None; + + self.diag.bssid(&mut |bssid| { + let builder = unwrap!(builder.take()); + + parent = Some(if let Some(bssid) = bssid { + builder.non_null()?.set(Octets::new(bssid))? + } else { + builder.null()? + }); + + Ok(()) + })?; + + Ok(unwrap!(parent.take())) + } + + fn security_type(&self, _ctx: &ReadContext<'_>) -> Result, Error> { + self.diag.security_type() + } + + fn wi_fi_version(&self, _ctx: &ReadContext<'_>) -> Result, Error> { + self.diag.wi_fi_version() + } + + fn channel_number(&self, _ctx: &ReadContext<'_>) -> Result, Error> { + self.diag.channel_number() + } + + fn rssi(&self, _ctx: &ReadContext<'_>) -> Result, Error> { + self.diag.rssi() + } + + fn handle_reset_counts(&self, _ctx: &InvokeContext<'_>) -> Result<(), Error> { + Err(ErrorCode::InvalidAction.into()) + } +} diff --git a/rs-matter/src/data_model/sdm/wifi_nw_diagnostics.rs b/rs-matter/src/data_model/sdm/wifi_nw_diagnostics.rs deleted file mode 100644 index 2fb2c1c8..00000000 --- a/rs-matter/src/data_model/sdm/wifi_nw_diagnostics.rs +++ /dev/null @@ -1,266 +0,0 @@ -/* - * - * Copyright (c) 2023 Project CHIP Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -use core::cell::RefCell; - -use rs_matter_macros::{FromTLV, ToTLV}; - -use strum::{EnumDiscriminants, FromRepr}; - -use crate::data_model::objects::{ - Access, AttrDataEncoder, AttrType, Attribute, Cluster, CmdDataEncoder, Command, Dataver, - Handler, InvokeContext, NonBlockingHandler, Quality, ReadContext, WriteContext, -}; -use crate::error::{Error, ErrorCode}; -use crate::tlv::{TLVTag, TLVWrite}; -use crate::{attribute_enum, attributes, command_enum, commands, with}; - -pub const ID: u32 = 0x0036; - -#[derive(FromRepr, EnumDiscriminants, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u32)] -pub enum Attributes { - Bssid = 0x00, - SecurityType(AttrType) = 0x01, - WifiVersion(AttrType) = 0x02, - ChannelNumber(AttrType) = 0x03, - Rssi(AttrType) = 0x04, - BeaconLostCount(AttrType) = 0x05, - BeaconRxCount(AttrType) = 0x06, - PacketMulticastRxCount(AttrType) = 0x07, - PacketMulticastTxCount(AttrType) = 0x08, - PacketUnicastRxCount(AttrType) = 0x09, - PacketUnicastTxCount(AttrType) = 0x0a, - CurrentMaxRate(AttrType) = 0x0b, - OverrunCount(AttrType) = 0x0c, -} - -attribute_enum!(Attributes); - -#[derive(FromRepr, EnumDiscriminants, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u32)] -pub enum Commands { - ResetCounts = 0x0, -} - -command_enum!(Commands); - -pub const CLUSTER: Cluster<'static> = Cluster { - id: ID as _, - revision: 1, - feature_map: 0, - attributes: attributes!( - Attribute::new( - AttributesDiscriminants::Bssid as _, - Access::RV, - Quality::NONE, - ), - Attribute::new( - AttributesDiscriminants::SecurityType as _, - Access::RV, - Quality::FIXED, - ), - Attribute::new( - AttributesDiscriminants::WifiVersion as _, - Access::RV, - Quality::FIXED, - ), - Attribute::new( - AttributesDiscriminants::ChannelNumber as _, - Access::RV, - Quality::FIXED, - ), - Attribute::new( - AttributesDiscriminants::Rssi as _, - Access::RV, - Quality::FIXED, - ), - ), - commands: commands!(Command::new( - CommandsDiscriminants::ResetCounts as _, - None, - Access::WA, - ),), - with_attrs: with!(all), - with_cmds: with!(all), -}; - -#[derive(Debug, Copy, Clone, Eq, PartialEq, FromTLV, ToTLV, FromRepr)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum WiFiSecurity { - Unspecified = 0, - Unencrypted = 1, - Wep = 2, - WpaPersonal = 3, - Wpa2Personal = 4, - Wpa3Personal = 5, -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq, FromTLV, ToTLV, FromRepr)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum WiFiVersion { - A = 0, - B = 1, - G = 2, - N = 3, - AC = 4, - AX = 5, -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq, FromTLV, ToTLV, FromRepr)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum AssociationFailure { - Unknown = 0, - AssociationFailed = 1, - AuthenticationFailed = 2, - SsidNotFound = 3, -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq, FromTLV, ToTLV, FromRepr)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum ConnectionStatus { - Connected = 0, - NotConnected = 1, -} - -#[derive(Debug, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct WifiNwDiagData { - pub bssid: [u8; 6], - pub security_type: WiFiSecurity, - pub wifi_version: WiFiVersion, - pub channel_number: u16, - pub rssi: i8, -} - -/// A cluster implementing the Matter Wifi Diagnostics Cluster. -#[derive(Debug, Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct WifiNwDiagCluster { - data_ver: Dataver, - data: RefCell, -} - -impl WifiNwDiagCluster { - /// Create a new instance. - pub const fn new(data_ver: Dataver, data: WifiNwDiagData) -> Self { - Self { - data_ver, - data: RefCell::new(data), - } - } - - pub fn set(&self, data: WifiNwDiagData) -> bool { - if *self.data.borrow() != data { - *self.data.borrow_mut() = data; - self.data_ver.changed(); - - true - } else { - false - } - } - - /// Read the value of an attribute. - pub fn read( - &self, - ctx: &ReadContext<'_>, - encoder: AttrDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - let attr = ctx.attr(); - - if let Some(mut writer) = encoder.with_dataver(self.data_ver.get())? { - if attr.is_system() { - CLUSTER.read(attr.attr_id, writer) - } else { - let data = self.data.borrow(); - - match attr.attr_id.try_into()? { - Attributes::Bssid => writer.str(&TLVTag::Anonymous, &data.bssid), - Attributes::SecurityType(codec) => codec.encode(writer, data.security_type), - Attributes::WifiVersion(codec) => codec.encode(writer, data.wifi_version), - Attributes::ChannelNumber(codec) => codec.encode(writer, data.channel_number), - Attributes::Rssi(codec) => codec.encode(writer, data.rssi), - other => { - error!("Attribute {:?} not supported", other); - Err(ErrorCode::AttributeNotFound.into()) - } - } - } - } else { - Ok(()) - } - } - - /// Write the value of an attribute. - pub fn write(&self, ctx: &WriteContext<'_>) -> Result<(), Error> { - ctx.attr().check_dataver(self.data_ver.get())?; - - self.data_ver.changed(); - - Ok(()) - } - - /// Invoke a command. - pub fn invoke( - &self, - ctx: &InvokeContext<'_>, - _encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - let cmd = ctx.cmd(); - - match cmd.cmd_id.try_into()? { - Commands::ResetCounts => { - debug!("ResetCounts: Not yet supported"); - } - } - - self.data_ver.changed(); - - Ok(()) - } -} - -impl Handler for WifiNwDiagCluster { - fn read( - &self, - ctx: &ReadContext<'_>, - encoder: AttrDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - WifiNwDiagCluster::read(self, ctx, encoder) - } - - fn write(&self, ctx: &WriteContext<'_>) -> Result<(), Error> { - WifiNwDiagCluster::write(self, ctx) - } - - fn invoke( - &self, - ctx: &InvokeContext<'_>, - encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - WifiNwDiagCluster::invoke(self, ctx, encoder) - } -} - -impl NonBlockingHandler for WifiNwDiagCluster {} diff --git a/rs-matter/src/data_model/system_model/access_control.rs b/rs-matter/src/data_model/system_model/acl.rs similarity index 55% rename from rs-matter/src/data_model/system_model/access_control.rs rename to rs-matter/src/data_model/system_model/acl.rs index 1e5ec637..ceb53bff 100644 --- a/rs-matter/src/data_model/system_model/access_control.rs +++ b/rs-matter/src/data_model/system_model/acl.rs @@ -15,226 +15,214 @@ * limitations under the License. */ -use core::num::NonZeroU8; +//! This module contains the implementation of the Access Control cluster and its handler. -use strum::{EnumDiscriminants, FromRepr}; +use core::num::NonZeroU8; use crate::acl::{self, AclEntry}; -use crate::data_model::objects::*; +use crate::data_model::objects::{ + ArrayAttributeRead, ArrayAttributeWrite, AttrDetails, ChangeNotify, Cluster, Dataver, + ReadContext, WriteContext, +}; use crate::error::{Error, ErrorCode}; use crate::fabric::FabricMgr; -use crate::interaction_model::messages::ib::{attr_list_write, ListOperation}; -use crate::tlv::{FromTLV, TLVElement, TLVTag, TLVWrite, ToTLV}; -use crate::{attribute_enum, attributes, commands, with}; +use crate::tlv::{TLVArray, TLVBuilderParent}; +use crate::with; -pub const ID: u32 = 0x001F; - -#[derive(FromRepr, EnumDiscriminants, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u32)] -pub enum Attributes { - Acl(()) = 0, - Extension(()) = 1, - SubjectsPerEntry(AttrType) = 2, - TargetsPerEntry(AttrType) = 3, - EntriesPerFabric(AttrType) = 4, -} - -attribute_enum!(Attributes); - -pub const CLUSTER: Cluster<'static> = Cluster { - id: ID, - revision: 1, - feature_map: 0, - attributes: attributes!( - Attribute::new( - AttributesDiscriminants::Acl as _, - Access::RWFA, - Quality::NONE, - ), - Attribute::new( - AttributesDiscriminants::Extension as _, - Access::RWFA, - Quality::NONE, - ), - Attribute::new( - AttributesDiscriminants::SubjectsPerEntry as _, - Access::RV, - Quality::FIXED, - ), - Attribute::new( - AttributesDiscriminants::TargetsPerEntry as _, - Access::RV, - Quality::FIXED, - ), - Attribute::new( - AttributesDiscriminants::EntriesPerFabric as _, - Access::RV, - Quality::FIXED, - ), - ), - commands: commands!(), - with_attrs: with!(all), - with_cmds: with!(all), -}; +pub use crate::data_model::clusters::access_control::*; +/// The system implementation of a handler for the Access Control Matter cluster. #[derive(Debug, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct AccessControlCluster { - data_ver: Dataver, +pub struct AclHandler { + dataver: Dataver, } -impl AccessControlCluster { - pub const fn new(data_ver: Dataver) -> Self { - Self { data_ver } +impl AclHandler { + /// Create a new instance of `AclHandler` with the given `dataver` + pub const fn new(dataver: Dataver) -> Self { + Self { dataver } } - pub fn read( - &self, - ctx: &ReadContext<'_>, - encoder: AttrDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - self.read_acl_attr( - &ctx.exchange().matter().fabric_mgr.borrow(), - ctx.attr(), - encoder, - ) - } - - pub fn write(&self, ctx: &WriteContext<'_>) -> Result<(), Error> { - let exchange = ctx.exchange(); - let attr = ctx.attr(); - let data = ctx.data(); - - match attr.attr_id.try_into()? { - Attributes::Acl(_) => { - attr.check_dataver(self.data_ver.get())?; - attr_list_write(attr, data, |op, data| { - self.write_acl_attr( - &mut exchange.matter().fabric_mgr.borrow_mut(), - &op, - data, - NonZeroU8::new(attr.fab_idx).ok_or(ErrorCode::Invalid)?, - ) - }) - } - other => { - error!("Attribute {:?} not supported", other); - Err(ErrorCode::AttributeNotFound.into()) - } - } + /// Adapt the handler instance to the generic `rs-matter` `Handler` trait + pub const fn adapt(self) -> HandlerAdaptor { + HandlerAdaptor(self) } - fn read_acl_attr( + /// For unit-testing + /// Read the ACL entries from the fabric manager and write them into the builder + fn acl( &self, fabric_mgr: &FabricMgr, - attr: &AttrDetails, - encoder: AttrDataEncoder, - ) -> Result<(), Error> { - if let Some(mut writer) = encoder.with_dataver(self.data_ver.get())? { - if attr.is_system() { - CLUSTER.read(attr.attr_id, writer) - } else { - match attr.attr_id.try_into()? { - Attributes::Acl(_) => { - writer.start_array(&AttrDataWriter::TAG)?; - for fabric in fabric_mgr.iter() { - if !attr.fab_filter || fabric.fab_idx().get() == attr.fab_idx { - for entry in fabric.acl_iter() { - entry.to_tlv(&TLVTag::Anonymous, &mut *writer)?; - } - } - } - writer.end_container()?; - - writer.complete() - } - Attributes::Extension(_) => { - // Empty for now - writer.start_array(&AttrDataWriter::TAG)?; - writer.end_container()?; - - writer.complete() - } - Attributes::SubjectsPerEntry(codec) => { - codec.encode(writer, acl::SUBJECTS_PER_ENTRY as u16) - } - Attributes::TargetsPerEntry(codec) => { - codec.encode(writer, acl::TARGETS_PER_ENTRY as u16) - } - Attributes::EntriesPerFabric(codec) => { - codec.encode(writer, acl::ENTRIES_PER_FABRIC as u16) - } + attr: &AttrDetails<'_>, + builder: ArrayAttributeRead< + AccessControlEntryStructArrayBuilder

, + AccessControlEntryStructBuilder

, + >, + ) -> Result { + let mut acls = fabric_mgr + .iter() + .filter(|fabric| !attr.fab_filter || fabric.fab_idx().get() == attr.fab_idx) + .flat_map(|fabric| fabric.acl_iter().map(|entry| (fabric.fab_idx(), entry))); + + match builder { + ArrayAttributeRead::ReadAll(mut builder) => { + for (fab_idx, entry) in acls { + builder = entry.read_into(fab_idx, builder.push()?)?; } + + builder.end() + } + ArrayAttributeRead::ReadOne(index, builder) => { + let Some((fab_idx, entry)) = acls.nth(index as usize) else { + return Err(ErrorCode::InvalidAction.into()); // TODO + }; + + entry.read_into(fab_idx, builder) } - } else { - Ok(()) } } - /// Write the ACL Attribute - /// - /// This takes care of 4 things, add item, edit item, delete item, delete list. - /// Care about fabric-scoped behaviour is taken - fn write_acl_attr( + /// For unit-testing + /// Set the ACL entries in the fabric manager + fn set_acl( &self, fabric_mgr: &mut FabricMgr, - op: &ListOperation, - data: &TLVElement, fab_idx: NonZeroU8, + value: ArrayAttributeWrite< + TLVArray<'_, AccessControlEntryStruct<'_>>, + AccessControlEntryStruct<'_>, + >, + _notify: &dyn ChangeNotify, ) -> Result<(), Error> { - debug!("Performing ACL operation {:?}", op); - match op { - ListOperation::AddItem | ListOperation::EditItem(_) => { - let acl_entry = AclEntry::from_tlv(data)?; - debug!("ACL {:?}", acl_entry); - - if let ListOperation::EditItem(index) = op { - fabric_mgr.acl_update(fab_idx, *index as _, acl_entry)?; - } else { - fabric_mgr.acl_add(fab_idx, acl_entry)?; + match value { + ArrayAttributeWrite::Replace(list) => { + // Check the well-formedness of the list first + for entry in &list { + let entry = entry?; + entry.check()?; + } + if list.iter().count() > acl::ENTRIES_PER_FABRIC { + Err(ErrorCode::InvalidAction)?; } - Ok(()) + // Now add everything + fabric_mgr.acl_remove_all(fab_idx)?; + for entry in list { + let entry = unwrap!(entry); + unwrap!(fabric_mgr.acl_add_init(fab_idx, AclEntry::init_with(fab_idx, &entry))); + } + } + ArrayAttributeWrite::Add(entry) => { + fabric_mgr.acl_add_init(fab_idx, AclEntry::init_with(fab_idx, &entry))?; + } + ArrayAttributeWrite::Update(index, entry) => { + fabric_mgr.acl_update_init( + fab_idx, + index as _, + AclEntry::init_with(fab_idx, &entry), + )?; + } + ArrayAttributeWrite::Remove(index) => { + fabric_mgr.acl_remove(fab_idx, index as _)?; } - ListOperation::DeleteItem(index) => fabric_mgr.acl_remove(fab_idx, *index as _), - ListOperation::DeleteList => fabric_mgr.acl_remove_all(fab_idx), } + + Ok(()) } } -impl Handler for AccessControlCluster { - fn read( +impl ClusterHandler for AclHandler { + const CLUSTER: Cluster<'static> = FULL_CLUSTER + .with_revision(1) + .with_attrs(with!(required)) + .with_cmds(with!()); + + fn dataver(&self) -> u32 { + self.dataver.get() + } + + fn dataver_changed(&self) { + self.dataver.changed(); + } + + fn acl( &self, ctx: &ReadContext<'_>, - encoder: AttrDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - AccessControlCluster::read(self, ctx, encoder) + builder: ArrayAttributeRead< + AccessControlEntryStructArrayBuilder

, + AccessControlEntryStructBuilder

, + >, + ) -> Result { + self.acl( + &ctx.exchange().matter().fabric_mgr.borrow(), + ctx.attr(), + builder, + ) } - fn write(&self, ctx: &WriteContext<'_>) -> Result<(), Error> { - AccessControlCluster::write(self, ctx) + fn subjects_per_access_control_entry(&self, _ctx: &ReadContext<'_>) -> Result { + Ok(acl::SUBJECTS_PER_ENTRY as _) + } + + fn targets_per_access_control_entry(&self, _ctx: &ReadContext<'_>) -> Result { + Ok(acl::TARGETS_PER_ENTRY as _) + } + + fn access_control_entries_per_fabric(&self, _ctx: &ReadContext<'_>) -> Result { + Ok(acl::ENTRIES_PER_FABRIC as _) + } + + fn set_acl( + &self, + ctx: &WriteContext<'_>, + value: ArrayAttributeWrite< + TLVArray<'_, AccessControlEntryStruct<'_>>, + AccessControlEntryStruct<'_>, + >, + ) -> Result<(), Error> { + let fab_idx = NonZeroU8::new(ctx.attr().fab_idx).ok_or(ErrorCode::Invalid)?; + self.set_acl( + &mut ctx.exchange().matter().fabric_mgr.borrow_mut(), + fab_idx, + value, + ctx.notify, + ) } } -impl NonBlockingHandler for AccessControlCluster {} +impl AccessControlEntryStruct<'_> { + /// Checks the well-formedness of the TLV value + // TODO: This should be auto-generated by the `import!` macro + pub(crate) fn check(&self) -> Result<(), Error> { + self.auth_mode()?; + self.privilege()?; + self.subjects()?; + self.targets()?; + + Ok(()) + } +} #[cfg(test)] mod tests { + use core::num::NonZeroU8; + use crate::acl::{AclEntry, AuthMode}; use crate::crypto::KeyPair; - use crate::data_model::objects::{AttrDataEncoder, AttrDetails, Node, Privilege}; - use crate::data_model::system_model::access_control::Dataver; - use crate::fabric::FabricMgr; - use crate::interaction_model::messages::ib::ListOperation; - use crate::tlv::{ - get_root_node_struct, TLVControl, TLVElement, TLVTag, TLVTagType, TLVValueType, TLVWriter, - ToTLV, + use crate::data_model::objects::{ + ArrayAttributeRead, ArrayAttributeWrite, AttrDataEncoder, AttrDataWriter, AttrDetails, + Node, Privilege, + }; + use crate::data_model::system_model::acl::{ + AccessControlEntryStruct, AccessControlEntryStructArrayBuilder, Dataver, }; + use crate::fabric::FabricMgr; + use crate::tlv::{get_root_node_struct, TLVElement, TLVTag, TLVWriteParent, TLVWriter, ToTLV}; use crate::utils::rand::dummy_rand; use crate::utils::storage::WriteBuf; - use super::AccessControlCluster; + use super::AclHandler; use crate::acl::tests::{FAB_1, FAB_2}; @@ -250,7 +238,7 @@ mod tests { // Add fabric with ID 1 unwrap!(fab_mgr.add_with_post_init(unwrap!(KeyPair::new(dummy_rand)), |_| Ok(()))); - let acl = AccessControlCluster::new(Dataver::new(0)); + let acl = AclHandler::new(Dataver::new(0)); let new = AclEntry::new(Some(FAB_2), Privilege::VIEW, AuthMode::Case); @@ -259,8 +247,7 @@ mod tests { // Test, ACL has fabric index 2, but the accessing fabric is 1 // the fabric index in the TLV should be ignored and the ACL should be created with entry 1 - let result = acl.write_acl_attr(&mut fab_mgr, &ListOperation::AddItem, &data, FAB_1); - assert!(result.is_ok()); + acl_add(&acl, &mut fab_mgr, &data, FAB_1); let verifier = AclEntry::new(Some(FAB_1), Privilege::VIEW, AuthMode::Case); for fabric in fab_mgr.iter() { @@ -298,17 +285,16 @@ mod tests { for i in &verifier { fab_mgr.acl_add(i.fab_idx.unwrap(), i.clone()).unwrap(); } - let acl = AccessControlCluster::new(Dataver::new(0)); + let acl = AclHandler::new(Dataver::new(0)); let new = AclEntry::new(Some(FAB_2), Privilege::VIEW, AuthMode::Case); new.to_tlv(&TLVTag::Anonymous, &mut tw).unwrap(); let data = get_root_node_struct(writebuf.as_slice()).unwrap(); // Test, Edit Fabric 2's index 1 - with accessing fabric as 2 - allow - let result = acl.write_acl_attr(&mut fab_mgr, &ListOperation::EditItem(1), &data, FAB_2); + acl_edit(&acl, &mut fab_mgr, 1, &data, FAB_2); // Fabric 2's index 1, is actually our index 2, update the verifier verifier[2] = new; - assert!(result.is_ok()); // Also validate in the fab_mgr that the entries are in the right order assert_eq!(fab_mgr.get(FAB_1).unwrap().acl_iter().count(), 1); @@ -351,14 +337,10 @@ mod tests { for i in &input { fab_mgr.acl_add(i.fab_idx.unwrap(), i.clone()).unwrap(); } - let acl = AccessControlCluster::new(Dataver::new(0)); - // data is don't-care actually - let data = &[TLVControl::new(TLVTagType::Anonymous, TLVValueType::Null).as_raw()]; - let data = TLVElement::new(data.as_slice()); + let acl = AclHandler::new(Dataver::new(0)); // Test: delete Fabric 1's index 0 - let result = acl.write_acl_attr(&mut fab_mgr, &ListOperation::DeleteItem(0), &data, FAB_1); - assert!(result.is_ok()); + acl_remove(&acl, &mut fab_mgr, 0, FAB_1); let verifier = [input[0].clone(), input[2].clone()]; // Also validate in the fab_mgr that the entries are in the right order @@ -398,7 +380,8 @@ mod tests { for i in input { fab_mgr.acl_add(i.fab_idx.unwrap(), i).unwrap(); } - let acl = AccessControlCluster::new(Dataver::new(0)); + let acl = AclHandler::new(Dataver::new(0)); + // Test 1, all 3 entries are read in the response without fabric filtering { let attr = AttrDetails { @@ -416,10 +399,7 @@ mod tests { wildcard: false, }; - let mut tw = TLVWriter::new(&mut writebuf); - let encoder = AttrDataEncoder::new(&attr, &mut tw); - - acl.read_acl_attr(&fab_mgr, &attr, encoder).unwrap(); + acl_read(&acl, &fab_mgr, &attr, &mut writebuf); assert_eq!( &[ 21, 53, 1, 36, 0, 0, 55, 1, 36, 2, 0, 36, 3, 0, 36, 4, 0, 24, 54, 2, 21, 36, 1, @@ -449,10 +429,7 @@ mod tests { wildcard: false, }; - let mut tw = TLVWriter::new(&mut writebuf); - let encoder = AttrDataEncoder::new(&attr, &mut tw); - - acl.read_acl_attr(&fab_mgr, &attr, encoder).unwrap(); + acl_read(&acl, &fab_mgr, &attr, &mut writebuf); assert_eq!( &[ 21, 53, 1, 36, 0, 0, 55, 1, 36, 2, 0, 36, 3, 0, 36, 4, 0, 24, 54, 2, 21, 36, 1, @@ -480,10 +457,7 @@ mod tests { wildcard: false, }; - let mut tw = TLVWriter::new(&mut writebuf); - let encoder = AttrDataEncoder::new(&attr, &mut tw); - - acl.read_acl_attr(&fab_mgr, &attr, encoder).unwrap(); + acl_read(&acl, &fab_mgr, &attr, &mut writebuf); assert_eq!( &[ 21, 53, 1, 36, 0, 0, 55, 1, 36, 2, 0, 36, 3, 0, 36, 4, 0, 24, 54, 2, 21, 36, 1, @@ -494,4 +468,59 @@ mod tests { ); } } + + fn acl_read( + acl: &AclHandler, + fab_mgr: &FabricMgr, + attr: &AttrDetails<'_>, + writebuf: &mut WriteBuf<'_>, + ) { + let mut tw = TLVWriter::new(writebuf); + let encoder = AttrDataEncoder::new(attr, &mut tw); + let mut writer = unwrap!(unwrap!(encoder.with_dataver(acl.dataver.get()))); + let build_root = TLVWriteParent::new((), writer.writer()); + unwrap!(acl.acl( + fab_mgr, + attr, + ArrayAttributeRead::ReadAll(unwrap!(AccessControlEntryStructArrayBuilder::new( + build_root, + &AttrDataWriter::TAG + ))) + )); + + unwrap!(writer.complete()); + } + + fn acl_add( + acl: &AclHandler, + fab_mgr: &mut FabricMgr, + data: &TLVElement<'_>, + fab_idx: NonZeroU8, + ) { + unwrap!(acl.set_acl( + fab_mgr, + fab_idx, + ArrayAttributeWrite::Add(AccessControlEntryStruct::new(data.clone())), + &() + )); + } + + fn acl_edit( + acl: &AclHandler, + fab_mgr: &mut FabricMgr, + index: u16, + data: &TLVElement<'_>, + fab_idx: NonZeroU8, + ) { + unwrap!(acl.set_acl( + fab_mgr, + fab_idx, + ArrayAttributeWrite::Update(index, AccessControlEntryStruct::new(data.clone())), + &() + )); + } + + fn acl_remove(acl: &AclHandler, fab_mgr: &mut FabricMgr, index: u16, fab_idx: NonZeroU8) { + unwrap!(acl.set_acl(fab_mgr, fab_idx, ArrayAttributeWrite::Remove(index), &())); + } } diff --git a/rs-matter/src/data_model/system_model/desc.rs b/rs-matter/src/data_model/system_model/desc.rs new file mode 100644 index 00000000..66b90f42 --- /dev/null +++ b/rs-matter/src/data_model/system_model/desc.rs @@ -0,0 +1,243 @@ +/* + * + * Copyright (c) 2020-2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! This module contains the implementation of the Descriptor cluster and its handler. + +use core::fmt::Debug; + +use crate::data_model::objects::{ + ArrayAttributeRead, Cluster, Dataver, Endpoint, EndptId, ReadContext, +}; +use crate::error::{Error, ErrorCode}; +use crate::tlv::{TLVBuilderParent, ToTLVArrayBuilder, ToTLVBuilder}; +use crate::with; + +pub use crate::data_model::clusters::descriptor::*; + +/// A parts matcher suitable for regular Matter devices +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct StandardPartsMatcher; + +impl PartsMatcher for StandardPartsMatcher { + fn matches(&self, our_endpoint: EndptId, endpoint: EndptId) -> bool { + our_endpoint == 0 && endpoint != our_endpoint + } +} + +/// A parts matcher suitable for the aggregator endpoints of bridged Matter devices +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct AggregatorPartsMatcher; + +impl PartsMatcher for AggregatorPartsMatcher { + fn matches(&self, our_endpoint: EndptId, endpoint: EndptId) -> bool { + endpoint != our_endpoint && endpoint != 0 + } +} + +/// A trait for describing which endpoints (parts) should be returned +/// from the POV of our endpoint +/// +/// For standard Matter devices, all endpoints should be returned as parts. +/// However - for queries on aggregator endpoints (i.e. those present in Matter bridges) - +/// only endpoints different from the aggregator and from the root endpoint should be returned. +pub trait PartsMatcher: Debug { + /// Return `true` if the endpoint should be returned as a part + /// + /// # Arguments + /// - `our_endpoint`: The endpoint ID of the endpoint that is being queried + /// - `endpoint`: The endpoint ID of the endpoint that is being checked + fn matches(&self, our_endpoint: EndptId, endpoint: EndptId) -> bool; +} + +impl PartsMatcher for &T +where + T: PartsMatcher, +{ + fn matches(&self, our_endpoint: EndptId, endpoint: EndptId) -> bool { + (**self).matches(our_endpoint, endpoint) + } +} + +impl PartsMatcher for &mut T +where + T: PartsMatcher, +{ + fn matches(&self, our_endpoint: EndptId, endpoint: EndptId) -> bool { + (**self).matches(our_endpoint, endpoint) + } +} + +/// The system implementation of a handler for the Descriptor Matter cluster. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DescHandler<'a> { + dataver: Dataver, + matcher: &'a dyn PartsMatcher, +} + +impl DescHandler<'static> { + /// Create a new instance of `DescHandler` with the given `Dataver` + /// and a matcher suitable for regular Matter devices + pub const fn new(dataver: Dataver) -> Self { + Self::new_matching(dataver, &StandardPartsMatcher) + } + + /// Create a new instance of `DescHandler` with the given `Dataver` + /// and a matcher suitable for aggregator endpoints + pub const fn new_aggregator(dataver: Dataver) -> Self { + Self::new_matching(dataver, &AggregatorPartsMatcher) + } +} + +impl<'a> DescHandler<'a> { + /// Create a new instance of `DescHandler` with the given `Dataver` + /// and a custom matcher + pub const fn new_matching(dataver: Dataver, matcher: &'a dyn PartsMatcher) -> DescHandler<'a> { + Self { dataver, matcher } + } + + /// Adapt the handler instance to the generic `rs-matter` `Handler` trait + pub const fn adapt(self) -> HandlerAdaptor { + HandlerAdaptor(self) + } + + fn endpoint<'b>(ctx: &'b ReadContext<'_>) -> Result<&'b Endpoint<'b>, Error> { + ctx.attr() + .node + .endpoint(ctx.attr().endpoint_id) + .ok_or_else(|| ErrorCode::EndpointNotFound.into()) + } +} + +impl ClusterHandler for DescHandler<'_> { + const CLUSTER: Cluster<'static> = FULL_CLUSTER + .with_revision(1) + .with_attrs(with!(required)) + .with_cmds(with!()); + + fn dataver(&self) -> u32 { + self.dataver.get() + } + + fn dataver_changed(&self) { + self.dataver.changed(); + } + + fn device_type_list( + &self, + ctx: &ReadContext<'_>, + builder: ArrayAttributeRead, DeviceTypeStructBuilder

>, + ) -> Result { + let endpoint = Self::endpoint(ctx)?; + + match builder { + ArrayAttributeRead::ReadAll(mut builder) => { + for dev_type in endpoint.device_types { + builder = builder + .push()? + .device_type(dev_type.dtype as _)? + .revision(dev_type.drev)? + .end()?; + } + + builder.end() + } + ArrayAttributeRead::ReadOne(index, builder) => { + let Some(dev_type) = endpoint.device_types.get(index as usize) else { + return Err(ErrorCode::InvalidAction.into()); // TODO + }; + + builder + .device_type(dev_type.dtype as _)? + .revision(dev_type.drev)? + .end() + } + } + } + + fn server_list( + &self, + ctx: &ReadContext<'_>, + builder: ArrayAttributeRead, ToTLVBuilder>, + ) -> Result { + let endpoint = Self::endpoint(ctx)?; + + match builder { + ArrayAttributeRead::ReadAll(mut builder) => { + for cluster in endpoint.clusters { + builder = builder.push(&cluster.id)?; + } + + builder.end() + } + ArrayAttributeRead::ReadOne(index, builder) => { + let Some(cluster) = endpoint.clusters.get(index as usize) else { + return Err(ErrorCode::InvalidAction.into()); // TODO + }; + + builder.set(&cluster.id) + } + } + } + + fn client_list( + &self, + ctx: &ReadContext<'_>, + builder: ArrayAttributeRead, ToTLVBuilder>, + ) -> Result { + let _endpoint = Self::endpoint(ctx)?; + + // Client clusters not support yet + match builder { + ArrayAttributeRead::ReadAll(builder) => builder.end(), + ArrayAttributeRead::ReadOne(_, _) => Err(ErrorCode::InvalidAction.into()), // TODO + } + } + + fn parts_list( + &self, + ctx: &ReadContext<'_>, + builder: ArrayAttributeRead, ToTLVBuilder>, + ) -> Result { + let mut ep_ids = ctx + .attr() + .node + .endpoints + .iter() + .map(|e| e.id) + .filter(|e| self.matcher.matches(ctx.attr().endpoint_id, *e)); + + match builder { + ArrayAttributeRead::ReadAll(mut builder) => { + for id in ep_ids { + builder = builder.push(&id)?; + } + + builder.end() + } + ArrayAttributeRead::ReadOne(index, builder) => { + let Some(ep_id) = ep_ids.nth(index as usize) else { + return Err(ErrorCode::InvalidAction.into()); // TODO + }; + + builder.set(&ep_id) + } + } + } +} diff --git a/rs-matter/src/data_model/system_model/descriptor.rs b/rs-matter/src/data_model/system_model/descriptor.rs deleted file mode 100644 index ce7e0ede..00000000 --- a/rs-matter/src/data_model/system_model/descriptor.rs +++ /dev/null @@ -1,257 +0,0 @@ -/* - * - * Copyright (c) 2020-2022 Project CHIP Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -use core::fmt::Debug; - -use strum::FromRepr; - -use crate::data_model::objects::*; -use crate::error::Error; -use crate::tlv::TLVTag; -use crate::tlv::{TLVWrite, TLVWriter, TagType, ToTLV}; -use crate::{attribute_enum, attributes, commands, with}; - -pub const ID: u32 = 0x001D; - -#[derive(FromRepr)] -#[repr(u32)] -#[allow(clippy::enum_variant_names)] -pub enum Attributes { - DeviceTypeList = 0, - ServerList = 1, - ClientList = 2, - PartsList = 3, -} - -attribute_enum!(Attributes); - -pub const CLUSTER: Cluster<'static> = Cluster { - id: ID as _, - revision: 1, - feature_map: 0, - attributes: attributes!( - Attribute::new(Attributes::DeviceTypeList as _, Access::RV, Quality::NONE), - Attribute::new(Attributes::ServerList as _, Access::RV, Quality::NONE), - Attribute::new(Attributes::PartsList as _, Access::RV, Quality::NONE), - Attribute::new(Attributes::ClientList as _, Access::RV, Quality::NONE), - ), - commands: commands!(), - with_attrs: with!(all), - with_cmds: with!(all), -}; - -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -struct StandardPartsMatcher; - -impl PartsMatcher for StandardPartsMatcher { - fn describe(&self, our_endpoint: EndptId, endpoint: EndptId) -> bool { - our_endpoint == 0 && endpoint != our_endpoint - } -} - -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -struct AggregatorPartsMatcher; - -impl PartsMatcher for AggregatorPartsMatcher { - fn describe(&self, our_endpoint: EndptId, endpoint: EndptId) -> bool { - endpoint != our_endpoint && endpoint != 0 - } -} - -pub trait PartsMatcher: Debug { - fn describe(&self, our_endpoint: EndptId, endpoint: EndptId) -> bool; -} - -impl PartsMatcher for &T -where - T: PartsMatcher, -{ - fn describe(&self, our_endpoint: EndptId, endpoint: EndptId) -> bool { - (**self).describe(our_endpoint, endpoint) - } -} - -impl PartsMatcher for &mut T -where - T: PartsMatcher, -{ - fn describe(&self, our_endpoint: EndptId, endpoint: EndptId) -> bool { - (**self).describe(our_endpoint, endpoint) - } -} - -#[derive(Clone)] -pub struct DescriptorCluster<'a> { - data_ver: Dataver, - matcher: &'a dyn PartsMatcher, -} - -impl DescriptorCluster<'static> { - pub const fn new(data_ver: Dataver) -> Self { - Self::new_matching(data_ver, &StandardPartsMatcher) - } - - pub const fn new_aggregator(data_ver: Dataver) -> Self { - Self::new_matching(data_ver, &AggregatorPartsMatcher) - } -} - -impl<'a> DescriptorCluster<'a> { - pub const fn new_matching( - data_ver: Dataver, - matcher: &'a dyn PartsMatcher, - ) -> DescriptorCluster<'a> { - Self { data_ver, matcher } - } - - pub fn read( - &self, - ctx: &ReadContext<'_>, - encoder: AttrDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - let attr = ctx.attr(); - - if let Some(mut writer) = encoder.with_dataver(self.data_ver.get())? { - if attr.is_system() { - CLUSTER.read(ctx.attr().attr_id, writer) - } else { - match attr.attr_id.try_into()? { - Attributes::DeviceTypeList => { - self.encode_devtype_list( - attr.node, - attr.endpoint_id, - &AttrDataWriter::TAG, - &mut writer, - )?; - writer.complete() - } - Attributes::ServerList => { - self.encode_server_list( - attr.node, - attr.endpoint_id, - &AttrDataWriter::TAG, - &mut writer, - )?; - writer.complete() - } - Attributes::PartsList => { - self.encode_parts_list( - attr.node, - attr.endpoint_id, - &AttrDataWriter::TAG, - &mut writer, - )?; - writer.complete() - } - Attributes::ClientList => { - self.encode_client_list( - attr.node, - attr.endpoint_id, - &AttrDataWriter::TAG, - &mut writer, - )?; - writer.complete() - } - } - } - } else { - Ok(()) - } - } - - fn encode_devtype_list( - &self, - node: &Node, - endpoint_id: u16, - tag: &TLVTag, - tw: &mut TLVWriter, - ) -> Result<(), Error> { - tw.start_array(tag)?; - for endpoint in node.endpoints { - if endpoint.id == endpoint_id { - for dev_type in endpoint.device_types { - dev_type.to_tlv(&TagType::Anonymous, &mut *tw)?; - } - } - } - - tw.end_container() - } - - fn encode_server_list( - &self, - node: &Node, - endpoint_id: u16, - tag: &TLVTag, - tw: &mut TLVWriter, - ) -> Result<(), Error> { - tw.start_array(tag)?; - for endpoint in node.endpoints { - if endpoint.id == endpoint_id { - for cluster in endpoint.clusters { - tw.u32(&TLVTag::Anonymous, cluster.id as _)?; - } - } - } - - tw.end_container() - } - - fn encode_parts_list( - &self, - node: &Node, - endpoint_id: u16, - tag: &TLVTag, - tw: &mut TLVWriter, - ) -> Result<(), Error> { - tw.start_array(tag)?; - - for endpoint in node.endpoints { - if self.matcher.describe(endpoint_id, endpoint.id) { - tw.u16(&TLVTag::Anonymous, endpoint.id)?; - } - } - - tw.end_container() - } - - fn encode_client_list( - &self, - _node: &Node, - _endpoint_id: u16, - tag: &TLVTag, - tw: &mut TLVWriter, - ) -> Result<(), Error> { - // No Clients supported - tw.start_array(tag)?; - tw.end_container() - } -} - -impl Handler for DescriptorCluster<'_> { - fn read( - &self, - ctx: &ReadContext<'_>, - encoder: AttrDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - DescriptorCluster::read(self, ctx, encoder) - } -} - -impl NonBlockingHandler for DescriptorCluster<'_> {} diff --git a/rs-matter/src/data_model/system_model/mod.rs b/rs-matter/src/data_model/system_model/mod.rs index 7a395d53..7690cb31 100644 --- a/rs-matter/src/data_model/system_model/mod.rs +++ b/rs-matter/src/data_model/system_model/mod.rs @@ -15,5 +15,5 @@ * limitations under the License. */ -pub mod access_control; -pub mod descriptor; +pub mod acl; +pub mod desc; diff --git a/rs-matter/src/fabric.rs b/rs-matter/src/fabric.rs index 4b223955..4e0b1150 100644 --- a/rs-matter/src/fabric.rs +++ b/rs-matter/src/fabric.rs @@ -28,26 +28,12 @@ use crate::data_model::objects::Privilege; use crate::error::{Error, ErrorCode}; use crate::group_keys::KeySet; use crate::mdns::{Mdns, ServiceMode}; -use crate::tlv::{FromTLV, OctetStr, TLVElement, TLVTag, TLVWrite, TagType, ToTLV, UtfStr}; +use crate::tlv::{FromTLV, TLVElement, TLVTag, TLVWrite, TagType, ToTLV}; use crate::utils::init::{init, Init, InitMaybeUninit, IntoFallibleInit}; use crate::utils::storage::{Vec, WriteBuf}; const COMPRESSED_FABRIC_ID_LEN: usize = 8; -#[derive(Debug, ToTLV)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[tlvargs(lifetime = "'a", start = 1)] -pub struct FabricDescriptor<'a> { - root_public_key: OctetStr<'a>, - vendor_id: u16, - fabric_id: u64, - node_id: u64, - label: UtfStr<'a>, - // TODO: Instead of the direct value, we should consider GlobalElements::FabricIndex - #[tagval(0xFE)] - pub fab_idx: NonZeroU8, -} - /// Fabric type #[derive(Debug, ToTLV, FromTLV)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -231,6 +217,16 @@ impl Fabric { self.fab_idx } + /// Return the fabric's Vendor ID + pub fn vendor_id(&self) -> u16 { + self.vendor_id + } + + /// Return the fabric's label + pub fn label(&self) -> &str { + &self.label + } + /// Return the fabric's Root CA in encoded TLV form /// /// Use `CertRef` to decode on the fly @@ -258,23 +254,6 @@ impl Fabric { &self.ipk } - /// Return the fabric's descriptor - pub fn descriptor<'a>( - &'a self, - root_ca_cert: &'a CertRef<'a>, - ) -> Result, Error> { - let desc = FabricDescriptor { - root_public_key: OctetStr::new(root_ca_cert.pubkey()?), - vendor_id: self.vendor_id, - fabric_id: self.fabric_id, - node_id: self.node_id, - label: self.label.as_str(), - fab_idx: self.fab_idx, - }; - - Ok(desc) - } - /// Return an iterator over the ACL entries of the fabric pub fn acl_iter(&self) -> impl Iterator { self.acl.iter() @@ -297,6 +276,29 @@ impl Fabric { Ok(self.acl.len() - 1) } + /// Add a new ACL entry to the fabric using the supplied initializer. + /// + /// Return the index of the added entry. + fn acl_add_init(&mut self, init: I) -> Result + where + I: Init, + { + // if entry.auth_mode() == AuthMode::Pase { + // // Reserved for future use + // Err(ErrorCode::ConstraintError)?; + // } + + self.acl.push_init(init, || ErrorCode::NoSpace.into())?; + + let idx = self.acl.len() - 1; + let entry = &mut self.acl[idx]; + + // Overwrite the fabric index with our accessing fabric index + entry.fab_idx = Some(self.fab_idx); + + Ok(idx) + } + /// Update an existing ACL entry in the fabric fn acl_update(&mut self, idx: usize, mut entry: AclEntry) -> Result<(), Error> { if self.acl.len() <= idx { @@ -311,6 +313,27 @@ impl Fabric { Ok(()) } + /// Update an existing ACL entry in the fabric using the supplied initializer + fn acl_update_init(&mut self, idx: usize, init: I) -> Result<(), Error> + where + I: Init, + { + if self.acl.len() <= idx { + return Err(ErrorCode::NotFound.into()); + } + + // TODO: Needs #214 + let mut entry = MaybeUninit::uninit(); + let entry = entry.try_init_with(init)?.clone(); + + self.acl[idx] = entry; + + // Overwrite the fabric index with our accessing fabric index + self.acl[idx].fab_idx = Some(self.fab_idx); + + Ok(()) + } + /// Remove an ACL entry from the fabric fn acl_remove(&mut self, idx: usize) -> Result<(), Error> { if self.acl.len() <= idx { @@ -338,7 +361,7 @@ impl Fabric { } } - error!( + debug!( "ACL Disallow for subjects {} fab idx {}", req.accessor().subjects(), req.accessor().fab_idx @@ -654,7 +677,7 @@ impl FabricMgr { // ], // Extension: [] // } - if req.accessor().auth_mode() == AuthMode::Pase { + if req.accessor().auth_mode() == Some(AuthMode::Pase) { return true; } @@ -682,6 +705,22 @@ impl FabricMgr { Ok(index) } + /// Add a new ACL entry to the fabric with the provided local index and initializer + /// + /// Return the index of the added entry. + pub fn acl_add_init(&mut self, fab_idx: NonZeroU8, init: I) -> Result + where + I: Init, + { + let index = self + .get_mut(fab_idx) + .ok_or(ErrorCode::NotFound)? + .acl_add_init(init)?; + self.changed = true; + + Ok(index) + } + /// Update an existing ACL entry in the fabric with the provided local index pub fn acl_update( &mut self, @@ -697,6 +736,24 @@ impl FabricMgr { Ok(()) } + /// Update an existing ACL entry in the fabric with the provided local index and initializer + pub fn acl_update_init( + &mut self, + fab_idx: NonZeroU8, + idx: usize, + init: I, + ) -> Result<(), Error> + where + I: Init, + { + self.get_mut(fab_idx) + .ok_or(ErrorCode::NotFound)? + .acl_update_init(idx, init)?; + self.changed = true; + + Ok(()) + } + /// Remove an ACL entry from the fabric with the provided local index pub fn acl_remove(&mut self, fab_idx: NonZeroU8, idx: usize) -> Result<(), Error> { self.get_mut(fab_idx) diff --git a/rs-matter/src/data_model/sdm/failsafe.rs b/rs-matter/src/failsafe.rs similarity index 99% rename from rs-matter/src/data_model/sdm/failsafe.rs rename to rs-matter/src/failsafe.rs index 8f42d20d..8b9c15ca 100644 --- a/rs-matter/src/data_model/sdm/failsafe.rs +++ b/rs-matter/src/failsafe.rs @@ -327,6 +327,8 @@ impl FailSafe { })? .fab_idx(); + info!("Added operational fabric with local index {}", fab_idx); + let State::Armed(ctx) = &mut self.state else { // Impossible to be in any other state because otherwise // check_state would have failed diff --git a/rs-matter/src/lib.rs b/rs-matter/src/lib.rs index a75c47c8..a404a058 100644 --- a/rs-matter/src/lib.rs +++ b/rs-matter/src/lib.rs @@ -86,6 +86,7 @@ pub mod crypto; pub mod data_model; pub mod error; pub mod fabric; +pub mod failsafe; pub mod group_keys; pub mod interaction_model; pub mod mdns; diff --git a/rs-matter/src/mdns.rs b/rs-matter/src/mdns.rs index dc57833e..8cb1078c 100644 --- a/rs-matter/src/mdns.rs +++ b/rs-matter/src/mdns.rs @@ -195,7 +195,7 @@ pub enum ServiceMode { impl ServiceMode { pub fn service FnOnce(&Service<'a>) -> Result>( &self, - dev_att: &BasicInfoConfig, + dev_det: &BasicInfoConfig, matter_port: u16, name: &str, f: F, @@ -211,18 +211,18 @@ impl ServiceMode { }), ServiceMode::Commissionable(discriminator) => { let discriminator_str = Self::get_discriminator_str(*discriminator); - let vp = Self::get_vp(dev_att.vid, dev_att.pid); + let vp = Self::get_vp(dev_det.vid, dev_det.pid); let mut sai_str = heapless::String::<5>::new(); - write_unwrap!(sai_str, "{}", dev_att.sai.unwrap_or(300)); + write_unwrap!(sai_str, "{}", dev_det.sai.unwrap_or(300)); let mut sii_str = heapless::String::<5>::new(); - write_unwrap!(sii_str, "{}", dev_att.sii.unwrap_or(5000)); + write_unwrap!(sii_str, "{}", dev_det.sii.unwrap_or(5000)); let txt_kvs = &[ ("D", discriminator_str.as_str()), ("CM", "1"), - ("DN", dev_att.device_name), + ("DN", dev_det.device_name), ("VP", &vp), ("SAI", sai_str.as_str()), // Session Active Interval ("SII", sii_str.as_str()), // Session Idle Interval diff --git a/rs-matter/src/persist.rs b/rs-matter/src/persist.rs index 68d87e61..5d9adcd4 100644 --- a/rs-matter/src/persist.rs +++ b/rs-matter/src/persist.rs @@ -25,12 +25,17 @@ pub mod fileio { use std::io::{Read, Write}; use std::path::Path; + use embassy_futures::select::{select, Either}; + use embassy_sync::blocking_mutex::raw::{NoopRawMutex, RawMutex}; + + use crate::data_model::networks::wireless::{Wifi, WirelessNetwork, WirelessNetworks}; use crate::error::{Error, ErrorCode}; use crate::utils::init::{init, Init}; use crate::Matter; const KEY_FABRICS: &str = "fabrics"; const KEY_BASIC_INFO: &str = "basic_info"; + const KEY_WIRELESS_NETWORKS: &str = "wireless_networks"; pub struct Psm { buf: MaybeUninit<[u8; N]>, @@ -96,11 +101,69 @@ pub mod fileio { Ok(()) } + pub fn load_networks( + &mut self, + dir: &Path, + networks: &WirelessNetworks, + ) -> Result<(), Error> + where + M: RawMutex, + T: WirelessNetwork, + { + fs::create_dir_all(dir)?; + + if let Some(data) = Self::load_key(dir, KEY_WIRELESS_NETWORKS, unsafe { + self.buf.assume_init_mut() + })? { + networks.load(data)?; + } + + Ok(()) + } + + pub fn store_networks( + &mut self, + dir: &Path, + networks: &WirelessNetworks, + ) -> Result<(), Error> + where + M: RawMutex, + T: WirelessNetwork, + { + if networks.changed() { + fs::create_dir_all(dir)?; + + if let Some(data) = networks.store(unsafe { self.buf.assume_init_mut() })? { + Self::store_key(dir, KEY_WIRELESS_NETWORKS, data)?; + } + } + + Ok(()) + } + pub async fn run>( &mut self, dir: P, matter: &Matter<'_>, ) -> Result<(), Error> { + self.run_with_networks( + dir, + matter, + Option::<&WirelessNetworks<0, NoopRawMutex, Wifi>>::None, + ) + .await + } + + pub async fn run_with_networks, const W: usize, M, T>( + &mut self, + dir: P, + matter: &Matter<'_>, + networks: Option<&WirelessNetworks>, + ) -> Result<(), Error> + where + M: RawMutex, + T: WirelessNetwork, + { let dir = dir.as_ref(); // NOTE: Calling `load` here does not make sense, because the `Psm::run` future / async method is executed @@ -110,11 +173,24 @@ pub mod fileio { // // User is supposed to instead explicitly call `load` before calling `Psm::run` and `Matter::run` // self.load(dir, matter)?; + // self.load_networks(dir, networks)?; loop { - matter.wait_persist().await; - - self.store(dir, matter)?; + if let Some(networks) = networks { + match select(matter.wait_persist(), networks.wait_persist()).await { + Either::First(_) => { + matter.wait_persist().await; + self.store(dir, matter)?; + } + Either::Second(_) => { + networks.wait_persist().await; + self.store_networks(dir, networks)?; + } + } + } else { + matter.wait_persist().await; + self.store(dir, matter)?; + } } } diff --git a/rs-matter/src/secure_channel/pake.rs b/rs-matter/src/secure_channel/pake.rs index cfb329d0..a00a6694 100644 --- a/rs-matter/src/secure_channel/pake.rs +++ b/rs-matter/src/secure_channel/pake.rs @@ -32,6 +32,13 @@ use crate::utils::rand::Rand; use super::common::SCStatusCodes; use super::spake2p::{Spake2P, VerifierData, MAX_SALT_SIZE_BYTES}; +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PaseSessionType { + Basic, + Enhanced, +} + struct PaseSession { mdns_service_name: heapless::String<16>, verifier: VerifierData, @@ -52,6 +59,14 @@ impl PaseSession { }? Error) } + fn session_type(&self) -> PaseSessionType { + if self.verifier.password.is_some() { + PaseSessionType::Basic + } else { + PaseSessionType::Enhanced + } + } + fn add_mdns(&mut self, discriminator: u16, rand: Rand, mdns: &dyn Mdns) -> Result<(), Error> { let mut buf = [0; 8]; (rand)(&mut buf); @@ -96,8 +111,10 @@ impl PaseMgr { }) } - pub fn is_pase_session_enabled(&self) -> bool { - self.session.is_some() + pub fn session_type(&self) -> Option { + self.session + .as_opt_ref() + .map(|session| session.session_type()) } pub fn enable_basic_pase_session( diff --git a/rs-matter/src/tlv/write.rs b/rs-matter/src/tlv/write.rs index 63e2ba6e..5c58a802 100644 --- a/rs-matter/src/tlv/write.rs +++ b/rs-matter/src/tlv/write.rs @@ -81,6 +81,26 @@ impl TLVWrite for TLVWriter<'_, '_> { fn rewind_to(&mut self, pos: Self::Position) { WriteBuf::rewind_tail_to(self.0, pos) } + + fn available_space(&mut self) -> &mut [u8] { + WriteBuf::empty_as_mut_slice(self.0) + } + + fn str_cb( + &mut self, + tag: &TLVTag, + cb: impl FnOnce(&mut [u8]) -> Result, + ) -> Result<(), Error> { + self.0.str_cb(tag, cb) + } + + fn utf8_cb( + &mut self, + tag: &TLVTag, + cb: impl FnOnce(&mut [u8]) -> Result, + ) -> Result<(), Error> { + self.0.utf8_cb(tag, cb) + } } /// A trait representing a storage where data can be serialized as a TLV stream. @@ -227,6 +247,26 @@ pub trait TLVWrite { self.stri(tag, data.len(), data.iter().copied()) } + /// Write a tag and a TLV Octet String to the TLV stream, where the Octet String is a slice of u8 bytes. + /// + /// The writing is done via a user-supplied callback `cb`, that is expected to fill the provided buffer with the data + /// and to return the length of the written data. + /// + /// This method is useful when the data to be written needs to be computed first, and the computation needs a buffer where + /// to operate. + /// + /// Note that this method always uses a Str16l value type to write the data, which restricts the data length to no more than + /// 65535 bytes. + /// + /// Note also that this method might not be supported by all `TLVWrite` implementations. + fn str_cb( + &mut self, + _tag: &TLVTag, + _cb: impl FnOnce(&mut [u8]) -> Result, + ) -> Result<(), Error> { + unimplemented!("str_cb not implemented for this TLVWrite instance"); + } + /// Write a tag and a TLV Octet String to the TLV stream, where the Octet String is /// anything that can be turned into an iterator of u8 bytes. /// @@ -287,6 +327,26 @@ pub trait TLVWrite { self.write_raw_data(data) } + /// Write a tag and a TLV UTF-8 String to the TLV stream, where the UTF-8 String is a str. + /// + /// The writing is done via a user-supplied callback `cb`, that is expected to fill the provided buffer with the data + /// and to return the length of the written data. + /// + /// This method is useful when the data to be written needs to be computed first, and the computation needs a buffer where + /// to operate. + /// + /// Note that this method always uses a Utf16l value type to write the data, which restricts the data length to no more than + /// 65535 bytes. + /// + /// Note also that this method might not be supported by all `TLVWrite` implementations. + fn utf8_cb( + &mut self, + _tag: &TLVTag, + _cb: impl FnOnce(&mut [u8]) -> Result, + ) -> Result<(), Error> { + unimplemented!("utf8_cb not implemented for this TLVWrite instance"); + } + /// Write a tag and a value indicating the start of a Struct TLV container. /// /// NOTE: The user must call `end_container` after writing all the Struct fields @@ -398,11 +458,31 @@ pub trait TLVWrite { Ok(()) } + /// Append a single byte to the TLV stream. fn write(&mut self, byte: u8) -> Result<(), Error>; - fn get_tail(&self) -> Self::Position; + /// Get the current position in the TLV stream. + /// + /// NOTE: This method might not be supported by all implementations and therefore it might panic. + fn get_tail(&self) -> Self::Position { + unimplemented!("get_tail not implemented for this TLVWrite instance"); + } + + /// Rewind the TLV stream to a previous position. + /// + /// NOTE: This method might not be supported by all implementations and therefore it might panic. + fn rewind_to(&mut self, _pos: Self::Position) { + unimplemented!("rewind_to not implemented for this TLVWrite instance"); + } - fn rewind_to(&mut self, _pos: Self::Position); + /// Get a mutable slice of the available space in the TLV stream. + /// + /// NOTE: This method assumes that the TLV write implementation is writing to a buffer, + /// which might not be the case for all implementations. Therefore, this method might panic + /// if the implementation does not support it. + fn available_space(&mut self) -> &mut [u8] { + unimplemented!("available_space not implemented for this TLVWrite instance"); + } } impl TLVWrite for &mut T @@ -422,6 +502,26 @@ where fn rewind_to(&mut self, pos: Self::Position) { (**self).rewind_to(pos) } + + fn available_space(&mut self) -> &mut [u8] { + (**self).available_space() + } + + fn str_cb( + &mut self, + tag: &TLVTag, + cb: impl FnOnce(&mut [u8]) -> Result, + ) -> Result<(), Error> { + (**self).str_cb(tag, cb) + } + + fn utf8_cb( + &mut self, + tag: &TLVTag, + cb: impl FnOnce(&mut [u8]) -> Result, + ) -> Result<(), Error> { + (**self).utf8_cb(tag, cb) + } } impl TLVWrite for WriteBuf<'_> { @@ -438,6 +538,26 @@ impl TLVWrite for WriteBuf<'_> { fn rewind_to(&mut self, pos: Self::Position) { WriteBuf::rewind_tail_to(self, pos) } + + fn available_space(&mut self) -> &mut [u8] { + WriteBuf::empty_as_mut_slice(self) + } + + fn str_cb( + &mut self, + tag: &TLVTag, + cb: impl FnOnce(&mut [u8]) -> Result, + ) -> Result<(), Error> { + WriteBuf::str_cb(self, tag, cb) + } + + fn utf8_cb( + &mut self, + tag: &TLVTag, + cb: impl FnOnce(&mut [u8]) -> Result, + ) -> Result<(), Error> { + WriteBuf::utf8_cb(self, tag, cb) + } } impl WriteBuf<'_> { @@ -494,25 +614,6 @@ impl WriteBuf<'_> { } } -/// A TLVWrite implementation that counts the number of bytes written. -impl TLVWrite for usize { - type Position = usize; - - fn write(&mut self, _byte: u8) -> Result<(), Error> { - *self += 1; - - Ok(()) - } - - fn get_tail(&self) -> Self::Position { - *self - } - - fn rewind_to(&mut self, pos: Self::Position) { - *self = pos; - } -} - #[cfg(test)] mod tests { use core::f32; diff --git a/rs-matter/src/transport/network/btp.rs b/rs-matter/src/transport/network/btp.rs index 770ad9ad..df074910 100644 --- a/rs-matter/src/transport/network/btp.rs +++ b/rs-matter/src/transport/network/btp.rs @@ -167,6 +167,14 @@ where } } + pub fn conn_ct(&self) -> usize { + self.context.borrow().conn_ct() + } + + pub async fn wait_changed(&self) { + self.context.borrow().wait_changed().await + } + /// Wait until there is at least one Matter (a.k.a. BTP SDU) packet available for consumption. pub async fn wait_available(&self) -> Result<(), Error> { self.context.borrow().wait_available().await diff --git a/rs-matter/src/transport/network/btp/context.rs b/rs-matter/src/transport/network/btp/context.rs index 7ea3bea6..c0e423d9 100644 --- a/rs-matter/src/transport/network/btp/context.rs +++ b/rs-matter/src/transport/network/btp/context.rs @@ -15,7 +15,10 @@ * limitations under the License. */ +use core::sync::atomic::Ordering; + use embassy_sync::blocking_mutex::raw::RawMutex; +use portable_atomic::AtomicUsize; use crate::error::{Error, ErrorCode}; use crate::fmt::Bytes; @@ -190,6 +193,8 @@ where pub(crate) recv_notif: Notification, pub(crate) ack_notif: Notification, pub(crate) send_notif: Notification, + pub(crate) changed_notif: Notification, + pub(crate) conn_ct: AtomicUsize, } impl Default for BtpContext @@ -215,6 +220,8 @@ where recv_notif: Notification::new(), ack_notif: Notification::new(), send_notif: Notification::new(), + changed_notif: Notification::new(), + conn_ct: AtomicUsize::new(0), } } @@ -227,6 +234,8 @@ where recv_notif: Notification::new(), ack_notif: Notification::new(), send_notif: Notification::new(), + changed_notif: Notification::new(), + conn_ct: AtomicUsize::new(0), }) } } @@ -239,6 +248,8 @@ where /// so that the peripheral can report to it subscribe, unsubscribe and write events. pub(crate) fn on_event(&self, event: GattPeripheralEvent) { let result = match event { + GattPeripheralEvent::NotifyConnected(address) => self.on_connect(address), + GattPeripheralEvent::NotifyDisconnected(address) => self.on_disconnect(address), GattPeripheralEvent::NotifySubscribed(address) => self.on_subscribe(address), GattPeripheralEvent::NotifyUnsubscribed(address) => self.on_unsubscribe(address), GattPeripheralEvent::Write { @@ -309,6 +320,26 @@ where }) } + /// Handles a subscribe event to characteristic `C2` from the GATT peripheral. + fn on_connect(&self, address: BtAddr) -> Result<(), Error> { + debug!("Connect request from {}", address); + + self.conn_ct.fetch_add(1, Ordering::SeqCst); + self.changed_notif.notify(); + + Ok(()) + } + + /// Handles an unsubscribe event to characteristic `C2` from the GATT peripheral. + fn on_disconnect(&self, address: BtAddr) -> Result<(), Error> { + debug!("Disconnect request from {}", address); + + self.conn_ct.fetch_sub(1, Ordering::SeqCst); + self.changed_notif.notify(); + + Ok(()) + } + /// Handles a subscribe event to characteristic `C2` from the GATT peripheral. fn on_subscribe(&self, address: BtAddr) -> Result<(), Error> { debug!("Subscribe request from {}", address); @@ -329,6 +360,8 @@ where warn!("No session for address {}", address); } + self.changed_notif.notify(); + Ok(()) }) } @@ -337,7 +370,19 @@ where fn on_unsubscribe(&self, address: BtAddr) -> Result<(), Error> { debug!("Unsubscribe request from {}", address); - self.remove(|session| session.address() == address) + self.remove(|session| session.address() == address)?; + + self.changed_notif.notify(); + + Ok(()) + } + + pub fn conn_ct(&self) -> usize { + self.conn_ct.load(Ordering::SeqCst) + } + + pub async fn wait_changed(&self) { + self.changed_notif.wait().await; } /// Removes all sesssions that match the provided condition. diff --git a/rs-matter/src/transport/network/btp/gatt.rs b/rs-matter/src/transport/network/btp/gatt.rs index 83873ebc..c0855e6b 100644 --- a/rs-matter/src/transport/network/btp/gatt.rs +++ b/rs-matter/src/transport/network/btp/gatt.rs @@ -137,6 +137,8 @@ impl AdvData { #[derive(Debug, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum GattPeripheralEvent<'a> { + NotifyConnected(BtAddr), + NotifyDisconnected(BtAddr), /// A GATT central has subscribed for notifications from characteristic `C2`. /// In other words, the GATT central is now ready to receive BTP packets. /// diff --git a/rs-matter/src/transport/session.rs b/rs-matter/src/transport/session.rs index 172eaa5b..30767092 100644 --- a/rs-matter/src/transport/session.rs +++ b/rs-matter/src/transport/session.rs @@ -779,6 +779,11 @@ impl SessionMgr { None } } + + /// Iterate over the sessions + pub fn iter(&self) -> impl Iterator { + self.sessions.iter() + } } impl fmt::Display for SessionMgr { diff --git a/rs-matter/tests/common/attributes.rs b/rs-matter/tests/common/attributes.rs deleted file mode 100644 index 55a5841b..00000000 --- a/rs-matter/tests/common/attributes.rs +++ /dev/null @@ -1,134 +0,0 @@ -/* - * - * Copyright (c) 2020-2022 Project CHIP Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -use rs_matter::{ - interaction_model::{messages::ib::AttrResp, messages::msg::ReportDataMsg}, - tlv::{TLVElement, TLVList, TLVWriter, TagType, ToTLV}, - utils::storage::WriteBuf, -}; - -/// Assert that the data received in the outbuf matches our expectations -pub fn __assert_attr_report(received: &ReportDataMsg, expected: &[AttrResp], skip_data: bool) { - let mut index = 0; - - // We can't use assert_eq because it will also try to match data-version - for inv_response in received.attr_reports.as_ref().unwrap().iter() { - println!("Validating index {}", index); - match &expected[index] { - AttrResp::Data(e_d) => match inv_response { - AttrResp::Data(d) => { - // We don't match the data-version - assert_eq!(e_d.path, d.path); - if !skip_data { - assert_eq!(e_d.data, d.data); - } - } - _ => { - panic!("Invalid response, expected AttrRespIn::Data"); - } - }, - AttrResp::Status(s) => assert_eq!(AttrResp::Status(s.clone()), inv_response), - } - println!("Index {} success", index); - index += 1; - } - assert_eq!(index, expected.len()); -} - -pub fn assert_attr_report(received: &ReportDataMsg, expected: &[AttrResp]) { - __assert_attr_report(received, expected, false) -} - -pub fn assert_attr_report_skip_data(received: &ReportDataMsg, expected: &[AttrResp]) { - __assert_attr_report(received, expected, true) -} - -// We have to hard-code this here, and it should match the tag -// of the 'data' part in AttrData -pub const ATTR_DATA_TAG_DATA: u8 = 2; - -#[macro_export] -macro_rules! attr_data { - ($endpoint:expr, $cluster:expr, $attr: expr, $data:expr) => { - AttrResp::Data(AttrData { - data_ver: None, - path: AttrPath { - endpoint: Some($endpoint), - cluster: Some($cluster), - attr: Some($attr as u16), - ..Default::default() - }, - data: EncodeValue::Tlv(TLVElement::new(TagType::Context(ATTR_DATA_TAG_DATA), $data)), - }) - }; -} - -#[macro_export] -macro_rules! attr_data_path { - ($path:expr, $data:expr) => { - AttrResp::Data(AttrData { - data_ver: None, - path: AttrPath { - endpoint: $path.endpoint, - cluster: $path.cluster, - attr: $path.leaf.map(|x| x as u16), - ..Default::default() - }, - data: EncodeValue::Tlv(TLVElement::new(TagType::Context(ATTR_DATA_TAG_DATA), $data)), - }) - }; -} - -#[macro_export] -macro_rules! attr_status { - ($path:expr, $status:expr) => { - AttrResp::Status(AttrStatus::new($path, $status, 0)) - }; -} - -pub struct TLVHolder { - buf: [u8; 100], - used_len: usize, -} - -impl TLVHolder { - pub fn new_array<'a, T, I>(ctx_tag: u8, data: I) -> Self - where - T: ToTLV + 'a, - I: IntoIterator, - { - let mut s = Self { - buf: [0; 100], - used_len: 0, - }; - let mut wb = WriteBuf::new(&mut s.buf); - let mut tw = TLVWriter::new(&mut wb); - let _ = tw.start_array(TagType::Context(ctx_tag)); - for e in data { - let _ = e.to_tlv(&mut tw, TagType::Anonymous); - } - let _ = tw.end_container(); - - s.used_len = wb.as_slice().len(); - s - } - - pub fn to_tlv(&self) -> TLVElement { - let s = &self.buf[..self.used_len]; - TLVList::new(s).iter().next().unwrap() - } -} diff --git a/rs-matter/tests/common/e2e/im/echo_cluster.rs b/rs-matter/tests/common/e2e/im/echo_cluster.rs index 490f65f8..4f7e2ee5 100644 --- a/rs-matter/tests/common/e2e/im/echo_cluster.rs +++ b/rs-matter/tests/common/e2e/im/echo_cluster.rs @@ -134,8 +134,8 @@ impl TestChecker { } } -/// A sample cluster that echoes back the input data. Useful for testing. -pub struct EchoCluster { +/// A sample cluster handler that echoes back the input data. Useful for testing. +pub struct EchoHandler { pub data_ver: Dataver, pub multiplier: u8, pub att1: Cell, @@ -144,7 +144,7 @@ pub struct EchoCluster { pub att_custom: Cell, } -impl EchoCluster { +impl EchoHandler { pub const fn new(multiplier: u8, data_ver: Dataver) -> Self { Self { data_ver, @@ -278,17 +278,17 @@ impl EchoCluster { } } -impl Handler for EchoCluster { +impl Handler for EchoHandler { fn read( &self, ctx: &ReadContext<'_>, encoder: AttrDataEncoder<'_, '_, '_>, ) -> Result<(), Error> { - EchoCluster::read(self, ctx, encoder) + EchoHandler::read(self, ctx, encoder) } fn write(&self, ctx: &WriteContext<'_>) -> Result<(), Error> { - EchoCluster::write(self, ctx) + EchoHandler::write(self, ctx) } fn invoke( @@ -296,8 +296,8 @@ impl Handler for EchoCluster { ctx: &InvokeContext<'_>, encoder: CmdDataEncoder<'_, '_, '_>, ) -> Result<(), Error> { - EchoCluster::invoke(self, ctx, encoder) + EchoHandler::invoke(self, ctx, encoder) } } -impl NonBlockingHandler for EchoCluster {} +impl NonBlockingHandler for EchoHandler {} diff --git a/rs-matter/tests/common/e2e/im/handler.rs b/rs-matter/tests/common/e2e/im/handler.rs index 6551605c..c1b228cf 100644 --- a/rs-matter/tests/common/e2e/im/handler.rs +++ b/rs-matter/tests/common/e2e/im/handler.rs @@ -15,31 +15,25 @@ * limitations under the License. */ -use rs_matter::data_model::basic_info::{BasicInfoHandler, ClusterHandler as _}; use rs_matter::data_model::device_types::{DEV_TYPE_ON_OFF_LIGHT, DEV_TYPE_ROOT_NODE}; use rs_matter::data_model::objects::{ - AsyncHandler, AsyncMetadata, AttrDataEncoder, CmdDataEncoder, Dataver, Endpoint, Handler, - InvokeContext, Metadata, Node, NonBlockingHandler, ReadContext, WriteContext, + Async, AsyncHandler, AsyncMetadata, AttrDataEncoder, ChainedHandler, CmdDataEncoder, Dataver, + EmptyHandler, Endpoint, InvokeContext, Node, ReadContext, WriteContext, }; use rs_matter::data_model::on_off::{self, ClusterHandler as _, OnOffHandler}; -use rs_matter::data_model::root_endpoint::{self, EthRootEndpointHandler}; -use rs_matter::data_model::sdm::admin_commissioning; -use rs_matter::data_model::sdm::gen_comm::{ClusterHandler as _, GenCommHandler}; -use rs_matter::data_model::sdm::noc; -use rs_matter::data_model::sdm::nw_commissioning; -use rs_matter::data_model::system_model::access_control; -use rs_matter::data_model::system_model::descriptor::{self, DescriptorCluster}; +use rs_matter::data_model::root_endpoint::{with_eth, with_sys, EthHandler, SysHandler}; +use rs_matter::data_model::system_model::desc::{self, ClusterHandler as _, DescHandler}; use rs_matter::error::Error; -use rs_matter::handler_chain_type; use rs_matter::Matter; +use rs_matter::{clusters, handler_chain_type}; use crate::common::e2e::E2eRunner; -use super::echo_cluster::{self, EchoCluster}; +use super::echo_cluster::{self, EchoHandler}; /// A sample handler for E2E IM tests. pub struct E2eTestHandler<'a>( - handler_chain_type!(on_off::HandlerAdaptor, EchoCluster, DescriptorCluster<'static>, EchoCluster | EthRootEndpointHandler<'a>), + handler_chain_type!(Async>, Async, Async>>, Async | EthHandler<'a, SysHandler<'a, EmptyHandler>>), ); impl<'a> E2eTestHandler<'a> { @@ -48,89 +42,63 @@ impl<'a> E2eTestHandler<'a> { endpoints: &[ Endpoint { id: 0, - clusters: &[ - descriptor::CLUSTER, - BasicInfoHandler::CLUSTER, - GenCommHandler::CLUSTER, - nw_commissioning::ETH_CLUSTER, - admin_commissioning::CLUSTER, - noc::CLUSTER, - access_control::CLUSTER, - echo_cluster::CLUSTER, - ], + clusters: clusters!(eth; echo_cluster::CLUSTER), device_types: &[DEV_TYPE_ROOT_NODE], }, Endpoint { id: 1, - clusters: &[ - descriptor::CLUSTER, + clusters: clusters!( + DescHandler::CLUSTER, OnOffHandler::CLUSTER, echo_cluster::CLUSTER, - ], + ), device_types: &[DEV_TYPE_ON_OFF_LIGHT], }, ], }; pub fn new(matter: &'a Matter<'a>) -> Self { - let handler = root_endpoint::eth_handler(0, matter.rand()) - .chain( - 0, - echo_cluster::ID, - EchoCluster::new(2, Dataver::new_rand(matter.rand())), - ) - .chain( - 1, - descriptor::ID, - DescriptorCluster::new(Dataver::new_rand(matter.rand())), - ) - .chain( - 1, - echo_cluster::ID, - EchoCluster::new(3, Dataver::new_rand(matter.rand())), - ) - .chain( - 1, - OnOffHandler::CLUSTER.id, - OnOffHandler::new(Dataver::new_rand(matter.rand())).adapt(), - ); + let handler = with_eth( + &(), + &(), + matter.rand(), + with_sys(&false, matter.rand(), EmptyHandler), + ); + + let handler = ChainedHandler::new( + 0, + echo_cluster::ID, + Async(EchoHandler::new(2, Dataver::new_rand(matter.rand()))), + handler, + ) + .chain( + 1, + DescHandler::CLUSTER.id, + Async(DescHandler::new(Dataver::new_rand(matter.rand())).adapt()), + ) + .chain( + 1, + echo_cluster::ID, + Async(EchoHandler::new(3, Dataver::new_rand(matter.rand()))), + ) + .chain( + 1, + OnOffHandler::CLUSTER.id, + Async(OnOffHandler::new(Dataver::new_rand(matter.rand())).adapt()), + ); Self(handler) } - pub fn echo_cluster(&self, endpoint: u16) -> &EchoCluster { + pub fn echo_cluster(&self, endpoint: u16) -> &EchoHandler { match endpoint { - 0 => &self.0.next.next.next.handler, - 1 => &self.0.next.handler, + 0 => &self.0.next.next.next.handler.0, + 1 => &self.0.next.handler.0, _ => panic!(), } } } -impl Handler for E2eTestHandler<'_> { - fn read( - &self, - ctx: &ReadContext<'_>, - encoder: AttrDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - self.0.read(ctx, encoder) - } - - fn write(&self, ctx: &WriteContext<'_>) -> Result<(), Error> { - self.0.write(ctx) - } - - fn invoke( - &self, - ctx: &InvokeContext<'_>, - encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - self.0.invoke(ctx, encoder) - } -} - -impl NonBlockingHandler for E2eTestHandler<'_> {} - impl AsyncHandler for E2eTestHandler<'_> { fn read_awaits(&self, _ctx: &ReadContext<'_>) -> bool { false @@ -149,11 +117,11 @@ impl AsyncHandler for E2eTestHandler<'_> { ctx: &ReadContext<'_>, encoder: AttrDataEncoder<'_, '_, '_>, ) -> Result<(), Error> { - self.0.read(ctx, encoder) + self.0.read(ctx, encoder).await } async fn write(&self, ctx: &WriteContext<'_>) -> Result<(), Error> { - self.0.write(ctx) + self.0.write(ctx).await } async fn invoke( @@ -161,18 +129,7 @@ impl AsyncHandler for E2eTestHandler<'_> { ctx: &InvokeContext<'_>, encoder: CmdDataEncoder<'_, '_, '_>, ) -> Result<(), Error> { - self.0.invoke(ctx, encoder) - } -} - -impl Metadata for E2eTestHandler<'_> { - type MetadataGuard<'g> - = Node<'g> - where - Self: 'g; - - fn lock(&self) -> Self::MetadataGuard<'_> { - Self::NODE + self.0.invoke(ctx, encoder).await } } diff --git a/rs-matter/tests/common/e2e/tlv.rs b/rs-matter/tests/common/e2e/tlv.rs index e9a788da..e6bb7f56 100644 --- a/rs-matter/tests/common/e2e/tlv.rs +++ b/rs-matter/tests/common/e2e/tlv.rs @@ -105,7 +105,7 @@ where similar::ChangeTag::Equal => " ", }; - write!(diff_str, "{}{}", sign, change).unwrap(); + write!(diff_str, "{sign}{change}").unwrap(); } panic!("Expected does not match actual:\n== Diff:\n{diff_str}"); diff --git a/rs-matter/tests/common/im_engine.rs b/rs-matter/tests/common/im_engine.rs deleted file mode 100644 index 65532441..00000000 --- a/rs-matter/tests/common/im_engine.rs +++ /dev/null @@ -1,452 +0,0 @@ -/* - * - * Copyright (c) 2020-2022 Project CHIP Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -use core::num::NonZeroU8; - -use crate::common::echo_cluster; - -use embassy_futures::{block_on, join::join, select::select3}; - -use embassy_sync::{ - blocking_mutex::raw::NoopRawMutex, - zerocopy_channel::{Channel, Receiver, Sender}, -}; - -use embassy_time::{Duration, Timer}; - -use rs_matter::{ - acl::{AclEntry, AuthMode}, - data_model::{ - cluster_basic_information::{self, BasicInfoConfig}, - cluster_on_off::{self, OnOffCluster}, - core::{DataModel, IMBuffer}, - device_types::{DEV_TYPE_ON_OFF_LIGHT, DEV_TYPE_ROOT_NODE}, - objects::{ - AttrData, AttrDataEncoder, AttrDetails, Dataver, Endpoint, Handler, HandlerCompat, - Metadata, Node, NonBlockingHandler, Privilege, - }, - root_endpoint::{self, EthRootEndpointHandler}, - sdm::{ - admin_commissioning, - dev_att::{DataType, DevAttDataFetcher}, - general_commissioning, noc, nw_commissioning, - }, - subscriptions::Subscriptions, - system_model::{ - access_control, - descriptor::{self, DescriptorCluster}, - }, - }, - error::{Error, ErrorCode}, - handler_chain_type, - interaction_model::core::{OpCode, PROTO_ID_INTERACTION_MODEL}, - mdns::MdnsService, - respond::Responder, - tlv::{TLVWriter, TagType, ToTLV}, - transport::{ - exchange::{Exchange, MessageMeta, MAX_EXCHANGE_TX_BUF_SIZE}, - network::{ - Address, Ipv4Addr, NetworkReceive, NetworkSend, SocketAddr, SocketAddrV4, - MAX_RX_PACKET_SIZE, MAX_TX_PACKET_SIZE, - }, - session::{NocCatIds, ReservedSession, SessionMode}, - }, - utils::{select::Coalesce, storage::pooled::PooledBuffers}, - Matter, MATTER_PORT, -}; - -use super::echo_cluster::EchoCluster; - -const BASIC_INFO: BasicInfoConfig<'static> = BasicInfoConfig { - vid: 10, - pid: 11, - hw_ver: 12, - sw_ver: 13, - sw_ver_str: "13", - serial_no: "aabbccdd", - device_name: "Test Device", - product_name: "TestProd", - vendor_name: "TestVendor", -}; - -struct DummyDevAtt; - -impl DevAttDataFetcher for DummyDevAtt { - fn get_devatt_data(&self, _data_type: DataType, _data: &mut [u8]) -> Result { - Ok(2) - } -} - -pub const IM_ENGINE_PEER_ID: u64 = 445566; -pub const IM_ENGINE_REMOTE_PEER_ID: u64 = 123456; - -const NODE: Node<'static> = Node { - id: 0, - endpoints: &[ - Endpoint { - id: 0, - clusters: &[ - descriptor::CLUSTER, - cluster_basic_information::CLUSTER, - general_commissioning::CLUSTER, - nw_commissioning::ETH_CLUSTER, - admin_commissioning::CLUSTER, - noc::CLUSTER, - access_control::CLUSTER, - echo_cluster::CLUSTER, - ], - device_types: &[DEV_TYPE_ROOT_NODE], - }, - Endpoint { - id: 1, - clusters: &[ - descriptor::CLUSTER, - cluster_on_off::CLUSTER, - echo_cluster::CLUSTER, - ], - device_types: &[DEV_TYPE_ON_OFF_LIGHT], - }, - ], -}; - -pub struct ImInput<'a> { - action: OpCode, - data: &'a dyn ToTLV, - delay: Option, -} - -impl<'a> ImInput<'a> { - pub fn new(action: OpCode, data: &'a dyn ToTLV) -> Self { - Self::new_delayed(action, data, None) - } - - pub fn new_delayed(action: OpCode, data: &'a dyn ToTLV, delay: Option) -> Self { - Self { - action, - data, - delay, - } - } -} - -pub struct ImOutput { - pub action: OpCode, - pub data: heapless::Vec, -} - -pub struct ImEngineHandler<'a> { - handler: handler_chain_type!(OnOffCluster, EchoCluster, DescriptorCluster<'static>, EchoCluster | EthRootEndpointHandler<'a>), -} - -impl<'a> ImEngineHandler<'a> { - pub fn new(matter: &'a Matter<'a>) -> Self { - let handler = root_endpoint::eth_handler(0, matter.rand()) - .chain( - 0, - echo_cluster::ID, - EchoCluster::new(2, Dataver::new_rand(matter.rand())), - ) - .chain( - 1, - descriptor::ID, - DescriptorCluster::new(Dataver::new_rand(matter.rand())), - ) - .chain( - 1, - echo_cluster::ID, - EchoCluster::new(3, Dataver::new_rand(matter.rand())), - ) - .chain( - 1, - cluster_on_off::ID, - OnOffCluster::new(Dataver::new_rand(matter.rand())), - ); - - Self { handler } - } - - pub fn echo_cluster(&self, endpoint: u16) -> &EchoCluster { - match endpoint { - 0 => &self.handler.next.next.next.handler, - 1 => &self.handler.next.handler, - _ => panic!(), - } - } -} - -impl<'a> Handler for ImEngineHandler<'a> { - fn read( - &self, - exchange: &Exchange, - attr: &AttrDetails, - encoder: AttrDataEncoder, - ) -> Result<(), Error> { - self.handler.read(exchange, attr, encoder) - } - - fn write(&self, exchange: &Exchange, attr: &AttrDetails, data: AttrData) -> Result<(), Error> { - self.handler.write(exchange, attr, data) - } - - fn invoke( - &self, - exchange: &rs_matter::transport::exchange::Exchange, - cmd: &rs_matter::data_model::objects::CmdDetails, - data: &rs_matter::tlv::TLVElement, - encoder: rs_matter::data_model::objects::CmdDataEncoder, - ) -> Result<(), Error> { - self.handler.invoke(exchange, cmd, data, encoder) - } -} - -impl<'a> NonBlockingHandler for ImEngineHandler<'a> {} - -impl<'a> Metadata for ImEngineHandler<'a> { - type MetadataGuard<'g> = Node<'g> where Self: 'g; - - fn lock(&self) -> Self::MetadataGuard<'_> { - NODE - } -} - -/// An Interaction Model Engine to facilitate easy testing -pub struct ImEngine<'a> { - pub matter: Matter<'a>, - cat_ids: NocCatIds, -} - -impl<'a> ImEngine<'a> { - pub fn new_default() -> Self { - Self::new(Default::default()) - } - - /// Create the interaction model engine - pub fn new(cat_ids: NocCatIds) -> Self { - Self { - matter: Self::new_matter(), - cat_ids, - } - } - - pub fn add_default_acl(&self) { - // Only allow the standard peer node id of the IM Engine - let mut default_acl = - AclEntry::new(NonZeroU8::new(1).unwrap(), Privilege::ADMIN, AuthMode::Case); - default_acl.add_subject(IM_ENGINE_PEER_ID).unwrap(); - self.matter.acl_mgr.borrow_mut().add(default_acl).unwrap(); - } - - pub fn handler(&self) -> ImEngineHandler<'_> { - ImEngineHandler::new(&self.matter) - } - - fn new_matter() -> Matter<'static> { - #[cfg(feature = "std")] - use rs_matter::utils::epoch::sys_epoch as epoch; - - #[cfg(not(feature = "std"))] - use rs_matter::utils::epoch::dummy_epoch as epoch; - - #[cfg(feature = "std")] - use rs_matter::utils::rand::sys_rand as rand; - - #[cfg(not(feature = "std"))] - use rs_matter::utils::rand::dummy_rand as rand; - - let matter = Matter::new( - &BASIC_INFO, - &DummyDevAtt, - MdnsService::Disabled, - epoch, - rand, - MATTER_PORT, - ); - - matter.initialize_transport_buffers().unwrap(); - - matter - } - - fn init_matter(matter: &Matter, local_nodeid: u64, remote_nodeid: u64, cat_ids: &NocCatIds) { - matter.transport_mgr.reset().unwrap(); - - let mut session = ReservedSession::reserve_now(matter).unwrap(); - - session - .update( - local_nodeid, - remote_nodeid, - 1, - 1, - ADDR, - SessionMode::Case { - fab_idx: NonZeroU8::new(1).unwrap(), - cat_ids: *cat_ids, - }, - None, - None, - None, - ) - .unwrap(); - - session.complete(); - } - - pub fn process( - &self, - handler: &ImEngineHandler, - input: &[&ImInput], - out: &mut heapless::Vec, - ) -> Result<(), Error> { - out.clear(); - - Self::init_matter( - &self.matter, - IM_ENGINE_REMOTE_PEER_ID, - IM_ENGINE_PEER_ID, - &self.cat_ids, - ); - - let matter_client = Self::new_matter(); - Self::init_matter( - &matter_client, - IM_ENGINE_PEER_ID, - IM_ENGINE_REMOTE_PEER_ID, - &self.cat_ids, - ); - - let mut buf1 = [heapless::Vec::new(); 1]; - let mut buf2 = [heapless::Vec::new(); 1]; - - let mut pipe1 = NetworkPipe::::new(&mut buf1); - let mut pipe2 = NetworkPipe::::new(&mut buf2); - - let (send_remote, recv_local) = pipe1.split(); - let (send_local, recv_remote) = pipe2.split(); - - let matter_client = &matter_client; - - let buffers = PooledBuffers::<10, NoopRawMutex, IMBuffer>::new(0); - - let subscriptions = Subscriptions::<1>::new(); - - let responder = Responder::new( - "Default", - DataModel::new(&buffers, &subscriptions, HandlerCompat(handler)), - &self.matter, - 0, - ); - - block_on( - select3( - matter_client - .transport_mgr - .run(NetworkSendImpl(send_local), NetworkReceiveImpl(recv_local)), - self.matter.transport_mgr.run( - NetworkSendImpl(send_remote), - NetworkReceiveImpl(recv_remote), - ), - join(responder.respond_once("0"), async move { - let mut exchange = Exchange::initiate( - matter_client, - 1, /*just one fabric in tests*/ - IM_ENGINE_REMOTE_PEER_ID, - true, - ) - .await?; - - for ip in input { - exchange - .send_with(|_, wb| { - ip.data - .to_tlv(&mut TLVWriter::new(wb), TagType::Anonymous)?; - - Ok(Some(MessageMeta { - proto_id: PROTO_ID_INTERACTION_MODEL, - proto_opcode: ip.action as _, - reliable: true, - })) - }) - .await?; - - { - // In a separate block so that the RX message is dropped before we start waiting - - let rx = exchange.recv().await?; - - out.push(ImOutput { - action: rx.meta().opcode()?, - data: heapless::Vec::from_slice(rx.payload()) - .map_err(|_| ErrorCode::NoSpace)?, - }) - .map_err(|_| ErrorCode::NoSpace)?; - } - - let delay = ip.delay.unwrap_or(0); - if delay > 0 { - Timer::after(Duration::from_millis(delay as _)).await; - } - } - - exchange.acknowledge().await?; - - Ok(()) - }) - .coalesce(), - ) - .coalesce(), - ) - } -} - -const ADDR: Address = Address::Udp(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0))); - -type NetworkPipe<'a, const N: usize> = Channel<'a, NoopRawMutex, heapless::Vec>; -struct NetworkReceiveImpl<'a, const N: usize>(Receiver<'a, NoopRawMutex, heapless::Vec>); -struct NetworkSendImpl<'a, const N: usize>(Sender<'a, NoopRawMutex, heapless::Vec>); - -impl<'a, const N: usize> NetworkSend for NetworkSendImpl<'a, N> { - async fn send_to(&mut self, data: &[u8], _addr: Address) -> Result<(), Error> { - let vec = self.0.send().await; - - vec.clear(); - vec.extend_from_slice(data).unwrap(); - - self.0.send_done(); - - Ok(()) - } -} - -impl<'a, const N: usize> NetworkReceive for NetworkReceiveImpl<'a, N> { - async fn wait_available(&mut self) -> Result<(), Error> { - self.0.receive().await; - - Ok(()) - } - - async fn recv_from(&mut self, buffer: &mut [u8]) -> Result<(usize, Address), Error> { - let vec = self.0.receive().await; - - buffer[..vec.len()].copy_from_slice(vec); - let len = vec.len(); - - self.0.receive_done(); - - Ok((len, ADDR)) - } -} diff --git a/rs-matter/tests/data_model/acl_and_dataver.rs b/rs-matter/tests/data_model/acl_and_dataver.rs index cb34a7cb..1f23afd3 100644 --- a/rs-matter/tests/data_model/acl_and_dataver.rs +++ b/rs-matter/tests/data_model/acl_and_dataver.rs @@ -18,7 +18,8 @@ use core::num::NonZeroU8; use rs_matter::acl::{gen_noc_cat, AclEntry, AuthMode, Target}; -use rs_matter::data_model::{objects::Privilege, system_model::access_control}; +use rs_matter::data_model::objects::Privilege; +use rs_matter::data_model::system_model::acl::{self, ClusterHandler as _}; use rs_matter::interaction_model::core::IMStatusCode; use rs_matter::interaction_model::messages::ib::{ AttrPath, AttrStatus, ClusterPath, DataVersionFilter, @@ -403,8 +404,8 @@ fn write_with_runtime_acl_add() { let acl_att = GenericPath::new( Some(0), - Some(access_control::ID), - Some(access_control::AttributesDiscriminants::Acl as u32), + Some(acl::AclHandler::CLUSTER.id), + Some(acl::AttributeId::Acl as u32), ); let acl_input = TestAttrData::new(None, AttrPath::new(&acl_att), &allow_acl); @@ -412,7 +413,11 @@ fn write_with_runtime_acl_add() { let mut basic_acl = AclEntry::new(None, Privilege::ADMIN, AuthMode::Case); basic_acl.add_subject(IM_ENGINE_PEER_ID).unwrap(); basic_acl - .add_target(Target::new(Some(0), Some(access_control::ID), None)) + .add_target(Target::new( + Some(0), + Some(acl::AclHandler::CLUSTER.id), + None, + )) .unwrap(); im.matter .fabric_mgr diff --git a/rs-matter/tests/data_model/long_reads.rs b/rs-matter/tests/data_model/long_reads.rs index e6fa0a6f..6653c679 100644 --- a/rs-matter/tests/data_model/long_reads.rs +++ b/rs-matter/tests/data_model/long_reads.rs @@ -16,10 +16,8 @@ */ use rs_matter::data_model::objects::GlobalElements; -use rs_matter::data_model::sdm::{ - admin_commissioning as adm_comm, gen_comm, noc, nw_commissioning, -}; -use rs_matter::data_model::system_model::{access_control as acl, descriptor}; +use rs_matter::data_model::sdm::{adm_comm, gen_comm, gen_diag, grp_key_mgmt, net_comm, noc}; +use rs_matter::data_model::system_model::{acl, desc}; use rs_matter::data_model::{basic_info, on_off}; use rs_matter::interaction_model::core::IMStatusCode; use rs_matter::interaction_model::messages::ib::AttrPath; @@ -36,16 +34,26 @@ use crate::common::e2e::ImEngine; use crate::common::init_env_logger; static ATTR_RESPS: &[TestAttrResp<'static>] = &[ - attr_data!(0, 29, descriptor::Attributes::DeviceTypeList, None), - attr_data!(0, 29, descriptor::Attributes::ServerList, None), - attr_data!(0, 29, descriptor::Attributes::PartsList, None), - attr_data!(0, 29, descriptor::Attributes::ClientList, None), + attr_data!(0, 29, desc::AttributeId::DeviceTypeList, None), + attr_data!(0, 29, desc::AttributeId::ServerList, None), + attr_data!(0, 29, desc::AttributeId::ClientList, None), + attr_data!(0, 29, desc::AttributeId::PartsList, None), attr_data!(0, 29, GlobalElements::GeneratedCmdList, None), attr_data!(0, 29, GlobalElements::AcceptedCmdList, None), attr_data!(0, 29, GlobalElements::EventList, None), attr_data!(0, 29, GlobalElements::AttributeList, None), attr_data!(0, 29, GlobalElements::FeatureMap, None), attr_data!(0, 29, GlobalElements::ClusterRevision, None), + attr_data!(0, 31, acl::AttributeId::Acl, None), + attr_data!(0, 31, acl::AttributeId::SubjectsPerAccessControlEntry, None), + attr_data!(0, 31, acl::AttributeId::TargetsPerAccessControlEntry, None), + attr_data!(0, 31, acl::AttributeId::AccessControlEntriesPerFabric, None), + attr_data!(0, 31, GlobalElements::GeneratedCmdList, None), + attr_data!(0, 31, GlobalElements::AcceptedCmdList, None), + attr_data!(0, 31, GlobalElements::EventList, None), + attr_data!(0, 31, GlobalElements::AttributeList, None), + attr_data!(0, 31, GlobalElements::FeatureMap, None), + attr_data!(0, 31, GlobalElements::ClusterRevision, None), attr_data!(0, 40, basic_info::AttributeId::DataModelRevision, None), attr_data!(0, 40, basic_info::AttributeId::VendorName, None), attr_data!(0, 40, basic_info::AttributeId::VendorID, None), @@ -83,84 +91,69 @@ static ATTR_RESPS: &[TestAttrResp<'static>] = &[ attr_data!(0, 48, GlobalElements::AttributeList, None), attr_data!(0, 48, GlobalElements::FeatureMap, None), attr_data!(0, 48, GlobalElements::ClusterRevision, None), - attr_data!(0, 49, nw_commissioning::Attributes::MaxNetworks, None), - attr_data!(0, 49, nw_commissioning::Attributes::Networks, None), - attr_data!( - 0, - 49, - nw_commissioning::Attributes::ConnectMaxTimeSecs, - None - ), - attr_data!(0, 49, nw_commissioning::Attributes::InterfaceEnabled, None), - attr_data!( - 0, - 49, - nw_commissioning::Attributes::LastNetworkingStatus, - None - ), - attr_data!(0, 49, nw_commissioning::Attributes::LastNetworkID, None), - attr_data!( - 0, - 49, - nw_commissioning::Attributes::LastConnectErrorValue, - None - ), - attr_data!(0, 49, GlobalElements::GeneratedCmdList, None), - attr_data!(0, 49, GlobalElements::AcceptedCmdList, None), - attr_data!(0, 49, GlobalElements::EventList, None), - attr_data!(0, 49, GlobalElements::AttributeList, None), - attr_data!(0, 49, GlobalElements::FeatureMap, None), - attr_data!(0, 49, GlobalElements::ClusterRevision, None), - attr_data!(0, 60, adm_comm::AttributesDiscriminants::WindowStatus, None), - attr_data!( - 0, - 60, - adm_comm::AttributesDiscriminants::AdminFabricIndex, - None - ), - attr_data!( - 0, - 60, - adm_comm::AttributesDiscriminants::AdminVendorId, - None - ), + attr_data!(0, 51, gen_diag::AttributeId::NetworkInterfaces, None), + attr_data!(0, 51, gen_diag::AttributeId::RebootCount, None), + attr_data!(0, 51, gen_diag::AttributeId::TestEventTriggersEnabled, None), + attr_data!(0, 51, GlobalElements::GeneratedCmdList, None), + attr_data!(0, 51, GlobalElements::AcceptedCmdList, None), + attr_data!(0, 51, GlobalElements::EventList, None), + attr_data!(0, 51, GlobalElements::AttributeList, None), + attr_data!(0, 51, GlobalElements::FeatureMap, None), + attr_data!(0, 51, GlobalElements::ClusterRevision, None), + attr_data!(0, 60, adm_comm::AttributeId::WindowStatus, None), + attr_data!(0, 60, adm_comm::AttributeId::AdminFabricIndex, None), + attr_data!(0, 60, adm_comm::AttributeId::AdminVendorId, None), attr_data!(0, 60, GlobalElements::GeneratedCmdList, None), attr_data!(0, 60, GlobalElements::AcceptedCmdList, None), attr_data!(0, 60, GlobalElements::EventList, None), attr_data!(0, 60, GlobalElements::AttributeList, None), attr_data!(0, 60, GlobalElements::FeatureMap, None), attr_data!(0, 60, GlobalElements::ClusterRevision, None), - attr_data!( - 0, - 62, - noc::AttributesDiscriminants::CurrentFabricIndex, - None - ), - attr_data!(0, 62, noc::AttributesDiscriminants::Fabrics, None), - attr_data!(0, 62, noc::AttributesDiscriminants::SupportedFabrics, None), - attr_data!( - 0, - 62, - noc::AttributesDiscriminants::CommissionedFabrics, - None - ), + attr_data!(0, 62, noc::AttributeId::NOCs, None), + attr_data!(0, 62, noc::AttributeId::Fabrics, None), + attr_data!(0, 62, noc::AttributeId::SupportedFabrics, None), + attr_data!(0, 62, noc::AttributeId::CommissionedFabrics, None), + attr_data!(0, 62, noc::AttributeId::TrustedRootCertificates, None), + attr_data!(0, 62, noc::AttributeId::CurrentFabricIndex, None), attr_data!(0, 62, GlobalElements::GeneratedCmdList, None), attr_data!(0, 62, GlobalElements::AcceptedCmdList, None), attr_data!(0, 62, GlobalElements::EventList, None), attr_data!(0, 62, GlobalElements::AttributeList, None), attr_data!(0, 62, GlobalElements::FeatureMap, None), attr_data!(0, 62, GlobalElements::ClusterRevision, None), - attr_data!(0, 31, acl::AttributesDiscriminants::Acl, None), - attr_data!(0, 31, acl::AttributesDiscriminants::Extension, None), - attr_data!(0, 31, acl::AttributesDiscriminants::SubjectsPerEntry, None), - attr_data!(0, 31, acl::AttributesDiscriminants::TargetsPerEntry, None), - attr_data!(0, 31, acl::AttributesDiscriminants::EntriesPerFabric, None), - attr_data!(0, 31, GlobalElements::GeneratedCmdList, None), - attr_data!(0, 31, GlobalElements::AcceptedCmdList, None), - attr_data!(0, 31, GlobalElements::EventList, None), - attr_data!(0, 31, GlobalElements::AttributeList, None), - attr_data!(0, 31, GlobalElements::FeatureMap, None), - attr_data!(0, 31, GlobalElements::ClusterRevision, None), + attr_data!(0, 63, grp_key_mgmt::AttributeId::GroupKeyMap, None), + attr_data!(0, 63, grp_key_mgmt::AttributeId::GroupTable, None), + attr_data!(0, 63, grp_key_mgmt::AttributeId::MaxGroupsPerFabric, None), + attr_data!( + 0, + 63, + grp_key_mgmt::AttributeId::MaxGroupKeysPerFabric, + None + ), + attr_data!(0, 63, GlobalElements::GeneratedCmdList, None), + attr_data!(0, 63, GlobalElements::AcceptedCmdList, None), + attr_data!(0, 63, GlobalElements::EventList, None), + attr_data!(0, 63, GlobalElements::AttributeList, None), + attr_data!(0, 63, GlobalElements::FeatureMap, None), + attr_data!(0, 63, GlobalElements::ClusterRevision, None), + attr_data!(0, 49, net_comm::AttributeId::MaxNetworks, None), + attr_data!(0, 49, net_comm::AttributeId::Networks, None), + attr_data!(0, 49, net_comm::AttributeId::InterfaceEnabled, None), + attr_data!(0, 49, net_comm::AttributeId::LastNetworkingStatus, None), + attr_data!(0, 49, net_comm::AttributeId::LastNetworkID, None), + attr_data!(0, 49, net_comm::AttributeId::LastConnectErrorValue, None), + attr_data!(0, 49, GlobalElements::GeneratedCmdList, None), + attr_data!(0, 49, GlobalElements::AcceptedCmdList, None), + attr_data!(0, 49, GlobalElements::EventList, None), + attr_data!(0, 49, GlobalElements::AttributeList, None), + attr_data!(0, 49, GlobalElements::FeatureMap, None), + attr_data!(0, 49, GlobalElements::ClusterRevision, None), + attr_data!(0, 55, GlobalElements::GeneratedCmdList, None), + attr_data!(0, 55, GlobalElements::AcceptedCmdList, None), + attr_data!(0, 55, GlobalElements::EventList, None), + attr_data!(0, 55, GlobalElements::AttributeList, None), + attr_data!(0, 55, GlobalElements::FeatureMap, None), + attr_data!(0, 55, GlobalElements::ClusterRevision, None), attr_data!(0, echo::ID, echo::AttributesDiscriminants::Att1, None), attr_data!(0, echo::ID, echo::AttributesDiscriminants::Att2, None), attr_data!(0, echo::ID, echo::AttributesDiscriminants::AttCustom, None), @@ -170,10 +163,10 @@ static ATTR_RESPS: &[TestAttrResp<'static>] = &[ attr_data!(0, echo::ID, GlobalElements::AttributeList, None), attr_data!(0, echo::ID, GlobalElements::FeatureMap, None), attr_data!(0, echo::ID, GlobalElements::ClusterRevision, None), - attr_data!(1, 29, descriptor::Attributes::DeviceTypeList, None), - attr_data!(1, 29, descriptor::Attributes::ServerList, None), - attr_data!(1, 29, descriptor::Attributes::PartsList, None), - attr_data!(1, 29, descriptor::Attributes::ClientList, None), + attr_data!(1, 29, desc::AttributeId::DeviceTypeList, None), + attr_data!(1, 29, desc::AttributeId::ServerList, None), + attr_data!(1, 29, desc::AttributeId::ClientList, None), + attr_data!(1, 29, desc::AttributeId::PartsList, None), attr_data!(1, 29, GlobalElements::GeneratedCmdList, None), attr_data!(1, 29, GlobalElements::AcceptedCmdList, None), attr_data!(1, 29, GlobalElements::EventList, None), @@ -201,7 +194,7 @@ static ATTR_RESPS: &[TestAttrResp<'static>] = &[ #[test] fn test_long_read_success() { const PART_1: usize = 38; - const PART_2: usize = 36; + const PART_2: usize = 37; const PART_3: usize = 37; // Read the entire attribute database, which requires 3 reads to complete @@ -264,8 +257,8 @@ fn test_long_read_success() { #[test] fn test_long_read_subscription_success() { const PART_1: usize = 38; - const PART_2: usize = 36; - const PART_3: usize = 36; + const PART_2: usize = 37; + const PART_3: usize = 37; // Subscribe to the entire attribute database, which requires 3 reads to complete init_env_logger(); From 13e84263232affee449edaae897f25e6ad3494df Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Tue, 3 Jun 2025 07:10:30 +0000 Subject: [PATCH 2/2] Address code review feedback --- rs-matter/src/data_model/networks/wireless/thread.rs | 3 +++ rs-matter/src/data_model/system_model/acl.rs | 2 ++ 2 files changed, 5 insertions(+) diff --git a/rs-matter/src/data_model/networks/wireless/thread.rs b/rs-matter/src/data_model/networks/wireless/thread.rs index 8a437b75..b105adbc 100644 --- a/rs-matter/src/data_model/networks/wireless/thread.rs +++ b/rs-matter/src/data_model/networks/wireless/thread.rs @@ -66,6 +66,9 @@ impl Thread { /// Get the Extended PAN ID from the operational dataset pub fn ext_pan_id(&self) -> &[u8] { + // This unwrap! should never fail because the Thread TLV dataset + // is checked - upon creation of the `Thread` instance - to be valid, + // i.e. to contain an Extended PAN ID TLV. unwrap!(Self::dataset_ext_pan_id(&self.dataset.vec)) } } diff --git a/rs-matter/src/data_model/system_model/acl.rs b/rs-matter/src/data_model/system_model/acl.rs index ceb53bff..f0ac81da 100644 --- a/rs-matter/src/data_model/system_model/acl.rs +++ b/rs-matter/src/data_model/system_model/acl.rs @@ -109,6 +109,8 @@ impl AclHandler { // Now add everything fabric_mgr.acl_remove_all(fab_idx)?; for entry in list { + // unwrap! calls below can't fail because we already checked that the entry is well-formed + // and the length of the list is within the limit let entry = unwrap!(entry); unwrap!(fabric_mgr.acl_add_init(fab_idx, AclEntry::init_with(fab_idx, &entry))); }