/* * * 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 matter::{ acl::{AclEntry, AuthMode, Target}, data_model::{ objects::{AttrValue, EncodeValue, Privilege}, system_model::access_control, }, interaction_model::{ core::{IMStatusCode, OpCode}, messages::{ ib::{AttrData, AttrPath, AttrResp, AttrStatus, ClusterPath, DataVersionFilter}, msg::{ReadReq, ReportDataMsg, WriteReq}, }, messages::{msg, GenericPath}, }, tlv::{self, ElementType, FromTLV, TLVArray, TLVElement, TLVWriter, TagType}, }; use crate::{ attr_data, attr_status, common::{ attributes::*, echo_cluster::{self, ATTR_WRITE_DEFAULT_VALUE}, im_engine::{ImEngine, ImInput}, }, }; // Helper for handling Read Req sequences for this file fn handle_read_reqs( im: &mut ImEngine, peer_node_id: u64, input: &[AttrPath], expected: &[AttrResp], ) { let mut out_buf = [0u8; 400]; let received = gen_read_reqs_output(im, peer_node_id, input, None, &mut out_buf); assert_attr_report(&received, expected) } fn gen_read_reqs_output<'a>( im: &mut ImEngine, peer_node_id: u64, input: &[AttrPath], dataver_filters: Option>, out_buf: &'a mut [u8], ) -> ReportDataMsg<'a> { let mut read_req = ReadReq::new(true).set_attr_requests(input); read_req.dataver_filters = dataver_filters; let mut input = ImInput::new(OpCode::ReadRequest, &read_req); input.set_peer_node_id(peer_node_id); let (_, out_buf) = im.process(&input, out_buf); tlv::print_tlv_list(out_buf); let root = tlv::get_root_node_struct(out_buf).unwrap(); ReportDataMsg::from_tlv(&root).unwrap() } // Helper for handling Write Attribute sequences fn handle_write_reqs( im: &mut ImEngine, peer_node_id: u64, input: &[AttrData], expected: &[AttrStatus], ) { let mut out_buf = [0u8; 400]; let write_req = WriteReq::new(false, input); let mut input = ImInput::new(OpCode::WriteRequest, &write_req); input.set_peer_node_id(peer_node_id); let (_, out_buf) = im.process(&input, &mut out_buf); tlv::print_tlv_list(out_buf); let root = tlv::get_root_node_struct(out_buf).unwrap(); let mut index = 0; let response_iter = root .find_tag(msg::WriteRespTag::WriteResponses as u32) .unwrap() .confirm_array() .unwrap() .enter() .unwrap(); for response in response_iter { println!("Validating index {}", index); let status = AttrStatus::from_tlv(&response).unwrap(); assert_eq!(expected[index], status); println!("Index {} success", index); index += 1; } assert_eq!(index, expected.len()); } #[test] /// Ensure that wildcard read attributes don't include error response /// and silently drop the data when access is not granted fn wc_read_attribute() { let _ = env_logger::try_init(); let wc_att1 = GenericPath::new( None, Some(echo_cluster::ID), Some(echo_cluster::Attributes::Att1 as u32), ); let ep0_att1 = GenericPath::new( Some(0), Some(echo_cluster::ID), Some(echo_cluster::Attributes::Att1 as u32), ); let ep1_att1 = GenericPath::new( Some(1), Some(echo_cluster::ID), Some(echo_cluster::Attributes::Att1 as u32), ); let peer = 98765; let mut im = ImEngine::new(); // Test1: Empty Response as no ACL matches let input = &[AttrPath::new(&wc_att1)]; let expected = &[]; handle_read_reqs(&mut im, peer, input, expected); // Add ACL to allow our peer to only access endpoint 0 let mut acl = AclEntry::new(1, Privilege::ADMIN, AuthMode::Case); acl.add_subject(peer).unwrap(); acl.add_target(Target::new(Some(0), None, None)).unwrap(); im.acl_mgr.add(acl).unwrap(); // Test2: Only Single response as only single endpoint is allowed let input = &[AttrPath::new(&wc_att1)]; let expected = &[attr_data!(ep0_att1, ElementType::U16(0x1234))]; handle_read_reqs(&mut im, peer, input, expected); // Add ACL to allow our peer to only access endpoint 1 let mut acl = AclEntry::new(1, Privilege::ADMIN, AuthMode::Case); acl.add_subject(peer).unwrap(); acl.add_target(Target::new(Some(1), None, None)).unwrap(); im.acl_mgr.add(acl).unwrap(); // Test3: Both responses are valid let input = &[AttrPath::new(&wc_att1)]; let expected = &[ attr_data!(ep0_att1, ElementType::U16(0x1234)), attr_data!(ep1_att1, ElementType::U16(0x1234)), ]; handle_read_reqs(&mut im, peer, input, expected); } #[test] /// Ensure that exact read attribute includes error response /// when access is not granted fn exact_read_attribute() { let _ = env_logger::try_init(); let wc_att1 = GenericPath::new( Some(0), Some(echo_cluster::ID), Some(echo_cluster::Attributes::Att1 as u32), ); let ep0_att1 = GenericPath::new( Some(0), Some(echo_cluster::ID), Some(echo_cluster::Attributes::Att1 as u32), ); let peer = 98765; let mut im = ImEngine::new(); // Test1: Unsupported Access error as no ACL matches let input = &[AttrPath::new(&wc_att1)]; let expected = &[attr_status!(&ep0_att1, IMStatusCode::UnsupportedAccess)]; handle_read_reqs(&mut im, peer, input, expected); // Add ACL to allow our peer to access any endpoint let mut acl = AclEntry::new(1, Privilege::ADMIN, AuthMode::Case); acl.add_subject(peer).unwrap(); im.acl_mgr.add(acl).unwrap(); // Test2: Only Single response as only single endpoint is allowed let input = &[AttrPath::new(&wc_att1)]; let expected = &[attr_data!(ep0_att1, ElementType::U16(0x1234))]; handle_read_reqs(&mut im, peer, input, expected); } fn read_cluster_id_write_attr(im: &ImEngine, endpoint: u16) -> AttrValue { let node = im.dm.node.read().unwrap(); let echo = node.get_cluster(endpoint, echo_cluster::ID).unwrap(); *echo .base() .read_attribute_raw(echo_cluster::Attributes::AttWrite as u16) .unwrap() } fn read_cluster_id_data_ver(im: &ImEngine, endpoint: u16) -> u32 { let node = im.dm.node.read().unwrap(); let echo = node.get_cluster(endpoint, echo_cluster::ID).unwrap(); echo.base().get_dataver() } #[test] /// Ensure that an write attribute with a wildcard either performs the operation, /// if allowed, or silently drops the request fn wc_write_attribute() { let _ = env_logger::try_init(); let val0 = 10; let val1 = 20; let attr_data0 = |tag, t: &mut TLVWriter| { let _ = t.u16(tag, val0); }; let attr_data1 = |tag, t: &mut TLVWriter| { let _ = t.u16(tag, val1); }; let wc_att = GenericPath::new( None, Some(echo_cluster::ID), Some(echo_cluster::Attributes::AttWrite as u32), ); let ep0_att = GenericPath::new( Some(0), Some(echo_cluster::ID), Some(echo_cluster::Attributes::AttWrite as u32), ); let ep1_att = GenericPath::new( Some(1), Some(echo_cluster::ID), Some(echo_cluster::Attributes::AttWrite as u32), ); let input0 = &[AttrData::new( None, AttrPath::new(&wc_att), EncodeValue::Closure(&attr_data0), )]; let input1 = &[AttrData::new( None, AttrPath::new(&wc_att), EncodeValue::Closure(&attr_data1), )]; let peer = 98765; let mut im = ImEngine::new(); // Test 1: Wildcard write to an attribute without permission should return // no error handle_write_reqs(&mut im, peer, input0, &[]); { let node = im.dm.node.read().unwrap(); let echo = node.get_cluster(0, echo_cluster::ID).unwrap(); assert_eq!( AttrValue::Uint16(ATTR_WRITE_DEFAULT_VALUE), *echo .base() .read_attribute_raw(echo_cluster::Attributes::AttWrite as u16) .unwrap() ); } // Add ACL to allow our peer to access one endpoint let mut acl = AclEntry::new(1, Privilege::ADMIN, AuthMode::Case); acl.add_subject(peer).unwrap(); acl.add_target(Target::new(Some(0), None, None)).unwrap(); im.acl_mgr.add(acl).unwrap(); // Test 2: Wildcard write to attributes will only return attributes // where the writes were successful handle_write_reqs( &mut im, peer, input0, &[AttrStatus::new(&ep0_att, IMStatusCode::Sucess, 0)], ); assert_eq!(AttrValue::Uint16(val0), read_cluster_id_write_attr(&im, 0)); assert_eq!( AttrValue::Uint16(ATTR_WRITE_DEFAULT_VALUE), read_cluster_id_write_attr(&im, 1) ); // Add ACL to allow our peer to access another endpoint let mut acl = AclEntry::new(1, Privilege::ADMIN, AuthMode::Case); acl.add_subject(peer).unwrap(); acl.add_target(Target::new(Some(1), None, None)).unwrap(); im.acl_mgr.add(acl).unwrap(); // Test 3: Wildcard write to attributes will return multiple attributes // where the writes were successful handle_write_reqs( &mut im, peer, input1, &[ AttrStatus::new(&ep0_att, IMStatusCode::Sucess, 0), AttrStatus::new(&ep1_att, IMStatusCode::Sucess, 0), ], ); assert_eq!(AttrValue::Uint16(val1), read_cluster_id_write_attr(&im, 0)); assert_eq!(AttrValue::Uint16(val1), read_cluster_id_write_attr(&im, 1)); } #[test] /// Ensure that an write attribute without a wildcard returns an error when the /// ACL disallows the access, and returns success once access is granted fn exact_write_attribute() { let _ = env_logger::try_init(); let val0 = 10; let attr_data0 = |tag, t: &mut TLVWriter| { let _ = t.u16(tag, val0); }; let ep0_att = GenericPath::new( Some(0), Some(echo_cluster::ID), Some(echo_cluster::Attributes::AttWrite as u32), ); let input = &[AttrData::new( None, AttrPath::new(&ep0_att), EncodeValue::Closure(&attr_data0), )]; let expected_fail = &[AttrStatus::new( &ep0_att, IMStatusCode::UnsupportedAccess, 0, )]; let expected_success = &[AttrStatus::new(&ep0_att, IMStatusCode::Sucess, 0)]; let peer = 98765; let mut im = ImEngine::new(); // Test 1: Exact write to an attribute without permission should return // Unsupported Access Error handle_write_reqs(&mut im, peer, input, expected_fail); assert_eq!( AttrValue::Uint16(ATTR_WRITE_DEFAULT_VALUE), read_cluster_id_write_attr(&im, 0) ); // Add ACL to allow our peer to access any endpoint let mut acl = AclEntry::new(1, Privilege::ADMIN, AuthMode::Case); acl.add_subject(peer).unwrap(); im.acl_mgr.add(acl).unwrap(); // Test 1: Exact write to an attribute with permission should grant // access handle_write_reqs(&mut im, peer, input, expected_success); assert_eq!(AttrValue::Uint16(val0), read_cluster_id_write_attr(&im, 0)); } #[test] /// Ensure that a write attribute with insufficient permissions is rejected fn insufficient_perms_write() { let _ = env_logger::try_init(); let val0 = 10; let attr_data0 = |tag, t: &mut TLVWriter| { let _ = t.u16(tag, val0); }; let ep0_att = GenericPath::new( Some(0), Some(echo_cluster::ID), Some(echo_cluster::Attributes::AttWrite as u32), ); let input0 = &[AttrData::new( None, AttrPath::new(&ep0_att), EncodeValue::Closure(&attr_data0), )]; let peer = 98765; let mut im = ImEngine::new(); // Add ACL to allow our peer with only OPERATE permission let mut acl = AclEntry::new(1, Privilege::OPERATE, AuthMode::Case); acl.add_subject(peer).unwrap(); acl.add_target(Target::new(Some(0), None, None)).unwrap(); im.acl_mgr.add(acl).unwrap(); // Test: Not enough permission should return error handle_write_reqs( &mut im, peer, input0, &[AttrStatus::new( &ep0_att, IMStatusCode::UnsupportedAccess, 0, )], ); assert_eq!( AttrValue::Uint16(ATTR_WRITE_DEFAULT_VALUE), read_cluster_id_write_attr(&im, 0) ); } #[test] /// Ensure that a write to the ACL attribute instantaneously grants permission /// Here we have 2 ACLs, the first (basic_acl) allows access only to the ACL cluster /// Then we execute a write attribute with 3 writes /// - Write Attr to Echo Cluster (permission denied) /// - Write Attr to ACL Cluster (allowed, this ACL also grants universal access) /// - Write Attr to Echo Cluster again (successful this time) fn write_with_runtime_acl_add() { let _ = env_logger::try_init(); let peer = 98765; let mut im = ImEngine::new(); let val0 = 10; let attr_data0 = |tag, t: &mut TLVWriter| { let _ = t.u16(tag, val0); }; let ep0_att = GenericPath::new( Some(0), Some(echo_cluster::ID), Some(echo_cluster::Attributes::AttWrite as u32), ); let input0 = AttrData::new( None, AttrPath::new(&ep0_att), EncodeValue::Closure(&attr_data0), ); // Create ACL to allow our peer ADMIN on everything let mut allow_acl = AclEntry::new(1, Privilege::ADMIN, AuthMode::Case); allow_acl.add_subject(peer).unwrap(); let acl_att = GenericPath::new( Some(0), Some(access_control::ID), Some(access_control::Attributes::Acl as u32), ); let acl_input = AttrData::new( None, AttrPath::new(&acl_att), EncodeValue::Value(&allow_acl), ); // Create ACL that only allows write to the ACL Cluster let mut basic_acl = AclEntry::new(1, Privilege::ADMIN, AuthMode::Case); basic_acl.add_subject(peer).unwrap(); basic_acl .add_target(Target::new(Some(0), Some(access_control::ID), None)) .unwrap(); im.acl_mgr.add(basic_acl).unwrap(); // Test: deny write (with error), then ACL is added, then allow write handle_write_reqs( &mut im, peer, // write to echo-cluster attribute, write to acl attribute, write to echo-cluster attribute &[input0, acl_input, input0], &[ AttrStatus::new(&ep0_att, IMStatusCode::UnsupportedAccess, 0), AttrStatus::new(&acl_att, IMStatusCode::Sucess, 0), AttrStatus::new(&ep0_att, IMStatusCode::Sucess, 0), ], ); assert_eq!(AttrValue::Uint16(val0), read_cluster_id_write_attr(&im, 0)); } #[test] /// Data Version filtering should ignore the attributes that are filtered /// - in case of wildcard reads /// - in case of exact read attribute fn test_read_data_ver() { // 1 Attr Read Requests // - wildcard endpoint, att1 // - 2 responses are expected let _ = env_logger::try_init(); let peer = 98765; let mut im = ImEngine::new(); // Add ACL to allow our peer with only OPERATE permission let acl = AclEntry::new(1, Privilege::OPERATE, AuthMode::Case); im.acl_mgr.add(acl).unwrap(); let wc_ep_att1 = GenericPath::new( None, Some(echo_cluster::ID), Some(echo_cluster::Attributes::Att1 as u32), ); let input = &[AttrPath::new(&wc_ep_att1)]; let expected = &[ attr_data!( GenericPath::new( Some(0), Some(echo_cluster::ID), Some(echo_cluster::Attributes::Att1 as u32) ), ElementType::U16(0x1234) ), attr_data!( GenericPath::new( Some(1), Some(echo_cluster::ID), Some(echo_cluster::Attributes::Att1 as u32) ), ElementType::U16(0x1234) ), ]; let mut out_buf = [0u8; 400]; // Test 1: Simple read to retrieve the current Data Version of Cluster at Endpoint 0 let received = gen_read_reqs_output(&mut im, peer, input, None, &mut out_buf); assert_attr_report(&received, expected); let data_ver_cluster_at_0 = received .attr_reports .as_ref() .unwrap() .get_index(0) .unwrap_data() .data_ver .unwrap(); let dataver_filter = [DataVersionFilter { path: ClusterPath { node: None, endpoint: 0, cluster: echo_cluster::ID, }, data_ver: data_ver_cluster_at_0, }]; // Test 2: Add Dataversion filter for cluster at endpoint 0 only single entry should be retrieved let received = gen_read_reqs_output( &mut im, peer, input, Some(TLVArray::Slice(&dataver_filter)), &mut out_buf, ); let expected_only_one = &[attr_data!( GenericPath::new( Some(1), Some(echo_cluster::ID), Some(echo_cluster::Attributes::Att1 as u32) ), ElementType::U16(0x1234) )]; assert_attr_report(&received, expected_only_one); // Test 3: Exact read attribute let ep0_att1 = GenericPath::new( Some(0), Some(echo_cluster::ID), Some(echo_cluster::Attributes::Att1 as u32), ); let input = &[AttrPath::new(&ep0_att1)]; let received = gen_read_reqs_output( &mut im, peer, input, Some(TLVArray::Slice(&dataver_filter)), &mut out_buf, ); let expected_error = &[]; assert_attr_report(&received, expected_error); } #[test] /// - Write with the correct data version should go through /// - Write with incorrect data version should fail with error /// - Wildcard write with incorrect data version should be ignored fn test_write_data_ver() { // 1 Attr Read Requests // - wildcard endpoint, att1 // - 2 responses are expected let _ = env_logger::try_init(); let peer = 98765; let mut im = ImEngine::new(); // Add ACL to allow our peer with only OPERATE permission let acl = AclEntry::new(1, Privilege::ADMIN, AuthMode::Case); im.acl_mgr.add(acl).unwrap(); let wc_ep_attwrite = GenericPath::new( None, Some(echo_cluster::ID), Some(echo_cluster::Attributes::AttWrite as u32), ); let ep0_attwrite = GenericPath::new( Some(0), Some(echo_cluster::ID), Some(echo_cluster::Attributes::AttWrite as u32), ); let val0 = 10u16; let val1 = 11u16; let attr_data0 = EncodeValue::Value(&val0); let attr_data1 = EncodeValue::Value(&val1); let initial_data_ver = read_cluster_id_data_ver(&im, 0); // Test 1: Write with correct dataversion should succeed let input_correct_dataver = &[AttrData::new( Some(initial_data_ver), AttrPath::new(&ep0_attwrite), attr_data0, )]; handle_write_reqs( &mut im, peer, input_correct_dataver, &[AttrStatus::new(&ep0_attwrite, IMStatusCode::Sucess, 0)], ); assert_eq!(AttrValue::Uint16(val0), read_cluster_id_write_attr(&im, 0)); // Test 2: Write with incorrect dataversion should fail // Now the data version would have incremented due to the previous write let input_correct_dataver = &[AttrData::new( Some(initial_data_ver), AttrPath::new(&ep0_attwrite), attr_data1, )]; handle_write_reqs( &mut im, peer, input_correct_dataver, &[AttrStatus::new( &ep0_attwrite, IMStatusCode::DataVersionMismatch, 0, )], ); assert_eq!(AttrValue::Uint16(val0), read_cluster_id_write_attr(&im, 0)); // Test 3: Wildcard write with incorrect dataversion should ignore that cluster // In this case, while the data version is correct for endpoint 0, the endpoint 1's // data version would not match let new_data_ver = read_cluster_id_data_ver(&im, 0); let input_correct_dataver = &[AttrData::new( Some(new_data_ver), AttrPath::new(&wc_ep_attwrite), attr_data1, )]; handle_write_reqs( &mut im, peer, input_correct_dataver, &[AttrStatus::new(&ep0_attwrite, IMStatusCode::Sucess, 0)], ); assert_eq!(AttrValue::Uint16(val1), read_cluster_id_write_attr(&im, 0)); assert_eq!(initial_data_ver + 1, new_data_ver); }