327 lines
10 KiB
Rust
327 lines
10 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 crate::{
|
|
acl::AccessReq,
|
|
data_model::objects::{Access, AttrValue, Attribute, EncodeValue, Quality},
|
|
error::*,
|
|
interaction_model::{command::CommandReq, core::IMStatusCode},
|
|
// TODO: This layer shouldn't really depend on the TLV layer, should create an abstraction layer
|
|
tlv::{Nullable, TLVElement, TLVWriter, TagType},
|
|
};
|
|
use log::error;
|
|
use num_derive::FromPrimitive;
|
|
use rand::Rng;
|
|
use std::fmt::{self, Debug};
|
|
|
|
use super::Encoder;
|
|
|
|
pub const ATTRS_PER_CLUSTER: usize = 8;
|
|
pub const CMDS_PER_CLUSTER: usize = 8;
|
|
|
|
#[derive(FromPrimitive, Debug)]
|
|
pub enum GlobalElements {
|
|
_ClusterRevision = 0xFFFD,
|
|
FeatureMap = 0xFFFC,
|
|
AttributeList = 0xFFFB,
|
|
_EventList = 0xFFFA,
|
|
_ClientGenCmd = 0xFFF9,
|
|
ServerGenCmd = 0xFFF8,
|
|
FabricIndex = 0xFE,
|
|
}
|
|
|
|
// TODO: What if we instead of creating this, we just pass the AttrData/AttrPath to the read/write
|
|
// methods?
|
|
pub struct AttrDetails {
|
|
pub attr_id: u16,
|
|
pub list_index: Option<Nullable<u16>>,
|
|
pub fab_idx: u8,
|
|
pub fab_filter: bool,
|
|
}
|
|
|
|
pub trait ClusterType {
|
|
// TODO: 5 methods is going to be quite expensive for vtables of all the clusters
|
|
fn base(&self) -> &Cluster;
|
|
fn base_mut(&mut self) -> &mut Cluster;
|
|
fn read_custom_attribute(&self, _encoder: &mut dyn Encoder, _attr: &AttrDetails) {}
|
|
|
|
fn handle_command(&mut self, cmd_req: &mut CommandReq) -> Result<(), IMStatusCode> {
|
|
let cmd = cmd_req.cmd.path.leaf.map(|a| a as u16);
|
|
println!("Received command: {:?}", cmd);
|
|
|
|
Err(IMStatusCode::UnsupportedCommand)
|
|
}
|
|
|
|
/// Write an attribute
|
|
///
|
|
/// Note that if this method is defined, you must handle the write for all the attributes. Even those
|
|
/// that are not 'custom'. This is different from how you handle the read_custom_attribute() method.
|
|
/// The reason for this being, you may want to handle an attribute write request even though it is a
|
|
/// standard attribute like u16, u32 etc.
|
|
///
|
|
/// If you wish to update the standard attribute in the data model database, you must call the
|
|
/// write_attribute_from_tlv() method from the base cluster, as is shown here in the default case
|
|
fn write_attribute(
|
|
&mut self,
|
|
attr: &AttrDetails,
|
|
data: &TLVElement,
|
|
) -> Result<(), IMStatusCode> {
|
|
self.base_mut().write_attribute_from_tlv(attr.attr_id, data)
|
|
}
|
|
}
|
|
|
|
pub struct Cluster {
|
|
pub(super) id: u32,
|
|
attributes: Vec<Attribute>,
|
|
feature_map: Option<u32>,
|
|
data_ver: u32,
|
|
}
|
|
|
|
impl Cluster {
|
|
pub fn new(id: u32) -> Result<Cluster, Error> {
|
|
let mut c = Cluster {
|
|
id,
|
|
attributes: Vec::with_capacity(ATTRS_PER_CLUSTER),
|
|
feature_map: None,
|
|
data_ver: rand::thread_rng().gen_range(0..0xFFFFFFFF),
|
|
};
|
|
c.add_default_attributes()?;
|
|
Ok(c)
|
|
}
|
|
|
|
pub fn id(&self) -> u32 {
|
|
self.id
|
|
}
|
|
|
|
pub fn get_dataver(&self) -> u32 {
|
|
self.data_ver
|
|
}
|
|
|
|
pub fn set_feature_map(&mut self, map: u32) -> Result<(), Error> {
|
|
if self.feature_map.is_none() {
|
|
self.add_attribute(Attribute::new(
|
|
GlobalElements::FeatureMap as u16,
|
|
AttrValue::Uint32(map),
|
|
Access::RV,
|
|
Quality::NONE,
|
|
)?)?;
|
|
} else {
|
|
self.write_attribute_raw(GlobalElements::FeatureMap as u16, AttrValue::Uint32(map))
|
|
.map_err(|_| Error::Invalid)?;
|
|
}
|
|
self.feature_map = Some(map);
|
|
Ok(())
|
|
}
|
|
|
|
fn add_default_attributes(&mut self) -> Result<(), Error> {
|
|
self.add_attribute(Attribute::new(
|
|
GlobalElements::AttributeList as u16,
|
|
AttrValue::Custom,
|
|
Access::RV,
|
|
Quality::NONE,
|
|
)?)
|
|
}
|
|
|
|
pub fn add_attribute(&mut self, attr: Attribute) -> Result<(), Error> {
|
|
if self.attributes.len() < self.attributes.capacity() {
|
|
self.attributes.push(attr);
|
|
Ok(())
|
|
} else {
|
|
Err(Error::NoSpace)
|
|
}
|
|
}
|
|
|
|
fn get_attribute_index(&self, attr_id: u16) -> Option<usize> {
|
|
self.attributes.iter().position(|c| c.id == attr_id)
|
|
}
|
|
|
|
fn get_attribute(&self, attr_id: u16) -> Result<&Attribute, Error> {
|
|
let index = self
|
|
.get_attribute_index(attr_id)
|
|
.ok_or(Error::AttributeNotFound)?;
|
|
Ok(&self.attributes[index])
|
|
}
|
|
|
|
fn get_attribute_mut(&mut self, attr_id: u16) -> Result<&mut Attribute, Error> {
|
|
let index = self
|
|
.get_attribute_index(attr_id)
|
|
.ok_or(Error::AttributeNotFound)?;
|
|
Ok(&mut self.attributes[index])
|
|
}
|
|
|
|
// Returns a slice of attribute, with either a single attribute or all (wildcard)
|
|
pub fn get_wildcard_attribute(
|
|
&self,
|
|
attribute: Option<u16>,
|
|
) -> Result<(&[Attribute], bool), IMStatusCode> {
|
|
if let Some(a) = attribute {
|
|
if let Some(i) = self.get_attribute_index(a) {
|
|
Ok((&self.attributes[i..i + 1], false))
|
|
} else {
|
|
Err(IMStatusCode::UnsupportedAttribute)
|
|
}
|
|
} else {
|
|
Ok((&self.attributes[..], true))
|
|
}
|
|
}
|
|
|
|
pub fn read_attribute(
|
|
c: &dyn ClusterType,
|
|
access_req: &mut AccessReq,
|
|
encoder: &mut dyn Encoder,
|
|
attr: &AttrDetails,
|
|
) {
|
|
let mut error = IMStatusCode::Sucess;
|
|
let base = c.base();
|
|
let a = if let Ok(a) = base.get_attribute(attr.attr_id) {
|
|
a
|
|
} else {
|
|
encoder.encode_status(IMStatusCode::UnsupportedAttribute, 0);
|
|
return;
|
|
};
|
|
|
|
if !a.access.contains(Access::READ) {
|
|
error = IMStatusCode::UnsupportedRead;
|
|
}
|
|
|
|
access_req.set_target_perms(a.access);
|
|
if !access_req.allow() {
|
|
error = IMStatusCode::UnsupportedAccess;
|
|
}
|
|
|
|
if error != IMStatusCode::Sucess {
|
|
encoder.encode_status(error, 0);
|
|
} else if Attribute::is_system_attr(attr.attr_id) {
|
|
c.base().read_system_attribute(encoder, a)
|
|
} else if a.value != AttrValue::Custom {
|
|
encoder.encode(EncodeValue::Value(&a.value))
|
|
} else {
|
|
c.read_custom_attribute(encoder, attr)
|
|
}
|
|
}
|
|
|
|
fn encode_attribute_ids(&self, tag: TagType, tw: &mut TLVWriter) {
|
|
let _ = tw.start_array(tag);
|
|
for a in &self.attributes {
|
|
let _ = tw.u16(TagType::Anonymous, a.id);
|
|
}
|
|
let _ = tw.end_container();
|
|
}
|
|
|
|
fn read_system_attribute(&self, encoder: &mut dyn Encoder, attr: &Attribute) {
|
|
let global_attr: Option<GlobalElements> = num::FromPrimitive::from_u16(attr.id);
|
|
if let Some(global_attr) = global_attr {
|
|
match global_attr {
|
|
GlobalElements::AttributeList => {
|
|
encoder.encode(EncodeValue::Closure(&|tag, tw| {
|
|
self.encode_attribute_ids(tag, tw)
|
|
}));
|
|
return;
|
|
}
|
|
GlobalElements::FeatureMap => {
|
|
let val = if let Some(m) = self.feature_map { m } else { 0 };
|
|
encoder.encode(EncodeValue::Value(&val));
|
|
return;
|
|
}
|
|
_ => {
|
|
error!("This attribute not yet handled {:?}", global_attr);
|
|
}
|
|
}
|
|
}
|
|
encoder.encode_status(IMStatusCode::UnsupportedAttribute, 0)
|
|
}
|
|
|
|
pub fn read_attribute_raw(&self, attr_id: u16) -> Result<&AttrValue, IMStatusCode> {
|
|
let a = self
|
|
.get_attribute(attr_id)
|
|
.map_err(|_| IMStatusCode::UnsupportedAttribute)?;
|
|
Ok(&a.value)
|
|
}
|
|
|
|
pub fn write_attribute(
|
|
c: &mut dyn ClusterType,
|
|
access_req: &mut AccessReq,
|
|
data: &TLVElement,
|
|
attr: &AttrDetails,
|
|
) -> Result<(), IMStatusCode> {
|
|
let base = c.base_mut();
|
|
let a = if let Ok(a) = base.get_attribute_mut(attr.attr_id) {
|
|
a
|
|
} else {
|
|
return Err(IMStatusCode::UnsupportedAttribute);
|
|
};
|
|
|
|
if !a.access.contains(Access::WRITE) {
|
|
return Err(IMStatusCode::UnsupportedWrite);
|
|
}
|
|
|
|
access_req.set_target_perms(a.access);
|
|
if !access_req.allow() {
|
|
return Err(IMStatusCode::UnsupportedAccess);
|
|
}
|
|
|
|
c.write_attribute(attr, data)
|
|
}
|
|
|
|
pub fn write_attribute_from_tlv(
|
|
&mut self,
|
|
attr_id: u16,
|
|
data: &TLVElement,
|
|
) -> Result<(), IMStatusCode> {
|
|
let a = self.get_attribute_mut(attr_id)?;
|
|
if a.value != AttrValue::Custom {
|
|
let mut value = a.value;
|
|
value
|
|
.update_from_tlv(data)
|
|
.map_err(|_| IMStatusCode::Failure)?;
|
|
a.set_value(value)
|
|
.map(|_| {
|
|
self.cluster_changed();
|
|
})
|
|
.map_err(|_| IMStatusCode::UnsupportedWrite)
|
|
} else {
|
|
Err(IMStatusCode::UnsupportedAttribute)
|
|
}
|
|
}
|
|
|
|
pub fn write_attribute_raw(&mut self, attr_id: u16, value: AttrValue) -> Result<(), Error> {
|
|
let a = self.get_attribute_mut(attr_id)?;
|
|
a.set_value(value).map(|_| {
|
|
self.cluster_changed();
|
|
})
|
|
}
|
|
|
|
/// This method must be called for any changes to the data model
|
|
/// Currently this only increments the data version, but we can reuse the same
|
|
/// for raising events too
|
|
pub fn cluster_changed(&mut self) {
|
|
self.data_ver = self.data_ver.wrapping_add(1);
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Display for Cluster {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "id:{}, ", self.id)?;
|
|
write!(f, "attrs[")?;
|
|
let mut comma = "";
|
|
for element in self.attributes.iter() {
|
|
write!(f, "{} {}", comma, element)?;
|
|
comma = ",";
|
|
}
|
|
write!(f, " ], ")
|
|
}
|
|
}
|