rs-matter/matter/tests/data_model/timed_requests.rs
2023-01-07 13:31:28 +05:30

284 lines
7.9 KiB
Rust

/*
*
* 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,
);
}