rs-matter/rs-matter/tests/common/im_engine.rs
2023-08-09 12:14:55 +05:30

406 lines
12 KiB
Rust

/*
*
* Copyright (c) 2020-2022 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use crate::common::echo_cluster;
use core::borrow::Borrow;
use core::future::pending;
use core::time::Duration;
use embassy_futures::select::select3;
use rs_matter::{
acl::{AclEntry, AuthMode},
data_model::{
cluster_basic_information::{self, BasicInfoConfig},
cluster_on_off::{self, OnOffCluster},
device_types::{DEV_TYPE_ON_OFF_LIGHT, DEV_TYPE_ROOT_NODE},
objects::{
AttrData, AttrDataEncoder, AttrDetails, Endpoint, Handler, HandlerCompat, Metadata,
Node, NonBlockingHandler, Privilege,
},
root_endpoint::{self, RootEndpointHandler},
sdm::{
admin_commissioning,
dev_att::{DataType, DevAttDataFetcher},
general_commissioning, noc, nw_commissioning,
},
system_model::{
access_control,
descriptor::{self, DescriptorCluster},
},
},
error::{Error, ErrorCode},
handler_chain_type,
interaction_model::core::{OpCode, PROTO_ID_INTERACTION_MODEL},
mdns::DummyMdns,
secure_channel::{self, common::PROTO_ID_SECURE_CHANNEL, spake2p::VerifierData},
tlv::{TLVWriter, TagType, ToTLV},
transport::{
core::PacketBuffers,
packet::{Packet, MAX_RX_BUF_SIZE, MAX_TX_BUF_SIZE},
pipe::Pipe,
},
transport::{
network::Address,
session::{CaseDetails, CloneData, NocCatIds, SessionMode},
},
utils::select::{EitherUnwrap, Notification},
CommissioningData, Matter, MATTER_PORT,
};
use super::echo_cluster::EchoCluster;
const BASIC_INFO: BasicInfoConfig<'static> = BasicInfoConfig {
vid: 10,
pid: 11,
hw_ver: 12,
sw_ver: 13,
sw_ver_str: "13",
serial_no: "aabbccdd",
device_name: "Test Device",
product_name: "TestProd",
vendor_name: "TestVendor",
};
struct DummyDevAtt;
impl DevAttDataFetcher for DummyDevAtt {
fn get_devatt_data(&self, _data_type: DataType, _data: &mut [u8]) -> Result<usize, Error> {
Ok(2)
}
}
pub const IM_ENGINE_PEER_ID: u64 = 445566;
pub const IM_ENGINE_REMOTE_PEER_ID: u64 = 123456;
const NODE: Node<'static> = Node {
id: 0,
endpoints: &[
Endpoint {
id: 0,
clusters: &[
descriptor::CLUSTER,
cluster_basic_information::CLUSTER,
general_commissioning::CLUSTER,
nw_commissioning::CLUSTER,
admin_commissioning::CLUSTER,
noc::CLUSTER,
access_control::CLUSTER,
echo_cluster::CLUSTER,
],
device_type: DEV_TYPE_ROOT_NODE,
},
Endpoint {
id: 1,
clusters: &[
descriptor::CLUSTER,
cluster_on_off::CLUSTER,
echo_cluster::CLUSTER,
],
device_type: DEV_TYPE_ON_OFF_LIGHT,
},
],
};
pub struct ImInput<'a> {
action: OpCode,
data: &'a dyn ToTLV,
delay: Option<u16>,
}
impl<'a> ImInput<'a> {
pub fn new(action: OpCode, data: &'a dyn ToTLV) -> Self {
Self::new_delayed(action, data, None)
}
pub fn new_delayed(action: OpCode, data: &'a dyn ToTLV, delay: Option<u16>) -> Self {
Self {
action,
data,
delay,
}
}
}
pub struct ImOutput {
pub action: OpCode,
pub data: heapless::Vec<u8, MAX_TX_BUF_SIZE>,
}
pub struct ImEngineHandler<'a> {
handler: handler_chain_type!(OnOffCluster, EchoCluster, DescriptorCluster<'static>, EchoCluster | RootEndpointHandler<'a>),
}
impl<'a> ImEngineHandler<'a> {
pub fn new(matter: &'a Matter<'a>) -> Self {
let handler = root_endpoint::handler(0, matter)
.chain(0, echo_cluster::ID, EchoCluster::new(2, *matter.borrow()))
.chain(1, descriptor::ID, DescriptorCluster::new(*matter.borrow()))
.chain(1, echo_cluster::ID, EchoCluster::new(3, *matter.borrow()))
.chain(1, cluster_on_off::ID, OnOffCluster::new(*matter.borrow()));
Self { handler }
}
pub fn echo_cluster(&self, endpoint: u16) -> &EchoCluster {
match endpoint {
0 => &self.handler.next.next.next.handler,
1 => &self.handler.next.handler,
_ => panic!(),
}
}
}
impl<'a> Handler for ImEngineHandler<'a> {
fn read(&self, attr: &AttrDetails, encoder: AttrDataEncoder) -> Result<(), Error> {
self.handler.read(attr, encoder)
}
fn write(&self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
self.handler.write(attr, data)
}
fn invoke(
&self,
exchange: &rs_matter::transport::exchange::Exchange,
cmd: &rs_matter::data_model::objects::CmdDetails,
data: &rs_matter::tlv::TLVElement,
encoder: rs_matter::data_model::objects::CmdDataEncoder,
) -> Result<(), Error> {
self.handler.invoke(exchange, cmd, data, encoder)
}
}
impl<'a> NonBlockingHandler for ImEngineHandler<'a> {}
impl<'a> Metadata for ImEngineHandler<'a> {
type MetadataGuard<'g> = Node<'g> where Self: 'g;
fn lock(&self) -> Self::MetadataGuard<'_> {
NODE
}
}
static mut DNS: DummyMdns = DummyMdns;
/// An Interaction Model Engine to facilitate easy testing
pub struct ImEngine<'a> {
pub matter: Matter<'a>,
cat_ids: NocCatIds,
}
impl<'a> ImEngine<'a> {
pub fn new_default() -> Self {
Self::new(Default::default())
}
/// Create the interaction model engine
pub fn new(cat_ids: NocCatIds) -> Self {
#[cfg(feature = "std")]
use rs_matter::utils::epoch::sys_epoch as epoch;
#[cfg(not(feature = "std"))]
use rs_matter::utils::epoch::dummy_epoch as epoch;
#[cfg(feature = "std")]
use rs_matter::utils::rand::sys_rand as rand;
#[cfg(not(feature = "std"))]
use rs_matter::utils::rand::dummy_rand as rand;
let matter = Matter::new(
&BASIC_INFO,
&DummyDevAtt,
unsafe { &mut DNS },
epoch,
rand,
MATTER_PORT,
);
Self { matter, cat_ids }
}
pub fn add_default_acl(&self) {
// Only allow the standard peer node id of the IM Engine
let mut default_acl = AclEntry::new(1, Privilege::ADMIN, AuthMode::Case);
default_acl.add_subject(IM_ENGINE_PEER_ID).unwrap();
self.matter.acl_mgr.borrow_mut().add(default_acl).unwrap();
}
pub fn handler(&self) -> ImEngineHandler<'_> {
ImEngineHandler::new(&self.matter)
}
pub fn process<const N: usize>(
&self,
handler: &ImEngineHandler,
input: &[&ImInput],
out: &mut heapless::Vec<ImOutput, N>,
) -> Result<(), Error> {
self.matter.reset_transport();
let clone_data = CloneData::new(
IM_ENGINE_REMOTE_PEER_ID,
IM_ENGINE_PEER_ID,
1,
1,
Address::default(),
SessionMode::Case(CaseDetails::new(1, &self.cat_ids)),
);
let sess_idx = self
.matter
.session_mgr
.borrow_mut()
.clone_session(&clone_data)
.unwrap();
let mut tx_pipe_buf = [0; MAX_RX_BUF_SIZE];
let mut rx_pipe_buf = [0; MAX_TX_BUF_SIZE];
let mut tx_buf = [0; MAX_RX_BUF_SIZE];
let mut rx_buf = [0; MAX_TX_BUF_SIZE];
let tx_pipe = Pipe::new(&mut tx_buf);
let rx_pipe = Pipe::new(&mut rx_buf);
let tx_pipe = &tx_pipe;
let rx_pipe = &rx_pipe;
let tx_pipe_buf = &mut tx_pipe_buf;
let rx_pipe_buf = &mut rx_pipe_buf;
let handler = &handler;
let mut msg_ctr = self
.matter
.session_mgr
.borrow_mut()
.mut_by_index(sess_idx)
.unwrap()
.get_msg_ctr();
let resp_notif = Notification::new();
let resp_notif = &resp_notif;
let mut buffers = PacketBuffers::new();
let buffers = &mut buffers;
embassy_futures::block_on(async move {
select3(
self.matter.run_piped(
buffers,
tx_pipe,
rx_pipe,
CommissioningData {
// TODO: Hard-coded for now
verifier: VerifierData::new_with_pw(123456, *self.matter.borrow()),
discriminator: 250,
},
&HandlerCompat(handler),
),
async move {
let mut acknowledge = false;
for ip in input {
Self::send(ip, tx_pipe_buf, rx_pipe, msg_ctr, acknowledge).await?;
resp_notif.wait().await;
if let Some(delay) = ip.delay {
if delay > 0 {
#[cfg(feature = "std")]
std::thread::sleep(Duration::from_millis(delay as _));
}
}
msg_ctr += 2;
acknowledge = true;
}
pending::<()>().await;
Ok(())
},
async move {
out.clear();
while out.len() < input.len() {
let (len, _) = tx_pipe.recv(rx_pipe_buf).await;
let mut rx = Packet::new_rx(&mut rx_pipe_buf[..len]);
rx.plain_hdr_decode()?;
rx.proto_decode(IM_ENGINE_REMOTE_PEER_ID, Some(&[0u8; 16]))?;
if rx.get_proto_id() != PROTO_ID_SECURE_CHANNEL
|| rx.get_proto_opcode::<secure_channel::common::OpCode>()?
!= secure_channel::common::OpCode::MRPStandAloneAck
{
out.push(ImOutput {
action: rx.get_proto_opcode()?,
data: heapless::Vec::from_slice(rx.as_slice())
.map_err(|_| ErrorCode::NoSpace)?,
})
.map_err(|_| ErrorCode::NoSpace)?;
resp_notif.signal(());
}
}
Ok(())
},
)
.await
.unwrap()
})?;
Ok(())
}
async fn send(
input: &ImInput<'_>,
tx_buf: &mut [u8],
rx_pipe: &Pipe<'_>,
msg_ctr: u32,
acknowledge: bool,
) -> Result<(), Error> {
let mut tx = Packet::new_tx(tx_buf);
tx.set_proto_id(PROTO_ID_INTERACTION_MODEL);
tx.set_proto_opcode(input.action as u8);
let mut tw = TLVWriter::new(tx.get_writebuf()?);
input.data.to_tlv(&mut tw, TagType::Anonymous)?;
tx.plain.ctr = msg_ctr + 1;
tx.plain.sess_id = 1;
tx.proto.set_initiator();
if acknowledge {
tx.proto.set_ack(msg_ctr - 1);
}
tx.proto_encode(
Address::default(),
Some(IM_ENGINE_REMOTE_PEER_ID),
IM_ENGINE_PEER_ID,
false,
Some(&[0u8; 16]),
)?;
rx_pipe.send(Address::default(), tx.as_slice()).await;
Ok(())
}
}