288 lines
8.7 KiB
Rust
288 lines
8.7 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::{
|
|
convert::TryInto,
|
|
sync::{Arc, Mutex, Once},
|
|
};
|
|
|
|
use matter::{
|
|
attribute_enum, command_enum,
|
|
data_model::objects::{
|
|
Access, AttrData, AttrDataEncoder, AttrDataWriter, AttrDetails, AttrType, Attribute,
|
|
Cluster, CmdDataEncoder, CmdDataWriter, CmdDetails, Dataver, Handler, Quality,
|
|
ATTRIBUTE_LIST, FEATURE_MAP,
|
|
},
|
|
error::Error,
|
|
interaction_model::{
|
|
core::Transaction,
|
|
messages::ib::{attr_list_write, ListOperation},
|
|
},
|
|
tlv::{TLVElement, TagType},
|
|
utils::rand::Rand,
|
|
};
|
|
use num_derive::FromPrimitive;
|
|
use strum::{EnumDiscriminants, FromRepr};
|
|
|
|
pub const ID: u32 = 0xABCD;
|
|
|
|
#[derive(FromRepr, EnumDiscriminants)]
|
|
#[repr(u16)]
|
|
pub enum Attributes {
|
|
Att1(AttrType<u16>) = 0,
|
|
Att2(AttrType<u16>) = 1,
|
|
AttWrite(AttrType<u16>) = 2,
|
|
AttCustom(AttrType<u32>) = 3,
|
|
AttWriteList(()) = 4,
|
|
}
|
|
|
|
attribute_enum!(Attributes);
|
|
|
|
#[derive(FromRepr)]
|
|
#[repr(u32)]
|
|
pub enum Commands {
|
|
EchoReq = 0x00,
|
|
}
|
|
|
|
command_enum!(Commands);
|
|
|
|
#[derive(FromPrimitive)]
|
|
pub enum RespCommands {
|
|
EchoResp = 0x01,
|
|
}
|
|
|
|
pub const CLUSTER: Cluster<'static> = Cluster {
|
|
id: ID,
|
|
feature_map: 0,
|
|
attributes: &[
|
|
FEATURE_MAP,
|
|
ATTRIBUTE_LIST,
|
|
Attribute::new(
|
|
AttributesDiscriminants::Att1 as u16,
|
|
Access::RV,
|
|
Quality::NONE,
|
|
),
|
|
Attribute::new(
|
|
AttributesDiscriminants::Att2 as u16,
|
|
Access::RV,
|
|
Quality::NONE,
|
|
),
|
|
Attribute::new(
|
|
AttributesDiscriminants::AttWrite as u16,
|
|
Access::WRITE.union(Access::NEED_ADMIN),
|
|
Quality::NONE,
|
|
),
|
|
Attribute::new(
|
|
AttributesDiscriminants::AttCustom as u16,
|
|
Access::READ.union(Access::NEED_VIEW),
|
|
Quality::NONE,
|
|
),
|
|
Attribute::new(
|
|
AttributesDiscriminants::AttWriteList as u16,
|
|
Access::WRITE.union(Access::NEED_ADMIN),
|
|
Quality::NONE,
|
|
),
|
|
],
|
|
commands: &[Commands::EchoReq as _],
|
|
};
|
|
|
|
/// 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 data_ver: Dataver,
|
|
pub multiplier: u8,
|
|
pub att1: u16,
|
|
pub att2: u16,
|
|
pub att_write: u16,
|
|
pub att_custom: u32,
|
|
}
|
|
|
|
impl EchoCluster {
|
|
pub fn new(multiplier: u8, rand: Rand) -> Self {
|
|
Self {
|
|
data_ver: Dataver::new(rand),
|
|
multiplier,
|
|
att1: 0x1234,
|
|
att2: 0x5678,
|
|
att_write: ATTR_WRITE_DEFAULT_VALUE,
|
|
att_custom: ATTR_CUSTOM_VALUE,
|
|
}
|
|
}
|
|
|
|
pub fn read(&self, attr: &AttrDetails, encoder: AttrDataEncoder) -> Result<(), Error> {
|
|
if let Some(mut writer) = encoder.with_dataver(self.data_ver.get())? {
|
|
if attr.is_system() {
|
|
CLUSTER.read(attr.attr_id, writer)
|
|
} else {
|
|
match attr.attr_id.try_into()? {
|
|
Attributes::Att1(codec) => codec.encode(writer, 0x1234),
|
|
Attributes::Att2(codec) => codec.encode(writer, 0x5678),
|
|
Attributes::AttWrite(codec) => codec.encode(writer, ATTR_WRITE_DEFAULT_VALUE),
|
|
Attributes::AttCustom(codec) => codec.encode(writer, ATTR_CUSTOM_VALUE),
|
|
Attributes::AttWriteList(_) => {
|
|
let tc_handle = TestChecker::get().unwrap();
|
|
let tc = tc_handle.lock().unwrap();
|
|
|
|
writer.start_array(AttrDataWriter::TAG)?;
|
|
for i in tc.write_list.iter().flatten() {
|
|
writer.u16(TagType::Anonymous, *i)?;
|
|
}
|
|
writer.end_container()?;
|
|
|
|
writer.complete()
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub fn write(&mut self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
|
|
let data = data.with_dataver(self.data_ver.get())?;
|
|
|
|
match attr.attr_id.try_into()? {
|
|
Attributes::Att1(codec) => self.att1 = codec.decode(data)?,
|
|
Attributes::Att2(codec) => self.att2 = codec.decode(data)?,
|
|
Attributes::AttWrite(codec) => self.att_write = codec.decode(data)?,
|
|
Attributes::AttCustom(codec) => self.att_custom = codec.decode(data)?,
|
|
Attributes::AttWriteList(_) => {
|
|
attr_list_write(attr, data, |op, data| self.write_attr_list(&op, data))?
|
|
}
|
|
}
|
|
|
|
self.data_ver.changed();
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn invoke(
|
|
&mut self,
|
|
_transaction: &mut Transaction,
|
|
cmd: &CmdDetails,
|
|
data: &TLVElement,
|
|
encoder: CmdDataEncoder,
|
|
) -> Result<(), Error> {
|
|
match cmd.cmd_id.try_into()? {
|
|
// This will generate an echo response on the same endpoint
|
|
// with data multiplied by the multiplier
|
|
Commands::EchoReq => {
|
|
let a = data.u8()?;
|
|
|
|
let mut writer = encoder.with_command(RespCommands::EchoResp as _)?;
|
|
|
|
writer.start_struct(CmdDataWriter::TAG)?;
|
|
// Echo = input * self.multiplier
|
|
writer.u8(TagType::Context(0), a * self.multiplier)?;
|
|
writer.end_container()?;
|
|
|
|
writer.complete()
|
|
}
|
|
}
|
|
}
|
|
|
|
fn write_attr_list(&mut self, op: &ListOperation, data: &TLVElement) -> Result<(), Error> {
|
|
let tc_handle = TestChecker::get().unwrap();
|
|
let mut tc = tc_handle.lock().unwrap();
|
|
match op {
|
|
ListOperation::AddItem => {
|
|
let data = data.u16()?;
|
|
for i in 0..WRITE_LIST_MAX {
|
|
if tc.write_list[i].is_none() {
|
|
tc.write_list[i] = Some(data);
|
|
return Ok(());
|
|
}
|
|
}
|
|
|
|
Err(Error::ResourceExhausted)
|
|
}
|
|
ListOperation::EditItem(index) => {
|
|
let data = data.u16()?;
|
|
if tc.write_list[*index as usize].is_some() {
|
|
tc.write_list[*index as usize] = Some(data);
|
|
Ok(())
|
|
} else {
|
|
Err(Error::InvalidAction)
|
|
}
|
|
}
|
|
ListOperation::DeleteItem(index) => {
|
|
if tc.write_list[*index as usize].is_some() {
|
|
tc.write_list[*index as usize] = None;
|
|
Ok(())
|
|
} else {
|
|
Err(Error::InvalidAction)
|
|
}
|
|
}
|
|
ListOperation::DeleteList => {
|
|
for i in 0..WRITE_LIST_MAX {
|
|
tc.write_list[i] = None;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub const ATTR_CUSTOM_VALUE: u32 = 0xcafebeef;
|
|
pub const ATTR_WRITE_DEFAULT_VALUE: u16 = 0xcafe;
|
|
|
|
impl Handler for EchoCluster {
|
|
fn read(&self, attr: &AttrDetails, encoder: AttrDataEncoder) -> Result<(), Error> {
|
|
EchoCluster::read(self, attr, encoder)
|
|
}
|
|
|
|
fn write(&mut self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
|
|
EchoCluster::write(self, attr, data)
|
|
}
|
|
|
|
fn invoke(
|
|
&mut self,
|
|
transaction: &mut Transaction,
|
|
cmd: &CmdDetails,
|
|
data: &TLVElement,
|
|
encoder: CmdDataEncoder,
|
|
) -> Result<(), Error> {
|
|
EchoCluster::invoke(self, transaction, cmd, data, encoder)
|
|
}
|
|
}
|