diff --git a/rs-matter/src/cert/asn1_writer.rs b/rs-matter/src/cert/asn1_writer.rs index 87fac3c..7eb1c96 100644 --- a/rs-matter/src/cert/asn1_writer.rs +++ b/rs-matter/src/cert/asn1_writer.rs @@ -264,8 +264,8 @@ impl<'a> CertConsumer for ASN1Writer<'a> { self.write_str(0x06, oid) } - fn utctime(&mut self, _tag: &str, epoch: u32) -> Result<(), Error> { - let matter_epoch = MATTER_EPOCH_SECS + epoch as u64; + fn utctime(&mut self, _tag: &str, epoch: u64) -> Result<(), Error> { + let matter_epoch = MATTER_EPOCH_SECS + epoch; let dt = OffsetDateTime::from_unix_timestamp(matter_epoch as _).unwrap(); diff --git a/rs-matter/src/cert/mod.rs b/rs-matter/src/cert/mod.rs index 8878622..9e03a52 100644 --- a/rs-matter/src/cert/mod.rs +++ b/rs-matter/src/cert/mod.rs @@ -21,7 +21,7 @@ use crate::{ crypto::KeyPair, error::{Error, ErrorCode}, tlv::{self, FromTLV, OctetStr, TLVArray, TLVElement, TLVWriter, TagType, ToTLV}, - utils::writebuf::WriteBuf, + utils::{epoch::MATTER_CERT_DOESNT_EXPIRE, writebuf::WriteBuf}, }; use log::error; use num_derive::FromPrimitive; @@ -650,8 +650,14 @@ impl<'a> Cert<'a> { self.issuer.encode("Issuer:", w)?; w.start_seq("Validity:")?; - w.utctime("Not Before:", self.not_before)?; - w.utctime("Not After:", self.not_after)?; + w.utctime("Not Before:", self.not_before.into())?; + if self.not_after == 0 { + // As per the spec a Not-After value of 0, indicates no well-defined + // expiration date and should return in GeneralizedTime of 99991231235959Z + w.utctime("Not After:", MATTER_CERT_DOESNT_EXPIRE)?; + } else { + w.utctime("Not After:", self.not_after.into())?; + } w.end_seq()?; self.subject.encode("Subject:", w)?; @@ -710,8 +716,9 @@ impl<'a> CertVerifier<'a> { let k = KeyPair::new_from_public(parent.get_pubkey())?; k.verify_msg(asn1, self.cert.get_signature()).map_err(|e| { error!( - "Error in signature verification of certificate: {:x?}", - self.cert.get_subject_key_id() + "Error in signature verification of certificate: {:x?} by {:x?}", + self.cert.get_subject_key_id(), + parent.get_subject_key_id() ); e })?; @@ -744,7 +751,7 @@ pub trait CertConsumer { fn start_ctx(&mut self, tag: &str, id: u8) -> Result<(), Error>; fn end_ctx(&mut self) -> Result<(), Error>; fn oid(&mut self, tag: &str, oid: &[u8]) -> Result<(), Error>; - fn utctime(&mut self, tag: &str, epoch: u32) -> Result<(), Error>; + fn utctime(&mut self, tag: &str, epoch: u64) -> Result<(), Error>; } const MAX_DEPTH: usize = 10; @@ -826,6 +833,16 @@ mod tests { ); } + #[test] + fn test_zero_value_of_not_after_field() { + let noc = Cert::new(&test_vectors::NOC_NOT_AFTER_ZERO).unwrap(); + let rca = Cert::new(&test_vectors::RCA_FOR_NOC_NOT_AFTER_ZERO).unwrap(); + + let v = noc.verify_chain_start(); + let v = v.add_cert(&rca).unwrap(); + v.finalise().unwrap(); + } + #[test] fn test_cert_corrupted() { use crate::error::ErrorCode; @@ -1112,5 +1129,47 @@ mod tests { 0x16, 0x80, 0x14, 0x72, 0xc2, 0x01, 0xf7, 0x57, 0x19, 0x13, 0xb3, 0x48, 0xca, 0x00, 0xca, 0x7b, 0x45, 0xf4, 0x77, 0x46, 0x68, 0xc9, 0x7e, ]; + + /// An NOC that contains a Not-After validity field of '0' + pub const NOC_NOT_AFTER_ZERO: [u8; 251] = [ + 0x15, 0x30, 0x1, 0x1, 0x1, 0x24, 0x2, 0x1, 0x37, 0x3, 0x27, 0x14, 0xfc, 0x8d, 0xcf, + 0x45, 0x19, 0xff, 0x9a, 0x9a, 0x24, 0x15, 0x1, 0x18, 0x26, 0x4, 0x21, 0x39, 0x5a, 0x2c, + 0x24, 0x5, 0x0, 0x37, 0x6, 0x24, 0x15, 0x1, 0x26, 0x11, 0x6c, 0x4a, 0x95, 0xd2, 0x18, + 0x24, 0x7, 0x1, 0x24, 0x8, 0x1, 0x30, 0x9, 0x41, 0x4, 0x41, 0x7f, 0xb1, 0x61, 0xb0, + 0xbe, 0x19, 0x41, 0x81, 0xb9, 0x9f, 0xe8, 0x7b, 0xdd, 0xdf, 0xc4, 0x46, 0xe0, 0x74, + 0xba, 0x83, 0x21, 0xda, 0x3d, 0xf7, 0x88, 0x68, 0x14, 0xa6, 0x9d, 0xa9, 0x14, 0x88, + 0x94, 0x1e, 0xd3, 0x86, 0x62, 0xc7, 0x6f, 0xb4, 0x79, 0xd2, 0xaf, 0x34, 0xe7, 0xd6, + 0x4d, 0x87, 0x29, 0x67, 0x10, 0x73, 0xb9, 0x81, 0xe0, 0x9, 0xe1, 0x13, 0xbb, 0x6a, + 0xd2, 0x21, 0xaa, 0x37, 0xa, 0x35, 0x1, 0x28, 0x1, 0x18, 0x24, 0x2, 0x1, 0x36, 0x3, + 0x4, 0x2, 0x4, 0x1, 0x18, 0x30, 0x4, 0x14, 0x98, 0xaf, 0xa1, 0x3d, 0x41, 0x67, 0x7a, + 0x34, 0x8c, 0x67, 0x6c, 0xcc, 0x17, 0x6e, 0xd5, 0x58, 0xd8, 0x2b, 0x86, 0x8, 0x30, 0x5, + 0x14, 0xf8, 0xcf, 0xd0, 0x45, 0x6b, 0xe, 0xd1, 0x6f, 0xc5, 0x67, 0xdf, 0x81, 0xd7, + 0xe9, 0xb7, 0xeb, 0x39, 0x78, 0xec, 0x40, 0x18, 0x30, 0xb, 0x40, 0xf9, 0x80, 0x94, + 0xbf, 0xcf, 0x72, 0xa5, 0x54, 0x87, 0x12, 0x35, 0xc, 0x38, 0x79, 0xa8, 0xb, 0x21, 0x94, + 0xb5, 0x71, 0x2, 0xcb, 0xb, 0xda, 0xf9, 0x6c, 0x54, 0xcb, 0x50, 0x4b, 0x2, 0x5, 0xea, + 0xff, 0xfd, 0xb2, 0x1b, 0x24, 0x30, 0x79, 0xb1, 0x69, 0x87, 0xa5, 0x7, 0xc6, 0x76, + 0x15, 0x70, 0xc0, 0xec, 0x14, 0xd3, 0x9f, 0x1a, 0xa7, 0xe1, 0xca, 0x25, 0x2e, 0x44, + 0xfc, 0x96, 0x4d, 0x18, + ]; + pub const RCA_FOR_NOC_NOT_AFTER_ZERO: [u8; 251] = [ + 0x15, 0x30, 0x1, 0x1, 0x0, 0x24, 0x2, 0x1, 0x37, 0x3, 0x27, 0x14, 0xfc, 0x8d, 0xcf, + 0x45, 0x19, 0xff, 0x9a, 0x9a, 0x24, 0x15, 0x1, 0x18, 0x26, 0x4, 0xb1, 0x2a, 0x38, 0x2c, + 0x26, 0x5, 0x31, 0x5e, 0x19, 0x2e, 0x37, 0x6, 0x27, 0x14, 0xfc, 0x8d, 0xcf, 0x45, 0x19, + 0xff, 0x9a, 0x9a, 0x24, 0x15, 0x1, 0x18, 0x24, 0x7, 0x1, 0x24, 0x8, 0x1, 0x30, 0x9, + 0x41, 0x4, 0x15, 0x69, 0x1e, 0x7b, 0x6a, 0xea, 0x5, 0xdb, 0xf8, 0x4b, 0xfd, 0xdc, 0x6c, + 0x75, 0x46, 0x74, 0xb0, 0x60, 0xdb, 0x4, 0x71, 0xb6, 0xd0, 0x52, 0xf2, 0xf8, 0xe6, + 0xbb, 0xd, 0xe5, 0x60, 0x1f, 0x84, 0x66, 0x4f, 0x3c, 0x90, 0x89, 0xa6, 0xc6, 0x99, + 0x61, 0xfb, 0x89, 0xf7, 0xa, 0xa6, 0xe4, 0xa2, 0x21, 0xd3, 0x37, 0x30, 0x1b, 0xd2, + 0x11, 0xc5, 0xcc, 0x0, 0xf4, 0x7a, 0x14, 0xfc, 0x3c, 0x37, 0xa, 0x35, 0x1, 0x29, 0x1, + 0x18, 0x24, 0x2, 0x60, 0x30, 0x4, 0x14, 0xf8, 0xcf, 0xd0, 0x45, 0x6b, 0xe, 0xd1, 0x6f, + 0xc5, 0x67, 0xdf, 0x81, 0xd7, 0xe9, 0xb7, 0xeb, 0x39, 0x78, 0xec, 0x40, 0x30, 0x5, + 0x14, 0xf8, 0xcf, 0xd0, 0x45, 0x6b, 0xe, 0xd1, 0x6f, 0xc5, 0x67, 0xdf, 0x81, 0xd7, + 0xe9, 0xb7, 0xeb, 0x39, 0x78, 0xec, 0x40, 0x18, 0x30, 0xb, 0x40, 0x4c, 0xae, 0xac, + 0xc1, 0x26, 0xdd, 0x56, 0xc, 0x85, 0x86, 0xbc, 0xeb, 0xa2, 0xb5, 0xb7, 0xdf, 0x49, + 0x92, 0x62, 0xcd, 0x2a, 0xb6, 0x4e, 0xc5, 0x31, 0x7c, 0xd9, 0xb, 0x1c, 0xe9, 0x6e, + 0xe5, 0x82, 0xc7, 0xb8, 0xda, 0x22, 0x31, 0x7b, 0x23, 0x5a, 0x2a, 0xe6, 0x76, 0x28, + 0xb6, 0xd4, 0xc7, 0x7b, 0x1c, 0x9c, 0x85, 0x71, 0x5f, 0xe6, 0xf6, 0x21, 0x50, 0x5c, + 0xa7, 0x7c, 0xc7, 0x1d, 0x9a, 0x18, + ]; } } diff --git a/rs-matter/src/cert/printer.rs b/rs-matter/src/cert/printer.rs index a4c4efe..b037576 100644 --- a/rs-matter/src/cert/printer.rs +++ b/rs-matter/src/cert/printer.rs @@ -122,8 +122,8 @@ impl<'a, 'b> CertConsumer for CertPrinter<'a, 'b> { } Ok(()) } - fn utctime(&mut self, tag: &str, epoch: u32) -> Result<(), Error> { - let matter_epoch = MATTER_EPOCH_SECS + epoch as u64; + fn utctime(&mut self, tag: &str, epoch: u64) -> Result<(), Error> { + let matter_epoch = MATTER_EPOCH_SECS + epoch; let dt = OffsetDateTime::from_unix_timestamp(matter_epoch as _).unwrap(); diff --git a/rs-matter/src/data_model/root_endpoint.rs b/rs-matter/src/data_model/root_endpoint.rs index 69df3bd..b39e12f 100644 --- a/rs-matter/src/data_model/root_endpoint.rs +++ b/rs-matter/src/data_model/root_endpoint.rs @@ -15,8 +15,12 @@ use super::{ sdm::{ admin_commissioning::{self, AdminCommCluster}, dev_att::DevAttDataFetcher, + ethernet_nw_diagnostics::{self, EthNwDiagCluster}, failsafe::FailSafe, general_commissioning::{self, GenCommCluster}, + general_diagnostics::{self, GenDiagCluster}, + group_key_management, + group_key_management::GrpKeyMgmtCluster, noc::{self, NocCluster}, nw_commissioning::{self, NwCommCluster}, }, @@ -33,10 +37,13 @@ pub type RootEndpointHandler<'a> = handler_chain_type!( NwCommCluster, AdminCommCluster<'a>, NocCluster<'a>, - AccessControlCluster<'a> + AccessControlCluster<'a>, + GenDiagCluster, + EthNwDiagCluster, + GrpKeyMgmtCluster ); -pub const CLUSTERS: [Cluster<'static>; 7] = [ +pub const CLUSTERS: [Cluster<'static>; 10] = [ descriptor::CLUSTER, cluster_basic_information::CLUSTER, general_commissioning::CLUSTER, @@ -44,6 +51,9 @@ pub const CLUSTERS: [Cluster<'static>; 7] = [ admin_commissioning::CLUSTER, noc::CLUSTER, access_control::CLUSTER, + general_diagnostics::CLUSTER, + ethernet_nw_diagnostics::CLUSTER, + group_key_management::CLUSTER, ]; pub const fn endpoint(id: EndptId) -> Endpoint<'static> { @@ -95,6 +105,21 @@ pub fn wrap<'a>( rand: Rand, ) -> RootEndpointHandler<'a> { EmptyHandler + .chain( + endpoint_id, + group_key_management::ID, + GrpKeyMgmtCluster::new(rand), + ) + .chain( + endpoint_id, + ethernet_nw_diagnostics::ID, + EthNwDiagCluster::new(rand), + ) + .chain( + endpoint_id, + general_diagnostics::ID, + GenDiagCluster::new(rand), + ) .chain( endpoint_id, access_control::ID, diff --git a/rs-matter/src/data_model/sdm/ethernet_nw_diagnostics.rs b/rs-matter/src/data_model/sdm/ethernet_nw_diagnostics.rs new file mode 100644 index 0000000..83ea376 --- /dev/null +++ b/rs-matter/src/data_model/sdm/ethernet_nw_diagnostics.rs @@ -0,0 +1,146 @@ +/* + * + * 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::convert::TryInto; + +use crate::{ + attribute_enum, cmd_enter, command_enum, data_model::objects::AttrType, data_model::objects::*, + error::Error, tlv::TLVElement, transport::exchange::Exchange, utils::rand::Rand, +}; +use log::info; +use strum::{EnumDiscriminants, FromRepr}; + +pub const ID: u32 = 0x0037; + +#[derive(FromRepr, EnumDiscriminants)] +#[repr(u16)] +pub enum Attributes { + PacketRxCount(AttrType) = 0x02, + PacketTxCount(AttrType) = 0x03, +} + +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::PacketRxCount as u16, + Access::RV, + Quality::NONE, + ), + Attribute::new( + AttributesDiscriminants::PacketTxCount as u16, + Access::RV, + Quality::FIXED, + ), + ], + commands: &[CommandsDiscriminants::ResetCounts as _], +}; + +pub struct EthNwDiagCluster { + data_ver: Dataver, +} + +impl EthNwDiagCluster { + pub fn new(rand: Rand) -> Self { + Self { + data_ver: Dataver::new(rand), + } + } + + pub fn read(&self, attr: &AttrDetails, encoder: AttrDataEncoder) -> Result<(), Error> { + 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::PacketRxCount(codec) => codec.encode(writer, 1), + Attributes::PacketTxCount(codec) => codec.encode(writer, 1), + } + } + } else { + Ok(()) + } + } + + pub fn write(&self, _attr: &AttrDetails, data: AttrData) -> Result<(), Error> { + let _data = data.with_dataver(self.data_ver.get())?; + + self.data_ver.changed(); + + Ok(()) + } + + pub fn invoke( + &self, + _exchange: &Exchange, + cmd: &CmdDetails, + _data: &TLVElement, + _encoder: CmdDataEncoder, + ) -> Result<(), Error> { + match cmd.cmd_id.try_into()? { + Commands::ResetCounts => { + cmd_enter!("ResetCounts: Not yet supported"); + } + } + + self.data_ver.changed(); + + Ok(()) + } +} + +impl Handler for EthNwDiagCluster { + fn read(&self, attr: &AttrDetails, encoder: AttrDataEncoder) -> Result<(), Error> { + EthNwDiagCluster::read(self, attr, encoder) + } + + fn write(&self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> { + EthNwDiagCluster::write(self, attr, data) + } + + fn invoke( + &self, + exchange: &Exchange, + cmd: &CmdDetails, + data: &TLVElement, + encoder: CmdDataEncoder, + ) -> Result<(), Error> { + EthNwDiagCluster::invoke(self, exchange, cmd, data, encoder) + } +} + +// TODO: Might be removed once the `on` member is externalized +impl NonBlockingHandler for EthNwDiagCluster {} + +impl ChangeNotifier<()> for EthNwDiagCluster { + fn consume_change(&mut self) -> Option<()> { + self.data_ver.consume_change(()) + } +} diff --git a/rs-matter/src/data_model/sdm/general_commissioning.rs b/rs-matter/src/data_model/sdm/general_commissioning.rs index 0784bae..5e28eec 100644 --- a/rs-matter/src/data_model/sdm/general_commissioning.rs +++ b/rs-matter/src/data_model/sdm/general_commissioning.rs @@ -121,9 +121,15 @@ struct FailSafeParams { bread_crumb: u8, } +#[derive(ToTLV)] +struct BasicCommissioningInfo { + expiry_len: u16, + max_cmltv_failsafe_secs: u16, +} + pub struct GenCommCluster<'a> { data_ver: Dataver, - expiry_len: u16, + basic_comm_info: BasicCommissioningInfo, failsafe: &'a RefCell, } @@ -133,7 +139,10 @@ impl<'a> GenCommCluster<'a> { data_ver: Dataver::new(rand), failsafe, // TODO: Arch-Specific - expiry_len: 120, + basic_comm_info: BasicCommissioningInfo { + expiry_len: 120, + max_cmltv_failsafe_secs: 120, + }, } } @@ -157,10 +166,8 @@ impl<'a> GenCommCluster<'a> { codec.encode(writer, RegLocationType::IndoorOutdoor as _) } Attributes::BasicCommissioningInfo(_) => { - writer.start_struct(AttrDataWriter::TAG)?; - writer.u16(TagType::Context(0), self.expiry_len)?; - writer.end_container()?; - + self.basic_comm_info + .to_tlv(&mut writer, AttrDataWriter::TAG)?; writer.complete() } } diff --git a/rs-matter/src/data_model/sdm/general_diagnostics.rs b/rs-matter/src/data_model/sdm/general_diagnostics.rs new file mode 100644 index 0000000..091c165 --- /dev/null +++ b/rs-matter/src/data_model/sdm/general_diagnostics.rs @@ -0,0 +1,157 @@ +/* + * + * 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::convert::TryInto; + +use crate::{ + attribute_enum, cmd_enter, command_enum, + data_model::objects::AttrType, + data_model::objects::*, + error::{Error, ErrorCode}, + tlv::TLVElement, + transport::exchange::Exchange, + utils::rand::Rand, +}; +use log::info; +use strum::{EnumDiscriminants, FromRepr}; + +pub const ID: u32 = 0x0033; + +#[derive(FromRepr, EnumDiscriminants)] +#[repr(u16)] +pub enum Attributes { + NetworkInterfaces(()) = 0x00, + RebootCount(AttrType) = 0x01, + TestEventTriggersEnabled(AttrType) = 0x08, +} + +attribute_enum!(Attributes); + +#[derive(FromRepr, EnumDiscriminants)] +#[repr(u32)] +pub enum Commands { + TestEventTrigger = 0x0, +} + +command_enum!(Commands); + +pub const CLUSTER: Cluster<'static> = Cluster { + id: ID as _, + feature_map: 0, + attributes: &[ + FEATURE_MAP, + ATTRIBUTE_LIST, + Attribute::new( + AttributesDiscriminants::NetworkInterfaces as u16, + Access::RV, + Quality::NONE, + ), + Attribute::new( + AttributesDiscriminants::RebootCount as u16, + Access::RV, + Quality::PERSISTENT, + ), + Attribute::new( + AttributesDiscriminants::TestEventTriggersEnabled as u16, + Access::RV, + Quality::NONE, + ), + ], + commands: &[CommandsDiscriminants::TestEventTrigger as _], +}; + +pub struct GenDiagCluster { + data_ver: Dataver, +} + +impl GenDiagCluster { + pub fn new(rand: Rand) -> Self { + Self { + data_ver: Dataver::new(rand), + } + } + + pub fn read(&self, attr: &AttrDetails, encoder: AttrDataEncoder) -> Result<(), Error> { + 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), + _ => Err(ErrorCode::AttributeNotFound.into()), + } + } + } else { + Ok(()) + } + } + + pub fn write(&self, _attr: &AttrDetails, data: AttrData) -> Result<(), Error> { + let _data = data.with_dataver(self.data_ver.get())?; + + self.data_ver.changed(); + + Ok(()) + } + + pub fn invoke( + &self, + _exchange: &Exchange, + cmd: &CmdDetails, + _data: &TLVElement, + _encoder: CmdDataEncoder, + ) -> Result<(), Error> { + 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, attr: &AttrDetails, encoder: AttrDataEncoder) -> Result<(), Error> { + GenDiagCluster::read(self, attr, encoder) + } + + fn write(&self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> { + GenDiagCluster::write(self, attr, data) + } + + fn invoke( + &self, + exchange: &Exchange, + cmd: &CmdDetails, + data: &TLVElement, + encoder: CmdDataEncoder, + ) -> Result<(), Error> { + GenDiagCluster::invoke(self, exchange, cmd, data, encoder) + } +} + +// TODO: Might be removed once the `on` member is externalized +impl NonBlockingHandler for GenDiagCluster {} + +impl ChangeNotifier<()> for GenDiagCluster { + fn consume_change(&mut self) -> Option<()> { + self.data_ver.consume_change(()) + } +} diff --git a/rs-matter/src/data_model/sdm/group_key_management.rs b/rs-matter/src/data_model/sdm/group_key_management.rs new file mode 100644 index 0000000..64a9fd0 --- /dev/null +++ b/rs-matter/src/data_model/sdm/group_key_management.rs @@ -0,0 +1,164 @@ +/* + * + * 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::convert::TryInto; + +use crate::{ + attribute_enum, cmd_enter, command_enum, + data_model::objects::AttrType, + data_model::objects::*, + error::{Error, ErrorCode}, + tlv::TLVElement, + transport::exchange::Exchange, + utils::rand::Rand, +}; +use log::info; +use strum::{EnumDiscriminants, FromRepr}; + +pub const ID: u32 = 0x003F; + +#[derive(FromRepr, EnumDiscriminants)] +#[repr(u16)] +pub enum Attributes { + GroupKeyMap(()) = 0x00, + GroupTable(()) = 0x01, + MaxGroupsPerFabric(AttrType) = 0x02, + MaxGroupKeysPerFabric(AttrType) = 0x03, +} + +attribute_enum!(Attributes); + +#[derive(FromRepr, EnumDiscriminants)] +#[repr(u32)] +pub enum Commands { + KeySetWrite = 0x0, +} + +command_enum!(Commands); + +pub const CLUSTER: Cluster<'static> = Cluster { + id: ID as _, + feature_map: 0, + attributes: &[ + FEATURE_MAP, + ATTRIBUTE_LIST, + Attribute::new( + AttributesDiscriminants::GroupKeyMap as u16, + Access::RWFVM, + Quality::PERSISTENT, + ), + Attribute::new( + AttributesDiscriminants::GroupTable as u16, + Access::RF, + Quality::NONE, + ), + Attribute::new( + AttributesDiscriminants::MaxGroupsPerFabric as u16, + Access::READ, + Quality::FIXED, + ), + Attribute::new( + AttributesDiscriminants::MaxGroupKeysPerFabric as u16, + Access::READ, + Quality::FIXED, + ), + ], + commands: &[CommandsDiscriminants::KeySetWrite as _], +}; + +pub struct GrpKeyMgmtCluster { + data_ver: Dataver, +} + +impl GrpKeyMgmtCluster { + pub fn new(rand: Rand) -> Self { + Self { + data_ver: Dataver::new(rand), + } + } + + pub fn read(&self, attr: &AttrDetails, encoder: AttrDataEncoder) -> Result<(), Error> { + 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), + _ => Err(ErrorCode::AttributeNotFound.into()), + } + } + } else { + Ok(()) + } + } + + pub fn write(&self, _attr: &AttrDetails, data: AttrData) -> Result<(), Error> { + let _data = data.with_dataver(self.data_ver.get())?; + + self.data_ver.changed(); + + Ok(()) + } + + pub fn invoke( + &self, + _exchange: &Exchange, + cmd: &CmdDetails, + _data: &TLVElement, + _encoder: CmdDataEncoder, + ) -> Result<(), Error> { + 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, attr: &AttrDetails, encoder: AttrDataEncoder) -> Result<(), Error> { + GrpKeyMgmtCluster::read(self, attr, encoder) + } + + fn write(&self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> { + GrpKeyMgmtCluster::write(self, attr, data) + } + + fn invoke( + &self, + exchange: &Exchange, + cmd: &CmdDetails, + data: &TLVElement, + encoder: CmdDataEncoder, + ) -> Result<(), Error> { + GrpKeyMgmtCluster::invoke(self, exchange, cmd, data, encoder) + } +} + +// TODO: Might be removed once the `on` member is externalized +impl NonBlockingHandler for GrpKeyMgmtCluster {} + +impl ChangeNotifier<()> for GrpKeyMgmtCluster { + fn consume_change(&mut self) -> Option<()> { + self.data_ver.consume_change(()) + } +} diff --git a/rs-matter/src/data_model/sdm/mod.rs b/rs-matter/src/data_model/sdm/mod.rs index 1ce25ad..8a87277 100644 --- a/rs-matter/src/data_model/sdm/mod.rs +++ b/rs-matter/src/data_model/sdm/mod.rs @@ -17,7 +17,10 @@ pub mod admin_commissioning; pub mod dev_att; +pub mod ethernet_nw_diagnostics; pub mod failsafe; pub mod general_commissioning; +pub mod general_diagnostics; +pub mod group_key_management; pub mod noc; pub mod nw_commissioning; diff --git a/rs-matter/src/data_model/sdm/noc.rs b/rs-matter/src/data_model/sdm/noc.rs index 8b66cb4..7c6324a 100644 --- a/rs-matter/src/data_model/sdm/noc.rs +++ b/rs-matter/src/data_model/sdm/noc.rs @@ -186,7 +186,7 @@ struct NocResp<'a> { #[tlvargs(lifetime = "'a")] struct AddNocReq<'a> { noc_value: OctetStr<'a>, - icac_value: OctetStr<'a>, + icac_value: Option>, ipk_value: OctetStr<'a>, case_admin_subject: u64, vendor_id: u16, @@ -358,13 +358,17 @@ impl<'a> NocCluster<'a> { let noc = heapless::Vec::from_slice(r.noc_value.0).map_err(|_| NocStatus::InvalidNOC)?; - let icac = if !r.icac_value.0.is_empty() { - let icac_cert = Cert::new(r.icac_value.0).map_err(|_| NocStatus::InvalidNOC)?; - info!("Received ICAC as: {}", icac_cert); + let icac = if let Some(icac_value) = r.icac_value { + if !icac_value.0.is_empty() { + let icac_cert = Cert::new(icac_value.0).map_err(|_| NocStatus::InvalidNOC)?; + info!("Received ICAC as: {}", icac_cert); - let icac = - heapless::Vec::from_slice(r.icac_value.0).map_err(|_| NocStatus::InvalidNOC)?; - Some(icac) + let icac = + heapless::Vec::from_slice(icac_value.0).map_err(|_| NocStatus::InvalidNOC)?; + Some(icac) + } else { + None + } } else { None }; @@ -601,6 +605,20 @@ impl<'a> NocCluster<'a> { Ok(()) } + fn add_rca_to_session_noc_data(exchange: &Exchange, data: &TLVElement) -> Result<(), Error> { + exchange.with_session_mut(|sess| { + let noc_data = sess.get_noc_data().ok_or(ErrorCode::NoSession)?; + + let req = CommonReq::from_tlv(data).map_err(Error::map_invalid_command)?; + info!("Received Trusted Cert:{:x?}", req.str); + + noc_data.root_ca = + heapless::Vec::from_slice(req.str.0).map_err(|_| ErrorCode::BufferTooSmall)?; + + Ok(()) + }) + } + fn handle_command_addtrustedrootcert( &self, exchange: &Exchange, @@ -613,21 +631,12 @@ impl<'a> NocCluster<'a> { // This may happen on CASE or PASE. For PASE, the existence of NOC Data is necessary match exchange.with_session(|sess| Ok(sess.get_session_mode().clone()))? { - SessionMode::Case(_) => error!("CASE: AddTrustedRootCert handling pending"), // For a CASE Session, we just return success for now, + SessionMode::Case(_) => { + // TODO - Updating the Trusted RCA of an existing Fabric + Self::add_rca_to_session_noc_data(exchange, data)?; + } SessionMode::Pase => { - exchange.with_session_mut(|sess| { - let noc_data = sess.get_noc_data().ok_or(ErrorCode::NoSession)?; - - let req = CommonReq::from_tlv(data).map_err(Error::map_invalid_command)?; - info!("Received Trusted Cert:{:x?}", req.str); - - noc_data.root_ca = heapless::Vec::from_slice(req.str.0) - .map_err(|_| ErrorCode::BufferTooSmall)?; - - Ok(()) - })?; - - // TODO + Self::add_rca_to_session_noc_data(exchange, data)?; } _ => (), } diff --git a/rs-matter/src/fabric.rs b/rs-matter/src/fabric.rs index 8959407..203215f 100644 --- a/rs-matter/src/fabric.rs +++ b/rs-matter/src/fabric.rs @@ -33,7 +33,6 @@ use crate::{ const COMPRESSED_FABRIC_ID_LEN: usize = 8; -#[allow(dead_code)] #[derive(Debug, ToTLV)] #[tlvargs(lifetime = "'a", start = 1)] pub struct FabricDescriptor<'a> { diff --git a/rs-matter/src/utils/epoch.rs b/rs-matter/src/utils/epoch.rs index 8236813..630f878 100644 --- a/rs-matter/src/utils/epoch.rs +++ b/rs-matter/src/utils/epoch.rs @@ -2,6 +2,11 @@ use core::time::Duration; pub type Epoch = fn() -> Duration; +// As per the spec, if Not After is 0, it should set the time to GeneralizedTime value of +// 99991231235959Z +// So CERT_DOESNT_EXPIRE value is calculated as epoch(99991231235959Z) - MATTER_EPOCH_SECS +pub const MATTER_CERT_DOESNT_EXPIRE: u64 = 252455615999; + pub const MATTER_EPOCH_SECS: u64 = 946684800; // Seconds from 1970/01/01 00:00:00 till 2000/01/01 00:00:00 UTC pub fn dummy_epoch() -> Duration { diff --git a/rs-matter/tests/data_model/long_reads.rs b/rs-matter/tests/data_model/long_reads.rs index 888e05a..1235f96 100644 --- a/rs-matter/tests/data_model/long_reads.rs +++ b/rs-matter/tests/data_model/long_reads.rs @@ -19,7 +19,10 @@ use rs_matter::{ data_model::{ cluster_basic_information as basic_info, cluster_on_off as onoff, objects::{EncodeValue, GlobalElements}, - sdm::{admin_commissioning as adm_comm, general_commissioning as gen_comm, noc}, + sdm::{ + admin_commissioning as adm_comm, general_commissioning as gen_comm, noc, + nw_commissioning, + }, system_model::{access_control as acl, descriptor}, }, interaction_model::{ @@ -130,6 +133,48 @@ fn wildcard_read_resp(part: u8) -> Vec> { ), attr_data!(0, 49, GlobalElements::FeatureMap, dont_care.clone()), attr_data!(0, 49, GlobalElements::AttributeList, dont_care.clone()), + attr_data!( + 0, + 49, + nw_commissioning::Attributes::MaxNetworks, + dont_care.clone() + ), + attr_data!( + 0, + 49, + nw_commissioning::Attributes::Networks, + dont_care.clone() + ), + attr_data!( + 0, + 49, + nw_commissioning::Attributes::ConnectMaxTimeSecs, + dont_care.clone() + ), + attr_data!( + 0, + 49, + nw_commissioning::Attributes::InterfaceEnabled, + dont_care.clone() + ), + attr_data!( + 0, + 49, + nw_commissioning::Attributes::LastNetworkingStatus, + dont_care.clone() + ), + attr_data!( + 0, + 49, + nw_commissioning::Attributes::LastNetworkID, + dont_care.clone() + ), + attr_data!( + 0, + 49, + nw_commissioning::Attributes::LastConnectErrorValue, + dont_care.clone() + ), attr_data!(0, 60, GlobalElements::FeatureMap, dont_care.clone()), attr_data!(0, 60, GlobalElements::AttributeList, dont_care.clone()), attr_data!( @@ -158,6 +203,9 @@ fn wildcard_read_resp(part: u8) -> Vec> { noc::AttributesDiscriminants::CurrentFabricIndex, dont_care.clone() ), + ]; + + let part2 = vec![ attr_data!( 0, 62, @@ -185,9 +233,6 @@ fn wildcard_read_resp(part: u8) -> Vec> { acl::AttributesDiscriminants::Extension, dont_care.clone() ), - ]; - - let part2 = vec![ attr_data!( 0, 31,