250 lines
7.8 KiB
Rust
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(num::FromPrimitive::from_u32)
|
|
.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(())
|
|
}
|
|
}
|
|
}
|
|
}
|