rs-matter/matter/tests/common/echo_cluster.rs
2022-12-27 09:52:25 +05:30

250 lines
7.8 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 std::sync::{Arc, Mutex, Once};
use matter::{
data_model::objects::{
Access, AttrDetails, AttrValue, Attribute, Cluster, ClusterType, EncodeValue, Encoder,
Quality,
},
error::Error,
interaction_model::{
command::CommandReq,
core::IMStatusCode,
messages::ib::{self, attr_list_write, ListOperation},
},
tlv::{TLVElement, TLVWriter, TagType, ToTLV},
};
use num_derive::FromPrimitive;
pub const ID: u32 = 0xABCD;
#[derive(FromPrimitive)]
pub enum Commands {
EchoReq = 0x00,
EchoResp = 0x01,
}
/// This is used in the tests to validate any settings that may have happened
/// to the custom data parts of the cluster
pub struct TestChecker {
pub write_list: [Option<u16>; WRITE_LIST_MAX],
}
static mut G_TEST_CHECKER: Option<Arc<Mutex<TestChecker>>> = None;
static INIT: Once = Once::new();
impl TestChecker {
fn new() -> Self {
Self {
write_list: [None; WRITE_LIST_MAX],
}
}
/// Get a handle to the globally unique mDNS instance
pub fn get() -> Result<Arc<Mutex<Self>>, Error> {
unsafe {
INIT.call_once(|| {
G_TEST_CHECKER = Some(Arc::new(Mutex::new(Self::new())));
});
Ok(G_TEST_CHECKER.as_ref().ok_or(Error::Invalid)?.clone())
}
}
}
pub const WRITE_LIST_MAX: usize = 5;
pub struct EchoCluster {
pub base: Cluster,
pub multiplier: u8,
}
#[derive(FromPrimitive)]
pub enum Attributes {
Att1 = 0,
Att2 = 1,
AttWrite = 2,
AttCustom = 3,
AttWriteList = 4,
}
pub const ATTR_CUSTOM_VALUE: u32 = 0xcafebeef;
pub const ATTR_WRITE_DEFAULT_VALUE: u16 = 0xcafe;
impl ClusterType for EchoCluster {
fn base(&self) -> &Cluster {
&self.base
}
fn base_mut(&mut self) -> &mut Cluster {
&mut self.base
}
fn read_custom_attribute(&self, encoder: &mut dyn Encoder, attr: &AttrDetails) {
match num::FromPrimitive::from_u16(attr.attr_id) {
Some(Attributes::AttCustom) => encoder.encode(EncodeValue::Closure(&|tag, tw| {
let _ = tw.u32(tag, ATTR_CUSTOM_VALUE);
})),
Some(Attributes::AttWriteList) => {
let tc_handle = TestChecker::get().unwrap();
let tc = tc_handle.lock().unwrap();
encoder.encode(EncodeValue::Closure(&|tag, tw| {
let _ = tw.start_array(tag);
for i in tc.write_list.iter().flatten() {
let _ = tw.u16(TagType::Anonymous, *i);
}
let _ = tw.end_container();
}))
}
_ => (),
}
}
fn write_attribute(
&mut self,
attr: &AttrDetails,
data: &TLVElement,
) -> Result<(), IMStatusCode> {
match num::FromPrimitive::from_u16(attr.attr_id) {
Some(Attributes::AttWriteList) => {
attr_list_write(attr, data, |op, data| self.write_attr_list(op, data))
}
_ => self.base.write_attribute_from_tlv(attr.attr_id, data),
}
}
fn handle_command(&mut self, cmd_req: &mut CommandReq) -> Result<(), IMStatusCode> {
let cmd = cmd_req
.cmd
.path
.leaf
.map(|c| num::FromPrimitive::from_u32(c))
.ok_or(IMStatusCode::UnsupportedCommand)?
.ok_or(IMStatusCode::UnsupportedCommand)?;
match cmd {
// This will generate an echo response on the same endpoint
// with data multiplied by the multiplier
Commands::EchoReq => {
let a = cmd_req.data.u8().unwrap();
let mut echo_response = cmd_req.cmd;
echo_response.path.leaf = Some(Commands::EchoResp as u32);
let cmd_data = |tag: TagType, t: &mut TLVWriter| {
let _ = t.start_struct(tag);
// Echo = input * self.multiplier
let _ = t.u8(TagType::Context(0), a * self.multiplier);
let _ = t.end_container();
};
let invoke_resp = ib::InvResp::Cmd(ib::CmdData::new(
echo_response,
EncodeValue::Closure(&cmd_data),
));
let _ = invoke_resp.to_tlv(cmd_req.resp, TagType::Anonymous);
cmd_req.trans.complete();
}
_ => {
return Err(IMStatusCode::UnsupportedCommand);
}
}
Ok(())
}
}
impl EchoCluster {
pub fn new(multiplier: u8) -> Result<Box<Self>, Error> {
let mut c = Box::new(Self {
base: Cluster::new(ID)?,
multiplier,
});
c.base.add_attribute(Attribute::new(
Attributes::Att1 as u16,
AttrValue::Uint16(0x1234),
Access::RV,
Quality::NONE,
)?)?;
c.base.add_attribute(Attribute::new(
Attributes::Att2 as u16,
AttrValue::Uint16(0x5678),
Access::RV,
Quality::NONE,
)?)?;
c.base.add_attribute(Attribute::new(
Attributes::AttWrite as u16,
AttrValue::Uint16(ATTR_WRITE_DEFAULT_VALUE),
Access::WRITE | Access::NEED_ADMIN,
Quality::NONE,
)?)?;
c.base.add_attribute(Attribute::new(
Attributes::AttCustom as u16,
AttrValue::Custom,
Access::READ | Access::NEED_VIEW,
Quality::NONE,
)?)?;
c.base.add_attribute(Attribute::new(
Attributes::AttWriteList as u16,
AttrValue::Custom,
Access::WRITE | Access::NEED_ADMIN,
Quality::NONE,
)?)?;
Ok(c)
}
fn write_attr_list(
&mut self,
op: ListOperation,
data: &TLVElement,
) -> Result<(), IMStatusCode> {
let tc_handle = TestChecker::get().unwrap();
let mut tc = tc_handle.lock().unwrap();
match op {
ListOperation::AddItem => {
let data = data.u16().map_err(|_| IMStatusCode::Failure)?;
for i in 0..WRITE_LIST_MAX {
if tc.write_list[i].is_none() {
tc.write_list[i] = Some(data);
return Ok(());
}
}
Err(IMStatusCode::ResourceExhausted)
}
ListOperation::EditItem(index) => {
let data = data.u16().map_err(|_| IMStatusCode::Failure)?;
if tc.write_list[index as usize].is_some() {
tc.write_list[index as usize] = Some(data);
Ok(())
} else {
Err(IMStatusCode::InvalidAction)
}
}
ListOperation::DeleteItem(index) => {
if tc.write_list[index as usize].is_some() {
tc.write_list[index as usize] = None;
Ok(())
} else {
Err(IMStatusCode::InvalidAction)
}
}
ListOperation::DeleteList => {
for i in 0..WRITE_LIST_MAX {
tc.write_list[i] = None;
}
Ok(())
}
}
}
}