From 91b451db0649a454873a98be4db241b46b125c0a Mon Sep 17 00:00:00 2001 From: Kedar Sovani Date: Sat, 7 Jan 2023 13:31:28 +0530 Subject: [PATCH] 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, + ); +}