diff --git a/rs-matter/src/data_model/root_endpoint.rs b/rs-matter/src/data_model/root_endpoint.rs index 2ed71bac..68bd372b 100644 --- a/rs-matter/src/data_model/root_endpoint.rs +++ b/rs-matter/src/data_model/root_endpoint.rs @@ -40,12 +40,26 @@ const WIFI_NW_CLUSTERS: [Cluster<'static>; 10] = [ group_key_management::CLUSTER, ]; -/// The type of operational network (Ethernet, Wifi or (future) Thread) +const THREAD_NW_CLUSTERS: [Cluster<'static>; 10] = [ + descriptor::CLUSTER, + cluster_basic_information::CLUSTER, + general_commissioning::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)] pub enum OperNwType { Ethernet, Wifi, + Thread, } /// A utility function to create a root (Endpoint 0) object using the requested operational network type. @@ -62,6 +76,7 @@ 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, } } diff --git a/rs-matter/src/data_model/sdm/mod.rs b/rs-matter/src/data_model/sdm/mod.rs index 3e958825..f352a065 100644 --- a/rs-matter/src/data_model/sdm/mod.rs +++ b/rs-matter/src/data_model/sdm/mod.rs @@ -24,4 +24,5 @@ pub mod general_diagnostics; pub mod group_key_management; pub mod noc; pub mod nw_commissioning; +pub mod thread_nw_diagnostics; pub mod wifi_nw_diagnostics; diff --git a/rs-matter/src/data_model/sdm/thread_nw_diagnostics.rs b/rs-matter/src/data_model/sdm/thread_nw_diagnostics.rs new file mode 100644 index 00000000..b52e879f --- /dev/null +++ b/rs-matter/src/data_model/sdm/thread_nw_diagnostics.rs @@ -0,0 +1,487 @@ +/* + * + * 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 log::info; + +use strum::{EnumDiscriminants, FromRepr}; + +use crate::data_model::objects::*; +use crate::error::{Error, ErrorCode}; +use crate::tlv::{FromTLV, TLVElement, TLVTag, TLVWrite, ToTLV}; +use crate::transport::exchange::Exchange; +use crate::{attribute_enum, command_enum}; + +pub const ID: u32 = 0x0036; + +#[derive(FromRepr, EnumDiscriminants)] +#[repr(u16)] +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)] +#[repr(u32)] +pub enum Commands { + ResetCounts = 0x0, +} + +command_enum!(Commands); + +pub const CLUSTER: Cluster<'static> = Cluster { + id: ID as _, + feature_map: 0, + attributes: &[ + FEATURE_MAP, + ATTRIBUTE_LIST, + Attribute::new( + AttributesDiscriminants::Channel as u16, + Access::RV, + Quality::NONE, + ), + Attribute::new( + AttributesDiscriminants::RoutingRole as u16, + Access::RV, + Quality::FIXED, + ), + Attribute::new( + AttributesDiscriminants::NetworkName as u16, + Access::RV, + Quality::FIXED, + ), + Attribute::new( + AttributesDiscriminants::PanId as u16, + Access::RV, + Quality::FIXED, + ), + Attribute::new( + AttributesDiscriminants::ExtendedPanId as u16, + Access::RV, + Quality::FIXED, + ), + Attribute::new( + AttributesDiscriminants::MeshLocalPrefix as u16, + Access::RV, + Quality::FIXED, + ), + Attribute::new( + AttributesDiscriminants::NeightborTable as u16, + Access::RV, + Quality::FIXED, + ), + Attribute::new( + AttributesDiscriminants::RouteTable as u16, + Access::RV, + Quality::FIXED, + ), + Attribute::new( + AttributesDiscriminants::PartitionId as u16, + Access::RV, + Quality::FIXED, + ), + Attribute::new( + AttributesDiscriminants::Weighting as u16, + Access::RV, + Quality::FIXED, + ), + Attribute::new( + AttributesDiscriminants::DataVersion as u16, + Access::RV, + Quality::FIXED, + ), + Attribute::new( + AttributesDiscriminants::StableDataVersion as u16, + Access::RV, + Quality::FIXED, + ), + Attribute::new( + AttributesDiscriminants::LeaderRouterId as u16, + Access::RV, + Quality::FIXED, + ), + Attribute::new( + AttributesDiscriminants::SecurityPolicy as u16, + Access::RV, + Quality::FIXED, + ), + Attribute::new( + AttributesDiscriminants::ChannelPage0Mask as u16, + Access::RV, + Quality::FIXED, + ), + Attribute::new( + AttributesDiscriminants::OperationalDatasetComponents as u16, + Access::RV, + Quality::FIXED, + ), + Attribute::new( + AttributesDiscriminants::ActiveNetworkFaultsList as u16, + Access::RV, + Quality::FIXED, + ), + ], + commands: &[CommandsDiscriminants::ResetCounts as _], +}; + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, FromTLV, ToTLV, FromRepr)] +#[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)] +#[repr(u8)] +pub enum NetworkFault { + Unspecified = 0, + Linkdown = 1, + HardwareFailure = 2, + NetworkJammed = 3, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, FromTLV, ToTLV, FromRepr)] +#[repr(u8)] +pub enum ConnectionStatus { + Connected = 0, + NotConnected = 1, +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash, FromTLV, ToTLV)] +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)] +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)] +pub struct SecurityPolicy { + pub rotation_time: u16, + pub flags: u16, +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash, FromTLV, ToTLV)] +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, + _exchange: &Exchange, + 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::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() + } + _ => Err(ErrorCode::AttributeNotFound.into()), + } + } + } else { + Ok(()) + } + } + + /// Write the value of an attribute. + pub fn write( + &self, + _exchange: &Exchange, + _attr: &AttrDetails, + data: AttrData, + ) -> Result<(), Error> { + let _data = data.with_dataver(self.data_ver.get())?; + + self.data_ver.changed(); + + Ok(()) + } + + /// Invoke a command. + pub fn invoke( + &self, + _exchange: &Exchange, + cmd: &CmdDetails, + _data: &TLVElement, + _encoder: CmdDataEncoder, + ) -> Result<(), Error> { + match cmd.cmd_id.try_into()? { + Commands::ResetCounts => { + info!("ResetCounts: Not yet supported"); + } + } + + self.data_ver.changed(); + + Ok(()) + } +} + +impl<'a> Handler for ThreadNwDiagCluster<'a> { + fn read( + &self, + exchange: &Exchange, + attr: &AttrDetails, + encoder: AttrDataEncoder, + ) -> Result<(), Error> { + ThreadNwDiagCluster::read(self, exchange, attr, encoder) + } + + fn write(&self, exchange: &Exchange, attr: &AttrDetails, data: AttrData) -> Result<(), Error> { + ThreadNwDiagCluster::write(self, exchange, attr, data) + } + + fn invoke( + &self, + exchange: &Exchange, + cmd: &CmdDetails, + data: &TLVElement, + encoder: CmdDataEncoder, + ) -> Result<(), Error> { + ThreadNwDiagCluster::invoke(self, exchange, cmd, data, encoder) + } +} + +impl<'a> NonBlockingHandler for ThreadNwDiagCluster<'a> {}