rs-matter/matter/tests/common/echo_cluster.rs
ivmarkov d446007f6b Support for no_std
Support for no_std

Further no_std compat
2023-05-14 09:08:51 +00:00

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)
}
}