From 13ea55228029d8a6002d00075302ed26d4031f93 Mon Sep 17 00:00:00 2001 From: Kedar Sovani Date: Wed, 4 Jan 2023 12:21:52 +0530 Subject: [PATCH 1/7] Tests: Make a provision to override 'Exchange' in the im_engine --- matter/tests/common/im_engine.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/matter/tests/common/im_engine.rs b/matter/tests/common/im_engine.rs index 1d1ac38..22dc68d 100644 --- a/matter/tests/common/im_engine.rs +++ b/matter/tests/common/im_engine.rs @@ -58,6 +58,10 @@ pub struct ImEngine { pub dm: DataModel, pub acl_mgr: Arc, pub im: Box, + // By default, a new exchange is created for every run, if you wish to instead using a specific + // exchange, set this variable. This is helpful in situations where you have to run multiple + // actions in the same transaction (exchange) + pub exch: Option, } pub struct ImInput<'a> { @@ -111,12 +115,19 @@ impl ImEngine { let im = Box::new(InteractionModel::new(Box::new(dm.clone()))); - Self { dm, acl_mgr, im } + Self { + dm, + acl_mgr, + im, + exch: None, + } } /// Run a transaction through the interaction model engine pub fn process(&mut self, input: &ImInput, data_out: &mut [u8]) -> usize { - let mut exch = Exchange::new(1, 0, exchange::Role::Responder); + let mut new_exch = Exchange::new(1, 0, exchange::Role::Responder); + // Choose whether to use a new exchange, or use the one from the ImEngine configuration + let mut exch = self.exch.as_mut().unwrap_or_else(|| &mut new_exch); let mut sess_mgr: SessionMgr = Default::default(); From 08225653838b4fa4b661ec7bccc0ff9ca68c150c Mon Sep 17 00:00:00 2001 From: Kedar Sovani Date: Fri, 6 Jan 2023 07:43:34 +0530 Subject: [PATCH 2/7] Tests: Minor simplification change --- matter/tests/common/im_engine.rs | 17 +++++++++++------ matter/tests/data_model/acl_and_dataver.rs | 6 ++---- matter/tests/data_model/attribute_lists.rs | 3 +-- matter/tests/data_model/attributes.rs | 7 +++---- matter/tests/data_model/commands.rs | 3 +-- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/matter/tests/common/im_engine.rs b/matter/tests/common/im_engine.rs index 22dc68d..4167025 100644 --- a/matter/tests/common/im_engine.rs +++ b/matter/tests/common/im_engine.rs @@ -29,7 +29,7 @@ use matter::{ error::Error, fabric::FabricMgr, interaction_model::{core::OpCode, messages::ib::CmdPath, messages::msg, InteractionModel}, - tlv::{TLVWriter, TagType, ToTLV}, + tlv::{TLVArray, TLVWriter, TagType, ToTLV}, transport::packet::Packet, transport::proto_demux::HandleProto, transport::{ @@ -124,7 +124,7 @@ impl ImEngine { } /// Run a transaction through the interaction model engine - pub fn process(&mut self, input: &ImInput, data_out: &mut [u8]) -> usize { + pub fn process<'a>(&mut self, input: &ImInput, data_out: &'a mut [u8]) -> (u8, &'a mut [u8]) { let mut new_exch = Exchange::new(1, 0, exchange::Role::Responder); // Choose whether to use a new exchange, or use the one from the ImEngine configuration let mut exch = self.exch.as_mut().unwrap_or_else(|| &mut new_exch); @@ -163,16 +163,21 @@ impl ImEngine { self.im.handle_proto_id(&mut ctx).unwrap(); let out_data_len = ctx.tx.as_borrow_slice().len(); data_out[..out_data_len].copy_from_slice(ctx.tx.as_borrow_slice()); - out_data_len + let response = ctx.tx.get_proto_opcode(); + (response, &mut data_out[..out_data_len]) } } // Create an Interaction Model, Data Model and run a rx/tx transaction through it -pub fn im_engine(action: OpCode, data_in: &[u8], data_out: &mut [u8]) -> (DataModel, usize) { +pub fn im_engine<'a>( + action: OpCode, + data_in: &[u8], + data_out: &'a mut [u8], +) -> (DataModel, u8, &'a mut [u8]) { let mut engine = ImEngine::new(); let input = ImInput::new(action, data_in); - let output_len = engine.process(&input, data_out); - (engine.dm, output_len) + let (response, output) = engine.process(&input, data_out); + (engine.dm, response, output) } pub struct TestData<'a, 'b> { diff --git a/matter/tests/data_model/acl_and_dataver.rs b/matter/tests/data_model/acl_and_dataver.rs index e44c536..d505957 100644 --- a/matter/tests/data_model/acl_and_dataver.rs +++ b/matter/tests/data_model/acl_and_dataver.rs @@ -73,8 +73,7 @@ fn gen_read_reqs_output<'a>( let mut input = ImInput::new(OpCode::ReadRequest, wb.as_borrow_slice()); input.set_peer_node_id(peer_node_id); - let out_buf_len = im.process(&input, out_buf); - let out_buf = &out_buf[..out_buf_len]; + let (_, out_buf) = im.process(&input, out_buf); tlv::print_tlv_list(out_buf); let root = tlv::get_root_node_struct(out_buf).unwrap(); @@ -100,9 +99,8 @@ fn handle_write_reqs( let mut input = ImInput::new(OpCode::WriteRequest, wb.as_borrow_slice()); input.set_peer_node_id(peer_node_id); - let out_buf_len = im.process(&input, &mut out_buf); + let (_, out_buf) = im.process(&input, &mut out_buf); - let out_buf = &out_buf[..out_buf_len]; tlv::print_tlv_list(out_buf); let root = tlv::get_root_node_struct(out_buf).unwrap(); diff --git a/matter/tests/data_model/attribute_lists.rs b/matter/tests/data_model/attribute_lists.rs index 399d9a0..739ddaa 100644 --- a/matter/tests/data_model/attribute_lists.rs +++ b/matter/tests/data_model/attribute_lists.rs @@ -46,8 +46,7 @@ fn handle_write_reqs(input: &[AttrData], expected: &[AttrStatus]) -> DataModel { let write_req = WriteReq::new(false, input); write_req.to_tlv(&mut tw, TagType::Anonymous).unwrap(); - let (dm, out_buf_len) = im_engine(OpCode::WriteRequest, wb.as_borrow_slice(), &mut out_buf); - let out_buf = &out_buf[..out_buf_len]; + let (dm, _, out_buf) = im_engine(OpCode::WriteRequest, wb.as_borrow_slice(), &mut out_buf); tlv::print_tlv_list(out_buf); let root = tlv::get_root_node_struct(out_buf).unwrap(); diff --git a/matter/tests/data_model/attributes.rs b/matter/tests/data_model/attributes.rs index aaa9a74..07a88ad 100644 --- a/matter/tests/data_model/attributes.rs +++ b/matter/tests/data_model/attributes.rs @@ -54,8 +54,7 @@ fn gen_read_reqs_output<'a>(input: &[AttrPath], out_buf: &'a mut [u8]) -> Report let read_req = ReadReq::new(true).set_attr_requests(input); read_req.to_tlv(&mut tw, TagType::Anonymous).unwrap(); - let (_, out_buf_len) = im_engine(OpCode::ReadRequest, wb.as_borrow_slice(), out_buf); - let out_buf = &out_buf[..out_buf_len]; + let (_, _, out_buf) = im_engine(OpCode::ReadRequest, wb.as_borrow_slice(), out_buf); tlv::print_tlv_list(out_buf); let root = tlv::get_root_node_struct(out_buf).unwrap(); ReportDataMsg::from_tlv(&root).unwrap() @@ -73,12 +72,12 @@ fn handle_write_reqs(input: &[AttrData], expected: &[AttrStatus]) -> DataModel { let write_req = WriteReq::new(false, input); write_req.to_tlv(&mut tw, TagType::Anonymous).unwrap(); - let (dm, out_buf_len) = im_engine(OpCode::WriteRequest, wb.as_borrow_slice(), &mut out_buf); - let out_buf = &out_buf[..out_buf_len]; + let (dm, _, out_buf) = im_engine(OpCode::WriteRequest, wb.as_borrow_slice(), &mut out_buf); tlv::print_tlv_list(out_buf); let root = tlv::get_root_node_struct(out_buf).unwrap(); let mut index = 0; + let response_iter = root .find_tag(msg::WriteRespTag::WriteResponses as u32) .unwrap() diff --git a/matter/tests/data_model/commands.rs b/matter/tests/data_model/commands.rs index 8fdc838..af21f95 100644 --- a/matter/tests/data_model/commands.rs +++ b/matter/tests/data_model/commands.rs @@ -47,8 +47,7 @@ fn handle_commands(input: &[(CmdPath, Option)], expected: &[ExpectedInvResp] td.commands(input).unwrap(); - let (_, out_buf_len) = im_engine(OpCode::InvokeRequest, wb.as_borrow_slice(), &mut out_buf); - let out_buf = &out_buf[..out_buf_len]; + let (_, _, out_buf) = im_engine(OpCode::InvokeRequest, wb.as_borrow_slice(), &mut out_buf); tlv::print_tlv_list(out_buf); let root = tlv::get_root_node_struct(out_buf).unwrap(); From e05e75f24c80cd2601bc715977aabbfdfb92ebd7 Mon Sep 17 00:00:00 2001 From: Kedar Sovani Date: Fri, 6 Jan 2023 07:44:46 +0530 Subject: [PATCH 3/7] tests: tests for timed write requests handling --- matter/src/interaction_model/core.rs | 2 +- matter/src/interaction_model/messages.rs | 4 +- matter/tests/data_model/timed_requests.rs | 185 ++++++++++++++++++++++ matter/tests/data_model_tests.rs | 1 + 4 files changed, 189 insertions(+), 3 deletions(-) create mode 100644 matter/tests/data_model/timed_requests.rs diff --git a/matter/src/interaction_model/core.rs b/matter/src/interaction_model/core.rs index bc22385..2540c02 100644 --- a/matter/src/interaction_model/core.rs +++ b/matter/src/interaction_model/core.rs @@ -44,7 +44,7 @@ use super::{messages::msg::TimedReq, InteractionConsumer}; /* Interaction Model ID as per the Matter Spec */ const PROTO_ID_INTERACTION_MODEL: usize = 0x01; -#[derive(FromPrimitive, Debug, Copy, Clone)] +#[derive(FromPrimitive, Debug, Copy, Clone, PartialEq)] pub enum OpCode { Reserved = 0, StatusResponse = 1, diff --git a/matter/src/interaction_model/messages.rs b/matter/src/interaction_model/messages.rs index e54bad0..adb7ee0 100644 --- a/matter/src/interaction_model/messages.rs +++ b/matter/src/interaction_model/messages.rs @@ -73,12 +73,12 @@ pub mod msg { use super::ib::{AttrData, AttrPath, AttrResp, CmdData, DataVersionFilter}; - #[derive(FromTLV)] + #[derive(FromTLV, ToTLV)] pub struct TimedReq { pub timeout: u16, } - #[derive(ToTLV)] + #[derive(FromTLV, ToTLV)] pub struct StatusResp { pub status: IMStatusCode, } diff --git a/matter/tests/data_model/timed_requests.rs b/matter/tests/data_model/timed_requests.rs new file mode 100644 index 0000000..a22454f --- /dev/null +++ b/matter/tests/data_model/timed_requests.rs @@ -0,0 +1,185 @@ +/* + * + * 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::time; +use std::thread; + +use matter::{ + data_model::{ + core::DataModel, + objects::{AttrValue, EncodeValue}, + }, + interaction_model::{ + core::{IMStatusCode, OpCode}, + messages::{ + ib::{AttrData, AttrPath, AttrStatus}, + msg::{StatusResp, TimedReq, WriteReq}, + }, + messages::{msg, GenericPath}, + }, + tlv::{self, FromTLV, TLVWriter, TagType, ToTLV}, + transport::exchange::{self, Exchange}, + utils::writebuf::WriteBuf, +}; + +use crate::common::{ + echo_cluster, + im_engine::{ImEngine, ImInput}, +}; + +enum WriteResponse<'a> { + TransactionError, + TransactionSuccess(&'a [AttrStatus]), +} + +// Helper for handling Write Attribute sequences +fn handle_timed_write_reqs( + input: &[AttrData], + expected: WriteResponse, + timeout: u16, + delay: u16, +) -> DataModel { + let mut buf = [0u8; 400]; + let buf_len = buf.len(); + + 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)); + + // Send Timed Req + let mut out_buf = [0u8; 400]; + let mut wb = WriteBuf::new(&mut buf, buf_len); + let mut tw = TLVWriter::new(&mut wb); + let timed_req = TimedReq { timeout }; + timed_req.to_tlv(&mut tw, TagType::Anonymous).unwrap(); + let im_input = ImInput::new(OpCode::TimedRequest, wb.as_borrow_slice()); + let (_, out_buf) = im_engine.process(&im_input, &mut out_buf); + tlv::print_tlv_list(out_buf); + + // Process any delays + let delay = time::Duration::from_millis(delay.into()); + thread::sleep(delay); + + // Send Write Req + let mut out_buf = [0u8; 400]; + let mut wb = WriteBuf::new(&mut buf, buf_len); + let mut tw = TLVWriter::new(&mut wb); + let write_req = WriteReq::new(false, input); + write_req.to_tlv(&mut tw, TagType::Anonymous).unwrap(); + let input = ImInput::new(OpCode::WriteRequest, wb.as_borrow_slice()); + let (resp_opcode, out_buf) = im_engine.process(&input, &mut out_buf); + + tlv::print_tlv_list(out_buf); + let root = tlv::get_root_node_struct(out_buf).unwrap(); + + let mut index = 0; + + match expected { + WriteResponse::TransactionSuccess(t) => { + assert_eq!( + num::FromPrimitive::from_u8(resp_opcode), + Some(OpCode::WriteResponse) + ); + let response_iter = root + .find_tag(msg::WriteRespTag::WriteResponses as u32) + .unwrap() + .confirm_array() + .unwrap() + .enter() + .unwrap(); + for response in response_iter { + println!("Validating index {}", index); + let status = AttrStatus::from_tlv(&response).unwrap(); + assert_eq!(t[index], status); + println!("Index {} success", index); + index += 1; + } + assert_eq!(index, t.len()); + } + WriteResponse::TransactionError => { + assert_eq!( + num::FromPrimitive::from_u8(resp_opcode), + Some(OpCode::StatusResponse) + ); + let status_resp = StatusResp::from_tlv(&root).unwrap(); + assert_eq!(status_resp.status, IMStatusCode::Timeout); + } + } + im_engine.dm +} + +#[test] +fn test_timed_write_fail_and_success() { + // - 1 Timed Attr Write Transaction should fail due to timeout + // - 1 Timed Attr Write Transaction should succeed + let val0 = 10; + let _ = env_logger::try_init(); + let attr_data0 = |tag, t: &mut TLVWriter| { + let _ = t.u16(tag, val0); + }; + + let ep_att = GenericPath::new( + None, + Some(echo_cluster::ID), + Some(echo_cluster::Attributes::AttWrite as u32), + ); + let input = &[AttrData::new( + None, + AttrPath::new(&ep_att), + EncodeValue::Closure(&attr_data0), + )]; + + let ep0_att = GenericPath::new( + Some(0), + Some(echo_cluster::ID), + Some(echo_cluster::Attributes::AttWrite as u32), + ); + + let ep1_att = GenericPath::new( + Some(1), + Some(echo_cluster::ID), + Some(echo_cluster::Attributes::AttWrite as u32), + ); + let expected = &[ + AttrStatus::new(&ep0_att, IMStatusCode::Sucess, 0), + AttrStatus::new(&ep1_att, IMStatusCode::Sucess, 0), + ]; + + // Test with incorrect handling + handle_timed_write_reqs(input, WriteResponse::TransactionError, 400, 500); + + // Test with correct handling + let dm = handle_timed_write_reqs(input, WriteResponse::TransactionSuccess(expected), 400, 0); + assert_eq!( + AttrValue::Uint16(val0), + dm.read_attribute_raw( + 0, + echo_cluster::ID, + echo_cluster::Attributes::AttWrite as u16 + ) + .unwrap() + ); + assert_eq!( + AttrValue::Uint16(val0), + dm.read_attribute_raw( + 0, + echo_cluster::ID, + echo_cluster::Attributes::AttWrite as u16 + ) + .unwrap() + ); +} diff --git a/matter/tests/data_model_tests.rs b/matter/tests/data_model_tests.rs index 318906e..64a6f90 100644 --- a/matter/tests/data_model_tests.rs +++ b/matter/tests/data_model_tests.rs @@ -22,4 +22,5 @@ mod data_model { mod attribute_lists; mod attributes; mod commands; + mod timed_requests; } From 5c877982322700a03c9e4d1cafbda686defe650a Mon Sep 17 00:00:00 2001 From: Kedar Sovani Date: Fri, 6 Jan 2023 08:24:23 +0530 Subject: [PATCH 4/7] tests: simplification for command tests --- matter/src/interaction_model/messages.rs | 2 +- matter/tests/common/im_engine.rs | 40 +--------------- matter/tests/data_model/commands.rs | 60 ++++++++++++++---------- 3 files changed, 36 insertions(+), 66 deletions(-) diff --git a/matter/src/interaction_model/messages.rs b/matter/src/interaction_model/messages.rs index adb7ee0..3eb5e72 100644 --- a/matter/src/interaction_model/messages.rs +++ b/matter/src/interaction_model/messages.rs @@ -83,7 +83,7 @@ pub mod msg { pub status: IMStatusCode, } - #[derive(FromTLV)] + #[derive(FromTLV, ToTLV)] #[tlvargs(lifetime = "'a")] pub struct InvReq<'a> { pub suppress_response: Option, diff --git a/matter/tests/common/im_engine.rs b/matter/tests/common/im_engine.rs index 4167025..c33464e 100644 --- a/matter/tests/common/im_engine.rs +++ b/matter/tests/common/im_engine.rs @@ -28,8 +28,7 @@ use matter::{ }, error::Error, fabric::FabricMgr, - interaction_model::{core::OpCode, messages::ib::CmdPath, messages::msg, InteractionModel}, - tlv::{TLVArray, TLVWriter, TagType, ToTLV}, + interaction_model::{core::OpCode, InteractionModel}, transport::packet::Packet, transport::proto_demux::HandleProto, transport::{ @@ -39,7 +38,6 @@ use matter::{ proto_demux::ProtoCtx, session::{CloneData, SessionMgr, SessionMode}, }, - utils::writebuf::WriteBuf, }; use std::{ net::{Ipv4Addr, SocketAddr}, @@ -179,39 +177,3 @@ pub fn im_engine<'a>( let (response, output) = engine.process(&input, data_out); (engine.dm, response, output) } - -pub struct TestData<'a, 'b> { - tw: TLVWriter<'a, 'b>, -} - -impl<'a, 'b> TestData<'a, 'b> { - pub fn new(buf: &'b mut WriteBuf<'a>) -> Self { - Self { - tw: TLVWriter::new(buf), - } - } - - pub fn commands(&mut self, cmds: &[(CmdPath, Option)]) -> Result<(), Error> { - self.tw.start_struct(TagType::Anonymous)?; - self.tw.bool( - TagType::Context(msg::InvReqTag::SupressResponse as u8), - false, - )?; - self.tw - .bool(TagType::Context(msg::InvReqTag::TimedReq as u8), false)?; - self.tw - .start_array(TagType::Context(msg::InvReqTag::InvokeRequests as u8))?; - - for (cmd, data) in cmds { - self.tw.start_struct(TagType::Anonymous)?; - cmd.to_tlv(&mut self.tw, TagType::Context(0))?; - if let Some(d) = *data { - self.tw.u8(TagType::Context(1), d)?; - } - self.tw.end_container()?; - } - - self.tw.end_container()?; - self.tw.end_container() - } -} diff --git a/matter/tests/data_model/commands.rs b/matter/tests/data_model/commands.rs index af21f95..9583a34 100644 --- a/matter/tests/data_model/commands.rs +++ b/matter/tests/data_model/commands.rs @@ -19,17 +19,17 @@ use matter::{ data_model::{cluster_on_off, objects::EncodeValue}, interaction_model::{ core::{IMStatusCode, OpCode}, - messages::ib::{CmdPath, CmdStatus, InvResp}, messages::msg, + messages::{ + ib::{CmdData, CmdPath, CmdStatus, InvResp}, + msg::InvReq, + }, }, - tlv::{self, FromTLV}, + tlv::{self, FromTLV, TLVArray, TLVWriter, TagType, ToTLV}, utils::writebuf::WriteBuf, }; -use crate::common::{ - echo_cluster, - im_engine::{im_engine, TestData}, -}; +use crate::common::{echo_cluster, im_engine::im_engine}; enum ExpectedInvResp { Cmd(CmdPath, u8), @@ -37,15 +37,20 @@ enum ExpectedInvResp { } // Helper for handling Invoke Command sequences -fn handle_commands(input: &[(CmdPath, Option)], expected: &[ExpectedInvResp]) { +fn handle_commands(input: &[CmdData], expected: &[ExpectedInvResp]) { let mut buf = [0u8; 400]; let mut out_buf = [0u8; 400]; let buf_len = buf.len(); let mut wb = WriteBuf::new(&mut buf, buf_len); - let mut td = TestData::new(&mut wb); + let mut tw = TLVWriter::new(&mut wb); - td.commands(input).unwrap(); + let req = InvReq { + suppress_response: Some(false), + timed_request: Some(false), + inv_requests: Some(TLVArray::Slice(input)), + }; + req.to_tlv(&mut tw, TagType::Anonymous).unwrap(); let (_, _, out_buf) = im_engine(OpCode::InvokeRequest, wb.as_borrow_slice(), &mut out_buf); tlv::print_tlv_list(out_buf); @@ -92,15 +97,21 @@ fn handle_commands(input: &[(CmdPath, Option)], expected: &[ExpectedInvResp] assert_eq!(index, expected.len()); } +macro_rules! cmd_data { + ($path:ident, $data:literal) => { + CmdData::new($path, EncodeValue::Value(&($data as u32))) + }; +} + macro_rules! echo_req { ($endpoint:literal, $data:literal) => { - ( + CmdData::new( CmdPath::new( Some($endpoint), Some(echo_cluster::ID), Some(echo_cluster::Commands::EchoReq as u16), ), - Some($data), + EncodeValue::Value(&($data as u32)), ) }; } @@ -158,11 +169,11 @@ fn test_invoke_cmds_unsupported_fields() { let invalid_command = CmdPath::new(Some(0), Some(echo_cluster::ID), Some(0x1234)); let invalid_command_wc_endpoint = CmdPath::new(None, Some(echo_cluster::ID), Some(0x1234)); let input = &[ - (invalid_endpoint, Some(5)), - (invalid_cluster, Some(5)), - (invalid_cluster_wc_endpoint, Some(5)), - (invalid_command, Some(5)), - (invalid_command_wc_endpoint, Some(5)), + cmd_data!(invalid_endpoint, 5), + cmd_data!(invalid_cluster, 5), + cmd_data!(invalid_cluster_wc_endpoint, 5), + cmd_data!(invalid_command, 5), + cmd_data!(invalid_command_wc_endpoint, 5), ]; let expected = &[ @@ -190,15 +201,12 @@ fn test_invoke_cmd_wc_endpoint_all_have_clusters() { // 1 echo Request with wildcard endpoint // should generate 2 responses from the echo clusters on both let _ = env_logger::try_init(); - - let input = &[( - CmdPath::new( - None, - Some(echo_cluster::ID), - Some(echo_cluster::Commands::EchoReq as u16), - ), - Some(5), - )]; + let path = CmdPath::new( + None, + Some(echo_cluster::ID), + Some(echo_cluster::Commands::EchoReq as u16), + ); + let input = &[cmd_data!(path, 5)]; let expected = &[echo_resp!(0, 10), echo_resp!(1, 15)]; handle_commands(input, expected); } @@ -219,7 +227,7 @@ fn test_invoke_cmd_wc_endpoint_only_1_has_cluster() { Some(cluster_on_off::ID), Some(cluster_on_off::Commands::On as u16), ); - let input = &[(target, Some(1))]; + let input = &[cmd_data!(target, 1)]; let expected = &[ExpectedInvResp::Status(CmdStatus::new( expected_path, IMStatusCode::Sucess, From ebe2e979f5c1520cce378f17d0aba6c9cde7d0b5 Mon Sep 17 00:00:00 2001 From: Kedar Sovani Date: Fri, 6 Jan 2023 22:15:01 +0530 Subject: [PATCH 5/7] tests: minor restructuring --- matter/src/interaction_model/messages.rs | 27 ++++++++++++++----- matter/src/tlv/traits.rs | 32 +++++++++++++++++++++++ matter/tests/common/attributes.rs | 10 ++----- matter/tests/data_model/attributes.rs | 24 +++-------------- matter/tests/data_model/commands.rs | 11 ++------ matter/tests/data_model/timed_requests.rs | 23 +++------------- 6 files changed, 65 insertions(+), 62 deletions(-) diff --git a/matter/src/interaction_model/messages.rs b/matter/src/interaction_model/messages.rs index 3eb5e72..c5c1ca0 100644 --- a/matter/src/interaction_model/messages.rs +++ b/matter/src/interaction_model/messages.rs @@ -71,7 +71,7 @@ pub mod msg { tlv::{FromTLV, TLVArray, TLVElement, TLVWriter, TagType, ToTLV}, }; - use super::ib::{AttrData, AttrPath, AttrResp, CmdData, DataVersionFilter}; + use super::ib::{self, AttrData, AttrPath, AttrResp, AttrStatus, CmdData, DataVersionFilter}; #[derive(FromTLV, ToTLV)] pub struct TimedReq { @@ -83,6 +83,12 @@ pub mod msg { pub status: IMStatusCode, } + pub enum InvReqTag { + SupressResponse = 0, + TimedReq = 1, + InvokeRequests = 2, + } + #[derive(FromTLV, ToTLV)] #[tlvargs(lifetime = "'a")] pub struct InvReq<'a> { @@ -91,15 +97,18 @@ pub mod msg { pub inv_requests: Option>>, } + // This enum is helpful when we are constructing the response + // step by step in incremental manner pub enum InvRespTag { SupressResponse = 0, InvokeResponses = 1, } - pub enum InvReqTag { - SupressResponse = 0, - TimedReq = 1, - InvokeRequests = 2, + #[derive(FromTLV, ToTLV, Debug)] + #[tlvargs(lifetime = "'a")] + pub struct InvResp<'a> { + pub suppress_response: Option, + pub inv_responses: Option>>, } #[derive(Default, ToTLV, FromTLV)] @@ -171,6 +180,12 @@ pub mod msg { } // Write Response + #[derive(ToTLV, FromTLV)] + #[tlvargs(lifetime = "'a")] + pub struct WriteResp<'a> { + pub write_responses: TLVArray<'a, AttrStatus>, + } + pub enum WriteRespTag { WriteResponses = 0, } @@ -190,7 +205,7 @@ pub mod ib { use super::GenericPath; // Command Response - #[derive(Clone, Copy, FromTLV, ToTLV)] + #[derive(Clone, Copy, FromTLV, ToTLV, Debug)] #[tlvargs(lifetime = "'a")] pub enum InvResp<'a> { Cmd(CmdData<'a>), diff --git a/matter/src/tlv/traits.rs b/matter/src/tlv/traits.rs index a4d2ae3..ab03a63 100644 --- a/matter/src/tlv/traits.rs +++ b/matter/src/tlv/traits.rs @@ -19,6 +19,7 @@ use super::{ElementType, TLVContainerIterator, TLVElement, TLVWriter, TagType}; use crate::error::Error; use core::slice::Iter; use log::error; +use std::fmt::Debug; pub trait FromTLV<'a> { fn from_tlv(t: &TLVElement<'a>) -> Result @@ -282,6 +283,7 @@ impl TLVArrayOwned { } } +#[derive(Copy, Clone)] pub enum TLVArray<'a, T> { // This is used for the to-tlv path Slice(&'a [T]), @@ -342,6 +344,27 @@ impl<'a, T: FromTLV<'a> + Copy> Iterator for TLVArrayIter<'a, T> { } } +impl<'a, T> PartialEq<&[T]> for TLVArray<'a, T> +where + T: ToTLV + FromTLV<'a> + Copy + PartialEq, +{ + fn eq(&self, other: &&[T]) -> bool { + let mut iter1 = self.iter(); + let mut iter2 = other.into_iter(); + loop { + match (iter1.next(), iter2.next()) { + (None, None) => return true, + (Some(x), Some(y)) => { + if x != *y { + return false; + } + } + _ => return false, + } + } + } +} + impl<'a, T: ToTLV> ToTLV for TLVArray<'a, T> { fn to_tlv(&self, tw: &mut TLVWriter, tag_type: TagType) -> Result<(), Error> { match *self { @@ -364,6 +387,15 @@ impl<'a, T> FromTLV<'a> for TLVArray<'a, T> { } } +impl<'a, T: Debug + ToTLV + FromTLV<'a> + Copy> Debug for TLVArray<'a, T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for i in self.iter() { + writeln!(f, "{:?}", i)?; + } + writeln!(f, "") + } +} + #[cfg(test)] mod tests { use super::{FromTLV, OctetStr, TLVElement, TLVWriter, TagType, ToTLV}; diff --git a/matter/tests/common/attributes.rs b/matter/tests/common/attributes.rs index 42d4c83..1131c27 100644 --- a/matter/tests/common/attributes.rs +++ b/matter/tests/common/attributes.rs @@ -21,6 +21,7 @@ use matter::interaction_model::{messages::ib::AttrResp, messages::msg::ReportDat pub fn assert_attr_report(received: &ReportDataMsg, expected: &[AttrResp]) { 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] { @@ -34,14 +35,7 @@ pub fn assert_attr_report(received: &ReportDataMsg, expected: &[AttrResp]) { panic!("Invalid response, expected AttrRespIn::Data"); } }, - AttrResp::Status(e_s) => match inv_response { - AttrResp::Status(s) => { - assert_eq!(e_s, s); - } - _ => { - panic!("Invalid response, expected AttrRespIn::Status"); - } - }, + AttrResp::Status(s) => assert_eq!(AttrResp::Status(s), inv_response), } println!("Index {} success", index); index += 1; diff --git a/matter/tests/data_model/attributes.rs b/matter/tests/data_model/attributes.rs index 07a88ad..1ff64a4 100644 --- a/matter/tests/data_model/attributes.rs +++ b/matter/tests/data_model/attributes.rs @@ -23,11 +23,11 @@ use matter::{ }, interaction_model::{ core::{IMStatusCode, OpCode}, + messages::GenericPath, messages::{ ib::{AttrData, AttrPath, AttrResp, AttrStatus}, - msg::{ReadReq, ReportDataMsg, WriteReq}, + msg::{ReadReq, ReportDataMsg, WriteReq, WriteResp}, }, - messages::{msg, GenericPath}, }, tlv::{self, ElementType, FromTLV, TLVElement, TLVList, TLVWriter, TagType, ToTLV}, utils::writebuf::WriteBuf, @@ -73,26 +73,10 @@ fn handle_write_reqs(input: &[AttrData], expected: &[AttrStatus]) -> DataModel { write_req.to_tlv(&mut tw, TagType::Anonymous).unwrap(); let (dm, _, out_buf) = im_engine(OpCode::WriteRequest, wb.as_borrow_slice(), &mut out_buf); - tlv::print_tlv_list(out_buf); let root = tlv::get_root_node_struct(out_buf).unwrap(); + let response = WriteResp::from_tlv(&root).unwrap(); + assert_eq!(response.write_responses, expected); - let mut index = 0; - - let response_iter = root - .find_tag(msg::WriteRespTag::WriteResponses as u32) - .unwrap() - .confirm_array() - .unwrap() - .enter() - .unwrap(); - for response in response_iter { - println!("Validating index {}", index); - let status = AttrStatus::from_tlv(&response).unwrap(); - assert_eq!(expected[index], status); - println!("Index {} success", index); - index += 1; - } - assert_eq!(index, expected.len()); dm } diff --git a/matter/tests/data_model/commands.rs b/matter/tests/data_model/commands.rs index 9583a34..c97b672 100644 --- a/matter/tests/data_model/commands.rs +++ b/matter/tests/data_model/commands.rs @@ -56,17 +56,10 @@ fn handle_commands(input: &[CmdData], expected: &[ExpectedInvResp]) { tlv::print_tlv_list(out_buf); let root = tlv::get_root_node_struct(out_buf).unwrap(); + let resp = msg::InvResp::from_tlv(&root).unwrap(); let mut index = 0; - let cmd_list_iter = root - .find_tag(msg::InvRespTag::InvokeResponses as u32) - .unwrap() - .confirm_array() - .unwrap() - .enter() - .unwrap(); - for response in cmd_list_iter { + for inv_response in resp.inv_responses.unwrap().iter() { println!("Validating index {}", index); - let inv_response = InvResp::from_tlv(&response).unwrap(); match expected[index] { ExpectedInvResp::Cmd(e_c, e_d) => match inv_response { InvResp::Cmd(c) => { diff --git a/matter/tests/data_model/timed_requests.rs b/matter/tests/data_model/timed_requests.rs index a22454f..6d2dc00 100644 --- a/matter/tests/data_model/timed_requests.rs +++ b/matter/tests/data_model/timed_requests.rs @@ -25,11 +25,11 @@ use matter::{ }, interaction_model::{ core::{IMStatusCode, OpCode}, + messages::GenericPath, messages::{ ib::{AttrData, AttrPath, AttrStatus}, - msg::{StatusResp, TimedReq, WriteReq}, + msg::{StatusResp, TimedReq, WriteReq, WriteResp}, }, - messages::{msg, GenericPath}, }, tlv::{self, FromTLV, TLVWriter, TagType, ToTLV}, transport::exchange::{self, Exchange}, @@ -86,29 +86,14 @@ fn handle_timed_write_reqs( tlv::print_tlv_list(out_buf); let root = tlv::get_root_node_struct(out_buf).unwrap(); - let mut index = 0; - match expected { WriteResponse::TransactionSuccess(t) => { assert_eq!( num::FromPrimitive::from_u8(resp_opcode), Some(OpCode::WriteResponse) ); - let response_iter = root - .find_tag(msg::WriteRespTag::WriteResponses as u32) - .unwrap() - .confirm_array() - .unwrap() - .enter() - .unwrap(); - for response in response_iter { - println!("Validating index {}", index); - let status = AttrStatus::from_tlv(&response).unwrap(); - assert_eq!(t[index], status); - println!("Index {} success", index); - index += 1; - } - assert_eq!(index, t.len()); + let resp = WriteResp::from_tlv(&root).unwrap(); + assert_eq!(resp.write_responses, t); } WriteResponse::TransactionError => { assert_eq!( From 4a041e1f8cf2bcad38752cb22927432e28ea360d Mon Sep 17 00:00:00 2001 From: Kedar Sovani Date: Sat, 7 Jan 2023 09:24:30 +0530 Subject: [PATCH 6/7] tests: use 'dyn ToTLV' to avoid duplication --- matter/tests/common/im_engine.rs | 31 +++++++++++++------ matter/tests/data_model/acl_and_dataver.rs | 20 ++----------- matter/tests/data_model/attribute_lists.rs | 35 ++++------------------ matter/tests/data_model/attributes.rs | 20 ++----------- matter/tests/data_model/commands.rs | 12 ++------ matter/tests/data_model/timed_requests.rs | 16 ++-------- 6 files changed, 39 insertions(+), 95 deletions(-) diff --git a/matter/tests/common/im_engine.rs b/matter/tests/common/im_engine.rs index c33464e..ddd8b30 100644 --- a/matter/tests/common/im_engine.rs +++ b/matter/tests/common/im_engine.rs @@ -29,6 +29,7 @@ use matter::{ error::Error, fabric::FabricMgr, interaction_model::{core::OpCode, InteractionModel}, + tlv::{TLVWriter, TagType, ToTLV}, transport::packet::Packet, transport::proto_demux::HandleProto, transport::{ @@ -38,6 +39,7 @@ use matter::{ proto_demux::ProtoCtx, session::{CloneData, SessionMgr, SessionMode}, }, + utils::writebuf::WriteBuf, }; use std::{ net::{Ipv4Addr, SocketAddr}, @@ -64,16 +66,16 @@ pub struct ImEngine { pub struct ImInput<'a> { action: OpCode, - data_in: &'a [u8], + data: &'a dyn ToTLV, peer_id: u64, } pub const IM_ENGINE_PEER_ID: u64 = 445566; impl<'a> ImInput<'a> { - pub fn new(action: OpCode, data_in: &'a [u8]) -> Self { + pub fn new(action: OpCode, data: &'a dyn ToTLV) -> Self { Self { action, - data_in, + data, peer_id: IM_ENGINE_PEER_ID, } } @@ -152,10 +154,21 @@ impl ImEngine { rx.set_proto_id(0x01); rx.set_proto_opcode(input.action as u8); rx.peer = Address::default(); - let in_data_len = input.data_in.len(); - let rx_buf = rx.as_borrow_slice(); - rx_buf[..in_data_len].copy_from_slice(input.data_in); - rx.get_parsebuf().unwrap().set_len(in_data_len); + + { + let mut buf = [0u8; 400]; + let buf_len = buf.len(); + let mut wb = WriteBuf::new(&mut buf, buf_len); + let mut tw = TLVWriter::new(&mut wb); + + input.data.to_tlv(&mut tw, TagType::Anonymous).unwrap(); + + let input_data = wb.as_borrow_slice(); + let in_data_len = input_data.len(); + let rx_buf = rx.as_borrow_slice(); + rx_buf[..in_data_len].copy_from_slice(input_data); + rx.get_parsebuf().unwrap().set_len(in_data_len); + } let mut ctx = ProtoCtx::new(exch_ctx, rx, tx); self.im.handle_proto_id(&mut ctx).unwrap(); @@ -169,11 +182,11 @@ impl ImEngine { // Create an Interaction Model, Data Model and run a rx/tx transaction through it pub fn im_engine<'a>( action: OpCode, - data_in: &[u8], + data: &dyn ToTLV, data_out: &'a mut [u8], ) -> (DataModel, u8, &'a mut [u8]) { let mut engine = ImEngine::new(); - let input = ImInput::new(action, data_in); + let input = ImInput::new(action, data); let (response, output) = engine.process(&input, data_out); (engine.dm, response, output) } diff --git a/matter/tests/data_model/acl_and_dataver.rs b/matter/tests/data_model/acl_and_dataver.rs index d505957..11e3576 100644 --- a/matter/tests/data_model/acl_and_dataver.rs +++ b/matter/tests/data_model/acl_and_dataver.rs @@ -29,8 +29,7 @@ use matter::{ }, messages::{msg, GenericPath}, }, - tlv::{self, ElementType, FromTLV, TLVArray, TLVElement, TLVWriter, TagType, ToTLV}, - utils::writebuf::WriteBuf, + tlv::{self, ElementType, FromTLV, TLVArray, TLVElement, TLVWriter, TagType}, }; use crate::{ @@ -61,16 +60,10 @@ fn gen_read_reqs_output<'a>( dataver_filters: Option>, out_buf: &'a mut [u8], ) -> ReportDataMsg<'a> { - let mut buf = [0u8; 400]; - let buf_len = buf.len(); - let mut wb = WriteBuf::new(&mut buf, buf_len); - let mut tw = TLVWriter::new(&mut wb); - let mut read_req = ReadReq::new(true).set_attr_requests(input); read_req.dataver_filters = dataver_filters; - read_req.to_tlv(&mut tw, TagType::Anonymous).unwrap(); - let mut input = ImInput::new(OpCode::ReadRequest, wb.as_borrow_slice()); + let mut input = ImInput::new(OpCode::ReadRequest, &read_req); input.set_peer_node_id(peer_node_id); let (_, out_buf) = im.process(&input, out_buf); @@ -87,17 +80,10 @@ fn handle_write_reqs( input: &[AttrData], expected: &[AttrStatus], ) { - let mut buf = [0u8; 400]; let mut out_buf = [0u8; 400]; - - let buf_len = buf.len(); - let mut wb = WriteBuf::new(&mut buf, buf_len); - let mut tw = TLVWriter::new(&mut wb); - let write_req = WriteReq::new(false, input); - write_req.to_tlv(&mut tw, TagType::Anonymous).unwrap(); - let mut input = ImInput::new(OpCode::WriteRequest, wb.as_borrow_slice()); + let mut input = ImInput::new(OpCode::WriteRequest, &write_req); input.set_peer_node_id(peer_node_id); let (_, out_buf) = im.process(&input, &mut out_buf); diff --git a/matter/tests/data_model/attribute_lists.rs b/matter/tests/data_model/attribute_lists.rs index 739ddaa..34b1353 100644 --- a/matter/tests/data_model/attribute_lists.rs +++ b/matter/tests/data_model/attribute_lists.rs @@ -19,14 +19,13 @@ use matter::{ data_model::{core::DataModel, objects::EncodeValue}, interaction_model::{ core::{IMStatusCode, OpCode}, + messages::GenericPath, messages::{ ib::{AttrData, AttrPath, AttrStatus}, - msg::WriteReq, + msg::{WriteReq, WriteResp}, }, - messages::{msg, GenericPath}, }, - tlv::{self, FromTLV, Nullable, TLVWriter, TagType, ToTLV}, - utils::writebuf::WriteBuf, + tlv::{self, FromTLV, Nullable}, }; use crate::common::{ @@ -36,36 +35,14 @@ use crate::common::{ // Helper for handling Write Attribute sequences fn handle_write_reqs(input: &[AttrData], expected: &[AttrStatus]) -> DataModel { - let mut buf = [0u8; 400]; let mut out_buf = [0u8; 400]; - - let buf_len = buf.len(); - let mut wb = WriteBuf::new(&mut buf, buf_len); - let mut tw = TLVWriter::new(&mut wb); - let write_req = WriteReq::new(false, input); - write_req.to_tlv(&mut tw, TagType::Anonymous).unwrap(); - let (dm, _, out_buf) = im_engine(OpCode::WriteRequest, wb.as_borrow_slice(), &mut out_buf); + let (dm, _, out_buf) = im_engine(OpCode::WriteRequest, &write_req, &mut out_buf); tlv::print_tlv_list(out_buf); let root = tlv::get_root_node_struct(out_buf).unwrap(); - - let mut index = 0; - let response_iter = root - .find_tag(msg::WriteRespTag::WriteResponses as u32) - .unwrap() - .confirm_array() - .unwrap() - .enter() - .unwrap(); - for response in response_iter { - println!("Validating index {}", index); - let status = AttrStatus::from_tlv(&response).unwrap(); - assert_eq!(expected[index], status); - println!("Index {} success", index); - index += 1; - } - assert_eq!(index, expected.len()); + let resp = WriteResp::from_tlv(&root).unwrap(); + assert_eq!(resp.write_responses, expected); dm } diff --git a/matter/tests/data_model/attributes.rs b/matter/tests/data_model/attributes.rs index 1ff64a4..5e230c7 100644 --- a/matter/tests/data_model/attributes.rs +++ b/matter/tests/data_model/attributes.rs @@ -29,7 +29,7 @@ use matter::{ msg::{ReadReq, ReportDataMsg, WriteReq, WriteResp}, }, }, - tlv::{self, ElementType, FromTLV, TLVElement, TLVList, TLVWriter, TagType, ToTLV}, + tlv::{self, ElementType, FromTLV, TLVElement, TLVList, TLVWriter, TagType}, utils::writebuf::WriteBuf, }; @@ -46,15 +46,8 @@ fn handle_read_reqs(input: &[AttrPath], expected: &[AttrResp]) { // Helper for handling Read Req sequences fn gen_read_reqs_output<'a>(input: &[AttrPath], out_buf: &'a mut [u8]) -> ReportDataMsg<'a> { - let mut buf = [0u8; 400]; - let buf_len = buf.len(); - let mut wb = WriteBuf::new(&mut buf, buf_len); - let mut tw = TLVWriter::new(&mut wb); - let read_req = ReadReq::new(true).set_attr_requests(input); - read_req.to_tlv(&mut tw, TagType::Anonymous).unwrap(); - - let (_, _, out_buf) = im_engine(OpCode::ReadRequest, wb.as_borrow_slice(), out_buf); + let (_, _, out_buf) = im_engine(OpCode::ReadRequest, &read_req, out_buf); tlv::print_tlv_list(out_buf); let root = tlv::get_root_node_struct(out_buf).unwrap(); ReportDataMsg::from_tlv(&root).unwrap() @@ -62,17 +55,10 @@ fn gen_read_reqs_output<'a>(input: &[AttrPath], out_buf: &'a mut [u8]) -> Report // Helper for handling Write Attribute sequences fn handle_write_reqs(input: &[AttrData], expected: &[AttrStatus]) -> DataModel { - let mut buf = [0u8; 400]; let mut out_buf = [0u8; 400]; - - let buf_len = buf.len(); - let mut wb = WriteBuf::new(&mut buf, buf_len); - let mut tw = TLVWriter::new(&mut wb); - let write_req = WriteReq::new(false, input); - write_req.to_tlv(&mut tw, TagType::Anonymous).unwrap(); - let (dm, _, out_buf) = im_engine(OpCode::WriteRequest, wb.as_borrow_slice(), &mut out_buf); + let (dm, _, out_buf) = im_engine(OpCode::WriteRequest, &write_req, &mut out_buf); let root = tlv::get_root_node_struct(out_buf).unwrap(); let response = WriteResp::from_tlv(&root).unwrap(); assert_eq!(response.write_responses, expected); diff --git a/matter/tests/data_model/commands.rs b/matter/tests/data_model/commands.rs index c97b672..e7cf2d3 100644 --- a/matter/tests/data_model/commands.rs +++ b/matter/tests/data_model/commands.rs @@ -25,8 +25,7 @@ use matter::{ msg::InvReq, }, }, - tlv::{self, FromTLV, TLVArray, TLVWriter, TagType, ToTLV}, - utils::writebuf::WriteBuf, + tlv::{self, FromTLV, TLVArray}, }; use crate::common::{echo_cluster, im_engine::im_engine}; @@ -38,21 +37,14 @@ enum ExpectedInvResp { // Helper for handling Invoke Command sequences fn handle_commands(input: &[CmdData], expected: &[ExpectedInvResp]) { - let mut buf = [0u8; 400]; let mut out_buf = [0u8; 400]; - - let buf_len = buf.len(); - let mut wb = WriteBuf::new(&mut buf, buf_len); - let mut tw = TLVWriter::new(&mut wb); - let req = InvReq { suppress_response: Some(false), timed_request: Some(false), inv_requests: Some(TLVArray::Slice(input)), }; - req.to_tlv(&mut tw, TagType::Anonymous).unwrap(); - let (_, _, out_buf) = im_engine(OpCode::InvokeRequest, wb.as_borrow_slice(), &mut out_buf); + let (_, _, out_buf) = im_engine(OpCode::InvokeRequest, &req, &mut out_buf); tlv::print_tlv_list(out_buf); let root = tlv::get_root_node_struct(out_buf).unwrap(); diff --git a/matter/tests/data_model/timed_requests.rs b/matter/tests/data_model/timed_requests.rs index 6d2dc00..09f5fec 100644 --- a/matter/tests/data_model/timed_requests.rs +++ b/matter/tests/data_model/timed_requests.rs @@ -31,9 +31,8 @@ use matter::{ msg::{StatusResp, TimedReq, WriteReq, WriteResp}, }, }, - tlv::{self, FromTLV, TLVWriter, TagType, ToTLV}, + tlv::{self, FromTLV, TLVWriter}, transport::exchange::{self, Exchange}, - utils::writebuf::WriteBuf, }; use crate::common::{ @@ -53,20 +52,14 @@ fn handle_timed_write_reqs( timeout: u16, delay: u16, ) -> DataModel { - let mut buf = [0u8; 400]; - let buf_len = buf.len(); - 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)); // Send Timed Req let mut out_buf = [0u8; 400]; - let mut wb = WriteBuf::new(&mut buf, buf_len); - let mut tw = TLVWriter::new(&mut wb); let timed_req = TimedReq { timeout }; - timed_req.to_tlv(&mut tw, TagType::Anonymous).unwrap(); - let im_input = ImInput::new(OpCode::TimedRequest, wb.as_borrow_slice()); + let im_input = ImInput::new(OpCode::TimedRequest, &timed_req); let (_, out_buf) = im_engine.process(&im_input, &mut out_buf); tlv::print_tlv_list(out_buf); @@ -76,11 +69,8 @@ fn handle_timed_write_reqs( // Send Write Req let mut out_buf = [0u8; 400]; - let mut wb = WriteBuf::new(&mut buf, buf_len); - let mut tw = TLVWriter::new(&mut wb); let write_req = WriteReq::new(false, input); - write_req.to_tlv(&mut tw, TagType::Anonymous).unwrap(); - let input = ImInput::new(OpCode::WriteRequest, wb.as_borrow_slice()); + let input = ImInput::new(OpCode::WriteRequest, &write_req); let (resp_opcode, out_buf) = im_engine.process(&input, &mut out_buf); tlv::print_tlv_list(out_buf); From 91b451db0649a454873a98be4db241b46b125c0a Mon Sep 17 00:00:00 2001 From: Kedar Sovani Date: Sat, 7 Jan 2023 13:31:28 +0530 Subject: [PATCH 7/7] tests: Add tests for timed invoke --- matter/tests/common/commands.rs | 98 ++++++++++++ matter/tests/common/mod.rs | 1 + matter/tests/data_model/commands.rs | 82 ++-------- matter/tests/data_model/timed_requests.rs | 174 ++++++++++++++++++---- 4 files changed, 257 insertions(+), 98 deletions(-) create mode 100644 matter/tests/common/commands.rs diff --git a/matter/tests/common/commands.rs b/matter/tests/common/commands.rs new file mode 100644 index 0000000..919565a --- /dev/null +++ b/matter/tests/common/commands.rs @@ -0,0 +1,98 @@ +/* + * + * 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 matter::{ + data_model::objects::EncodeValue, + interaction_model::{ + messages::ib::{CmdPath, CmdStatus, InvResp}, + messages::msg, + }, +}; + +pub enum ExpectedInvResp { + Cmd(CmdPath, u8), + Status(CmdStatus), +} + +pub fn assert_inv_response(resp: &msg::InvResp, expected: &[ExpectedInvResp]) { + let mut index = 0; + for inv_response in resp.inv_responses.unwrap().iter() { + println!("Validating index {}", index); + match expected[index] { + ExpectedInvResp::Cmd(e_c, e_d) => match inv_response { + InvResp::Cmd(c) => { + assert_eq!(e_c, c.path); + match c.data { + EncodeValue::Tlv(t) => { + assert_eq!(e_d, t.find_tag(0).unwrap().u8().unwrap()) + } + _ => panic!("Incorrect CmdDataType"), + } + } + _ => { + panic!("Invalid response, expected InvResponse::Cmd"); + } + }, + ExpectedInvResp::Status(e_status) => match inv_response { + InvResp::Status(status) => { + assert_eq!(e_status, status); + } + _ => { + panic!("Invalid response, expected InvResponse::Status"); + } + }, + } + println!("Index {} success", index); + index += 1; + } + assert_eq!(index, expected.len()); +} + +#[macro_export] +macro_rules! cmd_data { + ($path:ident, $data:literal) => { + CmdData::new($path, EncodeValue::Value(&($data as u32))) + }; +} + +#[macro_export] +macro_rules! echo_req { + ($endpoint:literal, $data:literal) => { + CmdData::new( + CmdPath::new( + Some($endpoint), + Some(echo_cluster::ID), + Some(echo_cluster::Commands::EchoReq as u16), + ), + EncodeValue::Value(&($data as u32)), + ) + }; +} + +#[macro_export] +macro_rules! echo_resp { + ($endpoint:literal, $data:literal) => { + ExpectedInvResp::Cmd( + CmdPath::new( + Some($endpoint), + Some(echo_cluster::ID), + Some(echo_cluster::Commands::EchoResp as u16), + ), + $data, + ) + }; +} diff --git a/matter/tests/common/mod.rs b/matter/tests/common/mod.rs index 652a9d4..dea136a 100644 --- a/matter/tests/common/mod.rs +++ b/matter/tests/common/mod.rs @@ -16,5 +16,6 @@ */ pub mod attributes; +pub mod commands; pub mod echo_cluster; pub mod im_engine; diff --git a/matter/tests/data_model/commands.rs b/matter/tests/data_model/commands.rs index e7cf2d3..ef0b434 100644 --- a/matter/tests/data_model/commands.rs +++ b/matter/tests/data_model/commands.rs @@ -15,26 +15,25 @@ * limitations under the License. */ +use crate::{ + cmd_data, + common::{commands::*, echo_cluster, im_engine::im_engine}, + echo_req, echo_resp, +}; + use matter::{ data_model::{cluster_on_off, objects::EncodeValue}, interaction_model::{ core::{IMStatusCode, OpCode}, - messages::msg, messages::{ - ib::{CmdData, CmdPath, CmdStatus, InvResp}, + ib::{CmdData, CmdPath, CmdStatus}, + msg, msg::InvReq, }, }, tlv::{self, FromTLV, TLVArray}, }; -use crate::common::{echo_cluster, im_engine::im_engine}; - -enum ExpectedInvResp { - Cmd(CmdPath, u8), - Status(CmdStatus), -} - // Helper for handling Invoke Command sequences fn handle_commands(input: &[CmdData], expected: &[ExpectedInvResp]) { let mut out_buf = [0u8; 400]; @@ -47,71 +46,8 @@ fn handle_commands(input: &[CmdData], expected: &[ExpectedInvResp]) { let (_, _, out_buf) = im_engine(OpCode::InvokeRequest, &req, &mut out_buf); tlv::print_tlv_list(out_buf); let root = tlv::get_root_node_struct(out_buf).unwrap(); - let resp = msg::InvResp::from_tlv(&root).unwrap(); - let mut index = 0; - for inv_response in resp.inv_responses.unwrap().iter() { - println!("Validating index {}", index); - match expected[index] { - ExpectedInvResp::Cmd(e_c, e_d) => match inv_response { - InvResp::Cmd(c) => { - assert_eq!(e_c, c.path); - match c.data { - EncodeValue::Tlv(t) => { - assert_eq!(e_d, t.find_tag(0).unwrap().u8().unwrap()) - } - _ => panic!("Incorrect CmdDataType"), - } - } - _ => { - panic!("Invalid response, expected InvResponse::Cmd"); - } - }, - ExpectedInvResp::Status(e_status) => match inv_response { - InvResp::Status(status) => { - assert_eq!(e_status, status); - } - _ => { - panic!("Invalid response, expected InvResponse::Status"); - } - }, - } - println!("Index {} success", index); - index += 1; - } - assert_eq!(index, expected.len()); -} - -macro_rules! cmd_data { - ($path:ident, $data:literal) => { - CmdData::new($path, EncodeValue::Value(&($data as u32))) - }; -} - -macro_rules! echo_req { - ($endpoint:literal, $data:literal) => { - CmdData::new( - CmdPath::new( - Some($endpoint), - Some(echo_cluster::ID), - Some(echo_cluster::Commands::EchoReq as u16), - ), - EncodeValue::Value(&($data as u32)), - ) - }; -} - -macro_rules! echo_resp { - ($endpoint:literal, $data:literal) => { - ExpectedInvResp::Cmd( - CmdPath::new( - Some($endpoint), - Some(echo_cluster::ID), - Some(echo_cluster::Commands::EchoResp as u16), - ), - $data, - ) - }; + assert_inv_response(&resp, expected) } #[test] diff --git a/matter/tests/data_model/timed_requests.rs b/matter/tests/data_model/timed_requests.rs index 09f5fec..75af2e4 100644 --- a/matter/tests/data_model/timed_requests.rs +++ b/matter/tests/data_model/timed_requests.rs @@ -25,21 +25,56 @@ use matter::{ }, interaction_model::{ core::{IMStatusCode, OpCode}, - messages::GenericPath, + messages::{ib::CmdData, ib::CmdPath, msg::InvReq, GenericPath}, messages::{ ib::{AttrData, AttrPath, AttrStatus}, - msg::{StatusResp, TimedReq, WriteReq, WriteResp}, + msg::{self, StatusResp, TimedReq, WriteReq, WriteResp}, }, }, - tlv::{self, FromTLV, TLVWriter}, + tlv::{self, FromTLV, TLVArray, TLVWriter, ToTLV}, transport::exchange::{self, Exchange}, }; -use crate::common::{ - echo_cluster, - im_engine::{ImEngine, ImInput}, +use crate::{ + common::{ + commands::*, + echo_cluster, + im_engine::{ImEngine, ImInput}, + }, + echo_req, echo_resp, }; +fn handle_timed_reqs<'a>( + opcode: OpCode, + request: &dyn ToTLV, + timeout: u16, + delay: u16, + output: &'a mut [u8], +) -> (u8, DataModel, &'a [u8]) { + 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)); + + if timeout != 0 { + // Send Timed Req + let mut tmp_buf = [0u8; 400]; + let timed_req = TimedReq { timeout }; + let im_input = ImInput::new(OpCode::TimedRequest, &timed_req); + let (_, out_buf) = im_engine.process(&im_input, &mut tmp_buf); + tlv::print_tlv_list(out_buf); + } else { + println!("Skipping timed request"); + } + + // Process any delays + let delay = time::Duration::from_millis(delay.into()); + thread::sleep(delay); + + // Send Write Req + let input = ImInput::new(opcode, request); + let (resp_opcode, output) = im_engine.process(&input, output); + (resp_opcode, im_engine.dm, output) +} enum WriteResponse<'a> { TransactionError, TransactionSuccess(&'a [AttrStatus]), @@ -52,27 +87,16 @@ fn handle_timed_write_reqs( timeout: u16, delay: u16, ) -> DataModel { - 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)); - - // Send Timed Req - let mut out_buf = [0u8; 400]; - let timed_req = TimedReq { timeout }; - let im_input = ImInput::new(OpCode::TimedRequest, &timed_req); - let (_, out_buf) = im_engine.process(&im_input, &mut out_buf); - tlv::print_tlv_list(out_buf); - - // Process any delays - let delay = time::Duration::from_millis(delay.into()); - thread::sleep(delay); - - // Send Write Req let mut out_buf = [0u8; 400]; let write_req = WriteReq::new(false, input); - let input = ImInput::new(OpCode::WriteRequest, &write_req); - let (resp_opcode, out_buf) = im_engine.process(&input, &mut out_buf); + let (resp_opcode, dm, out_buf) = handle_timed_reqs( + OpCode::WriteRequest, + &write_req, + timeout, + delay, + &mut out_buf, + ); tlv::print_tlv_list(out_buf); let root = tlv::get_root_node_struct(out_buf).unwrap(); @@ -94,7 +118,7 @@ fn handle_timed_write_reqs( assert_eq!(status_resp.status, IMStatusCode::Timeout); } } - im_engine.dm + dm } #[test] @@ -158,3 +182,103 @@ fn test_timed_write_fail_and_success() { .unwrap() ); } + +enum TimedInvResponse<'a> { + TransactionError(IMStatusCode), + TransactionSuccess(&'a [ExpectedInvResp]), +} +// Helper for handling Invoke Command sequences +fn handle_timed_commands( + input: &[CmdData], + expected: TimedInvResponse, + timeout: u16, + delay: u16, + set_timed_request: bool, +) -> DataModel { + let mut out_buf = [0u8; 400]; + let req = InvReq { + suppress_response: Some(false), + timed_request: Some(set_timed_request), + inv_requests: Some(TLVArray::Slice(input)), + }; + + let (resp_opcode, dm, out_buf) = + handle_timed_reqs(OpCode::InvokeRequest, &req, timeout, delay, &mut out_buf); + tlv::print_tlv_list(out_buf); + let root = tlv::get_root_node_struct(out_buf).unwrap(); + + match expected { + TimedInvResponse::TransactionSuccess(t) => { + assert_eq!( + num::FromPrimitive::from_u8(resp_opcode), + Some(OpCode::InvokeResponse) + ); + let resp = msg::InvResp::from_tlv(&root).unwrap(); + assert_inv_response(&resp, t) + } + TimedInvResponse::TransactionError(e) => { + assert_eq!( + num::FromPrimitive::from_u8(resp_opcode), + Some(OpCode::StatusResponse) + ); + let status_resp = StatusResp::from_tlv(&root).unwrap(); + assert_eq!(status_resp.status, e); + } + } + dm +} + +#[test] +fn test_timed_cmd_success() { + // A timed request that works + let _ = env_logger::try_init(); + + let input = &[echo_req!(0, 5), echo_req!(1, 10)]; + let expected = &[echo_resp!(0, 10), echo_resp!(1, 30)]; + handle_timed_commands( + input, + TimedInvResponse::TransactionSuccess(expected), + 400, + 0, + true, + ); +} + +#[test] +fn test_timed_cmd_timeout() { + // A timed request that is executed after t imeout + let _ = env_logger::try_init(); + + let input = &[echo_req!(0, 5), echo_req!(1, 10)]; + handle_timed_commands( + input, + TimedInvResponse::TransactionError(IMStatusCode::Timeout), + 400, + 500, + true, + ); +} + +#[test] +fn test_timed_cmd_timedout_mismatch() { + // A timed request with timeout mismatch + let _ = env_logger::try_init(); + + let input = &[echo_req!(0, 5), echo_req!(1, 10)]; + handle_timed_commands( + input, + TimedInvResponse::TransactionError(IMStatusCode::TimedRequestMisMatch), + 400, + 0, + false, + ); + + let input = &[echo_req!(0, 5), echo_req!(1, 10)]; + handle_timed_commands( + input, + TimedInvResponse::TransactionError(IMStatusCode::TimedRequestMisMatch), + 0, + 0, + true, + ); +}