/* * * 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; WRITE_LIST_MAX], } static mut G_TEST_CHECKER: Option>> = 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>, 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, 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(()) } } } }