diff --git a/matter/src/data_model/cluster_basic_information.rs b/matter/src/data_model/cluster_basic_information.rs index 3026138..4560cc5 100644 --- a/matter/src/data_model/cluster_basic_information.rs +++ b/matter/src/data_model/cluster_basic_information.rs @@ -22,7 +22,7 @@ use num_derive::FromPrimitive; pub const ID: u32 = 0x0028; #[derive(FromPrimitive)] -enum Attributes { +pub enum Attributes { DMRevision = 0, VendorId = 2, ProductId = 4, diff --git a/matter/src/data_model/objects/encoder.rs b/matter/src/data_model/objects/encoder.rs index 24a81aa..d565316 100644 --- a/matter/src/data_model/objects/encoder.rs +++ b/matter/src/data_model/objects/encoder.rs @@ -42,7 +42,7 @@ pub enum EncodeValue<'a> { Tlv(TLVElement<'a>), /// This indicates a static value. This variant is typically used in the transmit/ /// to-tlv path - Value(&'a (dyn ToTLV)), + Value(&'a dyn ToTLV), } impl<'a> EncodeValue<'a> { diff --git a/matter/src/data_model/sdm/noc.rs b/matter/src/data_model/sdm/noc.rs index b8c5e94..7785fbd 100644 --- a/matter/src/data_model/sdm/noc.rs +++ b/matter/src/data_model/sdm/noc.rs @@ -81,7 +81,7 @@ pub enum Commands { } #[derive(FromPrimitive)] -enum Attributes { +pub enum Attributes { NOCs = 0, Fabrics = 1, SupportedFabrics = 2, diff --git a/matter/src/data_model/system_model/descriptor.rs b/matter/src/data_model/system_model/descriptor.rs index ac778bb..010ed0a 100644 --- a/matter/src/data_model/system_model/descriptor.rs +++ b/matter/src/data_model/system_model/descriptor.rs @@ -28,7 +28,7 @@ pub const ID: u32 = 0x001D; #[derive(FromPrimitive)] #[allow(clippy::enum_variant_names)] -enum Attributes { +pub enum Attributes { DeviceTypeList = 0, ServerList = 1, ClientList = 2, diff --git a/matter/tests/common/attributes.rs b/matter/tests/common/attributes.rs index f4e0f9b..1879adf 100644 --- a/matter/tests/common/attributes.rs +++ b/matter/tests/common/attributes.rs @@ -15,10 +15,14 @@ * limitations under the License. */ -use matter::interaction_model::{messages::ib::AttrResp, messages::msg::ReportDataMsg}; +use matter::{ + interaction_model::{messages::ib::AttrResp, messages::msg::ReportDataMsg}, + tlv::{TLVElement, TLVList, TLVWriter, TagType, ToTLV}, + utils::writebuf::WriteBuf, +}; /// Assert that the data received in the outbuf matches our expectations -pub fn assert_attr_report(received: &ReportDataMsg, expected: &[AttrResp]) { +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 @@ -29,7 +33,9 @@ pub fn assert_attr_report(received: &ReportDataMsg, expected: &[AttrResp]) { AttrResp::Data(d) => { // We don't match the data-version assert_eq!(e_d.path, d.path); - assert_eq!(e_d.data, d.data); + if !skip_data { + assert_eq!(e_d.data, d.data); + } } _ => { panic!("Invalid response, expected AttrRespIn::Data"); @@ -43,6 +49,14 @@ pub fn assert_attr_report(received: &ReportDataMsg, expected: &[AttrResp]) { 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; @@ -85,3 +99,37 @@ macro_rules! attr_status { 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 buf_len = s.buf.len(); + let mut wb = WriteBuf::new(&mut s.buf, buf_len); + 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/matter/tests/data_model/attributes.rs b/matter/tests/data_model/attributes.rs index 43d97f6..091b89f 100644 --- a/matter/tests/data_model/attributes.rs +++ b/matter/tests/data_model/attributes.rs @@ -29,8 +29,7 @@ use matter::{ msg::{ReadReq, ReportDataMsg, WriteReq, WriteResp}, }, }, - tlv::{self, ElementType, FromTLV, TLVElement, TLVList, TLVWriter, TagType}, - utils::writebuf::WriteBuf, + tlv::{self, ElementType, FromTLV, TLVElement, TLVWriter, TagType}, }; use crate::{ @@ -208,19 +207,6 @@ fn test_read_wc_endpoint_only_1_has_cluster() { handle_read_reqs(input, expected); } -fn get_tlvs<'a>(buf: &'a mut [u8], data: &[u16]) -> TLVElement<'a> { - let buf_len = buf.len(); - let mut wb = WriteBuf::new(buf, buf_len); - let mut tw = TLVWriter::new(&mut wb); - let _ = tw.start_array(TagType::Context(2)); - for e in data { - let _ = tw.u16(TagType::Anonymous, *e); - } - let _ = tw.end_container(); - let tlv_array = TLVList::new(wb.as_slice()).iter().next().unwrap(); - tlv_array -} - #[test] fn test_read_wc_endpoint_wc_attribute() { // 1 Attr Read Request @@ -230,9 +216,8 @@ fn test_read_wc_endpoint_wc_attribute() { let wc_ep_wc_attr = GenericPath::new(None, Some(echo_cluster::ID), None); let input = &[AttrPath::new(&wc_ep_wc_attr)]; - let mut buf = [0u8; 100]; - let attr_list_tlvs = get_tlvs( - &mut buf, + let attr_list = TLVHolder::new_array( + 2, &[ GlobalElements::FeatureMap as u16, GlobalElements::AttributeList as u16, @@ -242,6 +227,7 @@ fn test_read_wc_endpoint_wc_attribute() { echo_cluster::Attributes::AttCustom as u16, ], ); + let attr_list_tlv = attr_list.to_tlv(); let expected = &[ attr_data_path!( @@ -258,7 +244,7 @@ fn test_read_wc_endpoint_wc_attribute() { Some(echo_cluster::ID), Some(GlobalElements::AttributeList as u32), ), - attr_list_tlvs.get_element_type() + attr_list_tlv.get_element_type() ), attr_data_path!( GenericPath::new( @@ -298,7 +284,7 @@ fn test_read_wc_endpoint_wc_attribute() { Some(echo_cluster::ID), Some(GlobalElements::AttributeList as u32), ), - attr_list_tlvs.get_element_type() + attr_list_tlv.get_element_type() ), attr_data_path!( GenericPath::new( diff --git a/matter/tests/data_model/long_reads.rs b/matter/tests/data_model/long_reads.rs new file mode 100644 index 0000000..a82d7b1 --- /dev/null +++ b/matter/tests/data_model/long_reads.rs @@ -0,0 +1,184 @@ +/* + * + * 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 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}, + system_model::{access_control as acl, descriptor}, + }, + interaction_model::{ + core::{IMStatusCode, OpCode}, + messages::GenericPath, + messages::{ + ib::{AttrData, AttrPath, AttrResp}, + msg::{ReadReq, ReportDataMsg, StatusResp}, + }, + }, + tlv::{self, ElementType, FromTLV, TLVElement, TagType, ToTLV}, + transport::{ + exchange::{self, Exchange}, + udp::MAX_RX_BUF_SIZE, + }, +}; + +use crate::{ + attr_data, + common::{ + attributes::*, + echo_cluster as echo, + im_engine::{ImEngine, ImInput}, + }, +}; + +pub struct LongRead { + im_engine: ImEngine, +} + +impl LongRead { + pub fn new() -> Self { + let mut im_engine = ImEngine::new(); + // Use the same exchange for all parts of the transaction + im_engine.exch = Some(Exchange::new(1, 0, exchange::Role::Responder)); + Self { im_engine } + } + + pub fn process<'a>( + &mut self, + action: OpCode, + data: &dyn ToTLV, + data_out: &'a mut [u8], + ) -> (u8, &'a mut [u8]) { + let input = ImInput::new(action, data); + let (response, output) = self.im_engine.process(&input, data_out); + (response, output) + } +} + +fn wildcard_read_resp(part: u8) -> Vec> { + // For brevity, we only check the AttrPath, not the actual 'data' + let dont_care = ElementType::U8(0); + let part1 = vec![ + attr_data!(0, 29, GlobalElements::FeatureMap, dont_care), + attr_data!(0, 29, GlobalElements::AttributeList, dont_care), + attr_data!(0, 29, descriptor::Attributes::DeviceTypeList, dont_care), + attr_data!(0, 29, descriptor::Attributes::ServerList, dont_care), + attr_data!(0, 29, descriptor::Attributes::PartsList, dont_care), + attr_data!(0, 29, descriptor::Attributes::ClientList, dont_care), + attr_data!(0, 40, GlobalElements::FeatureMap, dont_care), + attr_data!(0, 40, GlobalElements::AttributeList, dont_care), + attr_data!(0, 40, basic_info::Attributes::DMRevision, dont_care), + attr_data!(0, 40, basic_info::Attributes::VendorId, dont_care), + attr_data!(0, 40, basic_info::Attributes::ProductId, dont_care), + attr_data!(0, 40, basic_info::Attributes::HwVer, dont_care), + attr_data!(0, 40, basic_info::Attributes::SwVer, dont_care), + attr_data!(0, 40, basic_info::Attributes::SwVerString, dont_care), + attr_data!(0, 40, basic_info::Attributes::SerialNo, dont_care), + attr_data!(0, 48, GlobalElements::FeatureMap, dont_care), + attr_data!(0, 48, GlobalElements::AttributeList, dont_care), + attr_data!(0, 48, gen_comm::Attributes::BreadCrumb, dont_care), + attr_data!(0, 48, gen_comm::Attributes::RegConfig, dont_care), + attr_data!(0, 48, gen_comm::Attributes::LocationCapability, dont_care), + attr_data!( + 0, + 48, + gen_comm::Attributes::BasicCommissioningInfo, + dont_care + ), + attr_data!(0, 49, GlobalElements::FeatureMap, dont_care), + attr_data!(0, 49, GlobalElements::AttributeList, dont_care), + attr_data!(0, 60, GlobalElements::FeatureMap, dont_care), + attr_data!(0, 60, GlobalElements::AttributeList, dont_care), + attr_data!(0, 60, adm_comm::Attributes::WindowStatus, dont_care), + attr_data!(0, 60, adm_comm::Attributes::AdminFabricIndex, dont_care), + attr_data!(0, 60, adm_comm::Attributes::AdminVendorId, dont_care), + attr_data!(0, 62, GlobalElements::FeatureMap, dont_care), + attr_data!(0, 62, GlobalElements::AttributeList, dont_care), + attr_data!(0, 62, noc::Attributes::CurrentFabricIndex, dont_care), + attr_data!(0, 62, noc::Attributes::Fabrics, dont_care), + attr_data!(0, 62, noc::Attributes::SupportedFabrics, dont_care), + attr_data!(0, 62, noc::Attributes::CommissionedFabrics, dont_care), + attr_data!(0, 31, GlobalElements::FeatureMap, dont_care), + attr_data!(0, 31, GlobalElements::AttributeList, dont_care), + attr_data!(0, 31, acl::Attributes::Acl, dont_care), + attr_data!(0, 31, acl::Attributes::Extension, dont_care), + attr_data!(0, 31, acl::Attributes::SubjectsPerEntry, dont_care), + attr_data!(0, 31, acl::Attributes::TargetsPerEntry, dont_care), + attr_data!(0, 31, acl::Attributes::EntriesPerFabric, dont_care), + attr_data!(0, echo::ID, GlobalElements::FeatureMap, dont_care), + attr_data!(0, echo::ID, GlobalElements::AttributeList, dont_care), + attr_data!(0, echo::ID, echo::Attributes::Att1, dont_care), + attr_data!(0, echo::ID, echo::Attributes::Att2, dont_care), + attr_data!(0, echo::ID, echo::Attributes::AttCustom, dont_care), + attr_data!(1, 29, GlobalElements::FeatureMap, dont_care), + attr_data!(1, 29, GlobalElements::AttributeList, dont_care), + attr_data!(1, 29, descriptor::Attributes::DeviceTypeList, dont_care), + ]; + + let part2 = vec![ + attr_data!(1, 29, descriptor::Attributes::ServerList, dont_care), + attr_data!(1, 29, descriptor::Attributes::PartsList, dont_care), + attr_data!(1, 29, descriptor::Attributes::ClientList, dont_care), + attr_data!(1, 6, GlobalElements::FeatureMap, dont_care), + attr_data!(1, 6, GlobalElements::AttributeList, dont_care), + attr_data!(1, 6, onoff::Attributes::OnOff, dont_care), + attr_data!(1, echo::ID, GlobalElements::FeatureMap, dont_care), + attr_data!(1, echo::ID, GlobalElements::AttributeList, dont_care), + attr_data!(1, echo::ID, echo::Attributes::Att1, dont_care), + attr_data!(1, echo::ID, echo::Attributes::Att2, dont_care), + attr_data!(1, echo::ID, echo::Attributes::AttCustom, dont_care), + ]; + + if part == 1 { + part1 + } else { + part2 + } +} + +#[test] +fn test_long_read_success() { + // Read the entire attribute database, which requires 2 reads to complete + let _ = env_logger::try_init(); + let mut lr = LongRead::new(); + let mut output = [0_u8; MAX_RX_BUF_SIZE + 100]; + + let wc_path = GenericPath::new(None, None, None); + + let read_all = [AttrPath::new(&wc_path)]; + let read_req = ReadReq::new(true).set_attr_requests(&read_all); + let expected_part1 = wildcard_read_resp(1); + let (out_code, out_data) = lr.process(OpCode::ReadRequest, &read_req, &mut output); + let root = tlv::get_root_node_struct(out_data).unwrap(); + let report_data = ReportDataMsg::from_tlv(&root).unwrap(); + assert_attr_report_skip_data(&report_data, &expected_part1); + assert_eq!(report_data.more_chunks, Some(true)); + assert_eq!(out_code, OpCode::ReportData as u8); + + // Ask for the next read by sending a status report + let status_report = StatusResp { + status: IMStatusCode::Success, + }; + let expected_part2 = wildcard_read_resp(2); + let (out_code, out_data) = lr.process(OpCode::StatusResponse, &status_report, &mut output); + let root = tlv::get_root_node_struct(out_data).unwrap(); + let report_data = ReportDataMsg::from_tlv(&root).unwrap(); + assert_attr_report_skip_data(&report_data, &expected_part2); + assert_eq!(report_data.more_chunks, None); + assert_eq!(out_code, OpCode::ReportData as u8); +} diff --git a/matter/tests/data_model_tests.rs b/matter/tests/data_model_tests.rs index 64a6f90..392909f 100644 --- a/matter/tests/data_model_tests.rs +++ b/matter/tests/data_model_tests.rs @@ -22,5 +22,6 @@ mod data_model { mod attribute_lists; mod attributes; mod commands; + mod long_reads; mod timed_requests; }