Merge pull request #4 from project-chip/feature/timed_request_tests
Add tests for timed requests
This commit is contained in:
commit
a4915bb0d6
13 changed files with 537 additions and 264 deletions
|
@ -44,7 +44,7 @@ use super::{messages::msg::TimedReq, InteractionConsumer};
|
||||||
/* Interaction Model ID as per the Matter Spec */
|
/* Interaction Model ID as per the Matter Spec */
|
||||||
const PROTO_ID_INTERACTION_MODEL: usize = 0x01;
|
const PROTO_ID_INTERACTION_MODEL: usize = 0x01;
|
||||||
|
|
||||||
#[derive(FromPrimitive, Debug, Copy, Clone)]
|
#[derive(FromPrimitive, Debug, Copy, Clone, PartialEq)]
|
||||||
pub enum OpCode {
|
pub enum OpCode {
|
||||||
Reserved = 0,
|
Reserved = 0,
|
||||||
StatusResponse = 1,
|
StatusResponse = 1,
|
||||||
|
|
|
@ -71,19 +71,25 @@ pub mod msg {
|
||||||
tlv::{FromTLV, TLVArray, TLVElement, TLVWriter, TagType, ToTLV},
|
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)]
|
#[derive(FromTLV, ToTLV)]
|
||||||
pub struct TimedReq {
|
pub struct TimedReq {
|
||||||
pub timeout: u16,
|
pub timeout: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(ToTLV)]
|
#[derive(FromTLV, ToTLV)]
|
||||||
pub struct StatusResp {
|
pub struct StatusResp {
|
||||||
pub status: IMStatusCode,
|
pub status: IMStatusCode,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(FromTLV)]
|
pub enum InvReqTag {
|
||||||
|
SupressResponse = 0,
|
||||||
|
TimedReq = 1,
|
||||||
|
InvokeRequests = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromTLV, ToTLV)]
|
||||||
#[tlvargs(lifetime = "'a")]
|
#[tlvargs(lifetime = "'a")]
|
||||||
pub struct InvReq<'a> {
|
pub struct InvReq<'a> {
|
||||||
pub suppress_response: Option<bool>,
|
pub suppress_response: Option<bool>,
|
||||||
|
@ -91,15 +97,18 @@ pub mod msg {
|
||||||
pub inv_requests: Option<TLVArray<'a, CmdData<'a>>>,
|
pub inv_requests: Option<TLVArray<'a, CmdData<'a>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This enum is helpful when we are constructing the response
|
||||||
|
// step by step in incremental manner
|
||||||
pub enum InvRespTag {
|
pub enum InvRespTag {
|
||||||
SupressResponse = 0,
|
SupressResponse = 0,
|
||||||
InvokeResponses = 1,
|
InvokeResponses = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum InvReqTag {
|
#[derive(FromTLV, ToTLV, Debug)]
|
||||||
SupressResponse = 0,
|
#[tlvargs(lifetime = "'a")]
|
||||||
TimedReq = 1,
|
pub struct InvResp<'a> {
|
||||||
InvokeRequests = 2,
|
pub suppress_response: Option<bool>,
|
||||||
|
pub inv_responses: Option<TLVArray<'a, ib::InvResp<'a>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, ToTLV, FromTLV)]
|
#[derive(Default, ToTLV, FromTLV)]
|
||||||
|
@ -171,6 +180,12 @@ pub mod msg {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write Response
|
// Write Response
|
||||||
|
#[derive(ToTLV, FromTLV)]
|
||||||
|
#[tlvargs(lifetime = "'a")]
|
||||||
|
pub struct WriteResp<'a> {
|
||||||
|
pub write_responses: TLVArray<'a, AttrStatus>,
|
||||||
|
}
|
||||||
|
|
||||||
pub enum WriteRespTag {
|
pub enum WriteRespTag {
|
||||||
WriteResponses = 0,
|
WriteResponses = 0,
|
||||||
}
|
}
|
||||||
|
@ -190,7 +205,7 @@ pub mod ib {
|
||||||
use super::GenericPath;
|
use super::GenericPath;
|
||||||
|
|
||||||
// Command Response
|
// Command Response
|
||||||
#[derive(Clone, Copy, FromTLV, ToTLV)]
|
#[derive(Clone, Copy, FromTLV, ToTLV, Debug)]
|
||||||
#[tlvargs(lifetime = "'a")]
|
#[tlvargs(lifetime = "'a")]
|
||||||
pub enum InvResp<'a> {
|
pub enum InvResp<'a> {
|
||||||
Cmd(CmdData<'a>),
|
Cmd(CmdData<'a>),
|
||||||
|
|
|
@ -19,6 +19,7 @@ use super::{ElementType, TLVContainerIterator, TLVElement, TLVWriter, TagType};
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use core::slice::Iter;
|
use core::slice::Iter;
|
||||||
use log::error;
|
use log::error;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
pub trait FromTLV<'a> {
|
pub trait FromTLV<'a> {
|
||||||
fn from_tlv(t: &TLVElement<'a>) -> Result<Self, Error>
|
fn from_tlv(t: &TLVElement<'a>) -> Result<Self, Error>
|
||||||
|
@ -282,6 +283,7 @@ impl<T> TLVArrayOwned<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
pub enum TLVArray<'a, T> {
|
pub enum TLVArray<'a, T> {
|
||||||
// This is used for the to-tlv path
|
// This is used for the to-tlv path
|
||||||
Slice(&'a [T]),
|
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> {
|
impl<'a, T: ToTLV> ToTLV for TLVArray<'a, T> {
|
||||||
fn to_tlv(&self, tw: &mut TLVWriter, tag_type: TagType) -> Result<(), Error> {
|
fn to_tlv(&self, tw: &mut TLVWriter, tag_type: TagType) -> Result<(), Error> {
|
||||||
match *self {
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{FromTLV, OctetStr, TLVElement, TLVWriter, TagType, ToTLV};
|
use super::{FromTLV, OctetStr, TLVElement, TLVWriter, TagType, ToTLV};
|
||||||
|
|
|
@ -21,6 +21,7 @@ use matter::interaction_model::{messages::ib::AttrResp, messages::msg::ReportDat
|
||||||
pub fn assert_attr_report(received: &ReportDataMsg, expected: &[AttrResp]) {
|
pub fn assert_attr_report(received: &ReportDataMsg, expected: &[AttrResp]) {
|
||||||
let mut index = 0;
|
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() {
|
for inv_response in received.attr_reports.as_ref().unwrap().iter() {
|
||||||
println!("Validating index {}", index);
|
println!("Validating index {}", index);
|
||||||
match expected[index] {
|
match expected[index] {
|
||||||
|
@ -34,14 +35,7 @@ pub fn assert_attr_report(received: &ReportDataMsg, expected: &[AttrResp]) {
|
||||||
panic!("Invalid response, expected AttrRespIn::Data");
|
panic!("Invalid response, expected AttrRespIn::Data");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
AttrResp::Status(e_s) => match inv_response {
|
AttrResp::Status(s) => assert_eq!(AttrResp::Status(s), inv_response),
|
||||||
AttrResp::Status(s) => {
|
|
||||||
assert_eq!(e_s, s);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
panic!("Invalid response, expected AttrRespIn::Status");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
println!("Index {} success", index);
|
println!("Index {} success", index);
|
||||||
index += 1;
|
index += 1;
|
||||||
|
|
98
matter/tests/common/commands.rs
Normal file
98
matter/tests/common/commands.rs
Normal file
|
@ -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,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
|
@ -28,7 +28,7 @@ use matter::{
|
||||||
},
|
},
|
||||||
error::Error,
|
error::Error,
|
||||||
fabric::FabricMgr,
|
fabric::FabricMgr,
|
||||||
interaction_model::{core::OpCode, messages::ib::CmdPath, messages::msg, InteractionModel},
|
interaction_model::{core::OpCode, InteractionModel},
|
||||||
tlv::{TLVWriter, TagType, ToTLV},
|
tlv::{TLVWriter, TagType, ToTLV},
|
||||||
transport::packet::Packet,
|
transport::packet::Packet,
|
||||||
transport::proto_demux::HandleProto,
|
transport::proto_demux::HandleProto,
|
||||||
|
@ -58,20 +58,24 @@ pub struct ImEngine {
|
||||||
pub dm: DataModel,
|
pub dm: DataModel,
|
||||||
pub acl_mgr: Arc<AclMgr>,
|
pub acl_mgr: Arc<AclMgr>,
|
||||||
pub im: Box<InteractionModel>,
|
pub im: Box<InteractionModel>,
|
||||||
|
// 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<Exchange>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ImInput<'a> {
|
pub struct ImInput<'a> {
|
||||||
action: OpCode,
|
action: OpCode,
|
||||||
data_in: &'a [u8],
|
data: &'a dyn ToTLV,
|
||||||
peer_id: u64,
|
peer_id: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const IM_ENGINE_PEER_ID: u64 = 445566;
|
pub const IM_ENGINE_PEER_ID: u64 = 445566;
|
||||||
impl<'a> ImInput<'a> {
|
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 {
|
Self {
|
||||||
action,
|
action,
|
||||||
data_in,
|
data,
|
||||||
peer_id: IM_ENGINE_PEER_ID,
|
peer_id: IM_ENGINE_PEER_ID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,12 +115,19 @@ impl ImEngine {
|
||||||
|
|
||||||
let im = Box::new(InteractionModel::new(Box::new(dm.clone())));
|
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
|
/// 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 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();
|
let mut sess_mgr: SessionMgr = Default::default();
|
||||||
|
|
||||||
|
@ -143,59 +154,39 @@ impl ImEngine {
|
||||||
rx.set_proto_id(0x01);
|
rx.set_proto_id(0x01);
|
||||||
rx.set_proto_opcode(input.action as u8);
|
rx.set_proto_opcode(input.action as u8);
|
||||||
rx.peer = Address::default();
|
rx.peer = Address::default();
|
||||||
let in_data_len = input.data_in.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();
|
let rx_buf = rx.as_borrow_slice();
|
||||||
rx_buf[..in_data_len].copy_from_slice(input.data_in);
|
rx_buf[..in_data_len].copy_from_slice(input_data);
|
||||||
rx.get_parsebuf().unwrap().set_len(in_data_len);
|
rx.get_parsebuf().unwrap().set_len(in_data_len);
|
||||||
|
}
|
||||||
|
|
||||||
let mut ctx = ProtoCtx::new(exch_ctx, rx, tx);
|
let mut ctx = ProtoCtx::new(exch_ctx, rx, tx);
|
||||||
self.im.handle_proto_id(&mut ctx).unwrap();
|
self.im.handle_proto_id(&mut ctx).unwrap();
|
||||||
let out_data_len = ctx.tx.as_borrow_slice().len();
|
let out_data_len = ctx.tx.as_borrow_slice().len();
|
||||||
data_out[..out_data_len].copy_from_slice(ctx.tx.as_borrow_slice());
|
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
|
// 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: &dyn ToTLV,
|
||||||
|
data_out: &'a mut [u8],
|
||||||
|
) -> (DataModel, u8, &'a mut [u8]) {
|
||||||
let mut engine = ImEngine::new();
|
let mut engine = ImEngine::new();
|
||||||
let input = ImInput::new(action, data_in);
|
let input = ImInput::new(action, data);
|
||||||
let output_len = engine.process(&input, data_out);
|
let (response, output) = engine.process(&input, data_out);
|
||||||
(engine.dm, output_len)
|
(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<u8>)]) -> 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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,5 +16,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
pub mod attributes;
|
pub mod attributes;
|
||||||
|
pub mod commands;
|
||||||
pub mod echo_cluster;
|
pub mod echo_cluster;
|
||||||
pub mod im_engine;
|
pub mod im_engine;
|
||||||
|
|
|
@ -29,8 +29,7 @@ use matter::{
|
||||||
},
|
},
|
||||||
messages::{msg, GenericPath},
|
messages::{msg, GenericPath},
|
||||||
},
|
},
|
||||||
tlv::{self, ElementType, FromTLV, TLVArray, TLVElement, TLVWriter, TagType, ToTLV},
|
tlv::{self, ElementType, FromTLV, TLVArray, TLVElement, TLVWriter, TagType},
|
||||||
utils::writebuf::WriteBuf,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -61,20 +60,13 @@ fn gen_read_reqs_output<'a>(
|
||||||
dataver_filters: Option<TLVArray<'a, DataVersionFilter>>,
|
dataver_filters: Option<TLVArray<'a, DataVersionFilter>>,
|
||||||
out_buf: &'a mut [u8],
|
out_buf: &'a mut [u8],
|
||||||
) -> ReportDataMsg<'a> {
|
) -> 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);
|
let mut read_req = ReadReq::new(true).set_attr_requests(input);
|
||||||
read_req.dataver_filters = dataver_filters;
|
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);
|
input.set_peer_node_id(peer_node_id);
|
||||||
|
|
||||||
let out_buf_len = im.process(&input, out_buf);
|
let (_, out_buf) = im.process(&input, out_buf);
|
||||||
let out_buf = &out_buf[..out_buf_len];
|
|
||||||
|
|
||||||
tlv::print_tlv_list(out_buf);
|
tlv::print_tlv_list(out_buf);
|
||||||
let root = tlv::get_root_node_struct(out_buf).unwrap();
|
let root = tlv::get_root_node_struct(out_buf).unwrap();
|
||||||
|
@ -88,21 +80,13 @@ fn handle_write_reqs(
|
||||||
input: &[AttrData],
|
input: &[AttrData],
|
||||||
expected: &[AttrStatus],
|
expected: &[AttrStatus],
|
||||||
) {
|
) {
|
||||||
let mut buf = [0u8; 400];
|
|
||||||
let mut out_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);
|
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);
|
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);
|
tlv::print_tlv_list(out_buf);
|
||||||
let root = tlv::get_root_node_struct(out_buf).unwrap();
|
let root = tlv::get_root_node_struct(out_buf).unwrap();
|
||||||
|
|
||||||
|
|
|
@ -19,14 +19,13 @@ use matter::{
|
||||||
data_model::{core::DataModel, objects::EncodeValue},
|
data_model::{core::DataModel, objects::EncodeValue},
|
||||||
interaction_model::{
|
interaction_model::{
|
||||||
core::{IMStatusCode, OpCode},
|
core::{IMStatusCode, OpCode},
|
||||||
|
messages::GenericPath,
|
||||||
messages::{
|
messages::{
|
||||||
ib::{AttrData, AttrPath, AttrStatus},
|
ib::{AttrData, AttrPath, AttrStatus},
|
||||||
msg::WriteReq,
|
msg::{WriteReq, WriteResp},
|
||||||
},
|
},
|
||||||
messages::{msg, GenericPath},
|
|
||||||
},
|
},
|
||||||
tlv::{self, FromTLV, Nullable, TLVWriter, TagType, ToTLV},
|
tlv::{self, FromTLV, Nullable},
|
||||||
utils::writebuf::WriteBuf,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::common::{
|
use crate::common::{
|
||||||
|
@ -36,37 +35,14 @@ use crate::common::{
|
||||||
|
|
||||||
// Helper for handling Write Attribute sequences
|
// Helper for handling Write Attribute sequences
|
||||||
fn handle_write_reqs(input: &[AttrData], expected: &[AttrStatus]) -> DataModel {
|
fn handle_write_reqs(input: &[AttrData], expected: &[AttrStatus]) -> DataModel {
|
||||||
let mut buf = [0u8; 400];
|
|
||||||
let mut out_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);
|
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 (dm, _, out_buf) = im_engine(OpCode::WriteRequest, &write_req, &mut out_buf);
|
||||||
let out_buf = &out_buf[..out_buf_len];
|
|
||||||
tlv::print_tlv_list(out_buf);
|
tlv::print_tlv_list(out_buf);
|
||||||
let root = tlv::get_root_node_struct(out_buf).unwrap();
|
let root = tlv::get_root_node_struct(out_buf).unwrap();
|
||||||
|
let resp = WriteResp::from_tlv(&root).unwrap();
|
||||||
let mut index = 0;
|
assert_eq!(resp.write_responses, expected);
|
||||||
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
|
dm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,13 +23,13 @@ use matter::{
|
||||||
},
|
},
|
||||||
interaction_model::{
|
interaction_model::{
|
||||||
core::{IMStatusCode, OpCode},
|
core::{IMStatusCode, OpCode},
|
||||||
|
messages::GenericPath,
|
||||||
messages::{
|
messages::{
|
||||||
ib::{AttrData, AttrPath, AttrResp, AttrStatus},
|
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},
|
tlv::{self, ElementType, FromTLV, TLVElement, TLVList, TLVWriter, TagType},
|
||||||
utils::writebuf::WriteBuf,
|
utils::writebuf::WriteBuf,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -46,16 +46,8 @@ fn handle_read_reqs(input: &[AttrPath], expected: &[AttrResp]) {
|
||||||
|
|
||||||
// Helper for handling Read Req sequences
|
// Helper for handling Read Req sequences
|
||||||
fn gen_read_reqs_output<'a>(input: &[AttrPath], out_buf: &'a mut [u8]) -> ReportDataMsg<'a> {
|
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);
|
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, &read_req, out_buf);
|
||||||
|
|
||||||
let (_, out_buf_len) = im_engine(OpCode::ReadRequest, wb.as_borrow_slice(), out_buf);
|
|
||||||
let out_buf = &out_buf[..out_buf_len];
|
|
||||||
tlv::print_tlv_list(out_buf);
|
tlv::print_tlv_list(out_buf);
|
||||||
let root = tlv::get_root_node_struct(out_buf).unwrap();
|
let root = tlv::get_root_node_struct(out_buf).unwrap();
|
||||||
ReportDataMsg::from_tlv(&root).unwrap()
|
ReportDataMsg::from_tlv(&root).unwrap()
|
||||||
|
@ -63,37 +55,14 @@ fn gen_read_reqs_output<'a>(input: &[AttrPath], out_buf: &'a mut [u8]) -> Report
|
||||||
|
|
||||||
// Helper for handling Write Attribute sequences
|
// Helper for handling Write Attribute sequences
|
||||||
fn handle_write_reqs(input: &[AttrData], expected: &[AttrStatus]) -> DataModel {
|
fn handle_write_reqs(input: &[AttrData], expected: &[AttrStatus]) -> DataModel {
|
||||||
let mut buf = [0u8; 400];
|
|
||||||
let mut out_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);
|
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 (dm, _, out_buf) = im_engine(OpCode::WriteRequest, &write_req, &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();
|
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
|
dm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,108 +15,39 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
cmd_data,
|
||||||
|
common::{commands::*, echo_cluster, im_engine::im_engine},
|
||||||
|
echo_req, echo_resp,
|
||||||
|
};
|
||||||
|
|
||||||
use matter::{
|
use matter::{
|
||||||
data_model::{cluster_on_off, objects::EncodeValue},
|
data_model::{cluster_on_off, objects::EncodeValue},
|
||||||
interaction_model::{
|
interaction_model::{
|
||||||
core::{IMStatusCode, OpCode},
|
core::{IMStatusCode, OpCode},
|
||||||
messages::ib::{CmdPath, CmdStatus, InvResp},
|
messages::{
|
||||||
messages::msg,
|
ib::{CmdData, CmdPath, CmdStatus},
|
||||||
|
msg,
|
||||||
|
msg::InvReq,
|
||||||
},
|
},
|
||||||
tlv::{self, FromTLV},
|
},
|
||||||
utils::writebuf::WriteBuf,
|
tlv::{self, FromTLV, TLVArray},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::common::{
|
|
||||||
echo_cluster,
|
|
||||||
im_engine::{im_engine, TestData},
|
|
||||||
};
|
|
||||||
|
|
||||||
enum ExpectedInvResp {
|
|
||||||
Cmd(CmdPath, u8),
|
|
||||||
Status(CmdStatus),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper for handling Invoke Command sequences
|
// Helper for handling Invoke Command sequences
|
||||||
fn handle_commands(input: &[(CmdPath, Option<u8>)], expected: &[ExpectedInvResp]) {
|
fn handle_commands(input: &[CmdData], expected: &[ExpectedInvResp]) {
|
||||||
let mut buf = [0u8; 400];
|
|
||||||
let mut out_buf = [0u8; 400];
|
let mut out_buf = [0u8; 400];
|
||||||
|
let req = InvReq {
|
||||||
|
suppress_response: Some(false),
|
||||||
|
timed_request: Some(false),
|
||||||
|
inv_requests: Some(TLVArray::Slice(input)),
|
||||||
|
};
|
||||||
|
|
||||||
let buf_len = buf.len();
|
let (_, _, out_buf) = im_engine(OpCode::InvokeRequest, &req, &mut out_buf);
|
||||||
let mut wb = WriteBuf::new(&mut buf, buf_len);
|
|
||||||
let mut td = TestData::new(&mut wb);
|
|
||||||
|
|
||||||
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];
|
|
||||||
tlv::print_tlv_list(out_buf);
|
tlv::print_tlv_list(out_buf);
|
||||||
let root = tlv::get_root_node_struct(out_buf).unwrap();
|
let root = tlv::get_root_node_struct(out_buf).unwrap();
|
||||||
|
let resp = msg::InvResp::from_tlv(&root).unwrap();
|
||||||
let mut index = 0;
|
assert_inv_response(&resp, expected)
|
||||||
let cmd_list_iter = root
|
|
||||||
.find_tag(msg::InvRespTag::InvokeResponses as u32)
|
|
||||||
.unwrap()
|
|
||||||
.confirm_array()
|
|
||||||
.unwrap()
|
|
||||||
.enter()
|
|
||||||
.unwrap();
|
|
||||||
for response in cmd_list_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) => {
|
|
||||||
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! echo_req {
|
|
||||||
($endpoint:literal, $data:literal) => {
|
|
||||||
(
|
|
||||||
CmdPath::new(
|
|
||||||
Some($endpoint),
|
|
||||||
Some(echo_cluster::ID),
|
|
||||||
Some(echo_cluster::Commands::EchoReq as u16),
|
|
||||||
),
|
|
||||||
Some($data),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -159,11 +90,11 @@ fn test_invoke_cmds_unsupported_fields() {
|
||||||
let invalid_command = CmdPath::new(Some(0), Some(echo_cluster::ID), Some(0x1234));
|
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 invalid_command_wc_endpoint = CmdPath::new(None, Some(echo_cluster::ID), Some(0x1234));
|
||||||
let input = &[
|
let input = &[
|
||||||
(invalid_endpoint, Some(5)),
|
cmd_data!(invalid_endpoint, 5),
|
||||||
(invalid_cluster, Some(5)),
|
cmd_data!(invalid_cluster, 5),
|
||||||
(invalid_cluster_wc_endpoint, Some(5)),
|
cmd_data!(invalid_cluster_wc_endpoint, 5),
|
||||||
(invalid_command, Some(5)),
|
cmd_data!(invalid_command, 5),
|
||||||
(invalid_command_wc_endpoint, Some(5)),
|
cmd_data!(invalid_command_wc_endpoint, 5),
|
||||||
];
|
];
|
||||||
|
|
||||||
let expected = &[
|
let expected = &[
|
||||||
|
@ -191,15 +122,12 @@ fn test_invoke_cmd_wc_endpoint_all_have_clusters() {
|
||||||
// 1 echo Request with wildcard endpoint
|
// 1 echo Request with wildcard endpoint
|
||||||
// should generate 2 responses from the echo clusters on both
|
// should generate 2 responses from the echo clusters on both
|
||||||
let _ = env_logger::try_init();
|
let _ = env_logger::try_init();
|
||||||
|
let path = CmdPath::new(
|
||||||
let input = &[(
|
|
||||||
CmdPath::new(
|
|
||||||
None,
|
None,
|
||||||
Some(echo_cluster::ID),
|
Some(echo_cluster::ID),
|
||||||
Some(echo_cluster::Commands::EchoReq as u16),
|
Some(echo_cluster::Commands::EchoReq as u16),
|
||||||
),
|
);
|
||||||
Some(5),
|
let input = &[cmd_data!(path, 5)];
|
||||||
)];
|
|
||||||
let expected = &[echo_resp!(0, 10), echo_resp!(1, 15)];
|
let expected = &[echo_resp!(0, 10), echo_resp!(1, 15)];
|
||||||
handle_commands(input, expected);
|
handle_commands(input, expected);
|
||||||
}
|
}
|
||||||
|
@ -220,7 +148,7 @@ fn test_invoke_cmd_wc_endpoint_only_1_has_cluster() {
|
||||||
Some(cluster_on_off::ID),
|
Some(cluster_on_off::ID),
|
||||||
Some(cluster_on_off::Commands::On as u16),
|
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(
|
let expected = &[ExpectedInvResp::Status(CmdStatus::new(
|
||||||
expected_path,
|
expected_path,
|
||||||
IMStatusCode::Sucess,
|
IMStatusCode::Sucess,
|
||||||
|
|
284
matter/tests/data_model/timed_requests.rs
Normal file
284
matter/tests/data_model/timed_requests.rs
Normal file
|
@ -0,0 +1,284 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* 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::CmdData, ib::CmdPath, msg::InvReq, GenericPath},
|
||||||
|
messages::{
|
||||||
|
ib::{AttrData, AttrPath, AttrStatus},
|
||||||
|
msg::{self, StatusResp, TimedReq, WriteReq, WriteResp},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tlv::{self, FromTLV, TLVArray, TLVWriter, ToTLV},
|
||||||
|
transport::exchange::{self, Exchange},
|
||||||
|
};
|
||||||
|
|
||||||
|
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]),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper for handling Write Attribute sequences
|
||||||
|
fn handle_timed_write_reqs(
|
||||||
|
input: &[AttrData],
|
||||||
|
expected: WriteResponse,
|
||||||
|
timeout: u16,
|
||||||
|
delay: u16,
|
||||||
|
) -> DataModel {
|
||||||
|
let mut out_buf = [0u8; 400];
|
||||||
|
let write_req = WriteReq::new(false, input);
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
match expected {
|
||||||
|
WriteResponse::TransactionSuccess(t) => {
|
||||||
|
assert_eq!(
|
||||||
|
num::FromPrimitive::from_u8(resp_opcode),
|
||||||
|
Some(OpCode::WriteResponse)
|
||||||
|
);
|
||||||
|
let resp = WriteResp::from_tlv(&root).unwrap();
|
||||||
|
assert_eq!(resp.write_responses, t);
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
|
@ -22,4 +22,5 @@ mod data_model {
|
||||||
mod attribute_lists;
|
mod attribute_lists;
|
||||||
mod attributes;
|
mod attributes;
|
||||||
mod commands;
|
mod commands;
|
||||||
|
mod timed_requests;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue