/* * * 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 super::{ cluster_basic_information::BasicInfoConfig, device_types::device_type_add_root_node, objects::{self, *}, sdm::dev_att::DevAttDataFetcher, system_model::descriptor::DescriptorCluster, }; use crate::{ acl::{AccessReq, Accessor, AclMgr, AuthMode}, error::*, fabric::FabricMgr, interaction_model::{ command::CommandReq, core::IMStatusCode, messages::{ ib::{self, AttrData, DataVersionFilter}, msg::{self, InvReq, ReadReq, WriteReq}, GenericPath, }, InteractionConsumer, Transaction, }, secure_channel::pake::PaseMgr, tlv::{TLVArray, TLVWriter, TagType, ToTLV}, transport::session::{Session, SessionMode}, }; use log::{error, info}; use std::sync::{Arc, RwLock}; #[derive(Clone)] pub struct DataModel { pub node: Arc>>, acl_mgr: Arc, } impl DataModel { pub fn new( dev_details: &BasicInfoConfig, dev_att: Box, fabric_mgr: Arc, acl_mgr: Arc, pase_mgr: PaseMgr, ) -> Result { let dm = DataModel { node: Arc::new(RwLock::new(Node::new()?)), acl_mgr: acl_mgr.clone(), }; { let mut node = dm.node.write()?; node.set_changes_cb(Box::new(dm.clone())); device_type_add_root_node( &mut node, dev_details, dev_att, fabric_mgr, acl_mgr, pase_mgr, )?; } Ok(dm) } pub fn read_attribute_raw( &self, endpoint: u16, cluster: u32, attr: u16, ) -> Result { let node = self.node.read().unwrap(); let cluster = node.get_cluster(endpoint, cluster)?; cluster.base().read_attribute_raw(attr).map(|a| *a) } // Encode a write attribute from a path that may or may not be wildcard fn handle_write_attr_path( node: &mut Node, accessor: &Accessor, attr_data: &AttrData, tw: &mut TLVWriter, ) { let gen_path = attr_data.path.to_gp(); let mut encoder = AttrWriteEncoder::new(tw, TagType::Anonymous); encoder.set_path(gen_path); // The unsupported pieces of the wildcard path if attr_data.path.cluster.is_none() { encoder.encode_status(IMStatusCode::UnsupportedCluster, 0); return; } if attr_data.path.attr.is_none() { encoder.encode_status(IMStatusCode::UnsupportedAttribute, 0); return; } // Get the data let write_data = match &attr_data.data { EncodeValue::Closure(_) | EncodeValue::Value(_) => { error!("Not supported"); return; } EncodeValue::Tlv(t) => t, }; if gen_path.is_wildcard() { // This is a wildcard path, skip error // This is required because there could be access control errors too that need // to be taken care of. encoder.skip_error(); } let mut attr = AttrDetails { // will be udpated in the loop below attr_id: 0, list_index: attr_data.path.list_index, fab_filter: false, fab_idx: accessor.fab_idx, }; let result = node.for_each_cluster_mut(&gen_path, |path, c| { if attr_data.data_ver.is_some() && Some(c.base().get_dataver()) != attr_data.data_ver { encoder.encode_status(IMStatusCode::DataVersionMismatch, 0); return Ok(()); } attr.attr_id = path.leaf.unwrap_or_default() as u16; encoder.set_path(*path); let mut access_req = AccessReq::new(accessor, path, Access::WRITE); let r = match Cluster::write_attribute(c, &mut access_req, write_data, &attr) { Ok(_) => IMStatusCode::Sucess, Err(e) => e, }; encoder.encode_status(r, 0); Ok(()) }); if let Err(e) = result { // We hit this only if this is a non-wildcard path and some parts of the path are missing encoder.encode_status(e, 0); } } // Encode a read attribute from a path that may or may not be wildcard fn handle_read_attr_path( node: &Node, accessor: &Accessor, attr_encoder: &mut AttrReadEncoder, attr_details: &mut AttrDetails, ) { let path = attr_encoder.path; // Skip error reporting for wildcard paths, don't for concrete paths attr_encoder.skip_error(path.is_wildcard()); let result = node.for_each_attribute(&path, |path, c| { // Ignore processing if data filter matches. // For a wildcard attribute, this may end happening unnecessarily for all attributes, although // a single skip for the cluster is sufficient. That requires us to replace this for_each with a // for_each_cluster let cluster_data_ver = c.base().get_dataver(); if Self::data_filter_matches(&attr_encoder.data_ver_filters, path, cluster_data_ver) { return Ok(()); } attr_details.attr_id = path.leaf.unwrap_or_default() as u16; // Overwrite the previous path with the concrete path attr_encoder.set_path(*path); // Set the cluster's data version attr_encoder.set_data_ver(cluster_data_ver); let mut access_req = AccessReq::new(accessor, path, Access::READ); Cluster::read_attribute(c, &mut access_req, attr_encoder, attr_details); Ok(()) }); if let Err(e) = result { // We hit this only if this is a non-wildcard path attr_encoder.encode_status(e, 0); } } // Handle command from a path that may or may not be wildcard fn handle_command_path(node: &mut Node, cmd_req: &mut CommandReq) { let wildcard = cmd_req.cmd.path.is_wildcard(); let path = cmd_req.cmd.path; let result = node.for_each_cluster_mut(&path, |path, c| { cmd_req.cmd.path = *path; let result = c.handle_command(cmd_req); if let Err(e) = result { // It is likely that we might have to do an 'Access' aware traversal // if there are other conditions in the wildcard scenario that shouldn't be // encoded as CmdStatus if !(wildcard && e == IMStatusCode::UnsupportedCommand) { let invoke_resp = ib::InvResp::status_new(cmd_req.cmd, e, 0); let _ = invoke_resp.to_tlv(cmd_req.resp, TagType::Anonymous); } } Ok(()) }); if !wildcard { if let Err(e) = result { // We hit this only if this is a non-wildcard path let invoke_resp = ib::InvResp::status_new(cmd_req.cmd, e, 0); let _ = invoke_resp.to_tlv(cmd_req.resp, TagType::Anonymous); } } } fn sess_to_accessor(&self, sess: &Session) -> Accessor { match sess.get_session_mode() { SessionMode::Case(c) => Accessor::new( c, sess.get_peer_node_id().unwrap_or_default(), AuthMode::Case, self.acl_mgr.clone(), ), SessionMode::Pase => Accessor::new(0, 1, AuthMode::Pase, self.acl_mgr.clone()), SessionMode::PlainText => Accessor::new(0, 1, AuthMode::Invalid, self.acl_mgr.clone()), } } /// Returns true if the path matches the cluster path and the data version is a match fn data_filter_matches( filters: &Option<&TLVArray>, path: &GenericPath, data_ver: u32, ) -> bool { if let Some(filters) = *filters { for filter in filters.iter() { // TODO: No handling of 'node' comparision yet if Some(filter.path.endpoint) == path.endpoint && Some(filter.path.cluster) == path.cluster && filter.data_ver == data_ver { return true; } } } false } } impl objects::ChangeConsumer for DataModel { fn endpoint_added(&self, id: u16, endpoint: &mut Endpoint) -> Result<(), Error> { endpoint.add_cluster(DescriptorCluster::new(id, self.clone())?)?; Ok(()) } } impl InteractionConsumer for DataModel { fn consume_write_attr( &self, write_req: &WriteReq, trans: &mut Transaction, tw: &mut TLVWriter, ) -> Result<(), Error> { let accessor = self.sess_to_accessor(trans.session); tw.start_array(TagType::Context(msg::WriteRespTag::WriteResponses as u8))?; let mut node = self.node.write().unwrap(); for attr_data in write_req.write_requests.iter() { DataModel::handle_write_attr_path(&mut node, &accessor, &attr_data, tw); } tw.end_container()?; Ok(()) } fn consume_read_attr( &self, read_req: &ReadReq, trans: &mut Transaction, tw: &mut TLVWriter, ) -> Result<(), Error> { let mut attr_encoder = AttrReadEncoder::new(tw); if let Some(filters) = &read_req.dataver_filters { attr_encoder.set_data_ver_filters(filters); } let mut attr_details = AttrDetails { // This will be updated internally attr_id: 0, // This will be updated internally list_index: None, // This will be updated internally fab_idx: 0, fab_filter: read_req.fabric_filtered, }; if let Some(attr_requests) = &read_req.attr_requests { let accessor = self.sess_to_accessor(trans.session); let node = self.node.read().unwrap(); attr_encoder .tw .start_array(TagType::Context(msg::ReportDataTag::AttributeReports as u8))?; for attr_path in attr_requests.iter() { attr_encoder.set_path(attr_path.to_gp()); // Extract the attr_path fields into various structures attr_details.list_index = attr_path.list_index; attr_details.fab_idx = accessor.fab_idx; DataModel::handle_read_attr_path( &node, &accessor, &mut attr_encoder, &mut attr_details, ); } tw.end_container()?; } Ok(()) } fn consume_invoke_cmd( &self, inv_req_msg: &InvReq, trans: &mut Transaction, tw: &mut TLVWriter, ) -> Result<(), Error> { let mut node = self.node.write().unwrap(); if let Some(inv_requests) = &inv_req_msg.inv_requests { // Array of InvokeResponse IBs tw.start_array(TagType::Context(msg::InvRespTag::InvokeResponses as u8))?; for i in inv_requests.iter() { let data = if let Some(data) = i.data.unwrap_tlv() { data } else { continue; }; info!("Invoke Commmand Handler executing: {:?}", i.path); let mut cmd_req = CommandReq { cmd: i.path, data, trans, resp: tw, }; DataModel::handle_command_path(&mut node, &mut cmd_req); } tw.end_container()?; } Ok(()) } } /// Encoder for generating a response to a read request pub struct AttrReadEncoder<'a, 'b, 'c> { tw: &'a mut TLVWriter<'b, 'c>, data_ver: u32, path: GenericPath, skip_error: bool, data_ver_filters: Option<&'a TLVArray<'a, DataVersionFilter>>, } impl<'a, 'b, 'c> AttrReadEncoder<'a, 'b, 'c> { pub fn new(tw: &'a mut TLVWriter<'b, 'c>) -> Self { Self { tw, data_ver: 0, skip_error: false, path: Default::default(), data_ver_filters: None, } } pub fn skip_error(&mut self, skip: bool) { self.skip_error = skip; } pub fn set_data_ver(&mut self, data_ver: u32) { self.data_ver = data_ver; } pub fn set_data_ver_filters(&mut self, filters: &'a TLVArray<'a, DataVersionFilter>) { self.data_ver_filters = Some(filters); } pub fn set_path(&mut self, path: GenericPath) { self.path = path; } } impl<'a, 'b, 'c> Encoder for AttrReadEncoder<'a, 'b, 'c> { fn encode(&mut self, value: EncodeValue) { let resp = ib::AttrResp::Data(ib::AttrData::new( Some(self.data_ver), ib::AttrPath::new(&self.path), value, )); let _ = resp.to_tlv(self.tw, TagType::Anonymous); } fn encode_status(&mut self, status: IMStatusCode, cluster_status: u16) { if !self.skip_error { let resp = ib::AttrResp::Status(ib::AttrStatus::new(&self.path, status, cluster_status)); let _ = resp.to_tlv(self.tw, TagType::Anonymous); } } } /// Encoder for generating a response to a write request pub struct AttrWriteEncoder<'a, 'b, 'c> { tw: &'a mut TLVWriter<'b, 'c>, tag: TagType, path: GenericPath, skip_error: bool, } impl<'a, 'b, 'c> AttrWriteEncoder<'a, 'b, 'c> { pub fn new(tw: &'a mut TLVWriter<'b, 'c>, tag: TagType) -> Self { Self { tw, tag, path: Default::default(), skip_error: false, } } pub fn skip_error(&mut self) { self.skip_error = true; } pub fn set_path(&mut self, path: GenericPath) { self.path = path; } } impl<'a, 'b, 'c> Encoder for AttrWriteEncoder<'a, 'b, 'c> { fn encode(&mut self, _value: EncodeValue) { // Only status encodes for AttrWriteResponse } fn encode_status(&mut self, status: IMStatusCode, cluster_status: u16) { if self.skip_error && status != IMStatusCode::Sucess { // Don't encode errors return; } let resp = ib::AttrStatus::new(&self.path, status, cluster_status); let _ = resp.to_tlv(self.tw, self.tag); } }