diff --git a/.github/workflows/build-tlv-tool.yml b/.github/workflows/build-tlv-tool.yml new file mode 100644 index 0000000..e0d4a6c --- /dev/null +++ b/.github/workflows/build-tlv-tool.yml @@ -0,0 +1,26 @@ +name: Build-TLV-Tool + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Build + run: cd tools/tlv_tool; cargo build --verbose + - name: Archive artifacts + uses: actions/upload-artifact@v2 + with: + name: tlv_tool + path: tools/tlv_tool/target/debug/tlv_tool + diff --git a/.github/workflows/test-linux-mbedtls.yml b/.github/workflows/test-linux-mbedtls.yml new file mode 100644 index 0000000..5640faa --- /dev/null +++ b/.github/workflows/test-linux-mbedtls.yml @@ -0,0 +1,22 @@ +name: Test-Linux-mbedTLS + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build_and_test: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Build + run: cd matter; cargo build --verbose --no-default-features --features crypto_mbedtls + - name: Run tests + run: cd matter; cargo test --verbose --no-default-features --features crypto_mbedtls -- --test-threads=1 diff --git a/.github/workflows/test-linux-openssl.yml b/.github/workflows/test-linux-openssl.yml new file mode 100644 index 0000000..fdfa525 --- /dev/null +++ b/.github/workflows/test-linux-openssl.yml @@ -0,0 +1,22 @@ +name: Test-Linux-OpenSSL + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build_and_test: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Build + run: cd matter; cargo build --verbose --no-default-features --features crypto_openssl + - name: Run tests + run: cd matter; cargo test --verbose --no-default-features --features crypto_openssl -- --test-threads=1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c8e9e48 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +target +Cargo.lock +.vscode diff --git a/README.md b/README.md index 8f2625f..dbabef2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,58 @@ -# matter-rs: The Rust Implementaion of Matter + +# matter-rs: The Rust Implementation of Matter ![experimental](https://img.shields.io/badge/status-Experimental-red) [![license](https://img.shields.io/badge/license-Apache2-green.svg)](https://raw.githubusercontent.com/project-chip/matter-rs/main/LICENSE) + + +[![Test Linux (OpenSSL)](https://github.com/project-chip/matter-rs/actions/workflows/test-linux-openssl.yml/badge.svg)](https://github.com/project-chip/matter-rs/actions/workflows/test-linux-openssl.yml) +[![Test Linux (mbedTLS)](https://github.com/project-chip/matter-rs/actions/workflows/test-linux-mbedtls.yml/badge.svg)](https://github.com/project-chip/matter-rs/actions/workflows/test-linux-mbedtls.yml) + +## Build + +Building the library: +``` +$ cd matter +$ cargo build +``` + +Building the example: +``` +$ cd matter +$ RUST_LOG="matter" cargo run --example onoff_light +``` + +With the chip-tool (the current tool for testing Matter) use the Ethernet commissioning mechanism: +``` +$ chip-tool pairing ethernet 12344321 123456 0 5540 +``` + +Interact with the device +``` +# Read server-list +$ chip-tool descriptor read server-list 12344321 0 + +# Read On/Off status +$ chip-tool onoff read on-off 12344321 1 + +# Toggle On/Off by invoking the command +$ chip-tool onoff on 12344321 1 +``` + +## Functionality +- Secure Channel: + - PASE + - CASE +- Interactions: + - Invoke Command(s), Read Attribute(s), Write Attribute(s) +- Commissioning: + - over Ethernet + - Network Commissioning Cluster + - General Commissioning Cluster + - Operational Certificates Cluster +- Some [TODO](TODO.md) are captured here + +## Notes + +The matter-rs project is a work-in-progress and does NOT yet fully implement Matter. + diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..3a0423c --- /dev/null +++ b/TODO.md @@ -0,0 +1,49 @@ +* udp.c: + * Could get rid of 'smol' in here, no other processing is performed in this thread +* TLVList: + * The 'Pointer' could be directly used in the TLVListIterator, makes it common + * Not too happy with the way iterator_consumer is done for ContainerIterator, we could just zip the internal ListIterator instead? + * Implement the IntoIterator Trait as well for the TLVElement. This was done earlier, but I backtracker after I ran into same lifetime issues +* Some configurable values like number of exchanges per session, number of sessions supported etc, can be bubbled up to some configurator for this crate. I wonder how that is done. +* About outgoing counter, is it incremented if we send mutliple acknowledgements to the same retransmitted packet? So let's say peer retransmits a packet with ctr 4, for 3 times. Our response ctr, is, say 20. Then should we respond with 20, 21, 22, or 20, 20, 20? +* I had to use Box::new() to pin ownership for certain objects. Not yet able to use try_new() in the stable releases, and I am not a fan of APIs that panic. We should mostly look at things like heapless:pool or stuff. These objects should really be in the bss, with a single ownership. +* It might be more efficient to avoid using .find_element() on TLVs. Earlier it was created this way because the spec mentions that the order may change, but it appears that this is unlikely, looking at the C++ implementation. If so, we could be faster, by just specifying looking for tag followed by value. +* PASE: + - Pick some sensible and strong values for PBKDF2{iterCnt and Salt-length} based on SoC capability + - Verifier should only store w0 and L, w1 shouldn't even be stored + - Allow some way to open the PASE window + - Allow some way to pass in the 'passcode' and 'salt' + - In case of error in any of the legs, return StatusReport + - Provide a way to delete the exchange + - SPAKE2+: the check with I (abort if `h*X == I`), as indicated by the RFC is pending + +* Implement the ARM Fail Safe and Regulatory Config properly. Currently we just ack them to proceed further +* Currently AEAD, sha256 etc are directly used from rust crates. Instead use implementations from openssl/mbedtls - Done. Upstream MRs pending +* rust-mbedTLS: We have to do some gymnastics because current APIs only support signature encoded in ASN1 format. Fix this upstream +* CASE: + - Handle initial MRP Parameters struct from Sigma1 +* FailSafe: + - Enable timer and expiration handling for fail-safe context +* Cert Verification: + - Time validation (Not Before/Not After) + - KeyUsage flags and others are pending +* Transport Mgr: + - Add plain_encode and proto_encode in Packet + - A new proto_tx should be created in the acks_to_send loop also, otherwise, there is a potential chance of reuse + - 'transport' object's ownership needs to be inside session, or in the least 'exchange' + - Sending 'close session' is pending on session reclamation because 'transport' object isn't owned + - Convert the SessionHandle to &Session? Why maintain a separate object for this? +* Exchange: + - What should happen when an exchange is closed by the higher layer, our tx-retrans is pending, and we got a retrans for that exchange? +* ACL: + - Device-Type based ACLs + - NOC CAT + - Applying ACLs to commands (requires some restructuring of the commands) + - I think we can the encoder to AccessReq Object making it a complete object for access within the DM + - List processing of attribute write is missing in IM. List behaviour is add/edit/delete. Currently we only do 'add' +* Interaction Model + - List processing of write attributes is different (delete, modify, edit), needs to be handled +* DataModel: + - Shall we use a CmdEncoder as a parameter for all the handle_commands()? + - Need to define common data types for cluster_id_t, endpoint_id_t so their sizes are constantly defined somewhere + diff --git a/boxslab/Cargo.toml b/boxslab/Cargo.toml new file mode 100644 index 0000000..fa4f430 --- /dev/null +++ b/boxslab/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "boxslab" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bitmaps={version="3.2.0", features=[]} diff --git a/boxslab/src/lib.rs b/boxslab/src/lib.rs new file mode 100644 index 0000000..33bbe6e --- /dev/null +++ b/boxslab/src/lib.rs @@ -0,0 +1,241 @@ +/* + * + * 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::{ + mem::MaybeUninit, + ops::{Deref, DerefMut}, + sync::Mutex, +}; + +// TODO: why is max bitmap size 64 a correct max size? Could we match +// boxslabs instead or store used/not used inside the box slabs themselves? +const MAX_BITMAP_SIZE: usize = 64; +pub struct Bitmap { + inner: bitmaps::Bitmap, + max_size: usize, +} + +impl Bitmap { + pub fn new(max_size: usize) -> Self { + assert!(max_size <= MAX_BITMAP_SIZE); + Bitmap { + inner: bitmaps::Bitmap::new(), + max_size, + } + } + + pub fn set(&mut self, index: usize) -> bool { + assert!(index < self.max_size); + self.inner.set(index, true) + } + + pub fn reset(&mut self, index: usize) -> bool { + assert!(index < self.max_size); + self.inner.set(index, false) + } + + pub fn first_false_index(&self) -> Option { + match self.inner.first_false_index() { + Some(idx) if idx < self.max_size => Some(idx), + _ => None, + } + } + + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } + + pub fn is_full(&self) -> bool { + self.first_false_index().is_none() + } +} + +#[macro_export] +macro_rules! box_slab { + ($name:ident,$t:ty,$v:expr) => { + use std::mem::MaybeUninit; + use std::sync::Once; + use $crate::{BoxSlab, Slab, SlabPool}; + + pub struct $name; + + impl SlabPool for $name { + type SlabType = $t; + fn get_slab() -> &'static Slab { + const MAYBE_INIT: MaybeUninit<$t> = MaybeUninit::uninit(); + static mut SLAB_POOL: [MaybeUninit<$t>; $v] = [MAYBE_INIT; $v]; + static mut SLAB_SPACE: Option> = None; + static mut INIT: Once = Once::new(); + unsafe { + INIT.call_once(|| { + SLAB_SPACE = Some(Slab::<$name>::init(&mut SLAB_POOL, $v)); + }); + SLAB_SPACE.as_ref().unwrap() + } + } + } + }; +} + +pub trait SlabPool { + type SlabType: 'static; + fn get_slab() -> &'static Slab + where + Self: Sized; +} + +pub struct Inner { + pool: &'static mut [MaybeUninit], + map: Bitmap, +} + +// TODO: Instead of a mutex, we should replace this with a CAS loop +pub struct Slab(Mutex>); + +impl Slab { + pub fn init(pool: &'static mut [MaybeUninit], size: usize) -> Self { + Self(Mutex::new(Inner { + pool, + map: Bitmap::new(size), + })) + } + + pub fn new(new_object: T::SlabType) -> Option> { + let slab = T::get_slab(); + let mut inner = slab.0.lock().unwrap(); + if let Some(index) = inner.map.first_false_index() { + inner.map.set(index); + inner.pool[index].write(new_object); + let cell_ptr = unsafe { &mut *inner.pool[index].as_mut_ptr() }; + Some(BoxSlab { + data: cell_ptr, + index, + }) + } else { + None + } + } + + pub fn free(&self, index: usize) { + let mut inner = self.0.lock().unwrap(); + inner.map.reset(index); + let old_value = std::mem::replace(&mut inner.pool[index], MaybeUninit::uninit()); + let _old_value = unsafe { old_value.assume_init() }; + // This will drop the old_value + } +} + +pub struct BoxSlab { + // Because the data is a reference within the MaybeUninit, we don't have a mechanism + // to go out to the MaybeUninit from this reference. Hence this index + index: usize, + // TODO: We should figure out a way to get rid of the index too + data: &'static mut T::SlabType, +} + +impl Drop for BoxSlab { + fn drop(&mut self) { + T::get_slab().free(self.index); + } +} + +impl Deref for BoxSlab { + type Target = T::SlabType; + fn deref(&self) -> &Self::Target { + self.data + } +} + +impl DerefMut for BoxSlab { + fn deref_mut(&mut self) -> &mut Self::Target { + self.data + } +} + +#[cfg(test)] +mod tests { + use std::{ops::Deref, sync::Arc}; + + pub struct Test { + val: Arc, + } + + box_slab!(TestSlab, Test, 3); + + #[test] + fn simple_alloc_free() { + { + let a = Slab::::new(Test { val: Arc::new(10) }).unwrap(); + assert_eq!(*a.val.deref(), 10); + let inner = TestSlab::get_slab().0.lock().unwrap(); + assert_eq!(inner.map.is_empty(), false); + } + // Validates that the 'Drop' got executed + let inner = TestSlab::get_slab().0.lock().unwrap(); + assert_eq!(inner.map.is_empty(), true); + println!("Box Size {}", std::mem::size_of::>()); + println!("BoxSlab Size {}", std::mem::size_of::>()); + } + + #[test] + fn alloc_full_block() { + { + let a = Slab::::new(Test { val: Arc::new(10) }).unwrap(); + let b = Slab::::new(Test { val: Arc::new(11) }).unwrap(); + let c = Slab::::new(Test { val: Arc::new(12) }).unwrap(); + // Test that at overflow, we return None + assert_eq!( + Slab::::new(Test { val: Arc::new(13) }).is_none(), + true + ); + assert_eq!(*b.val.deref(), 11); + + { + let inner = TestSlab::get_slab().0.lock().unwrap(); + // Test that the bitmap is marked as full + assert_eq!(inner.map.is_full(), true); + } + + // Purposefully drop, to test that new allocation is possible + std::mem::drop(b); + let d = Slab::::new(Test { val: Arc::new(21) }).unwrap(); + assert_eq!(*d.val.deref(), 21); + + // Ensure older allocations are still valid + assert_eq!(*a.val.deref(), 10); + assert_eq!(*c.val.deref(), 12); + } + + // Validates that the 'Drop' got executed - test that the bitmap is empty + let inner = TestSlab::get_slab().0.lock().unwrap(); + assert_eq!(inner.map.is_empty(), true); + } + + #[test] + fn test_drop_logic() { + let root = Arc::new(10); + { + let _a = Slab::::new(Test { val: root.clone() }).unwrap(); + let _b = Slab::::new(Test { val: root.clone() }).unwrap(); + let _c = Slab::::new(Test { val: root.clone() }).unwrap(); + assert_eq!(Arc::strong_count(&root), 4); + } + // Test that Drop was correctly called on all the members of the pool + assert_eq!(Arc::strong_count(&root), 1); + } +} diff --git a/examples/onoff_light/src/dev_att.rs b/examples/onoff_light/src/dev_att.rs new file mode 100644 index 0000000..a16d53f --- /dev/null +++ b/examples/onoff_light/src/dev_att.rs @@ -0,0 +1,165 @@ +/* + * + * 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::data_model::sdm::dev_att::{DataType, DevAttDataFetcher}; +use matter::error::Error; + +pub struct HardCodedDevAtt {} + +impl HardCodedDevAtt { + pub fn new() -> Self { + Self {} + } +} + +// credentials/examples/ExamplePAI.cpp FFF1 +const PAI_CERT: [u8; 463] = [ + 0x30, 0x82, 0x01, 0xcb, 0x30, 0x82, 0x01, 0x71, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x08, 0x56, + 0xad, 0x82, 0x22, 0xad, 0x94, 0x5b, 0x64, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, + 0x04, 0x03, 0x02, 0x30, 0x30, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x0f, + 0x4d, 0x61, 0x74, 0x74, 0x65, 0x72, 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, 0x50, 0x41, 0x41, 0x31, + 0x14, 0x30, 0x12, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xa2, 0x7c, 0x02, 0x01, 0x0c, + 0x04, 0x46, 0x46, 0x46, 0x31, 0x30, 0x20, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x32, 0x30, 0x35, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x18, 0x0f, 0x39, 0x39, 0x39, 0x39, 0x31, 0x32, 0x33, 0x31, + 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x3d, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, + 0x04, 0x03, 0x0c, 0x1c, 0x4d, 0x61, 0x74, 0x74, 0x65, 0x72, 0x20, 0x44, 0x65, 0x76, 0x20, 0x50, + 0x41, 0x49, 0x20, 0x30, 0x78, 0x46, 0x46, 0x46, 0x31, 0x20, 0x6e, 0x6f, 0x20, 0x50, 0x49, 0x44, + 0x31, 0x14, 0x30, 0x12, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xa2, 0x7c, 0x02, 0x01, + 0x0c, 0x04, 0x46, 0x46, 0x46, 0x31, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, + 0x04, 0x41, 0x9a, 0x93, 0x15, 0xc2, 0x17, 0x3e, 0x0c, 0x8c, 0x87, 0x6d, 0x03, 0xcc, 0xfc, 0x94, + 0x48, 0x52, 0x64, 0x7f, 0x7f, 0xec, 0x5e, 0x50, 0x82, 0xf4, 0x05, 0x99, 0x28, 0xec, 0xa8, 0x94, + 0xc5, 0x94, 0x15, 0x13, 0x09, 0xac, 0x63, 0x1e, 0x4c, 0xb0, 0x33, 0x92, 0xaf, 0x68, 0x4b, 0x0b, + 0xaf, 0xb7, 0xe6, 0x5b, 0x3b, 0x81, 0x62, 0xc2, 0xf5, 0x2b, 0xf9, 0x31, 0xb8, 0xe7, 0x7a, 0xaa, + 0x82, 0xa3, 0x66, 0x30, 0x64, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, + 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, + 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, + 0x04, 0x16, 0x04, 0x14, 0x63, 0x54, 0x0e, 0x47, 0xf6, 0x4b, 0x1c, 0x38, 0xd1, 0x38, 0x84, 0xa4, + 0x62, 0xd1, 0x6c, 0x19, 0x5d, 0x8f, 0xfb, 0x3c, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, + 0x18, 0x30, 0x16, 0x80, 0x14, 0x6a, 0xfd, 0x22, 0x77, 0x1f, 0x51, 0x1f, 0xec, 0xbf, 0x16, 0x41, + 0x97, 0x67, 0x10, 0xdc, 0xdc, 0x31, 0xa1, 0x71, 0x7e, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, + 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x48, 0x00, 0x30, 0x45, 0x02, 0x21, 0x00, 0xb2, 0xef, 0x27, + 0xf4, 0x9a, 0xe9, 0xb5, 0x0f, 0xb9, 0x1e, 0xea, 0xc9, 0x4c, 0x4d, 0x0b, 0xdb, 0xb8, 0xd7, 0x92, + 0x9c, 0x6c, 0xb8, 0x8f, 0xac, 0xe5, 0x29, 0x36, 0x8d, 0x12, 0x05, 0x4c, 0x0c, 0x02, 0x20, 0x65, + 0x5d, 0xc9, 0x2b, 0x86, 0xbd, 0x90, 0x98, 0x82, 0xa6, 0xc6, 0x21, 0x77, 0xb8, 0x25, 0xd7, 0xd0, + 0x5e, 0xdb, 0xe7, 0xc2, 0x2f, 0x9f, 0xea, 0x71, 0x22, 0x0e, 0x7e, 0xa7, 0x03, 0xf8, 0x91, +]; + +// credentials/examples/ExampleDACs.cpp FFF1-8000-0002-Cert +const DAC_CERT: [u8; 492] = [ + 0x30, 0x82, 0x01, 0xe8, 0x30, 0x82, 0x01, 0x8e, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x08, 0x52, + 0x72, 0x4d, 0x21, 0xe2, 0xc1, 0x74, 0xaf, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, + 0x04, 0x03, 0x02, 0x30, 0x3d, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x1c, + 0x4d, 0x61, 0x74, 0x74, 0x65, 0x72, 0x20, 0x44, 0x65, 0x76, 0x20, 0x50, 0x41, 0x49, 0x20, 0x30, + 0x78, 0x46, 0x46, 0x46, 0x31, 0x20, 0x6e, 0x6f, 0x20, 0x50, 0x49, 0x44, 0x31, 0x14, 0x30, 0x12, + 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xa2, 0x7c, 0x02, 0x01, 0x0c, 0x04, 0x46, 0x46, + 0x46, 0x31, 0x30, 0x20, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x32, 0x30, 0x35, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x5a, 0x18, 0x0f, 0x39, 0x39, 0x39, 0x39, 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, + 0x39, 0x35, 0x39, 0x5a, 0x30, 0x53, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, + 0x1c, 0x4d, 0x61, 0x74, 0x74, 0x65, 0x72, 0x20, 0x44, 0x65, 0x76, 0x20, 0x44, 0x41, 0x43, 0x20, + 0x30, 0x78, 0x46, 0x46, 0x46, 0x31, 0x2f, 0x30, 0x78, 0x38, 0x30, 0x30, 0x32, 0x31, 0x14, 0x30, + 0x12, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xa2, 0x7c, 0x02, 0x01, 0x0c, 0x04, 0x46, + 0x46, 0x46, 0x31, 0x31, 0x14, 0x30, 0x12, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xa2, + 0x7c, 0x02, 0x02, 0x0c, 0x04, 0x38, 0x30, 0x30, 0x32, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, + 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, + 0x03, 0x42, 0x00, 0x04, 0xda, 0x93, 0xf1, 0x67, 0x36, 0x25, 0x67, 0x50, 0xd9, 0x03, 0xb0, 0x34, + 0xba, 0x45, 0x88, 0xab, 0xaf, 0x58, 0x95, 0x4f, 0x77, 0xaa, 0x9f, 0xd9, 0x98, 0x9d, 0xfd, 0x40, + 0x0d, 0x7a, 0xb3, 0xfd, 0xc9, 0x75, 0x3b, 0x3b, 0x92, 0x1b, 0x29, 0x4c, 0x95, 0x0f, 0xd9, 0xd2, + 0x80, 0xd1, 0x4c, 0x43, 0x86, 0x2f, 0x16, 0xdc, 0x85, 0x4b, 0x00, 0xed, 0x39, 0xe7, 0x50, 0xba, + 0xbf, 0x1d, 0xc4, 0xca, 0xa3, 0x60, 0x30, 0x5e, 0x30, 0x0c, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, + 0x01, 0xff, 0x04, 0x02, 0x30, 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, + 0x04, 0x04, 0x03, 0x02, 0x07, 0x80, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, + 0x14, 0xef, 0x06, 0x56, 0x11, 0x9c, 0x1c, 0x91, 0xa7, 0x9a, 0x94, 0xe6, 0xdc, 0xf3, 0x79, 0x79, + 0xdb, 0xd0, 0x7f, 0xf8, 0xa3, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, + 0x80, 0x14, 0x63, 0x54, 0x0e, 0x47, 0xf6, 0x4b, 0x1c, 0x38, 0xd1, 0x38, 0x84, 0xa4, 0x62, 0xd1, + 0x6c, 0x19, 0x5d, 0x8f, 0xfb, 0x3c, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, + 0x03, 0x02, 0x03, 0x48, 0x00, 0x30, 0x45, 0x02, 0x20, 0x46, 0x86, 0x81, 0x07, 0x33, 0xbf, 0x0d, + 0xc8, 0xff, 0x4c, 0xb5, 0x14, 0x5a, 0x6b, 0xfa, 0x1a, 0xec, 0xff, 0xa8, 0xb6, 0xda, 0xb6, 0xc3, + 0x51, 0xaa, 0xee, 0xcd, 0xaf, 0xb8, 0xbe, 0x95, 0x7d, 0x02, 0x21, 0x00, 0xe8, 0xc2, 0x8d, 0x6b, + 0xfc, 0xc8, 0x7a, 0x7d, 0x54, 0x2e, 0xad, 0x6e, 0xda, 0xca, 0x14, 0x8d, 0x5f, 0xa5, 0x06, 0x1e, + 0x51, 0x7c, 0xbe, 0x4f, 0x24, 0xa7, 0x20, 0xe1, 0xc0, 0x59, 0xde, 0x1a, +]; + +const DAC_PUBKEY: [u8; 65] = [ + 0x04, 0xda, 0x93, 0xf1, 0x67, 0x36, 0x25, 0x67, 0x50, 0xd9, 0x03, 0xb0, 0x34, 0xba, 0x45, 0x88, + 0xab, 0xaf, 0x58, 0x95, 0x4f, 0x77, 0xaa, 0x9f, 0xd9, 0x98, 0x9d, 0xfd, 0x40, 0x0d, 0x7a, 0xb3, + 0xfd, 0xc9, 0x75, 0x3b, 0x3b, 0x92, 0x1b, 0x29, 0x4c, 0x95, 0x0f, 0xd9, 0xd2, 0x80, 0xd1, 0x4c, + 0x43, 0x86, 0x2f, 0x16, 0xdc, 0x85, 0x4b, 0x00, 0xed, 0x39, 0xe7, 0x50, 0xba, 0xbf, 0x1d, 0xc4, + 0xca, +]; + +const DAC_PRIVKEY: [u8; 32] = [ + 0xda, 0xf2, 0x1a, 0x7e, 0xa4, 0x7a, 0x70, 0x48, 0x02, 0xa7, 0xe6, 0x6c, 0x50, 0xeb, 0x10, 0xba, + 0xc3, 0xbd, 0xd1, 0x68, 0x80, 0x39, 0x80, 0x66, 0xff, 0xda, 0xd7, 0xf5, 0x20, 0x98, 0xb6, 0x85, +]; + +// +const CERT_DECLARATION: [u8; 541] = [ + 0x30, 0x82, 0x02, 0x19, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02, 0xa0, + 0x82, 0x02, 0x0a, 0x30, 0x82, 0x02, 0x06, 0x02, 0x01, 0x03, 0x31, 0x0d, 0x30, 0x0b, 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x30, 0x82, 0x01, 0x71, 0x06, 0x09, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01, 0xa0, 0x82, 0x01, 0x62, 0x04, 0x82, 0x01, 0x5e, + 0x15, 0x24, 0x00, 0x01, 0x25, 0x01, 0xf1, 0xff, 0x36, 0x02, 0x05, 0x00, 0x80, 0x05, 0x01, 0x80, + 0x05, 0x02, 0x80, 0x05, 0x03, 0x80, 0x05, 0x04, 0x80, 0x05, 0x05, 0x80, 0x05, 0x06, 0x80, 0x05, + 0x07, 0x80, 0x05, 0x08, 0x80, 0x05, 0x09, 0x80, 0x05, 0x0a, 0x80, 0x05, 0x0b, 0x80, 0x05, 0x0c, + 0x80, 0x05, 0x0d, 0x80, 0x05, 0x0e, 0x80, 0x05, 0x0f, 0x80, 0x05, 0x10, 0x80, 0x05, 0x11, 0x80, + 0x05, 0x12, 0x80, 0x05, 0x13, 0x80, 0x05, 0x14, 0x80, 0x05, 0x15, 0x80, 0x05, 0x16, 0x80, 0x05, + 0x17, 0x80, 0x05, 0x18, 0x80, 0x05, 0x19, 0x80, 0x05, 0x1a, 0x80, 0x05, 0x1b, 0x80, 0x05, 0x1c, + 0x80, 0x05, 0x1d, 0x80, 0x05, 0x1e, 0x80, 0x05, 0x1f, 0x80, 0x05, 0x20, 0x80, 0x05, 0x21, 0x80, + 0x05, 0x22, 0x80, 0x05, 0x23, 0x80, 0x05, 0x24, 0x80, 0x05, 0x25, 0x80, 0x05, 0x26, 0x80, 0x05, + 0x27, 0x80, 0x05, 0x28, 0x80, 0x05, 0x29, 0x80, 0x05, 0x2a, 0x80, 0x05, 0x2b, 0x80, 0x05, 0x2c, + 0x80, 0x05, 0x2d, 0x80, 0x05, 0x2e, 0x80, 0x05, 0x2f, 0x80, 0x05, 0x30, 0x80, 0x05, 0x31, 0x80, + 0x05, 0x32, 0x80, 0x05, 0x33, 0x80, 0x05, 0x34, 0x80, 0x05, 0x35, 0x80, 0x05, 0x36, 0x80, 0x05, + 0x37, 0x80, 0x05, 0x38, 0x80, 0x05, 0x39, 0x80, 0x05, 0x3a, 0x80, 0x05, 0x3b, 0x80, 0x05, 0x3c, + 0x80, 0x05, 0x3d, 0x80, 0x05, 0x3e, 0x80, 0x05, 0x3f, 0x80, 0x05, 0x40, 0x80, 0x05, 0x41, 0x80, + 0x05, 0x42, 0x80, 0x05, 0x43, 0x80, 0x05, 0x44, 0x80, 0x05, 0x45, 0x80, 0x05, 0x46, 0x80, 0x05, + 0x47, 0x80, 0x05, 0x48, 0x80, 0x05, 0x49, 0x80, 0x05, 0x4a, 0x80, 0x05, 0x4b, 0x80, 0x05, 0x4c, + 0x80, 0x05, 0x4d, 0x80, 0x05, 0x4e, 0x80, 0x05, 0x4f, 0x80, 0x05, 0x50, 0x80, 0x05, 0x51, 0x80, + 0x05, 0x52, 0x80, 0x05, 0x53, 0x80, 0x05, 0x54, 0x80, 0x05, 0x55, 0x80, 0x05, 0x56, 0x80, 0x05, + 0x57, 0x80, 0x05, 0x58, 0x80, 0x05, 0x59, 0x80, 0x05, 0x5a, 0x80, 0x05, 0x5b, 0x80, 0x05, 0x5c, + 0x80, 0x05, 0x5d, 0x80, 0x05, 0x5e, 0x80, 0x05, 0x5f, 0x80, 0x05, 0x60, 0x80, 0x05, 0x61, 0x80, + 0x05, 0x62, 0x80, 0x05, 0x63, 0x80, 0x18, 0x24, 0x03, 0x16, 0x2c, 0x04, 0x13, 0x5a, 0x49, 0x47, + 0x32, 0x30, 0x31, 0x34, 0x32, 0x5a, 0x42, 0x33, 0x33, 0x30, 0x30, 0x30, 0x33, 0x2d, 0x32, 0x34, + 0x24, 0x05, 0x00, 0x24, 0x06, 0x00, 0x25, 0x07, 0x94, 0x26, 0x24, 0x08, 0x00, 0x18, 0x31, 0x7d, + 0x30, 0x7b, 0x02, 0x01, 0x03, 0x80, 0x14, 0x62, 0xfa, 0x82, 0x33, 0x59, 0xac, 0xfa, 0xa9, 0x96, + 0x3e, 0x1c, 0xfa, 0x14, 0x0a, 0xdd, 0xf5, 0x04, 0xf3, 0x71, 0x60, 0x30, 0x0b, 0x06, 0x09, 0x60, + 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x04, 0x03, 0x02, 0x04, 0x47, 0x30, 0x45, 0x02, 0x20, 0x24, 0xe5, 0xd1, 0xf4, 0x7a, 0x7d, + 0x7b, 0x0d, 0x20, 0x6a, 0x26, 0xef, 0x69, 0x9b, 0x7c, 0x97, 0x57, 0xb7, 0x2d, 0x46, 0x90, 0x89, + 0xde, 0x31, 0x92, 0xe6, 0x78, 0xc7, 0x45, 0xe7, 0xf6, 0x0c, 0x02, 0x21, 0x00, 0xf8, 0xaa, 0x2f, + 0xa7, 0x11, 0xfc, 0xb7, 0x9b, 0x97, 0xe3, 0x97, 0xce, 0xda, 0x66, 0x7b, 0xae, 0x46, 0x4e, 0x2b, + 0xd3, 0xff, 0xdf, 0xc3, 0xcc, 0xed, 0x7a, 0xa8, 0xca, 0x5f, 0x4c, 0x1a, 0x7c, +]; + +impl DevAttDataFetcher for HardCodedDevAtt { + fn get_devatt_data(&self, data_type: DataType, data: &mut [u8]) -> Result { + let src = match data_type { + DataType::CertDeclaration => &CERT_DECLARATION[..], + DataType::PAI => &PAI_CERT[..], + DataType::DAC => &DAC_CERT[..], + DataType::DACPubKey => &DAC_PUBKEY[..], + DataType::DACPrivKey => &DAC_PRIVKEY[..], + }; + if src.len() <= data.len() { + let data = &mut data[0..src.len()]; + data.copy_from_slice(src); + Ok(src.len()) + } else { + Err(Error::NoSpace) + } + } +} diff --git a/examples/onoff_light/src/lib.rs b/examples/onoff_light/src/lib.rs new file mode 100644 index 0000000..43ca1b1 --- /dev/null +++ b/examples/onoff_light/src/lib.rs @@ -0,0 +1,18 @@ +/* + * + * 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. + */ + +pub mod dev_att; diff --git a/examples/onoff_light/src/main.rs b/examples/onoff_light/src/main.rs new file mode 100644 index 0000000..15bfc4f --- /dev/null +++ b/examples/onoff_light/src/main.rs @@ -0,0 +1,53 @@ +/* + * + * 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. + */ + +mod dev_att; +use matter::core::{self, CommissioningData}; +use matter::data_model::cluster_basic_information::BasicInfoConfig; +use matter::data_model::device_types::device_type_add_on_off_light; +use rand::prelude::*; + +fn main() { + env_logger::init(); + let mut comm_data = CommissioningData { + // TODO: Hard-coded for now + passwd: 123456, + discriminator: 250, + ..Default::default() + }; + rand::thread_rng().fill_bytes(&mut comm_data.salt); + + // vid/pid should match those in the DAC + let dev_info = BasicInfoConfig { + vid: 0xFFF1, + pid: 0x8002, + hw_ver: 2, + sw_ver: 1, + }; + let dev_att = Box::new(dev_att::HardCodedDevAtt::new()); + + let mut matter = core::Matter::new(dev_info, dev_att, comm_data).unwrap(); + let dm = matter.get_data_model(); + { + let mut node = dm.node.write().unwrap(); + let endpoint = device_type_add_on_off_light(&mut node).unwrap(); + println!("Added OnOff Light Device type at endpoint id: {}", endpoint); + println!("Data Model now is: {}", node); + } + + matter.start_daemon().unwrap(); +} diff --git a/matter/Cargo.toml b/matter/Cargo.toml new file mode 100644 index 0000000..32974f8 --- /dev/null +++ b/matter/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "matter-iot" +version = "0.1.0" +edition = "2018" +authors = ["Kedar Sovani "] +description = "Native RUST implementation of the Matter (Smart-Home) ecosystem" +repository = "https://github.com/kedars/matter-rs" +readme = "README.md" +keywords = ["matter", "smart", "smart-home", "IoT", "ESP32"] +categories = ["embedded", "network-programming"] +license = "MIT" + +[lib] +name = "matter" +path = "src/lib.rs" + +[features] +default = ["crypto_mbedtls"] +crypto_openssl = ["openssl", "foreign-types", "hmac", "sha2"] +crypto_mbedtls = ["mbedtls"] +crypto_esp_mbedtls = ["esp-idf-sys"] + +[dependencies] +boxslab = { path = "../boxslab"} +matter_macro_derive = { path = "../matter_macro_derive"} +bitflags = "1.3" +byteorder = "1.4.3" +heapless = {version = "0.7.7", features = ["x86-sync-pool"] } +generic-array = "0.14.5" +num = "0.3" +num-derive = "0.3.3" +num-traits = "0.2.14" +log = { version = "0.4.14", features = ["max_level_debug", "release_max_level_debug"] } +env_logger = "0.9.0" +rand = "0.8.4" +esp-idf-sys = { version = "0.30", features = ["binstart"], optional = true } +openssl = { git = "https://github.com/sfackler/rust-openssl", optional = true} +foreign-types = { version = "0.3.1", optional = true} +sha2 = { version = "0.9.8", optional = true} +hmac = { version = "0.11.0", optional = true} +mbedtls = { git = "https://github.com/fortanix/rust-mbedtls", optional = true} +subtle = "2.4.1" +colored = "2.0.0" +smol = "1.2.5" +owning_ref = "0.4.1" +safemem = "0.3.3" +chrono = { version = "0.4.19", default-features = false, features = ["clock", "std"] } +async-channel = "1.6" + +[target.'cfg(target_os = "macos")'.dependencies] +astro-dnssd = "0.3" + +[[example]] +name = "onoff_light" +path = "../examples/onoff_light/src/main.rs" diff --git a/matter/src/acl.rs b/matter/src/acl.rs new file mode 100644 index 0000000..433f1cb --- /dev/null +++ b/matter/src/acl.rs @@ -0,0 +1,649 @@ +/* + * + * 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::sync::{Arc, Mutex, MutexGuard, RwLock}; + +use crate::{ + data_model::objects::{Access, Privilege}, + error::Error, + fabric, + interaction_model::messages::GenericPath, + sys::Psm, + tlv::{FromTLV, TLVElement, TLVList, TLVWriter, TagType, ToTLV}, + utils::writebuf::WriteBuf, +}; +use log::error; +use num_derive::FromPrimitive; + +// Matter Minimum Requirements +pub const SUBJECTS_PER_ENTRY: usize = 4; +pub const TARGETS_PER_ENTRY: usize = 3; +pub const ENTRIES_PER_FABRIC: usize = 3; + +// TODO: Check if this and the SessionMode can be combined into some generic data structure +#[derive(FromPrimitive, Copy, Clone, PartialEq, Debug)] +pub enum AuthMode { + Pase = 1, + Case = 2, + Group = 3, + Invalid = 4, +} + +impl FromTLV<'_> for AuthMode { + fn from_tlv(t: &TLVElement) -> Result + where + Self: Sized, + { + num::FromPrimitive::from_u32(t.u32()?) + .filter(|a| *a != AuthMode::Invalid) + .ok_or(Error::Invalid) + } +} + +impl ToTLV for AuthMode { + fn to_tlv( + &self, + tw: &mut crate::tlv::TLVWriter, + tag: crate::tlv::TagType, + ) -> Result<(), Error> { + match self { + AuthMode::Invalid => Ok(()), + _ => tw.u8(tag, *self as u8), + } + } +} + +/// The Accessor Object +pub struct Accessor { + /// The fabric index of the accessor + pub fab_idx: u8, + /// Accessor's identified: could be node-id, NoC CAT, group id + id: u64, + /// The Authmode of this session + auth_mode: AuthMode, + // TODO: Is this the right place for this though, or should we just use a global-acl-handle-get + acl_mgr: Arc, +} + +impl Accessor { + pub fn new(fab_idx: u8, id: u64, auth_mode: AuthMode, acl_mgr: Arc) -> Self { + Self { + fab_idx, + id, + auth_mode, + acl_mgr, + } + } +} + +#[derive(Debug)] +pub struct AccessDesc<'a> { + /// The object to be acted upon + path: &'a GenericPath, + /// The target permissions + target_perms: Option, + // The operation being done + // TODO: Currently this is Access, but we need a way to represent the 'invoke' somehow too + operation: Access, +} + +/// Access Request Object +pub struct AccessReq<'a> { + accessor: &'a Accessor, + object: AccessDesc<'a>, +} + +impl<'a> AccessReq<'a> { + /// Creates an access request object + /// + /// An access request specifies the _accessor_ attempting to access _path_ + /// with _operation_ + pub fn new(accessor: &'a Accessor, path: &'a GenericPath, operation: Access) -> Self { + AccessReq { + accessor, + object: AccessDesc { + path, + target_perms: None, + operation, + }, + } + } + + /// Add target's permissions to the request + /// + /// The permissions that are associated with the target (identified by the + /// path in the AccessReq) are added to the request + pub fn set_target_perms(&mut self, perms: Access) { + self.object.target_perms = Some(perms); + } + + /// Checks if access is allowed + /// + /// This checks all the ACL list to identify if any of the ACLs provides the + /// _accessor_ the necessary privileges to access the target as per its + /// permissions + pub fn allow(&self) -> bool { + self.accessor.acl_mgr.allow(self) + } +} + +#[derive(FromTLV, ToTLV, Copy, Clone, Debug, PartialEq)] +pub struct Target { + cluster: Option, + endpoint: Option, + device_type: Option, +} + +impl Target { + pub fn new(endpoint: Option, cluster: Option, device_type: Option) -> Self { + Self { + cluster, + endpoint, + device_type, + } + } +} + +type Subjects = [Option; SUBJECTS_PER_ENTRY]; +type Targets = [Option; TARGETS_PER_ENTRY]; +#[derive(ToTLV, FromTLV, Copy, Clone, Debug, PartialEq)] +#[tlvargs(start = 1)] +pub struct AclEntry { + privilege: Privilege, + auth_mode: AuthMode, + subjects: Subjects, + targets: Targets, + // TODO: Instead of the direct value, we should consider GlobalElements::FabricIndex + #[tagval(0xFE)] + pub fab_idx: Option, +} + +impl AclEntry { + pub fn new(fab_idx: u8, privilege: Privilege, auth_mode: AuthMode) -> Self { + const INIT_SUBJECTS: Option = None; + const INIT_TARGETS: Option = None; + let privilege = privilege; + Self { + fab_idx: Some(fab_idx), + privilege, + auth_mode, + subjects: [INIT_SUBJECTS; SUBJECTS_PER_ENTRY], + targets: [INIT_TARGETS; TARGETS_PER_ENTRY], + } + } + + pub fn add_subject(&mut self, subject: u64) -> Result<(), Error> { + let index = self + .subjects + .iter() + .position(|s| s.is_none()) + .ok_or(Error::NoSpace)?; + self.subjects[index] = Some(subject); + Ok(()) + } + + pub fn add_target(&mut self, target: Target) -> Result<(), Error> { + let index = self + .targets + .iter() + .position(|s| s.is_none()) + .ok_or(Error::NoSpace)?; + self.targets[index] = Some(target); + Ok(()) + } + + fn match_accessor(&self, accessor: &Accessor) -> bool { + if self.auth_mode != accessor.auth_mode { + return false; + } + + let mut allow = false; + let mut entries_exist = false; + for i in self.subjects.iter().flatten() { + entries_exist = true; + if accessor.id == *i { + allow = true; + } + } + if !entries_exist { + // Subjects array empty implies allow for all subjects + allow = true; + } + + // true if both are true + allow && self.fab_idx == Some(accessor.fab_idx) + } + + fn match_access_desc(&self, object: &AccessDesc) -> bool { + let mut allow = false; + let mut entries_exist = false; + for t in self.targets.iter().flatten() { + entries_exist = true; + if (t.endpoint.is_none() || t.endpoint == object.path.endpoint) + && (t.cluster.is_none() || t.cluster == object.path.cluster) + { + allow = true + } + } + if !entries_exist { + // Targets array empty implies allow for all targets + allow = true; + } + + if allow { + // Check that the object's access allows this operation with this privilege + if let Some(access) = object.target_perms { + access.is_ok(object.operation, self.privilege) + } else { + false + } + } else { + false + } + } + + pub fn allow(&self, req: &AccessReq) -> bool { + self.match_accessor(req.accessor) && self.match_access_desc(&req.object) + } +} + +const MAX_ACL_ENTRIES: usize = ENTRIES_PER_FABRIC * fabric::MAX_SUPPORTED_FABRICS; +type AclEntries = [Option; MAX_ACL_ENTRIES]; + +#[derive(ToTLV, FromTLV, Debug)] +struct AclMgrInner { + entries: AclEntries, +} + +const ACL_KV_ENTRY: &str = "acl"; +const ACL_KV_MAX_SIZE: usize = 300; +impl AclMgrInner { + pub fn store(&self, psm: &MutexGuard) -> Result<(), Error> { + let mut acl_tlvs = [0u8; ACL_KV_MAX_SIZE]; + let mut wb = WriteBuf::new(&mut acl_tlvs, ACL_KV_MAX_SIZE); + let mut tw = TLVWriter::new(&mut wb); + self.entries.to_tlv(&mut tw, TagType::Anonymous)?; + psm.set_kv_slice(ACL_KV_ENTRY, wb.as_slice()) + } + + pub fn load(psm: &MutexGuard) -> Result { + let mut acl_tlvs = Vec::new(); + psm.get_kv_slice(ACL_KV_ENTRY, &mut acl_tlvs)?; + let root = TLVList::new(&acl_tlvs) + .iter() + .next() + .ok_or(Error::Invalid)?; + + Ok(Self { + entries: AclEntries::from_tlv(&root)?, + }) + } + + /// Traverse fabric specific entries to find the index + /// + /// If the ACL Mgr has 3 entries with fabric indexes, 1, 2, 1, then the list + /// index 1 for Fabric 1 in the ACL Mgr will be the actual index 2 (starting from 0) + fn for_index_in_fabric( + &mut self, + index: u8, + fab_idx: u8, + ) -> Result<&mut Option, Error> { + // Can't use flatten as we need to borrow the Option<> not the 'AclEntry' + for (curr_index, entry) in self + .entries + .iter_mut() + .filter(|e| e.filter(|e1| e1.fab_idx == Some(fab_idx)).is_some()) + .enumerate() + { + if curr_index == index as usize { + return Ok(entry); + } + } + Err(Error::NotFound) + } +} + +pub struct AclMgr { + inner: RwLock, + // The Option<> is solely because test execution is faster + // Doing this here adds the least overhead during ACL verification + psm: Option>>, +} + +impl AclMgr { + pub fn new() -> Result { + AclMgr::new_with(true) + } + + pub fn new_with(psm_support: bool) -> Result { + const INIT: Option = None; + let mut psm = None; + + let inner = if !psm_support { + AclMgrInner { + entries: [INIT; MAX_ACL_ENTRIES], + } + } else { + let psm_handle = Psm::get()?; + let inner = { + let psm_lock = psm_handle.lock().unwrap(); + AclMgrInner::load(&psm_lock) + }; + + psm = Some(psm_handle); + inner.unwrap_or({ + // Error loading from PSM + AclMgrInner { + entries: [INIT; MAX_ACL_ENTRIES], + } + }) + }; + Ok(Self { + inner: RwLock::new(inner), + psm, + }) + } + + pub fn erase_all(&self) { + let mut inner = self.inner.write().unwrap(); + for i in 0..MAX_ACL_ENTRIES { + inner.entries[i] = None; + } + if let Some(psm) = self.psm.as_ref() { + let psm = psm.lock().unwrap(); + let _ = inner.store(&psm).map_err(|e| { + error!("Error in storing ACLs {}", e); + }); + } + } + + pub fn add(&self, entry: AclEntry) -> Result<(), Error> { + let mut inner = self.inner.write().unwrap(); + let cnt = inner + .entries + .iter() + .flatten() + .filter(|a| a.fab_idx == entry.fab_idx) + .count(); + if cnt >= ENTRIES_PER_FABRIC { + return Err(Error::NoSpace); + } + let index = inner + .entries + .iter() + .position(|a| a.is_none()) + .ok_or(Error::NoSpace)?; + inner.entries[index] = Some(entry); + + if let Some(psm) = self.psm.as_ref() { + let psm = psm.lock().unwrap(); + inner.store(&psm) + } else { + Ok(()) + } + } + + // Since the entries are fabric-scoped, the index is only for entries with the matching fabric index + pub fn edit(&self, index: u8, fab_idx: u8, new: AclEntry) -> Result<(), Error> { + let mut inner = self.inner.write().unwrap(); + let old = inner.for_index_in_fabric(index, fab_idx)?; + *old = Some(new); + + if let Some(psm) = self.psm.as_ref() { + let psm = psm.lock().unwrap(); + inner.store(&psm) + } else { + Ok(()) + } + } + + pub fn delete(&self, index: u8, fab_idx: u8) -> Result<(), Error> { + let mut inner = self.inner.write().unwrap(); + let old = inner.for_index_in_fabric(index, fab_idx)?; + *old = None; + + if let Some(psm) = self.psm.as_ref() { + let psm = psm.lock().unwrap(); + inner.store(&psm) + } else { + Ok(()) + } + } + + pub fn delete_for_fabric(&self, fab_idx: u8) -> Result<(), Error> { + let mut inner = self.inner.write().unwrap(); + + for i in 0..MAX_ACL_ENTRIES { + if inner.entries[i] + .filter(|e| e.fab_idx == Some(fab_idx)) + .is_some() + { + inner.entries[i] = None; + } + } + + if let Some(psm) = self.psm.as_ref() { + let psm = psm.lock().unwrap(); + inner.store(&psm) + } else { + Ok(()) + } + } + + pub fn for_each_acl(&self, mut f: T) -> Result<(), Error> + where + T: FnMut(&AclEntry), + { + let inner = self.inner.read().unwrap(); + for entry in inner.entries.iter().flatten() { + f(entry) + } + Ok(()) + } + + pub fn allow(&self, req: &AccessReq) -> bool { + // PASE Sessions have implicit access grant + if req.accessor.auth_mode == AuthMode::Pase { + return true; + } + let inner = self.inner.read().unwrap(); + for e in inner.entries.iter().flatten() { + if e.allow(req) { + return true; + } + } + error!( + "ACL Disallow for src id {} fab idx {}", + req.accessor.id, req.accessor.fab_idx + ); + error!("{}", self); + false + } +} + +impl std::fmt::Display for AclMgr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let inner = self.inner.read().unwrap(); + write!(f, "ACLS: [")?; + for i in inner.entries.iter().flatten() { + write!(f, " {{ {:?} }}, ", i)?; + } + write!(f, "]") + } +} + +#[cfg(test)] +mod tests { + use crate::{ + data_model::objects::{Access, Privilege}, + interaction_model::messages::GenericPath, + }; + use std::sync::Arc; + + use super::{AccessReq, Accessor, AclEntry, AclMgr, AuthMode, Target}; + + #[test] + fn test_basic_empty_subject_target() { + let am = Arc::new(AclMgr::new_with(false).unwrap()); + am.erase_all(); + let accessor = Accessor::new(2, 112233, AuthMode::Case, am.clone()); + let path = GenericPath::new(Some(1), Some(1234), None); + let mut req = AccessReq::new(&accessor, &path, Access::READ); + req.set_target_perms(Access::RWVA); + + // Default deny + assert_eq!(req.allow(), false); + + // Deny for session mode mismatch + let new = AclEntry::new(1, Privilege::VIEW, AuthMode::Pase); + am.add(new).unwrap(); + assert_eq!(req.allow(), false); + + // Deny for fab idx mismatch + let new = AclEntry::new(1, Privilege::VIEW, AuthMode::Case); + am.add(new).unwrap(); + assert_eq!(req.allow(), false); + + // Allow + let new = AclEntry::new(2, Privilege::VIEW, AuthMode::Case); + am.add(new).unwrap(); + assert_eq!(req.allow(), true); + } + + #[test] + fn test_subject() { + let am = Arc::new(AclMgr::new_with(false).unwrap()); + am.erase_all(); + let accessor = Accessor::new(2, 112233, AuthMode::Case, am.clone()); + let path = GenericPath::new(Some(1), Some(1234), None); + let mut req = AccessReq::new(&accessor, &path, Access::READ); + req.set_target_perms(Access::RWVA); + + // Deny for subject mismatch + let mut new = AclEntry::new(2, Privilege::VIEW, AuthMode::Case); + new.add_subject(112232).unwrap(); + am.add(new).unwrap(); + assert_eq!(req.allow(), false); + + // Allow for subject match - target is wildcard + let mut new = AclEntry::new(2, Privilege::VIEW, AuthMode::Case); + new.add_subject(112233).unwrap(); + am.add(new).unwrap(); + assert_eq!(req.allow(), true); + } + + #[test] + fn test_target() { + let am = Arc::new(AclMgr::new_with(false).unwrap()); + am.erase_all(); + let accessor = Accessor::new(2, 112233, AuthMode::Case, am.clone()); + let path = GenericPath::new(Some(1), Some(1234), None); + let mut req = AccessReq::new(&accessor, &path, Access::READ); + req.set_target_perms(Access::RWVA); + + // Deny for target mismatch + let mut new = AclEntry::new(2, Privilege::VIEW, AuthMode::Case); + new.add_target(Target { + cluster: Some(2), + endpoint: Some(4567), + device_type: None, + }) + .unwrap(); + am.add(new).unwrap(); + assert_eq!(req.allow(), false); + + // Allow for cluster match - subject wildcard + let mut new = AclEntry::new(2, Privilege::VIEW, AuthMode::Case); + new.add_target(Target { + cluster: Some(1234), + endpoint: None, + device_type: None, + }) + .unwrap(); + am.add(new).unwrap(); + assert_eq!(req.allow(), true); + + // Clean Slate + am.erase_all(); + + // Allow for endpoint match - subject wildcard + let mut new = AclEntry::new(2, Privilege::VIEW, AuthMode::Case); + new.add_target(Target { + cluster: None, + endpoint: Some(1), + device_type: None, + }) + .unwrap(); + am.add(new).unwrap(); + assert_eq!(req.allow(), true); + + // Clean Slate + am.erase_all(); + + // Allow for exact match + let mut new = AclEntry::new(2, Privilege::VIEW, AuthMode::Case); + new.add_target(Target { + cluster: Some(1234), + endpoint: Some(1), + device_type: None, + }) + .unwrap(); + new.add_subject(112233).unwrap(); + am.add(new).unwrap(); + assert_eq!(req.allow(), true); + } + + #[test] + fn test_privilege() { + let am = Arc::new(AclMgr::new_with(false).unwrap()); + am.erase_all(); + let accessor = Accessor::new(2, 112233, AuthMode::Case, am.clone()); + let path = GenericPath::new(Some(1), Some(1234), None); + + // Create an Exact Match ACL with View privilege + let mut new = AclEntry::new(2, Privilege::VIEW, AuthMode::Case); + new.add_target(Target { + cluster: Some(1234), + endpoint: Some(1), + device_type: None, + }) + .unwrap(); + new.add_subject(112233).unwrap(); + am.add(new).unwrap(); + + // Write on an RWVA without admin access - deny + let mut req = AccessReq::new(&accessor, &path, Access::WRITE); + req.set_target_perms(Access::RWVA); + assert_eq!(req.allow(), false); + + // Create an Exact Match ACL with Admin privilege + let mut new = AclEntry::new(2, Privilege::ADMIN, AuthMode::Case); + new.add_target(Target { + cluster: Some(1234), + endpoint: Some(1), + device_type: None, + }) + .unwrap(); + new.add_subject(112233).unwrap(); + am.add(new).unwrap(); + + // Write on an RWVA with admin access - allow + let mut req = AccessReq::new(&accessor, &path, Access::WRITE); + req.set_target_perms(Access::RWVA); + assert_eq!(req.allow(), true); + } +} diff --git a/matter/src/cert/asn1_writer.rs b/matter/src/cert/asn1_writer.rs new file mode 100644 index 0000000..db9fd08 --- /dev/null +++ b/matter/src/cert/asn1_writer.rs @@ -0,0 +1,265 @@ +/* + * + * 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::{CertConsumer, MAX_DEPTH}; +use crate::error::Error; +use chrono::{TimeZone, Utc}; + +#[derive(Debug)] +pub struct ASN1Writer<'a> { + buf: &'a mut [u8], + // The current write offset in the buffer + offset: usize, + // If multiple 'composite' structures are being written, their starts are + // captured in this + depth: [usize; MAX_DEPTH], + // The current depth of operation within the depth stack + current_depth: usize, +} + +const RESERVE_LEN_BYTES: usize = 3; +impl<'a> ASN1Writer<'a> { + pub fn new(buf: &'a mut [u8]) -> Self { + Self { + buf, + offset: 0, + depth: [0; MAX_DEPTH], + current_depth: 0, + } + } + + pub fn append_with(&mut self, size: usize, f: F) -> Result<(), Error> + where + F: FnOnce(&mut Self), + { + if self.offset + size <= self.buf.len() { + f(self); + self.offset += size; + return Ok(()); + } + Err(Error::NoSpace) + } + + pub fn append_tlv(&mut self, tag: u8, len: usize, f: F) -> Result<(), Error> + where + F: FnOnce(&mut Self), + { + let total_len = 1 + ASN1Writer::bytes_to_encode_len(len)? + len; + if self.offset + total_len <= self.buf.len() { + self.buf[self.offset] = tag; + self.offset += 1; + self.offset = self.encode_len(self.offset, len)?; + f(self); + self.offset += len; + return Ok(()); + } + Err(Error::NoSpace) + } + + fn add_compound(&mut self, val: u8) -> Result<(), Error> { + // We reserve 3 bytes for encoding the length + // If a shorter length is actually required, we will move everything back + self.append_with(1 + RESERVE_LEN_BYTES, |t| t.buf[t.offset] = val)?; + self.depth[self.current_depth] = self.offset; + self.current_depth += 1; + if self.current_depth >= MAX_DEPTH { + Err(Error::NoSpace) + } else { + Ok(()) + } + } + + fn encode_len(&mut self, mut at_offset: usize, len: usize) -> Result { + let mut bytes_of_len = ASN1Writer::bytes_to_encode_len(len)?; + if bytes_of_len > 1 { + self.buf[at_offset] = (0x80 | (bytes_of_len - 1)) as u8; + at_offset += 1; + bytes_of_len -= 1; + } + + // At this point bytes_of_len is the actual number of bytes for the length encoding + // after the 0x80 (if it was present) + let mut octet_number = bytes_of_len - 1; + // We start encoding the highest octest first + loop { + self.buf[at_offset] = ((len >> (octet_number * 8)) & 0xff) as u8; + + at_offset += 1; + if octet_number == 0 { + break; + } + octet_number -= 1; + } + + Ok(at_offset) + } + + fn end_compound(&mut self) -> Result<(), Error> { + if self.current_depth == 0 { + return Err(Error::Invalid); + } + let seq_len = self.get_compound_len(); + let write_offset = self.get_length_encoding_offset(); + + let mut write_offset = self.encode_len(write_offset, seq_len)?; + + // Shift everything by as much + let shift_len = self.depth[self.current_depth - 1] - write_offset; + if shift_len > 0 { + for _i in 0..seq_len { + self.buf[write_offset] = self.buf[write_offset + shift_len]; + write_offset += 1; + } + } + self.current_depth -= 1; + self.offset -= shift_len; + Ok(()) + } + + fn get_compound_len(&self) -> usize { + self.offset - self.depth[self.current_depth - 1] + } + + fn bytes_to_encode_len(len: usize) -> Result { + let len = if len < 128 { + // This is directly encoded + 1 + } else if len < 256 { + // This is done with an 0xA1 followed by actual len + 2 + } else if len < 65536 { + // This is done with an 0xA2 followed by 2 bytes of actual len + 3 + } else { + return Err(Error::NoSpace); + }; + Ok(len) + } + + fn get_length_encoding_offset(&self) -> usize { + self.depth[self.current_depth - 1] - RESERVE_LEN_BYTES + } + + pub fn as_slice(&self) -> &[u8] { + &self.buf[..self.offset] + } + + fn write_str(&mut self, vtype: u8, s: &[u8]) -> Result<(), Error> { + self.append_tlv(vtype, s.len(), |t| { + let end_offset = t.offset + s.len(); + t.buf[t.offset..end_offset].copy_from_slice(s); + }) + } +} + +impl<'a> CertConsumer for ASN1Writer<'a> { + fn start_seq(&mut self, _tag: &str) -> Result<(), Error> { + self.add_compound(0x30) + } + + fn end_seq(&mut self) -> Result<(), Error> { + self.end_compound() + } + + fn integer(&mut self, _tag: &str, i: &[u8]) -> Result<(), Error> { + self.write_str(0x02, i) + } + + fn utf8str(&mut self, _tag: &str, s: &str) -> Result<(), Error> { + // Note: ASN1 has 3 string, this is UTF8String + self.write_str(0x0c, s.as_bytes()) + } + + fn bitstr(&mut self, _tag: &str, truncate: bool, s: &[u8]) -> Result<(), Error> { + // Note: ASN1 has 3 string, this is BIT String + + // Strip off the end zeroes + let mut last_byte = s.len() - 1; + let mut num_of_zero = 0; + if truncate { + while s[last_byte] == 0 { + last_byte -= 1; + } + // For the last valid byte, identifying the number of last bits + // that are 0s + num_of_zero = s[last_byte].trailing_zeros() as u8; + } + let s = &s[..(last_byte + 1)]; + self.append_tlv(0x03, s.len() + 1, |t| { + t.buf[t.offset] = num_of_zero; + let end_offset = t.offset + 1 + s.len(); + t.buf[(t.offset + 1)..end_offset].copy_from_slice(s); + }) + } + + fn ostr(&mut self, _tag: &str, s: &[u8]) -> Result<(), Error> { + // Note: ASN1 has 3 string, this is Octet String + self.write_str(0x04, s) + } + + fn start_compound_ostr(&mut self, _tag: &str) -> Result<(), Error> { + // Note: ASN1 has 3 string, this is compound Octet String + self.add_compound(0x04) + } + + fn end_compound_ostr(&mut self) -> Result<(), Error> { + self.end_compound() + } + + fn bool(&mut self, _tag: &str, b: bool) -> Result<(), Error> { + self.append_tlv(0x01, 1, |t| { + if b { + t.buf[t.offset] = 0xFF; + } else { + t.buf[t.offset] = 0x00; + } + }) + } + + fn start_set(&mut self, _tag: &str) -> Result<(), Error> { + self.add_compound(0x31) + } + + fn end_set(&mut self) -> Result<(), Error> { + self.end_compound() + } + + fn ctx(&mut self, _tag: &str, id: u8, val: &[u8]) -> Result<(), Error> { + self.write_str(0x80 | id, val) + } + + fn start_ctx(&mut self, _tag: &str, val: u8) -> Result<(), Error> { + self.add_compound(0xA0 | val) + } + + fn end_ctx(&mut self) -> Result<(), Error> { + self.end_compound() + } + + fn oid(&mut self, _tag: &str, oid: &[u8]) -> Result<(), Error> { + self.write_str(0x06, oid) + } + + fn utctime(&mut self, _tag: &str, epoch: u32) -> Result<(), Error> { + let mut matter_epoch = Utc.ymd(2000, 1, 1).and_hms(0, 0, 0).timestamp(); + matter_epoch += epoch as i64; + + let dt = Utc.timestamp(matter_epoch, 0); + let time_str = format!("{}Z", dt.format("%y%m%d%H%M%S")); + self.write_str(0x17, time_str.as_bytes()) + } +} diff --git a/matter/src/cert/mod.rs b/matter/src/cert/mod.rs new file mode 100644 index 0000000..add3b22 --- /dev/null +++ b/matter/src/cert/mod.rs @@ -0,0 +1,869 @@ +/* + * + * 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::fmt; + +use crate::{ + crypto::{CryptoKeyPair, KeyPair}, + error::Error, + tlv::{self, FromTLV, TLVArrayOwned, TLVElement, TLVWriter, TagType, ToTLV}, + utils::writebuf::WriteBuf, +}; +use log::error; +use num_derive::FromPrimitive; + +use self::{asn1_writer::ASN1Writer, printer::CertPrinter}; + +// As per https://datatracker.ietf.org/doc/html/rfc5280 + +const OID_PUB_KEY_ECPUBKEY: [u8; 7] = [0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01]; +const OID_EC_TYPE_PRIME256V1: [u8; 8] = [0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07]; +const OID_ECDSA_WITH_SHA256: [u8; 8] = [0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02]; + +#[derive(FromPrimitive)] +pub enum CertTags { + SerialNum = 1, + SignAlgo = 2, + Issuer = 3, + NotBefore = 4, + NotAfter = 5, + Subject = 6, + PubKeyAlgo = 7, + EcCurveId = 8, + EcPubKey = 9, + Extensions = 10, + Signature = 11, +} + +#[derive(FromPrimitive, Debug)] +pub enum EcCurveIdValue { + Prime256V1 = 1, +} + +pub fn get_ec_curve_id(algo: u8) -> Option { + num::FromPrimitive::from_u8(algo) +} + +#[derive(FromPrimitive, Debug)] +pub enum PubKeyAlgoValue { + EcPubKey = 1, +} + +pub fn get_pubkey_algo(algo: u8) -> Option { + num::FromPrimitive::from_u8(algo) +} + +#[derive(FromPrimitive, Debug)] +pub enum SignAlgoValue { + ECDSAWithSHA256 = 1, +} + +pub fn get_sign_algo(algo: u8) -> Option { + num::FromPrimitive::from_u8(algo) +} + +const KEY_USAGE_DIGITAL_SIGN: u16 = 0x0001; +const KEY_USAGE_NON_REPUDIATION: u16 = 0x0002; +const KEY_USAGE_KEY_ENCIPHERMENT: u16 = 0x0004; +const KEY_USAGE_DATA_ENCIPHERMENT: u16 = 0x0008; +const KEY_USAGE_KEY_AGREEMENT: u16 = 0x0010; +const KEY_USAGE_KEY_CERT_SIGN: u16 = 0x0020; +const KEY_USAGE_CRL_SIGN: u16 = 0x0040; +const KEY_USAGE_ENCIPHER_ONLY: u16 = 0x0080; +const KEY_USAGE_DECIPHER_ONLY: u16 = 0x0100; + +fn reverse_byte(byte: u8) -> u8 { + const LOOKUP: [u8; 16] = [ + 0x00, 0x08, 0x04, 0x0c, 0x02, 0x0a, 0x06, 0x0e, 0x01, 0x09, 0x05, 0x0d, 0x03, 0x0b, 0x07, + 0x0f, + ]; + (LOOKUP[(byte & 0x0f) as usize] << 4) | LOOKUP[(byte >> 4) as usize] +} + +fn int_to_bitstring(mut a: u16, buf: &mut [u8]) { + if buf.len() >= 2 { + buf[0] = reverse_byte((a & 0xff) as u8); + a >>= 8; + buf[1] = reverse_byte((a & 0xff) as u8); + } +} + +macro_rules! add_if { + ($key:ident, $bit:ident,$str:literal) => { + if ($key & $bit) != 0 { + $str + } else { + "" + } + }; +} + +fn get_print_str(key_usage: u16) -> String { + format!( + "{}{}{}{}{}{}{}{}{}", + add_if!(key_usage, KEY_USAGE_DIGITAL_SIGN, "digitalSignature "), + add_if!(key_usage, KEY_USAGE_NON_REPUDIATION, "nonRepudiation "), + add_if!(key_usage, KEY_USAGE_KEY_ENCIPHERMENT, "keyEncipherment "), + add_if!(key_usage, KEY_USAGE_DATA_ENCIPHERMENT, "dataEncipherment "), + add_if!(key_usage, KEY_USAGE_KEY_AGREEMENT, "keyAgreement "), + add_if!(key_usage, KEY_USAGE_KEY_CERT_SIGN, "keyCertSign "), + add_if!(key_usage, KEY_USAGE_CRL_SIGN, "CRLSign "), + add_if!(key_usage, KEY_USAGE_ENCIPHER_ONLY, "encipherOnly "), + add_if!(key_usage, KEY_USAGE_DECIPHER_ONLY, "decipherOnly "), + ) +} + +#[allow(unused_assignments)] +fn encode_key_usage(key_usage: u16, w: &mut dyn CertConsumer) -> Result<(), Error> { + let mut key_usage_str = [0u8; 2]; + int_to_bitstring(key_usage, &mut key_usage_str); + w.bitstr(&get_print_str(key_usage), true, &key_usage_str)?; + Ok(()) +} + +fn encode_extended_key_usage( + list: &TLVArrayOwned, + w: &mut dyn CertConsumer, +) -> Result<(), Error> { + const OID_SERVER_AUTH: [u8; 8] = [0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01]; + const OID_CLIENT_AUTH: [u8; 8] = [0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02]; + const OID_CODE_SIGN: [u8; 8] = [0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x03]; + const OID_EMAIL_PROT: [u8; 8] = [0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x04]; + const OID_TIMESTAMP: [u8; 8] = [0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x08]; + const OID_OCSP_SIGN: [u8; 8] = [0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x09]; + let encoding = [ + ("", &[0; 8]), + ("ServerAuth", &OID_SERVER_AUTH), + ("ClientAuth", &OID_CLIENT_AUTH), + ("CodeSign", &OID_CODE_SIGN), + ("EmailProtection", &OID_EMAIL_PROT), + ("Timestamp", &OID_TIMESTAMP), + ("OCSPSign", &OID_OCSP_SIGN), + ]; + + w.start_seq("")?; + for t in list.iter() { + let t = *t as usize; + if t > 0 && t <= encoding.len() { + w.oid(encoding[t].0, encoding[t].1)?; + } else { + error!("Skipping encoding key usage out of bounds"); + } + } + w.end_seq()?; + Ok(()) +} + +#[derive(FromTLV, ToTLV, Default)] +#[tlvargs(start = 1)] +struct BasicConstraints { + is_ca: bool, + path: Option, +} + +impl BasicConstraints { + pub fn encode(&self, w: &mut dyn CertConsumer) -> Result<(), Error> { + w.start_seq("")?; + if self.is_ca { + // Encode CA only if true + w.bool("CA:", true)? + } + if self.path.is_some() { + error!("Path Len is not yet implemented"); + } + w.end_seq() + } +} + +fn encode_extension_start( + tag: &str, + critical: bool, + oid: &[u8], + w: &mut dyn CertConsumer, +) -> Result<(), Error> { + w.start_seq(tag)?; + w.oid("", oid)?; + if critical { + w.bool("critical:", true)?; + } + w.start_compound_ostr("value:") +} + +fn encode_extension_end(w: &mut dyn CertConsumer) -> Result<(), Error> { + w.end_compound_ostr()?; + w.end_seq() +} + +#[derive(FromTLV, ToTLV, Default)] +#[tlvargs(start = 1, datatype = "list")] +struct Extensions { + basic_const: Option, + key_usage: Option, + ext_key_usage: Option>, + subj_key_id: Option>, + auth_key_id: Option>, + future_extensions: Option>, +} + +impl Extensions { + fn encode(&self, w: &mut dyn CertConsumer) -> Result<(), Error> { + const OID_BASIC_CONSTRAINTS: [u8; 3] = [0x55, 0x1D, 0x13]; + const OID_KEY_USAGE: [u8; 3] = [0x55, 0x1D, 0x0F]; + const OID_EXT_KEY_USAGE: [u8; 3] = [0x55, 0x1D, 0x25]; + const OID_SUBJ_KEY_IDENTIFIER: [u8; 3] = [0x55, 0x1D, 0x0E]; + const OID_AUTH_KEY_ID: [u8; 3] = [0x55, 0x1D, 0x23]; + + w.start_ctx("X509v3 extensions:", 3)?; + w.start_seq("")?; + if let Some(t) = &self.basic_const { + encode_extension_start("X509v3 Basic Constraints", true, &OID_BASIC_CONSTRAINTS, w)?; + t.encode(w)?; + encode_extension_end(w)?; + } + if let Some(t) = self.key_usage { + encode_extension_start("X509v3 Key Usage", true, &OID_KEY_USAGE, w)?; + encode_key_usage(t, w)?; + encode_extension_end(w)?; + } + if let Some(t) = &self.ext_key_usage { + encode_extension_start("X509v3 Extended Key Usage", true, &OID_EXT_KEY_USAGE, w)?; + encode_extended_key_usage(t, w)?; + encode_extension_end(w)?; + } + if let Some(t) = &self.subj_key_id { + encode_extension_start("Subject Key ID", false, &OID_SUBJ_KEY_IDENTIFIER, w)?; + w.ostr("", t.as_slice())?; + encode_extension_end(w)?; + } + if let Some(t) = &self.auth_key_id { + encode_extension_start("Auth Key ID", false, &OID_AUTH_KEY_ID, w)?; + w.start_seq("")?; + w.ctx("", 0, t.as_slice())?; + w.end_seq()?; + encode_extension_end(w)?; + } + if let Some(t) = &self.future_extensions { + error!("Future Extensions Not Yet Supported: {:x?}", t.as_slice()) + } + w.end_seq()?; + w.end_ctx()?; + Ok(()) + } +} +const MAX_DN_ENTRIES: usize = 5; + +#[derive(FromPrimitive, Copy, Clone)] +enum DnTags { + NodeId = 17, + FirmwareSignId = 18, + IcaId = 19, + RootCaId = 20, + FabricId = 21, + NocCat = 22, +} + +#[derive(Default)] +struct DistNames { + // The order in which the DNs arrive is important, as the signing + // requires that the ASN1 notation retains the same order + dn: Vec<(u8, u64)>, +} + +impl DistNames { + fn u64(&self, match_id: DnTags) -> Option { + self.dn + .iter() + .find(|(id, _)| *id == match_id as u8) + .map(|(_, value)| *value) + } +} + +impl<'a> FromTLV<'a> for DistNames { + fn from_tlv(t: &TLVElement<'a>) -> Result { + let mut d = Self { + dn: Vec::with_capacity(MAX_DN_ENTRIES), + }; + let iter = t.confirm_list()?.enter().ok_or(Error::Invalid)?; + for t in iter { + if let TagType::Context(tag) = t.get_tag() { + let value = t.u64().map_err(|e| { + // Non-integer DNs not yet supported + error!("This DN is not yet supported{}", tag); + e + })?; + d.dn.push((tag, value)); + } + } + Ok(d) + } +} + +impl ToTLV for DistNames { + fn to_tlv(&self, tw: &mut TLVWriter, tag: TagType) -> Result<(), Error> { + tw.start_list(tag)?; + for (name, value) in &self.dn { + tw.u64(TagType::Context(*name), *value)?; + } + tw.end_container() + } +} + +impl DistNames { + fn encode(&self, tag: &str, w: &mut dyn CertConsumer) -> Result<(), Error> { + const OID_MATTER_NODE_ID: [u8; 10] = + [0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0xA2, 0x7C, 0x01, 0x01]; + const OID_MATTER_FW_SIGN_ID: [u8; 10] = + [0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0xA2, 0x7C, 0x01, 0x02]; + const OID_MATTER_ICA_ID: [u8; 10] = + [0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0xA2, 0x7C, 0x01, 0x03]; + const OID_MATTER_ROOT_CA_ID: [u8; 10] = + [0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0xA2, 0x7C, 0x01, 0x04]; + const OID_MATTER_FABRIC_ID: [u8; 10] = + [0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0xA2, 0x7C, 0x01, 0x05]; + const OID_MATTER_NOC_CAT_ID: [u8; 10] = + [0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0xA2, 0x7C, 0x01, 0x06]; + + let dn_encoding = [ + ("Chip Node Id:", &OID_MATTER_NODE_ID), + ("Chip Firmware Signing Id:", &OID_MATTER_FW_SIGN_ID), + ("Chip ICA Id:", &OID_MATTER_ICA_ID), + ("Chip Root CA Id:", &OID_MATTER_ROOT_CA_ID), + ("Chip Fabric Id:", &OID_MATTER_FABRIC_ID), + ]; + + w.start_seq(tag)?; + for (id, value) in &self.dn { + if let Ok(tag) = num::FromPrimitive::from_u8(*id).ok_or(Error::InvalidData) { + match tag { + DnTags::NocCat => { + w.start_set("")?; + w.start_seq("")?; + w.oid("Chip NOC CAT Id:", &OID_MATTER_NOC_CAT_ID)?; + w.utf8str("", format!("{:08X}", value).as_str())?; + w.end_seq()?; + w.end_set()?; + } + _ => { + let index: usize = (*id as usize) - (DnTags::NodeId as usize); + let this = &dn_encoding[index]; + encode_u64_dn(*value, this.0, this.1, w)?; + } + } + } else { + error!("Non Matter DNs are not yet supported {}", id); + } + } + w.end_seq()?; + Ok(()) + } +} + +fn encode_u64_dn( + value: u64, + name: &str, + oid: &[u8], + w: &mut dyn CertConsumer, +) -> Result<(), Error> { + w.start_set("")?; + w.start_seq("")?; + w.oid(name, oid)?; + w.utf8str("", format!("{:016X}", value).as_str())?; + w.end_seq()?; + w.end_set() +} + +#[derive(FromTLV, ToTLV, Default)] +#[tlvargs(start = 1)] +pub struct Cert { + serial_no: Vec, + sign_algo: u8, + issuer: DistNames, + not_before: u32, + not_after: u32, + subject: DistNames, + pubkey_algo: u8, + ec_curve_id: u8, + pubkey: Vec, + extensions: Extensions, + signature: Vec, +} + +// TODO: Instead of parsing the TLVs everytime, we should just cache this, but the encoding +// rules in terms of sequence may get complicated. Need to look into this +impl Cert { + pub fn new(cert_bin: &[u8]) -> Result { + let root = tlv::get_root_node(cert_bin)?; + Cert::from_tlv(&root) + } + + pub fn get_node_id(&self) -> Result { + self.subject.u64(DnTags::NodeId).ok_or(Error::NoNodeId) + } + + pub fn get_fabric_id(&self) -> Result { + self.subject.u64(DnTags::FabricId).ok_or(Error::NoFabricId) + } + + pub fn get_pubkey(&self) -> &[u8] { + self.pubkey.as_slice() + } + + pub fn get_subject_key_id(&self) -> Result<&[u8], Error> { + self.extensions.subj_key_id.as_deref().ok_or(Error::Invalid) + } + + pub fn is_authority(&self, their: &Cert) -> Result { + if let Some(our_auth_key) = &self.extensions.auth_key_id { + let their_subject = their.get_subject_key_id()?; + if our_auth_key == their_subject { + Ok(true) + } else { + Ok(false) + } + } else { + Ok(false) + } + } + + pub fn get_signature(&self) -> &[u8] { + self.signature.as_slice() + } + + pub fn as_tlv(&self, buf: &mut [u8]) -> Result { + let mut wb = WriteBuf::new(buf, buf.len()); + let mut tw = TLVWriter::new(&mut wb); + self.to_tlv(&mut tw, TagType::Anonymous)?; + Ok(wb.as_slice().len()) + } + + pub fn as_asn1(&self, buf: &mut [u8]) -> Result { + let mut w = ASN1Writer::new(buf); + let _ = self.encode(&mut w)?; + Ok(w.as_slice().len()) + } + + pub fn verify_chain_start(&self) -> CertVerifier { + CertVerifier::new(self) + } + + fn encode(&self, w: &mut dyn CertConsumer) -> Result<(), Error> { + w.start_seq("")?; + + w.start_ctx("Version:", 0)?; + w.integer("", &[2])?; + w.end_ctx()?; + + w.integer("Serial Num:", self.serial_no.as_slice())?; + + w.start_seq("Signature Algorithm:")?; + let (str, oid) = match get_sign_algo(self.sign_algo).ok_or(Error::Invalid)? { + SignAlgoValue::ECDSAWithSHA256 => ("ECDSA with SHA256", OID_ECDSA_WITH_SHA256), + }; + w.oid(str, &oid)?; + w.end_seq()?; + + self.issuer.encode("Issuer:", w)?; + + w.start_seq("Validity:")?; + w.utctime("Not Before:", self.not_before)?; + w.utctime("Not After:", self.not_after)?; + w.end_seq()?; + + self.subject.encode("Subject:", w)?; + + w.start_seq("")?; + w.start_seq("Public Key Algorithm")?; + let (str, pub_key) = match get_pubkey_algo(self.pubkey_algo).ok_or(Error::Invalid)? { + PubKeyAlgoValue::EcPubKey => ("ECPubKey", OID_PUB_KEY_ECPUBKEY), + }; + w.oid(str, &pub_key)?; + let (str, curve_id) = match get_ec_curve_id(self.ec_curve_id).ok_or(Error::Invalid)? { + EcCurveIdValue::Prime256V1 => ("Prime256v1", OID_EC_TYPE_PRIME256V1), + }; + w.oid(str, &curve_id)?; + w.end_seq()?; + + w.bitstr("Public-Key:", false, self.pubkey.as_slice())?; + w.end_seq()?; + + self.extensions.encode(w)?; + + // We do not encode the Signature in the DER certificate + + w.end_seq() + } +} + +impl fmt::Display for Cert { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut printer = CertPrinter::new(f); + let _ = self + .encode(&mut printer) + .map_err(|e| error!("Error decoding certificate: {}", e)); + // Signature is not encoded by the Cert Decoder + writeln!(f, "Signature: {:x?}", self.get_signature()) + } +} + +pub struct CertVerifier<'a> { + cert: &'a Cert, +} + +impl<'a> CertVerifier<'a> { + pub fn new(cert: &'a Cert) -> Self { + Self { cert } + } + + pub fn add_cert(self, parent: &'a Cert) -> Result, Error> { + if !self.cert.is_authority(parent)? { + return Err(Error::InvalidAuthKey); + } + let mut asn1 = [0u8; MAX_ASN1_CERT_SIZE]; + let len = self.cert.as_asn1(&mut asn1)?; + let asn1 = &asn1[..len]; + + let k = KeyPair::new_from_public(parent.get_pubkey())?; + k.verify_msg(asn1, self.cert.get_signature()).map_err(|e| { + error!( + "Error in signature verification of certificate: {:#02x?}", + self.cert.get_subject_key_id() + ); + e + })?; + + // TODO: other validation checks + Ok(CertVerifier::new(parent)) + } + + pub fn finalise(self) -> Result<(), Error> { + let cert = self.cert; + self.add_cert(cert)?; + Ok(()) + } +} + +pub trait CertConsumer { + fn start_seq(&mut self, tag: &str) -> Result<(), Error>; + fn end_seq(&mut self) -> Result<(), Error>; + fn integer(&mut self, tag: &str, i: &[u8]) -> Result<(), Error>; + fn utf8str(&mut self, tag: &str, s: &str) -> Result<(), Error>; + fn bitstr(&mut self, tag: &str, truncate: bool, s: &[u8]) -> Result<(), Error>; + fn ostr(&mut self, tag: &str, s: &[u8]) -> Result<(), Error>; + fn start_compound_ostr(&mut self, tag: &str) -> Result<(), Error>; + fn end_compound_ostr(&mut self) -> Result<(), Error>; + fn bool(&mut self, tag: &str, b: bool) -> Result<(), Error>; + fn start_set(&mut self, tag: &str) -> Result<(), Error>; + fn end_set(&mut self) -> Result<(), Error>; + fn ctx(&mut self, tag: &str, id: u8, val: &[u8]) -> Result<(), Error>; + fn start_ctx(&mut self, tag: &str, id: u8) -> Result<(), Error>; + fn end_ctx(&mut self) -> Result<(), Error>; + fn oid(&mut self, tag: &str, oid: &[u8]) -> Result<(), Error>; + fn utctime(&mut self, tag: &str, epoch: u32) -> Result<(), Error>; +} + +const MAX_DEPTH: usize = 10; +const MAX_ASN1_CERT_SIZE: usize = 800; + +mod asn1_writer; +mod printer; + +#[cfg(test)] +mod tests { + use crate::cert::Cert; + use crate::error::Error; + use crate::tlv::{self, FromTLV, TLVWriter, TagType, ToTLV}; + use crate::utils::writebuf::WriteBuf; + + #[test] + fn test_asn1_encode_success() { + { + let mut asn1_buf = [0u8; 1000]; + let c = Cert::new(&test_vectors::ASN1_INPUT1).unwrap(); + let len = c.as_asn1(&mut asn1_buf).unwrap(); + assert_eq!(&test_vectors::ASN1_OUTPUT1, &asn1_buf[..len]); + } + + { + let mut asn1_buf = [0u8; 1000]; + let c = Cert::new(&test_vectors::ASN1_INPUT2).unwrap(); + let len = c.as_asn1(&mut asn1_buf).unwrap(); + assert_eq!(&test_vectors::ASN1_OUTPUT2, &asn1_buf[..len]); + } + } + + #[test] + fn test_verify_chain_success() { + let noc = Cert::new(&test_vectors::NOC1_SUCCESS).unwrap(); + let icac = Cert::new(&test_vectors::ICAC1_SUCCESS).unwrap(); + let rca = Cert::new(&test_vectors::RCA1_SUCCESS).unwrap(); + let a = noc.verify_chain_start(); + a.add_cert(&icac) + .unwrap() + .add_cert(&rca) + .unwrap() + .finalise() + .unwrap(); + } + + #[test] + fn test_verify_chain_incomplete() { + // The chain doesn't lead up to a self-signed certificate + let noc = Cert::new(&test_vectors::NOC1_SUCCESS).unwrap(); + let icac = Cert::new(&test_vectors::ICAC1_SUCCESS).unwrap(); + let a = noc.verify_chain_start(); + assert_eq!( + Err(Error::InvalidAuthKey), + a.add_cert(&icac).unwrap().finalise() + ); + } + + #[test] + fn test_auth_key_chain_incorrect() { + let noc = Cert::new(&test_vectors::NOC1_AUTH_KEY_FAIL).unwrap(); + let icac = Cert::new(&test_vectors::ICAC1_SUCCESS).unwrap(); + let a = noc.verify_chain_start(); + assert_eq!(Err(Error::InvalidAuthKey), a.add_cert(&icac).map(|_| ())); + } + + #[test] + fn test_cert_corrupted() { + let noc = Cert::new(&test_vectors::NOC1_CORRUPT_CERT).unwrap(); + let icac = Cert::new(&test_vectors::ICAC1_SUCCESS).unwrap(); + let a = noc.verify_chain_start(); + assert_eq!(Err(Error::InvalidSignature), a.add_cert(&icac).map(|_| ())); + } + + #[test] + fn test_tlv_conversions() { + let test_input: [&[u8]; 3] = [ + &test_vectors::NOC1_SUCCESS, + &test_vectors::ICAC1_SUCCESS, + &test_vectors::RCA1_SUCCESS, + ]; + + for input in test_input.iter() { + println!("Testing next input..."); + let root = tlv::get_root_node(*input).unwrap(); + let cert = Cert::from_tlv(&root).unwrap(); + let mut buf = [0u8; 1024]; + let buf_len = buf.len(); + let mut wb = WriteBuf::new(&mut buf, buf_len); + let mut tw = TLVWriter::new(&mut wb); + cert.to_tlv(&mut tw, TagType::Anonymous).unwrap(); + assert_eq!(*input, wb.as_slice()); + } + } + + mod test_vectors { + // Group 1 + pub const NOC1_SUCCESS: [u8; 247] = [ + 0x15, 0x30, 0x1, 0x1, 0x1, 0x24, 0x2, 0x1, 0x37, 0x3, 0x24, 0x13, 0x1, 0x24, 0x15, 0x1, + 0x18, 0x26, 0x4, 0x80, 0x22, 0x81, 0x27, 0x26, 0x5, 0x80, 0x25, 0x4d, 0x3a, 0x37, 0x6, + 0x26, 0x11, 0x2, 0x5c, 0xbc, 0x0, 0x24, 0x15, 0x1, 0x18, 0x24, 0x7, 0x1, 0x24, 0x8, + 0x1, 0x30, 0x9, 0x41, 0x4, 0xba, 0x22, 0x56, 0x43, 0x4f, 0x59, 0x98, 0x32, 0x8d, 0xb8, + 0xcb, 0x3f, 0x24, 0x90, 0x9a, 0x96, 0x94, 0x43, 0x46, 0x67, 0xc2, 0x11, 0xe3, 0x80, + 0x26, 0x65, 0xfc, 0x65, 0x37, 0x77, 0x3, 0x25, 0x18, 0xd8, 0xdc, 0x85, 0xfa, 0xe6, + 0x42, 0xe7, 0x55, 0xc9, 0x37, 0xcc, 0xb, 0x78, 0x84, 0x3d, 0x2f, 0xac, 0x81, 0x88, + 0x2e, 0x69, 0x0, 0xa5, 0xfc, 0xcd, 0xe0, 0xad, 0xb2, 0x69, 0xca, 0x73, 0x37, 0xa, 0x35, + 0x1, 0x28, 0x1, 0x18, 0x24, 0x2, 0x1, 0x36, 0x3, 0x4, 0x2, 0x4, 0x1, 0x18, 0x30, 0x4, + 0x14, 0x39, 0x68, 0x16, 0x1e, 0xb5, 0x56, 0x6d, 0xd3, 0xf8, 0x61, 0xf2, 0x95, 0xf3, + 0x55, 0xa0, 0xfb, 0xd2, 0x82, 0xc2, 0x29, 0x30, 0x5, 0x14, 0xce, 0x60, 0xb4, 0x28, + 0x96, 0x72, 0x27, 0x64, 0x81, 0xbc, 0x4f, 0x0, 0x78, 0xa3, 0x30, 0x48, 0xfe, 0x6e, + 0x65, 0x86, 0x18, 0x30, 0xb, 0x40, 0x2, 0x88, 0x42, 0x0, 0x6f, 0xcc, 0xe0, 0xf0, 0x6c, + 0xd9, 0xf9, 0x5e, 0xe4, 0xc2, 0xaa, 0x1f, 0x57, 0x71, 0x62, 0xdb, 0x6b, 0x4e, 0xe7, + 0x55, 0x3f, 0xc6, 0xc7, 0x9f, 0xf8, 0x30, 0xeb, 0x16, 0x6e, 0x6d, 0xc6, 0x9c, 0xb, + 0xb7, 0xe2, 0xb8, 0xe3, 0xe7, 0x57, 0x88, 0x7b, 0xda, 0xe5, 0x79, 0x39, 0x6d, 0x2c, + 0x37, 0xb2, 0x7f, 0xc3, 0x63, 0x2f, 0x7e, 0x70, 0xab, 0x5a, 0x2c, 0xf7, 0x5b, 0x18, + ]; + pub const ICAC1_SUCCESS: [u8; 237] = [ + 21, 48, 1, 1, 0, 36, 2, 1, 55, 3, 36, 20, 0, 36, 21, 1, 24, 38, 4, 128, 34, 129, 39, + 38, 5, 128, 37, 77, 58, 55, 6, 36, 19, 1, 36, 21, 1, 24, 36, 7, 1, 36, 8, 1, 48, 9, 65, + 4, 86, 25, 119, 24, 63, 212, 255, 43, 88, 61, 233, 121, 52, 102, 223, 233, 0, 251, 109, + 161, 239, 224, 204, 220, 119, 48, 192, 111, 182, 45, 255, 190, 84, 160, 149, 117, 11, + 139, 7, 188, 85, 219, 156, 182, 85, 19, 8, 184, 223, 2, 227, 64, 107, 174, 52, 245, 12, + 186, 201, 242, 191, 241, 231, 80, 55, 10, 53, 1, 41, 1, 24, 36, 2, 96, 48, 4, 20, 206, + 96, 180, 40, 150, 114, 39, 100, 129, 188, 79, 0, 120, 163, 48, 72, 254, 110, 101, 134, + 48, 5, 20, 212, 86, 147, 190, 112, 121, 244, 156, 112, 107, 7, 111, 17, 28, 109, 229, + 100, 164, 68, 116, 24, 48, 11, 64, 243, 8, 190, 128, 155, 254, 245, 21, 205, 241, 217, + 246, 204, 182, 247, 41, 81, 91, 33, 155, 230, 223, 212, 116, 33, 162, 208, 148, 100, + 89, 175, 253, 78, 212, 7, 69, 207, 140, 45, 129, 249, 64, 104, 70, 68, 43, 164, 19, + 126, 114, 138, 79, 104, 238, 20, 226, 88, 118, 105, 56, 12, 92, 31, 171, 24, + ]; + // A single byte in the auth key id is changed in this + pub const NOC1_AUTH_KEY_FAIL: [u8; 247] = [ + 0x15, 0x30, 0x1, 0x1, 0x1, 0x24, 0x2, 0x1, 0x37, 0x3, 0x24, 0x13, 0x1, 0x24, 0x15, 0x1, + 0x18, 0x26, 0x4, 0x80, 0x22, 0x81, 0x27, 0x26, 0x5, 0x80, 0x25, 0x4d, 0x3a, 0x37, 0x6, + 0x26, 0x11, 0x2, 0x5c, 0xbc, 0x0, 0x24, 0x15, 0x1, 0x18, 0x24, 0x7, 0x1, 0x24, 0x8, + 0x1, 0x30, 0x9, 0x41, 0x4, 0xba, 0x22, 0x56, 0x43, 0x4f, 0x59, 0x98, 0x32, 0x8d, 0xb8, + 0xcb, 0x3f, 0x24, 0x90, 0x9a, 0x96, 0x94, 0x43, 0x46, 0x67, 0xc2, 0x11, 0xe3, 0x80, + 0x26, 0x65, 0xfc, 0x65, 0x37, 0x77, 0x3, 0x25, 0x18, 0xd8, 0xdc, 0x85, 0xfa, 0xe6, + 0x42, 0xe7, 0x55, 0xc9, 0x37, 0xcc, 0xb, 0x78, 0x84, 0x3d, 0x2f, 0xac, 0x81, 0x88, + 0x2e, 0x69, 0x0, 0xa5, 0xfc, 0xcd, 0xe0, 0xad, 0xb2, 0x69, 0xca, 0x73, 0x37, 0xa, 0x35, + 0x1, 0x28, 0x1, 0x18, 0x24, 0x2, 0x1, 0x36, 0x3, 0x4, 0x2, 0x4, 0x1, 0x18, 0x30, 0x4, + 0x14, 0x39, 0x68, 0x16, 0x1e, 0xb5, 0x56, 0x6d, 0xd3, 0xf8, 0x61, 0xf2, 0x95, 0xf3, + 0x55, 0xa0, 0xfb, 0xd2, 0x82, 0xc2, 0x29, 0x30, 0x5, 0x14, 0xce, 0x61, 0xb4, 0x28, + 0x96, 0x72, 0x27, 0x64, 0x81, 0xbc, 0x4f, 0x0, 0x78, 0xa3, 0x30, 0x48, 0xfe, 0x6e, + 0x65, 0x86, 0x18, 0x30, 0xb, 0x40, 0x2, 0x88, 0x42, 0x0, 0x6f, 0xcc, 0xe0, 0xf0, 0x6c, + 0xd9, 0xf9, 0x5e, 0xe4, 0xc2, 0xaa, 0x1f, 0x57, 0x71, 0x62, 0xdb, 0x6b, 0x4e, 0xe7, + 0x55, 0x3f, 0xc6, 0xc7, 0x9f, 0xf8, 0x30, 0xeb, 0x16, 0x6e, 0x6d, 0xc6, 0x9c, 0xb, + 0xb7, 0xe2, 0xb8, 0xe3, 0xe7, 0x57, 0x88, 0x7b, 0xda, 0xe5, 0x79, 0x39, 0x6d, 0x2c, + 0x37, 0xb2, 0x7f, 0xc3, 0x63, 0x2f, 0x7e, 0x70, 0xab, 0x5a, 0x2c, 0xf7, 0x5b, 0x18, + ]; + // A single byte in the Certificate contents is changed in this + pub const NOC1_CORRUPT_CERT: [u8; 247] = [ + 0x15, 0x30, 0x1, 0x1, 0x1, 0x24, 0x2, 0x1, 0x37, 0x3, 0x24, 0x13, 0x1, 0x24, 0x15, 0x1, + 0x18, 0x26, 0x4, 0x80, 0x22, 0x81, 0x27, 0x26, 0x5, 0x80, 0x25, 0x4d, 0x3a, 0x37, 0x6, + 0x26, 0x11, 0x2, 0x5c, 0xbc, 0x0, 0x24, 0x15, 0x1, 0x18, 0x24, 0x7, 0x1, 0x24, 0x8, + 0x1, 0x30, 0x9, 0x41, 0x4, 0xba, 0x23, 0x56, 0x43, 0x4f, 0x59, 0x98, 0x32, 0x8d, 0xb8, + 0xcb, 0x3f, 0x24, 0x90, 0x9a, 0x96, 0x94, 0x43, 0x46, 0x67, 0xc2, 0x11, 0xe3, 0x80, + 0x26, 0x65, 0xfc, 0x65, 0x37, 0x77, 0x3, 0x25, 0x18, 0xd8, 0xdc, 0x85, 0xfa, 0xe6, + 0x42, 0xe7, 0x55, 0xc9, 0x37, 0xcc, 0xb, 0x78, 0x84, 0x3d, 0x2f, 0xac, 0x81, 0x88, + 0x2e, 0x69, 0x0, 0xa5, 0xfc, 0xcd, 0xe0, 0xad, 0xb2, 0x69, 0xca, 0x73, 0x37, 0xa, 0x35, + 0x1, 0x28, 0x1, 0x18, 0x24, 0x2, 0x1, 0x36, 0x3, 0x4, 0x2, 0x4, 0x1, 0x18, 0x30, 0x4, + 0x14, 0x39, 0x68, 0x16, 0x1e, 0xb5, 0x56, 0x6d, 0xd3, 0xf8, 0x61, 0xf2, 0x95, 0xf3, + 0x55, 0xa0, 0xfb, 0xd2, 0x82, 0xc2, 0x29, 0x30, 0x5, 0x14, 0xce, 0x60, 0xb4, 0x28, + 0x96, 0x72, 0x27, 0x64, 0x81, 0xbc, 0x4f, 0x0, 0x78, 0xa3, 0x30, 0x48, 0xfe, 0x6e, + 0x65, 0x86, 0x18, 0x30, 0xb, 0x40, 0x2, 0x88, 0x42, 0x0, 0x6f, 0xcc, 0xe0, 0xf0, 0x6c, + 0xd9, 0xf9, 0x5e, 0xe4, 0xc2, 0xaa, 0x1f, 0x57, 0x71, 0x62, 0xdb, 0x6b, 0x4e, 0xe7, + 0x55, 0x3f, 0xc6, 0xc7, 0x9f, 0xf8, 0x30, 0xeb, 0x16, 0x6e, 0x6d, 0xc6, 0x9c, 0xb, + 0xb7, 0xe2, 0xb8, 0xe3, 0xe7, 0x57, 0x88, 0x7b, 0xda, 0xe5, 0x79, 0x39, 0x6d, 0x2c, + 0x37, 0xb2, 0x7f, 0xc3, 0x63, 0x2f, 0x7e, 0x70, 0xab, 0x5a, 0x2c, 0xf7, 0x5b, 0x18, + ]; + pub const RCA1_SUCCESS: [u8; 237] = [ + 0x15, 0x30, 0x1, 0x1, 0x0, 0x24, 0x2, 0x1, 0x37, 0x3, 0x24, 0x14, 0x0, 0x24, 0x15, 0x1, + 0x18, 0x26, 0x4, 0x80, 0x22, 0x81, 0x27, 0x26, 0x5, 0x80, 0x25, 0x4d, 0x3a, 0x37, 0x6, + 0x24, 0x14, 0x0, 0x24, 0x15, 0x1, 0x18, 0x24, 0x7, 0x1, 0x24, 0x8, 0x1, 0x30, 0x9, + 0x41, 0x4, 0x6d, 0x70, 0x7e, 0x4b, 0x98, 0xf6, 0x2b, 0xab, 0x44, 0xd6, 0xfe, 0xa3, + 0x2e, 0x39, 0xd8, 0xc3, 0x0, 0xa0, 0xe, 0xa8, 0x6c, 0x83, 0xff, 0x69, 0xd, 0xe8, 0x42, + 0x1, 0xeb, 0xd, 0xaa, 0x68, 0x5d, 0xcb, 0x97, 0x2, 0x80, 0x1d, 0xa8, 0x50, 0x2, 0x2e, + 0x5a, 0xa2, 0x5a, 0x2e, 0x51, 0x26, 0x4, 0xd2, 0x39, 0x62, 0xcd, 0x82, 0x38, 0x63, + 0x28, 0xbf, 0x15, 0x1c, 0xa6, 0x27, 0xe0, 0xd7, 0x37, 0xa, 0x35, 0x1, 0x29, 0x1, 0x18, + 0x24, 0x2, 0x60, 0x30, 0x4, 0x14, 0xd4, 0x56, 0x93, 0xbe, 0x70, 0x79, 0xf4, 0x9c, 0x70, + 0x6b, 0x7, 0x6f, 0x11, 0x1c, 0x6d, 0xe5, 0x64, 0xa4, 0x44, 0x74, 0x30, 0x5, 0x14, 0xd4, + 0x56, 0x93, 0xbe, 0x70, 0x79, 0xf4, 0x9c, 0x70, 0x6b, 0x7, 0x6f, 0x11, 0x1c, 0x6d, + 0xe5, 0x64, 0xa4, 0x44, 0x74, 0x18, 0x30, 0xb, 0x40, 0x3, 0xd, 0x77, 0xe1, 0x9e, 0xea, + 0x9c, 0x5, 0x5c, 0xcc, 0x47, 0xe8, 0xb3, 0x18, 0x1a, 0xd1, 0x74, 0xee, 0xc6, 0x2e, + 0xa1, 0x20, 0x16, 0xbd, 0x20, 0xb4, 0x3d, 0xac, 0x24, 0xbe, 0x17, 0xf9, 0xe, 0xb7, + 0x9a, 0x98, 0xc8, 0xbc, 0x6a, 0xce, 0x99, 0x2a, 0x2e, 0x63, 0x4c, 0x76, 0x6, 0x45, + 0x93, 0xd3, 0x7c, 0x4, 0x0, 0xe4, 0xc7, 0x78, 0xe9, 0x83, 0x5b, 0xc, 0x33, 0x61, 0x5c, + 0x2e, 0x18, + ]; + pub const ASN1_INPUT1: [u8; 237] = [ + 0x15, 0x30, 0x01, 0x01, 0x00, 0x24, 0x02, 0x01, 0x37, 0x03, 0x24, 0x14, 0x00, 0x24, + 0x15, 0x03, 0x18, 0x26, 0x04, 0x80, 0x22, 0x81, 0x27, 0x26, 0x05, 0x80, 0x25, 0x4d, + 0x3a, 0x37, 0x06, 0x24, 0x13, 0x01, 0x24, 0x15, 0x03, 0x18, 0x24, 0x07, 0x01, 0x24, + 0x08, 0x01, 0x30, 0x09, 0x41, 0x04, 0x69, 0xda, 0xe9, 0x42, 0x88, 0xcf, 0x64, 0x94, + 0x2d, 0xd5, 0x0a, 0x74, 0x2d, 0x50, 0xe8, 0x5e, 0xbe, 0x15, 0x53, 0x24, 0xe5, 0xc5, + 0x6b, 0xe5, 0x7f, 0xc1, 0x41, 0x11, 0x21, 0xdd, 0x46, 0xa3, 0x0d, 0x63, 0xc3, 0xe3, + 0x90, 0x7a, 0x69, 0x64, 0xdd, 0x66, 0x78, 0x10, 0xa6, 0xc8, 0x0f, 0xfd, 0xb6, 0xf2, + 0x9b, 0x88, 0x50, 0x93, 0x77, 0x9e, 0xf7, 0xb4, 0xda, 0x94, 0x11, 0x33, 0x1e, 0xfe, + 0x37, 0x0a, 0x35, 0x01, 0x29, 0x01, 0x18, 0x24, 0x02, 0x60, 0x30, 0x04, 0x14, 0xdf, + 0xfb, 0x79, 0xf1, 0x2b, 0xbf, 0x68, 0x18, 0x59, 0x7f, 0xf7, 0xe8, 0xaf, 0x88, 0x91, + 0x1c, 0x72, 0x32, 0xf7, 0x52, 0x30, 0x05, 0x14, 0xed, 0x31, 0x5e, 0x1a, 0xb7, 0xb9, + 0x7a, 0xca, 0x04, 0x79, 0x5d, 0x82, 0x57, 0x7a, 0xd7, 0x0a, 0x75, 0xd0, 0xdb, 0x7a, + 0x18, 0x30, 0x0b, 0x40, 0xe5, 0xd4, 0xe6, 0x0e, 0x98, 0x62, 0x2f, 0xaa, 0x59, 0xe0, + 0x28, 0x59, 0xc2, 0xd4, 0xcd, 0x34, 0x85, 0x7f, 0x93, 0xbe, 0x14, 0x35, 0xa3, 0x76, + 0x8a, 0xc9, 0x2f, 0x59, 0x39, 0xa0, 0xb0, 0x75, 0xe8, 0x8e, 0x11, 0xa9, 0xc1, 0x9e, + 0xaa, 0xab, 0xa0, 0xdb, 0xb4, 0x79, 0x63, 0xfc, 0x02, 0x03, 0x27, 0x25, 0xac, 0x21, + 0x6f, 0xef, 0x27, 0xab, 0x0f, 0x90, 0x09, 0x99, 0x05, 0xa8, 0x60, 0xd8, 0x18, + ]; + pub const ASN1_INPUT2: [u8; 247] = [ + 0x15, 0x30, 0x01, 0x01, 0x01, 0x24, 0x02, 0x01, 0x37, 0x03, 0x24, 0x13, 0x01, 0x24, + 0x15, 0x03, 0x18, 0x26, 0x04, 0x80, 0x22, 0x81, 0x27, 0x26, 0x05, 0x80, 0x25, 0x4d, + 0x3a, 0x37, 0x06, 0x26, 0x11, 0x69, 0xb6, 0x01, 0x00, 0x24, 0x15, 0x03, 0x18, 0x24, + 0x07, 0x01, 0x24, 0x08, 0x01, 0x30, 0x09, 0x41, 0x04, 0x93, 0x04, 0xc6, 0xc4, 0xe1, + 0xbc, 0x9a, 0xc8, 0xf5, 0xb3, 0x7f, 0x83, 0xd6, 0x7f, 0x79, 0xc5, 0x35, 0xdc, 0x7f, + 0xac, 0x87, 0xca, 0xcd, 0x08, 0x80, 0x4a, 0x55, 0x60, 0x80, 0x09, 0xd3, 0x9b, 0x4a, + 0xc8, 0xe7, 0x7b, 0x4d, 0x5c, 0x82, 0x88, 0x24, 0xdf, 0x1c, 0xfd, 0xef, 0xb4, 0xbc, + 0xb7, 0x2f, 0x36, 0xf7, 0x2b, 0xb2, 0xcc, 0x14, 0x69, 0x63, 0xcc, 0x89, 0xd2, 0x74, + 0x3f, 0xd1, 0x98, 0x37, 0x0a, 0x35, 0x01, 0x28, 0x01, 0x18, 0x24, 0x02, 0x01, 0x36, + 0x03, 0x04, 0x02, 0x04, 0x01, 0x18, 0x30, 0x04, 0x14, 0x9c, 0xe7, 0xd9, 0xa8, 0x6b, + 0xf8, 0x71, 0xfa, 0x08, 0x10, 0xa3, 0xf2, 0x3a, 0x95, 0x30, 0xb1, 0x9e, 0xae, 0xc4, + 0x2c, 0x30, 0x05, 0x14, 0xdf, 0xfb, 0x79, 0xf1, 0x2b, 0xbf, 0x68, 0x18, 0x59, 0x7f, + 0xf7, 0xe8, 0xaf, 0x88, 0x91, 0x1c, 0x72, 0x32, 0xf7, 0x52, 0x18, 0x30, 0x0b, 0x40, + 0xcf, 0x01, 0x37, 0x65, 0xd6, 0x8a, 0xca, 0xd8, 0x33, 0x9f, 0x0f, 0x4f, 0xd5, 0xed, + 0x48, 0x42, 0x91, 0xca, 0xab, 0xf7, 0xae, 0xe1, 0x3b, 0x2b, 0xef, 0x9f, 0x43, 0x5a, + 0x96, 0xe0, 0xa5, 0x38, 0x8e, 0x39, 0xd0, 0x20, 0x8a, 0x0c, 0x92, 0x2b, 0x21, 0x7d, + 0xf5, 0x6c, 0x1d, 0x65, 0x6c, 0x0f, 0xd1, 0xe8, 0x55, 0x14, 0x5e, 0x27, 0xfd, 0xa4, + 0xac, 0xf9, 0x93, 0xdb, 0x29, 0x49, 0xaa, 0x71, 0x18, + ]; + + pub const ASN1_OUTPUT1: [u8; 388] = [ + 0x30, 0x82, 0x01, 0x80, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x01, 0x00, 0x30, 0x0a, + 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x30, 0x44, 0x31, 0x20, + 0x30, 0x1e, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xa2, 0x7c, 0x01, 0x04, + 0x0c, 0x10, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, + 0x01, 0x82, 0xa2, 0x7c, 0x01, 0x05, 0x0c, 0x10, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0x30, 0x1e, 0x17, 0x0d, + 0x32, 0x31, 0x30, 0x31, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, + 0x0d, 0x33, 0x30, 0x31, 0x32, 0x33, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, + 0x30, 0x44, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, + 0xa2, 0x7c, 0x01, 0x03, 0x0c, 0x10, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x0a, + 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xa2, 0x7c, 0x01, 0x05, 0x0c, 0x10, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, + 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, + 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0x69, + 0xda, 0xe9, 0x42, 0x88, 0xcf, 0x64, 0x94, 0x2d, 0xd5, 0x0a, 0x74, 0x2d, 0x50, 0xe8, + 0x5e, 0xbe, 0x15, 0x53, 0x24, 0xe5, 0xc5, 0x6b, 0xe5, 0x7f, 0xc1, 0x41, 0x11, 0x21, + 0xdd, 0x46, 0xa3, 0x0d, 0x63, 0xc3, 0xe3, 0x90, 0x7a, 0x69, 0x64, 0xdd, 0x66, 0x78, + 0x10, 0xa6, 0xc8, 0x0f, 0xfd, 0xb6, 0xf2, 0x9b, 0x88, 0x50, 0x93, 0x77, 0x9e, 0xf7, + 0xb4, 0xda, 0x94, 0x11, 0x33, 0x1e, 0xfe, 0xa3, 0x63, 0x30, 0x61, 0x30, 0x0f, 0x06, + 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, + 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, + 0x01, 0x06, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xdf, + 0xfb, 0x79, 0xf1, 0x2b, 0xbf, 0x68, 0x18, 0x59, 0x7f, 0xf7, 0xe8, 0xaf, 0x88, 0x91, + 0x1c, 0x72, 0x32, 0xf7, 0x52, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, + 0x30, 0x16, 0x80, 0x14, 0xed, 0x31, 0x5e, 0x1a, 0xb7, 0xb9, 0x7a, 0xca, 0x04, 0x79, + 0x5d, 0x82, 0x57, 0x7a, 0xd7, 0x0a, 0x75, 0xd0, 0xdb, 0x7a, + ]; + pub const ASN1_OUTPUT2: [u8; 421] = [ + 0x30, 0x82, 0x01, 0xa1, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x01, 0x01, 0x30, 0x0a, + 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x30, 0x44, 0x31, 0x20, + 0x30, 0x1e, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xa2, 0x7c, 0x01, 0x03, + 0x0c, 0x10, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x31, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, + 0x01, 0x82, 0xa2, 0x7c, 0x01, 0x05, 0x0c, 0x10, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0x30, 0x1e, 0x17, 0x0d, + 0x32, 0x31, 0x30, 0x31, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, + 0x0d, 0x33, 0x30, 0x31, 0x32, 0x33, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, + 0x30, 0x44, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, + 0xa2, 0x7c, 0x01, 0x01, 0x0c, 0x10, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x31, 0x42, 0x36, 0x36, 0x39, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x0a, + 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xa2, 0x7c, 0x01, 0x05, 0x0c, 0x10, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, + 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, + 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0x93, + 0x04, 0xc6, 0xc4, 0xe1, 0xbc, 0x9a, 0xc8, 0xf5, 0xb3, 0x7f, 0x83, 0xd6, 0x7f, 0x79, + 0xc5, 0x35, 0xdc, 0x7f, 0xac, 0x87, 0xca, 0xcd, 0x08, 0x80, 0x4a, 0x55, 0x60, 0x80, + 0x09, 0xd3, 0x9b, 0x4a, 0xc8, 0xe7, 0x7b, 0x4d, 0x5c, 0x82, 0x88, 0x24, 0xdf, 0x1c, + 0xfd, 0xef, 0xb4, 0xbc, 0xb7, 0x2f, 0x36, 0xf7, 0x2b, 0xb2, 0xcc, 0x14, 0x69, 0x63, + 0xcc, 0x89, 0xd2, 0x74, 0x3f, 0xd1, 0x98, 0xa3, 0x81, 0x83, 0x30, 0x81, 0x80, 0x30, + 0x0c, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x02, 0x30, 0x00, 0x30, + 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x07, + 0x80, 0x30, 0x20, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x01, 0x01, 0xff, 0x04, 0x16, 0x30, + 0x14, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x06, 0x08, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, + 0x04, 0x16, 0x04, 0x14, 0x9c, 0xe7, 0xd9, 0xa8, 0x6b, 0xf8, 0x71, 0xfa, 0x08, 0x10, + 0xa3, 0xf2, 0x3a, 0x95, 0x30, 0xb1, 0x9e, 0xae, 0xc4, 0x2c, 0x30, 0x1f, 0x06, 0x03, + 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xdf, 0xfb, 0x79, 0xf1, 0x2b, + 0xbf, 0x68, 0x18, 0x59, 0x7f, 0xf7, 0xe8, 0xaf, 0x88, 0x91, 0x1c, 0x72, 0x32, 0xf7, + 0x52, + ]; + } +} diff --git a/matter/src/cert/printer.rs b/matter/src/cert/printer.rs new file mode 100644 index 0000000..23b7020 --- /dev/null +++ b/matter/src/cert/printer.rs @@ -0,0 +1,132 @@ +/* + * + * 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::{CertConsumer, MAX_DEPTH}; +use crate::error::Error; +use chrono::{TimeZone, Utc}; +use std::fmt; + +pub struct CertPrinter<'a, 'b> { + level: usize, + f: &'b mut fmt::Formatter<'a>, +} + +impl<'a, 'b> CertPrinter<'a, 'b> { + pub fn new(f: &'b mut fmt::Formatter<'a>) -> Self { + Self { level: 0, f } + } +} + +const SPACE: [&str; MAX_DEPTH] = [ + "", + "", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", +]; + +impl<'a, 'b> CertConsumer for CertPrinter<'a, 'b> { + fn start_seq(&mut self, tag: &str) -> Result<(), Error> { + if !tag.is_empty() { + let _ = writeln!(self.f, "{} {}", SPACE[self.level], tag); + } + self.level += 1; + Ok(()) + } + fn end_seq(&mut self) -> Result<(), Error> { + self.level -= 1; + Ok(()) + } + fn integer(&mut self, tag: &str, i: &[u8]) -> Result<(), Error> { + let _ = writeln!(self.f, "{} {} {:x?}", SPACE[self.level], tag, i); + Ok(()) + } + fn utf8str(&mut self, tag: &str, s: &str) -> Result<(), Error> { + let _ = writeln!(self.f, "{} {} {:x?}", SPACE[self.level], tag, s); + Ok(()) + } + fn bitstr(&mut self, tag: &str, _truncate: bool, s: &[u8]) -> Result<(), Error> { + let _ = writeln!(self.f, "{} {} {:x?}", SPACE[self.level], tag, s); + Ok(()) + } + fn ostr(&mut self, tag: &str, s: &[u8]) -> Result<(), Error> { + let _ = writeln!(self.f, "{} {} {:x?}", SPACE[self.level], tag, s); + Ok(()) + } + fn start_compound_ostr(&mut self, tag: &str) -> Result<(), Error> { + if !tag.is_empty() { + let _ = writeln!(self.f, "{} {}", SPACE[self.level], tag); + } + self.level += 1; + Ok(()) + } + fn end_compound_ostr(&mut self) -> Result<(), Error> { + self.level -= 1; + Ok(()) + } + fn bool(&mut self, tag: &str, b: bool) -> Result<(), Error> { + let _ = writeln!(self.f, "{} {} {}", SPACE[self.level], tag, b); + Ok(()) + } + fn start_set(&mut self, tag: &str) -> Result<(), Error> { + if !tag.is_empty() { + let _ = writeln!(self.f, "{} {}", SPACE[self.level], tag); + } + self.level += 1; + Ok(()) + } + fn end_set(&mut self) -> Result<(), Error> { + self.level -= 1; + Ok(()) + } + fn ctx(&mut self, tag: &str, id: u8, val: &[u8]) -> Result<(), Error> { + let _ = writeln!(self.f, "{} {}[{}]{:x?}", SPACE[self.level], tag, id, val); + Ok(()) + } + fn start_ctx(&mut self, tag: &str, val: u8) -> Result<(), Error> { + let _ = writeln!(self.f, "{} {} [{}]", SPACE[self.level], tag, val); + self.level += 1; + Ok(()) + } + fn end_ctx(&mut self) -> Result<(), Error> { + self.level -= 1; + Ok(()) + } + fn oid(&mut self, tag: &str, _oid: &[u8]) -> Result<(), Error> { + if !tag.is_empty() { + let _ = writeln!(self.f, "{} {}", SPACE[self.level], tag); + } + Ok(()) + } + fn utctime(&mut self, tag: &str, epoch: u32) -> Result<(), Error> { + let mut matter_epoch = Utc.ymd(2000, 1, 1).and_hms(0, 0, 0).timestamp(); + matter_epoch += epoch as i64; + let _ = writeln!( + self.f, + "{} {} {}", + SPACE[self.level], + tag, + Utc.timestamp(matter_epoch, 0) + ); + Ok(()) + } +} diff --git a/matter/src/core.rs b/matter/src/core.rs new file mode 100644 index 0000000..0706413 --- /dev/null +++ b/matter/src/core.rs @@ -0,0 +1,106 @@ +/* + * + * 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::AclMgr, + data_model::{ + cluster_basic_information::BasicInfoConfig, core::DataModel, + sdm::dev_att::DevAttDataFetcher, + }, + error::*, + fabric::FabricMgr, + interaction_model::InteractionModel, + mdns::Mdns, + secure_channel::core::SecureChannel, + transport, +}; +use std::sync::Arc; + +#[derive(Default)] +/// Device Commissioning Data +pub struct CommissioningData { + /// The commissioning salt + pub salt: [u8; 16], + /// The password for commissioning the device + // TODO: We should replace this with verifier instead of password + pub passwd: u32, + /// The 12-bit discriminator used to differentiate between multiple devices + pub discriminator: u16, +} + +/// The primary Matter Object +pub struct Matter { + transport_mgr: transport::mgr::Mgr, + data_model: DataModel, + fabric_mgr: Arc, +} + +impl Matter { + /// Creates a new Matter object + /// + /// # Parameters + /// * dev_att: An object that implements the trait [DevAttDataFetcher]. Any Matter device + /// requires a set of device attestation certificates and keys. It is the responsibility of + /// this object to return the device attestation details when queried upon. + pub fn new( + dev_det: BasicInfoConfig, + dev_att: Box, + dev_comm: CommissioningData, + ) -> Result, Error> { + let mdns = Mdns::get()?; + mdns.set_values(dev_det.vid, dev_det.pid, dev_comm.discriminator); + + let fabric_mgr = Arc::new(FabricMgr::new()?); + let acl_mgr = Arc::new(AclMgr::new()?); + let open_comm_window = fabric_mgr.is_empty(); + let data_model = DataModel::new(dev_det, dev_att, fabric_mgr.clone(), acl_mgr)?; + let mut matter = Box::new(Matter { + transport_mgr: transport::mgr::Mgr::new()?, + data_model, + fabric_mgr, + }); + let interaction_model = + Box::new(InteractionModel::new(Box::new(matter.data_model.clone()))); + matter.transport_mgr.register_protocol(interaction_model)?; + let mut secure_channel = Box::new(SecureChannel::new(matter.fabric_mgr.clone())); + if open_comm_window { + secure_channel.open_comm_window(&dev_comm.salt, dev_comm.passwd)?; + } + + matter.transport_mgr.register_protocol(secure_channel)?; + Ok(matter) + } + + /// Returns an Arc to [DataModel] + /// + /// The Data Model is where you express what is the type of your device. Typically + /// once you gets this reference, you acquire the write lock and add your device + /// types, clusters, attributes, commands to the data model. + pub fn get_data_model(&self) -> DataModel { + self.data_model.clone() + } + + /// Starts the Matter daemon + /// + /// This call does NOT return + /// + /// This call starts the Matter daemon that starts communication with other Matter + /// devices on the network. + pub fn start_daemon(&mut self) -> Result<(), Error> { + self.transport_mgr.start() + } +} diff --git a/matter/src/crypto/crypto_dummy.rs b/matter/src/crypto/crypto_dummy.rs new file mode 100644 index 0000000..80c1288 --- /dev/null +++ b/matter/src/crypto/crypto_dummy.rs @@ -0,0 +1,57 @@ +/* + * + * 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 log::error; + +use crate::error::Error; + +use super::CryptoKeyPair; + +pub struct KeyPairDummy {} + +impl KeyPairDummy { + pub fn new() -> Result { + Ok(Self {}) + } +} + +impl CryptoKeyPair for KeyPairDummy { + fn get_csr<'a>(&self, _out_csr: &'a mut [u8]) -> Result<&'a [u8], Error> { + error!("This API should never get called"); + Err(Error::Invalid) + } + fn get_public_key(&self, _pub_key: &mut [u8]) -> Result { + error!("This API should never get called"); + Err(Error::Invalid) + } + fn get_private_key(&self, _pub_key: &mut [u8]) -> Result { + error!("This API should never get called"); + Err(Error::Invalid) + } + fn derive_secret(self, _peer_pub_key: &[u8], _secret: &mut [u8]) -> Result { + error!("This API should never get called"); + Err(Error::Invalid) + } + fn sign_msg(&self, _msg: &[u8], _signature: &mut [u8]) -> Result { + error!("This API should never get called"); + Err(Error::Invalid) + } + fn verify_msg(&self, _msg: &[u8], _signature: &[u8]) -> Result<(), Error> { + error!("This API should never get called"); + Err(Error::Invalid) + } +} diff --git a/matter/src/crypto/crypto_esp_mbedtls.rs b/matter/src/crypto/crypto_esp_mbedtls.rs new file mode 100644 index 0000000..9a8495d --- /dev/null +++ b/matter/src/crypto/crypto_esp_mbedtls.rs @@ -0,0 +1,133 @@ +/* + * + * 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 log::error; + +use crate::error::Error; + +use super::CryptoKeyPair; + +pub fn hkdf_sha256(_salt: &[u8], _ikm: &[u8], _info: &[u8], _key: &mut [u8]) -> Result<(), Error> { + error!("This API should never get called"); + Ok(()) +} + +#[derive(Clone)] +pub struct Sha256 {} + +impl Sha256 { + pub fn new() -> Result { + Ok(Self {}) + } + + pub fn update(&mut self, _data: &[u8]) -> Result<(), Error> { + Ok(()) + } + + pub fn finish(self, _digest: &mut [u8]) -> Result<(), Error> { + Ok(()) + } +} + +pub struct HmacSha256 {} + +impl HmacSha256 { + pub fn new(_key: &[u8]) -> Result { + error!("This API should never get called"); + Ok(Self {}) + } + + pub fn update(&mut self, _data: &[u8]) -> Result<(), Error> { + error!("This API should never get called"); + Ok(()) + } + + pub fn finish(self, _out: &mut [u8]) -> Result<(), Error> { + error!("This API should never get called"); + Ok(()) + } +} + +pub struct KeyPair {} + +impl KeyPair { + pub fn new() -> Result { + error!("This API should never get called"); + + Ok(Self {}) + } + + pub fn new_from_components(_pub_key: &[u8], priv_key: &[u8]) -> Result { + error!("This API should never get called"); + + Ok(Self {}) + } + + pub fn new_from_public(pub_key: &[u8]) -> Result { + error!("This API should never get called"); + + Ok(Self {}) + } +} + +impl CryptoKeyPair for KeyPair { + fn get_csr<'a>(&self, _out_csr: &'a mut [u8]) -> Result<&'a [u8], Error> { + error!("This API should never get called"); + Err(Error::Invalid) + } + fn get_public_key(&self, _pub_key: &mut [u8]) -> Result { + error!("This API should never get called"); + Err(Error::Invalid) + } + fn derive_secret(self, _peer_pub_key: &[u8], _secret: &mut [u8]) -> Result { + error!("This API should never get called"); + Err(Error::Invalid) + } + fn sign_msg(&self, _msg: &[u8], _signature: &mut [u8]) -> Result { + error!("This API should never get called"); + Err(Error::Invalid) + } + fn verify_msg(&self, _msg: &[u8], _signature: &[u8]) -> Result<(), Error> { + error!("This API should never get called"); + Err(Error::Invalid) + } +} + +pub fn pbkdf2_hmac(pass: &[u8], iter: usize, salt: &[u8], key: &mut [u8]) -> Result<(), Error> { + error!("This API should never get called"); + + Ok(()) +} + +pub fn encrypt_in_place( + _key: &[u8], + _nonce: &[u8], + _ad: &[u8], + _data: &mut [u8], + _data_len: usize, +) -> Result { + Ok(0) +} + +pub fn decrypt_in_place( + _key: &[u8], + _nonce: &[u8], + _ad: &[u8], + _data: &mut [u8], +) -> Result { + Ok(0) +} diff --git a/matter/src/crypto/crypto_mbedtls.rs b/matter/src/crypto/crypto_mbedtls.rs new file mode 100644 index 0000000..bf9cb88 --- /dev/null +++ b/matter/src/crypto/crypto_mbedtls.rs @@ -0,0 +1,379 @@ +/* + * + * 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::sync::Arc; + +use log::error; +use mbedtls::{ + bignum::Mpi, + cipher::{Authenticated, Cipher}, + ecp::EcPoint, + hash::{self, Hkdf, Hmac, Md, Type}, + pk::{EcGroup, EcGroupId, Pk}, + rng::{CtrDrbg, OsEntropy}, + x509, +}; + +use super::CryptoKeyPair; +use crate::error::Error; + +pub struct HmacSha256 { + inner: Hmac, +} + +impl HmacSha256 { + pub fn new(key: &[u8]) -> Result { + Ok(Self { + inner: Hmac::new(Type::Sha256, key)?, + }) + } + + pub fn update(&mut self, data: &[u8]) -> Result<(), Error> { + self.inner.update(data).map_err(|_| Error::TLSStack) + } + + pub fn finish(self, out: &mut [u8]) -> Result<(), Error> { + self.inner.finish(out).map_err(|_| Error::TLSStack)?; + Ok(()) + } +} + +pub struct KeyPair { + key: Pk, +} + +impl KeyPair { + pub fn new() -> Result { + let mut ctr_drbg = CtrDrbg::new(Arc::new(OsEntropy::new()), None)?; + Ok(Self { + key: Pk::generate_ec(&mut ctr_drbg, EcGroupId::SecP256R1)?, + }) + } + + pub fn new_from_components(_pub_key: &[u8], priv_key: &[u8]) -> Result { + // No rust-mbedtls API yet for creating keypair from both public and private key + let priv_key = Mpi::from_binary(priv_key)?; + Ok(Self { + key: Pk::private_from_ec_components(EcGroup::new(EcGroupId::SecP256R1)?, priv_key)?, + }) + } + + pub fn new_from_public(pub_key: &[u8]) -> Result { + let group = EcGroup::new(EcGroupId::SecP256R1)?; + let pub_key = EcPoint::from_binary(&group, pub_key)?; + + Ok(Self { + key: Pk::public_from_ec_components(group, pub_key)?, + }) + } +} + +impl CryptoKeyPair for KeyPair { + fn get_csr<'a>(&self, out_csr: &'a mut [u8]) -> Result<&'a [u8], Error> { + let tmp_priv = self.key.ec_private()?; + let mut tmp_key = + Pk::private_from_ec_components(EcGroup::new(EcGroupId::SecP256R1)?, tmp_priv)?; + + let mut builder = x509::csr::Builder::new(); + builder.key(&mut tmp_key); + builder.signature_hash(mbedtls::hash::Type::Sha256); + builder.subject("O=CSR")?; + + let mut ctr_drbg = CtrDrbg::new(Arc::new(OsEntropy::new()), None)?; + match builder.write_der(out_csr, &mut ctr_drbg) { + Ok(Some(a)) => Ok(a), + Ok(None) => { + error!("Error in writing CSR: None received"); + Err(Error::Invalid) + } + Err(e) => { + error!("Error in writing CSR {}", e); + Err(Error::TLSStack) + } + } + } + + fn get_public_key(&self, pub_key: &mut [u8]) -> Result { + let public_key = self.key.ec_public()?; + let group = EcGroup::new(EcGroupId::SecP256R1)?; + let vec = public_key.to_binary(&group, false)?; + + let len = vec.len(); + pub_key[..len].copy_from_slice(vec.as_slice()); + Ok(len) + } + + fn get_private_key(&self, priv_key: &mut [u8]) -> Result { + let priv_key_mpi = self.key.ec_private()?; + let vec = priv_key_mpi.to_binary()?; + + let len = vec.len(); + priv_key[..len].copy_from_slice(vec.as_slice()); + Ok(len) + } + + fn derive_secret(self, peer_pub_key: &[u8], secret: &mut [u8]) -> Result { + // mbedtls requires a 'mut' key. Instead of making a change in our Trait, + // we just clone the key this way + + let tmp_key = self.key.ec_private()?; + let mut tmp_key = + Pk::private_from_ec_components(EcGroup::new(EcGroupId::SecP256R1)?, tmp_key)?; + + let group = EcGroup::new(EcGroupId::SecP256R1)?; + let other = EcPoint::from_binary(&group, peer_pub_key)?; + let other = Pk::public_from_ec_components(group, other)?; + + let mut ctr_drbg = CtrDrbg::new(Arc::new(OsEntropy::new()), None)?; + + let len = tmp_key.agree(&other, secret, &mut ctr_drbg)?; + Ok(len) + } + + fn sign_msg(&self, msg: &[u8], signature: &mut [u8]) -> Result { + // mbedtls requires a 'mut' key. Instead of making a change in our Trait, + // we just clone the key this way + let tmp_key = self.key.ec_private()?; + let mut tmp_key = + Pk::private_from_ec_components(EcGroup::new(EcGroupId::SecP256R1)?, tmp_key)?; + + // First get the SHA256 of the message + let mut msg_hash = [0_u8; super::SHA256_HASH_LEN_BYTES]; + Md::hash(hash::Type::Sha256, msg, &mut msg_hash)?; + let mut ctr_drbg = CtrDrbg::new(Arc::new(OsEntropy::new()), None)?; + + if signature.len() < super::EC_SIGNATURE_LEN_BYTES { + return Err(Error::NoSpace); + } + safemem::write_bytes(signature, 0); + + // mbedTLS writes the DER signature first + // TODO: Update rust-mbedtls to provide raw level APIs to get r and s values + let mut tmp_sign = [0u8; super::EC_SIGNATURE_LEN_BYTES * 3]; + tmp_key.sign(hash::Type::Sha256, &msg_hash, &mut tmp_sign, &mut ctr_drbg)?; + let len = convert_asn1_sign_to_r_s(&mut tmp_sign)?; + signature[..len].copy_from_slice(&tmp_sign[..len]); + Ok(len) + } + + fn verify_msg(&self, msg: &[u8], signature: &[u8]) -> Result<(), Error> { + // mbedtls requires a 'mut' key. Instead of making a change in our Trait, + // we just clone the key this way + let tmp_key = self.key.ec_public()?; + let mut tmp_key = + Pk::public_from_ec_components(EcGroup::new(EcGroupId::SecP256R1)?, tmp_key)?; + + // First get the SHA256 of the message + let mut msg_hash = [0_u8; super::SHA256_HASH_LEN_BYTES]; + Md::hash(hash::Type::Sha256, msg, &mut msg_hash)?; + + // current rust-mbedTLS APIs the signature to be in DER format + let mut mbedtls_sign = [0u8; super::EC_SIGNATURE_LEN_BYTES * 3]; + let len = convert_r_s_to_asn1_sign(signature, &mut mbedtls_sign); + let mbedtls_sign = &mbedtls_sign[..len]; + + if let Err(e) = tmp_key.verify(hash::Type::Sha256, &msg_hash, mbedtls_sign) { + println!("The error is {}", e); + Err(Error::InvalidSignature) + } else { + Ok(()) + } + } +} + +fn convert_r_s_to_asn1_sign(signature: &[u8], mbedtls_sign: &mut [u8]) -> usize { + let mut offset = 0; + mbedtls_sign[offset] = 0x30; + offset += 1; + let mut len = 68; + if (signature[0] & 0x80) == 0x80 { + len += 1; + } + if (signature[32] & 0x80) == 0x80 { + len += 1; + } + mbedtls_sign[offset] = len; + offset += 1; + mbedtls_sign[offset] = 0x02; + offset += 1; + if (signature[0] & 0x80) == 0x80 { + // It seems if topmost bit is 1, there is an extra 0 + mbedtls_sign[offset] = 33; + offset += 1; + mbedtls_sign[offset] = 0; + offset += 1; + } else { + mbedtls_sign[offset] = 32; + offset += 1; + } + mbedtls_sign[offset..(offset + 32)].copy_from_slice(&signature[..32]); + offset += 32; + + mbedtls_sign[offset] = 0x02; + offset += 1; + if (signature[32] & 0x80) == 0x80 { + // It seems if topmost bit is 1, there is an extra 0 + mbedtls_sign[offset] = 33; + offset += 1; + mbedtls_sign[offset] = 0; + offset += 1; + } else { + mbedtls_sign[offset] = 32; + offset += 1; + } + + mbedtls_sign[offset..(offset + 32)].copy_from_slice(&signature[32..64]); + offset += 32; + + offset +} + +// mbedTLS sign() function directly encodes the signature in ASN1. The lower level function +// is not yet exposed to us through the Rust crate. So here, I am crudely extracting the 'r' +// and 's' values from the ASN1 encoding and writing 'r' and 's' back sequentially as is expected +// per the Matter spec. +fn convert_asn1_sign_to_r_s(signature: &mut [u8]) -> Result { + if signature[0] == 0x30 { + // Type 0x30 ASN1 Sequence + // Length: Skip + let mut offset: usize = 2; + + // Type 0x2 is Integer (first integer is r) + if signature[offset] != 2 { + return Err(Error::Invalid); + } + offset += 1; + + // Length + let len = signature[offset]; + offset += 1; + // XXX Once, I have seen a crash in this conversion, need to dig + if len < 32 { + error!( + "Cannot deal with this: this will crash: the slice is: {:x?}", + signature + ); + } + + // Sometimes length is more than 32 with a 0 prefix-padded, skip over that + offset += (len - 32) as usize; + + // Extract the 32 bytes of 'r' + let mut r = [0_u8; super::BIGNUM_LEN_BYTES]; + r.copy_from_slice(&signature[offset..(offset + 32)]); + offset += 32; + + // Type 0x2 is Integer (this integer is s) + if signature[offset] != 2 { + return Err(Error::Invalid); + } + offset += 1; + + // Length + let len = signature[offset]; + offset += 1; + // Sometimes length is more than 32 with a 0 prefix-padded, skip over that + offset += (len - 32) as usize; + + // Extract the 32 bytes of 's' + let mut s = [0_u8; super::BIGNUM_LEN_BYTES]; + s.copy_from_slice(&signature[offset..(offset + 32)]); + + signature[0..32].copy_from_slice(&r); + signature[32..64].copy_from_slice(&s); + + Ok(64) + } else { + Err(Error::Invalid) + } +} + +pub fn pbkdf2_hmac(pass: &[u8], iter: usize, salt: &[u8], key: &mut [u8]) -> Result<(), Error> { + mbedtls::hash::pbkdf2_hmac(Type::Sha256, pass, salt, iter as u32, key) + .map_err(|_e| Error::TLSStack) +} + +pub fn hkdf_sha256(salt: &[u8], ikm: &[u8], info: &[u8], key: &mut [u8]) -> Result<(), Error> { + Hkdf::hkdf(Type::Sha256, salt, ikm, info, key).map_err(|_e| Error::TLSStack) +} + +pub fn encrypt_in_place( + key: &[u8], + nonce: &[u8], + ad: &[u8], + data: &mut [u8], + data_len: usize, +) -> Result { + let cipher = Cipher::<_, Authenticated, _>::new( + mbedtls::cipher::raw::CipherId::Aes, + mbedtls::cipher::raw::CipherMode::CCM, + (key.len() * 8) as u32, + )?; + let cipher = cipher.set_key_iv(key, nonce)?; + let (data, tag) = data.split_at_mut(data_len); + let tag = &mut tag[..super::AEAD_MIC_LEN_BYTES]; + cipher + .encrypt_auth_inplace(ad, data, tag) + .map(|(len, _)| len) + .map_err(|_e| Error::TLSStack) +} + +pub fn decrypt_in_place( + key: &[u8], + nonce: &[u8], + ad: &[u8], + data: &mut [u8], +) -> Result { + let cipher = Cipher::<_, Authenticated, _>::new( + mbedtls::cipher::raw::CipherId::Aes, + mbedtls::cipher::raw::CipherMode::CCM, + (key.len() * 8) as u32, + )?; + let cipher = cipher.set_key_iv(key, nonce)?; + let data_len = data.len() - super::AEAD_MIC_LEN_BYTES; + let (data, tag) = data.split_at_mut(data_len); + cipher + .decrypt_auth_inplace(ad, data, tag) + .map(|(len, _)| len) + .map_err(|e| { + error!("Error during decryption: {:?}", e); + Error::TLSStack + }) +} + +#[derive(Clone)] +pub struct Sha256 { + ctx: Md, +} + +impl Sha256 { + pub fn new() -> Result { + Ok(Self { + ctx: Md::new(Type::Sha256)?, + }) + } + + pub fn update(&mut self, data: &[u8]) -> Result<(), Error> { + self.ctx.update(data).map_err(|_| Error::TLSStack)?; + Ok(()) + } + + pub fn finish(self, digest: &mut [u8]) -> Result<(), Error> { + self.ctx.finish(digest).map_err(|_| Error::TLSStack)?; + Ok(()) + } +} diff --git a/matter/src/crypto/crypto_openssl.rs b/matter/src/crypto/crypto_openssl.rs new file mode 100644 index 0000000..cfa24a2 --- /dev/null +++ b/matter/src/crypto/crypto_openssl.rs @@ -0,0 +1,386 @@ +/* + * + * 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::error::Error; + +use super::CryptoKeyPair; +use foreign_types::ForeignTypeRef; +use log::error; +use openssl::asn1::Asn1Type; +use openssl::bn::{BigNum, BigNumContext}; +use openssl::cipher::CipherRef; +use openssl::cipher_ctx::{CipherCtx, CipherCtxRef}; +use openssl::derive::Deriver; +use openssl::ec::{EcGroup, EcKey, EcPoint, EcPointRef, PointConversionForm}; +use openssl::ecdsa::EcdsaSig; +use openssl::error::ErrorStack; +use openssl::hash::{Hasher, MessageDigest}; +use openssl::md::Md; +use openssl::nid::Nid; +use openssl::pkey::{self, Id, PKey, Private}; +use openssl::pkey_ctx::PkeyCtx; +use openssl::symm::{self}; +use openssl::x509::{X509NameBuilder, X509ReqBuilder, X509}; + +// We directly use the hmac crate here, there was a self-referential structure +// problem while using OpenSSL's Signer +// TODO: Use proper OpenSSL method for this +use hmac::{Hmac, Mac, NewMac}; +pub struct HmacSha256 { + ctx: Hmac, +} + +impl HmacSha256 { + pub fn new(key: &[u8]) -> Result { + Ok(Self { + ctx: Hmac::::new_from_slice(key).map_err(|_x| Error::InvalidKeyLength)?, + }) + } + + pub fn update(&mut self, data: &[u8]) -> Result<(), Error> { + Ok(self.ctx.update(data)) + } + + pub fn finish(self, out: &mut [u8]) -> Result<(), Error> { + let a = self.ctx.finalize().into_bytes(); + out.copy_from_slice(a.as_slice()); + Ok(()) + } +} + +pub enum KeyType { + Public(EcKey), + Private(EcKey), +} +pub struct KeyPair { + key: KeyType, +} + +impl KeyPair { + pub fn new() -> Result { + let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)?; + let key = EcKey::generate(&group)?; + Ok(Self { + key: KeyType::Private(key), + }) + } + + pub fn new_from_components(pub_key: &[u8], priv_key: &[u8]) -> Result { + let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)?; + let mut ctx = BigNumContext::new()?; + let priv_key = BigNum::from_slice(priv_key)?; + let pub_key = EcPoint::from_bytes(&group, pub_key, &mut ctx)?; + Ok(Self { + key: KeyType::Private(EcKey::from_private_components(&group, &priv_key, &pub_key)?), + }) + } + + pub fn new_from_public(pub_key: &[u8]) -> Result { + let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)?; + let mut ctx = BigNumContext::new()?; + let pub_key = EcPoint::from_bytes(&group, pub_key, &mut ctx)?; + + Ok(Self { + key: KeyType::Public(EcKey::from_public_key(&group, &pub_key)?), + }) + } + + fn public_key_point(&self) -> &EcPointRef { + match &self.key { + KeyType::Public(k) => k.public_key(), + KeyType::Private(k) => k.public_key(), + } + } + + fn private_key(&self) -> Result<&EcKey, Error> { + match &self.key { + KeyType::Public(_) => Err(Error::Invalid), + KeyType::Private(k) => Ok(&k), + } + } +} + +impl CryptoKeyPair for KeyPair { + fn get_public_key(&self, pub_key: &mut [u8]) -> Result { + let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)?; + let mut bn_ctx = BigNumContext::new()?; + let s = self.public_key_point().to_bytes( + &group, + PointConversionForm::UNCOMPRESSED, + &mut bn_ctx, + )?; + let len = s.len(); + pub_key[..len].copy_from_slice(s.as_slice()); + Ok(len) + } + + fn get_private_key(&self, priv_key: &mut [u8]) -> Result { + let s = self.private_key()?.private_key().to_vec(); + let len = s.len(); + priv_key[..len].copy_from_slice(s.as_slice()); + Ok(len) + } + + fn derive_secret(self, peer_pub_key: &[u8], secret: &mut [u8]) -> Result { + let self_pkey = PKey::from_ec_key(self.private_key()?.clone())?; + + let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)?; + let mut ctx = BigNumContext::new()?; + let point = EcPoint::from_bytes(&group, peer_pub_key, &mut ctx)?; + let peer_key = EcKey::from_public_key(&group, &point)?; + let peer_pkey = PKey::from_ec_key(peer_key)?; + + let mut deriver = Deriver::new(&self_pkey)?; + deriver.set_peer(&peer_pkey)?; + Ok(deriver.derive(secret)?) + } + + fn get_csr<'a>(&self, out_csr: &'a mut [u8]) -> Result<&'a [u8], Error> { + let mut builder = X509ReqBuilder::new()?; + builder.set_version(0)?; + + let pkey = PKey::from_ec_key(self.private_key()?.clone())?; + builder.set_pubkey(&pkey)?; + + let mut name_builder = X509NameBuilder::new()?; + name_builder.append_entry_by_text_with_type("O", "CSR", Asn1Type::IA5STRING)?; + let subject_name = name_builder.build(); + builder.set_subject_name(&subject_name)?; + + builder.sign(&pkey, MessageDigest::sha256())?; + + let csr_vec = builder.build().to_der()?; + let csr = csr_vec.as_slice(); + if csr.len() < out_csr.len() { + let a = &mut out_csr[0..csr.len()]; + a.copy_from_slice(csr); + Ok(a) + } else { + Err(Error::NoSpace) + } + } + + fn sign_msg(&self, msg: &[u8], signature: &mut [u8]) -> Result { + // First get the SHA256 of the message + let mut h = Hasher::new(MessageDigest::sha256())?; + h.update(msg)?; + let msg = h.finish()?; + + if signature.len() < super::EC_SIGNATURE_LEN_BYTES { + return Err(Error::NoSpace); + } + safemem::write_bytes(signature, 0); + + let sig = EcdsaSig::sign(&msg, self.private_key()?)?; + let r = sig.r().to_vec(); + signature[0..r.len()].copy_from_slice(r.as_slice()); + let s = sig.s().to_vec(); + signature[32..(32 + s.len())].copy_from_slice(s.as_slice()); + Ok(64) + } + + fn verify_msg(&self, msg: &[u8], signature: &[u8]) -> Result<(), Error> { + // First get the SHA256 of the message + let mut h = Hasher::new(MessageDigest::sha256())?; + h.update(msg)?; + let msg = h.finish()?; + + let r = BigNum::from_slice(&signature[0..super::BIGNUM_LEN_BYTES])?; + let s = + BigNum::from_slice(&signature[super::BIGNUM_LEN_BYTES..(2 * super::BIGNUM_LEN_BYTES)])?; + let sig = EcdsaSig::from_private_components(r, s)?; + + let k = match &self.key { + KeyType::Public(key) => key, + _ => { + error!("Not yet supported"); + return Err(Error::Invalid); + } + }; + if !sig.verify(&msg, k)? { + Err(Error::InvalidSignature) + } else { + Ok(()) + } + } +} + +const P256_KEY_LEN: usize = 256 / 8; +pub fn pubkey_from_der<'a>(der: &'a [u8], out_key: &mut [u8]) -> Result<(), Error> { + if out_key.len() != P256_KEY_LEN { + error!("Insufficient length"); + Err(Error::NoSpace) + } else { + let key = X509::from_der(der)?.public_key()?.public_key_to_der()?; + let len = key.len(); + let out_key = &mut out_key[..len]; + out_key.copy_from_slice(key.as_slice()); + Ok(()) + } +} + +pub fn pbkdf2_hmac(pass: &[u8], iter: usize, salt: &[u8], key: &mut [u8]) -> Result<(), Error> { + openssl::pkcs5::pbkdf2_hmac(pass, salt, iter, MessageDigest::sha256(), key) + .map_err(|_e| Error::TLSStack) +} + +pub fn hkdf_sha256(salt: &[u8], ikm: &[u8], info: &[u8], key: &mut [u8]) -> Result<(), Error> { + let mut ctx = PkeyCtx::new_id(Id::HKDF)?; + ctx.derive_init()?; + ctx.set_hkdf_md(Md::sha256())?; + ctx.set_hkdf_key(ikm)?; + if !salt.is_empty() { + ctx.set_hkdf_salt(salt)?; + } + ctx.add_hkdf_info(info)?; + ctx.derive(Some(key))?; + Ok(()) +} + +pub fn encrypt_in_place( + key: &[u8], + nonce: &[u8], + ad: &[u8], + data: &mut [u8], + data_len: usize, +) -> Result { + let (plain_text, tag) = data.split_at_mut(data_len); + + let result = lowlevel_encrypt_aead( + key, + Some(nonce), + ad, + plain_text, + &mut tag[..super::AEAD_MIC_LEN_BYTES], + )?; + data[..data_len].copy_from_slice(result.as_slice()); + Ok(result.len() + super::AEAD_MIC_LEN_BYTES) +} + +pub fn decrypt_in_place( + key: &[u8], + nonce: &[u8], + ad: &[u8], + data: &mut [u8], +) -> Result { + let tag_start = data.len() - super::AEAD_MIC_LEN_BYTES; + let (data, tag) = data.split_at_mut(tag_start); + let result = lowlevel_decrypt_aead(key, Some(nonce), ad, data, &tag)?; + data[..result.len()].copy_from_slice(result.as_slice()); + Ok(result.len()) +} + +// The default encrypt/decrypt routines in rust-mbedtls have a problem in the ordering of +// set-tag-length. This causes the CCM tag-length to be use incorrectly. +// Instead we use the low-level CipherCtx APIs here to get the desired behaviour. +// More details available here: https://github.com/sfackler/rust-openssl/pull/1594/ +// Need to pursue this PR when I get a chance +pub fn lowlevel_encrypt_aead( + key: &[u8], + iv: Option<&[u8]>, + aad: &[u8], + data: &[u8], + tag: &mut [u8], +) -> Result, ErrorStack> { + let t = symm::Cipher::aes_128_ccm(); + let mut ctx = CipherCtx::new()?; + CipherCtxRef::encrypt_init( + &mut ctx, + Some(unsafe { CipherRef::from_ptr(t.as_ptr() as *mut _) }), + None, + None, + )?; + + ctx.set_tag_length(tag.len())?; + ctx.set_key_length(key.len())?; + if let (Some(iv), Some(iv_len)) = (iv, t.iv_len()) { + if iv.len() != iv_len { + ctx.set_iv_length(iv.len())?; + } + } + CipherCtxRef::encrypt_init(&mut ctx, None, Some(key), iv)?; + + let mut out = vec![0; data.len() + t.block_size()]; + ctx.set_data_len(data.len())?; + + ctx.cipher_update(aad, None)?; + let count = ctx.cipher_update(data, Some(&mut out))?; + let rest = ctx.cipher_final(&mut out[count..])?; + ctx.tag(tag)?; + out.truncate(count + rest); + Ok(out) +} + +pub fn lowlevel_decrypt_aead( + key: &[u8], + iv: Option<&[u8]>, + aad: &[u8], + data: &[u8], + tag: &[u8], +) -> Result, ErrorStack> { + let t = symm::Cipher::aes_128_ccm(); + let mut ctx = CipherCtx::new()?; + CipherCtxRef::decrypt_init( + &mut ctx, + Some(unsafe { CipherRef::from_ptr(t.as_ptr() as *mut _) }), + None, + None, + )?; + + ctx.set_tag_length(tag.len())?; + ctx.set_key_length(key.len())?; + if let (Some(iv), Some(iv_len)) = (iv, t.iv_len()) { + if iv.len() != iv_len { + ctx.set_iv_length(iv.len())?; + } + } + CipherCtxRef::decrypt_init(&mut ctx, None, Some(key), iv)?; + + let mut out = vec![0; data.len() + t.block_size()]; + + ctx.set_tag(tag)?; + ctx.set_data_len(data.len())?; + + ctx.cipher_update(aad, None)?; + let count = ctx.cipher_update(data, Some(&mut out))?; + + out.truncate(count); + Ok(out) +} + +#[derive(Clone)] +pub struct Sha256 { + hasher: Hasher, +} + +impl Sha256 { + pub fn new() -> Result { + Ok(Self { + hasher: Hasher::new(MessageDigest::sha256())?, + }) + } + + pub fn update(&mut self, data: &[u8]) -> Result<(), Error> { + self.hasher.update(data).map_err(|_| Error::TLSStack) + } + + pub fn finish(mut self, data: &mut [u8]) -> Result<(), Error> { + let h = self.hasher.finish()?; + data.copy_from_slice(h.as_ref()); + Ok(()) + } +} diff --git a/matter/src/crypto/mod.rs b/matter/src/crypto/mod.rs new file mode 100644 index 0000000..aa707f7 --- /dev/null +++ b/matter/src/crypto/mod.rs @@ -0,0 +1,164 @@ +/* + * + * 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::error::Error; + +pub const SYMM_KEY_LEN_BITS: usize = 128; +pub const SYMM_KEY_LEN_BYTES: usize = SYMM_KEY_LEN_BITS / 8; + +pub const AEAD_MIC_LEN_BITS: usize = 128; +pub const AEAD_MIC_LEN_BYTES: usize = AEAD_MIC_LEN_BITS / 8; + +pub const AEAD_NONCE_LEN_BYTES: usize = 13; +pub const AEAD_AAD_LEN_BYTES: usize = 8; + +pub const SHA256_HASH_LEN_BYTES: usize = 256 / 8; + +pub const BIGNUM_LEN_BYTES: usize = 32; +pub const EC_POINT_LEN_BYTES: usize = 65; + +pub const ECDH_SHARED_SECRET_LEN_BYTES: usize = 32; + +pub const EC_SIGNATURE_LEN_BYTES: usize = 64; + +// APIs particular to a KeyPair so a KeyPair object can be defined +pub trait CryptoKeyPair { + fn get_csr<'a>(&self, csr: &'a mut [u8]) -> Result<&'a [u8], Error>; + fn get_public_key(&self, pub_key: &mut [u8]) -> Result; + fn get_private_key(&self, priv_key: &mut [u8]) -> Result; + fn derive_secret(self, peer_pub_key: &[u8], secret: &mut [u8]) -> Result; + fn sign_msg(&self, msg: &[u8], signature: &mut [u8]) -> Result; + fn verify_msg(&self, msg: &[u8], signature: &[u8]) -> Result<(), Error>; +} + +#[cfg(feature = "crypto_esp_mbedtls")] +mod crypto_esp_mbedtls; +#[cfg(feature = "crypto_esp_mbedtls")] +pub use self::crypto_esp_mbedtls::*; + +#[cfg(feature = "crypto_mbedtls")] +mod crypto_mbedtls; +#[cfg(feature = "crypto_mbedtls")] +pub use self::crypto_mbedtls::*; + +#[cfg(feature = "crypto_openssl")] +mod crypto_openssl; +#[cfg(feature = "crypto_openssl")] +pub use self::crypto_openssl::*; + +pub mod crypto_dummy; + +#[cfg(test)] +mod tests { + use crate::error::Error; + + use super::{CryptoKeyPair, KeyPair}; + + #[test] + fn test_verify_msg_success() { + let key = KeyPair::new_from_public(&test_vectors::PUB_KEY1).unwrap(); + key.verify_msg(&test_vectors::MSG1_SUCCESS, &test_vectors::SIGNATURE1) + .unwrap(); + } + #[test] + fn test_verify_msg_fail() { + let key = KeyPair::new_from_public(&test_vectors::PUB_KEY1).unwrap(); + assert_eq!( + key.verify_msg(&test_vectors::MSG1_FAIL, &test_vectors::SIGNATURE1), + Err(Error::InvalidSignature) + ); + } + + mod test_vectors { + pub const PUB_KEY1: [u8; 65] = [ + 0x4, 0x56, 0x19, 0x77, 0x18, 0x3f, 0xd4, 0xff, 0x2b, 0x58, 0x3d, 0xe9, 0x79, 0x34, + 0x66, 0xdf, 0xe9, 0x0, 0xfb, 0x6d, 0xa1, 0xef, 0xe0, 0xcc, 0xdc, 0x77, 0x30, 0xc0, + 0x6f, 0xb6, 0x2d, 0xff, 0xbe, 0x54, 0xa0, 0x95, 0x75, 0xb, 0x8b, 0x7, 0xbc, 0x55, 0xdb, + 0x9c, 0xb6, 0x55, 0x13, 0x8, 0xb8, 0xdf, 0x2, 0xe3, 0x40, 0x6b, 0xae, 0x34, 0xf5, 0xc, + 0xba, 0xc9, 0xf2, 0xbf, 0xf1, 0xe7, 0x50, + ]; + pub const MSG1_SUCCESS: [u8; 421] = [ + 0x30, 0x82, 0x1, 0xa1, 0xa0, 0x3, 0x2, 0x1, 0x2, 0x2, 0x1, 0x1, 0x30, 0xa, 0x6, 0x8, + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x4, 0x3, 0x2, 0x30, 0x44, 0x31, 0x20, 0x30, 0x1e, 0x6, + 0xa, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0xa2, 0x7c, 0x1, 0x3, 0xc, 0x10, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x31, + 0x20, 0x30, 0x1e, 0x6, 0xa, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0xa2, 0x7c, 0x1, 0x5, 0xc, + 0x10, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x31, 0x30, 0x1e, 0x17, 0xd, 0x32, 0x31, 0x30, 0x31, 0x30, 0x31, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0xd, 0x33, 0x30, 0x31, 0x32, 0x33, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x44, 0x31, 0x20, 0x30, 0x1e, 0x6, 0xa, + 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0xa2, 0x7c, 0x1, 0x1, 0xc, 0x10, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x42, 0x43, 0x35, 0x43, 0x30, 0x32, 0x31, + 0x20, 0x30, 0x1e, 0x6, 0xa, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0xa2, 0x7c, 0x1, 0x5, 0xc, + 0x10, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x31, 0x30, 0x59, 0x30, 0x13, 0x6, 0x7, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x2, + 0x1, 0x6, 0x8, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x3, 0x1, 0x7, 0x3, 0x42, 0x0, 0x4, 0x6, + 0x47, 0xf2, 0x86, 0x4d, 0x27, 0x25, 0xdc, 0x1, 0xa, 0x87, 0xde, 0x8d, 0xca, 0x88, 0x37, + 0xcb, 0x3b, 0xd0, 0xea, 0x93, 0xa6, 0x24, 0x65, 0x8, 0x8f, 0xa1, 0x75, 0xc2, 0xd4, + 0x41, 0xfa, 0xca, 0x96, 0x54, 0xa3, 0xd8, 0x10, 0x85, 0x73, 0xce, 0x15, 0xa5, 0x38, + 0xc1, 0xe3, 0xb5, 0x6b, 0x61, 0x1, 0xd3, 0xc4, 0xb7, 0x6b, 0x61, 0x16, 0xc3, 0x77, + 0x8d, 0xe9, 0xb5, 0x44, 0xac, 0x14, 0xa3, 0x81, 0x83, 0x30, 0x81, 0x80, 0x30, 0xc, 0x6, + 0x3, 0x55, 0x1d, 0x13, 0x1, 0x1, 0xff, 0x4, 0x2, 0x30, 0x0, 0x30, 0xe, 0x6, 0x3, 0x55, + 0x1d, 0xf, 0x1, 0x1, 0xff, 0x4, 0x4, 0x3, 0x2, 0x7, 0x80, 0x30, 0x20, 0x6, 0x3, 0x55, + 0x1d, 0x25, 0x1, 0x1, 0xff, 0x4, 0x16, 0x30, 0x14, 0x6, 0x8, 0x2b, 0x6, 0x1, 0x5, 0x5, + 0x7, 0x3, 0x2, 0x6, 0x8, 0x2b, 0x6, 0x1, 0x5, 0x5, 0x7, 0x3, 0x1, 0x30, 0x1d, 0x6, 0x3, + 0x55, 0x1d, 0xe, 0x4, 0x16, 0x4, 0x14, 0xbd, 0xfd, 0x11, 0xac, 0x89, 0xb6, 0xe0, 0x90, + 0x7a, 0xf6, 0x12, 0x61, 0x78, 0x4d, 0x3d, 0x79, 0x56, 0xeb, 0xc2, 0xdc, 0x30, 0x1f, + 0x6, 0x3, 0x55, 0x1d, 0x23, 0x4, 0x18, 0x30, 0x16, 0x80, 0x14, 0xce, 0x60, 0xb4, 0x28, + 0x96, 0x72, 0x27, 0x64, 0x81, 0xbc, 0x4f, 0x0, 0x78, 0xa3, 0x30, 0x48, 0xfe, 0x6e, + 0x65, 0x86, + ]; + pub const MSG1_FAIL: [u8; 421] = [ + 0x30, 0x82, 0x1, 0xa1, 0xa0, 0x3, 0x2, 0x1, 0x2, 0x2, 0x1, 0x1, 0x30, 0xa, 0x6, 0x8, + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x4, 0x3, 0x2, 0x30, 0x44, 0x31, 0x20, 0x30, 0x1e, 0x6, + 0xa, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0xa2, 0x7c, 0x1, 0x3, 0xc, 0x10, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x31, + 0x20, 0x30, 0x1e, 0x6, 0xa, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0xa2, 0x7c, 0x1, 0x5, 0xc, + 0x10, 0x30, 0x30, 0x30, 0x31, 0x32, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x31, 0x30, 0x1e, 0x17, 0xd, 0x32, 0x31, 0x30, 0x31, 0x30, 0x31, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0xd, 0x33, 0x30, 0x31, 0x32, 0x33, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x44, 0x31, 0x20, 0x30, 0x1e, 0x6, 0xa, + 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0xa2, 0x7c, 0x1, 0x1, 0xc, 0x10, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x42, 0x43, 0x35, 0x43, 0x30, 0x32, 0x31, + 0x20, 0x30, 0x1e, 0x6, 0xa, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0xa2, 0x7c, 0x1, 0x5, 0xc, + 0x10, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x31, 0x30, 0x59, 0x30, 0x13, 0x6, 0x7, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x2, + 0x1, 0x6, 0x8, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x3, 0x1, 0x7, 0x3, 0x42, 0x0, 0x4, 0x6, + 0x47, 0xf2, 0x86, 0x4d, 0x27, 0x25, 0xdc, 0x1, 0xa, 0x87, 0xde, 0x8d, 0xca, 0x88, 0x37, + 0xcb, 0x3b, 0xd0, 0xea, 0x93, 0xa6, 0x24, 0x65, 0x8, 0x8f, 0xa1, 0x75, 0xc2, 0xd4, + 0x41, 0xfa, 0xca, 0x96, 0x54, 0xa3, 0xd8, 0x10, 0x85, 0x73, 0xce, 0x15, 0xa5, 0x38, + 0xc1, 0xe3, 0xb5, 0x6b, 0x61, 0x1, 0xd3, 0xc4, 0xb7, 0x6b, 0x61, 0x16, 0xc3, 0x77, + 0x8d, 0xe9, 0xb5, 0x44, 0xac, 0x14, 0xa3, 0x81, 0x83, 0x30, 0x81, 0x80, 0x30, 0xc, 0x6, + 0x3, 0x55, 0x1d, 0x13, 0x1, 0x1, 0xff, 0x4, 0x2, 0x30, 0x0, 0x30, 0xe, 0x6, 0x3, 0x55, + 0x1d, 0xf, 0x1, 0x1, 0xff, 0x4, 0x4, 0x3, 0x2, 0x7, 0x80, 0x30, 0x20, 0x6, 0x3, 0x55, + 0x1d, 0x25, 0x1, 0x1, 0xff, 0x4, 0x16, 0x30, 0x14, 0x6, 0x8, 0x2b, 0x6, 0x1, 0x5, 0x5, + 0x7, 0x3, 0x2, 0x6, 0x8, 0x2b, 0x6, 0x1, 0x5, 0x5, 0x7, 0x3, 0x1, 0x30, 0x1d, 0x6, 0x3, + 0x55, 0x1d, 0xe, 0x4, 0x16, 0x4, 0x14, 0xbd, 0xfd, 0x11, 0xac, 0x89, 0xb6, 0xe0, 0x90, + 0x7a, 0xf6, 0x12, 0x61, 0x78, 0x4d, 0x3d, 0x79, 0x56, 0xeb, 0xc2, 0xdc, 0x30, 0x1f, + 0x6, 0x3, 0x55, 0x1d, 0x23, 0x4, 0x18, 0x30, 0x16, 0x80, 0x14, 0xce, 0x60, 0xb4, 0x28, + 0x96, 0x72, 0x27, 0x64, 0x81, 0xbc, 0x4f, 0x0, 0x78, 0xa3, 0x30, 0x48, 0xfe, 0x6e, + 0x65, 0x86, + ]; + pub const SIGNATURE1: [u8; 64] = [ + 0x20, 0x16, 0xd0, 0x13, 0x1e, 0xd0, 0xb3, 0x9d, 0x44, 0x25, 0x16, 0xea, 0x9c, 0xf2, + 0x72, 0x44, 0xd7, 0xb0, 0xf4, 0xae, 0x4a, 0xa4, 0x37, 0x32, 0xcd, 0x6a, 0x79, 0x7a, + 0x4c, 0x48, 0x3, 0x6d, 0xef, 0xe6, 0x26, 0x82, 0x39, 0x28, 0x9, 0x22, 0xc8, 0x9a, 0xde, + 0xd5, 0x13, 0x9f, 0xc5, 0x40, 0x25, 0x85, 0x2c, 0x69, 0xe0, 0xdb, 0x6a, 0x79, 0x5b, + 0x21, 0x82, 0x13, 0xb0, 0x20, 0xb9, 0x69, + ]; + } +} diff --git a/matter/src/data_model/cluster_basic_information.rs b/matter/src/data_model/cluster_basic_information.rs new file mode 100644 index 0000000..fbc33a4 --- /dev/null +++ b/matter/src/data_model/cluster_basic_information.rs @@ -0,0 +1,96 @@ +/* + * + * 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::objects::*; +use crate::error::*; + +pub const ID: u32 = 0x0028; +enum Attributes { + VendorId = 2, + ProductId = 4, + HwVer = 7, + SwVer = 9, +} + +pub struct BasicInfoConfig { + pub vid: u16, + pub pid: u16, + pub hw_ver: u16, + pub sw_ver: u32, +} + +fn attr_vid_new(vid: u16) -> Result { + Attribute::new( + Attributes::VendorId as u16, + AttrValue::Uint16(vid), + Access::RV, + Quality::FIXED, + ) +} + +fn attr_pid_new(pid: u16) -> Result { + Attribute::new( + Attributes::ProductId as u16, + AttrValue::Uint16(pid), + Access::RV, + Quality::FIXED, + ) +} + +fn attr_hw_ver_new(hw_ver: u16) -> Result { + Attribute::new( + Attributes::HwVer as u16, + AttrValue::Uint16(hw_ver), + Access::RV, + Quality::FIXED, + ) +} + +fn attr_sw_ver_new(sw_ver: u32) -> Result { + Attribute::new( + Attributes::SwVer as u16, + AttrValue::Uint32(sw_ver), + Access::RV, + Quality::FIXED, + ) +} + +pub struct BasicInfoCluster { + base: Cluster, +} + +impl BasicInfoCluster { + pub fn new(cfg: BasicInfoConfig) -> Result, Error> { + let mut cluster = Box::new(BasicInfoCluster { + base: Cluster::new(ID)?, + }); + cluster.base.add_attribute(attr_vid_new(cfg.vid)?)?; + cluster.base.add_attribute(attr_pid_new(cfg.pid)?)?; + cluster.base.add_attribute(attr_hw_ver_new(cfg.hw_ver)?)?; + cluster.base.add_attribute(attr_sw_ver_new(cfg.sw_ver)?)?; + Ok(cluster) + } +} + +impl ClusterType for BasicInfoCluster { + fn base(&self) -> &Cluster { + &self.base + } + fn base_mut(&mut self) -> &mut Cluster { + &mut self.base + } +} diff --git a/matter/src/data_model/cluster_on_off.rs b/matter/src/data_model/cluster_on_off.rs new file mode 100644 index 0000000..56975d0 --- /dev/null +++ b/matter/src/data_model/cluster_on_off.rs @@ -0,0 +1,128 @@ +/* + * + * 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::objects::*; +use crate::{ + cmd_enter, + error::*, + interaction_model::{command::CommandReq, core::IMStatusCode}, +}; +use log::info; +use num_derive::FromPrimitive; + +pub const ID: u32 = 0x0006; + +pub enum Attributes { + OnOff = 0x0, +} + +#[derive(FromPrimitive)] +pub enum Commands { + Off = 0x0, + On = 0x01, + Toggle = 0x02, +} + +fn attr_on_off_new() -> Result { + // OnOff, Value: false + Attribute::new( + Attributes::OnOff as u16, + AttrValue::Bool(false), + Access::RV, + Quality::PERSISTENT, + ) +} + +pub struct OnOffCluster { + base: Cluster, +} + +impl OnOffCluster { + pub fn new() -> Result, Error> { + let mut cluster = Box::new(OnOffCluster { + base: Cluster::new(ID)?, + }); + cluster.base.add_attribute(attr_on_off_new()?)?; + Ok(cluster) + } +} + +impl ClusterType for OnOffCluster { + fn base(&self) -> &Cluster { + &self.base + } + fn base_mut(&mut self) -> &mut Cluster { + &mut self.base + } + + fn handle_command(&mut self, cmd_req: &mut CommandReq) -> Result<(), IMStatusCode> { + let cmd = cmd_req + .cmd + .path + .leaf + .map(num::FromPrimitive::from_u32) + .ok_or(IMStatusCode::UnsupportedCommand)? + .ok_or(IMStatusCode::UnsupportedCommand)?; + match cmd { + Commands::Off => { + cmd_enter!("Off"); + let value = self + .base + .read_attribute_raw(Attributes::OnOff as u16) + .unwrap(); + if AttrValue::Bool(true) == *value { + self.base + .write_attribute_raw(Attributes::OnOff as u16, AttrValue::Bool(false)) + .map_err(|_| IMStatusCode::Failure)?; + } + cmd_req.trans.complete(); + Err(IMStatusCode::Sucess) + } + Commands::On => { + cmd_enter!("On"); + let value = self + .base + .read_attribute_raw(Attributes::OnOff as u16) + .unwrap(); + if AttrValue::Bool(false) == *value { + self.base + .write_attribute_raw(Attributes::OnOff as u16, AttrValue::Bool(true)) + .map_err(|_| IMStatusCode::Failure)?; + } + + cmd_req.trans.complete(); + Err(IMStatusCode::Sucess) + } + Commands::Toggle => { + cmd_enter!("Toggle"); + let value = match self + .base + .read_attribute_raw(Attributes::OnOff as u16) + .unwrap() + { + &AttrValue::Bool(v) => v, + _ => false, + }; + self.base + .write_attribute_raw(Attributes::OnOff as u16, AttrValue::Bool(!value)) + .map_err(|_| IMStatusCode::Failure)?; + cmd_req.trans.complete(); + Err(IMStatusCode::Sucess) + } + } + } +} diff --git a/matter/src/data_model/cluster_template.rs b/matter/src/data_model/cluster_template.rs new file mode 100644 index 0000000..6555e9e --- /dev/null +++ b/matter/src/data_model/cluster_template.rs @@ -0,0 +1,44 @@ +/* + * + * 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::{ + data_model::objects::{Cluster, ClusterType}, + error::Error, +}; + +const CLUSTER_NETWORK_COMMISSIONING_ID: u32 = 0x0031; + +pub struct TemplateCluster { + base: Cluster, +} + +impl ClusterType for TemplateCluster { + fn base(&self) -> &Cluster { + &self.base + } + fn base_mut(&mut self) -> &mut Cluster { + &mut self.base + } +} + +impl TemplateCluster { + pub fn new() -> Result, Error> { + Ok(Box::new(Self { + base: Cluster::new(CLUSTER_NETWORK_COMMISSIONING_ID)?, + })) + } +} diff --git a/matter/src/data_model/core.rs b/matter/src/data_model/core.rs new file mode 100644 index 0000000..4b0db23 --- /dev/null +++ b/matter/src/data_model/core.rs @@ -0,0 +1,443 @@ +/* + * + * 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, + }, + 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, + ) -> 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)?; + } + 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); + } +} diff --git a/matter/src/data_model/device_types.rs b/matter/src/data_model/device_types.rs new file mode 100644 index 0000000..62b81f5 --- /dev/null +++ b/matter/src/data_model/device_types.rs @@ -0,0 +1,66 @@ +/* + * + * 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::BasicInfoCluster; +use super::cluster_basic_information::BasicInfoConfig; +use super::cluster_on_off::OnOffCluster; +use super::objects::*; +use super::sdm::dev_att::DevAttDataFetcher; +use super::sdm::general_commissioning::GenCommCluster; +use super::sdm::noc::NocCluster; +use super::sdm::nw_commissioning::NwCommCluster; +use super::system_model::access_control::AccessControlCluster; +use crate::acl::AclMgr; +use crate::error::*; +use crate::fabric::FabricMgr; +use std::sync::Arc; +use std::sync::RwLockWriteGuard; + +type WriteNode<'a> = RwLockWriteGuard<'a, Box>; + +pub fn device_type_add_root_node( + node: &mut WriteNode, + dev_info: BasicInfoConfig, + dev_att: Box, + fabric_mgr: Arc, + acl_mgr: Arc, +) -> Result { + // Add the root endpoint + let endpoint = node.add_endpoint()?; + if endpoint != 0 { + // Somehow endpoint 0 was already added, this shouldn't be the case + return Err(Error::Invalid); + }; + // Add the mandatory clusters + node.add_cluster(0, BasicInfoCluster::new(dev_info)?)?; + let general_commissioning = GenCommCluster::new()?; + let failsafe = general_commissioning.failsafe(); + node.add_cluster(0, general_commissioning)?; + node.add_cluster(0, NwCommCluster::new()?)?; + node.add_cluster( + 0, + NocCluster::new(dev_att, fabric_mgr, acl_mgr.clone(), failsafe)?, + )?; + node.add_cluster(0, AccessControlCluster::new(acl_mgr)?)?; + Ok(endpoint) +} + +pub fn device_type_add_on_off_light(node: &mut WriteNode) -> Result { + let endpoint = node.add_endpoint()?; + node.add_cluster(endpoint, OnOffCluster::new()?)?; + Ok(endpoint) +} diff --git a/matter/src/data_model/mod.rs b/matter/src/data_model/mod.rs new file mode 100644 index 0000000..dfc80c9 --- /dev/null +++ b/matter/src/data_model/mod.rs @@ -0,0 +1,26 @@ +/* + * + * 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. + */ + +pub mod core; +pub mod device_types; +pub mod objects; + +pub mod cluster_basic_information; +pub mod cluster_on_off; +pub mod cluster_template; +pub mod sdm; +pub mod system_model; diff --git a/matter/src/data_model/objects/attribute.rs b/matter/src/data_model/objects/attribute.rs new file mode 100644 index 0000000..4e8a6bd --- /dev/null +++ b/matter/src/data_model/objects/attribute.rs @@ -0,0 +1,274 @@ +/* + * + * 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::{GlobalElements, Privilege}; +use crate::{ + error::*, + // TODO: This layer shouldn't really depend on the TLV layer, should create an abstraction layer + tlv::{TLVElement, TLVWriter, TagType, ToTLV}, +}; +use bitflags::bitflags; +use log::error; +use std::fmt::{self, Debug, Formatter}; + +bitflags! { + #[derive(Default)] + pub struct Access: u16 { + // These must match the bits in the Privilege object :-| + const NEED_VIEW = 0x00001; + const NEED_OPERATE = 0x0002; + const NEED_MANAGE = 0x0004; + const NEED_ADMIN = 0x0008; + + const READ = 0x0010; + const WRITE = 0x0020; + const FAB_SCOPED = 0x0040; + const FAB_SENSITIVE = 0x0080; + const TIMED_ONLY = 0x0100; + + const READ_PRIVILEGE_MASK = Self::NEED_VIEW.bits | Self::NEED_MANAGE.bits | Self::NEED_OPERATE.bits | Self::NEED_ADMIN.bits; + const WRITE_PRIVILEGE_MASK = Self::NEED_MANAGE.bits | Self::NEED_OPERATE.bits | Self::NEED_ADMIN.bits; + const RV = Self::READ.bits | Self::NEED_VIEW.bits; + const RWVA = Self::READ.bits | Self::WRITE.bits | Self::NEED_VIEW.bits | Self::NEED_ADMIN.bits; + const RWFA = Self::READ.bits | Self::WRITE.bits | Self::FAB_SCOPED.bits | Self::NEED_ADMIN.bits; + const RWVM = Self::READ.bits | Self::WRITE.bits | Self::NEED_VIEW.bits | Self::NEED_MANAGE.bits; + } +} + +impl Access { + pub fn is_ok(&self, operation: Access, privilege: Privilege) -> bool { + let required = if operation.contains(Access::READ) { + *self & Access::READ_PRIVILEGE_MASK + } else if operation.contains(Access::WRITE) { + *self & Access::WRITE_PRIVILEGE_MASK + } else { + return false; + }; + + if required.is_empty() { + // There must be some required privilege for any object + return false; + } + + if ((privilege.bits() as u16) & required.bits()) == 0 { + return false; + } + + self.contains(operation) + } +} + +bitflags! { + #[derive(Default)] + pub struct Quality: u8 { + const NONE = 0x00; + const SCENE = 0x01; + const PERSISTENT = 0x02; + const FIXED = 0x03; + const NULLABLE = 0x04; + } +} + +/* This file needs some major revamp. + * - instead of allocating all over the heap, we should use some kind of slab/block allocator + * - instead of arrays, can use linked-lists to conserve space and avoid the internal fragmentation + */ + +#[derive(PartialEq, Copy, Clone)] +pub enum AttrValue { + Int64(i64), + Uint8(u8), + Uint16(u16), + Uint32(u32), + Uint64(u64), + Bool(bool), + Custom, +} + +impl Debug for AttrValue { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + match &self { + AttrValue::Int64(v) => write!(f, "{:?}", *v), + AttrValue::Uint8(v) => write!(f, "{:?}", *v), + AttrValue::Uint16(v) => write!(f, "{:?}", *v), + AttrValue::Uint32(v) => write!(f, "{:?}", *v), + AttrValue::Uint64(v) => write!(f, "{:?}", *v), + AttrValue::Bool(v) => write!(f, "{:?}", *v), + AttrValue::Custom => write!(f, "custom-attribute"), + }?; + Ok(()) + } +} + +impl ToTLV for AttrValue { + fn to_tlv(&self, tw: &mut TLVWriter, tag_type: TagType) -> Result<(), Error> { + // What is the time complexity of such long match statements? + match self { + AttrValue::Bool(v) => tw.bool(tag_type, *v), + AttrValue::Uint8(v) => tw.u8(tag_type, *v), + AttrValue::Uint16(v) => tw.u16(tag_type, *v), + AttrValue::Uint32(v) => tw.u32(tag_type, *v), + AttrValue::Uint64(v) => tw.u64(tag_type, *v), + _ => { + error!("Attribute type not yet supported"); + Err(Error::AttributeNotFound) + } + } + } +} + +impl AttrValue { + pub fn update_from_tlv(&mut self, tr: &TLVElement) -> Result<(), Error> { + match self { + AttrValue::Bool(v) => *v = tr.bool()?, + AttrValue::Uint8(v) => *v = tr.u8()?, + AttrValue::Uint16(v) => *v = tr.u16()?, + AttrValue::Uint32(v) => *v = tr.u32()?, + AttrValue::Uint64(v) => *v = tr.u64()?, + _ => { + error!("Attribute type not yet supported"); + return Err(Error::AttributeNotFound); + } + } + Ok(()) + } +} + +#[derive(Debug)] +pub struct Attribute { + pub(super) id: u16, + pub(super) value: AttrValue, + pub(super) quality: Quality, + pub(super) access: Access, +} + +impl Default for Attribute { + fn default() -> Attribute { + Attribute { + id: 0, + value: AttrValue::Bool(true), + quality: Default::default(), + access: Default::default(), + } + } +} + +impl Attribute { + pub fn new( + id: u16, + value: AttrValue, + access: Access, + quality: Quality, + ) -> Result { + Ok(Attribute { + id, + value, + access, + quality, + }) + } + + pub fn set_value(&mut self, value: AttrValue) -> Result<(), Error> { + if !self.quality.contains(Quality::FIXED) { + self.value = value; + Ok(()) + } else { + Err(Error::Invalid) + } + } + + pub fn is_system_attr(attr_id: u16) -> bool { + attr_id >= (GlobalElements::ServerGenCmd as u16) + } +} + +impl std::fmt::Display for Attribute { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}: {:?}", self.id, self.value) + } +} + +#[cfg(test)] +mod tests { + use super::Access; + use crate::data_model::objects::Privilege; + + #[test] + fn test_read() { + let c = Access::READ; + // Read without NEED_VIEW, implies No Read is possible + assert_eq!(c.is_ok(Access::READ, Privilege::VIEW), false); + + let c = Access::WRITE | Access::NEED_VIEW; + // Read without Read, implies No Read is possible + assert_eq!(c.is_ok(Access::READ, Privilege::VIEW), false); + + let c = Access::RV; + // Read with View or Admin privilege + assert_eq!(c.is_ok(Access::READ, Privilege::VIEW), true); + assert_eq!(c.is_ok(Access::READ, Privilege::ADMIN), true); + + let c = Access::READ | Access::NEED_ADMIN; + // Read without Admin privilege + assert_eq!(c.is_ok(Access::READ, Privilege::VIEW), false); + assert_eq!(c.is_ok(Access::READ, Privilege::OPERATE), false); + assert_eq!(c.is_ok(Access::READ, Privilege::MANAGE), false); + assert_eq!(c.is_ok(Access::READ, Privilege::ADMIN), true); + + let c = Access::READ | Access::NEED_OPERATE; + // Read without Operate privilege + assert_eq!(c.is_ok(Access::READ, Privilege::VIEW), false); + assert_eq!(c.is_ok(Access::READ, Privilege::OPERATE), true); + assert_eq!(c.is_ok(Access::READ, Privilege::MANAGE), true); + assert_eq!(c.is_ok(Access::READ, Privilege::ADMIN), true); + } + + #[test] + fn test_write() { + let c = Access::WRITE; + // Write NEED_*, implies No Write is possible + assert_eq!(c.is_ok(Access::WRITE, Privilege::VIEW), false); + + let c = Access::READ | Access::NEED_MANAGE; + // Write without Write, implies No Write is possible + assert_eq!(c.is_ok(Access::WRITE, Privilege::MANAGE), false); + + let c = Access::RWVA; + // Write with View and Admin privilege + assert_eq!(c.is_ok(Access::WRITE, Privilege::VIEW), false); + assert_eq!(c.is_ok(Access::WRITE, Privilege::ADMIN), true); + + let c = Access::RWVA; + // WRITE without Admin privilege + assert_eq!(c.is_ok(Access::WRITE, Privilege::VIEW), false); + assert_eq!(c.is_ok(Access::WRITE, Privilege::OPERATE), false); + assert_eq!(c.is_ok(Access::WRITE, Privilege::MANAGE), false); + assert_eq!(c.is_ok(Access::WRITE, Privilege::ADMIN), true); + // Read with View Privilege + assert_eq!(c.is_ok(Access::READ, Privilege::VIEW), true); + assert_eq!(c.is_ok(Access::READ, Privilege::OPERATE), true); + assert_eq!(c.is_ok(Access::READ, Privilege::MANAGE), true); + assert_eq!(c.is_ok(Access::READ, Privilege::ADMIN), true); + + let c = Access::WRITE | Access::NEED_OPERATE; + // WRITE without Operate privilege + assert_eq!(c.is_ok(Access::WRITE, Privilege::VIEW), false); + assert_eq!(c.is_ok(Access::WRITE, Privilege::OPERATE), true); + assert_eq!(c.is_ok(Access::WRITE, Privilege::MANAGE), true); + assert_eq!(c.is_ok(Access::WRITE, Privilege::ADMIN), true); + } +} diff --git a/matter/src/data_model/objects/cluster.rs b/matter/src/data_model/objects/cluster.rs new file mode 100644 index 0000000..d822b04 --- /dev/null +++ b/matter/src/data_model/objects/cluster.rs @@ -0,0 +1,329 @@ +/* + * + * 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>, + 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, + feature_map: Option, + data_ver: u32, +} + +impl Cluster { + pub fn new(id: u32) -> Result { + 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 { + 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, + ) -> 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 = 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, " ], ") + } +} diff --git a/matter/src/data_model/objects/encoder.rs b/matter/src/data_model/objects/encoder.rs new file mode 100644 index 0000000..8af9253 --- /dev/null +++ b/matter/src/data_model/objects/encoder.rs @@ -0,0 +1,117 @@ +/* + * + * 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::fmt::{Debug, Formatter}; + +use crate::{ + error::Error, + interaction_model::core::IMStatusCode, + tlv::{FromTLV, TLVElement, TLVWriter, TagType, ToTLV}, +}; +use log::error; + +// TODO: Should this return an IMStatusCode Error? But if yes, the higher layer +// may have already started encoding the 'success' headers, we might not to manage +// the tw.rewind() in that case, if we add this support +pub type EncodeValueGen<'a> = &'a dyn Fn(TagType, &mut TLVWriter); + +#[derive(Copy, Clone)] +/// A structure for encoding various types of values +pub enum EncodeValue<'a> { + /// This indicates a value that is dynamically generated. This variant + /// is typically used in the transmit/to-tlv path where we want to encode a value at + /// run time + Closure(EncodeValueGen<'a>), + /// This indicates a value that is in the TLVElement form. this variant is + /// typically used in the receive/from-tlv path where we don't want to decode the + /// full value but it can be done at the time of its usage + Tlv(TLVElement<'a>), + /// This indicates a static value. This variant is typically used in the transmit/ + /// to-tlv path + Value(&'a (dyn ToTLV)), +} + +impl<'a> EncodeValue<'a> { + pub fn unwrap_tlv(self) -> Option> { + match self { + EncodeValue::Tlv(t) => Some(t), + _ => None, + } + } +} + +impl<'a> PartialEq for EncodeValue<'a> { + fn eq(&self, other: &Self) -> bool { + match *self { + EncodeValue::Closure(_) => { + error!("PartialEq not yet supported"); + false + } + EncodeValue::Tlv(a) => { + if let EncodeValue::Tlv(b) = *other { + a == b + } else { + false + } + } + // Just claim false for now + EncodeValue::Value(_) => { + error!("PartialEq not yet supported"); + false + } + } + } +} + +impl<'a> Debug for EncodeValue<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + match *self { + EncodeValue::Closure(_) => write!(f, "Contains closure"), + EncodeValue::Tlv(t) => write!(f, "{:?}", t), + EncodeValue::Value(_) => write!(f, "Contains EncodeValue"), + }?; + Ok(()) + } +} + +impl<'a> ToTLV for EncodeValue<'a> { + fn to_tlv(&self, tw: &mut TLVWriter, tag_type: TagType) -> Result<(), Error> { + match self { + EncodeValue::Closure(f) => { + (f)(tag_type, tw); + Ok(()) + } + EncodeValue::Tlv(_) => (panic!("This looks invalid")), + EncodeValue::Value(v) => v.to_tlv(tw, tag_type), + } + } +} + +impl<'a> FromTLV<'a> for EncodeValue<'a> { + fn from_tlv(data: &TLVElement<'a>) -> Result { + Ok(EncodeValue::Tlv(*data)) + } +} + +/// An object that can encode EncodeValue into the necessary hierarchical structure +/// as expected by the Interaction Model +pub trait Encoder { + /// Encode a given value + fn encode(&mut self, value: EncodeValue); + /// Encode a status report + fn encode_status(&mut self, status: IMStatusCode, cluster_status: u16); +} diff --git a/matter/src/data_model/objects/endpoint.rs b/matter/src/data_model/objects/endpoint.rs new file mode 100644 index 0000000..a790fc6 --- /dev/null +++ b/matter/src/data_model/objects/endpoint.rs @@ -0,0 +1,105 @@ +/* + * + * 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::{data_model::objects::ClusterType, error::*, interaction_model::core::IMStatusCode}; + +use std::fmt; + +pub const CLUSTERS_PER_ENDPT: usize = 7; + +pub struct Endpoint { + clusters: Vec>, +} + +impl Endpoint { + pub fn new() -> Result, Error> { + Ok(Box::new(Endpoint { + clusters: Vec::with_capacity(CLUSTERS_PER_ENDPT), + })) + } + + pub fn add_cluster(&mut self, cluster: Box) -> Result<(), Error> { + if self.clusters.len() < self.clusters.capacity() { + self.clusters.push(cluster); + Ok(()) + } else { + Err(Error::NoSpace) + } + } + + fn get_cluster_index(&self, cluster_id: u32) -> Option { + self.clusters.iter().position(|c| c.base().id == cluster_id) + } + + pub fn get_cluster(&self, cluster_id: u32) -> Result<&dyn ClusterType, Error> { + let index = self + .get_cluster_index(cluster_id) + .ok_or(Error::ClusterNotFound)?; + Ok(self.clusters[index].as_ref()) + } + + pub fn get_cluster_mut(&mut self, cluster_id: u32) -> Result<&mut dyn ClusterType, Error> { + let index = self + .get_cluster_index(cluster_id) + .ok_or(Error::ClusterNotFound)?; + Ok(self.clusters[index].as_mut()) + } + + // Returns a slice of clusters, with either a single cluster or all (wildcard) + pub fn get_wildcard_clusters( + &self, + cluster: Option, + ) -> Result<(&[Box], bool), IMStatusCode> { + if let Some(c) = cluster { + if let Some(i) = self.get_cluster_index(c) { + Ok((&self.clusters[i..i + 1], false)) + } else { + Err(IMStatusCode::UnsupportedCluster) + } + } else { + Ok((self.clusters.as_slice(), true)) + } + } + + // Returns a slice of clusters, with either a single cluster or all (wildcard) + pub fn get_wildcard_clusters_mut( + &mut self, + cluster: Option, + ) -> Result<(&mut [Box], bool), IMStatusCode> { + if let Some(c) = cluster { + if let Some(i) = self.get_cluster_index(c) { + Ok((&mut self.clusters[i..i + 1], false)) + } else { + Err(IMStatusCode::UnsupportedCluster) + } + } else { + Ok((&mut self.clusters[..], true)) + } + } +} + +impl std::fmt::Display for Endpoint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "clusters:[")?; + let mut comma = ""; + for element in self.clusters.iter() { + write!(f, "{} {{ {} }}", comma, element.base())?; + comma = ", "; + } + write!(f, "]") + } +} diff --git a/matter/src/data_model/objects/mod.rs b/matter/src/data_model/objects/mod.rs new file mode 100644 index 0000000..2791cb4 --- /dev/null +++ b/matter/src/data_model/objects/mod.rs @@ -0,0 +1,34 @@ +/* + * + * 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. + */ + +mod attribute; +pub use attribute::*; + +mod cluster; +pub use cluster::*; + +mod endpoint; +pub use endpoint::*; + +mod node; +pub use node::*; + +mod privilege; +pub use privilege::*; + +mod encoder; +pub use encoder::*; diff --git a/matter/src/data_model/objects/node.rs b/matter/src/data_model/objects/node.rs new file mode 100644 index 0000000..e3b1598 --- /dev/null +++ b/matter/src/data_model/objects/node.rs @@ -0,0 +1,290 @@ +/* + * + * 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::{ + data_model::objects::{ClusterType, Endpoint}, + error::*, + interaction_model::{core::IMStatusCode, messages::GenericPath}, + // TODO: This layer shouldn't really depend on the TLV layer, should create an abstraction layer +}; +use std::fmt; + +pub trait ChangeConsumer { + fn endpoint_added(&self, id: u16, endpoint: &mut Endpoint) -> Result<(), Error>; +} + +pub const ENDPTS_PER_ACC: usize = 3; + +#[derive(Default)] +pub struct Node { + endpoints: [Option>; ENDPTS_PER_ACC], + changes_cb: Option>, +} + +impl std::fmt::Display for Node { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "node:")?; + for (i, element) in self.endpoints.iter().enumerate() { + if let Some(e) = element { + writeln!(f, "endpoint {}: {}", i, e)?; + } + } + write!(f, "") + } +} + +impl Node { + pub fn new() -> Result, Error> { + let node = Box::new(Node::default()); + Ok(node) + } + + pub fn set_changes_cb(&mut self, consumer: Box) { + self.changes_cb = Some(consumer); + } + + pub fn add_endpoint(&mut self) -> Result { + let index = self + .endpoints + .iter() + .position(|x| x.is_none()) + .ok_or(Error::NoSpace)?; + let mut endpoint = Endpoint::new()?; + if let Some(cb) = &self.changes_cb { + cb.endpoint_added(index as u16, &mut endpoint)?; + } + self.endpoints[index] = Some(endpoint); + Ok(index as u32) + } + + pub fn get_endpoint(&self, endpoint_id: u16) -> Result<&Endpoint, Error> { + if (endpoint_id as usize) < ENDPTS_PER_ACC { + let endpoint = self.endpoints[endpoint_id as usize] + .as_ref() + .ok_or(Error::EndpointNotFound)?; + Ok(endpoint) + } else { + Err(Error::EndpointNotFound) + } + } + + pub fn get_endpoint_mut(&mut self, endpoint_id: u16) -> Result<&mut Endpoint, Error> { + if (endpoint_id as usize) < ENDPTS_PER_ACC { + let endpoint = self.endpoints[endpoint_id as usize] + .as_mut() + .ok_or(Error::EndpointNotFound)?; + Ok(endpoint) + } else { + Err(Error::EndpointNotFound) + } + } + + pub fn get_cluster_mut(&mut self, e: u16, c: u32) -> Result<&mut dyn ClusterType, Error> { + self.get_endpoint_mut(e)?.get_cluster_mut(c) + } + + pub fn get_cluster(&self, e: u16, c: u32) -> Result<&dyn ClusterType, Error> { + self.get_endpoint(e)?.get_cluster(c) + } + + pub fn add_cluster( + &mut self, + endpoint_id: u32, + cluster: Box, + ) -> Result<(), Error> { + let endpoint_id = endpoint_id as usize; + if endpoint_id < ENDPTS_PER_ACC { + self.endpoints[endpoint_id] + .as_mut() + .ok_or(Error::NoEndpoint)? + .add_cluster(cluster) + } else { + Err(Error::Invalid) + } + } + + // Returns a slice of endpoints, with either a single endpoint or all (wildcard) + pub fn get_wildcard_endpoints( + &self, + endpoint: Option, + ) -> Result<(&[Option>], usize, bool), IMStatusCode> { + if let Some(e) = endpoint { + let e = e as usize; + if self.endpoints.len() <= e || self.endpoints[e].is_none() { + Err(IMStatusCode::UnsupportedEndpoint) + } else { + Ok((&self.endpoints[e..e + 1], e, false)) + } + } else { + Ok((&self.endpoints[..], 0, true)) + } + } + + pub fn get_wildcard_endpoints_mut( + &mut self, + endpoint: Option, + ) -> Result<(&mut [Option>], usize, bool), IMStatusCode> { + if let Some(e) = endpoint { + let e = e as usize; + if self.endpoints.len() <= e || self.endpoints[e].is_none() { + Err(IMStatusCode::UnsupportedEndpoint) + } else { + Ok((&mut self.endpoints[e..e + 1], e, false)) + } + } else { + Ok((&mut self.endpoints[..], 0, true)) + } + } + + /// Run a closure for all endpoints as specified in the path + /// + /// Note that the path is a GenericPath and hence can be a wildcard path. The behaviour + /// of this function is to only capture the successful invocations and ignore the erroneous + /// ones. This is inline with the expected behaviour for wildcard, where it implies that + /// 'please run this operation on this wildcard path "wherever possible"' + /// + /// It is expected that if the closure that you pass here returns an error it may not reach + /// out to the caller, in case there was a wildcard path specified + pub fn for_each_endpoint(&self, path: &GenericPath, mut f: T) -> Result<(), IMStatusCode> + where + T: FnMut(&GenericPath, &Endpoint) -> Result<(), IMStatusCode>, + { + let mut current_path = *path; + let (endpoints, mut endpoint_id, wildcard) = self.get_wildcard_endpoints(path.endpoint)?; + for e in endpoints.iter() { + if let Some(e) = e { + current_path.endpoint = Some(endpoint_id as u16); + f(¤t_path, e.as_ref()) + .or_else(|e| if !wildcard { Err(e) } else { Ok(()) })?; + } + endpoint_id += 1; + } + Ok(()) + } + + /// Run a closure for all endpoints (mutable) as specified in the path + /// + /// Note that the path is a GenericPath and hence can be a wildcard path. The behaviour + /// of this function is to only capture the successful invocations and ignore the erroneous + /// ones. This is inline with the expected behaviour for wildcard, where it implies that + /// 'please run this operation on this wildcard path "wherever possible"' + /// + /// It is expected that if the closure that you pass here returns an error it may not reach + /// out to the caller, in case there was a wildcard path specified + pub fn for_each_endpoint_mut( + &mut self, + path: &GenericPath, + mut f: T, + ) -> Result<(), IMStatusCode> + where + T: FnMut(&GenericPath, &mut Endpoint) -> Result<(), IMStatusCode>, + { + let mut current_path = *path; + let (endpoints, mut endpoint_id, wildcard) = + self.get_wildcard_endpoints_mut(path.endpoint)?; + for e in endpoints.iter_mut() { + if let Some(e) = e { + current_path.endpoint = Some(endpoint_id as u16); + f(¤t_path, e.as_mut()) + .or_else(|e| if !wildcard { Err(e) } else { Ok(()) })?; + } + endpoint_id += 1; + } + Ok(()) + } + + /// Run a closure for all clusters as specified in the path + /// + /// Note that the path is a GenericPath and hence can be a wildcard path. The behaviour + /// of this function is to only capture the successful invocations and ignore the erroneous + /// ones. This is inline with the expected behaviour for wildcard, where it implies that + /// 'please run this operation on this wildcard path "wherever possible"' + /// + /// It is expected that if the closure that you pass here returns an error it may not reach + /// out to the caller, in case there was a wildcard path specified + pub fn for_each_cluster(&self, path: &GenericPath, mut f: T) -> Result<(), IMStatusCode> + where + T: FnMut(&GenericPath, &dyn ClusterType) -> Result<(), IMStatusCode>, + { + self.for_each_endpoint(path, |p, e| { + let mut current_path = *p; + let (clusters, wildcard) = e.get_wildcard_clusters(p.cluster)?; + for c in clusters.iter() { + current_path.cluster = Some(c.base().id); + f(¤t_path, c.as_ref()) + .or_else(|e| if !wildcard { Err(e) } else { Ok(()) })?; + } + Ok(()) + }) + } + + /// Run a closure for all clusters (mutable) as specified in the path + /// + /// Note that the path is a GenericPath and hence can be a wildcard path. The behaviour + /// of this function is to only capture the successful invocations and ignore the erroneous + /// ones. This is inline with the expected behaviour for wildcard, where it implies that + /// 'please run this operation on this wildcard path "wherever possible"' + /// + /// It is expected that if the closure that you pass here returns an error it may not reach + /// out to the caller, in case there was a wildcard path specified + pub fn for_each_cluster_mut( + &mut self, + path: &GenericPath, + mut f: T, + ) -> Result<(), IMStatusCode> + where + T: FnMut(&GenericPath, &mut dyn ClusterType) -> Result<(), IMStatusCode>, + { + self.for_each_endpoint_mut(path, |p, e| { + let mut current_path = *p; + let (clusters, wildcard) = e.get_wildcard_clusters_mut(p.cluster)?; + + for c in clusters.iter_mut() { + current_path.cluster = Some(c.base().id); + f(¤t_path, c.as_mut()) + .or_else(|e| if !wildcard { Err(e) } else { Ok(()) })?; + } + Ok(()) + }) + } + + /// Run a closure for all attributes as specified in the path + /// + /// Note that the path is a GenericPath and hence can be a wildcard path. The behaviour + /// of this function is to only capture the successful invocations and ignore the erroneous + /// ones. This is inline with the expected behaviour for wildcard, where it implies that + /// 'please run this operation on this wildcard path "wherever possible"' + /// + /// It is expected that if the closure that you pass here returns an error it may not reach + /// out to the caller, in case there was a wildcard path specified + pub fn for_each_attribute(&self, path: &GenericPath, mut f: T) -> Result<(), IMStatusCode> + where + T: FnMut(&GenericPath, &dyn ClusterType) -> Result<(), IMStatusCode>, + { + self.for_each_cluster(path, |current_path, c| { + let mut current_path = *current_path; + let (attributes, wildcard) = c + .base() + .get_wildcard_attribute(path.leaf.map(|at| at as u16))?; + for a in attributes.iter() { + current_path.leaf = Some(a.id as u32); + f(¤t_path, c).or_else(|e| if !wildcard { Err(e) } else { Ok(()) })?; + } + Ok(()) + }) + } +} diff --git a/matter/src/data_model/objects/privilege.rs b/matter/src/data_model/objects/privilege.rs new file mode 100644 index 0000000..10069d5 --- /dev/null +++ b/matter/src/data_model/objects/privilege.rs @@ -0,0 +1,79 @@ +/* + * + * 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::{ + error::Error, + tlv::{FromTLV, TLVElement, ToTLV}, +}; +use log::error; + +use bitflags::bitflags; + +bitflags! { + #[derive(Default)] + pub struct Privilege: u8 { + const V = 0x01; + const O = 0x02; + const M = 0x04; + const A = 0x08; + + const VIEW = Self::V.bits; + const OPERATE = Self::V.bits | Self::O.bits; + const MANAGE = Self::V.bits | Self::O.bits | Self::M.bits; + const ADMIN = Self::V.bits | Self::O.bits| Self::M.bits| Self::A.bits; + } +} + +impl FromTLV<'_> for Privilege { + fn from_tlv(t: &TLVElement) -> Result + where + Self: Sized, + { + match t.u32()? { + 1 => Ok(Privilege::VIEW), + 2 => { + error!("ProxyView privilege not yet supporteds"); + Err(Error::Invalid) + } + 3 => Ok(Privilege::OPERATE), + 4 => Ok(Privilege::MANAGE), + 5 => Ok(Privilege::ADMIN), + _ => Err(Error::Invalid), + } + } +} + +impl ToTLV for Privilege { + fn to_tlv( + &self, + tw: &mut crate::tlv::TLVWriter, + tag: crate::tlv::TagType, + ) -> Result<(), Error> { + let val = if self.contains(Privilege::ADMIN) { + 5 + } else if self.contains(Privilege::OPERATE) { + 4 + } else if self.contains(Privilege::MANAGE) { + 3 + } else if self.contains(Privilege::VIEW) { + 1 + } else { + 0 + }; + tw.u8(tag, val) + } +} diff --git a/matter/src/data_model/sdm/dev_att.rs b/matter/src/data_model/sdm/dev_att.rs new file mode 100644 index 0000000..f3dbc2a --- /dev/null +++ b/matter/src/data_model/sdm/dev_att.rs @@ -0,0 +1,45 @@ +/* + * + * 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::error::Error; + +/// Device Attestation Data Type +pub enum DataType { + /// Certificate Declaration + CertDeclaration, + /// Product Attestation Intermediary Certificate + PAI, + /// Device Attestation Certificate + DAC, + /// Device Attestation Certificate - Public Key + DACPubKey, + /// Device Attestation Certificate - Private Key + DACPrivKey, +} + +/// The Device Attestation Data Fetcher Trait +/// +/// Objects that implement this trait allow the Matter subsystem to query the object +/// for the Device Attestation data that is programmed in the Matter device. +pub trait DevAttDataFetcher { + /// Get Device Attestation Data + /// + /// This API is expected to return the particular Device Attestation data as is + /// requested by the Matter subsystem. + /// The type of data that can be queried is defined in the [DataType] enum. + fn get_devatt_data(&self, data_type: DataType, data: &mut [u8]) -> Result; +} diff --git a/matter/src/data_model/sdm/failsafe.rs b/matter/src/data_model/sdm/failsafe.rs new file mode 100644 index 0000000..318b890 --- /dev/null +++ b/matter/src/data_model/sdm/failsafe.rs @@ -0,0 +1,138 @@ +/* + * + * 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::{error::Error, transport::session::SessionMode}; +use log::error; +use std::sync::RwLock; + +#[derive(PartialEq)] +#[allow(dead_code)] +enum NocState { + NocNotRecvd, + // This is the local fabric index + AddNocRecvd(u8), + UpdateNocRecvd(u8), +} + +#[derive(PartialEq)] +pub struct ArmedCtx { + session_mode: SessionMode, + timeout: u8, + noc_state: NocState, +} + +#[derive(PartialEq)] +pub enum State { + Idle, + Armed(ArmedCtx), +} + +pub struct FailSafeInner { + state: State, +} + +pub struct FailSafe { + state: RwLock, +} + +impl FailSafe { + pub fn new() -> Self { + Self { + state: RwLock::new(FailSafeInner { state: State::Idle }), + } + } + + pub fn arm(&self, timeout: u8, session_mode: SessionMode) -> Result<(), Error> { + let mut inner = self.state.write()?; + match &mut inner.state { + State::Idle => { + inner.state = State::Armed(ArmedCtx { + session_mode, + timeout, + noc_state: NocState::NocNotRecvd, + }) + } + State::Armed(c) => { + if c.session_mode != session_mode { + return Err(Error::Invalid); + } + // re-arm + c.timeout = timeout; + } + } + Ok(()) + } + + pub fn disarm(&self, session_mode: SessionMode) -> Result<(), Error> { + let mut inner = self.state.write()?; + match &mut inner.state { + State::Idle => { + error!("Received Fail-Safe Disarm without it being armed"); + return Err(Error::Invalid); + } + State::Armed(c) => { + match c.noc_state { + NocState::NocNotRecvd => return Err(Error::Invalid), + NocState::AddNocRecvd(idx) | NocState::UpdateNocRecvd(idx) => { + if SessionMode::Case(idx) != session_mode { + error!( + "Received disarm in separate session from previous Add/Update NOC" + ); + return Err(Error::Invalid); + } + } + } + inner.state = State::Idle; + } + } + Ok(()) + } + + pub fn is_armed(&self) -> bool { + self.state.read().unwrap().state != State::Idle + } + + pub fn record_add_noc(&self, fabric_index: u8) -> Result<(), Error> { + let mut inner = self.state.write()?; + match &mut inner.state { + State::Idle => Err(Error::Invalid), + State::Armed(c) => { + if c.noc_state == NocState::NocNotRecvd { + c.noc_state = NocState::AddNocRecvd(fabric_index); + Ok(()) + } else { + Err(Error::Invalid) + } + } + } + } + + pub fn allow_noc_change(&self) -> Result { + let mut inner = self.state.write()?; + let allow = match &mut inner.state { + State::Idle => false, + State::Armed(c) => c.noc_state == NocState::NocNotRecvd, + }; + Ok(allow) + } +} + +impl Default for FailSafe { + fn default() -> Self { + Self::new() + } +} diff --git a/matter/src/data_model/sdm/general_commissioning.rs b/matter/src/data_model/sdm/general_commissioning.rs new file mode 100644 index 0000000..220217b --- /dev/null +++ b/matter/src/data_model/sdm/general_commissioning.rs @@ -0,0 +1,277 @@ +/* + * + * 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::cmd_enter; +use crate::data_model::objects::*; +use crate::data_model::sdm::failsafe::FailSafe; +use crate::interaction_model::core::IMStatusCode; +use crate::interaction_model::messages::ib; +use crate::tlv::{FromTLV, TLVElement, TLVWriter, TagType, ToTLV}; +use crate::{error::*, interaction_model::command::CommandReq}; +use log::{error, info}; +use num_derive::FromPrimitive; +use std::sync::Arc; + +#[derive(Clone, Copy)] +#[allow(dead_code)] +enum CommissioningError { + Ok = 0, + ErrValueOutsideRange = 1, + ErrInvalidAuth = 2, + ErrNotCommissioning = 3, +} + +pub const ID: u32 = 0x0030; + +#[derive(FromPrimitive)] +pub enum Attributes { + BreadCrumb = 0, + BasicCommissioningInfo = 1, + RegConfig = 2, + LocationCapability = 3, +} + +#[derive(FromPrimitive)] +pub enum Commands { + ArmFailsafe = 0x00, + ArmFailsafeResp = 0x01, + SetRegulatoryConfig = 0x02, + SetRegulatoryConfigResp = 0x03, + CommissioningComplete = 0x04, + CommissioningCompleteResp = 0x05, +} + +pub enum RegLocationType { + Indoor = 0, + Outdoor = 1, + IndoorOutdoor = 2, +} + +fn attr_bread_crumb_new(bread_crumb: u64) -> Result { + Attribute::new( + Attributes::BreadCrumb as u16, + AttrValue::Uint64(bread_crumb), + Access::READ | Access::WRITE | Access::NEED_ADMIN, + Quality::NONE, + ) +} + +fn attr_reg_config_new(reg_config: RegLocationType) -> Result { + Attribute::new( + Attributes::RegConfig as u16, + AttrValue::Uint8(reg_config as u8), + Access::RV, + Quality::NONE, + ) +} + +fn attr_location_capability_new(reg_config: RegLocationType) -> Result { + Attribute::new( + Attributes::LocationCapability as u16, + AttrValue::Uint8(reg_config as u8), + Access::RV, + Quality::FIXED, + ) +} + +fn attr_comm_info_new() -> Result { + Attribute::new( + Attributes::BasicCommissioningInfo as u16, + AttrValue::Custom, + Access::RV, + Quality::FIXED, + ) +} + +#[derive(FromTLV, ToTLV)] +struct FailSafeParams { + expiry_len: u8, + bread_crumb: u8, +} + +pub struct GenCommCluster { + expiry_len: u16, + failsafe: Arc, + base: Cluster, +} + +impl ClusterType for GenCommCluster { + fn base(&self) -> &Cluster { + &self.base + } + fn base_mut(&mut self) -> &mut Cluster { + &mut self.base + } + + fn read_custom_attribute(&self, encoder: &mut dyn Encoder, attr: &AttrDetails) { + match num::FromPrimitive::from_u16(attr.attr_id) { + Some(Attributes::BasicCommissioningInfo) => { + encoder.encode(EncodeValue::Closure(&|tag, tw| { + let _ = tw.start_struct(tag); + let _ = tw.u16(TagType::Context(0), self.expiry_len); + let _ = tw.end_container(); + })) + } + _ => { + error!("Unsupported Attribute: this shouldn't happen"); + } + } + } + + fn handle_command(&mut self, cmd_req: &mut CommandReq) -> Result<(), IMStatusCode> { + let cmd = cmd_req + .cmd + .path + .leaf + .map(num::FromPrimitive::from_u32) + .ok_or(IMStatusCode::UnsupportedCommand)? + .ok_or(IMStatusCode::UnsupportedCommand)?; + match cmd { + Commands::ArmFailsafe => self.handle_command_armfailsafe(cmd_req), + Commands::SetRegulatoryConfig => self.handle_command_setregulatoryconfig(cmd_req), + Commands::CommissioningComplete => self.handle_command_commissioningcomplete(cmd_req), + _ => Err(IMStatusCode::UnsupportedCommand), + } + } +} + +impl GenCommCluster { + pub fn new() -> Result, Error> { + let failsafe = Arc::new(FailSafe::new()); + + let mut c = Box::new(GenCommCluster { + // TODO: Arch-Specific + expiry_len: 120, + failsafe, + base: Cluster::new(ID)?, + }); + c.base.add_attribute(attr_bread_crumb_new(0)?)?; + // TODO: Arch-Specific + c.base + .add_attribute(attr_reg_config_new(RegLocationType::IndoorOutdoor)?)?; + // TODO: Arch-Specific + c.base.add_attribute(attr_location_capability_new( + RegLocationType::IndoorOutdoor, + )?)?; + c.base.add_attribute(attr_comm_info_new()?)?; + + Ok(c) + } + + pub fn failsafe(&self) -> Arc { + self.failsafe.clone() + } + + fn handle_command_armfailsafe(&mut self, cmd_req: &mut CommandReq) -> Result<(), IMStatusCode> { + cmd_enter!("ARM Fail Safe"); + + let p = FailSafeParams::from_tlv(&cmd_req.data)?; + + if self + .failsafe + .arm(p.expiry_len, cmd_req.trans.session.get_session_mode()) + .is_err() + { + return Err(IMStatusCode::Busy); + } + + let cmd_data = CommonResponse { + error_code: CommissioningError::Ok as u8, + debug_txt: "".to_owned(), + }; + let resp = ib::InvResp::cmd_new( + 0, + ID, + Commands::ArmFailsafeResp as u16, + EncodeValue::Value(&cmd_data), + ); + let _ = resp.to_tlv(cmd_req.resp, TagType::Anonymous); + cmd_req.trans.complete(); + Ok(()) + } + + fn handle_command_setregulatoryconfig( + &mut self, + cmd_req: &mut CommandReq, + ) -> Result<(), IMStatusCode> { + cmd_enter!("Set Regulatory Config"); + let country_code = cmd_req + .data + .find_tag(1) + .map_err(|_| IMStatusCode::InvalidCommand)? + .slice() + .map_err(|_| IMStatusCode::InvalidCommand)?; + info!("Received country code: {:?}", country_code); + + let cmd_data = CommonResponse { + error_code: 0, + debug_txt: "".to_owned(), + }; + let resp = ib::InvResp::cmd_new( + 0, + ID, + Commands::SetRegulatoryConfigResp as u16, + EncodeValue::Value(&cmd_data), + ); + let _ = resp.to_tlv(cmd_req.resp, TagType::Anonymous); + cmd_req.trans.complete(); + Ok(()) + } + + fn handle_command_commissioningcomplete( + &mut self, + cmd_req: &mut CommandReq, + ) -> Result<(), IMStatusCode> { + cmd_enter!("Commissioning Complete"); + let mut status: u8 = CommissioningError::Ok as u8; + + // Has to be a Case Session + if cmd_req.trans.session.get_local_fabric_idx().is_none() { + status = CommissioningError::ErrInvalidAuth as u8; + } + + // AddNOC or UpdateNOC must have happened, and that too for the same fabric + // scope that is for this session + if self + .failsafe + .disarm(cmd_req.trans.session.get_session_mode()) + .is_err() + { + status = CommissioningError::ErrInvalidAuth as u8; + } + + let cmd_data = CommonResponse { + error_code: status, + debug_txt: "".to_owned(), + }; + let resp = ib::InvResp::cmd_new( + 0, + ID, + Commands::CommissioningCompleteResp as u16, + EncodeValue::Value(&cmd_data), + ); + let _ = resp.to_tlv(cmd_req.resp, TagType::Anonymous); + cmd_req.trans.complete(); + Ok(()) + } +} + +#[derive(FromTLV, ToTLV)] +struct CommonResponse { + error_code: u8, + debug_txt: String, +} diff --git a/matter/src/data_model/sdm/mod.rs b/matter/src/data_model/sdm/mod.rs new file mode 100644 index 0000000..cec166d --- /dev/null +++ b/matter/src/data_model/sdm/mod.rs @@ -0,0 +1,22 @@ +/* + * + * 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. + */ + +pub mod dev_att; +pub mod failsafe; +pub mod general_commissioning; +pub mod noc; +pub mod nw_commissioning; diff --git a/matter/src/data_model/sdm/noc.rs b/matter/src/data_model/sdm/noc.rs new file mode 100644 index 0000000..3812ed6 --- /dev/null +++ b/matter/src/data_model/sdm/noc.rs @@ -0,0 +1,475 @@ +/* + * + * 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::sync::Arc; +use std::time::{SystemTime, UNIX_EPOCH}; + +use crate::acl::{AclEntry, AclMgr, AuthMode}; +use crate::cert::Cert; +use crate::crypto::{self, CryptoKeyPair, KeyPair}; +use crate::data_model::objects::*; +use crate::data_model::sdm::dev_att; +use crate::fabric::{Fabric, FabricMgr}; +use crate::interaction_model::command::CommandReq; +use crate::interaction_model::core::IMStatusCode; +use crate::interaction_model::messages::ib; +use crate::tlv::{FromTLV, OctetStr, TLVElement, TLVWriter, TagType, ToTLV}; +use crate::transport::session::SessionMode; +use crate::utils::writebuf::WriteBuf; +use crate::{cmd_enter, error::*}; +use log::{error, info}; +use num_derive::FromPrimitive; + +use super::dev_att::{DataType, DevAttDataFetcher}; +use super::failsafe::FailSafe; + +// Node Operational Credentials Cluster + +#[derive(Clone, Copy)] +#[allow(dead_code)] +enum NocStatus { + Ok = 0, + InvalidPublicKey = 1, + InvalidNodeOpId = 2, + InvalidNOC = 3, + MissingCsr = 4, + TableFull = 5, + MissingAcl = 6, + MissingIpk = 7, + InsufficientPrivlege = 8, + FabricConflict = 9, + LabelConflict = 10, + InvalidFabricIndex = 11, +} + +// Some placeholder value for now +const MAX_CERT_DECLARATION_LEN: usize = 600; +// Some placeholder value for now +const MAX_CSR_LEN: usize = 300; +// As defined in the Matter Spec +const RESP_MAX: usize = 900; + +pub const ID: u32 = 0x003E; + +#[derive(FromPrimitive)] +pub enum Commands { + AttReq = 0x00, + AttReqResp = 0x01, + CertChainReq = 0x02, + CertChainResp = 0x03, + CSRReq = 0x04, + CSRResp = 0x05, + AddNOC = 0x06, + NOCResp = 0x08, + AddTrustedRootCert = 0x0b, +} + +pub struct NocCluster { + base: Cluster, + dev_att: Box, + fabric_mgr: Arc, + acl_mgr: Arc, + failsafe: Arc, +} +struct NocData { + pub key_pair: KeyPair, + pub root_ca: Cert, +} + +impl NocData { + pub fn new(key_pair: KeyPair) -> Self { + Self { + key_pair, + root_ca: Cert::default(), + } + } +} + +impl NocCluster { + pub fn new( + dev_att: Box, + fabric_mgr: Arc, + acl_mgr: Arc, + failsafe: Arc, + ) -> Result, Error> { + Ok(Box::new(Self { + dev_att, + fabric_mgr, + acl_mgr, + failsafe, + base: Cluster::new(ID)?, + })) + } + + fn add_acl(&self, fab_idx: u8, admin_subject: u64) -> Result<(), Error> { + let mut acl = AclEntry::new(fab_idx as u8, Privilege::ADMIN, AuthMode::Case); + acl.add_subject(admin_subject)?; + self.acl_mgr.add(acl) + } + + fn _handle_command_addnoc(&mut self, cmd_req: &mut CommandReq) -> Result<(), NocStatus> { + let noc_data = cmd_req + .trans + .session + .take_data::() + .ok_or(NocStatus::MissingCsr)?; + + if !self + .failsafe + .allow_noc_change() + .map_err(|_| NocStatus::InsufficientPrivlege)? + { + error!("AddNOC not allowed by Fail Safe"); + return Err(NocStatus::InsufficientPrivlege); + } + + let r = AddNocReq::from_tlv(&cmd_req.data).map_err(|_| NocStatus::InvalidNOC)?; + + let noc_value = Cert::new(r.noc_value.0).map_err(|_| NocStatus::InvalidNOC)?; + info!("Received NOC as: {}", noc_value); + let icac_value = Cert::new(r.icac_value.0).map_err(|_| NocStatus::InvalidNOC)?; + info!("Received ICAC as: {}", icac_value); + + let fabric = Fabric::new( + noc_data.key_pair, + noc_data.root_ca, + icac_value, + noc_value, + r.ipk_value.0, + ) + .map_err(|_| NocStatus::TableFull)?; + let fab_idx = self + .fabric_mgr + .add(fabric) + .map_err(|_| NocStatus::TableFull)?; + + if self.add_acl(fab_idx as u8, r.case_admin_subject).is_err() { + error!("Failed to add ACL, what to do?"); + } + + if self.failsafe.record_add_noc(fab_idx).is_err() { + error!("Failed to record NoC in the FailSafe, what to do?"); + } + + let cmd_data = NocResp { + status_code: NocStatus::Ok as u8, + fab_idx, + debug_txt: "".to_owned(), + }; + let resp = ib::InvResp::cmd_new( + 0, + ID, + Commands::NOCResp as u16, + EncodeValue::Value(&cmd_data), + ); + let _ = resp.to_tlv(cmd_req.resp, TagType::Anonymous); + cmd_req.trans.complete(); + Ok(()) + } + + fn handle_command_addnoc(&mut self, cmd_req: &mut CommandReq) -> Result<(), IMStatusCode> { + cmd_enter!("AddNOC"); + if let Err(e) = self._handle_command_addnoc(cmd_req) { + let cmd_data = NocResp { + status_code: e as u8, + fab_idx: 0, + debug_txt: "".to_owned(), + }; + let invoke_resp = ib::InvResp::cmd_new( + 0, + ID, + Commands::NOCResp as u16, + EncodeValue::Value(&cmd_data), + ); + let _ = invoke_resp.to_tlv(cmd_req.resp, TagType::Anonymous); + cmd_req.trans.complete(); + } + Ok(()) + } + + fn handle_command_attrequest(&mut self, cmd_req: &mut CommandReq) -> Result<(), IMStatusCode> { + cmd_enter!("AttestationRequest"); + + let req = CommonReq::from_tlv(&cmd_req.data).map_err(|_| IMStatusCode::InvalidCommand)?; + info!("Received Attestation Nonce:{:?}", req.str); + + let mut attest_challenge = [0u8; crypto::SYMM_KEY_LEN_BYTES]; + attest_challenge.copy_from_slice(cmd_req.trans.session.get_att_challenge()); + + let cmd_data = |tag: TagType, t: &mut TLVWriter| { + let mut buf: [u8; RESP_MAX] = [0; RESP_MAX]; + let mut attest_element = WriteBuf::new(&mut buf, RESP_MAX); + let _ = t.start_struct(tag); + let _ = + add_attestation_element(self.dev_att.as_ref(), req.str.0, &mut attest_element, t); + let _ = add_attestation_signature( + self.dev_att.as_ref(), + &mut attest_element, + &attest_challenge, + t, + ); + let _ = t.end_container(); + }; + let resp = ib::InvResp::cmd_new( + 0, + ID, + Commands::AttReqResp as u16, + EncodeValue::Closure(&cmd_data), + ); + let _ = resp.to_tlv(cmd_req.resp, TagType::Anonymous); + cmd_req.trans.complete(); + Ok(()) + } + + fn handle_command_certchainrequest( + &mut self, + cmd_req: &mut CommandReq, + ) -> Result<(), IMStatusCode> { + cmd_enter!("CertChainRequest"); + + info!("Received data: {}", cmd_req.data); + let cert_type = + get_certchainrequest_params(&cmd_req.data).map_err(|_| IMStatusCode::InvalidCommand)?; + + let mut buf: [u8; RESP_MAX] = [0; RESP_MAX]; + let len = self + .dev_att + .get_devatt_data(cert_type, &mut buf) + .map_err(|_| IMStatusCode::Failure)?; + let buf = &buf[0..len]; + + let cmd_data = CertChainResp { + cert: OctetStr::new(buf), + }; + let resp = ib::InvResp::cmd_new( + 0, + ID, + Commands::CertChainResp as u16, + EncodeValue::Value(&cmd_data), + ); + let _ = resp.to_tlv(cmd_req.resp, TagType::Anonymous); + cmd_req.trans.complete(); + Ok(()) + } + + fn handle_command_csrrequest(&mut self, cmd_req: &mut CommandReq) -> Result<(), IMStatusCode> { + cmd_enter!("CSRRequest"); + + let req = CommonReq::from_tlv(&cmd_req.data).map_err(|_| IMStatusCode::InvalidCommand)?; + info!("Received CSR Nonce:{:?}", req.str); + + if !self.failsafe.is_armed() { + return Err(IMStatusCode::UnsupportedAccess); + } + + let noc_keypair = KeyPair::new().map_err(|_| IMStatusCode::Failure)?; + let mut attest_challenge = [0u8; crypto::SYMM_KEY_LEN_BYTES]; + attest_challenge.copy_from_slice(cmd_req.trans.session.get_att_challenge()); + + let cmd_data = |tag: TagType, t: &mut TLVWriter| { + let mut buf: [u8; RESP_MAX] = [0; RESP_MAX]; + let mut nocsr_element = WriteBuf::new(&mut buf, RESP_MAX); + let _ = t.start_struct(tag); + let _ = add_nocsrelement(&noc_keypair, req.str.0, &mut nocsr_element, t); + let _ = add_attestation_signature( + self.dev_att.as_ref(), + &mut nocsr_element, + &attest_challenge, + t, + ); + let _ = t.end_container(); + }; + let resp = ib::InvResp::cmd_new( + 0, + ID, + Commands::CSRResp as u16, + EncodeValue::Closure(&cmd_data), + ); + + let _ = resp.to_tlv(cmd_req.resp, TagType::Anonymous); + let noc_data = Box::new(NocData::new(noc_keypair)); + // Store this in the session data instead of cluster data, so it gets cleared + // if the session goes away for some reason + cmd_req.trans.session.set_data(noc_data); + cmd_req.trans.complete(); + Ok(()) + } + + fn handle_command_addtrustedrootcert( + &mut self, + cmd_req: &mut CommandReq, + ) -> Result<(), IMStatusCode> { + cmd_enter!("AddTrustedRootCert"); + if !self.failsafe.is_armed() { + return Err(IMStatusCode::UnsupportedAccess); + } + + // This may happen on CASE or PASE. For PASE, the existence of NOC Data is necessary + match cmd_req.trans.session.get_session_mode() { + SessionMode::Case(_) => error!("CASE: AddTrustedRootCert handling pending"), // For a CASE Session, we just return success for now, + SessionMode::Pase => { + let noc_data = cmd_req + .trans + .session + .get_data::() + .ok_or(IMStatusCode::Failure)?; + + let req = + CommonReq::from_tlv(&cmd_req.data).map_err(|_| IMStatusCode::InvalidCommand)?; + info!("Received Trusted Cert:{:x?}", req.str); + + noc_data.root_ca = Cert::new(req.str.0).map_err(|_| IMStatusCode::Failure)?; + } + _ => (), + } + cmd_req.trans.complete(); + + Err(IMStatusCode::Sucess) + } +} + +impl ClusterType for NocCluster { + fn base(&self) -> &Cluster { + &self.base + } + fn base_mut(&mut self) -> &mut Cluster { + &mut self.base + } + + fn handle_command(&mut self, cmd_req: &mut CommandReq) -> Result<(), IMStatusCode> { + let cmd = cmd_req + .cmd + .path + .leaf + .map(num::FromPrimitive::from_u32) + .ok_or(IMStatusCode::UnsupportedCommand)? + .ok_or(IMStatusCode::UnsupportedCommand)?; + match cmd { + Commands::AddNOC => self.handle_command_addnoc(cmd_req), + Commands::CSRReq => self.handle_command_csrrequest(cmd_req), + Commands::AddTrustedRootCert => self.handle_command_addtrustedrootcert(cmd_req), + Commands::AttReq => self.handle_command_attrequest(cmd_req), + Commands::CertChainReq => self.handle_command_certchainrequest(cmd_req), + _ => Err(IMStatusCode::UnsupportedCommand), + } + } +} + +fn add_attestation_element( + dev_att: &dyn DevAttDataFetcher, + att_nonce: &[u8], + write_buf: &mut WriteBuf, + t: &mut TLVWriter, +) -> Result<(), Error> { + let mut cert_dec: [u8; MAX_CERT_DECLARATION_LEN] = [0; MAX_CERT_DECLARATION_LEN]; + let len = dev_att.get_devatt_data(dev_att::DataType::CertDeclaration, &mut cert_dec)?; + let cert_dec = &cert_dec[0..len]; + + let epoch = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() as u32; + let mut writer = TLVWriter::new(write_buf); + writer.start_struct(TagType::Anonymous)?; + writer.str16(TagType::Context(1), cert_dec)?; + writer.str8(TagType::Context(2), att_nonce)?; + writer.u32(TagType::Context(3), epoch)?; + writer.end_container()?; + + t.str16(TagType::Context(0), write_buf.as_borrow_slice())?; + Ok(()) +} + +fn add_attestation_signature( + dev_att: &dyn DevAttDataFetcher, + attest_element: &mut WriteBuf, + attest_challenge: &[u8], + resp: &mut TLVWriter, +) -> Result<(), Error> { + let dac_key = { + let mut pubkey = [0_u8; crypto::EC_POINT_LEN_BYTES]; + let mut privkey = [0_u8; crypto::BIGNUM_LEN_BYTES]; + dev_att.get_devatt_data(dev_att::DataType::DACPubKey, &mut pubkey)?; + dev_att.get_devatt_data(dev_att::DataType::DACPrivKey, &mut privkey)?; + KeyPair::new_from_components(&pubkey, &privkey) + }?; + attest_element.copy_from_slice(attest_challenge)?; + let mut signature = [0u8; crypto::EC_SIGNATURE_LEN_BYTES]; + dac_key.sign_msg(attest_element.as_borrow_slice(), &mut signature)?; + resp.str8(TagType::Context(1), &signature) +} + +fn add_nocsrelement( + noc_keypair: &KeyPair, + csr_nonce: &[u8], + write_buf: &mut WriteBuf, + resp: &mut TLVWriter, +) -> Result<(), Error> { + let mut csr: [u8; MAX_CSR_LEN] = [0; MAX_CSR_LEN]; + let csr = noc_keypair.get_csr(&mut csr)?; + let mut writer = TLVWriter::new(write_buf); + writer.start_struct(TagType::Anonymous)?; + writer.str8(TagType::Context(1), csr)?; + writer.str8(TagType::Context(2), csr_nonce)?; + writer.end_container()?; + + resp.str8(TagType::Context(0), write_buf.as_borrow_slice())?; + Ok(()) +} + +#[derive(ToTLV)] +struct CertChainResp<'a> { + cert: OctetStr<'a>, +} + +#[derive(ToTLV)] +struct NocResp { + status_code: u8, + fab_idx: u8, + debug_txt: String, +} + +#[derive(FromTLV)] +#[tlvargs(lifetime = "'a")] +struct AddNocReq<'a> { + noc_value: OctetStr<'a>, + icac_value: OctetStr<'a>, + ipk_value: OctetStr<'a>, + case_admin_subject: u64, + _vendor_id: u16, +} + +#[derive(FromTLV)] +#[tlvargs(lifetime = "'a")] +struct CommonReq<'a> { + str: OctetStr<'a>, +} + +#[derive(FromTLV)] +struct CertChainReq { + cert_type: u8, +} + +fn get_certchainrequest_params(data: &TLVElement) -> Result { + let cert_type = CertChainReq::from_tlv(data)?.cert_type; + + const CERT_TYPE_DAC: u8 = 1; + const CERT_TYPE_PAI: u8 = 2; + info!("Received Cert Type:{:?}", cert_type); + match cert_type { + CERT_TYPE_DAC => Ok(dev_att::DataType::DAC), + CERT_TYPE_PAI => Ok(dev_att::DataType::PAI), + _ => Err(Error::Invalid), + } +} diff --git a/matter/src/data_model/sdm/nw_commissioning.rs b/matter/src/data_model/sdm/nw_commissioning.rs new file mode 100644 index 0000000..3b08ec1 --- /dev/null +++ b/matter/src/data_model/sdm/nw_commissioning.rs @@ -0,0 +1,53 @@ +/* + * + * 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::{ + data_model::objects::{Cluster, ClusterType}, + error::Error, +}; + +pub const ID: u32 = 0x0031; + +pub struct NwCommCluster { + base: Cluster, +} + +impl ClusterType for NwCommCluster { + fn base(&self) -> &Cluster { + &self.base + } + fn base_mut(&mut self) -> &mut Cluster { + &mut self.base + } +} + +enum FeatureMap { + _Wifi = 0, + _Thread = 1, + Ethernet = 2, +} + +impl NwCommCluster { + pub fn new() -> Result, Error> { + let mut c = Box::new(Self { + base: Cluster::new(ID)?, + }); + // TODO: Arch-Specific + c.base.set_feature_map(FeatureMap::Ethernet as u32)?; + Ok(c) + } +} diff --git a/matter/src/data_model/system_model/access_control.rs b/matter/src/data_model/system_model/access_control.rs new file mode 100644 index 0000000..bf05bd7 --- /dev/null +++ b/matter/src/data_model/system_model/access_control.rs @@ -0,0 +1,394 @@ +/* + * + * 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::sync::Arc; + +use num_derive::FromPrimitive; + +use crate::acl::{self, AclEntry, AclMgr}; +use crate::data_model::objects::*; +use crate::error::*; +use crate::interaction_model::core::IMStatusCode; +use crate::interaction_model::messages::ib::{attr_list_write, ListOperation}; +use crate::tlv::{FromTLV, TLVElement, TagType, ToTLV}; +use log::{error, info}; + +pub const ID: u32 = 0x001F; + +#[derive(FromPrimitive)] +pub enum Attributes { + Acl = 0, + Extension = 1, + SubjectsPerEntry = 2, + TargetsPerEntry = 3, + EntriesPerFabric = 4, +} + +pub struct AccessControlCluster { + base: Cluster, + acl_mgr: Arc, +} + +impl AccessControlCluster { + pub fn new(acl_mgr: Arc) -> Result, Error> { + let mut c = Box::new(AccessControlCluster { + base: Cluster::new(ID)?, + acl_mgr, + }); + c.base.add_attribute(attr_acl_new()?)?; + c.base.add_attribute(attr_extension_new()?)?; + c.base.add_attribute(attr_subjects_per_entry_new()?)?; + c.base.add_attribute(attr_targets_per_entry_new()?)?; + c.base.add_attribute(attr_entries_per_fabric_new()?)?; + Ok(c) + } + + /// Write the ACL Attribute + /// + /// This takes care of 4 things, add item, edit item, delete item, delete list. + /// Care about fabric-scoped behaviour is taken + fn write_acl_attr( + &mut self, + op: ListOperation, + data: &TLVElement, + fab_idx: u8, + ) -> Result<(), IMStatusCode> { + info!("Performing ACL operation {:?}", op); + let result = match op { + ListOperation::AddItem | ListOperation::EditItem(_) => { + let mut acl_entry = + AclEntry::from_tlv(data).map_err(|_| IMStatusCode::ConstraintError)?; + info!("ACL {:?}", acl_entry); + // Overwrite the fabric index with our accessing fabric index + acl_entry.fab_idx = Some(fab_idx); + + if let ListOperation::EditItem(index) = op { + self.acl_mgr.edit(index as u8, fab_idx, acl_entry) + } else { + self.acl_mgr.add(acl_entry) + } + } + ListOperation::DeleteItem(index) => self.acl_mgr.delete(index as u8, fab_idx), + ListOperation::DeleteList => self.acl_mgr.delete_for_fabric(fab_idx), + }; + match result { + Ok(_) => Ok(()), + Err(Error::NoSpace) => Err(IMStatusCode::ResourceExhausted), + _ => Err(IMStatusCode::ConstraintError), + } + } +} + +impl ClusterType for AccessControlCluster { + fn base(&self) -> &Cluster { + &self.base + } + fn base_mut(&mut self) -> &mut Cluster { + &mut self.base + } + + fn read_custom_attribute(&self, encoder: &mut dyn Encoder, attr: &AttrDetails) { + match num::FromPrimitive::from_u16(attr.attr_id) { + Some(Attributes::Acl) => encoder.encode(EncodeValue::Closure(&|tag, tw| { + let _ = tw.start_array(tag); + let _ = self.acl_mgr.for_each_acl(|entry| { + if !attr.fab_filter || Some(attr.fab_idx) == entry.fab_idx { + let _ = entry.to_tlv(tw, TagType::Anonymous); + } + }); + let _ = tw.end_container(); + })), + Some(Attributes::Extension) => encoder.encode(EncodeValue::Closure(&|tag, tw| { + // Empty for now + let _ = tw.start_array(tag); + let _ = tw.end_container(); + })), + _ => { + error!("Attribute not yet supported: this shouldn't happen"); + } + } + } + + fn write_attribute( + &mut self, + attr: &AttrDetails, + data: &TLVElement, + ) -> Result<(), IMStatusCode> { + let result = match num::FromPrimitive::from_u16(attr.attr_id) { + Some(Attributes::Acl) => attr_list_write(attr, data, |op, data| { + self.write_acl_attr(op, data, attr.fab_idx) + }), + _ => { + error!("Attribute not yet supported: this shouldn't happen"); + Err(IMStatusCode::NotFound) + } + }; + if result.is_ok() { + self.base.cluster_changed(); + } + result + } +} + +fn attr_acl_new() -> Result { + Attribute::new( + Attributes::Acl as u16, + AttrValue::Custom, + Access::RWFA, + Quality::NONE, + ) +} + +fn attr_extension_new() -> Result { + Attribute::new( + Attributes::Extension as u16, + AttrValue::Custom, + Access::RWFA, + Quality::NONE, + ) +} + +fn attr_subjects_per_entry_new() -> Result { + Attribute::new( + Attributes::SubjectsPerEntry as u16, + AttrValue::Uint16(acl::SUBJECTS_PER_ENTRY as u16), + Access::RV, + Quality::FIXED, + ) +} + +fn attr_targets_per_entry_new() -> Result { + Attribute::new( + Attributes::TargetsPerEntry as u16, + AttrValue::Uint16(acl::TARGETS_PER_ENTRY as u16), + Access::RV, + Quality::FIXED, + ) +} + +fn attr_entries_per_fabric_new() -> Result { + Attribute::new( + Attributes::EntriesPerFabric as u16, + AttrValue::Uint16(acl::ENTRIES_PER_FABRIC as u16), + Access::RV, + Quality::FIXED, + ) +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use crate::{ + acl::{AclEntry, AclMgr, AuthMode}, + data_model::{ + core::AttrReadEncoder, + objects::{AttrDetails, ClusterType, Privilege}, + }, + interaction_model::messages::ib::ListOperation, + tlv::{get_root_node_struct, ElementType, TLVElement, TLVWriter, TagType, ToTLV}, + utils::writebuf::WriteBuf, + }; + + use super::AccessControlCluster; + + #[test] + /// Add an ACL entry + fn acl_cluster_add() { + let mut buf: [u8; 100] = [0; 100]; + let buf_len = buf.len(); + let mut writebuf = WriteBuf::new(&mut buf, buf_len); + let mut tw = TLVWriter::new(&mut writebuf); + + let acl_mgr = Arc::new(AclMgr::new_with(false).unwrap()); + let mut acl = AccessControlCluster::new(acl_mgr.clone()).unwrap(); + + let new = AclEntry::new(2, Privilege::VIEW, AuthMode::Case); + new.to_tlv(&mut tw, TagType::Anonymous).unwrap(); + let data = get_root_node_struct(writebuf.as_borrow_slice()).unwrap(); + + // Test, ACL has fabric index 2, but the accessing fabric is 1 + // the fabric index in the TLV should be ignored and the ACL should be created with entry 1 + let result = acl.write_acl_attr(ListOperation::AddItem, &data, 1); + assert_eq!(result, Ok(())); + + let verifier = AclEntry::new(1, Privilege::VIEW, AuthMode::Case); + acl_mgr + .for_each_acl(|a| { + assert_eq!(*a, verifier); + }) + .unwrap(); + } + + #[test] + /// - The listindex used for edit should be relative to the current fabric + fn acl_cluster_edit() { + let mut buf: [u8; 100] = [0; 100]; + let buf_len = buf.len(); + let mut writebuf = WriteBuf::new(&mut buf, buf_len); + let mut tw = TLVWriter::new(&mut writebuf); + + // Add 3 ACLs, belonging to fabric index 2, 1 and 2, in that order + let acl_mgr = Arc::new(AclMgr::new_with(false).unwrap()); + let mut verifier = [ + AclEntry::new(2, Privilege::VIEW, AuthMode::Case), + AclEntry::new(1, Privilege::VIEW, AuthMode::Case), + AclEntry::new(2, Privilege::ADMIN, AuthMode::Case), + ]; + for i in verifier { + acl_mgr.add(i).unwrap(); + } + let mut acl = AccessControlCluster::new(acl_mgr.clone()).unwrap(); + + let new = AclEntry::new(2, Privilege::VIEW, AuthMode::Case); + new.to_tlv(&mut tw, TagType::Anonymous).unwrap(); + let data = get_root_node_struct(writebuf.as_borrow_slice()).unwrap(); + + // Test, Edit Fabric 2's index 1 - with accessing fabring as 2 - allow + let result = acl.write_acl_attr(ListOperation::EditItem(1), &data, 2); + // Fabric 2's index 1, is actually our index 2, update the verifier + verifier[2] = new; + assert_eq!(result, Ok(())); + + // Also validate in the acl_mgr that the entries are in the right order + let mut index = 0; + acl_mgr + .for_each_acl(|a| { + assert_eq!(*a, verifier[index]); + index += 1; + }) + .unwrap(); + } + + #[test] + /// - The listindex used for delete should be relative to the current fabric + fn acl_cluster_delete() { + // Add 3 ACLs, belonging to fabric index 2, 1 and 2, in that order + let acl_mgr = Arc::new(AclMgr::new_with(false).unwrap()); + let input = [ + AclEntry::new(2, Privilege::VIEW, AuthMode::Case), + AclEntry::new(1, Privilege::VIEW, AuthMode::Case), + AclEntry::new(2, Privilege::ADMIN, AuthMode::Case), + ]; + for i in input { + acl_mgr.add(i).unwrap(); + } + let mut acl = AccessControlCluster::new(acl_mgr.clone()).unwrap(); + // data is don't-care actually + let data = TLVElement::new(TagType::Anonymous, ElementType::True); + + // Test , Delete Fabric 1's index 0 + let result = acl.write_acl_attr(ListOperation::DeleteItem(0), &data, 1); + assert_eq!(result, Ok(())); + + let verifier = [input[0], input[2]]; + // Also validate in the acl_mgr that the entries are in the right order + let mut index = 0; + acl_mgr + .for_each_acl(|a| { + assert_eq!(*a, verifier[index]); + index += 1; + }) + .unwrap(); + } + + #[test] + /// - acl read with and without fabric filtering + fn acl_cluster_read() { + let mut buf: [u8; 100] = [0; 100]; + let buf_len = buf.len(); + let mut writebuf = WriteBuf::new(&mut buf, buf_len); + + // Add 3 ACLs, belonging to fabric index 2, 1 and 2, in that order + let acl_mgr = Arc::new(AclMgr::new_with(false).unwrap()); + let input = [ + AclEntry::new(2, Privilege::VIEW, AuthMode::Case), + AclEntry::new(1, Privilege::VIEW, AuthMode::Case), + AclEntry::new(2, Privilege::ADMIN, AuthMode::Case), + ]; + for i in input { + acl_mgr.add(i).unwrap(); + } + let acl = AccessControlCluster::new(acl_mgr.clone()).unwrap(); + // Test 1, all 3 entries are read in the response without fabric filtering + { + let mut tw = TLVWriter::new(&mut writebuf); + let mut encoder = AttrReadEncoder::new(&mut tw); + let attr_details = AttrDetails { + attr_id: 0, + list_index: None, + fab_idx: 1, + fab_filter: false, + }; + acl.read_custom_attribute(&mut encoder, &attr_details); + assert_eq!( + &[ + 21, 53, 1, 36, 0, 0, 55, 1, 24, 54, 2, 21, 36, 1, 1, 36, 2, 2, 54, 3, 24, 54, + 4, 24, 36, 254, 2, 24, 21, 36, 1, 1, 36, 2, 2, 54, 3, 24, 54, 4, 24, 36, 254, + 1, 24, 21, 36, 1, 5, 36, 2, 2, 54, 3, 24, 54, 4, 24, 36, 254, 2, 24, 24, 24, + 24 + ], + writebuf.as_borrow_slice() + ); + } + writebuf.reset(0); + + // Test 2, only single entry is read in the response with fabric filtering and fabric idx 1 + { + let mut tw = TLVWriter::new(&mut writebuf); + let mut encoder = AttrReadEncoder::new(&mut tw); + + let attr_details = AttrDetails { + attr_id: 0, + list_index: None, + fab_idx: 1, + fab_filter: true, + }; + acl.read_custom_attribute(&mut encoder, &attr_details); + assert_eq!( + &[ + 21, 53, 1, 36, 0, 0, 55, 1, 24, 54, 2, 21, 36, 1, 1, 36, 2, 2, 54, 3, 24, 54, + 4, 24, 36, 254, 1, 24, 24, 24, 24 + ], + writebuf.as_borrow_slice() + ); + } + writebuf.reset(0); + + // Test 3, only single entry is read in the response with fabric filtering and fabric idx 2 + { + let mut tw = TLVWriter::new(&mut writebuf); + let mut encoder = AttrReadEncoder::new(&mut tw); + + let attr_details = AttrDetails { + attr_id: 0, + list_index: None, + fab_idx: 2, + fab_filter: true, + }; + acl.read_custom_attribute(&mut encoder, &attr_details); + assert_eq!( + &[ + 21, 53, 1, 36, 0, 0, 55, 1, 24, 54, 2, 21, 36, 1, 1, 36, 2, 2, 54, 3, 24, 54, + 4, 24, 36, 254, 2, 24, 21, 36, 1, 5, 36, 2, 2, 54, 3, 24, 54, 4, 24, 36, 254, + 2, 24, 24, 24, 24 + ], + writebuf.as_borrow_slice() + ); + } + } +} diff --git a/matter/src/data_model/system_model/descriptor.rs b/matter/src/data_model/system_model/descriptor.rs new file mode 100644 index 0000000..722a822 --- /dev/null +++ b/matter/src/data_model/system_model/descriptor.rs @@ -0,0 +1,98 @@ +/* + * + * 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 num_derive::FromPrimitive; + +use crate::data_model::core::DataModel; +use crate::data_model::objects::*; +use crate::error::*; +use crate::interaction_model::messages::GenericPath; +use crate::tlv::{TLVWriter, TagType}; +use log::error; + +pub const ID: u32 = 0x001D; + +#[derive(FromPrimitive)] +enum Attributes { + DeviceTypeList = 0, + ServerList = 1, + ClientList = 2, + PartsList = 3, +} + +pub struct DescriptorCluster { + base: Cluster, + endpoint_id: u16, + data_model: DataModel, +} + +impl DescriptorCluster { + pub fn new(endpoint_id: u16, data_model: DataModel) -> Result, Error> { + let mut c = Box::new(DescriptorCluster { + endpoint_id, + data_model, + base: Cluster::new(ID)?, + }); + c.base.add_attribute(attr_serverlist_new()?)?; + Ok(c) + } + + fn encode_server_list(&self, tag: TagType, tw: &mut TLVWriter) { + let path = GenericPath { + endpoint: Some(self.endpoint_id), + cluster: None, + leaf: None, + }; + let _ = tw.start_array(tag); + let dm = self.data_model.node.read().unwrap(); + let _ = dm.for_each_cluster(&path, |_current_path, c| { + let _ = tw.u32(TagType::Anonymous, c.base().id()); + Ok(()) + }); + let _ = tw.end_container(); + } +} + +impl ClusterType for DescriptorCluster { + fn base(&self) -> &Cluster { + &self.base + } + fn base_mut(&mut self) -> &mut Cluster { + &mut self.base + } + + fn read_custom_attribute(&self, encoder: &mut dyn Encoder, attr: &AttrDetails) { + match num::FromPrimitive::from_u16(attr.attr_id) { + Some(Attributes::ServerList) => encoder.encode(EncodeValue::Closure(&|tag, tw| { + self.encode_server_list(tag, tw) + })), + + _ => { + error!("Attribute not supported: this shouldn't happen"); + } + } + } +} + +fn attr_serverlist_new() -> Result { + Attribute::new( + Attributes::ServerList as u16, + AttrValue::Custom, + Access::RV, + Quality::NONE, + ) +} diff --git a/matter/src/data_model/system_model/mod.rs b/matter/src/data_model/system_model/mod.rs new file mode 100644 index 0000000..7a395d5 --- /dev/null +++ b/matter/src/data_model/system_model/mod.rs @@ -0,0 +1,19 @@ +/* + * + * 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. + */ + +pub mod access_control; +pub mod descriptor; diff --git a/matter/src/error.rs b/matter/src/error.rs new file mode 100644 index 0000000..03d6b6d --- /dev/null +++ b/matter/src/error.rs @@ -0,0 +1,125 @@ +/* + * + * 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::{array::TryFromSliceError, fmt, sync::PoisonError, time::SystemTimeError}; + +use async_channel::{SendError, TryRecvError}; +use log::error; + +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum Error { + AttributeNotFound, + AttributeIsCustom, + ClusterNotFound, + CommandNotFound, + EndpointNotFound, + Crypto, + TLSStack, + MdnsError, + Network, + NoCommand, + NoEndpoint, + NoExchange, + NoFabricId, + NoHandler, + NoNetworkInterface, + NoNodeId, + NoSession, + NoSpace, + NoSpaceAckTable, + NoSpaceRetransTable, + NoTagFound, + NotFound, + PacketPoolExhaust, + StdIoError, + SysTimeFail, + Invalid, + InvalidAAD, + InvalidData, + InvalidKeyLength, + InvalidOpcode, + InvalidPeerAddr, + // Invalid Auth Key in the Matter Certificate + InvalidAuthKey, + InvalidSignature, + InvalidState, + RwLock, + TLVNotFound, + TLVTypeMismatch, + TruncatedPacket, +} + +impl From for Error { + fn from(_e: std::io::Error) -> Self { + // Keep things simple for now + Self::StdIoError + } +} + +impl From> for Error { + fn from(_e: PoisonError) -> Self { + Self::RwLock + } +} + +#[cfg(feature = "crypto_openssl")] +impl From for Error { + fn from(e: openssl::error::ErrorStack) -> Self { + error!("Error in TLS: {}", e); + Self::TLSStack + } +} + +#[cfg(feature = "crypto_mbedtls")] +impl From for Error { + fn from(e: mbedtls::Error) -> Self { + error!("Error in TLS: {}", e); + Self::TLSStack + } +} + +impl From for Error { + fn from(_e: SystemTimeError) -> Self { + Self::SysTimeFail + } +} + +impl From for Error { + fn from(_e: TryFromSliceError) -> Self { + Self::Invalid + } +} + +impl From> for Error { + fn from(e: SendError) -> Self { + error!("Error in channel send {}", e); + Self::Invalid + } +} + +impl From for Error { + fn from(e: TryRecvError) -> Self { + error!("Error in channel try_recv {}", e); + Self::Invalid + } +} + +impl<'a> fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} diff --git a/matter/src/fabric.rs b/matter/src/fabric.rs new file mode 100644 index 0000000..8f85504 --- /dev/null +++ b/matter/src/fabric.rs @@ -0,0 +1,299 @@ +/* + * + * 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::sync::{Arc, Mutex, MutexGuard, RwLock}; + +use byteorder::{BigEndian, ByteOrder, LittleEndian}; +use log::info; +use owning_ref::RwLockReadGuardRef; + +use crate::{ + cert::Cert, + crypto::{self, crypto_dummy::KeyPairDummy, hkdf_sha256, CryptoKeyPair, HmacSha256, KeyPair}, + error::Error, + group_keys::KeySet, + mdns::{self, Mdns}, + sys::{Psm, SysMdnsService}, +}; + +const MAX_CERT_TLV_LEN: usize = 300; +const COMPRESSED_FABRIC_ID_LEN: usize = 8; + +macro_rules! fb_key { + ($index:ident, $key:ident) => { + &format!("fb{}{}", $index, $key) + }; +} + +const ST_RCA: &str = "rca"; +const ST_ICA: &str = "ica"; +const ST_NOC: &str = "noc"; +const ST_IPK: &str = "ipk"; +const ST_PBKEY: &str = "pubkey"; +const ST_PRKEY: &str = "privkey"; + +#[allow(dead_code)] +pub struct Fabric { + node_id: u64, + fabric_id: u64, + key_pair: Box, + pub root_ca: Cert, + pub icac: Cert, + pub noc: Cert, + pub ipk: KeySet, + compressed_id: [u8; COMPRESSED_FABRIC_ID_LEN], + mdns_service: Option, +} + +impl Fabric { + pub fn new( + key_pair: KeyPair, + root_ca: Cert, + icac: Cert, + noc: Cert, + ipk: &[u8], + ) -> Result { + let node_id = noc.get_node_id()?; + let fabric_id = noc.get_fabric_id()?; + + let mut f = Self { + node_id, + fabric_id, + key_pair: Box::new(key_pair), + root_ca, + icac, + noc, + ipk: KeySet::default(), + compressed_id: [0; COMPRESSED_FABRIC_ID_LEN], + mdns_service: None, + }; + Fabric::get_compressed_id(f.root_ca.get_pubkey(), fabric_id, &mut f.compressed_id)?; + f.ipk = KeySet::new(ipk, &f.compressed_id)?; + + let mut mdns_service_name = String::with_capacity(33); + for c in f.compressed_id { + mdns_service_name.push_str(&format!("{:02X}", c)); + } + mdns_service_name.push('-'); + let mut node_id_be: [u8; 8] = [0; 8]; + BigEndian::write_u64(&mut node_id_be, node_id); + for c in node_id_be { + mdns_service_name.push_str(&format!("{:02X}", c)); + } + info!("MDNS Service Name: {}", mdns_service_name); + f.mdns_service = Some( + Mdns::get()?.publish_service(&mdns_service_name, mdns::ServiceMode::Commissioned)?, + ); + Ok(f) + } + + pub fn dummy() -> Result { + Ok(Self { + node_id: 0, + fabric_id: 0, + key_pair: Box::new(KeyPairDummy::new()?), + root_ca: Cert::default(), + icac: Cert::default(), + noc: Cert::default(), + ipk: KeySet::default(), + compressed_id: [0; COMPRESSED_FABRIC_ID_LEN], + mdns_service: None, + }) + } + + fn get_compressed_id(root_pubkey: &[u8], fabric_id: u64, out: &mut [u8]) -> Result<(), Error> { + let root_pubkey = &root_pubkey[1..]; + let mut fabric_id_be: [u8; 8] = [0; 8]; + BigEndian::write_u64(&mut fabric_id_be, fabric_id); + const COMPRESSED_FABRIC_ID_INFO: [u8; 16] = [ + 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x46, 0x61, 0x62, 0x72, + 0x69, 0x63, + ]; + hkdf_sha256(&fabric_id_be, root_pubkey, &COMPRESSED_FABRIC_ID_INFO, out) + .map_err(|_| Error::NoSpace) + } + + pub fn match_dest_id(&self, random: &[u8], target: &[u8]) -> Result<(), Error> { + let mut mac = HmacSha256::new(self.ipk.op_key())?; + + mac.update(random)?; + mac.update(self.root_ca.get_pubkey())?; + + let mut buf: [u8; 8] = [0; 8]; + LittleEndian::write_u64(&mut buf, self.fabric_id); + mac.update(&buf)?; + + LittleEndian::write_u64(&mut buf, self.node_id); + mac.update(&buf)?; + + let mut id = [0_u8; crypto::SHA256_HASH_LEN_BYTES]; + mac.finish(&mut id)?; + if id.as_slice() == target { + Ok(()) + } else { + Err(Error::NotFound) + } + } + + pub fn sign_msg(&self, msg: &[u8], signature: &mut [u8]) -> Result { + self.key_pair.sign_msg(msg, signature) + } + + pub fn get_node_id(&self) -> u64 { + self.node_id + } + + pub fn get_fabric_id(&self) -> u64 { + self.fabric_id + } + + fn store(&self, index: usize, psm: &MutexGuard) -> Result<(), Error> { + let mut key = [0u8; MAX_CERT_TLV_LEN]; + let len = self.root_ca.as_tlv(&mut key)?; + psm.set_kv_slice(fb_key!(index, ST_RCA), &key[..len])?; + let len = self.icac.as_tlv(&mut key)?; + psm.set_kv_slice(fb_key!(index, ST_ICA), &key[..len])?; + let len = self.noc.as_tlv(&mut key)?; + psm.set_kv_slice(fb_key!(index, ST_NOC), &key[..len])?; + psm.set_kv_slice(fb_key!(index, ST_IPK), self.ipk.epoch_key())?; + + let mut key = [0_u8; crypto::EC_POINT_LEN_BYTES]; + let len = self.key_pair.get_public_key(&mut key)?; + let key = &key[..len]; + psm.set_kv_slice(fb_key!(index, ST_PBKEY), key)?; + + let mut key = [0_u8; crypto::BIGNUM_LEN_BYTES]; + let len = self.key_pair.get_private_key(&mut key)?; + let key = &key[..len]; + psm.set_kv_slice(fb_key!(index, ST_PRKEY), key)?; + + Ok(()) + } + + fn load(index: usize, psm: &MutexGuard) -> Result { + let mut root_ca = Vec::new(); + psm.get_kv_slice(fb_key!(index, ST_RCA), &mut root_ca)?; + let root_ca = Cert::new(root_ca.as_slice())?; + + let mut icac = Vec::new(); + psm.get_kv_slice(fb_key!(index, ST_ICA), &mut icac)?; + let icac = Cert::new(icac.as_slice())?; + + let mut noc = Vec::new(); + psm.get_kv_slice(fb_key!(index, ST_NOC), &mut noc)?; + let noc = Cert::new(noc.as_slice())?; + + let mut ipk = Vec::new(); + psm.get_kv_slice(fb_key!(index, ST_IPK), &mut ipk)?; + + let mut pub_key = Vec::new(); + psm.get_kv_slice(fb_key!(index, ST_PBKEY), &mut pub_key)?; + let mut priv_key = Vec::new(); + psm.get_kv_slice(fb_key!(index, ST_PRKEY), &mut priv_key)?; + let keypair = KeyPair::new_from_components(pub_key.as_slice(), priv_key.as_slice())?; + + Fabric::new(keypair, root_ca, icac, noc, ipk.as_slice()) + } +} + +pub const MAX_SUPPORTED_FABRICS: usize = 3; +#[derive(Default)] +pub struct FabricMgrInner { + // The outside world expects Fabric Index to be one more than the actual one + // since 0 is not allowed. Need to handle this cleanly somehow + pub fabrics: [Option; MAX_SUPPORTED_FABRICS], +} + +pub struct FabricMgr { + inner: RwLock, + psm: Arc>, +} + +impl FabricMgr { + pub fn new() -> Result { + let dummy_fabric = Fabric::dummy()?; + let mut mgr = FabricMgrInner::default(); + mgr.fabrics[0] = Some(dummy_fabric); + let mut fm = Self { + inner: RwLock::new(mgr), + psm: Psm::get()?, + }; + fm.load()?; + Ok(fm) + } + + fn store(&self, index: usize, fabric: &Fabric) -> Result<(), Error> { + let psm = self.psm.lock().unwrap(); + fabric.store(index, &psm) + } + + fn load(&mut self) -> Result<(), Error> { + let mut mgr = self.inner.write()?; + let psm = self.psm.lock().unwrap(); + for i in 0..MAX_SUPPORTED_FABRICS { + let result = Fabric::load(i, &psm); + if let Ok(fabric) = result { + info!("Adding new fabric at index {}", i); + mgr.fabrics[i] = Some(fabric); + } + } + Ok(()) + } + + pub fn add(&self, f: Fabric) -> Result { + let mut mgr = self.inner.write()?; + let index = mgr + .fabrics + .iter() + .position(|f| f.is_none()) + .ok_or(Error::NoSpace)?; + + self.store(index, &f)?; + + mgr.fabrics[index] = Some(f); + Ok(index as u8) + } + + pub fn match_dest_id(&self, random: &[u8], target: &[u8]) -> Result { + let mgr = self.inner.read()?; + for i in 0..MAX_SUPPORTED_FABRICS { + if let Some(fabric) = &mgr.fabrics[i] { + if fabric.match_dest_id(random, target).is_ok() { + return Ok(i); + } + } + } + Err(Error::NotFound) + } + + pub fn get_fabric<'ret, 'me: 'ret>( + &'me self, + idx: usize, + ) -> Result>, Error> { + Ok(RwLockReadGuardRef::new(self.inner.read()?).map(|fm| &fm.fabrics[idx])) + } + + pub fn is_empty(&self) -> bool { + let mgr = self.inner.read().unwrap(); + for i in 1..MAX_SUPPORTED_FABRICS { + if mgr.fabrics[i].is_some() { + return false; + } + } + true + } +} diff --git a/matter/src/group_keys.rs b/matter/src/group_keys.rs new file mode 100644 index 0000000..73c40e5 --- /dev/null +++ b/matter/src/group_keys.rs @@ -0,0 +1,76 @@ +/* + * + * 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::sync::{Arc, Mutex, Once}; + +use crate::{crypto, error::Error}; + +// This is just makeshift implementation for now, not used anywhere +pub struct GroupKeys {} + +static mut G_GRP_KEYS: Option>> = None; +static INIT: Once = Once::new(); + +impl GroupKeys { + fn new() -> Self { + Self {} + } + + pub fn get() -> Result>, Error> { + unsafe { + INIT.call_once(|| { + G_GRP_KEYS = Some(Arc::new(Mutex::new(GroupKeys::new()))); + }); + Ok(G_GRP_KEYS.as_ref().ok_or(Error::Invalid)?.clone()) + } + } + + pub fn insert_key() -> Result<(), Error> { + Ok(()) + } +} + +#[derive(Debug, Default)] +pub struct KeySet { + pub epoch_key: [u8; crypto::SYMM_KEY_LEN_BYTES], + pub op_key: [u8; crypto::SYMM_KEY_LEN_BYTES], +} + +impl KeySet { + pub fn new(epoch_key: &[u8], compressed_id: &[u8]) -> Result { + let mut ks = KeySet::default(); + KeySet::op_key_from_ipk(epoch_key, compressed_id, &mut ks.op_key)?; + ks.epoch_key.copy_from_slice(epoch_key); + Ok(ks) + } + + fn op_key_from_ipk(ipk: &[u8], compressed_id: &[u8], opkey: &mut [u8]) -> Result<(), Error> { + const GRP_KEY_INFO: [u8; 13] = [ + 0x47, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x20, 0x76, 0x31, 0x2e, 0x30, + ]; + + crypto::hkdf_sha256(compressed_id, ipk, &GRP_KEY_INFO, opkey).map_err(|_| Error::NoSpace) + } + + pub fn op_key(&self) -> &[u8] { + &self.op_key + } + + pub fn epoch_key(&self) -> &[u8] { + &self.epoch_key + } +} diff --git a/matter/src/interaction_model/command.rs b/matter/src/interaction_model/command.rs new file mode 100644 index 0000000..a566e17 --- /dev/null +++ b/matter/src/interaction_model/command.rs @@ -0,0 +1,76 @@ +/* + * + * 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::core::OpCode; +use super::messages::ib; +use super::messages::msg; +use super::messages::msg::InvReq; +use super::InteractionModel; +use super::Transaction; +use crate::{ + error::*, + tlv::{get_root_node_struct, print_tlv_list, FromTLV, TLVElement, TLVWriter, TagType}, + transport::{packet::Packet, proto_demux::ResponseRequired}, +}; +use log::error; + +#[macro_export] +macro_rules! cmd_enter { + ($e:expr) => {{ + use colored::Colorize; + info! {"{} {}", "Handling Command".cyan(), $e.cyan()} + }}; +} + +pub struct CommandReq<'a, 'b, 'c, 'd> { + pub cmd: ib::CmdPath, + pub data: TLVElement<'a>, + pub resp: &'a mut TLVWriter<'b, 'c>, + pub trans: &'a mut Transaction<'d>, +} + +impl InteractionModel { + pub fn handle_invoke_req( + &mut self, + trans: &mut Transaction, + rx_buf: &[u8], + proto_tx: &mut Packet, + ) -> Result { + proto_tx.set_proto_opcode(OpCode::InvokeResponse as u8); + + let mut tw = TLVWriter::new(proto_tx.get_writebuf()?); + let root = get_root_node_struct(rx_buf)?; + let inv_req = InvReq::from_tlv(&root)?; + + tw.start_struct(TagType::Anonymous)?; + // Suppress Response -> TODO: Need to revisit this for cases where we send a command back + tw.bool( + TagType::Context(msg::InvRespTag::SupressResponse as u8), + false, + )?; + + self.consumer + .consume_invoke_cmd(&inv_req, trans, &mut tw) + .map_err(|e| { + error!("Error in handling command: {:?}", e); + print_tlv_list(rx_buf); + e + })?; + tw.end_container()?; + Ok(ResponseRequired::Yes) + } +} diff --git a/matter/src/interaction_model/core.rs b/matter/src/interaction_model/core.rs new file mode 100644 index 0000000..86e0bef --- /dev/null +++ b/matter/src/interaction_model/core.rs @@ -0,0 +1,164 @@ +/* + * + * 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::{ + error::*, + tlv::{self, FromTLV, TLVElement, TLVWriter, TagType, ToTLV}, + transport::{ + proto_demux::{self, ProtoCtx, ResponseRequired}, + session::Session, + }, +}; +use colored::Colorize; +use log::{error, info}; +use num; +use num_derive::FromPrimitive; + +use super::InteractionConsumer; +use super::InteractionModel; +use super::Transaction; +use super::TransactionState; + +/* Handle messages related to the Interation Model + */ + +/* Interaction Model ID as per the Matter Spec */ +const PROTO_ID_INTERACTION_MODEL: usize = 0x01; + +#[derive(FromPrimitive, Debug, Copy, Clone)] +pub enum OpCode { + Reserved = 0, + StatusResponse = 1, + ReadRequest = 2, + SubscribeRequest = 3, + SubscriptResponse = 4, + ReportData = 5, + WriteRequest = 6, + WriteResponse = 7, + InvokeRequest = 8, + InvokeResponse = 9, + TimedRequest = 10, +} + +impl<'a> Transaction<'a> { + pub fn new(session: &'a mut Session) -> Self { + Self { + state: TransactionState::Ongoing, + data: None, + session, + } + } + + pub fn complete(&mut self) { + self.state = TransactionState::Complete + } + + pub fn is_complete(&self) -> bool { + self.state == TransactionState::Complete + } +} + +impl InteractionModel { + pub fn new(consumer: Box) -> InteractionModel { + InteractionModel { consumer } + } +} + +impl proto_demux::HandleProto for InteractionModel { + fn handle_proto_id(&mut self, ctx: &mut ProtoCtx) -> Result { + let mut trans = Transaction::new(&mut ctx.exch_ctx.sess); + let proto_opcode: OpCode = + num::FromPrimitive::from_u8(ctx.rx.get_proto_opcode()).ok_or(Error::Invalid)?; + ctx.tx.set_proto_id(PROTO_ID_INTERACTION_MODEL as u16); + + let buf = ctx.rx.as_borrow_slice(); + info!("{} {:?}", "Received command".cyan(), proto_opcode); + tlv::print_tlv_list(buf); + let result = match proto_opcode { + OpCode::InvokeRequest => self.handle_invoke_req(&mut trans, buf, &mut ctx.tx)?, + OpCode::ReadRequest => self.handle_read_req(&mut trans, buf, &mut ctx.tx)?, + OpCode::WriteRequest => self.handle_write_req(&mut trans, buf, &mut ctx.tx)?, + _ => { + error!("Opcode Not Handled: {:?}", proto_opcode); + return Err(Error::InvalidOpcode); + } + }; + + if result == ResponseRequired::Yes { + info!("Sending response"); + tlv::print_tlv_list(ctx.tx.as_borrow_slice()); + } + if trans.is_complete() { + ctx.exch_ctx.exch.close(); + } + Ok(result) + } + + fn get_proto_id(&self) -> usize { + PROTO_ID_INTERACTION_MODEL as usize + } +} + +#[derive(FromPrimitive, Debug, Clone, Copy, PartialEq)] +pub enum IMStatusCode { + Sucess = 0, + Failure = 1, + InvalidSubscription = 0x7D, + UnsupportedAccess = 0x7E, + UnsupportedEndpoint = 0x7F, + InvalidAction = 0x80, + UnsupportedCommand = 0x81, + InvalidCommand = 0x85, + UnsupportedAttribute = 0x86, + ConstraintError = 0x87, + UnsupportedWrite = 0x88, + ResourceExhausted = 0x89, + NotFound = 0x8b, + UnreportableAttribute = 0x8c, + InvalidDataType = 0x8d, + UnsupportedRead = 0x8f, + DataVersionMismatch = 0x92, + Timeout = 0x94, + Busy = 0x9c, + UnsupportedCluster = 0xc3, + NoUpstreamSubscription = 0xc5, + NeedsTimedInteraction = 0xc6, +} + +impl From for IMStatusCode { + fn from(e: Error) -> Self { + match e { + Error::EndpointNotFound => IMStatusCode::UnsupportedEndpoint, + Error::ClusterNotFound => IMStatusCode::UnsupportedCluster, + Error::AttributeNotFound => IMStatusCode::UnsupportedAttribute, + Error::CommandNotFound => IMStatusCode::UnsupportedCommand, + _ => IMStatusCode::Failure, + } + } +} + +impl FromTLV<'_> for IMStatusCode { + fn from_tlv(t: &TLVElement) -> Result { + num::FromPrimitive::from_u16(t.u16()?).ok_or(Error::Invalid) + } +} + +impl ToTLV for IMStatusCode { + fn to_tlv(&self, tw: &mut TLVWriter, tag_type: TagType) -> Result<(), Error> { + tw.u16(tag_type, *self as u16) + } +} diff --git a/matter/src/interaction_model/messages.rs b/matter/src/interaction_model/messages.rs new file mode 100644 index 0000000..974e9af --- /dev/null +++ b/matter/src/interaction_model/messages.rs @@ -0,0 +1,451 @@ +/* + * + * 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::{ + error::Error, + tlv::{FromTLV, TLVElement, TLVWriter, TagType, ToTLV}, +}; + +// A generic path with endpoint, clusters, and a leaf +// The leaf could be command, attribute, event +#[derive(Default, Clone, Copy, Debug, PartialEq, FromTLV, ToTLV)] +#[tlvargs(datatype = "list")] +pub struct GenericPath { + pub endpoint: Option, + pub cluster: Option, + pub leaf: Option, +} + +impl GenericPath { + pub fn new(endpoint: Option, cluster: Option, leaf: Option) -> Self { + Self { + endpoint, + cluster, + leaf, + } + } + + /// Returns Ok, if the path is non wildcard, otherwise returns an error + pub fn not_wildcard(&self) -> Result<(u16, u32, u32), Error> { + match *self { + GenericPath { + endpoint: Some(e), + cluster: Some(c), + leaf: Some(l), + } => Ok((e, c, l)), + _ => Err(Error::Invalid), + } + } + /// Returns true, if the path is wildcard + pub fn is_wildcard(&self) -> bool { + !matches!( + *self, + GenericPath { + endpoint: Some(_), + cluster: Some(_), + leaf: Some(_), + } + ) + } +} + +pub mod msg { + + use crate::{ + error::Error, + tlv::{FromTLV, TLVArray, TLVElement, TLVWriter, TagType, ToTLV}, + }; + + use super::ib::{AttrData, AttrPath, AttrResp, CmdData, DataVersionFilter}; + + #[derive(FromTLV)] + #[tlvargs(lifetime = "'a")] + pub struct InvReq<'a> { + pub suppress_response: Option, + pub timed_request: Option, + pub inv_requests: Option>>, + } + + pub enum InvRespTag { + SupressResponse = 0, + InvokeResponses = 1, + } + + pub enum InvReqTag { + SupressResponse = 0, + TimedReq = 1, + InvokeRequests = 2, + } + + #[derive(Default, ToTLV, FromTLV)] + #[tlvargs(lifetime = "'a")] + pub struct ReadReq<'a> { + pub attr_requests: Option>, + event_requests: Option, + event_filters: Option, + pub fabric_filtered: bool, + pub dataver_filters: Option>, + } + + impl<'a> ReadReq<'a> { + pub fn new(fabric_filtered: bool) -> Self { + Self { + fabric_filtered, + ..Default::default() + } + } + + pub fn set_attr_requests(mut self, requests: &'a [AttrPath]) -> Self { + self.attr_requests = Some(TLVArray::new(requests)); + self + } + } + + #[derive(ToTLV, FromTLV)] + #[tlvargs(lifetime = "'b")] + pub struct WriteReq<'a, 'b> { + pub supress_response: Option, + timed_request: Option, + pub write_requests: TLVArray<'a, AttrData<'b>>, + more_chunked: Option, + } + + impl<'a, 'b> WriteReq<'a, 'b> { + pub fn new(supress_response: bool, write_requests: &'a [AttrData<'b>]) -> Self { + let mut w = Self { + supress_response: None, + write_requests: TLVArray::new(write_requests), + timed_request: None, + more_chunked: None, + }; + if supress_response { + w.supress_response = Some(true); + } + w + } + } + + // Report Data + #[derive(FromTLV, ToTLV)] + #[tlvargs(lifetime = "'a")] + pub struct ReportDataMsg<'a> { + pub subscription_id: Option, + pub attr_reports: Option>>, + // TODO + pub event_reports: Option, + pub more_chunks: Option, + pub suppress_response: Option, + } + + pub enum ReportDataTag { + _SubscriptionId = 0, + AttributeReports = 1, + _EventReport = 2, + _MoreChunkedMsgs = 3, + SupressResponse = 4, + } + + // Write Response + pub enum WriteRespTag { + WriteResponses = 0, + } +} + +pub mod ib { + use std::fmt::Debug; + + use crate::{ + data_model::objects::{AttrDetails, EncodeValue}, + error::Error, + interaction_model::core::IMStatusCode, + tlv::{FromTLV, Nullable, TLVElement, TLVWriter, TagType, ToTLV}, + }; + use log::error; + + use super::GenericPath; + + // Command Response + #[derive(Clone, Copy, FromTLV, ToTLV)] + #[tlvargs(lifetime = "'a")] + pub enum InvResp<'a> { + Cmd(CmdData<'a>), + Status(CmdStatus), + } + + impl<'a> InvResp<'a> { + pub fn cmd_new(endpoint: u16, cluster: u32, cmd: u16, data: EncodeValue<'a>) -> Self { + Self::Cmd(CmdData::new( + CmdPath::new(Some(endpoint), Some(cluster), Some(cmd)), + data, + )) + } + + pub fn status_new(cmd_path: CmdPath, status: IMStatusCode, cluster_status: u16) -> Self { + Self::Status(CmdStatus { + path: cmd_path, + status: Status::new(status, cluster_status), + }) + } + } + + #[derive(FromTLV, ToTLV, Copy, Clone, PartialEq, Debug)] + pub struct CmdStatus { + path: CmdPath, + status: Status, + } + + impl CmdStatus { + pub fn new(path: CmdPath, status: IMStatusCode, cluster_status: u16) -> Self { + Self { + path, + status: Status { + status, + cluster_status, + }, + } + } + } + + #[derive(Debug, Clone, Copy, FromTLV, ToTLV)] + #[tlvargs(lifetime = "'a")] + pub struct CmdData<'a> { + pub path: CmdPath, + pub data: EncodeValue<'a>, + } + + impl<'a> CmdData<'a> { + pub fn new(path: CmdPath, data: EncodeValue<'a>) -> Self { + Self { path, data } + } + } + + // Status + #[derive(Debug, Clone, Copy, PartialEq, FromTLV, ToTLV)] + pub struct Status { + pub status: IMStatusCode, + pub cluster_status: u16, + } + + impl Status { + pub fn new(status: IMStatusCode, cluster_status: u16) -> Status { + Status { + status, + cluster_status, + } + } + } + + // Attribute Response + #[derive(Clone, Copy, FromTLV, ToTLV, PartialEq, Debug)] + #[tlvargs(lifetime = "'a")] + pub enum AttrResp<'a> { + Status(AttrStatus), + Data(AttrData<'a>), + } + + impl<'a> AttrResp<'a> { + pub fn new(data_ver: u32, path: &AttrPath, data: EncodeValue<'a>) -> Self { + AttrResp::Data(AttrData::new(Some(data_ver), *path, data)) + } + + pub fn unwrap_data(self) -> AttrData<'a> { + match self { + AttrResp::Data(d) => d, + _ => { + panic!("No data exists"); + } + } + } + } + + // Attribute Data + #[derive(Clone, Copy, PartialEq, FromTLV, ToTLV, Debug)] + #[tlvargs(lifetime = "'a")] + pub struct AttrData<'a> { + pub data_ver: Option, + pub path: AttrPath, + pub data: EncodeValue<'a>, + } + + impl<'a> AttrData<'a> { + pub fn new(data_ver: Option, path: AttrPath, data: EncodeValue<'a>) -> Self { + Self { + data_ver, + path, + data, + } + } + } + + #[derive(Debug)] + /// Operations on an Interaction Model List + pub enum ListOperation { + /// Add (append) an item to the list + AddItem, + /// Edit an item from the list + EditItem(u16), + /// Delete item from the list + DeleteItem(u16), + /// Delete the whole list + DeleteList, + } + + /// Attribute Lists in Attribute Data are special. Infer the correct meaning using this function + pub fn attr_list_write( + attr: &AttrDetails, + data: &TLVElement, + mut f: F, + ) -> Result<(), IMStatusCode> + where + F: FnMut(ListOperation, &TLVElement) -> Result<(), IMStatusCode>, + { + if let Some(Nullable::NotNull(index)) = attr.list_index { + // If list index is valid, + // - this is a modify item or delete item operation + if data.null().is_ok() { + // If data is NULL, delete item + f(ListOperation::DeleteItem(index), data) + } else { + f(ListOperation::EditItem(index), data) + } + } else { + if data.confirm_array().is_ok() { + // If data is list, this is either Delete List or OverWrite List operation + // in either case, we have to first delete the whole list + f(ListOperation::DeleteList, data)?; + // Now the data must be a list, that should be added item by item + + let container = data.enter().ok_or(Error::Invalid)?; + for d in container { + f(ListOperation::AddItem, &d)?; + } + Ok(()) + } else { + // If data is not a list, this must be an add operation + f(ListOperation::AddItem, data) + } + } + } + + #[derive(Debug, Clone, Copy, PartialEq, FromTLV, ToTLV)] + pub struct AttrStatus { + path: AttrPath, + status: Status, + } + + impl AttrStatus { + pub fn new(path: &GenericPath, status: IMStatusCode, cluster_status: u16) -> Self { + Self { + path: AttrPath::new(path), + status: super::ib::Status::new(status, cluster_status), + } + } + } + + // Attribute Path + #[derive(Default, Clone, Copy, Debug, PartialEq, FromTLV, ToTLV)] + #[tlvargs(datatype = "list")] + pub struct AttrPath { + pub tag_compression: Option, + pub node: Option, + pub endpoint: Option, + pub cluster: Option, + pub attr: Option, + pub list_index: Option>, + } + + impl AttrPath { + pub fn new(path: &GenericPath) -> Self { + Self { + endpoint: path.endpoint, + cluster: path.cluster, + attr: path.leaf.map(|x| x as u16), + ..Default::default() + } + } + + pub fn to_gp(&self) -> GenericPath { + GenericPath::new(self.endpoint, self.cluster, self.attr.map(|x| x as u32)) + } + } + + // Command Path + #[derive(Default, Debug, Copy, Clone, PartialEq)] + pub struct CmdPath { + pub path: GenericPath, + } + + #[macro_export] + macro_rules! cmd_path_ib { + ($endpoint:literal,$cluster:ident,$command:expr) => {{ + use $crate::interaction_model::messages::{ib::CmdPath, GenericPath}; + CmdPath { + path: GenericPath { + endpoint: Some($endpoint), + cluster: Some($cluster), + leaf: Some($command as u32), + }, + } + }}; + } + + impl CmdPath { + pub fn new(endpoint: Option, cluster: Option, command: Option) -> Self { + Self { + path: GenericPath { + endpoint, + cluster, + leaf: command.map(|a| a as u32), + }, + } + } + } + + impl FromTLV<'_> for CmdPath { + fn from_tlv(cmd_path: &TLVElement) -> Result { + let c = CmdPath { + path: GenericPath::from_tlv(cmd_path)?, + }; + + if c.path.leaf.is_none() { + error!("Wildcard command parameter not supported"); + Err(Error::CommandNotFound) + } else { + Ok(c) + } + } + } + + impl ToTLV for CmdPath { + fn to_tlv(&self, tw: &mut TLVWriter, tag_type: TagType) -> Result<(), Error> { + self.path.to_tlv(tw, tag_type) + } + } + + #[derive(FromTLV, ToTLV, Copy, Clone)] + pub struct ClusterPath { + pub node: Option, + pub endpoint: u16, + pub cluster: u32, + } + + #[derive(FromTLV, ToTLV, Copy, Clone)] + pub struct DataVersionFilter { + pub path: ClusterPath, + pub data_ver: u32, + } +} diff --git a/matter/src/interaction_model/mod.rs b/matter/src/interaction_model/mod.rs new file mode 100644 index 0000000..3bd45ca --- /dev/null +++ b/matter/src/interaction_model/mod.rs @@ -0,0 +1,65 @@ +/* + * + * 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::any::Any; + +use crate::{error::Error, tlv::TLVWriter, transport::session::Session}; + +use self::messages::msg::{InvReq, ReadReq, WriteReq}; + +#[derive(PartialEq)] +pub enum TransactionState { + Ongoing, + Complete, +} +pub struct Transaction<'a> { + pub state: TransactionState, + pub data: Option>, + pub session: &'a mut Session, +} + +pub trait InteractionConsumer { + fn consume_invoke_cmd( + &self, + req: &InvReq, + trans: &mut Transaction, + tw: &mut TLVWriter, + ) -> Result<(), Error>; + + fn consume_read_attr( + &self, + req: &ReadReq, + trans: &mut Transaction, + tw: &mut TLVWriter, + ) -> Result<(), Error>; + + fn consume_write_attr( + &self, + req: &WriteReq, + trans: &mut Transaction, + tw: &mut TLVWriter, + ) -> Result<(), Error>; +} + +pub struct InteractionModel { + consumer: Box, +} +pub mod command; +pub mod core; +pub mod messages; +pub mod read; +pub mod write; diff --git a/matter/src/interaction_model/read.rs b/matter/src/interaction_model/read.rs new file mode 100644 index 0000000..58f336e --- /dev/null +++ b/matter/src/interaction_model/read.rs @@ -0,0 +1,55 @@ +/* + * + * 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::{ + error::Error, + interaction_model::core::OpCode, + tlv::{get_root_node_struct, FromTLV, TLVWriter, TagType}, + transport::{packet::Packet, proto_demux::ResponseRequired}, +}; + +use super::{ + messages::msg::{self, ReadReq}, + InteractionModel, Transaction, +}; + +impl InteractionModel { + pub fn handle_read_req( + &mut self, + trans: &mut Transaction, + rx_buf: &[u8], + proto_tx: &mut Packet, + ) -> Result { + proto_tx.set_proto_opcode(OpCode::ReportData as u8); + + let mut tw = TLVWriter::new(proto_tx.get_writebuf()?); + let root = get_root_node_struct(rx_buf)?; + let read_req = ReadReq::from_tlv(&root)?; + + tw.start_struct(TagType::Anonymous)?; + self.consumer.consume_read_attr(&read_req, trans, &mut tw)?; + // Supress response always true for read interaction + tw.bool( + TagType::Context(msg::ReportDataTag::SupressResponse as u8), + true, + )?; + tw.end_container()?; + + trans.complete(); + Ok(ResponseRequired::Yes) + } +} diff --git a/matter/src/interaction_model/write.rs b/matter/src/interaction_model/write.rs new file mode 100644 index 0000000..2a78e50 --- /dev/null +++ b/matter/src/interaction_model/write.rs @@ -0,0 +1,55 @@ +/* + * + * 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 log::error; + +use crate::{ + error::Error, + tlv::{get_root_node_struct, FromTLV, TLVWriter, TagType}, + transport::{packet::Packet, proto_demux::ResponseRequired}, +}; + +use super::{core::OpCode, messages::msg::WriteReq, InteractionModel, Transaction}; + +impl InteractionModel { + pub fn handle_write_req( + &mut self, + trans: &mut Transaction, + rx_buf: &[u8], + proto_tx: &mut Packet, + ) -> Result { + proto_tx.set_proto_opcode(OpCode::WriteResponse as u8); + + let mut tw = TLVWriter::new(proto_tx.get_writebuf()?); + let root = get_root_node_struct(rx_buf)?; + let write_req = WriteReq::from_tlv(&root)?; + let supress_response = write_req.supress_response.unwrap_or_default(); + + tw.start_struct(TagType::Anonymous)?; + self.consumer + .consume_write_attr(&write_req, trans, &mut tw)?; + tw.end_container()?; + + trans.complete(); + if supress_response { + error!("Supress response is set, is this the expected handling?"); + Ok(ResponseRequired::No) + } else { + Ok(ResponseRequired::Yes) + } + } +} diff --git a/matter/src/lib.rs b/matter/src/lib.rs new file mode 100644 index 0000000..728c484 --- /dev/null +++ b/matter/src/lib.rs @@ -0,0 +1,87 @@ +/* + * + * 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. + */ + + +//! Native Rust Implementation of Matter (Smart-Home) +//! +//! This crate implements the Matter specification that can be run on embedded devices +//! to build Matter-compatible smart-home/IoT devices. +//! +//! Currently Ethernet based transport is supported. +//! +//! # Examples +//! ``` +//! use matter::{Matter, CommissioningData}; +//! use matter::data_model::device_types::device_type_add_on_off_light; +//! use matter::data_model::cluster_basic_information::BasicInfoConfig; +//! use rand::prelude::*; +//! +//! # use matter::data_model::sdm::dev_att::{DataType, DevAttDataFetcher}; +//! # use matter::error::Error; +//! # pub struct DevAtt{} +//! # impl DevAttDataFetcher for DevAtt{ +//! # fn get_devatt_data(&self, data_type: DataType, data: &mut [u8]) -> Result { Ok(0) } +//! # } +//! # let dev_att = Box::new(DevAtt{}); +//! +//! /// The commissioning data for this device +//! let mut comm_data = CommissioningData { +//! passwd: 123456, +//! discriminator: 250, +//! ..Default::default() +//! }; +//! rand::thread_rng().fill_bytes(&mut comm_data.salt); +//! +//! /// The basic information about this device +//! let dev_info = BasicInfoConfig { +//! vid: 0x8002, +//! pid: 0xFFF1, +//! hw_ver: 2, +//! sw_ver: 1, +//! }; +//! +//! /// Get the Matter Object +//! /// The dev_att is an object that implements the DevAttDataFetcher trait. +//! let mut matter = Matter::new(dev_info, dev_att, comm_data).unwrap(); +//! let dm = matter.get_data_model(); +//! { +//! let mut node = dm.node.write().unwrap(); +//! /// Add our device-types +//! let endpoint = device_type_add_on_off_light(&mut node).unwrap(); +//! } +//! // Start the Matter Daemon +//! // matter.start_daemon().unwrap(); +//! ``` +//! Start off exploring by going to the [Matter] object. + +pub mod acl; +pub mod cert; +pub mod core; +pub mod crypto; +pub mod data_model; +pub mod error; +pub mod fabric; +pub mod group_keys; +pub mod interaction_model; +pub mod mdns; +pub mod secure_channel; +pub mod sys; +pub mod tlv; +pub mod transport; +pub mod utils; + +pub use crate::core::*; diff --git a/matter/src/mdns.rs b/matter/src/mdns.rs new file mode 100644 index 0000000..f5aeb7c --- /dev/null +++ b/matter/src/mdns.rs @@ -0,0 +1,101 @@ +/* + * + * 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::sync::{Arc, Mutex, Once}; + +use crate::{ + error::Error, + sys::{sys_publish_service, SysMdnsService}, + transport::udp::MATTER_PORT, +}; + +#[derive(Default)] +/// The mDNS service handler +pub struct MdnsInner { + /// Vendor ID + vid: u16, + /// Product ID + pid: u16, + /// Discriminator + discriminator: u16, +} + +pub struct Mdns { + inner: Mutex, +} + +const SHORT_DISCRIMINATOR_MASK: u16 = 0x700; +const SHORT_DISCRIMINATOR_SHIFT: u16 = 8; + +static mut G_MDNS: Option> = None; +static INIT: Once = Once::new(); + +pub enum ServiceMode { + Commissioned, + Commissionable, +} + +impl Mdns { + fn new() -> Self { + Self { + inner: Mutex::new(MdnsInner { + ..Default::default() + }), + } + } + + /// Get a handle to the globally unique mDNS instance + pub fn get() -> Result, Error> { + unsafe { + INIT.call_once(|| { + G_MDNS = Some(Arc::new(Mdns::new())); + }); + Ok(G_MDNS.as_ref().ok_or(Error::Invalid)?.clone()) + } + } + + /// Set mDNS service specific values + /// Values like vid, pid, discriminator etc + // TODO: More things like device-type etc can be added here + pub fn set_values(&self, vid: u16, pid: u16, discriminator: u16) { + let mut inner = self.inner.lock().unwrap(); + inner.vid = vid; + inner.pid = pid; + inner.discriminator = discriminator; + } + + /// Publish a mDNS service + /// name - is the service name (comma separated subtypes may follow) + /// mode - the current service mode + pub fn publish_service(&self, name: &str, mode: ServiceMode) -> Result { + match mode { + ServiceMode::Commissioned => { + sys_publish_service(name, "_matter._tcp", MATTER_PORT, &[]) + } + ServiceMode::Commissionable => { + let inner = self.inner.lock().unwrap(); + let short = + (inner.discriminator & SHORT_DISCRIMINATOR_MASK) >> SHORT_DISCRIMINATOR_SHIFT; + let serv_type = format!("_matterc._udp,_S{},_L{}", short, inner.discriminator); + + let str_discriminator = format!("{}", inner.discriminator); + let txt_kvs = [["D", &str_discriminator], ["CM", "1"]]; + sys_publish_service(name, &serv_type, MATTER_PORT, &txt_kvs) + } + } + } +} diff --git a/matter/src/secure_channel/case.rs b/matter/src/secure_channel/case.rs new file mode 100644 index 0000000..c034532 --- /dev/null +++ b/matter/src/secure_channel/case.rs @@ -0,0 +1,542 @@ +/* + * + * 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::sync::Arc; + +use log::{error, trace}; +use owning_ref::RwLockReadGuardRef; +use rand::prelude::*; + +use crate::{ + cert::Cert, + crypto::{self, CryptoKeyPair, KeyPair, Sha256}, + error::Error, + fabric::{Fabric, FabricMgr, FabricMgrInner}, + secure_channel::common, + secure_channel::common::SCStatusCodes, + tlv::{get_root_node_struct, FromTLV, OctetStr, TLVElement, TLVWriter, TagType}, + transport::{ + network::Address, + proto_demux::ProtoCtx, + queue::{Msg, WorkQ}, + session::{CloneData, SessionMode}, + }, + utils::writebuf::WriteBuf, +}; + +#[derive(PartialEq)] +enum State { + Sigma1Rx, + Sigma3Rx, +} + +pub struct CaseSession { + state: State, + peer_sessid: u16, + local_sessid: u16, + tt_hash: Sha256, + shared_secret: [u8; crypto::ECDH_SHARED_SECRET_LEN_BYTES], + our_pub_key: [u8; crypto::EC_POINT_LEN_BYTES], + peer_pub_key: [u8; crypto::EC_POINT_LEN_BYTES], + local_fabric_idx: usize, +} +impl CaseSession { + pub fn new(peer_sessid: u16, local_sessid: u16) -> Result { + Ok(Self { + state: State::Sigma1Rx, + peer_sessid, + local_sessid, + tt_hash: Sha256::new()?, + shared_secret: [0; crypto::ECDH_SHARED_SECRET_LEN_BYTES], + our_pub_key: [0; crypto::EC_POINT_LEN_BYTES], + peer_pub_key: [0; crypto::EC_POINT_LEN_BYTES], + local_fabric_idx: 0, + }) + } +} + +pub struct Case { + fabric_mgr: Arc, +} + +impl Case { + pub fn new(fabric_mgr: Arc) -> Self { + Self { fabric_mgr } + } + + pub fn handle_casesigma3(&mut self, ctx: &mut ProtoCtx) -> Result<(), Error> { + let mut case_session = ctx + .exch_ctx + .exch + .take_exchange_data::() + .ok_or(Error::InvalidState)?; + if case_session.state != State::Sigma1Rx { + return Err(Error::Invalid); + } + case_session.state = State::Sigma3Rx; + + let fabric = self.fabric_mgr.get_fabric(case_session.local_fabric_idx)?; + if fabric.is_none() { + common::create_sc_status_report( + &mut ctx.tx, + common::SCStatusCodes::NoSharedTrustRoots, + None, + )?; + ctx.exch_ctx.exch.close(); + return Ok(()); + } + // Safe to unwrap here + let fabric = fabric.as_ref().as_ref().unwrap(); + + let root = get_root_node_struct(ctx.rx.as_borrow_slice())?; + let encrypted = root.find_tag(1)?.slice()?; + + let mut decrypted: [u8; 800] = [0; 800]; + if encrypted.len() > decrypted.len() { + error!("Data too large"); + return Err(Error::NoSpace); + } + let decrypted = &mut decrypted[..encrypted.len()]; + decrypted.copy_from_slice(encrypted); + + let len = Case::get_sigma3_decryption(fabric.ipk.op_key(), &case_session, decrypted)?; + let decrypted = &decrypted[..len]; + + let root = get_root_node_struct(decrypted)?; + let d = Sigma3Decrypt::from_tlv(&root)?; + + let initiator_noc = Cert::new(d.initiator_noc.0)?; + let initiator_icac = Cert::new(d.initiator_icac.0)?; + if let Err(e) = Case::validate_certs(fabric, &initiator_noc, &initiator_icac) { + error!("Certificate Chain doesn't match: {}", e); + common::create_sc_status_report( + &mut ctx.tx, + common::SCStatusCodes::InvalidParameter, + None, + )?; + ctx.exch_ctx.exch.close(); + return Ok(()); + } + + if Case::validate_sigma3_sign( + d.initiator_noc.0, + d.initiator_icac.0, + &initiator_noc, + d.signature.0, + &case_session, + ) + .is_err() + { + error!("Sigma3 Signature doesn't match"); + common::create_sc_status_report( + &mut ctx.tx, + common::SCStatusCodes::InvalidParameter, + None, + )?; + ctx.exch_ctx.exch.close(); + return Ok(()); + } + + // Only now do we add this message to the TT Hash + case_session.tt_hash.update(ctx.rx.as_borrow_slice())?; + let clone_data = Case::get_session_clone_data( + fabric.ipk.op_key(), + fabric.get_node_id(), + initiator_noc.get_node_id()?, + ctx.exch_ctx.sess.get_peer_addr(), + &case_session, + )?; + // Queue a transport mgr request to add a new session + WorkQ::get()?.sync_send(Msg::NewSession(clone_data))?; + + common::create_sc_status_report( + &mut ctx.tx, + SCStatusCodes::SessionEstablishmentSuccess, + None, + )?; + ctx.exch_ctx.exch.clear_exchange_data(); + ctx.exch_ctx.exch.close(); + + Ok(()) + } + + pub fn handle_casesigma1(&mut self, ctx: &mut ProtoCtx) -> Result<(), Error> { + let rx_buf = ctx.rx.as_borrow_slice(); + let root = get_root_node_struct(rx_buf)?; + let r = Sigma1Req::from_tlv(&root)?; + + let local_fabric_idx = self + .fabric_mgr + .match_dest_id(r.initiator_random.0, r.dest_id.0); + if local_fabric_idx.is_err() { + error!("Fabric Index mismatch"); + common::create_sc_status_report( + &mut ctx.tx, + common::SCStatusCodes::NoSharedTrustRoots, + None, + )?; + ctx.exch_ctx.exch.close(); + return Ok(()); + } + + let local_sessid = ctx.exch_ctx.sess.reserve_new_sess_id(); + let mut case_session = Box::new(CaseSession::new(r.initiator_sessid, local_sessid)?); + case_session.tt_hash.update(rx_buf)?; + case_session.local_fabric_idx = local_fabric_idx?; + if r.peer_pub_key.0.len() != crypto::EC_POINT_LEN_BYTES { + error!("Invalid public key length"); + return Err(Error::Invalid); + } + case_session.peer_pub_key.copy_from_slice(r.peer_pub_key.0); + trace!( + "Destination ID matched to fabric index {}", + case_session.local_fabric_idx + ); + + // Create an ephemeral Key Pair + let key_pair = KeyPair::new()?; + let _ = key_pair.get_public_key(&mut case_session.our_pub_key)?; + + // Derive the Shared Secret + let len = key_pair.derive_secret(r.peer_pub_key.0, &mut case_session.shared_secret)?; + if len != 32 { + error!("Derived secret length incorrect"); + return Err(Error::Invalid); + } + // println!("Derived secret: {:x?} len: {}", secret, len); + + let mut our_random: [u8; 32] = [0; 32]; + rand::thread_rng().fill_bytes(&mut our_random); + + // Derive the Encrypted Part + const MAX_ENCRYPTED_SIZE: usize = 800; + + let mut encrypted: [u8; MAX_ENCRYPTED_SIZE] = [0; MAX_ENCRYPTED_SIZE]; + let encrypted_len = { + let mut signature = [0u8; crypto::EC_SIGNATURE_LEN_BYTES]; + let fabric = self.fabric_mgr.get_fabric(case_session.local_fabric_idx)?; + if fabric.is_none() { + common::create_sc_status_report( + &mut ctx.tx, + common::SCStatusCodes::NoSharedTrustRoots, + None, + )?; + ctx.exch_ctx.exch.close(); + return Ok(()); + } + + let sign_len = Case::get_sigma2_sign( + &fabric, + &case_session.our_pub_key, + &case_session.peer_pub_key, + &mut signature, + )?; + let signature = &signature[..sign_len]; + + Case::get_sigma2_encryption( + &fabric, + &our_random, + &mut case_session, + signature, + &mut encrypted, + )? + }; + let encrypted = &encrypted[0..encrypted_len]; + + // Generate our Response Body + let mut tw = TLVWriter::new(ctx.tx.get_writebuf()?); + tw.start_struct(TagType::Anonymous)?; + tw.str8(TagType::Context(1), &our_random)?; + tw.u16(TagType::Context(2), local_sessid)?; + tw.str8(TagType::Context(3), &case_session.our_pub_key)?; + tw.str16(TagType::Context(4), encrypted)?; + tw.end_container()?; + case_session.tt_hash.update(ctx.tx.as_borrow_slice())?; + ctx.exch_ctx.exch.set_exchange_data(case_session); + Ok(()) + } + + fn get_session_clone_data( + ipk: &[u8], + local_nodeid: u64, + peer_nodeid: u64, + peer_addr: Address, + case_session: &CaseSession, + ) -> Result { + let mut session_keys = [0_u8; 3 * crypto::SYMM_KEY_LEN_BYTES]; + Case::get_session_keys( + ipk, + &case_session.tt_hash, + &case_session.shared_secret, + &mut session_keys, + )?; + + let mut clone_data = CloneData::new( + local_nodeid, + peer_nodeid, + case_session.peer_sessid, + case_session.local_sessid, + peer_addr, + SessionMode::Case(case_session.local_fabric_idx as u8), + ); + + clone_data.dec_key.copy_from_slice(&session_keys[0..16]); + clone_data.enc_key.copy_from_slice(&session_keys[16..32]); + clone_data + .att_challenge + .copy_from_slice(&session_keys[32..48]); + Ok(clone_data) + } + + fn validate_sigma3_sign( + initiator_noc: &[u8], + initiator_icac: &[u8], + initiator_noc_cert: &Cert, + sign: &[u8], + case_session: &CaseSession, + ) -> Result<(), Error> { + const MAX_TBS_SIZE: usize = 800; + let mut buf: [u8; MAX_TBS_SIZE] = [0; MAX_TBS_SIZE]; + let mut write_buf = WriteBuf::new(&mut buf, MAX_TBS_SIZE); + let mut tw = TLVWriter::new(&mut write_buf); + tw.start_struct(TagType::Anonymous)?; + tw.str8(TagType::Context(1), initiator_noc)?; + tw.str8(TagType::Context(2), initiator_icac)?; + tw.str8(TagType::Context(3), &case_session.peer_pub_key)?; + tw.str8(TagType::Context(4), &case_session.our_pub_key)?; + tw.end_container()?; + + let key = KeyPair::new_from_public(initiator_noc_cert.get_pubkey())?; + key.verify_msg(write_buf.as_slice(), sign)?; + Ok(()) + } + + fn validate_certs(fabric: &Fabric, noc: &Cert, icac: &Cert) -> Result<(), Error> { + if let Ok(fid) = icac.get_fabric_id() { + if fid != fabric.get_fabric_id() { + return Err(Error::Invalid); + } + } + + if fabric.get_fabric_id() != noc.get_fabric_id()? { + return Err(Error::Invalid); + } + + noc.verify_chain_start() + .add_cert(icac)? + .add_cert(&fabric.root_ca)? + .finalise()?; + + Ok(()) + } + + fn get_session_keys( + ipk: &[u8], + tt: &Sha256, + shared_secret: &[u8], + key: &mut [u8], + ) -> Result<(), Error> { + const SEKEYS_INFO: [u8; 11] = [ + 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x73, + ]; + if key.len() < 48 { + return Err(Error::NoSpace); + } + let mut salt = Vec::::with_capacity(256); + salt.extend_from_slice(ipk); + let tt = tt.clone(); + let mut tt_hash = [0u8; crypto::SHA256_HASH_LEN_BYTES]; + tt.finish(&mut tt_hash)?; + salt.extend_from_slice(&tt_hash); + // println!("Session Key: salt: {:x?}, len: {}", salt, salt.len()); + + crypto::hkdf_sha256(salt.as_slice(), shared_secret, &SEKEYS_INFO, key) + .map_err(|_x| Error::NoSpace)?; + // println!("Session Key: key: {:x?}", key); + + Ok(()) + } + + fn get_sigma3_decryption( + ipk: &[u8], + case_session: &CaseSession, + encrypted: &mut [u8], + ) -> Result { + let mut sigma3_key = [0_u8; crypto::SYMM_KEY_LEN_BYTES]; + Case::get_sigma3_key( + ipk, + &case_session.tt_hash, + &case_session.shared_secret, + &mut sigma3_key, + )?; + // println!("Sigma3 Key: {:x?}", sigma3_key); + + let nonce: [u8; 13] = [ + 0x4e, 0x43, 0x41, 0x53, 0x45, 0x5f, 0x53, 0x69, 0x67, 0x6d, 0x61, 0x33, 0x4e, + ]; + + let encrypted_len = encrypted.len(); + crypto::decrypt_in_place(&sigma3_key, &nonce, &[], encrypted)?; + Ok(encrypted_len - crypto::AEAD_MIC_LEN_BYTES) + } + + fn get_sigma3_key( + ipk: &[u8], + tt: &Sha256, + shared_secret: &[u8], + key: &mut [u8], + ) -> Result<(), Error> { + const S3K_INFO: [u8; 6] = [0x53, 0x69, 0x67, 0x6d, 0x61, 0x33]; + if key.len() < 16 { + return Err(Error::NoSpace); + } + let mut salt = Vec::::with_capacity(256); + salt.extend_from_slice(ipk); + + let tt = tt.clone(); + + let mut tt_hash = [0u8; crypto::SHA256_HASH_LEN_BYTES]; + tt.finish(&mut tt_hash)?; + salt.extend_from_slice(&tt_hash); + // println!("Sigma3Key: salt: {:x?}, len: {}", salt, salt.len()); + + crypto::hkdf_sha256(salt.as_slice(), shared_secret, &S3K_INFO, key) + .map_err(|_x| Error::NoSpace)?; + // println!("Sigma3Key: key: {:x?}", key); + + Ok(()) + } + + fn get_sigma2_key( + ipk: &[u8], + our_random: &[u8], + case_session: &mut CaseSession, + key: &mut [u8], + ) -> Result<(), Error> { + const S2K_INFO: [u8; 6] = [0x53, 0x69, 0x67, 0x6d, 0x61, 0x32]; + if key.len() < 16 { + return Err(Error::NoSpace); + } + let mut salt = Vec::::with_capacity(256); + salt.extend_from_slice(ipk); + salt.extend_from_slice(our_random); + salt.extend_from_slice(&case_session.our_pub_key); + + let tt = case_session.tt_hash.clone(); + + let mut tt_hash = [0u8; crypto::SHA256_HASH_LEN_BYTES]; + tt.finish(&mut tt_hash)?; + salt.extend_from_slice(&tt_hash); + // println!("Sigma2Key: salt: {:x?}, len: {}", salt, salt.len()); + + crypto::hkdf_sha256(salt.as_slice(), &case_session.shared_secret, &S2K_INFO, key) + .map_err(|_x| Error::NoSpace)?; + // println!("Sigma2Key: key: {:x?}", key); + + Ok(()) + } + + fn get_sigma2_encryption( + fabric: &RwLockReadGuardRef>, + our_random: &[u8], + case_session: &mut CaseSession, + signature: &[u8], + out: &mut [u8], + ) -> Result { + let mut resumption_id: [u8; 16] = [0; 16]; + rand::thread_rng().fill_bytes(&mut resumption_id); + + // We are guaranteed this unwrap will work + let fabric = fabric.as_ref().as_ref().unwrap(); + + let mut sigma2_key = [0_u8; crypto::SYMM_KEY_LEN_BYTES]; + Case::get_sigma2_key( + fabric.ipk.op_key(), + our_random, + case_session, + &mut sigma2_key, + )?; + + let mut write_buf = WriteBuf::new(out, out.len()); + let mut tw = TLVWriter::new(&mut write_buf); + tw.start_struct(TagType::Anonymous)?; + tw.str16_as(TagType::Context(1), |buf| fabric.noc.as_tlv(buf))?; + tw.str16_as(TagType::Context(2), |buf| fabric.icac.as_tlv(buf))?; + tw.str8(TagType::Context(3), signature)?; + tw.str8(TagType::Context(4), &resumption_id)?; + tw.end_container()?; + //println!("TBE is {:x?}", write_buf.as_borrow_slice()); + let nonce: [u8; crypto::AEAD_NONCE_LEN_BYTES] = [ + 0x4e, 0x43, 0x41, 0x53, 0x45, 0x5f, 0x53, 0x69, 0x67, 0x6d, 0x61, 0x32, 0x4e, + ]; + // let nonce = GenericArray::from_slice(&nonce); + // type AesCcm = Ccm; + // let cipher = AesCcm::new(GenericArray::from_slice(key)); + const TAG_LEN: usize = 16; + let tag = [0u8; TAG_LEN]; + write_buf.append(&tag)?; + let cipher_text = write_buf.as_mut_slice(); + + crypto::encrypt_in_place( + &sigma2_key, + &nonce, + &[], + cipher_text, + cipher_text.len() - TAG_LEN, + )?; + Ok(write_buf.as_slice().len()) + } + + fn get_sigma2_sign( + fabric: &RwLockReadGuardRef>, + our_pub_key: &[u8], + peer_pub_key: &[u8], + signature: &mut [u8], + ) -> Result { + // We are guaranteed this unwrap will work + let fabric = fabric.as_ref().as_ref().unwrap(); + const MAX_TBS_SIZE: usize = 800; + let mut buf: [u8; MAX_TBS_SIZE] = [0; MAX_TBS_SIZE]; + let mut write_buf = WriteBuf::new(&mut buf, MAX_TBS_SIZE); + let mut tw = TLVWriter::new(&mut write_buf); + tw.start_struct(TagType::Anonymous)?; + tw.str16_as(TagType::Context(1), |buf| fabric.noc.as_tlv(buf))?; + tw.str16_as(TagType::Context(2), |buf| fabric.icac.as_tlv(buf))?; + tw.str8(TagType::Context(3), our_pub_key)?; + tw.str8(TagType::Context(4), peer_pub_key)?; + tw.end_container()?; + //println!("TBS is {:x?}", write_buf.as_borrow_slice()); + fabric.sign_msg(write_buf.as_slice(), signature) + } +} + +#[derive(FromTLV)] +#[tlvargs(start = 1, lifetime = "'a")] +struct Sigma1Req<'a> { + initiator_random: OctetStr<'a>, + initiator_sessid: u16, + dest_id: OctetStr<'a>, + peer_pub_key: OctetStr<'a>, +} + +#[derive(FromTLV)] +#[tlvargs(start = 1, lifetime = "'a")] +struct Sigma3Decrypt<'a> { + initiator_noc: OctetStr<'a>, + initiator_icac: OctetStr<'a>, + signature: OctetStr<'a>, +} diff --git a/matter/src/secure_channel/common.rs b/matter/src/secure_channel/common.rs new file mode 100644 index 0000000..f368624 --- /dev/null +++ b/matter/src/secure_channel/common.rs @@ -0,0 +1,85 @@ +/* + * + * 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 num_derive::FromPrimitive; + +use crate::{error::Error, transport::packet::Packet}; + +use super::status_report::{create_status_report, GeneralCode}; + +/* Interaction Model ID as per the Matter Spec */ +pub const PROTO_ID_SECURE_CHANNEL: usize = 0x00; + +#[derive(FromPrimitive, Debug)] +pub enum OpCode { + MsgCounterSyncReq = 0x00, + MsgCounterSyncResp = 0x01, + MRPStandAloneAck = 0x10, + PBKDFParamRequest = 0x20, + PBKDFParamResponse = 0x21, + PASEPake1 = 0x22, + PASEPake2 = 0x23, + PASEPake3 = 0x24, + CASESigma1 = 0x30, + CASESigma2 = 0x31, + CASESigma3 = 0x32, + CASESigma2Resume = 0x33, + StatusReport = 0x40, +} + +#[derive(PartialEq)] +pub enum SCStatusCodes { + SessionEstablishmentSuccess = 0, + NoSharedTrustRoots = 1, + InvalidParameter = 2, + CloseSession = 3, + Busy = 4, + SessionNotFound = 5, +} + +pub fn create_sc_status_report( + proto_tx: &mut Packet, + status_code: SCStatusCodes, + proto_data: Option<&[u8]>, +) -> Result<(), Error> { + let general_code = match status_code { + SCStatusCodes::SessionEstablishmentSuccess => GeneralCode::Success, + SCStatusCodes::CloseSession => { + proto_tx.unset_reliable(); + // No time to manage reliable delivery for close session + // the session will be closed soon + GeneralCode::Success + } + SCStatusCodes::Busy + | SCStatusCodes::InvalidParameter + | SCStatusCodes::NoSharedTrustRoots + | SCStatusCodes::SessionNotFound => GeneralCode::Failure, + }; + create_status_report( + proto_tx, + general_code, + PROTO_ID_SECURE_CHANNEL as u32, + status_code as u16, + proto_data, + ) +} + +pub fn create_mrp_standalone_ack(proto_tx: &mut Packet) { + proto_tx.set_proto_id(PROTO_ID_SECURE_CHANNEL as u16); + proto_tx.set_proto_opcode(OpCode::MRPStandAloneAck as u8); + proto_tx.unset_reliable(); +} diff --git a/matter/src/secure_channel/core.rs b/matter/src/secure_channel/core.rs new file mode 100644 index 0000000..eb36531 --- /dev/null +++ b/matter/src/secure_channel/core.rs @@ -0,0 +1,140 @@ +/* + * + * 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::sync::Arc; + +use crate::{ + error::*, + fabric::FabricMgr, + mdns::{self, Mdns}, + secure_channel::{common::*, pake::PAKE}, + sys::SysMdnsService, + transport::proto_demux::{self, ProtoCtx, ResponseRequired}, +}; +use log::{error, info}; +use num; +use rand::prelude::*; + +use super::case::Case; + +/* Handle messages related to the Secure Channel + */ + +pub struct SecureChannel { + case: Case, + pake: Option<(PAKE, SysMdnsService)>, +} + +impl SecureChannel { + pub fn new(fabric_mgr: Arc) -> SecureChannel { + SecureChannel { + pake: None, + case: Case::new(fabric_mgr), + } + } + + pub fn open_comm_window(&mut self, salt: &[u8; 16], passwd: u32) -> Result<(), Error> { + let name: u64 = rand::thread_rng().gen_range(0..0xFFFFFFFFFFFFFFFF); + let name = format!("{:016X}", name); + let mdns = Mdns::get()?.publish_service(&name, mdns::ServiceMode::Commissionable)?; + self.pake = Some((PAKE::new(salt, passwd), mdns)); + Ok(()) + } + + pub fn close_comm_window(&mut self) { + self.pake = None; + } + + fn mrpstandaloneack_handler(&mut self, _ctx: &mut ProtoCtx) -> Result { + info!("In MRP StandAlone ACK Handler"); + Ok(ResponseRequired::No) + } + + fn pbkdfparamreq_handler(&mut self, ctx: &mut ProtoCtx) -> Result { + info!("In PBKDF Param Request Handler"); + ctx.tx.set_proto_opcode(OpCode::PBKDFParamResponse as u8); + if let Some((pake, _)) = &mut self.pake { + pake.handle_pbkdfparamrequest(ctx)?; + } else { + error!("PASE Not enabled"); + create_sc_status_report(&mut ctx.tx, SCStatusCodes::InvalidParameter, None)?; + } + Ok(ResponseRequired::Yes) + } + + fn pasepake1_handler(&mut self, ctx: &mut ProtoCtx) -> Result { + info!("In PASE Pake1 Handler"); + ctx.tx.set_proto_opcode(OpCode::PASEPake2 as u8); + if let Some((pake, _)) = &mut self.pake { + pake.handle_pasepake1(ctx)?; + } else { + error!("PASE Not enabled"); + create_sc_status_report(&mut ctx.tx, SCStatusCodes::InvalidParameter, None)?; + } + Ok(ResponseRequired::Yes) + } + + fn pasepake3_handler(&mut self, ctx: &mut ProtoCtx) -> Result { + info!("In PASE Pake3 Handler"); + if let Some((pake, _)) = &mut self.pake { + pake.handle_pasepake3(ctx)?; + // TODO: Currently we assume that PAKE is not successful and reset the PAKE object + self.pake = None; + } else { + error!("PASE Not enabled"); + create_sc_status_report(&mut ctx.tx, SCStatusCodes::InvalidParameter, None)?; + } + Ok(ResponseRequired::Yes) + } + + fn casesigma1_handler(&mut self, ctx: &mut ProtoCtx) -> Result { + info!("In CASE Sigma1 Handler"); + ctx.tx.set_proto_opcode(OpCode::CASESigma2 as u8); + self.case.handle_casesigma1(ctx)?; + Ok(ResponseRequired::Yes) + } + + fn casesigma3_handler(&mut self, ctx: &mut ProtoCtx) -> Result { + info!("In CASE Sigma3 Handler"); + self.case.handle_casesigma3(ctx)?; + Ok(ResponseRequired::Yes) + } +} + +impl proto_demux::HandleProto for SecureChannel { + fn handle_proto_id(&mut self, ctx: &mut ProtoCtx) -> Result { + let proto_opcode: OpCode = + num::FromPrimitive::from_u8(ctx.rx.get_proto_opcode()).ok_or(Error::Invalid)?; + ctx.tx.set_proto_id(PROTO_ID_SECURE_CHANNEL as u16); + match proto_opcode { + OpCode::MRPStandAloneAck => self.mrpstandaloneack_handler(ctx), + OpCode::PBKDFParamRequest => self.pbkdfparamreq_handler(ctx), + OpCode::PASEPake1 => self.pasepake1_handler(ctx), + OpCode::PASEPake3 => self.pasepake3_handler(ctx), + OpCode::CASESigma1 => self.casesigma1_handler(ctx), + OpCode::CASESigma3 => self.casesigma3_handler(ctx), + _ => { + error!("OpCode Not Handled: {:?}", proto_opcode); + Err(Error::InvalidOpcode) + } + } + } + + fn get_proto_id(&self) -> usize { + PROTO_ID_SECURE_CHANNEL as usize + } +} diff --git a/matter/src/secure_channel/crypto.rs b/matter/src/secure_channel/crypto.rs new file mode 100644 index 0000000..68dac8d --- /dev/null +++ b/matter/src/secure_channel/crypto.rs @@ -0,0 +1,52 @@ +/* + * + * 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::error::Error; + +// This trait allows us to switch between crypto providers like OpenSSL and mbedTLS for Spake2 +// Currently this is only validate for a verifier(responder) + +// A verifier will typically do: +// Step 1: w0 and L +// set_w0_from_w0s +// set_L +// Step 2: get_pB +// Step 3: get_TT_as_verifier(pA) +// Step 4: Computation of cA and cB happens outside since it doesn't use either BigNum or EcPoint +pub trait CryptoSpake2 { + fn new() -> Result + where + Self: Sized; + + fn set_w0_from_w0s(&mut self, w0s: &[u8]) -> Result<(), Error>; + fn set_w1_from_w1s(&mut self, w1s: &[u8]) -> Result<(), Error>; + fn set_w0(&mut self, w0: &[u8]) -> Result<(), Error>; + fn set_w1(&mut self, w1: &[u8]) -> Result<(), Error>; + + #[allow(non_snake_case)] + fn set_L(&mut self, w1s: &[u8]) -> Result<(), Error>; + #[allow(non_snake_case)] + fn get_pB(&mut self, pB: &mut [u8]) -> Result<(), Error>; + #[allow(non_snake_case)] + fn get_TT_as_verifier( + &mut self, + context: &[u8], + pA: &[u8], + pB: &[u8], + out: &mut [u8], + ) -> Result<(), Error>; +} diff --git a/matter/src/secure_channel/crypto_esp_mbedtls.rs b/matter/src/secure_channel/crypto_esp_mbedtls.rs new file mode 100644 index 0000000..632be2c --- /dev/null +++ b/matter/src/secure_channel/crypto_esp_mbedtls.rs @@ -0,0 +1,225 @@ +/* + * + * 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::error::Error; + +use super::crypto::CryptoSpake2; + +const MATTER_M_BIN: [u8; 65] = [ + 0x04, 0x88, 0x6e, 0x2f, 0x97, 0xac, 0xe4, 0x6e, 0x55, 0xba, 0x9d, 0xd7, 0x24, 0x25, 0x79, 0xf2, + 0x99, 0x3b, 0x64, 0xe1, 0x6e, 0xf3, 0xdc, 0xab, 0x95, 0xaf, 0xd4, 0x97, 0x33, 0x3d, 0x8f, 0xa1, + 0x2f, 0x5f, 0xf3, 0x55, 0x16, 0x3e, 0x43, 0xce, 0x22, 0x4e, 0x0b, 0x0e, 0x65, 0xff, 0x02, 0xac, + 0x8e, 0x5c, 0x7b, 0xe0, 0x94, 0x19, 0xc7, 0x85, 0xe0, 0xca, 0x54, 0x7d, 0x55, 0xa1, 0x2e, 0x2d, + 0x20, +]; +const MATTER_N_BIN: [u8; 65] = [ + 0x04, 0xd8, 0xbb, 0xd6, 0xc6, 0x39, 0xc6, 0x29, 0x37, 0xb0, 0x4d, 0x99, 0x7f, 0x38, 0xc3, 0x77, + 0x07, 0x19, 0xc6, 0x29, 0xd7, 0x01, 0x4d, 0x49, 0xa2, 0x4b, 0x4f, 0x98, 0xba, 0xa1, 0x29, 0x2b, + 0x49, 0x07, 0xd6, 0x0a, 0xa6, 0xbf, 0xad, 0xe4, 0x50, 0x08, 0xa6, 0x36, 0x33, 0x7f, 0x51, 0x68, + 0xc6, 0x4d, 0x9b, 0xd3, 0x60, 0x34, 0x80, 0x8c, 0xd5, 0x64, 0x49, 0x0b, 0x1e, 0x65, 0x6e, 0xdb, + 0xe7, +]; + +#[allow(non_snake_case)] + +pub struct CryptoEspMbedTls {} + +impl CryptoSpake2 for CryptoEspMbedTls { + #[allow(non_snake_case)] + fn new() -> Result { + Ok(CryptoEspMbedTls {}) + } + + // Computes w0 from w0s respectively + fn set_w0_from_w0s(&mut self, w0s: &[u8]) -> Result<(), Error> { + // From the Matter Spec, + // w0 = w0s mod p + // where p is the order of the curve + + Ok(()) + } + + fn set_w1_from_w1s(&mut self, w1s: &[u8]) -> Result<(), Error> { + // From the Matter Spec, + // w1 = w1s mod p + // where p is the order of the curve + + Ok(()) + } + + fn set_w0(&mut self, w0: &[u8]) -> Result<(), Error> { + Ok(()) + } + + fn set_w1(&mut self, w1: &[u8]) -> Result<(), Error> { + Ok(()) + } + + #[allow(non_snake_case)] + #[allow(dead_code)] + fn set_L(&mut self, w1s: &[u8]) -> Result<(), Error> { + // From the Matter spec, + // L = w1 * P + // where P is the generator of the underlying elliptic curve + Ok(()) + } + + #[allow(non_snake_case)] + fn get_pB(&mut self, pB: &mut [u8]) -> Result<(), Error> { + // From the SPAKE2+ spec (https://datatracker.ietf.org/doc/draft-bar-cfrg-spake2plus/) + // for y + // - select random y between 0 to p + // - Y = y*P + w0*N + // - pB = Y + + Ok(()) + } + + #[allow(non_snake_case)] + fn get_TT_as_verifier( + &mut self, + context: &[u8], + pA: &[u8], + pB: &[u8], + out: &mut [u8], + ) -> Result<(), Error> { + Ok(()) + } +} + +impl CryptoEspMbedTls {} + +#[cfg(test)] +mod tests { + + use super::CryptoEspMbedTls; + use crate::secure_channel::crypto::CryptoSpake2; + use crate::secure_channel::spake2p_test_vectors::test_vectors::*; + use openssl::bn::BigNum; + use openssl::ec::{EcPoint, PointConversionForm}; + + #[test] + #[allow(non_snake_case)] + fn test_get_X() { + for t in RFC_T { + let mut c = CryptoEspMbedTls::new().unwrap(); + let x = BigNum::from_slice(&t.x).unwrap(); + c.set_w0(&t.w0).unwrap(); + let P = c.group.generator(); + + let r = + CryptoEspMbedTls::do_add_mul(P, &x, &c.M, &c.w0, &c.group, &mut c.bn_ctx).unwrap(); + assert_eq!( + t.X, + r.to_bytes(&c.group, PointConversionForm::UNCOMPRESSED, &mut c.bn_ctx) + .unwrap() + .as_slice() + ); + } + } + + #[test] + #[allow(non_snake_case)] + fn test_get_Y() { + for t in RFC_T { + let mut c = CryptoEspMbedTls::new().unwrap(); + let y = BigNum::from_slice(&t.y).unwrap(); + c.set_w0(&t.w0).unwrap(); + let P = c.group.generator(); + let r = + CryptoEspMbedTls::do_add_mul(P, &y, &c.N, &c.w0, &c.group, &mut c.bn_ctx).unwrap(); + assert_eq!( + t.Y, + r.to_bytes(&c.group, PointConversionForm::UNCOMPRESSED, &mut c.bn_ctx) + .unwrap() + .as_slice() + ); + } + } + + #[test] + #[allow(non_snake_case)] + fn test_get_ZV_as_prover() { + for t in RFC_T { + let mut c = CryptoEspMbedTls::new().unwrap(); + let x = BigNum::from_slice(&t.x).unwrap(); + c.set_w0(&t.w0).unwrap(); + c.set_w1(&t.w1).unwrap(); + let Y = EcPoint::from_bytes(&c.group, &t.Y, &mut c.bn_ctx).unwrap(); + let (Z, V) = CryptoEspMbedTls::get_ZV_as_prover( + &c.w0, + &c.w1, + &mut c.N, + &Y, + &x, + &c.order, + &c.group, + &mut c.bn_ctx, + ) + .unwrap(); + + assert_eq!( + t.Z, + Z.to_bytes(&c.group, PointConversionForm::UNCOMPRESSED, &mut c.bn_ctx) + .unwrap() + .as_slice() + ); + assert_eq!( + t.V, + V.to_bytes(&c.group, PointConversionForm::UNCOMPRESSED, &mut c.bn_ctx) + .unwrap() + .as_slice() + ); + } + } + + #[test] + #[allow(non_snake_case)] + fn test_get_ZV_as_verifier() { + for t in RFC_T { + let mut c = CryptoEspMbedTls::new().unwrap(); + let y = BigNum::from_slice(&t.y).unwrap(); + c.set_w0(&t.w0).unwrap(); + let X = EcPoint::from_bytes(&c.group, &t.X, &mut c.bn_ctx).unwrap(); + let L = EcPoint::from_bytes(&c.group, &t.L, &mut c.bn_ctx).unwrap(); + let (Z, V) = CryptoEspMbedTls::get_ZV_as_verifier( + &c.w0, + &L, + &mut c.M, + &X, + &y, + &c.order, + &c.group, + &mut c.bn_ctx, + ) + .unwrap(); + + assert_eq!( + t.Z, + Z.to_bytes(&c.group, PointConversionForm::UNCOMPRESSED, &mut c.bn_ctx) + .unwrap() + .as_slice() + ); + assert_eq!( + t.V, + V.to_bytes(&c.group, PointConversionForm::UNCOMPRESSED, &mut c.bn_ctx) + .unwrap() + .as_slice() + ); + } + } +} diff --git a/matter/src/secure_channel/crypto_mbedtls.rs b/matter/src/secure_channel/crypto_mbedtls.rs new file mode 100644 index 0000000..57eeff2 --- /dev/null +++ b/matter/src/secure_channel/crypto_mbedtls.rs @@ -0,0 +1,382 @@ +/* + * + * 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::{ + ops::{Mul, Sub}, + sync::Arc, +}; + +use crate::error::Error; + +use super::crypto::CryptoSpake2; +use byteorder::{ByteOrder, LittleEndian}; +use log::error; +use mbedtls::{ + bignum::Mpi, + ecp::EcPoint, + hash::Md, + pk::{EcGroup, EcGroupId, Pk}, + rng::{CtrDrbg, OsEntropy}, +}; + +const MATTER_M_BIN: [u8; 65] = [ + 0x04, 0x88, 0x6e, 0x2f, 0x97, 0xac, 0xe4, 0x6e, 0x55, 0xba, 0x9d, 0xd7, 0x24, 0x25, 0x79, 0xf2, + 0x99, 0x3b, 0x64, 0xe1, 0x6e, 0xf3, 0xdc, 0xab, 0x95, 0xaf, 0xd4, 0x97, 0x33, 0x3d, 0x8f, 0xa1, + 0x2f, 0x5f, 0xf3, 0x55, 0x16, 0x3e, 0x43, 0xce, 0x22, 0x4e, 0x0b, 0x0e, 0x65, 0xff, 0x02, 0xac, + 0x8e, 0x5c, 0x7b, 0xe0, 0x94, 0x19, 0xc7, 0x85, 0xe0, 0xca, 0x54, 0x7d, 0x55, 0xa1, 0x2e, 0x2d, + 0x20, +]; +const MATTER_N_BIN: [u8; 65] = [ + 0x04, 0xd8, 0xbb, 0xd6, 0xc6, 0x39, 0xc6, 0x29, 0x37, 0xb0, 0x4d, 0x99, 0x7f, 0x38, 0xc3, 0x77, + 0x07, 0x19, 0xc6, 0x29, 0xd7, 0x01, 0x4d, 0x49, 0xa2, 0x4b, 0x4f, 0x98, 0xba, 0xa1, 0x29, 0x2b, + 0x49, 0x07, 0xd6, 0x0a, 0xa6, 0xbf, 0xad, 0xe4, 0x50, 0x08, 0xa6, 0x36, 0x33, 0x7f, 0x51, 0x68, + 0xc6, 0x4d, 0x9b, 0xd3, 0x60, 0x34, 0x80, 0x8c, 0xd5, 0x64, 0x49, 0x0b, 0x1e, 0x65, 0x6e, 0xdb, + 0xe7, +]; + +#[allow(non_snake_case)] + +pub struct CryptoMbedTLS { + group: EcGroup, + order: Mpi, + xy: Mpi, + w0: Mpi, + w1: Mpi, + M: EcPoint, + N: EcPoint, + L: EcPoint, + pB: EcPoint, +} + +impl CryptoSpake2 for CryptoMbedTLS { + #[allow(non_snake_case)] + fn new() -> Result { + let group = EcGroup::new(mbedtls::pk::EcGroupId::SecP256R1)?; + let order = group.order()?; + let M = EcPoint::from_binary(&group, &MATTER_M_BIN)?; + let N = EcPoint::from_binary(&group, &MATTER_N_BIN)?; + + Ok(CryptoMbedTLS { + group, + order, + xy: Mpi::new(0)?, + M, + N, + w0: Mpi::new(0)?, + w1: Mpi::new(0)?, + L: EcPoint::new()?, + pB: EcPoint::new()?, + }) + } + + // Computes w0 from w0s respectively + fn set_w0_from_w0s(&mut self, w0s: &[u8]) -> Result<(), Error> { + // From the Matter Spec, + // w0 = w0s mod p + // where p is the order of the curve + + self.w0 = Mpi::from_binary(w0s)?; + self.w0 = self.w0.modulo(&self.order)?; + Ok(()) + } + + fn set_w1_from_w1s(&mut self, w1s: &[u8]) -> Result<(), Error> { + // From the Matter Spec, + // w1 = w1s mod p + // where p is the order of the curve + + self.w1 = Mpi::from_binary(w1s)?; + self.w1 = self.w1.modulo(&self.order)?; + Ok(()) + } + + fn set_w0(&mut self, w0: &[u8]) -> Result<(), Error> { + self.w0 = Mpi::from_binary(w0)?; + Ok(()) + } + + fn set_w1(&mut self, w1: &[u8]) -> Result<(), Error> { + self.w1 = Mpi::from_binary(w1)?; + Ok(()) + } + + #[allow(non_snake_case)] + #[allow(dead_code)] + fn set_L(&mut self, w1s: &[u8]) -> Result<(), Error> { + // From the Matter spec, + // L = w1 * P + // where P is the generator of the underlying elliptic curve + self.set_w1_from_w1s(w1s)?; + // TODO: rust-mbedtls doesn't yet accept the DRBG parameter + self.L = self.group.generator()?.mul(&mut self.group, &self.w1)?; + Ok(()) + } + + #[allow(non_snake_case)] + fn get_pB(&mut self, pB: &mut [u8]) -> Result<(), Error> { + // From the SPAKE2+ spec (https://datatracker.ietf.org/doc/draft-bar-cfrg-spake2plus/) + // for y + // - select random y between 0 to p + // - Y = y*P + w0*N + // - pB = Y + + // A private key on this curve is a random number between 0 to p + let mut ctr_drbg = CtrDrbg::new(Arc::new(OsEntropy::new()), None)?; + self.xy = Pk::generate_ec(&mut ctr_drbg, EcGroupId::SecP256R1)?.ec_private()?; + + let P = self.group.generator()?; + self.pB = EcPoint::muladd(&mut self.group, &P, &self.xy, &self.N, &self.w0)?; + + let pB_internal = self.pB.to_binary(&self.group, false)?; + let pB_internal = pB_internal.as_slice(); + if pB_internal.len() != pB.len() { + error!("pB length mismatch"); + return Err(Error::Invalid); + } + pB.copy_from_slice(pB_internal); + Ok(()) + } + + #[allow(non_snake_case)] + fn get_TT_as_verifier( + &mut self, + context: &[u8], + pA: &[u8], + pB: &[u8], + out: &mut [u8], + ) -> Result<(), Error> { + let mut TT = Md::new(mbedtls::hash::Type::Sha256)?; + // context + CryptoMbedTLS::add_to_tt(&mut TT, context)?; + // 2 empty identifiers + CryptoMbedTLS::add_to_tt(&mut TT, &[])?; + CryptoMbedTLS::add_to_tt(&mut TT, &[])?; + // M + CryptoMbedTLS::add_to_tt(&mut TT, &MATTER_M_BIN)?; + // N + CryptoMbedTLS::add_to_tt(&mut TT, &MATTER_N_BIN)?; + // X = pA + CryptoMbedTLS::add_to_tt(&mut TT, pA)?; + // Y = pB + CryptoMbedTLS::add_to_tt(&mut TT, pB)?; + + let X = EcPoint::from_binary(&self.group, pA)?; + let (Z, V) = CryptoMbedTLS::get_ZV_as_verifier( + &self.w0, + &self.L, + &mut self.M, + &X, + &self.xy, + &self.order, + &mut self.group, + )?; + + // Z + let tmp = Z.to_binary(&self.group, false)?; + let tmp = tmp.as_slice(); + CryptoMbedTLS::add_to_tt(&mut TT, tmp)?; + + // V + let tmp = V.to_binary(&self.group, false)?; + let tmp = tmp.as_slice(); + CryptoMbedTLS::add_to_tt(&mut TT, tmp)?; + + // w0 + let tmp = self.w0.to_binary()?; + let tmp = tmp.as_slice(); + CryptoMbedTLS::add_to_tt(&mut TT, tmp)?; + + TT.finish(out)?; + Ok(()) + } +} + +impl CryptoMbedTLS { + fn add_to_tt(tt: &mut Md, buf: &[u8]) -> Result<(), Error> { + let mut len_buf: [u8; 8] = [0; 8]; + LittleEndian::write_u64(&mut len_buf, buf.len() as u64); + tt.update(&len_buf)?; + if !buf.is_empty() { + tt.update(buf)?; + } + Ok(()) + } + + #[inline(always)] + #[allow(non_snake_case)] + #[allow(dead_code)] + fn get_ZV_as_prover( + w0: &Mpi, + w1: &Mpi, + N: &mut EcPoint, + Y: &EcPoint, + x: &Mpi, + order: &Mpi, + group: &mut EcGroup, + ) -> Result<(EcPoint, EcPoint), Error> { + // As per the RFC, the operation here is: + // Z = h*x*(Y - w0*N) + // V = h*w1*(Y - w0*N) + + // We will follow the same sequence as in C++ SDK, under the assumption + // that the same sequence works for all embedded platforms. So the step + // of operations is: + // tmp = x*w0 + // Z = x*Y + tmp*N (N is inverted to get the 'negative' effect) + // Z = h*Z (cofactor Mul) + + let mut tmp = x.mul(w0)?; + tmp = tmp.modulo(order)?; + + let inverted_N = CryptoMbedTLS::invert(group, N)?; + let Z = EcPoint::muladd(group, Y, x, &inverted_N, &tmp)?; + // Cofactor for P256 is 1, so that is a No-Op + + let mut tmp = w0.mul(w1)?; + tmp = tmp.modulo(order)?; + let V = EcPoint::muladd(group, Y, w1, &inverted_N, &tmp)?; + Ok((Z, V)) + } + + #[inline(always)] + #[allow(non_snake_case)] + #[allow(dead_code)] + fn get_ZV_as_verifier( + w0: &Mpi, + L: &EcPoint, + M: &mut EcPoint, + X: &EcPoint, + y: &Mpi, + order: &Mpi, + group: &mut EcGroup, + ) -> Result<(EcPoint, EcPoint), Error> { + // As per the RFC, the operation here is: + // Z = h*y*(X - w0*M) + // V = h*y*L + + // We will follow the same sequence as in C++ SDK, under the assumption + // that the same sequence works for all embedded platforms. So the step + // of operations is: + // tmp = y*w0 + // Z = y*X + tmp*M (M is inverted to get the 'negative' effect) + // Z = h*Z (cofactor Mul) + + let mut tmp = y.mul(w0)?; + tmp = tmp.modulo(order)?; + + let inverted_M = CryptoMbedTLS::invert(group, M)?; + let Z = EcPoint::muladd(group, X, y, &inverted_M, &tmp)?; + // Cofactor for P256 is 1, so that is a No-Op + + let V = L.mul(group, y)?; + Ok((Z, V)) + } + + fn invert(group: &mut EcGroup, num: &EcPoint) -> Result { + let p = group.p()?; + let num_y = num.y()?; + let inverted_num_y = p.sub(&num_y)?; + EcPoint::from_components(num.x()?, inverted_num_y) + } +} + +#[cfg(test)] +mod tests { + + use super::CryptoMbedTLS; + use crate::secure_channel::crypto::CryptoSpake2; + use crate::secure_channel::spake2p_test_vectors::test_vectors::*; + use mbedtls::bignum::Mpi; + use mbedtls::ecp::EcPoint; + + #[test] + #[allow(non_snake_case)] + fn test_get_X() { + for t in RFC_T { + let mut c = CryptoMbedTLS::new().unwrap(); + let x = Mpi::from_binary(&t.x).unwrap(); + c.set_w0(&t.w0).unwrap(); + let P = c.group.generator().unwrap(); + + let r = EcPoint::muladd(&mut c.group, &P, &x, &c.M, &c.w0).unwrap(); + assert_eq!(t.X, r.to_binary(&c.group, false).unwrap().as_slice()); + } + } + + #[test] + #[allow(non_snake_case)] + fn test_get_Y() { + for t in RFC_T { + let mut c = CryptoMbedTLS::new().unwrap(); + let y = Mpi::from_binary(&t.y).unwrap(); + c.set_w0(&t.w0).unwrap(); + let P = c.group.generator().unwrap(); + let r = EcPoint::muladd(&mut c.group, &P, &y, &c.N, &c.w0).unwrap(); + assert_eq!(t.Y, r.to_binary(&c.group, false).unwrap().as_slice()); + } + } + + #[test] + #[allow(non_snake_case)] + fn test_get_ZV_as_prover() { + for t in RFC_T { + let mut c = CryptoMbedTLS::new().unwrap(); + let x = Mpi::from_binary(&t.x).unwrap(); + c.set_w0(&t.w0).unwrap(); + c.set_w1(&t.w1).unwrap(); + let Y = EcPoint::from_binary(&c.group, &t.Y).unwrap(); + let (Z, V) = CryptoMbedTLS::get_ZV_as_prover( + &c.w0, + &c.w1, + &mut c.N, + &Y, + &x, + &c.order, + &mut c.group, + ) + .unwrap(); + + assert_eq!(t.Z, Z.to_binary(&c.group, false).unwrap().as_slice()); + assert_eq!(t.V, V.to_binary(&c.group, false).unwrap().as_slice()); + } + } + + #[test] + #[allow(non_snake_case)] + fn test_get_ZV_as_verifier() { + for t in RFC_T { + let mut c = CryptoMbedTLS::new().unwrap(); + let y = Mpi::from_binary(&t.y).unwrap(); + c.set_w0(&t.w0).unwrap(); + let X = EcPoint::from_binary(&c.group, &t.X).unwrap(); + let L = EcPoint::from_binary(&c.group, &t.L).unwrap(); + let (Z, V) = CryptoMbedTLS::get_ZV_as_verifier( + &c.w0, + &L, + &mut c.M, + &X, + &y, + &c.order, + &mut c.group, + ) + .unwrap(); + + assert_eq!(t.Z, Z.to_binary(&c.group, false).unwrap().as_slice()); + assert_eq!(t.V, V.to_binary(&c.group, false).unwrap().as_slice()); + } + } +} diff --git a/matter/src/secure_channel/crypto_openssl.rs b/matter/src/secure_channel/crypto_openssl.rs new file mode 100644 index 0000000..0f80f4c --- /dev/null +++ b/matter/src/secure_channel/crypto_openssl.rs @@ -0,0 +1,445 @@ +/* + * + * 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::error::Error; + +use super::crypto::CryptoSpake2; +use byteorder::{ByteOrder, LittleEndian}; +use log::error; +use openssl::{ + bn::{BigNum, BigNumContext}, + ec::{EcGroup, EcPoint, EcPointRef, PointConversionForm}, + hash::{Hasher, MessageDigest}, + nid::Nid, +}; + +const MATTER_M_BIN: [u8; 65] = [ + 0x04, 0x88, 0x6e, 0x2f, 0x97, 0xac, 0xe4, 0x6e, 0x55, 0xba, 0x9d, 0xd7, 0x24, 0x25, 0x79, 0xf2, + 0x99, 0x3b, 0x64, 0xe1, 0x6e, 0xf3, 0xdc, 0xab, 0x95, 0xaf, 0xd4, 0x97, 0x33, 0x3d, 0x8f, 0xa1, + 0x2f, 0x5f, 0xf3, 0x55, 0x16, 0x3e, 0x43, 0xce, 0x22, 0x4e, 0x0b, 0x0e, 0x65, 0xff, 0x02, 0xac, + 0x8e, 0x5c, 0x7b, 0xe0, 0x94, 0x19, 0xc7, 0x85, 0xe0, 0xca, 0x54, 0x7d, 0x55, 0xa1, 0x2e, 0x2d, + 0x20, +]; +const MATTER_N_BIN: [u8; 65] = [ + 0x04, 0xd8, 0xbb, 0xd6, 0xc6, 0x39, 0xc6, 0x29, 0x37, 0xb0, 0x4d, 0x99, 0x7f, 0x38, 0xc3, 0x77, + 0x07, 0x19, 0xc6, 0x29, 0xd7, 0x01, 0x4d, 0x49, 0xa2, 0x4b, 0x4f, 0x98, 0xba, 0xa1, 0x29, 0x2b, + 0x49, 0x07, 0xd6, 0x0a, 0xa6, 0xbf, 0xad, 0xe4, 0x50, 0x08, 0xa6, 0x36, 0x33, 0x7f, 0x51, 0x68, + 0xc6, 0x4d, 0x9b, 0xd3, 0x60, 0x34, 0x80, 0x8c, 0xd5, 0x64, 0x49, 0x0b, 0x1e, 0x65, 0x6e, 0xdb, + 0xe7, +]; + +#[allow(non_snake_case)] + +pub struct CryptoOpenSSL { + group: EcGroup, + bn_ctx: BigNumContext, + // Stores the randomly generated x or y depending upon who we are + xy: BigNum, + w0: BigNum, + w1: BigNum, + M: EcPoint, + N: EcPoint, + L: EcPoint, + pB: EcPoint, + order: BigNum, +} + +impl CryptoSpake2 for CryptoOpenSSL { + #[allow(non_snake_case)] + fn new() -> Result { + let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)?; + let mut bn_ctx = BigNumContext::new()?; + let M = EcPoint::from_bytes(&group, &MATTER_M_BIN, &mut bn_ctx)?; + let N = EcPoint::from_bytes(&group, &MATTER_N_BIN, &mut bn_ctx)?; + let L = EcPoint::new(&group)?; + let pB = EcPoint::from_bytes(&group, &MATTER_N_BIN, &mut bn_ctx)?; + let mut order = BigNum::new()?; + group.as_ref().order(&mut order, &mut bn_ctx)?; + + Ok(CryptoOpenSSL { + group, + bn_ctx, + xy: BigNum::new()?, + w0: BigNum::new()?, + w1: BigNum::new()?, + order, + M, + N, + pB, + L, + }) + } + + // Computes w0 from w0s respectively + fn set_w0_from_w0s(&mut self, w0s: &[u8]) -> Result<(), Error> { + // From the Matter Spec, + // w0 = w0s mod p + // where p is the order of the curve + + let w0s = BigNum::from_slice(w0s)?; + self.w0.checked_rem(&w0s, &self.order, &mut self.bn_ctx)?; + + Ok(()) + } + + fn set_w1_from_w1s(&mut self, w1s: &[u8]) -> Result<(), Error> { + // From the Matter Spec, + // w1 = w1s mod p + // where p is the order of the curve + + let w1s = BigNum::from_slice(w1s)?; + self.w1.checked_rem(&w1s, &self.order, &mut self.bn_ctx)?; + + Ok(()) + } + + fn set_w0(&mut self, w0: &[u8]) -> Result<(), Error> { + self.w0 = BigNum::from_slice(w0)?; + Ok(()) + } + + fn set_w1(&mut self, w1: &[u8]) -> Result<(), Error> { + self.w1 = BigNum::from_slice(w1)?; + Ok(()) + } + + #[allow(non_snake_case)] + #[allow(dead_code)] + fn set_L(&mut self, w1s: &[u8]) -> Result<(), Error> { + // From the Matter spec, + // L = w1 * P + // where P is the generator of the underlying elliptic curve + self.set_w1_from_w1s(w1s)?; + self.L = EcPoint::new(&self.group)?; + self.L.mul_generator(&self.group, &self.w1, &self.bn_ctx)?; + Ok(()) + } + + #[allow(non_snake_case)] + fn get_pB(&mut self, pB: &mut [u8]) -> Result<(), Error> { + // From the SPAKE2+ spec (https://datatracker.ietf.org/doc/draft-bar-cfrg-spake2plus/) + // for y + // - select random y between 0 to p + // - Y = y*P + w0*N + // - pB = Y + self.order.rand_range(&mut self.xy)?; + let P = self.group.generator(); + self.pB = CryptoOpenSSL::do_add_mul( + P, + &self.xy, + &self.N, + &self.w0, + &self.group, + &mut self.bn_ctx, + )?; + let pB_internal = self.pB.to_bytes( + &self.group, + PointConversionForm::UNCOMPRESSED, + &mut self.bn_ctx, + )?; + let pB_internal = pB_internal.as_slice(); + if pB_internal.len() != pB.len() { + error!("pB length mismatch"); + return Err(Error::Invalid); + } + pB.copy_from_slice(pB_internal); + Ok(()) + } + + #[allow(non_snake_case)] + fn get_TT_as_verifier( + &mut self, + context: &[u8], + pA: &[u8], + pB: &[u8], + TT_hash: &mut [u8], + ) -> Result<(), Error> { + let mut TT = Hasher::new(MessageDigest::sha256())?; + // context + CryptoOpenSSL::add_to_tt(&mut TT, context)?; + // 2 empty identifiers + CryptoOpenSSL::add_to_tt(&mut TT, &[])?; + CryptoOpenSSL::add_to_tt(&mut TT, &[])?; + // M + CryptoOpenSSL::add_to_tt(&mut TT, &MATTER_M_BIN)?; + // N + CryptoOpenSSL::add_to_tt(&mut TT, &MATTER_N_BIN)?; + // X = pA + CryptoOpenSSL::add_to_tt(&mut TT, pA)?; + // Y = pB + CryptoOpenSSL::add_to_tt(&mut TT, pB)?; + + let X = EcPoint::from_bytes(&self.group, pA, &mut self.bn_ctx)?; + let (Z, V) = CryptoOpenSSL::get_ZV_as_verifier( + &self.w0, + &self.L, + &mut self.M, + &X, + &self.xy, + &self.order, + &self.group, + &mut self.bn_ctx, + )?; + + // Z + let tmp = Z.to_bytes( + &self.group, + PointConversionForm::UNCOMPRESSED, + &mut self.bn_ctx, + )?; + let tmp = tmp.as_slice(); + CryptoOpenSSL::add_to_tt(&mut TT, tmp)?; + + // V + let tmp = V.to_bytes( + &self.group, + PointConversionForm::UNCOMPRESSED, + &mut self.bn_ctx, + )?; + let tmp = tmp.as_slice(); + CryptoOpenSSL::add_to_tt(&mut TT, tmp)?; + + // w0 + let tmp = self.w0.to_vec(); + let tmp = tmp.as_slice(); + CryptoOpenSSL::add_to_tt(&mut TT, tmp)?; + + let h = TT.finish()?; + TT_hash.copy_from_slice(h.as_ref()); + Ok(()) + } +} + +impl CryptoOpenSSL { + fn add_to_tt(tt: &mut Hasher, buf: &[u8]) -> Result<(), Error> { + let mut len_buf: [u8; 8] = [0; 8]; + LittleEndian::write_u64(&mut len_buf, buf.len() as u64); + tt.update(&len_buf)?; + if buf.len() > 0 { + tt.update(buf)?; + } + Ok(()) + } + + // Do a*b + c*d + #[inline(always)] + fn do_add_mul( + a: &EcPointRef, + b: &BigNum, + c: &EcPoint, + d: &BigNum, + group: &EcGroup, + bn_ctx: &mut BigNumContext, + ) -> Result { + let mut mul1 = EcPoint::new(group)?; + let mut mul2 = EcPoint::new(group)?; + mul1.mul(group, a, b, bn_ctx)?; + mul2.mul(group, c, d, bn_ctx)?; + let mut result = EcPoint::new(group)?; + result.add(group, &mul1, &mul2, bn_ctx)?; + Ok(result) + } + + #[inline(always)] + #[allow(non_snake_case)] + #[allow(dead_code)] + fn get_ZV_as_prover( + w0: &BigNum, + w1: &BigNum, + N: &mut EcPoint, + Y: &EcPoint, + x: &BigNum, + order: &BigNum, + group: &EcGroup, + bn_ctx: &mut BigNumContext, + ) -> Result<(EcPoint, EcPoint), Error> { + // As per the RFC, the operation here is: + // Z = h*x*(Y - w0*N) + // V = h*w1*(Y - w0*N) + + // We will follow the same sequence as in C++ SDK, under the assumption + // that the same sequence works for all embedded platforms. So the step + // of operations is: + // tmp = x*w0 + // Z = x*Y + tmp*N (N is inverted to get the 'negative' effect) + // Z = h*Z (cofactor Mul) + + let mut tmp = BigNum::new()?; + tmp.mod_mul(x, w0, order, bn_ctx)?; + N.invert(group, bn_ctx)?; + let Z = CryptoOpenSSL::do_add_mul(Y, x, N, &tmp, group, bn_ctx)?; + // Cofactor for P256 is 1, so that is a No-Op + + tmp.mod_mul(w1, w0, order, bn_ctx)?; + let V = CryptoOpenSSL::do_add_mul(Y, w1, N, &tmp, group, bn_ctx)?; + Ok((Z, V)) + } + + #[inline(always)] + #[allow(non_snake_case)] + #[allow(dead_code)] + fn get_ZV_as_verifier( + w0: &BigNum, + L: &EcPoint, + M: &mut EcPoint, + X: &EcPoint, + y: &BigNum, + order: &BigNum, + group: &EcGroup, + bn_ctx: &mut BigNumContext, + ) -> Result<(EcPoint, EcPoint), Error> { + // As per the RFC, the operation here is: + // Z = h*y*(X - w0*M) + // V = h*y*L + + // We will follow the same sequence as in C++ SDK, under the assumption + // that the same sequence works for all embedded platforms. So the step + // of operations is: + // tmp = y*w0 + // Z = y*X + tmp*M (M is inverted to get the 'negative' effect) + // Z = h*Z (cofactor Mul) + + let mut tmp = BigNum::new()?; + tmp.mod_mul(y, w0, order, bn_ctx)?; + M.invert(group, bn_ctx)?; + let Z = CryptoOpenSSL::do_add_mul(X, y, M, &tmp, group, bn_ctx)?; + // Cofactor for P256 is 1, so that is a No-Op + + let mut V = EcPoint::new(group)?; + V.mul(group, L, y, bn_ctx)?; + Ok((Z, V)) + } +} + +#[cfg(test)] +mod tests { + + use super::CryptoOpenSSL; + use crate::secure_channel::crypto::CryptoSpake2; + use crate::secure_channel::spake2p_test_vectors::test_vectors::*; + use openssl::bn::BigNum; + use openssl::ec::{EcPoint, PointConversionForm}; + + #[test] + #[allow(non_snake_case)] + fn test_get_X() { + for t in RFC_T { + let mut c = CryptoOpenSSL::new().unwrap(); + let x = BigNum::from_slice(&t.x).unwrap(); + c.set_w0(&t.w0).unwrap(); + let P = c.group.generator(); + + let r = CryptoOpenSSL::do_add_mul(P, &x, &c.M, &c.w0, &c.group, &mut c.bn_ctx).unwrap(); + assert_eq!( + t.X, + r.to_bytes(&c.group, PointConversionForm::UNCOMPRESSED, &mut c.bn_ctx) + .unwrap() + .as_slice() + ); + } + } + + #[test] + #[allow(non_snake_case)] + fn test_get_Y() { + for t in RFC_T { + let mut c = CryptoOpenSSL::new().unwrap(); + let y = BigNum::from_slice(&t.y).unwrap(); + c.set_w0(&t.w0).unwrap(); + let P = c.group.generator(); + let r = CryptoOpenSSL::do_add_mul(P, &y, &c.N, &c.w0, &c.group, &mut c.bn_ctx).unwrap(); + assert_eq!( + t.Y, + r.to_bytes(&c.group, PointConversionForm::UNCOMPRESSED, &mut c.bn_ctx) + .unwrap() + .as_slice() + ); + } + } + + #[test] + #[allow(non_snake_case)] + fn test_get_ZV_as_prover() { + for t in RFC_T { + let mut c = CryptoOpenSSL::new().unwrap(); + let x = BigNum::from_slice(&t.x).unwrap(); + c.set_w0(&t.w0).unwrap(); + c.set_w1(&t.w1).unwrap(); + let Y = EcPoint::from_bytes(&c.group, &t.Y, &mut c.bn_ctx).unwrap(); + let (Z, V) = CryptoOpenSSL::get_ZV_as_prover( + &c.w0, + &c.w1, + &mut c.N, + &Y, + &x, + &c.order, + &c.group, + &mut c.bn_ctx, + ) + .unwrap(); + + assert_eq!( + t.Z, + Z.to_bytes(&c.group, PointConversionForm::UNCOMPRESSED, &mut c.bn_ctx) + .unwrap() + .as_slice() + ); + assert_eq!( + t.V, + V.to_bytes(&c.group, PointConversionForm::UNCOMPRESSED, &mut c.bn_ctx) + .unwrap() + .as_slice() + ); + } + } + + #[test] + #[allow(non_snake_case)] + fn test_get_ZV_as_verifier() { + for t in RFC_T { + let mut c = CryptoOpenSSL::new().unwrap(); + let y = BigNum::from_slice(&t.y).unwrap(); + c.set_w0(&t.w0).unwrap(); + let X = EcPoint::from_bytes(&c.group, &t.X, &mut c.bn_ctx).unwrap(); + let L = EcPoint::from_bytes(&c.group, &t.L, &mut c.bn_ctx).unwrap(); + let (Z, V) = CryptoOpenSSL::get_ZV_as_verifier( + &c.w0, + &L, + &mut c.M, + &X, + &y, + &c.order, + &c.group, + &mut c.bn_ctx, + ) + .unwrap(); + + assert_eq!( + t.Z, + Z.to_bytes(&c.group, PointConversionForm::UNCOMPRESSED, &mut c.bn_ctx) + .unwrap() + .as_slice() + ); + assert_eq!( + t.V, + V.to_bytes(&c.group, PointConversionForm::UNCOMPRESSED, &mut c.bn_ctx) + .unwrap() + .as_slice() + ); + } + } +} diff --git a/matter/src/secure_channel/mod.rs b/matter/src/secure_channel/mod.rs new file mode 100644 index 0000000..5a3d5b8 --- /dev/null +++ b/matter/src/secure_channel/mod.rs @@ -0,0 +1,32 @@ +/* + * + * 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. + */ + +pub mod case; +pub mod common; +#[cfg(feature = "crypto_esp_mbedtls")] +pub mod crypto_esp_mbedtls; +#[cfg(feature = "crypto_mbedtls")] +pub mod crypto_mbedtls; +#[cfg(feature = "crypto_openssl")] +pub mod crypto_openssl; + +pub mod core; +pub mod crypto; +pub mod pake; +pub mod spake2p; +pub mod spake2p_test_vectors; +pub mod status_report; diff --git a/matter/src/secure_channel/pake.rs b/matter/src/secure_channel/pake.rs new file mode 100644 index 0000000..008f193 --- /dev/null +++ b/matter/src/secure_channel/pake.rs @@ -0,0 +1,285 @@ +/* + * + * 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::time::{Duration, SystemTime}; + +use super::{ + common::{create_sc_status_report, SCStatusCodes}, + spake2p::Spake2P, +}; +use crate::{ + crypto, + error::Error, + sys::SPAKE2_ITERATION_COUNT, + tlv::{self, get_root_node_struct, FromTLV, OctetStr, TLVElement, TLVWriter, TagType, ToTLV}, + transport::{ + exchange::ExchangeCtx, + network::Address, + proto_demux::ProtoCtx, + queue::{Msg, WorkQ}, + session::{CloneData, SessionMode}, + }, +}; +use log::{error, info}; +use rand::prelude::*; + +// This file basically deals with the handlers for the PASE secure channel protocol +// TLV extraction and encoding is done in this file. +// We create a Spake2p object and set it up in the exchange-data. This object then +// handles Spake2+ specific stuff. + +const PASE_DISCARD_TIMEOUT_SECS: Duration = Duration::from_secs(60); + +const SPAKE2_SESSION_KEYS_INFO: [u8; 11] = *b"SessionKeys"; + +struct SessionData { + start_time: SystemTime, + exch_id: u16, + peer_addr: Address, + spake2p: Box, +} + +impl SessionData { + fn is_sess_expired(&self) -> Result { + if SystemTime::now().duration_since(self.start_time)? > PASE_DISCARD_TIMEOUT_SECS { + Ok(true) + } else { + Ok(false) + } + } +} + +enum PakeState { + Idle, + InProgress(SessionData), +} + +impl PakeState { + fn take(&mut self) -> Result { + let new = std::mem::replace(self, PakeState::Idle); + if let PakeState::InProgress(s) = new { + Ok(s) + } else { + Err(Error::InvalidSignature) + } + } + + fn is_idle(&self) -> bool { + std::mem::discriminant(self) == std::mem::discriminant(&PakeState::Idle) + } + + fn take_sess_data(&mut self, exch_ctx: &ExchangeCtx) -> Result { + let sd = self.take()?; + if sd.exch_id != exch_ctx.exch.get_id() || sd.peer_addr != exch_ctx.sess.get_peer_addr() { + Err(Error::InvalidState) + } else { + Ok(sd) + } + } + + fn make_in_progress(&mut self, spake2p: Box, exch_ctx: &ExchangeCtx) { + *self = PakeState::InProgress(SessionData { + start_time: SystemTime::now(), + spake2p, + exch_id: exch_ctx.exch.get_id(), + peer_addr: exch_ctx.sess.get_peer_addr(), + }); + } + + fn set_sess_data(&mut self, sd: SessionData) { + *self = PakeState::InProgress(sd); + } +} + +impl Default for PakeState { + fn default() -> Self { + Self::Idle + } +} + +#[derive(Default)] +pub struct PAKE { + salt: [u8; 16], + passwd: u32, + state: PakeState, +} + +impl PAKE { + pub fn new(salt: &[u8; 16], passwd: u32) -> Self { + // TODO: Can any PBKDF2 calculation be pre-computed here + PAKE { + passwd, + salt: *salt, + ..Default::default() + } + } + + #[allow(non_snake_case)] + pub fn handle_pasepake3(&mut self, ctx: &mut ProtoCtx) -> Result<(), Error> { + let mut sd = self.state.take_sess_data(&ctx.exch_ctx)?; + + let cA = extract_pasepake_1_or_3_params(ctx.rx.as_borrow_slice())?; + let (status_code, Ke) = sd.spake2p.handle_cA(cA); + + if status_code == SCStatusCodes::SessionEstablishmentSuccess { + // Get the keys + let Ke = Ke.ok_or(Error::Invalid)?; + let mut session_keys: [u8; 48] = [0; 48]; + crypto::hkdf_sha256(&[], Ke, &SPAKE2_SESSION_KEYS_INFO, &mut session_keys) + .map_err(|_x| Error::NoSpace)?; + + // Create a session + let data = sd.spake2p.get_app_data(); + let peer_sessid: u16 = (data & 0xffff) as u16; + let local_sessid: u16 = ((data >> 16) & 0xffff) as u16; + let mut clone_data = CloneData::new( + 0, + 0, + peer_sessid, + local_sessid, + ctx.exch_ctx.sess.get_peer_addr(), + SessionMode::Pase, + ); + clone_data.dec_key.copy_from_slice(&session_keys[0..16]); + clone_data.enc_key.copy_from_slice(&session_keys[16..32]); + clone_data + .att_challenge + .copy_from_slice(&session_keys[32..48]); + + // Queue a transport mgr request to add a new session + WorkQ::get()?.sync_send(Msg::NewSession(clone_data))?; + } + + create_sc_status_report(&mut ctx.tx, status_code, None)?; + ctx.exch_ctx.exch.close(); + Ok(()) + } + + #[allow(non_snake_case)] + pub fn handle_pasepake1(&mut self, ctx: &mut ProtoCtx) -> Result<(), Error> { + let mut sd = self.state.take_sess_data(&ctx.exch_ctx)?; + + let pA = extract_pasepake_1_or_3_params(ctx.rx.as_borrow_slice())?; + let mut pB: [u8; 65] = [0; 65]; + let mut cB: [u8; 32] = [0; 32]; + sd.spake2p + .start_verifier(self.passwd, SPAKE2_ITERATION_COUNT, &self.salt)?; + sd.spake2p.handle_pA(pA, &mut pB, &mut cB)?; + + let mut tw = TLVWriter::new(ctx.tx.get_writebuf()?); + let resp = Pake1Resp { + pb: OctetStr(&pB), + cb: OctetStr(&cB), + }; + resp.to_tlv(&mut tw, TagType::Anonymous)?; + + self.state.set_sess_data(sd); + + Ok(()) + } + + pub fn handle_pbkdfparamrequest(&mut self, ctx: &mut ProtoCtx) -> Result<(), Error> { + if !self.state.is_idle() { + let sd = self.state.take()?; + if sd.is_sess_expired()? { + info!("Previous session expired, clearing it"); + self.state = PakeState::Idle; + } else { + info!("Previous session in-progress, denying new request"); + // little-endian timeout (here we've hardcoded 500ms) + create_sc_status_report(&mut ctx.tx, SCStatusCodes::Busy, Some(&[0xf4, 0x01]))?; + return Ok(()); + } + } + + let root = tlv::get_root_node(ctx.rx.as_borrow_slice())?; + let a = PBKDFParamReq::from_tlv(&root)?; + if a.passcode_id != 0 { + error!("Can't yet handle passcode_id != 0"); + return Err(Error::Invalid); + } + + let mut our_random: [u8; 32] = [0; 32]; + rand::thread_rng().fill_bytes(&mut our_random); + + let local_sessid = ctx.exch_ctx.sess.reserve_new_sess_id(); + let spake2p_data: u32 = ((local_sessid as u32) << 16) | a.initiator_ssid as u32; + let mut spake2p = Box::new(Spake2P::new()); + spake2p.set_app_data(spake2p_data as u32); + + // Generate response + let mut tw = TLVWriter::new(ctx.tx.get_writebuf()?); + let mut resp = PBKDFParamResp { + init_random: a.initiator_random, + our_random: OctetStr(&our_random), + local_sessid, + params: None, + }; + if !a.has_params { + let params_resp = PBKDFParamRespParams { + count: SPAKE2_ITERATION_COUNT, + salt: OctetStr(&self.salt), + }; + resp.params = Some(params_resp); + } + resp.to_tlv(&mut tw, TagType::Anonymous)?; + + spake2p.set_context(ctx.rx.as_borrow_slice(), ctx.tx.as_borrow_slice())?; + self.state.make_in_progress(spake2p, &ctx.exch_ctx); + + Ok(()) + } +} + +#[derive(ToTLV)] +#[tlvargs(start = 1)] +struct Pake1Resp<'a> { + pb: OctetStr<'a>, + cb: OctetStr<'a>, +} + +#[derive(ToTLV)] +#[tlvargs(start = 1)] +struct PBKDFParamRespParams<'a> { + count: u32, + salt: OctetStr<'a>, +} + +#[derive(ToTLV)] +#[tlvargs(start = 1)] +struct PBKDFParamResp<'a> { + init_random: OctetStr<'a>, + our_random: OctetStr<'a>, + local_sessid: u16, + params: Option>, +} + +#[allow(non_snake_case)] +fn extract_pasepake_1_or_3_params(buf: &[u8]) -> Result<&[u8], Error> { + let root = get_root_node_struct(buf)?; + let pA = root.find_tag(1)?.slice()?; + Ok(pA) +} + +#[derive(FromTLV)] +#[tlvargs(lifetime = "'a", start = 1)] +struct PBKDFParamReq<'a> { + initiator_random: OctetStr<'a>, + initiator_ssid: u16, + passcode_id: u16, + has_params: bool, +} diff --git a/matter/src/secure_channel/spake2p.rs b/matter/src/secure_channel/spake2p.rs new file mode 100644 index 0000000..3870bcd --- /dev/null +++ b/matter/src/secure_channel/spake2p.rs @@ -0,0 +1,276 @@ +/* + * + * 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::crypto::{self, HmacSha256}; +use byteorder::{ByteOrder, LittleEndian}; +use subtle::ConstantTimeEq; + +use crate::{ + crypto::{pbkdf2_hmac, Sha256}, + error::Error, +}; + +#[cfg(feature = "crypto_openssl")] +use super::crypto_openssl::CryptoOpenSSL; + +#[cfg(feature = "crypto_mbedtls")] +use super::crypto_mbedtls::CryptoMbedTLS; + +#[cfg(feature = "crypto_esp_mbedtls")] +use super::crypto_esp_mbedtls::CryptoEspMbedTls; + +use super::{common::SCStatusCodes, crypto::CryptoSpake2}; + +// This file handle Spake2+ specific instructions. In itself, this file is +// independent from the BigNum and EC operations that are typically required +// Spake2+. We use the CryptoSpake2 trait object that allows us to abstract +// out the specific implementations. +// +// In the case of the verifier, we don't actually release the Ke until we +// validate that the cA is confirmed. + +#[derive(PartialEq, Copy, Clone, Debug)] +pub enum Spake2VerifierState { + // Initialised - w0, L are set + Init, + // Pending Confirmation - Keys are derived but pending confirmation + PendingConfirmation, + // Confirmed + Confirmed, +} + +#[derive(PartialEq, Debug)] +pub enum Spake2Mode { + Unknown, + Prover, + Verifier(Spake2VerifierState), +} + +#[allow(non_snake_case)] +pub struct Spake2P { + mode: Spake2Mode, + context: Option, + Ke: [u8; 16], + cA: [u8; 32], + crypto_spake2: Option>, + app_data: u32, +} + +const SPAKE2P_KEY_CONFIRM_INFO: [u8; 16] = *b"ConfirmationKeys"; +const SPAKE2P_CONTEXT_PREFIX: [u8; 26] = *b"CHIP PAKE V1 Commissioning"; +const CRYPTO_GROUP_SIZE_BYTES: usize = 32; +const CRYPTO_W_SIZE_BYTES: usize = CRYPTO_GROUP_SIZE_BYTES + 8; + +#[cfg(feature = "crypto_openssl")] +fn crypto_spake2_new() -> Result, Error> { + Ok(Box::new(CryptoOpenSSL::new()?)) +} + +#[cfg(feature = "crypto_mbedtls")] +fn crypto_spake2_new() -> Result, Error> { + Ok(Box::new(CryptoMbedTLS::new()?)) +} + +#[cfg(feature = "crypto_esp_mbedtls")] +fn crypto_spake2_new() -> Result, Error> { + Ok(Box::new(CryptoEspMbedTls::new()?)) +} + +impl Default for Spake2P { + fn default() -> Self { + Self::new() + } +} + +impl Spake2P { + pub fn new() -> Self { + Spake2P { + mode: Spake2Mode::Unknown, + context: None, + crypto_spake2: None, + Ke: [0; 16], + cA: [0; 32], + app_data: 0, + } + } + + pub fn set_app_data(&mut self, data: u32) { + self.app_data = data; + } + + pub fn get_app_data(&mut self) -> u32 { + self.app_data + } + + pub fn set_context(&mut self, buf1: &[u8], buf2: &[u8]) -> Result<(), Error> { + let mut context = Sha256::new()?; + context.update(&SPAKE2P_CONTEXT_PREFIX)?; + context.update(buf1)?; + context.update(buf2)?; + self.context = Some(context); + Ok(()) + } + + #[inline(always)] + fn get_w0w1s(pw: u32, iter: u32, salt: &[u8], w0w1s: &mut [u8]) { + let mut pw_str: [u8; 4] = [0; 4]; + LittleEndian::write_u32(&mut pw_str, pw); + let _ = pbkdf2_hmac(&pw_str, iter as usize, salt, w0w1s); + } + + pub fn start_verifier(&mut self, pw: u32, iter: u32, salt: &[u8]) -> Result<(), Error> { + let mut w0w1s: [u8; (2 * CRYPTO_W_SIZE_BYTES)] = [0; (2 * CRYPTO_W_SIZE_BYTES)]; + Spake2P::get_w0w1s(pw, iter, salt, &mut w0w1s); + self.crypto_spake2 = Some(crypto_spake2_new()?); + + let w0s_len = w0w1s.len() / 2; + if let Some(crypto_spake2) = &mut self.crypto_spake2 { + crypto_spake2.set_w0_from_w0s(&w0w1s[0..w0s_len])?; + crypto_spake2.set_L(&w0w1s[w0s_len..])?; + } + + self.mode = Spake2Mode::Verifier(Spake2VerifierState::Init); + Ok(()) + } + + #[allow(non_snake_case)] + pub fn handle_pA(&mut self, pA: &[u8], pB: &mut [u8], cB: &mut [u8]) -> Result<(), Error> { + if self.mode != Spake2Mode::Verifier(Spake2VerifierState::Init) { + return Err(Error::InvalidState); + } + + if let Some(crypto_spake2) = &mut self.crypto_spake2 { + crypto_spake2.get_pB(pB)?; + if let Some(context) = self.context.take() { + let mut hash = [0u8; crypto::SHA256_HASH_LEN_BYTES]; + context.finish(&mut hash)?; + let mut TT = [0u8; crypto::SHA256_HASH_LEN_BYTES]; + crypto_spake2.get_TT_as_verifier(&hash, pA, pB, &mut TT)?; + + Spake2P::get_Ke_and_cAcB(&TT, pA, pB, &mut self.Ke, &mut self.cA, cB)?; + } + } + // We are finished with using the crypto_spake2 now + self.crypto_spake2 = None; + self.mode = Spake2Mode::Verifier(Spake2VerifierState::PendingConfirmation); + Ok(()) + } + + #[allow(non_snake_case)] + pub fn handle_cA(&mut self, cA: &[u8]) -> (SCStatusCodes, Option<&[u8]>) { + if self.mode != Spake2Mode::Verifier(Spake2VerifierState::PendingConfirmation) { + return (SCStatusCodes::SessionNotFound, None); + } + self.mode = Spake2Mode::Verifier(Spake2VerifierState::Confirmed); + if cA.ct_eq(&self.cA).unwrap_u8() == 1 { + (SCStatusCodes::SessionEstablishmentSuccess, Some(&self.Ke)) + } else { + (SCStatusCodes::InvalidParameter, None) + } + } + + #[inline(always)] + #[allow(non_snake_case)] + #[allow(dead_code)] + fn get_Ke_and_cAcB( + TT: &[u8], + pA: &[u8], + pB: &[u8], + Ke: &mut [u8], + cA: &mut [u8], + cB: &mut [u8], + ) -> Result<(), Error> { + // Step 1: Ka || Ke = Hash(TT) + let KaKe = TT; + let KaKe_len = KaKe.len(); + let Ka = &KaKe[0..KaKe_len / 2]; + let ke_internal = &KaKe[(KaKe_len / 2)..]; + if ke_internal.len() == Ke.len() { + Ke.copy_from_slice(ke_internal); + } else { + return Err(Error::NoSpace); + } + + // Step 2: KcA || KcB = KDF(nil, Ka, "ConfirmationKeys") + let mut KcAKcB: [u8; 32] = [0; 32]; + crypto::hkdf_sha256(&[], Ka, &SPAKE2P_KEY_CONFIRM_INFO, &mut KcAKcB) + .map_err(|_x| Error::NoSpace)?; + + let KcA = &KcAKcB[0..(KcAKcB.len() / 2)]; + let KcB = &KcAKcB[(KcAKcB.len() / 2)..]; + + // Step 3: cA = HMAC(KcA, pB), cB = HMAC(KcB, pA) + let mut mac = HmacSha256::new(KcA)?; + mac.update(pB)?; + mac.finish(cA)?; + + let mut mac = HmacSha256::new(KcB)?; + mac.update(pA)?; + mac.finish(cB)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + + use super::Spake2P; + use crate::{ + crypto, + secure_channel::{spake2p::CRYPTO_W_SIZE_BYTES, spake2p_test_vectors::test_vectors::*}, + }; + + #[test] + fn test_pbkdf2() { + // These are the vectors from one sample run of chip-tool along with our PBKDFParamResponse + let salt = [ + 0x4, 0xa1, 0xd2, 0xc6, 0x11, 0xf0, 0xbd, 0x36, 0x78, 0x67, 0x79, 0x7b, 0xfe, 0x82, + 0x36, 0x0, + ]; + let mut w0w1s: [u8; (2 * CRYPTO_W_SIZE_BYTES)] = [0; (2 * CRYPTO_W_SIZE_BYTES)]; + Spake2P::get_w0w1s(123456, 2000, &salt, &mut w0w1s); + assert_eq!( + w0w1s, + [ + 0xc7, 0x89, 0x33, 0x9c, 0xc5, 0xeb, 0xbc, 0xf6, 0xdf, 0x04, 0xa9, 0x11, 0x11, 0x06, + 0x4c, 0x15, 0xac, 0x5a, 0xea, 0x67, 0x69, 0x9f, 0x32, 0x62, 0xcf, 0xc6, 0xe9, 0x19, + 0xe8, 0xa4, 0x0b, 0xb3, 0x42, 0xe8, 0xc6, 0x8e, 0xa9, 0x9a, 0x73, 0xe2, 0x59, 0xd1, + 0x17, 0xd8, 0xed, 0xcb, 0x72, 0x8c, 0xbf, 0x3b, 0xa9, 0x88, 0x02, 0xd8, 0x45, 0x4b, + 0xd0, 0x2d, 0xe5, 0xe4, 0x1c, 0xc3, 0xd7, 0x00, 0x03, 0x3c, 0x86, 0x20, 0x9a, 0x42, + 0x5f, 0x55, 0x96, 0x3b, 0x9f, 0x6f, 0x79, 0xef, 0xcb, 0x37 + ] + ) + } + + #[test] + #[allow(non_snake_case)] + fn test_get_Ke_and_cAcB() { + for t in RFC_T { + let mut Ke: [u8; 16] = [0; 16]; + let mut cA: [u8; 32] = [0; 32]; + let mut cB: [u8; 32] = [0; 32]; + let mut TT_hash = [0u8; crypto::SHA256_HASH_LEN_BYTES]; + let mut h = crypto::Sha256::new().unwrap(); + h.update(&t.TT[0..t.TT_len]).unwrap(); + h.finish(&mut TT_hash).unwrap(); + Spake2P::get_Ke_and_cAcB(&TT_hash, &t.X, &t.Y, &mut Ke, &mut cA, &mut cB).unwrap(); + assert_eq!(Ke, t.Ke); + assert_eq!(cA, t.cA); + assert_eq!(cB, t.cB); + } + } +} diff --git a/matter/src/secure_channel/spake2p_test_vectors.rs b/matter/src/secure_channel/spake2p_test_vectors.rs new file mode 100644 index 0000000..8072086 --- /dev/null +++ b/matter/src/secure_channel/spake2p_test_vectors.rs @@ -0,0 +1,497 @@ +/* + * + * 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. + */ + +#[cfg(test)] +pub mod test_vectors { + // Based on vectors used in the RFC + #[allow(non_snake_case)] + pub struct RFCTestVector { + pub w0: [u8; 32], + pub w1: [u8; 32], + pub x: [u8; 32], + pub X: [u8; 65], + pub y: [u8; 32], + pub Y: [u8; 65], + pub Z: [u8; 65], + pub V: [u8; 65], + pub L: [u8; 65], + pub cA: [u8; 32], + pub cB: [u8; 32], + pub Ke: [u8; 16], + pub TT: [u8; 547], + // The TT size changes, as they change the identifiers, address it through this + pub TT_len: usize, + } + pub const RFC_T: [RFCTestVector; 4] = [ + RFCTestVector { + w0: [ + 0xe6, 0x88, 0x7c, 0xf9, 0xbd, 0xfb, 0x75, 0x79, 0xc6, 0x9b, 0xf4, 0x79, 0x28, 0xa8, + 0x45, 0x14, 0xb5, 0xe3, 0x55, 0xac, 0x03, 0x48, 0x63, 0xf7, 0xff, 0xaf, 0x43, 0x90, + 0xe6, 0x7d, 0x79, 0x8c, + ], + w1: [ + 0x24, 0xb5, 0xae, 0x4a, 0xbd, 0xa8, 0x68, 0xec, 0x93, 0x36, 0xff, 0xc3, 0xb7, 0x8e, + 0xe3, 0x1c, 0x57, 0x55, 0xbe, 0xf1, 0x75, 0x92, 0x27, 0xef, 0x53, 0x72, 0xca, 0x13, + 0x9b, 0x94, 0xe5, 0x12, + ], + x: [ + 0x8b, 0x0f, 0x3f, 0x38, 0x39, 0x05, 0xcf, 0x3a, 0x3b, 0xb9, 0x55, 0xef, 0x8f, 0xb6, + 0x2e, 0x24, 0x84, 0x9d, 0xd3, 0x49, 0xa0, 0x5c, 0xa7, 0x9a, 0xaf, 0xb1, 0x80, 0x41, + 0xd3, 0x0c, 0xbd, 0xb6, + ], + X: [ + 0x04, 0xaf, 0x09, 0x98, 0x7a, 0x59, 0x3d, 0x3b, 0xac, 0x86, 0x94, 0xb1, 0x23, 0x83, + 0x94, 0x22, 0xc3, 0xcc, 0x87, 0xe3, 0x7d, 0x6b, 0x41, 0xc1, 0xd6, 0x30, 0xf0, 0x00, + 0xdd, 0x64, 0x98, 0x0e, 0x53, 0x7a, 0xe7, 0x04, 0xbc, 0xed, 0xe0, 0x4e, 0xa3, 0xbe, + 0xc9, 0xb7, 0x47, 0x5b, 0x32, 0xfa, 0x2c, 0xa3, 0xb6, 0x84, 0xbe, 0x14, 0xd1, 0x16, + 0x45, 0xe3, 0x8e, 0xa6, 0x60, 0x9e, 0xb3, 0x9e, 0x7e, + ], + y: [ + 0x2e, 0x08, 0x95, 0xb0, 0xe7, 0x63, 0xd6, 0xd5, 0xa9, 0x56, 0x44, 0x33, 0xe6, 0x4a, + 0xc3, 0xca, 0xc7, 0x4f, 0xf8, 0x97, 0xf6, 0xc3, 0x44, 0x52, 0x47, 0xba, 0x1b, 0xab, + 0x40, 0x08, 0x2a, 0x91, + ], + Y: [ + 0x04, 0x41, 0x75, 0x92, 0x62, 0x0a, 0xeb, 0xf9, 0xfd, 0x20, 0x36, 0x16, 0xbb, 0xb9, + 0xf1, 0x21, 0xb7, 0x30, 0xc2, 0x58, 0xb2, 0x86, 0xf8, 0x90, 0xc5, 0xf1, 0x9f, 0xea, + 0x83, 0x3a, 0x9c, 0x90, 0x0c, 0xbe, 0x90, 0x57, 0xbc, 0x54, 0x9a, 0x3e, 0x19, 0x97, + 0x5b, 0xe9, 0x92, 0x7f, 0x0e, 0x76, 0x14, 0xf0, 0x8d, 0x1f, 0x0a, 0x10, 0x8e, 0xed, + 0xe5, 0xfd, 0x7e, 0xb5, 0x62, 0x45, 0x84, 0xa4, 0xf4, + ], + Z: [ + 0x04, 0x71, 0xa3, 0x52, 0x82, 0xd2, 0x02, 0x6f, 0x36, 0xbf, 0x3c, 0xeb, 0x38, 0xfc, + 0xf8, 0x7e, 0x31, 0x12, 0xa4, 0x45, 0x2f, 0x46, 0xe9, 0xf7, 0xb4, 0x7f, 0xd7, 0x69, + 0xcf, 0xb5, 0x70, 0x14, 0x5b, 0x62, 0x58, 0x9c, 0x76, 0xb7, 0xaa, 0x1e, 0xb6, 0x08, + 0x0a, 0x83, 0x2e, 0x53, 0x32, 0xc3, 0x68, 0x98, 0x42, 0x69, 0x12, 0xe2, 0x9c, 0x40, + 0xef, 0x9e, 0x9c, 0x74, 0x2e, 0xee, 0x82, 0xbf, 0x30, + ], + V: [ + 0x04, 0x67, 0x18, 0x98, 0x1b, 0xf1, 0x5b, 0xc4, 0xdb, 0x53, 0x8f, 0xc1, 0xf1, 0xc1, + 0xd0, 0x58, 0xcb, 0x0e, 0xec, 0xec, 0xf1, 0xdb, 0xe1, 0xb1, 0xea, 0x08, 0xa4, 0xe2, + 0x52, 0x75, 0xd3, 0x82, 0xe8, 0x2b, 0x34, 0x8c, 0x81, 0x31, 0xd8, 0xed, 0x66, 0x9d, + 0x16, 0x9c, 0x2e, 0x03, 0xa8, 0x58, 0xdb, 0x7c, 0xf6, 0xca, 0x28, 0x53, 0xa4, 0x07, + 0x12, 0x51, 0xa3, 0x9f, 0xbe, 0x8c, 0xfc, 0x39, 0xbc, + ], + L: [ + 0x04, 0x95, 0x64, 0x5c, 0xfb, 0x74, 0xdf, 0x6e, 0x58, 0xf9, 0x74, 0x8b, 0xb8, 0x3a, + 0x86, 0x62, 0x0b, 0xab, 0x7c, 0x82, 0xe1, 0x07, 0xf5, 0x7d, 0x68, 0x70, 0xda, 0x8c, + 0xbc, 0xb2, 0xff, 0x9f, 0x70, 0x63, 0xa1, 0x4b, 0x64, 0x02, 0xc6, 0x2f, 0x99, 0xaf, + 0xcb, 0x97, 0x06, 0xa4, 0xd1, 0xa1, 0x43, 0x27, 0x32, 0x59, 0xfe, 0x76, 0xf1, 0xc6, + 0x05, 0xa3, 0x63, 0x97, 0x45, 0xa9, 0x21, 0x54, 0xb9, + ], + cA: [ + 0xd4, 0x37, 0x6f, 0x2d, 0xa9, 0xc7, 0x22, 0x26, 0xdd, 0x15, 0x1b, 0x77, 0xc2, 0x91, + 0x90, 0x71, 0x15, 0x5f, 0xc2, 0x2a, 0x20, 0x68, 0xd9, 0x0b, 0x5f, 0xaa, 0x6c, 0x78, + 0xc1, 0x1e, 0x77, 0xdd, + ], + cB: [ + 0x06, 0x60, 0xa6, 0x80, 0x66, 0x3e, 0x8c, 0x56, 0x95, 0x95, 0x6f, 0xb2, 0x2d, 0xff, + 0x29, 0x8b, 0x1d, 0x07, 0xa5, 0x26, 0xcf, 0x3c, 0xc5, 0x91, 0xad, 0xfe, 0xcd, 0x1f, + 0x6e, 0xf6, 0xe0, 0x2e, + ], + Ke: [ + 0x80, 0x1d, 0xb2, 0x97, 0x65, 0x48, 0x16, 0xeb, 0x4f, 0x02, 0x86, 0x81, 0x29, 0xb9, + 0xdc, 0x89, + ], + TT: [ + 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x50, 0x41, 0x4b, 0x45, 0x32, + 0x2b, 0x2d, 0x50, 0x32, 0x35, 0x36, 0x2d, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x2d, + 0x48, 0x4b, 0x44, 0x46, 0x20, 0x64, 0x72, 0x61, 0x66, 0x74, 0x2d, 0x30, 0x31, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x88, 0x6e, 0x2f, 0x97, 0xac, 0xe4, + 0x6e, 0x55, 0xba, 0x9d, 0xd7, 0x24, 0x25, 0x79, 0xf2, 0x99, 0x3b, 0x64, 0xe1, 0x6e, + 0xf3, 0xdc, 0xab, 0x95, 0xaf, 0xd4, 0x97, 0x33, 0x3d, 0x8f, 0xa1, 0x2f, 0x5f, 0xf3, + 0x55, 0x16, 0x3e, 0x43, 0xce, 0x22, 0x4e, 0x0b, 0x0e, 0x65, 0xff, 0x02, 0xac, 0x8e, + 0x5c, 0x7b, 0xe0, 0x94, 0x19, 0xc7, 0x85, 0xe0, 0xca, 0x54, 0x7d, 0x55, 0xa1, 0x2e, + 0x2d, 0x20, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xd8, 0xbb, 0xd6, + 0xc6, 0x39, 0xc6, 0x29, 0x37, 0xb0, 0x4d, 0x99, 0x7f, 0x38, 0xc3, 0x77, 0x07, 0x19, + 0xc6, 0x29, 0xd7, 0x01, 0x4d, 0x49, 0xa2, 0x4b, 0x4f, 0x98, 0xba, 0xa1, 0x29, 0x2b, + 0x49, 0x07, 0xd6, 0x0a, 0xa6, 0xbf, 0xad, 0xe4, 0x50, 0x08, 0xa6, 0x36, 0x33, 0x7f, + 0x51, 0x68, 0xc6, 0x4d, 0x9b, 0xd3, 0x60, 0x34, 0x80, 0x8c, 0xd5, 0x64, 0x49, 0x0b, + 0x1e, 0x65, 0x6e, 0xdb, 0xe7, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, + 0xaf, 0x09, 0x98, 0x7a, 0x59, 0x3d, 0x3b, 0xac, 0x86, 0x94, 0xb1, 0x23, 0x83, 0x94, + 0x22, 0xc3, 0xcc, 0x87, 0xe3, 0x7d, 0x6b, 0x41, 0xc1, 0xd6, 0x30, 0xf0, 0x00, 0xdd, + 0x64, 0x98, 0x0e, 0x53, 0x7a, 0xe7, 0x04, 0xbc, 0xed, 0xe0, 0x4e, 0xa3, 0xbe, 0xc9, + 0xb7, 0x47, 0x5b, 0x32, 0xfa, 0x2c, 0xa3, 0xb6, 0x84, 0xbe, 0x14, 0xd1, 0x16, 0x45, + 0xe3, 0x8e, 0xa6, 0x60, 0x9e, 0xb3, 0x9e, 0x7e, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x41, 0x75, 0x92, 0x62, 0x0a, 0xeb, 0xf9, 0xfd, 0x20, 0x36, 0x16, + 0xbb, 0xb9, 0xf1, 0x21, 0xb7, 0x30, 0xc2, 0x58, 0xb2, 0x86, 0xf8, 0x90, 0xc5, 0xf1, + 0x9f, 0xea, 0x83, 0x3a, 0x9c, 0x90, 0x0c, 0xbe, 0x90, 0x57, 0xbc, 0x54, 0x9a, 0x3e, + 0x19, 0x97, 0x5b, 0xe9, 0x92, 0x7f, 0x0e, 0x76, 0x14, 0xf0, 0x8d, 0x1f, 0x0a, 0x10, + 0x8e, 0xed, 0xe5, 0xfd, 0x7e, 0xb5, 0x62, 0x45, 0x84, 0xa4, 0xf4, 0x41, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x71, 0xa3, 0x52, 0x82, 0xd2, 0x02, 0x6f, 0x36, + 0xbf, 0x3c, 0xeb, 0x38, 0xfc, 0xf8, 0x7e, 0x31, 0x12, 0xa4, 0x45, 0x2f, 0x46, 0xe9, + 0xf7, 0xb4, 0x7f, 0xd7, 0x69, 0xcf, 0xb5, 0x70, 0x14, 0x5b, 0x62, 0x58, 0x9c, 0x76, + 0xb7, 0xaa, 0x1e, 0xb6, 0x08, 0x0a, 0x83, 0x2e, 0x53, 0x32, 0xc3, 0x68, 0x98, 0x42, + 0x69, 0x12, 0xe2, 0x9c, 0x40, 0xef, 0x9e, 0x9c, 0x74, 0x2e, 0xee, 0x82, 0xbf, 0x30, + 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x67, 0x18, 0x98, 0x1b, 0xf1, + 0x5b, 0xc4, 0xdb, 0x53, 0x8f, 0xc1, 0xf1, 0xc1, 0xd0, 0x58, 0xcb, 0x0e, 0xec, 0xec, + 0xf1, 0xdb, 0xe1, 0xb1, 0xea, 0x08, 0xa4, 0xe2, 0x52, 0x75, 0xd3, 0x82, 0xe8, 0x2b, + 0x34, 0x8c, 0x81, 0x31, 0xd8, 0xed, 0x66, 0x9d, 0x16, 0x9c, 0x2e, 0x03, 0xa8, 0x58, + 0xdb, 0x7c, 0xf6, 0xca, 0x28, 0x53, 0xa4, 0x07, 0x12, 0x51, 0xa3, 0x9f, 0xbe, 0x8c, + 0xfc, 0x39, 0xbc, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe6, 0x88, 0x7c, + 0xf9, 0xbd, 0xfb, 0x75, 0x79, 0xc6, 0x9b, 0xf4, 0x79, 0x28, 0xa8, 0x45, 0x14, 0xb5, + 0xe3, 0x55, 0xac, 0x03, 0x48, 0x63, 0xf7, 0xff, 0xaf, 0x43, 0x90, 0xe6, 0x7d, 0x79, + 0x8c, + ], + TT_len: 547, + }, + RFCTestVector { + w0: [ + 0xe6, 0x88, 0x7c, 0xf9, 0xbd, 0xfb, 0x75, 0x79, 0xc6, 0x9b, 0xf4, 0x79, 0x28, 0xa8, + 0x45, 0x14, 0xb5, 0xe3, 0x55, 0xac, 0x03, 0x48, 0x63, 0xf7, 0xff, 0xaf, 0x43, 0x90, + 0xe6, 0x7d, 0x79, 0x8c, + ], + w1: [ + 0x24, 0xb5, 0xae, 0x4a, 0xbd, 0xa8, 0x68, 0xec, 0x93, 0x36, 0xff, 0xc3, 0xb7, 0x8e, + 0xe3, 0x1c, 0x57, 0x55, 0xbe, 0xf1, 0x75, 0x92, 0x27, 0xef, 0x53, 0x72, 0xca, 0x13, + 0x9b, 0x94, 0xe5, 0x12, + ], + x: [ + 0xec, 0x82, 0xd9, 0x25, 0x83, 0x37, 0xf6, 0x12, 0x39, 0xc9, 0xcd, 0x68, 0xe8, 0xe5, + 0x32, 0xa3, 0xa6, 0xb8, 0x3d, 0x12, 0xd2, 0xb1, 0xca, 0x5d, 0x54, 0x3f, 0x44, 0xde, + 0xf1, 0x7d, 0xfb, 0x8d, + ], + X: [ + 0x04, 0x23, 0x07, 0x79, 0x96, 0x08, 0x24, 0x07, 0x6d, 0x36, 0x66, 0xa7, 0x41, 0x8e, + 0x4d, 0x43, 0x3e, 0x2f, 0xa1, 0x5b, 0x06, 0x17, 0x6e, 0xab, 0xdd, 0x57, 0x2f, 0x43, + 0xa3, 0x2e, 0xcc, 0x79, 0xa1, 0x92, 0xb2, 0x43, 0xd2, 0x62, 0x43, 0x10, 0xa7, 0x35, + 0x62, 0x73, 0xb8, 0x6e, 0x5f, 0xd9, 0xbd, 0x62, 0x7d, 0x3a, 0xde, 0x76, 0x2b, 0xae, + 0xff, 0x1a, 0x32, 0x0d, 0x4a, 0xd7, 0xa4, 0xe4, 0x7f, + ], + y: [ + 0xea, 0xc3, 0xf7, 0xde, 0x4b, 0x19, 0x8d, 0x5f, 0xe2, 0x5c, 0x44, 0x3c, 0x0c, 0xd4, + 0x96, 0x38, 0x07, 0xad, 0xd7, 0x67, 0x81, 0x5d, 0xd0, 0x2a, 0x6f, 0x01, 0x33, 0xb4, + 0xbc, 0x2c, 0x9e, 0xb0, + ], + Y: [ + 0x04, 0x45, 0x58, 0x64, 0x2e, 0x71, 0xb6, 0x16, 0xb2, 0x48, 0xc9, 0x58, 0x3b, 0xd6, + 0xd7, 0xaa, 0x1b, 0x39, 0x52, 0xc6, 0xdf, 0x6a, 0x9f, 0x74, 0x92, 0xa0, 0x60, 0x35, + 0xca, 0x5d, 0x92, 0x52, 0x2d, 0x84, 0x44, 0x3d, 0xe7, 0xaa, 0x20, 0xa5, 0x93, 0x80, + 0xfa, 0x4d, 0xe6, 0xb7, 0x43, 0x8d, 0x92, 0x5d, 0xbf, 0xb7, 0xf1, 0xcf, 0xe6, 0x0d, + 0x79, 0xac, 0xf9, 0x61, 0xee, 0x33, 0x98, 0x8c, 0x7d, + ], + Z: [ + 0x04, 0xb4, 0xe8, 0x77, 0x0f, 0x19, 0xf5, 0x8d, 0xdf, 0x83, 0xf9, 0x22, 0x0c, 0x3a, + 0x93, 0x05, 0x79, 0x26, 0x65, 0xe0, 0xc6, 0x09, 0x89, 0xe6, 0xee, 0x9d, 0x7f, 0xa4, + 0x49, 0xc7, 0x75, 0xd6, 0x39, 0x5f, 0x6f, 0x25, 0xf3, 0x07, 0xe3, 0x90, 0x3a, 0xc0, + 0x45, 0xa0, 0x13, 0xfb, 0xb5, 0xa6, 0x76, 0xe8, 0x72, 0xa6, 0xab, 0xfc, 0xf4, 0xd7, + 0xbb, 0x5a, 0xac, 0x69, 0xef, 0xd6, 0x14, 0x0e, 0xed, + ], + V: [ + 0x04, 0x14, 0x1d, 0xb8, 0x3b, 0xc7, 0xd9, 0x6f, 0x41, 0xb6, 0x36, 0x62, 0x2e, 0x7a, + 0x5c, 0x55, 0x2a, 0xd8, 0x32, 0x11, 0xff, 0x55, 0x31, 0x9a, 0xc2, 0x5e, 0xd0, 0xa0, + 0x9f, 0x08, 0x18, 0xbd, 0x94, 0x2e, 0x81, 0x50, 0x31, 0x9b, 0xfb, 0xfa, 0x68, 0x61, + 0x83, 0x80, 0x6d, 0xc6, 0x19, 0x11, 0x18, 0x3f, 0x6a, 0x0f, 0x59, 0x56, 0x15, 0x60, + 0x23, 0xd9, 0x6e, 0x0f, 0x93, 0xd2, 0x75, 0xbf, 0x50, + ], + L: [ + 0x04, 0x95, 0x64, 0x5c, 0xfb, 0x74, 0xdf, 0x6e, 0x58, 0xf9, 0x74, 0x8b, 0xb8, 0x3a, + 0x86, 0x62, 0x0b, 0xab, 0x7c, 0x82, 0xe1, 0x07, 0xf5, 0x7d, 0x68, 0x70, 0xda, 0x8c, + 0xbc, 0xb2, 0xff, 0x9f, 0x70, 0x63, 0xa1, 0x4b, 0x64, 0x02, 0xc6, 0x2f, 0x99, 0xaf, + 0xcb, 0x97, 0x06, 0xa4, 0xd1, 0xa1, 0x43, 0x27, 0x32, 0x59, 0xfe, 0x76, 0xf1, 0xc6, + 0x05, 0xa3, 0x63, 0x97, 0x45, 0xa9, 0x21, 0x54, 0xb9, + ], + cA: [ + 0xe1, 0xb9, 0x25, 0x88, 0x07, 0xba, 0x47, 0x50, 0xda, 0xe1, 0xd7, 0xf3, 0xc3, 0xc2, + 0x94, 0xf1, 0x3d, 0xc4, 0xfa, 0x60, 0xcd, 0xe3, 0x46, 0xd5, 0xde, 0x7d, 0x20, 0x0e, + 0x2f, 0x8f, 0xd3, 0xfc, + ], + cB: [ + 0xb9, 0xc3, 0x9d, 0xfa, 0x49, 0xc4, 0x77, 0x57, 0xde, 0x77, 0x8d, 0x9b, 0xed, 0xea, + 0xca, 0x24, 0x48, 0xb9, 0x05, 0xbe, 0x19, 0xa4, 0x3b, 0x94, 0xee, 0x24, 0xb7, 0x70, + 0x20, 0x81, 0x35, 0xe3, + ], + Ke: [ + 0x69, 0x89, 0xd8, 0xf9, 0x17, 0x7e, 0xf7, 0xdf, 0x67, 0xda, 0x43, 0x79, 0x87, 0xf0, + 0x72, 0x55, + ], + TT: [ + 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x50, 0x41, 0x4b, 0x45, 0x32, + 0x2b, 0x2d, 0x50, 0x32, 0x35, 0x36, 0x2d, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x2d, + 0x48, 0x4b, 0x44, 0x46, 0x20, 0x64, 0x72, 0x61, 0x66, 0x74, 0x2d, 0x30, 0x31, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x88, 0x6e, 0x2f, 0x97, 0xac, 0xe4, 0x6e, 0x55, 0xba, 0x9d, 0xd7, 0x24, + 0x25, 0x79, 0xf2, 0x99, 0x3b, 0x64, 0xe1, 0x6e, 0xf3, 0xdc, 0xab, 0x95, 0xaf, 0xd4, + 0x97, 0x33, 0x3d, 0x8f, 0xa1, 0x2f, 0x5f, 0xf3, 0x55, 0x16, 0x3e, 0x43, 0xce, 0x22, + 0x4e, 0x0b, 0x0e, 0x65, 0xff, 0x02, 0xac, 0x8e, 0x5c, 0x7b, 0xe0, 0x94, 0x19, 0xc7, + 0x85, 0xe0, 0xca, 0x54, 0x7d, 0x55, 0xa1, 0x2e, 0x2d, 0x20, 0x41, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0xd8, 0xbb, 0xd6, 0xc6, 0x39, 0xc6, 0x29, 0x37, 0xb0, + 0x4d, 0x99, 0x7f, 0x38, 0xc3, 0x77, 0x07, 0x19, 0xc6, 0x29, 0xd7, 0x01, 0x4d, 0x49, + 0xa2, 0x4b, 0x4f, 0x98, 0xba, 0xa1, 0x29, 0x2b, 0x49, 0x07, 0xd6, 0x0a, 0xa6, 0xbf, + 0xad, 0xe4, 0x50, 0x08, 0xa6, 0x36, 0x33, 0x7f, 0x51, 0x68, 0xc6, 0x4d, 0x9b, 0xd3, + 0x60, 0x34, 0x80, 0x8c, 0xd5, 0x64, 0x49, 0x0b, 0x1e, 0x65, 0x6e, 0xdb, 0xe7, 0x41, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x23, 0x07, 0x79, 0x96, 0x08, 0x24, + 0x07, 0x6d, 0x36, 0x66, 0xa7, 0x41, 0x8e, 0x4d, 0x43, 0x3e, 0x2f, 0xa1, 0x5b, 0x06, + 0x17, 0x6e, 0xab, 0xdd, 0x57, 0x2f, 0x43, 0xa3, 0x2e, 0xcc, 0x79, 0xa1, 0x92, 0xb2, + 0x43, 0xd2, 0x62, 0x43, 0x10, 0xa7, 0x35, 0x62, 0x73, 0xb8, 0x6e, 0x5f, 0xd9, 0xbd, + 0x62, 0x7d, 0x3a, 0xde, 0x76, 0x2b, 0xae, 0xff, 0x1a, 0x32, 0x0d, 0x4a, 0xd7, 0xa4, + 0xe4, 0x7f, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x45, 0x58, 0x64, + 0x2e, 0x71, 0xb6, 0x16, 0xb2, 0x48, 0xc9, 0x58, 0x3b, 0xd6, 0xd7, 0xaa, 0x1b, 0x39, + 0x52, 0xc6, 0xdf, 0x6a, 0x9f, 0x74, 0x92, 0xa0, 0x60, 0x35, 0xca, 0x5d, 0x92, 0x52, + 0x2d, 0x84, 0x44, 0x3d, 0xe7, 0xaa, 0x20, 0xa5, 0x93, 0x80, 0xfa, 0x4d, 0xe6, 0xb7, + 0x43, 0x8d, 0x92, 0x5d, 0xbf, 0xb7, 0xf1, 0xcf, 0xe6, 0x0d, 0x79, 0xac, 0xf9, 0x61, + 0xee, 0x33, 0x98, 0x8c, 0x7d, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, + 0xb4, 0xe8, 0x77, 0x0f, 0x19, 0xf5, 0x8d, 0xdf, 0x83, 0xf9, 0x22, 0x0c, 0x3a, 0x93, + 0x05, 0x79, 0x26, 0x65, 0xe0, 0xc6, 0x09, 0x89, 0xe6, 0xee, 0x9d, 0x7f, 0xa4, 0x49, + 0xc7, 0x75, 0xd6, 0x39, 0x5f, 0x6f, 0x25, 0xf3, 0x07, 0xe3, 0x90, 0x3a, 0xc0, 0x45, + 0xa0, 0x13, 0xfb, 0xb5, 0xa6, 0x76, 0xe8, 0x72, 0xa6, 0xab, 0xfc, 0xf4, 0xd7, 0xbb, + 0x5a, 0xac, 0x69, 0xef, 0xd6, 0x14, 0x0e, 0xed, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x14, 0x1d, 0xb8, 0x3b, 0xc7, 0xd9, 0x6f, 0x41, 0xb6, 0x36, 0x62, + 0x2e, 0x7a, 0x5c, 0x55, 0x2a, 0xd8, 0x32, 0x11, 0xff, 0x55, 0x31, 0x9a, 0xc2, 0x5e, + 0xd0, 0xa0, 0x9f, 0x08, 0x18, 0xbd, 0x94, 0x2e, 0x81, 0x50, 0x31, 0x9b, 0xfb, 0xfa, + 0x68, 0x61, 0x83, 0x80, 0x6d, 0xc6, 0x19, 0x11, 0x18, 0x3f, 0x6a, 0x0f, 0x59, 0x56, + 0x15, 0x60, 0x23, 0xd9, 0x6e, 0x0f, 0x93, 0xd2, 0x75, 0xbf, 0x50, 0x20, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xe6, 0x88, 0x7c, 0xf9, 0xbd, 0xfb, 0x75, 0x79, 0xc6, + 0x9b, 0xf4, 0x79, 0x28, 0xa8, 0x45, 0x14, 0xb5, 0xe3, 0x55, 0xac, 0x03, 0x48, 0x63, + 0xf7, 0xff, 0xaf, 0x43, 0x90, 0xe6, 0x7d, 0x79, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, + ], + TT_len: 541, + }, + RFCTestVector { + w0: [ + 0xe6, 0x88, 0x7c, 0xf9, 0xbd, 0xfb, 0x75, 0x79, 0xc6, 0x9b, 0xf4, 0x79, 0x28, 0xa8, + 0x45, 0x14, 0xb5, 0xe3, 0x55, 0xac, 0x03, 0x48, 0x63, 0xf7, 0xff, 0xaf, 0x43, 0x90, + 0xe6, 0x7d, 0x79, 0x8c, + ], + w1: [ + 0x24, 0xb5, 0xae, 0x4a, 0xbd, 0xa8, 0x68, 0xec, 0x93, 0x36, 0xff, 0xc3, 0xb7, 0x8e, + 0xe3, 0x1c, 0x57, 0x55, 0xbe, 0xf1, 0x75, 0x92, 0x27, 0xef, 0x53, 0x72, 0xca, 0x13, + 0x9b, 0x94, 0xe5, 0x12, + ], + x: [ + 0xba, 0x0f, 0x0f, 0x5b, 0x78, 0xef, 0x23, 0xfd, 0x07, 0x86, 0x8e, 0x46, 0xae, 0xca, + 0x63, 0xb5, 0x1f, 0xda, 0x51, 0x9a, 0x34, 0x20, 0x50, 0x1a, 0xcb, 0xe2, 0x3d, 0x53, + 0xc2, 0x91, 0x87, 0x48, + ], + X: [ + 0x04, 0xc1, 0x4d, 0x28, 0xf4, 0x37, 0x0f, 0xea, 0x20, 0x74, 0x51, 0x06, 0xce, 0xa5, + 0x8b, 0xcf, 0xb6, 0x0f, 0x29, 0x49, 0xfa, 0x4e, 0x13, 0x1b, 0x9a, 0xff, 0x5e, 0xa1, + 0x3f, 0xd5, 0xaa, 0x79, 0xd5, 0x07, 0xae, 0x1d, 0x22, 0x9e, 0x44, 0x7e, 0x00, 0x0f, + 0x15, 0xeb, 0x78, 0xa9, 0xa3, 0x2c, 0x2b, 0x88, 0x65, 0x2e, 0x34, 0x11, 0x64, 0x20, + 0x43, 0xc1, 0xb2, 0xb7, 0x99, 0x2c, 0xf2, 0xd4, 0xde, + ], + y: [ + 0x39, 0x39, 0x7f, 0xbe, 0x6d, 0xb4, 0x7e, 0x9f, 0xbd, 0x1a, 0x26, 0x3d, 0x79, 0xf5, + 0xd0, 0xaa, 0xa4, 0x4d, 0xf2, 0x6c, 0xe7, 0x55, 0xf7, 0x8e, 0x09, 0x26, 0x44, 0xb4, + 0x34, 0x53, 0x3a, 0x42, + ], + Y: [ + 0x04, 0xd1, 0xbe, 0xe3, 0x12, 0x0f, 0xd8, 0x7e, 0x86, 0xfe, 0x18, 0x9c, 0xb9, 0x52, + 0xdc, 0x68, 0x88, 0x23, 0x08, 0x0e, 0x62, 0x52, 0x4d, 0xd2, 0xc0, 0x8d, 0xff, 0xe3, + 0xd2, 0x2a, 0x0a, 0x89, 0x86, 0xaa, 0x64, 0xc9, 0xfe, 0x01, 0x91, 0x03, 0x3c, 0xaf, + 0xbc, 0x9b, 0xca, 0xef, 0xc8, 0xe2, 0xba, 0x8b, 0xa8, 0x60, 0xcd, 0x12, 0x7a, 0xf9, + 0xef, 0xdd, 0x7f, 0x1c, 0x3a, 0x41, 0x92, 0x0f, 0xe8, + ], + Z: [ + 0x04, 0xaa, 0xc7, 0x1c, 0xf4, 0xc8, 0xdf, 0x81, 0x81, 0xb8, 0x67, 0xc9, 0xec, 0xbe, + 0xe9, 0xd0, 0x96, 0x3c, 0xaf, 0x51, 0xf1, 0x53, 0x4a, 0x82, 0x34, 0x29, 0xc2, 0x6f, + 0xe5, 0x24, 0x83, 0x13, 0xff, 0xc5, 0xc5, 0xe4, 0x4e, 0xa8, 0x16, 0x21, 0x61, 0xab, + 0x6b, 0x3d, 0x73, 0xb8, 0x77, 0x04, 0xa4, 0x58, 0x89, 0xbf, 0x63, 0x43, 0xd9, 0x6f, + 0xa9, 0x6c, 0xd1, 0x64, 0x1e, 0xfa, 0x71, 0x60, 0x7c, + ], + V: [ + 0x04, 0xc7, 0xc9, 0x50, 0x53, 0x65, 0xf7, 0xce, 0x57, 0x29, 0x3c, 0x92, 0xa3, 0x7f, + 0x1b, 0xbd, 0xc6, 0x8e, 0x03, 0x22, 0x90, 0x1e, 0x61, 0xed, 0xef, 0x59, 0xfe, 0xe7, + 0x87, 0x6b, 0x17, 0xb0, 0x63, 0xe0, 0xfa, 0x4a, 0x12, 0x6e, 0xae, 0x0a, 0x67, 0x1b, + 0x37, 0xf1, 0x46, 0x4c, 0xf1, 0xcc, 0xad, 0x59, 0x1c, 0x33, 0xae, 0x94, 0x4e, 0x3b, + 0x1f, 0x31, 0x8d, 0x76, 0xe3, 0x6f, 0xea, 0x99, 0x66, + ], + L: [ + 0x04, 0x95, 0x64, 0x5c, 0xfb, 0x74, 0xdf, 0x6e, 0x58, 0xf9, 0x74, 0x8b, 0xb8, 0x3a, + 0x86, 0x62, 0x0b, 0xab, 0x7c, 0x82, 0xe1, 0x07, 0xf5, 0x7d, 0x68, 0x70, 0xda, 0x8c, + 0xbc, 0xb2, 0xff, 0x9f, 0x70, 0x63, 0xa1, 0x4b, 0x64, 0x02, 0xc6, 0x2f, 0x99, 0xaf, + 0xcb, 0x97, 0x06, 0xa4, 0xd1, 0xa1, 0x43, 0x27, 0x32, 0x59, 0xfe, 0x76, 0xf1, 0xc6, + 0x05, 0xa3, 0x63, 0x97, 0x45, 0xa9, 0x21, 0x54, 0xb9, + ], + cA: [ + 0xe5, 0x64, 0xc9, 0x3b, 0x30, 0x15, 0xef, 0xb9, 0x46, 0xdc, 0x16, 0xd6, 0x42, 0xbb, + 0xe7, 0xd1, 0xc8, 0xda, 0x5b, 0xe1, 0x64, 0xed, 0x9f, 0xc3, 0xba, 0xe4, 0xe0, 0xff, + 0x86, 0xe1, 0xbd, 0x3c, + ], + cB: [ + 0x07, 0x2a, 0x94, 0xd9, 0xa5, 0x4e, 0xdc, 0x20, 0x1d, 0x88, 0x91, 0x53, 0x4c, 0x23, + 0x17, 0xca, 0xdf, 0x3e, 0xa3, 0x79, 0x28, 0x27, 0xf4, 0x79, 0xe8, 0x73, 0xf9, 0x3e, + 0x90, 0xf2, 0x15, 0x52, + ], + Ke: [ + 0x2e, 0xa4, 0x0e, 0x4b, 0xad, 0xfa, 0x54, 0x52, 0xb5, 0x74, 0x4d, 0xc5, 0x98, 0x3e, + 0x99, 0xba, + ], + TT: [ + 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x50, 0x41, 0x4b, 0x45, 0x32, + 0x2b, 0x2d, 0x50, 0x32, 0x35, 0x36, 0x2d, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x2d, + 0x48, 0x4b, 0x44, 0x46, 0x20, 0x64, 0x72, 0x61, 0x66, 0x74, 0x2d, 0x30, 0x31, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x88, 0x6e, 0x2f, 0x97, 0xac, 0xe4, 0x6e, 0x55, 0xba, 0x9d, 0xd7, 0x24, + 0x25, 0x79, 0xf2, 0x99, 0x3b, 0x64, 0xe1, 0x6e, 0xf3, 0xdc, 0xab, 0x95, 0xaf, 0xd4, + 0x97, 0x33, 0x3d, 0x8f, 0xa1, 0x2f, 0x5f, 0xf3, 0x55, 0x16, 0x3e, 0x43, 0xce, 0x22, + 0x4e, 0x0b, 0x0e, 0x65, 0xff, 0x02, 0xac, 0x8e, 0x5c, 0x7b, 0xe0, 0x94, 0x19, 0xc7, + 0x85, 0xe0, 0xca, 0x54, 0x7d, 0x55, 0xa1, 0x2e, 0x2d, 0x20, 0x41, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0xd8, 0xbb, 0xd6, 0xc6, 0x39, 0xc6, 0x29, 0x37, 0xb0, + 0x4d, 0x99, 0x7f, 0x38, 0xc3, 0x77, 0x07, 0x19, 0xc6, 0x29, 0xd7, 0x01, 0x4d, 0x49, + 0xa2, 0x4b, 0x4f, 0x98, 0xba, 0xa1, 0x29, 0x2b, 0x49, 0x07, 0xd6, 0x0a, 0xa6, 0xbf, + 0xad, 0xe4, 0x50, 0x08, 0xa6, 0x36, 0x33, 0x7f, 0x51, 0x68, 0xc6, 0x4d, 0x9b, 0xd3, + 0x60, 0x34, 0x80, 0x8c, 0xd5, 0x64, 0x49, 0x0b, 0x1e, 0x65, 0x6e, 0xdb, 0xe7, 0x41, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xc1, 0x4d, 0x28, 0xf4, 0x37, 0x0f, + 0xea, 0x20, 0x74, 0x51, 0x06, 0xce, 0xa5, 0x8b, 0xcf, 0xb6, 0x0f, 0x29, 0x49, 0xfa, + 0x4e, 0x13, 0x1b, 0x9a, 0xff, 0x5e, 0xa1, 0x3f, 0xd5, 0xaa, 0x79, 0xd5, 0x07, 0xae, + 0x1d, 0x22, 0x9e, 0x44, 0x7e, 0x00, 0x0f, 0x15, 0xeb, 0x78, 0xa9, 0xa3, 0x2c, 0x2b, + 0x88, 0x65, 0x2e, 0x34, 0x11, 0x64, 0x20, 0x43, 0xc1, 0xb2, 0xb7, 0x99, 0x2c, 0xf2, + 0xd4, 0xde, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xd1, 0xbe, 0xe3, + 0x12, 0x0f, 0xd8, 0x7e, 0x86, 0xfe, 0x18, 0x9c, 0xb9, 0x52, 0xdc, 0x68, 0x88, 0x23, + 0x08, 0x0e, 0x62, 0x52, 0x4d, 0xd2, 0xc0, 0x8d, 0xff, 0xe3, 0xd2, 0x2a, 0x0a, 0x89, + 0x86, 0xaa, 0x64, 0xc9, 0xfe, 0x01, 0x91, 0x03, 0x3c, 0xaf, 0xbc, 0x9b, 0xca, 0xef, + 0xc8, 0xe2, 0xba, 0x8b, 0xa8, 0x60, 0xcd, 0x12, 0x7a, 0xf9, 0xef, 0xdd, 0x7f, 0x1c, + 0x3a, 0x41, 0x92, 0x0f, 0xe8, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, + 0xaa, 0xc7, 0x1c, 0xf4, 0xc8, 0xdf, 0x81, 0x81, 0xb8, 0x67, 0xc9, 0xec, 0xbe, 0xe9, + 0xd0, 0x96, 0x3c, 0xaf, 0x51, 0xf1, 0x53, 0x4a, 0x82, 0x34, 0x29, 0xc2, 0x6f, 0xe5, + 0x24, 0x83, 0x13, 0xff, 0xc5, 0xc5, 0xe4, 0x4e, 0xa8, 0x16, 0x21, 0x61, 0xab, 0x6b, + 0x3d, 0x73, 0xb8, 0x77, 0x04, 0xa4, 0x58, 0x89, 0xbf, 0x63, 0x43, 0xd9, 0x6f, 0xa9, + 0x6c, 0xd1, 0x64, 0x1e, 0xfa, 0x71, 0x60, 0x7c, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0xc7, 0xc9, 0x50, 0x53, 0x65, 0xf7, 0xce, 0x57, 0x29, 0x3c, 0x92, + 0xa3, 0x7f, 0x1b, 0xbd, 0xc6, 0x8e, 0x03, 0x22, 0x90, 0x1e, 0x61, 0xed, 0xef, 0x59, + 0xfe, 0xe7, 0x87, 0x6b, 0x17, 0xb0, 0x63, 0xe0, 0xfa, 0x4a, 0x12, 0x6e, 0xae, 0x0a, + 0x67, 0x1b, 0x37, 0xf1, 0x46, 0x4c, 0xf1, 0xcc, 0xad, 0x59, 0x1c, 0x33, 0xae, 0x94, + 0x4e, 0x3b, 0x1f, 0x31, 0x8d, 0x76, 0xe3, 0x6f, 0xea, 0x99, 0x66, 0x20, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xe6, 0x88, 0x7c, 0xf9, 0xbd, 0xfb, 0x75, 0x79, 0xc6, + 0x9b, 0xf4, 0x79, 0x28, 0xa8, 0x45, 0x14, 0xb5, 0xe3, 0x55, 0xac, 0x03, 0x48, 0x63, + 0xf7, 0xff, 0xaf, 0x43, 0x90, 0xe6, 0x7d, 0x79, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, + ], + TT_len: 541, + }, + RFCTestVector { + w0: [ + 0xe6, 0x88, 0x7c, 0xf9, 0xbd, 0xfb, 0x75, 0x79, 0xc6, 0x9b, 0xf4, 0x79, 0x28, 0xa8, + 0x45, 0x14, 0xb5, 0xe3, 0x55, 0xac, 0x03, 0x48, 0x63, 0xf7, 0xff, 0xaf, 0x43, 0x90, + 0xe6, 0x7d, 0x79, 0x8c, + ], + w1: [ + 0x24, 0xb5, 0xae, 0x4a, 0xbd, 0xa8, 0x68, 0xec, 0x93, 0x36, 0xff, 0xc3, 0xb7, 0x8e, + 0xe3, 0x1c, 0x57, 0x55, 0xbe, 0xf1, 0x75, 0x92, 0x27, 0xef, 0x53, 0x72, 0xca, 0x13, + 0x9b, 0x94, 0xe5, 0x12, + ], + x: [ + 0x5b, 0x47, 0x86, 0x19, 0x80, 0x4f, 0x49, 0x38, 0xd3, 0x61, 0xfb, 0xba, 0x3a, 0x20, + 0x64, 0x87, 0x25, 0x22, 0x2f, 0x0a, 0x54, 0xcc, 0x4c, 0x87, 0x61, 0x39, 0xef, 0xe7, + 0xd9, 0xa2, 0x17, 0x86, + ], + X: [ + 0x04, 0xa6, 0xdb, 0x23, 0xd0, 0x01, 0x72, 0x3f, 0xb0, 0x1f, 0xcf, 0xc9, 0xd0, 0x87, + 0x46, 0xc3, 0xc2, 0xa0, 0xa3, 0xfe, 0xff, 0x86, 0x35, 0xd2, 0x9c, 0xad, 0x28, 0x53, + 0xe7, 0x35, 0x86, 0x23, 0x42, 0x5c, 0xf3, 0x97, 0x12, 0xe9, 0x28, 0x05, 0x45, 0x61, + 0xba, 0x71, 0xe2, 0xdc, 0x11, 0xf3, 0x00, 0xf1, 0x76, 0x0e, 0x71, 0xeb, 0x17, 0x70, + 0x21, 0xa8, 0xf8, 0x5e, 0x78, 0x68, 0x90, 0x71, 0xcd, + ], + y: [ + 0x76, 0x67, 0x70, 0xda, 0xd8, 0xc8, 0xee, 0xcb, 0xa9, 0x36, 0x82, 0x3c, 0x0a, 0xed, + 0x04, 0x4b, 0x8c, 0x3c, 0x4f, 0x76, 0x55, 0xe8, 0xbe, 0xec, 0x44, 0xa1, 0x5d, 0xcb, + 0xca, 0xf7, 0x8e, 0x5e, + ], + Y: [ + 0x04, 0x39, 0x0d, 0x29, 0xbf, 0x18, 0x5c, 0x3a, 0xbf, 0x99, 0xf1, 0x50, 0xae, 0x7c, + 0x13, 0x38, 0x8c, 0x82, 0xb6, 0xbe, 0x0c, 0x07, 0xb1, 0xb8, 0xd9, 0x0d, 0x26, 0x85, + 0x3e, 0x84, 0x37, 0x4b, 0xbd, 0xc8, 0x2b, 0xec, 0xdb, 0x97, 0x8c, 0xa3, 0x79, 0x2f, + 0x47, 0x24, 0x24, 0x10, 0x6a, 0x25, 0x78, 0x01, 0x27, 0x52, 0xc1, 0x19, 0x38, 0xfc, + 0xf6, 0x0a, 0x41, 0xdf, 0x75, 0xff, 0x7c, 0xf9, 0x47, + ], + Z: [ + 0x04, 0x0a, 0x15, 0x0d, 0x9a, 0x62, 0xf5, 0x14, 0xc9, 0xa1, 0xfe, 0xdd, 0x78, 0x2a, + 0x02, 0x40, 0xa3, 0x42, 0x72, 0x10, 0x46, 0xce, 0xfb, 0x11, 0x11, 0xc3, 0xad, 0xb3, + 0xbe, 0x89, 0x3c, 0xe9, 0xfc, 0xd2, 0xff, 0xa1, 0x37, 0x92, 0x2f, 0xcf, 0x8a, 0x58, + 0x8d, 0x0f, 0x76, 0xba, 0x9c, 0x55, 0xc8, 0x5d, 0xa2, 0xaf, 0x3f, 0x1c, 0x78, 0x9c, + 0xa1, 0x79, 0x76, 0x81, 0x03, 0x87, 0xfb, 0x1d, 0x7e, + ], + V: [ + 0x04, 0xf8, 0xe2, 0x47, 0xcc, 0x26, 0x3a, 0x18, 0x46, 0x27, 0x2f, 0x5a, 0x3b, 0x61, + 0xb6, 0x8a, 0xa6, 0x0a, 0x5a, 0x26, 0x65, 0xd1, 0x0c, 0xd2, 0x2c, 0x89, 0xcd, 0x6b, + 0xad, 0x05, 0xdc, 0x0e, 0x5e, 0x65, 0x0f, 0x21, 0xff, 0x01, 0x71, 0x86, 0xcc, 0x92, + 0x65, 0x1a, 0x4c, 0xd7, 0xe6, 0x6c, 0xe8, 0x8f, 0x52, 0x92, 0x99, 0xf3, 0x40, 0xea, + 0x80, 0xfb, 0x90, 0xa9, 0xba, 0xd0, 0x94, 0xe1, 0xa6, + ], + L: [ + 0x04, 0x95, 0x64, 0x5c, 0xfb, 0x74, 0xdf, 0x6e, 0x58, 0xf9, 0x74, 0x8b, 0xb8, 0x3a, + 0x86, 0x62, 0x0b, 0xab, 0x7c, 0x82, 0xe1, 0x07, 0xf5, 0x7d, 0x68, 0x70, 0xda, 0x8c, + 0xbc, 0xb2, 0xff, 0x9f, 0x70, 0x63, 0xa1, 0x4b, 0x64, 0x02, 0xc6, 0x2f, 0x99, 0xaf, + 0xcb, 0x97, 0x06, 0xa4, 0xd1, 0xa1, 0x43, 0x27, 0x32, 0x59, 0xfe, 0x76, 0xf1, 0xc6, + 0x05, 0xa3, 0x63, 0x97, 0x45, 0xa9, 0x21, 0x54, 0xb9, + ], + cA: [ + 0x71, 0xd9, 0x41, 0x27, 0x79, 0xb6, 0xc4, 0x5a, 0x2c, 0x61, 0x5c, 0x9d, 0xf3, 0xf1, + 0xfd, 0x93, 0xdc, 0x0a, 0xaf, 0x63, 0x10, 0x4d, 0xa8, 0xec, 0xe4, 0xaa, 0x1b, 0x5a, + 0x3a, 0x41, 0x5f, 0xea, + ], + cB: [ + 0x09, 0x5d, 0xc0, 0x40, 0x03, 0x55, 0xcc, 0x23, 0x3f, 0xde, 0x74, 0x37, 0x81, 0x18, + 0x15, 0xb3, 0xc1, 0x52, 0x4a, 0xae, 0x80, 0xfd, 0x4e, 0x68, 0x10, 0xcf, 0x53, 0x1c, + 0xf1, 0x1d, 0x20, 0xe3, + ], + Ke: [ + 0xea, 0x32, 0x76, 0xd6, 0x83, 0x34, 0x57, 0x60, 0x97, 0xe0, 0x4b, 0x19, 0xee, 0x5a, + 0x3a, 0x8b, + ], + TT: [ + 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x50, 0x41, 0x4b, 0x45, 0x32, + 0x2b, 0x2d, 0x50, 0x32, 0x35, 0x36, 0x2d, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x2d, + 0x48, 0x4b, 0x44, 0x46, 0x20, 0x64, 0x72, 0x61, 0x66, 0x74, 0x2d, 0x30, 0x31, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x88, 0x6e, 0x2f, 0x97, + 0xac, 0xe4, 0x6e, 0x55, 0xba, 0x9d, 0xd7, 0x24, 0x25, 0x79, 0xf2, 0x99, 0x3b, 0x64, + 0xe1, 0x6e, 0xf3, 0xdc, 0xab, 0x95, 0xaf, 0xd4, 0x97, 0x33, 0x3d, 0x8f, 0xa1, 0x2f, + 0x5f, 0xf3, 0x55, 0x16, 0x3e, 0x43, 0xce, 0x22, 0x4e, 0x0b, 0x0e, 0x65, 0xff, 0x02, + 0xac, 0x8e, 0x5c, 0x7b, 0xe0, 0x94, 0x19, 0xc7, 0x85, 0xe0, 0xca, 0x54, 0x7d, 0x55, + 0xa1, 0x2e, 0x2d, 0x20, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xd8, + 0xbb, 0xd6, 0xc6, 0x39, 0xc6, 0x29, 0x37, 0xb0, 0x4d, 0x99, 0x7f, 0x38, 0xc3, 0x77, + 0x07, 0x19, 0xc6, 0x29, 0xd7, 0x01, 0x4d, 0x49, 0xa2, 0x4b, 0x4f, 0x98, 0xba, 0xa1, + 0x29, 0x2b, 0x49, 0x07, 0xd6, 0x0a, 0xa6, 0xbf, 0xad, 0xe4, 0x50, 0x08, 0xa6, 0x36, + 0x33, 0x7f, 0x51, 0x68, 0xc6, 0x4d, 0x9b, 0xd3, 0x60, 0x34, 0x80, 0x8c, 0xd5, 0x64, + 0x49, 0x0b, 0x1e, 0x65, 0x6e, 0xdb, 0xe7, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0xa6, 0xdb, 0x23, 0xd0, 0x01, 0x72, 0x3f, 0xb0, 0x1f, 0xcf, 0xc9, 0xd0, + 0x87, 0x46, 0xc3, 0xc2, 0xa0, 0xa3, 0xfe, 0xff, 0x86, 0x35, 0xd2, 0x9c, 0xad, 0x28, + 0x53, 0xe7, 0x35, 0x86, 0x23, 0x42, 0x5c, 0xf3, 0x97, 0x12, 0xe9, 0x28, 0x05, 0x45, + 0x61, 0xba, 0x71, 0xe2, 0xdc, 0x11, 0xf3, 0x00, 0xf1, 0x76, 0x0e, 0x71, 0xeb, 0x17, + 0x70, 0x21, 0xa8, 0xf8, 0x5e, 0x78, 0x68, 0x90, 0x71, 0xcd, 0x41, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x39, 0x0d, 0x29, 0xbf, 0x18, 0x5c, 0x3a, 0xbf, 0x99, + 0xf1, 0x50, 0xae, 0x7c, 0x13, 0x38, 0x8c, 0x82, 0xb6, 0xbe, 0x0c, 0x07, 0xb1, 0xb8, + 0xd9, 0x0d, 0x26, 0x85, 0x3e, 0x84, 0x37, 0x4b, 0xbd, 0xc8, 0x2b, 0xec, 0xdb, 0x97, + 0x8c, 0xa3, 0x79, 0x2f, 0x47, 0x24, 0x24, 0x10, 0x6a, 0x25, 0x78, 0x01, 0x27, 0x52, + 0xc1, 0x19, 0x38, 0xfc, 0xf6, 0x0a, 0x41, 0xdf, 0x75, 0xff, 0x7c, 0xf9, 0x47, 0x41, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x0a, 0x15, 0x0d, 0x9a, 0x62, 0xf5, + 0x14, 0xc9, 0xa1, 0xfe, 0xdd, 0x78, 0x2a, 0x02, 0x40, 0xa3, 0x42, 0x72, 0x10, 0x46, + 0xce, 0xfb, 0x11, 0x11, 0xc3, 0xad, 0xb3, 0xbe, 0x89, 0x3c, 0xe9, 0xfc, 0xd2, 0xff, + 0xa1, 0x37, 0x92, 0x2f, 0xcf, 0x8a, 0x58, 0x8d, 0x0f, 0x76, 0xba, 0x9c, 0x55, 0xc8, + 0x5d, 0xa2, 0xaf, 0x3f, 0x1c, 0x78, 0x9c, 0xa1, 0x79, 0x76, 0x81, 0x03, 0x87, 0xfb, + 0x1d, 0x7e, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xf8, 0xe2, 0x47, + 0xcc, 0x26, 0x3a, 0x18, 0x46, 0x27, 0x2f, 0x5a, 0x3b, 0x61, 0xb6, 0x8a, 0xa6, 0x0a, + 0x5a, 0x26, 0x65, 0xd1, 0x0c, 0xd2, 0x2c, 0x89, 0xcd, 0x6b, 0xad, 0x05, 0xdc, 0x0e, + 0x5e, 0x65, 0x0f, 0x21, 0xff, 0x01, 0x71, 0x86, 0xcc, 0x92, 0x65, 0x1a, 0x4c, 0xd7, + 0xe6, 0x6c, 0xe8, 0x8f, 0x52, 0x92, 0x99, 0xf3, 0x40, 0xea, 0x80, 0xfb, 0x90, 0xa9, + 0xba, 0xd0, 0x94, 0xe1, 0xa6, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe6, + 0x88, 0x7c, 0xf9, 0xbd, 0xfb, 0x75, 0x79, 0xc6, 0x9b, 0xf4, 0x79, 0x28, 0xa8, 0x45, + 0x14, 0xb5, 0xe3, 0x55, 0xac, 0x03, 0x48, 0x63, 0xf7, 0xff, 0xaf, 0x43, 0x90, 0xe6, + 0x7d, 0x79, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, + ], + TT_len: 535, + }, + ]; +} diff --git a/matter/src/secure_channel/status_report.rs b/matter/src/secure_channel/status_report.rs new file mode 100644 index 0000000..050cd5b --- /dev/null +++ b/matter/src/secure_channel/status_report.rs @@ -0,0 +1,60 @@ +/* + * + * 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::common::*; +use crate::{error::Error, transport::packet::Packet}; + +#[allow(dead_code)] +#[derive(Debug, Copy, Clone)] +pub enum GeneralCode { + Success = 0, + Failure = 1, + BadPrecondition = 2, + OutOfRange = 3, + BadRequest = 4, + Unsupported = 5, + Unexpected = 6, + ResourceExhausted = 7, + Busy = 8, + Timeout = 9, + Continue = 10, + Aborted = 11, + InvalidArgument = 12, + NotFound = 13, + AlreadyExists = 14, + PermissionDenied = 15, + DataLoss = 16, +} +pub fn create_status_report( + proto_tx: &mut Packet, + general_code: GeneralCode, + proto_id: u32, + proto_code: u16, + proto_data: Option<&[u8]>, +) -> Result<(), Error> { + proto_tx.set_proto_id(PROTO_ID_SECURE_CHANNEL as u16); + proto_tx.set_proto_opcode(OpCode::StatusReport as u8); + let wb = proto_tx.get_writebuf()?; + wb.le_u16(general_code as u16)?; + wb.le_u32(proto_id)?; + wb.le_u16(proto_code)?; + if let Some(s) = proto_data { + wb.copy_from_slice(s)?; + } + + Ok(()) +} diff --git a/matter/src/sys/mod.rs b/matter/src/sys/mod.rs new file mode 100644 index 0000000..e8e59cb --- /dev/null +++ b/matter/src/sys/mod.rs @@ -0,0 +1,31 @@ +/* + * + * 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. + */ + +#[cfg(target_os = "macos")] +mod sys_macos; +#[cfg(target_os = "macos")] +pub use self::sys_macos::*; + +#[cfg(target_os = "linux")] +mod sys_linux; +#[cfg(target_os = "linux")] +pub use self::sys_linux::*; + +#[cfg(any(target_os = "macos", target_os = "linux"))] +mod posix; +#[cfg(any(target_os = "macos", target_os = "linux"))] +pub use self::posix::*; diff --git a/matter/src/sys/posix.rs b/matter/src/sys/posix.rs new file mode 100644 index 0000000..e689b2c --- /dev/null +++ b/matter/src/sys/posix.rs @@ -0,0 +1,92 @@ +/* + * + * 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, + fs::{DirBuilder, File}, + io::{Read, Write}, + sync::{Arc, Mutex, Once}, +}; + +use crate::error::Error; + +pub const SPAKE2_ITERATION_COUNT: u32 = 2000; + +// The Packet Pool that is allocated from. POSIX systems can use +// higher values unlike embedded systems +pub const MAX_PACKET_POOL_SIZE: usize = 25; + +pub struct Psm {} + +static mut G_PSM: Option>> = None; +static INIT: Once = Once::new(); + +const PSM_DIR: &str = "/tmp/plonk_psm"; + +macro_rules! psm_path { + ($key:ident) => { + format!("{}/{}", PSM_DIR, $key) + }; +} + +impl Psm { + fn new() -> Result { + let result = DirBuilder::new().create(PSM_DIR); + if let Err(e) = result { + if e.kind() != std::io::ErrorKind::AlreadyExists { + return Err(e.into()); + } + } + + Ok(Self {}) + } + + pub fn get() -> Result>, Error> { + unsafe { + INIT.call_once(|| { + G_PSM = Some(Arc::new(Mutex::new(Psm::new().unwrap()))); + }); + Ok(G_PSM.as_ref().ok_or(Error::Invalid)?.clone()) + } + } + + pub fn set_kv_slice(&self, key: &str, val: &[u8]) -> Result<(), Error> { + let mut f = File::create(psm_path!(key))?; + f.write_all(val)?; + Ok(()) + } + + pub fn get_kv_slice(&self, key: &str, val: &mut Vec) -> Result { + let mut f = File::open(psm_path!(key))?; + let len = f.read_to_end(val)?; + Ok(len) + } + + pub fn set_kv_u64(&self, key: &str, val: u64) -> Result<(), Error> { + let mut f = File::create(psm_path!(key))?; + f.write_all(&val.to_be_bytes())?; + Ok(()) + } + + pub fn get_kv_u64(&self, key: &str, val: &mut u64) -> Result<(), Error> { + let mut f = File::open(psm_path!(key))?; + let mut vec = Vec::new(); + let _ = f.read_to_end(&mut vec)?; + *val = u64::from_be_bytes(vec.as_slice().try_into()?); + Ok(()) + } +} diff --git a/matter/src/sys/sys_linux.rs b/matter/src/sys/sys_linux.rs new file mode 100644 index 0000000..7aa760c --- /dev/null +++ b/matter/src/sys/sys_linux.rs @@ -0,0 +1,32 @@ +/* + * + * 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::error::Error; +use log::error; + +#[allow(dead_code)] +pub struct SysMdnsService {} + +pub fn sys_publish_service( + _name: &str, + _regtype: &str, + _port: u16, + _txt_kvs: &[[&str; 2]], +) -> Result { + error!("Linux is not yet supported for MDNS Service"); + Ok(SysMdnsService {}) +} diff --git a/matter/src/sys/sys_macos.rs b/matter/src/sys/sys_macos.rs new file mode 100644 index 0000000..ba2ce22 --- /dev/null +++ b/matter/src/sys/sys_macos.rs @@ -0,0 +1,46 @@ +/* + * + * 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::error::Error; +use astro_dnssd::{DNSServiceBuilder, RegisteredDnsService}; +use log::info; + +#[allow(dead_code)] +pub struct SysMdnsService { + s: RegisteredDnsService, +} + +/// Publish a mDNS service +/// name - can be a service name (comma separate subtypes may follow) +/// regtype - registration type (e.g. _matter_.tcp etc) +/// port - the port +pub fn sys_publish_service( + name: &str, + regtype: &str, + port: u16, + txt_kvs: &[[&str; 2]], +) -> Result { + let mut builder = DNSServiceBuilder::new(regtype, port).with_name(name); + + info!("mDNS Registration Type {}", regtype); + for kvs in txt_kvs { + info!("mDNS TXT key {} val {}", kvs[0], kvs[1]); + builder = builder.with_key_value(kvs[0].to_string(), kvs[1].to_string()); + } + let s = builder.register().map_err(|_| Error::MdnsError)?; + Ok(SysMdnsService { s }) +} diff --git a/matter/src/tlv/mod.rs b/matter/src/tlv/mod.rs new file mode 100644 index 0000000..f6115e1 --- /dev/null +++ b/matter/src/tlv/mod.rs @@ -0,0 +1,53 @@ +/* + * + * 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. + */ + +/* Tag Types */ +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum TagType { + Anonymous, + Context(u8), + CommonPrf16(u16), + CommonPrf32(u32), + ImplPrf16(u16), + ImplPrf32(u32), + FullQual48(u64), + FullQual64(u64), +} +pub const TAG_SHIFT_BITS: u8 = 5; +pub const TAG_MASK: u8 = 0xe0; +pub const TYPE_MASK: u8 = 0x1f; +pub const MAX_TAG_INDEX: usize = 8; + +pub static TAG_SIZE_MAP: [usize; MAX_TAG_INDEX] = [ + 0, // Anonymous + 1, // Context + 2, // CommonPrf16 + 4, // CommonPrf32 + 2, // ImplPrf16 + 4, // ImplPrf32 + 6, // FullQual48 + 8, // FullQual64 +]; + +mod parser; +mod traits; +mod writer; + +pub use matter_macro_derive::{FromTLV, ToTLV}; +pub use parser::*; +pub use traits::*; +pub use writer::*; diff --git a/matter/src/tlv/parser.rs b/matter/src/tlv/parser.rs new file mode 100644 index 0000000..bb07ae6 --- /dev/null +++ b/matter/src/tlv/parser.rs @@ -0,0 +1,1222 @@ +/* + * + * 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::error::Error; + +use byteorder::{ByteOrder, LittleEndian}; +use log::{error, info}; +use std::fmt; + +use super::{TagType, MAX_TAG_INDEX, TAG_MASK, TAG_SHIFT_BITS, TAG_SIZE_MAP, TYPE_MASK}; + +pub struct TLVList<'a> { + buf: &'a [u8], +} + +impl<'a> TLVList<'a> { + pub fn new(buf: &'a [u8]) -> TLVList<'a> { + TLVList { buf } + } +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Pointer<'a> { + buf: &'a [u8], + current: usize, + left: usize, +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum ElementType<'a> { + S8(i8), + S16(i16), + S32(i32), + S64(i64), + U8(u8), + U16(u16), + U32(u32), + U64(u64), + False, + True, + F32(f32), + F64(f64), + Utf8l(&'a [u8]), + Utf16l(&'a [u8]), + Utf32l, + Utf64l, + Str8l(&'a [u8]), + Str16l(&'a [u8]), + Str32l, + Str64l, + Null, + Struct(Pointer<'a>), + Array(Pointer<'a>), + List(Pointer<'a>), + EndCnt, + Last, +} + +const MAX_VALUE_INDEX: usize = 25; + +// This is a function that takes a TLVListIterator and returns the tag type +type ExtractTag = for<'a> fn(&TLVListIterator<'a>) -> TagType; +static TAG_EXTRACTOR: [ExtractTag; 8] = [ + // Anonymous 0 + |_t| TagType::Anonymous, + // Context 1 + |t| TagType::Context(t.buf[t.current]), + // CommonPrf16 2 + |t| TagType::CommonPrf16(LittleEndian::read_u16(&t.buf[t.current..])), + // CommonPrf32 3 + |t| TagType::CommonPrf32(LittleEndian::read_u32(&t.buf[t.current..])), + // ImplPrf16 4 + |t| TagType::ImplPrf16(LittleEndian::read_u16(&t.buf[t.current..])), + // ImplPrf32 5 + |t| TagType::ImplPrf32(LittleEndian::read_u32(&t.buf[t.current..])), + // FullQual48 6 + |t| TagType::FullQual48(LittleEndian::read_u48(&t.buf[t.current..]) as u64), + // FullQual64 7 + |t| TagType::FullQual64(LittleEndian::read_u64(&t.buf[t.current..])), +]; + +// This is a function that takes a TLVListIterator and returns the element type +// Some elements (like strings), also consume additional size, than that mentioned +// if this is the case, the additional size is returned +type ExtractValue = for<'a> fn(&TLVListIterator<'a>) -> (usize, ElementType<'a>); + +static VALUE_EXTRACTOR: [ExtractValue; MAX_VALUE_INDEX] = [ + // S8 0 + { |t| (0, ElementType::S8(t.buf[t.current] as i8)) }, + // S16 1 + { + |t| { + ( + 0, + ElementType::S16(LittleEndian::read_i16(&t.buf[t.current..])), + ) + } + }, + // S32 2 + { + |t| { + ( + 0, + ElementType::S32(LittleEndian::read_i32(&t.buf[t.current..])), + ) + } + }, + // S64 3 + { + |t| { + ( + 0, + ElementType::S64(LittleEndian::read_i64(&t.buf[t.current..])), + ) + } + }, + // U8 4 + { |t| (0, ElementType::U8(t.buf[t.current])) }, + // U16 5 + { + |t| { + ( + 0, + ElementType::U16(LittleEndian::read_u16(&t.buf[t.current..])), + ) + } + }, + // U32 6 + { + |t| { + ( + 0, + ElementType::U32(LittleEndian::read_u32(&t.buf[t.current..])), + ) + } + }, + // U64 7 + { + |t| { + ( + 0, + ElementType::U64(LittleEndian::read_u64(&t.buf[t.current..])), + ) + } + }, + // False 8 + { |_t| (0, ElementType::False) }, + // True 9 + { |_t| (0, ElementType::True) }, + // F32 10 + { |_t| (0, ElementType::Last) }, + // F64 11 + { |_t| (0, ElementType::Last) }, + // Utf8l 12 + { + |t| match read_length_value(1, t) { + Err(_) => (0, ElementType::Last), + Ok((size, string)) => (size, ElementType::Utf8l(string)), + } + }, + // Utf16l 13 + { + |t| match read_length_value(2, t) { + Err(_) => (0, ElementType::Last), + Ok((size, string)) => (size, ElementType::Utf16l(string)), + } + }, + // Utf32l 14 + { |_t| (0, ElementType::Last) }, + // Utf64l 15 + { |_t| (0, ElementType::Last) }, + // Str8l 16 + { + |t| match read_length_value(1, t) { + Err(_) => (0, ElementType::Last), + Ok((size, string)) => (size, ElementType::Str8l(string)), + } + }, + // Str16l 17 + { + |t| match read_length_value(2, t) { + Err(_) => (0, ElementType::Last), + Ok((size, string)) => (size, ElementType::Str16l(string)), + } + }, + // Str32l 18 + { |_t| (0, ElementType::Last) }, + // Str64l 19 + { |_t| (0, ElementType::Last) }, + // Null 20 + { |_t| (0, ElementType::Null) }, + // Struct 21 + { + |t| { + ( + 0, + ElementType::Struct(Pointer { + buf: t.buf, + current: t.current, + left: t.left, + }), + ) + } + }, + // Array 22 + { + |t| { + ( + 0, + ElementType::Array(Pointer { + buf: t.buf, + current: t.current, + left: t.left, + }), + ) + } + }, + // List 23 + { + |t| { + ( + 0, + ElementType::List(Pointer { + buf: t.buf, + current: t.current, + left: t.left, + }), + ) + } + }, + // EndCnt 24 + { |_t| (0, ElementType::EndCnt) }, +]; + +// The array indices here correspond to the numeric value of the Element Type as defined in the Matter Spec +static VALUE_SIZE_MAP: [usize; MAX_VALUE_INDEX] = [ + 1, // S8 0 + 2, // S16 1 + 4, // S32 2 + 8, // S64 3 + 1, // U8 4 + 2, // U16 5 + 4, // U32 6 + 8, // U64 7 + 0, // False 8 + 0, // True 9 + 4, // F32 10 + 8, // F64 11 + 1, // Utf8l 12 + 2, // Utf16l 13 + 4, // Utf32l 14 + 8, // Utf64l 15 + 1, // Str8l 16 + 2, // Str16l 17 + 4, // Str32l 18 + 8, // Str64l 19 + 0, // Null 20 + 0, // Struct 21 + 0, // Array 22 + 0, // List 23 + 0, // EndCnt 24 +]; + +fn read_length_value<'a>( + size_of_length_field: usize, + t: &TLVListIterator<'a>, +) -> Result<(usize, &'a [u8]), Error> { + // The current offset is the string size + let length: usize = LittleEndian::read_uint(&t.buf[t.current..], size_of_length_field) as usize; + // We'll consume the current offset (len) + the entire string + if length + size_of_length_field > t.left { + // Return Error + Err(Error::NoSpace) + } else { + Ok(( + // return the additional size only + length, + &t.buf[(t.current + size_of_length_field)..(t.current + size_of_length_field + length)], + )) + } +} + +#[derive(Debug, Copy, Clone)] +pub struct TLVElement<'a> { + tag_type: TagType, + element_type: ElementType<'a>, +} + +impl<'a> PartialEq for TLVElement<'a> { + fn eq(&self, other: &Self) -> bool { + match self.element_type { + ElementType::Struct(a) | ElementType::Array(a) | ElementType::List(a) => { + let mut our_iter = TLVListIterator::from_pointer(a); + let mut their = match other.element_type { + ElementType::Struct(b) | ElementType::Array(b) | ElementType::List(b) => { + TLVListIterator::from_pointer(b) + } + _ => { + // If we are a container, the other must be a container, else this is a mismatch + return false; + } + }; + let mut nest_level = 0_u8; + loop { + let ours = our_iter.next(); + let theirs = their.next(); + if std::mem::discriminant(&ours) != std::mem::discriminant(&theirs) { + // One of us reached end of list, but the other didn't, that's a mismatch + return false; + } + if ours.is_none() { + // End of list + break; + } + // guaranteed to work + let ours = ours.unwrap(); + let theirs = theirs.unwrap(); + + match ours.element_type { + ElementType::EndCnt => { + if nest_level == 0 { + break; + } else { + nest_level -= 1; + } + } + _ => { + if is_container(ours.element_type) { + nest_level += 1; + // Only compare the discriminants in case of array/list/structures, + // instead of actual element values. Those will be subsets within this same + // list that will get validated anyway + if std::mem::discriminant(&ours.element_type) + != std::mem::discriminant(&theirs.element_type) + { + return false; + } + } else if ours.element_type != theirs.element_type { + return false; + } + + if ours.tag_type != theirs.tag_type { + return false; + } + } + } + } + true + } + _ => self.tag_type == other.tag_type && self.element_type == other.element_type, + } + } +} + +impl<'a> TLVElement<'a> { + pub fn enter(&self) -> Option> { + let ptr = match self.element_type { + ElementType::Struct(a) | ElementType::Array(a) | ElementType::List(a) => a, + _ => return None, + }; + let list_iter = TLVListIterator { + buf: ptr.buf, + current: ptr.current, + left: ptr.left, + }; + Some(TLVContainerIterator { + list_iter, + prev_container: false, + iterator_consumed: false, + }) + } + + pub fn new(tag: TagType, value: ElementType<'a>) -> Self { + Self { + tag_type: tag, + element_type: value, + } + } + + pub fn i8(&self) -> Result { + match self.element_type { + ElementType::S8(a) => Ok(a), + _ => Err(Error::TLVTypeMismatch), + } + } + + pub fn u8(&self) -> Result { + match self.element_type { + ElementType::U8(a) => Ok(a), + _ => Err(Error::TLVTypeMismatch), + } + } + + pub fn u16(&self) -> Result { + match self.element_type { + ElementType::U8(a) => Ok(a.into()), + ElementType::U16(a) => Ok(a), + _ => Err(Error::TLVTypeMismatch), + } + } + + pub fn u32(&self) -> Result { + match self.element_type { + ElementType::U8(a) => Ok(a.into()), + ElementType::U16(a) => Ok(a.into()), + ElementType::U32(a) => Ok(a), + _ => Err(Error::TLVTypeMismatch), + } + } + + pub fn u64(&self) -> Result { + match self.element_type { + ElementType::U8(a) => Ok(a.into()), + ElementType::U16(a) => Ok(a.into()), + ElementType::U32(a) => Ok(a.into()), + ElementType::U64(a) => Ok(a), + _ => Err(Error::TLVTypeMismatch), + } + } + + pub fn slice(&self) -> Result<&'a [u8], Error> { + match self.element_type { + ElementType::Str8l(s) + | ElementType::Utf8l(s) + | ElementType::Str16l(s) + | ElementType::Utf16l(s) => Ok(s), + _ => Err(Error::TLVTypeMismatch), + } + } + + pub fn bool(&self) -> Result { + match self.element_type { + ElementType::False => Ok(false), + ElementType::True => Ok(true), + _ => Err(Error::TLVTypeMismatch), + } + } + + pub fn null(&self) -> Result<(), Error> { + match self.element_type { + ElementType::Null => Ok(()), + _ => Err(Error::TLVTypeMismatch), + } + } + + pub fn confirm_struct(&self) -> Result, Error> { + match self.element_type { + ElementType::Struct(_) => Ok(*self), + _ => Err(Error::TLVTypeMismatch), + } + } + + pub fn confirm_array(&self) -> Result, Error> { + match self.element_type { + ElementType::Array(_) => Ok(*self), + _ => Err(Error::TLVTypeMismatch), + } + } + + pub fn confirm_list(&self) -> Result, Error> { + match self.element_type { + ElementType::List(_) => Ok(*self), + _ => Err(Error::TLVTypeMismatch), + } + } + + pub fn find_tag(&self, tag: u32) -> Result, Error> { + let match_tag: TagType = TagType::Context(tag as u8); + + let iter = self.enter().ok_or(Error::TLVTypeMismatch)?; + for a in iter { + if match_tag == a.tag_type { + return Ok(a); + } + } + Err(Error::NoTagFound) + } + + pub fn get_tag(&self) -> TagType { + self.tag_type + } + + pub fn check_ctx_tag(&self, tag: u8) -> bool { + if let TagType::Context(our_tag) = self.tag_type { + if our_tag == tag { + return true; + } + } + false + } + + pub fn get_element_type(&self) -> ElementType { + self.element_type + } +} + +impl<'a> fmt::Display for TLVElement<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.tag_type { + TagType::Anonymous => (), + TagType::Context(tag) => write!(f, "{}: ", tag)?, + _ => write!(f, "Other Context Tag")?, + } + match self.element_type { + ElementType::Struct(_) => write!(f, "{{"), + ElementType::Array(_) => write!(f, "["), + ElementType::List(_) => write!(f, "["), + ElementType::EndCnt => write!(f, ">"), + ElementType::True => write!(f, "True"), + ElementType::False => write!(f, "False"), + ElementType::Str8l(a) + | ElementType::Utf8l(a) + | ElementType::Str16l(a) + | ElementType::Utf16l(a) => { + if let Ok(s) = std::str::from_utf8(a) { + write!(f, "len[{}]\"{}\"", s.len(), s) + } else { + write!(f, "len[{}]{:x?}", a.len(), a) + } + } + _ => write!(f, "{:?}", self.element_type), + } + } +} + +// This is a TLV List iterator, it only iterates over the individual TLVs in a TLV list +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct TLVListIterator<'a> { + buf: &'a [u8], + current: usize, + left: usize, +} + +impl<'a> TLVListIterator<'a> { + fn from_pointer(p: Pointer<'a>) -> Self { + Self { + buf: p.buf, + current: p.current, + left: p.left, + } + } + + fn advance(&mut self, len: usize) { + self.current += len; + self.left -= len; + } + + // Caller should ensure they are reading the _right_ tag at the _right_ place + fn read_this_tag(&mut self, tag_type: u8) -> Option { + if tag_type as usize >= MAX_TAG_INDEX { + return None; + } + let tag_size = TAG_SIZE_MAP[tag_type as usize]; + if tag_size > self.left { + return None; + } + let tag = (TAG_EXTRACTOR[tag_type as usize])(self); + self.advance(tag_size); + Some(tag) + } + + fn read_this_value(&mut self, element_type: u8) -> Option> { + if element_type as usize >= MAX_VALUE_INDEX { + return None; + } + let mut size = VALUE_SIZE_MAP[element_type as usize]; + if size > self.left { + error!( + "Invalid value found: {} self {:?} size {}", + element_type, self, size + ); + return None; + } + + let (extra_size, element) = (VALUE_EXTRACTOR[element_type as usize])(self); + if element != ElementType::Last { + size += extra_size; + self.advance(size); + Some(element) + } else { + None + } + } +} + +impl<'a> Iterator for TLVListIterator<'a> { + type Item = TLVElement<'a>; + /* Code for going to the next Element */ + fn next(&mut self) -> Option> { + if self.left < 1 { + return None; + } + /* Read Control */ + let control = self.buf[self.current]; + let tag_type = (control & TAG_MASK) >> TAG_SHIFT_BITS; + let element_type = control & TYPE_MASK; + self.advance(1); + + /* Consume Tag */ + let tag_type = self.read_this_tag(tag_type)?; + + /* Consume Value */ + let element_type = self.read_this_value(element_type)?; + + Some(TLVElement { + tag_type, + element_type, + }) + } +} + +impl<'a> TLVList<'a> { + pub fn iter(&self) -> TLVListIterator<'a> { + TLVListIterator { + current: 0, + left: self.buf.len(), + buf: self.buf, + } + } +} + +fn is_container(element_type: ElementType) -> bool { + matches!( + element_type, + ElementType::Struct(_) | ElementType::Array(_) | ElementType::List(_) + ) +} + +// This is a Container iterator, it iterates over containers in a TLV list +#[derive(Debug, PartialEq)] +pub struct TLVContainerIterator<'a> { + list_iter: TLVListIterator<'a>, + prev_container: bool, + iterator_consumed: bool, +} + +impl<'a> TLVContainerIterator<'a> { + fn skip_to_end_of_container(&mut self) -> Option> { + let mut nest_level = 0; + while let Some(element) = self.list_iter.next() { + // We know we are already in a container, we have to keep looking for end-of-container + // println!("Skip: element: {:x?} nest_level: {}", element, nest_level); + match element.element_type { + ElementType::EndCnt => { + if nest_level == 0 { + // Return the element following this element + // println!("Returning"); + // The final next() may be the end of the top-level container itself, if so, we must return None + let last_elem = self.list_iter.next()?; + match last_elem.element_type { + ElementType::EndCnt => { + self.iterator_consumed = true; + return None; + } + _ => return Some(last_elem), + } + } else { + nest_level -= 1; + } + } + _ => { + if is_container(element.element_type) { + nest_level += 1; + } + } + } + } + None + } +} + +impl<'a> Iterator for TLVContainerIterator<'a> { + type Item = TLVElement<'a>; + /* Code for going to the next Element */ + fn next(&mut self) -> Option> { + // This iterator may be consumed, but the underlying might not. This protects it from such occurrences + if self.iterator_consumed { + return None; + } + let element: TLVElement = if self.prev_container { + // println!("Calling skip to end of container"); + self.skip_to_end_of_container()? + } else { + self.list_iter.next()? + }; + // println!("Found element: {:x?}", element); + /* If we found end of container, that means our own container is over */ + if element.element_type == ElementType::EndCnt { + self.iterator_consumed = true; + return None; + } + + if is_container(element.element_type) { + self.prev_container = true; + } else { + self.prev_container = false; + } + Some(element) + } +} + +pub fn get_root_node(b: &[u8]) -> Result { + TLVList::new(b).iter().next().ok_or(Error::InvalidData) +} + +pub fn get_root_node_struct(b: &[u8]) -> Result { + TLVList::new(b) + .iter() + .next() + .ok_or(Error::InvalidData)? + .confirm_struct() +} + +pub fn get_root_node_list(b: &[u8]) -> Result { + TLVList::new(b) + .iter() + .next() + .ok_or(Error::InvalidData)? + .confirm_list() +} + +pub fn print_tlv_list(b: &[u8]) { + let tlvlist = TLVList::new(b); + + const MAX_DEPTH: usize = 9; + info!("TLV list:"); + let space_buf = " "; + let space: [&str; MAX_DEPTH] = [ + &space_buf[0..0], + &space_buf[0..4], + &space_buf[0..8], + &space_buf[0..12], + &space_buf[0..16], + &space_buf[0..20], + &space_buf[0..24], + &space_buf[0..28], + &space_buf[0..32], + ]; + let mut stack: [char; MAX_DEPTH] = [' '; MAX_DEPTH]; + let mut index = 0_usize; + let iter = tlvlist.iter(); + for a in iter { + match a.element_type { + ElementType::Struct(_) => { + if index < MAX_DEPTH { + println!("{}{}", space[index], a); + stack[index] = '}'; + index += 1; + } else { + error!("Too Deep"); + } + } + ElementType::Array(_) | ElementType::List(_) => { + if index < MAX_DEPTH { + println!("{}{}", space[index], a); + stack[index] = ']'; + index += 1; + } else { + error!("Too Deep"); + } + } + ElementType::EndCnt => { + if index > 0 { + index -= 1; + println!("{}{}", space[index], stack[index]); + } else { + error!("Incorrect TLV List"); + } + } + _ => println!("{}{}", space[index], a), + } + } + println!("---------"); +} + +#[cfg(test)] +mod tests { + use super::{ + get_root_node_list, get_root_node_struct, ElementType, Pointer, TLVElement, TLVList, + TagType, + }; + use crate::error::Error; + + #[test] + fn test_short_length_tag() { + // The 0x36 is an array with a tag, but we leave out the tag field + let b = [0x15, 0x36]; + let tlvlist = TLVList::new(&b); + let mut tlv_iter = tlvlist.iter(); + // Skip the 0x15 + tlv_iter.next(); + assert_eq!(tlv_iter.next(), None); + } + + #[test] + fn test_invalid_value_type() { + // The 0x24 is a a tagged integer, here we leave out the integer value + let b = [0x15, 0x1f, 0x0]; + let tlvlist = TLVList::new(&b); + let mut tlv_iter = tlvlist.iter(); + // Skip the 0x15 + tlv_iter.next(); + assert_eq!(tlv_iter.next(), None); + } + + #[test] + fn test_short_length_value_immediate() { + // The 0x24 is a a tagged integer, here we leave out the integer value + let b = [0x15, 0x24, 0x0]; + let tlvlist = TLVList::new(&b); + let mut tlv_iter = tlvlist.iter(); + // Skip the 0x15 + tlv_iter.next(); + assert_eq!(tlv_iter.next(), None); + } + + #[test] + fn test_short_length_value_string() { + // This is a tagged string, with tag 0 and length 0xb, but we only have 4 bytes in the string + let b = [0x15, 0x30, 0x00, 0x0b, 0x73, 0x6d, 0x61, 0x72]; + let tlvlist = TLVList::new(&b); + let mut tlv_iter = tlvlist.iter(); + // Skip the 0x15 + tlv_iter.next(); + assert_eq!(tlv_iter.next(), None); + } + + #[test] + fn test_valid_tag() { + // The 0x36 is an array with a tag, here tag is 0 + let b = [0x15, 0x36, 0x0]; + let tlvlist = TLVList::new(&b); + let mut tlv_iter = tlvlist.iter(); + // Skip the 0x15 + tlv_iter.next(); + assert_eq!( + tlv_iter.next(), + Some(TLVElement { + tag_type: TagType::Context(0), + element_type: ElementType::Array(Pointer { + buf: &[21, 54, 0], + current: 3, + left: 0 + }), + }) + ); + } + + #[test] + fn test_valid_value_immediate() { + // The 0x24 is a a tagged integer, here the integer is 2 + let b = [0x15, 0x24, 0x1, 0x2]; + let tlvlist = TLVList::new(&b); + let mut tlv_iter = tlvlist.iter(); + // Skip the 0x15 + tlv_iter.next(); + assert_eq!( + tlv_iter.next(), + Some(TLVElement { + tag_type: TagType::Context(1), + element_type: ElementType::U8(2), + }) + ); + } + + #[test] + fn test_valid_value_string() { + // This is a tagged string, with tag 0 and length 4, and we have 4 bytes in the string + let b = [0x15, 0x30, 0x5, 0x04, 0x73, 0x6d, 0x61, 0x72]; + let tlvlist = TLVList::new(&b); + let mut tlv_iter = tlvlist.iter(); + // Skip the 0x15 + tlv_iter.next(); + assert_eq!( + tlv_iter.next(), + Some(TLVElement { + tag_type: TagType::Context(5), + element_type: ElementType::Str8l(&[0x73, 0x6d, 0x61, 0x72]), + }) + ); + } + + #[test] + fn test_valid_value_string16() { + // This is a tagged string, with tag 0 and length 4, and we have 4 bytes in the string + let b = [ + 0x15, 0x31, 0x1, 0xd8, 0x1, 0x30, 0x82, 0x1, 0xd4, 0x30, 0x82, 0x1, 0x7a, 0xa0, 0x3, + 0x2, 0x1, 0x2, 0x2, 0x8, 0x3e, 0x6c, 0xe6, 0x50, 0x9a, 0xd8, 0x40, 0xcd, 0x30, 0xa, + 0x6, 0x8, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x4, 0x3, 0x2, 0x30, 0x30, 0x31, 0x18, 0x30, + 0x16, 0x6, 0x3, 0x55, 0x4, 0x3, 0xc, 0xf, 0x4d, 0x61, 0x74, 0x74, 0x65, 0x72, 0x20, + 0x54, 0x65, 0x73, 0x74, 0x20, 0x50, 0x41, 0x41, 0x31, 0x14, 0x30, 0x12, 0x6, 0xa, 0x2b, + 0x6, 0x1, 0x4, 0x1, 0x82, 0xa2, 0x7c, 0x2, 0x1, 0xc, 0x4, 0x46, 0x46, 0x46, 0x31, 0x30, + 0x20, 0x17, 0xd, 0x32, 0x31, 0x30, 0x36, 0x32, 0x38, 0x31, 0x34, 0x32, 0x33, 0x34, + 0x33, 0x5a, 0x18, 0xf, 0x39, 0x39, 0x39, 0x39, 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, + 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x46, 0x31, 0x18, 0x30, 0x16, 0x6, 0x3, 0x55, 0x4, + 0x3, 0xc, 0xf, 0x4d, 0x61, 0x74, 0x74, 0x65, 0x72, 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, + 0x50, 0x41, 0x49, 0x31, 0x14, 0x30, 0x12, 0x6, 0xa, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, + 0xa2, 0x7c, 0x2, 0x1, 0xc, 0x4, 0x46, 0x46, 0x46, 0x31, 0x31, 0x14, 0x30, 0x12, 0x6, + 0xa, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0xa2, 0x7c, 0x2, 0x2, 0xc, 0x4, 0x38, 0x30, 0x30, + 0x30, 0x30, 0x59, 0x30, 0x13, 0x6, 0x7, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x2, 0x1, 0x6, + 0x8, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x3, 0x1, 0x7, 0x3, 0x42, 0x0, 0x4, 0x80, 0xdd, + 0xf1, 0x1b, 0x22, 0x8f, 0x3e, 0x31, 0xf6, 0x3b, 0xcf, 0x57, 0x98, 0xda, 0x14, 0x62, + 0x3a, 0xeb, 0xbd, 0xe8, 0x2e, 0xf3, 0x78, 0xee, 0xad, 0xbf, 0xb1, 0x8f, 0xe1, 0xab, + 0xce, 0x31, 0xd0, 0x8e, 0xd4, 0xb2, 0x6, 0x4, 0xb6, 0xcc, 0xc6, 0xd9, 0xb5, 0xfa, 0xb6, + 0x4e, 0x7d, 0xe1, 0xc, 0xb7, 0x4b, 0xe0, 0x17, 0xc9, 0xec, 0x15, 0x16, 0x5, 0x6d, 0x70, + 0xf2, 0xcd, 0xb, 0x22, 0xa3, 0x66, 0x30, 0x64, 0x30, 0x12, 0x6, 0x3, 0x55, 0x1d, 0x13, + 0x1, 0x1, 0xff, 0x4, 0x8, 0x30, 0x6, 0x1, 0x1, 0xff, 0x2, 0x1, 0x0, 0x30, 0xe, 0x6, + 0x3, 0x55, 0x1d, 0xf, 0x1, 0x1, 0xff, 0x4, 0x4, 0x3, 0x2, 0x1, 0x6, 0x30, 0x1d, 0x6, + 0x3, 0x55, 0x1d, 0xe, 0x4, 0x16, 0x4, 0x14, 0xaf, 0x42, 0xb7, 0x9, 0x4d, 0xeb, 0xd5, + 0x15, 0xec, 0x6e, 0xcf, 0x33, 0xb8, 0x11, 0x15, 0x22, 0x5f, 0x32, 0x52, 0x88, 0x30, + 0x1f, 0x6, 0x3, 0x55, 0x1d, 0x23, 0x4, 0x18, 0x30, 0x16, 0x80, 0x14, 0x6a, 0xfd, 0x22, + 0x77, 0x1f, 0x51, 0x1f, 0xec, 0xbf, 0x16, 0x41, 0x97, 0x67, 0x10, 0xdc, 0xdc, 0x31, + 0xa1, 0x71, 0x7e, 0x30, 0xa, 0x6, 0x8, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x4, 0x3, 0x2, + 0x3, 0x48, 0x0, 0x30, 0x45, 0x2, 0x21, 0x0, 0x96, 0xc9, 0xc8, 0xcf, 0x2e, 0x1, 0x88, + 0x60, 0x5, 0xd8, 0xf5, 0xbc, 0x72, 0xc0, 0x7b, 0x75, 0xfd, 0x9a, 0x57, 0x69, 0x5a, + 0xc4, 0x91, 0x11, 0x31, 0x13, 0x8b, 0xea, 0x3, 0x3c, 0xe5, 0x3, 0x2, 0x20, 0x25, 0x54, + 0x94, 0x3b, 0xe5, 0x7d, 0x53, 0xd6, 0xc4, 0x75, 0xf7, 0xd2, 0x3e, 0xbf, 0xcf, 0xc2, + 0x3, 0x6c, 0xd2, 0x9b, 0xa6, 0x39, 0x3e, 0xc7, 0xef, 0xad, 0x87, 0x14, 0xab, 0x71, + 0x82, 0x19, 0x26, 0x2, 0x3e, 0x0, 0x0, 0x0, + ]; + let tlvlist = TLVList::new(&b); + let mut tlv_iter = tlvlist.iter(); + // Skip the 0x15 + tlv_iter.next(); + assert_eq!( + tlv_iter.next(), + Some(TLVElement { + tag_type: TagType::Context(1), + element_type: ElementType::Str16l(&[ + 0x30, 0x82, 0x1, 0xd4, 0x30, 0x82, 0x1, 0x7a, 0xa0, 0x3, 0x2, 0x1, 0x2, 0x2, + 0x8, 0x3e, 0x6c, 0xe6, 0x50, 0x9a, 0xd8, 0x40, 0xcd, 0x30, 0xa, 0x6, 0x8, 0x2a, + 0x86, 0x48, 0xce, 0x3d, 0x4, 0x3, 0x2, 0x30, 0x30, 0x31, 0x18, 0x30, 0x16, 0x6, + 0x3, 0x55, 0x4, 0x3, 0xc, 0xf, 0x4d, 0x61, 0x74, 0x74, 0x65, 0x72, 0x20, 0x54, + 0x65, 0x73, 0x74, 0x20, 0x50, 0x41, 0x41, 0x31, 0x14, 0x30, 0x12, 0x6, 0xa, + 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0xa2, 0x7c, 0x2, 0x1, 0xc, 0x4, 0x46, 0x46, + 0x46, 0x31, 0x30, 0x20, 0x17, 0xd, 0x32, 0x31, 0x30, 0x36, 0x32, 0x38, 0x31, + 0x34, 0x32, 0x33, 0x34, 0x33, 0x5a, 0x18, 0xf, 0x39, 0x39, 0x39, 0x39, 0x31, + 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x46, 0x31, + 0x18, 0x30, 0x16, 0x6, 0x3, 0x55, 0x4, 0x3, 0xc, 0xf, 0x4d, 0x61, 0x74, 0x74, + 0x65, 0x72, 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, 0x50, 0x41, 0x49, 0x31, 0x14, + 0x30, 0x12, 0x6, 0xa, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0xa2, 0x7c, 0x2, 0x1, + 0xc, 0x4, 0x46, 0x46, 0x46, 0x31, 0x31, 0x14, 0x30, 0x12, 0x6, 0xa, 0x2b, 0x6, + 0x1, 0x4, 0x1, 0x82, 0xa2, 0x7c, 0x2, 0x2, 0xc, 0x4, 0x38, 0x30, 0x30, 0x30, + 0x30, 0x59, 0x30, 0x13, 0x6, 0x7, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x2, 0x1, 0x6, + 0x8, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x3, 0x1, 0x7, 0x3, 0x42, 0x0, 0x4, 0x80, + 0xdd, 0xf1, 0x1b, 0x22, 0x8f, 0x3e, 0x31, 0xf6, 0x3b, 0xcf, 0x57, 0x98, 0xda, + 0x14, 0x62, 0x3a, 0xeb, 0xbd, 0xe8, 0x2e, 0xf3, 0x78, 0xee, 0xad, 0xbf, 0xb1, + 0x8f, 0xe1, 0xab, 0xce, 0x31, 0xd0, 0x8e, 0xd4, 0xb2, 0x6, 0x4, 0xb6, 0xcc, + 0xc6, 0xd9, 0xb5, 0xfa, 0xb6, 0x4e, 0x7d, 0xe1, 0xc, 0xb7, 0x4b, 0xe0, 0x17, + 0xc9, 0xec, 0x15, 0x16, 0x5, 0x6d, 0x70, 0xf2, 0xcd, 0xb, 0x22, 0xa3, 0x66, + 0x30, 0x64, 0x30, 0x12, 0x6, 0x3, 0x55, 0x1d, 0x13, 0x1, 0x1, 0xff, 0x4, 0x8, + 0x30, 0x6, 0x1, 0x1, 0xff, 0x2, 0x1, 0x0, 0x30, 0xe, 0x6, 0x3, 0x55, 0x1d, 0xf, + 0x1, 0x1, 0xff, 0x4, 0x4, 0x3, 0x2, 0x1, 0x6, 0x30, 0x1d, 0x6, 0x3, 0x55, 0x1d, + 0xe, 0x4, 0x16, 0x4, 0x14, 0xaf, 0x42, 0xb7, 0x9, 0x4d, 0xeb, 0xd5, 0x15, 0xec, + 0x6e, 0xcf, 0x33, 0xb8, 0x11, 0x15, 0x22, 0x5f, 0x32, 0x52, 0x88, 0x30, 0x1f, + 0x6, 0x3, 0x55, 0x1d, 0x23, 0x4, 0x18, 0x30, 0x16, 0x80, 0x14, 0x6a, 0xfd, + 0x22, 0x77, 0x1f, 0x51, 0x1f, 0xec, 0xbf, 0x16, 0x41, 0x97, 0x67, 0x10, 0xdc, + 0xdc, 0x31, 0xa1, 0x71, 0x7e, 0x30, 0xa, 0x6, 0x8, 0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x4, 0x3, 0x2, 0x3, 0x48, 0x0, 0x30, 0x45, 0x2, 0x21, 0x0, 0x96, 0xc9, + 0xc8, 0xcf, 0x2e, 0x1, 0x88, 0x60, 0x5, 0xd8, 0xf5, 0xbc, 0x72, 0xc0, 0x7b, + 0x75, 0xfd, 0x9a, 0x57, 0x69, 0x5a, 0xc4, 0x91, 0x11, 0x31, 0x13, 0x8b, 0xea, + 0x3, 0x3c, 0xe5, 0x3, 0x2, 0x20, 0x25, 0x54, 0x94, 0x3b, 0xe5, 0x7d, 0x53, + 0xd6, 0xc4, 0x75, 0xf7, 0xd2, 0x3e, 0xbf, 0xcf, 0xc2, 0x3, 0x6c, 0xd2, 0x9b, + 0xa6, 0x39, 0x3e, 0xc7, 0xef, 0xad, 0x87, 0x14, 0xab, 0x71, 0x82, 0x19 + ]), + }) + ); + assert_eq!( + tlv_iter.next(), + Some(TLVElement { + tag_type: TagType::Context(2), + element_type: ElementType::U32(62), + }) + ); + } + + #[test] + fn test_no_iterator_for_int() { + // The 0x24 is a a tagged integer, here the integer is 2 + let b = [0x15, 0x24, 0x1, 0x2]; + let tlvlist = TLVList::new(&b); + let mut tlv_iter = tlvlist.iter(); + // Skip the 0x15 + tlv_iter.next(); + assert_eq!(tlv_iter.next().unwrap().enter(), None); + } + + #[test] + fn test_struct_iteration_with_mix_values() { + // This is a struct with 3 valid values + let b = [ + 0x15, 0x24, 0x0, 0x2, 0x26, 0x2, 0x4e, 0x10, 0x02, 0x00, 0x30, 0x3, 0x04, 0x73, 0x6d, + 0x61, 0x72, + ]; + let mut root_iter = get_root_node_struct(&b).unwrap().enter().unwrap(); + assert_eq!( + root_iter.next(), + Some(TLVElement { + tag_type: TagType::Context(0), + element_type: ElementType::U8(2), + }) + ); + assert_eq!( + root_iter.next(), + Some(TLVElement { + tag_type: TagType::Context(2), + element_type: ElementType::U32(135246), + }) + ); + assert_eq!( + root_iter.next(), + Some(TLVElement { + tag_type: TagType::Context(3), + element_type: ElementType::Str8l(&[0x73, 0x6d, 0x61, 0x72]), + }) + ); + } + + #[test] + fn test_struct_find_element_mix_values() { + // This is a struct with 3 valid values + let b = [ + 0x15, 0x30, 0x3, 0x04, 0x73, 0x6d, 0x61, 0x72, 0x24, 0x0, 0x2, 0x26, 0x2, 0x4e, 0x10, + 0x02, 0x00, + ]; + let root = get_root_node_struct(&b).unwrap(); + + assert_eq!( + root.find_tag(0).unwrap(), + TLVElement { + tag_type: TagType::Context(0), + element_type: ElementType::U8(2), + } + ); + assert_eq!( + root.find_tag(2).unwrap(), + TLVElement { + tag_type: TagType::Context(2), + element_type: ElementType::U32(135246), + } + ); + assert_eq!( + root.find_tag(3).unwrap(), + TLVElement { + tag_type: TagType::Context(3), + element_type: ElementType::Str8l(&[0x73, 0x6d, 0x61, 0x72]), + } + ); + } + + #[test] + fn test_list_iteration_with_mix_values() { + // This is a list with 3 valid values + let b = [ + 0x17, 0x24, 0x0, 0x2, 0x26, 0x2, 0x4e, 0x10, 0x02, 0x00, 0x30, 0x3, 0x04, 0x73, 0x6d, + 0x61, 0x72, + ]; + let mut root_iter = get_root_node_list(&b).unwrap().enter().unwrap(); + assert_eq!( + root_iter.next(), + Some(TLVElement { + tag_type: TagType::Context(0), + element_type: ElementType::U8(2), + }) + ); + assert_eq!( + root_iter.next(), + Some(TLVElement { + tag_type: TagType::Context(2), + element_type: ElementType::U32(135246), + }) + ); + assert_eq!( + root_iter.next(), + Some(TLVElement { + tag_type: TagType::Context(3), + element_type: ElementType::Str8l(&[0x73, 0x6d, 0x61, 0x72]), + }) + ); + } + + #[test] + fn test_complex_structure_invoke_cmd() { + // This is what we typically get in an invoke command + let b = [ + 0x15, 0x36, 0x0, 0x15, 0x37, 0x0, 0x24, 0x0, 0x2, 0x24, 0x2, 0x6, 0x24, 0x3, 0x1, 0x18, + 0x35, 0x1, 0x18, 0x18, 0x18, 0x18, + ]; + + let root = get_root_node_struct(&b).unwrap(); + + let mut cmd_list_iter = root + .find_tag(0) + .unwrap() + .confirm_array() + .unwrap() + .enter() + .unwrap(); + println!("Command list iterator: {:?}", cmd_list_iter); + + // This is an array of CommandDataIB, but we'll only use the first element + let cmd_data_ib = cmd_list_iter.next().unwrap(); + + let cmd_path = cmd_data_ib.find_tag(0).unwrap().confirm_list().unwrap(); + assert_eq!( + cmd_path.find_tag(0).unwrap(), + TLVElement { + tag_type: TagType::Context(0), + element_type: ElementType::U8(2), + } + ); + assert_eq!( + cmd_path.find_tag(2).unwrap(), + TLVElement { + tag_type: TagType::Context(2), + element_type: ElementType::U8(6), + } + ); + assert_eq!( + cmd_path.find_tag(3).unwrap(), + TLVElement { + tag_type: TagType::Context(3), + element_type: ElementType::U8(1), + } + ); + assert_eq!(cmd_path.find_tag(1), Err(Error::NoTagFound)); + + // This is the variable of the invoke command + assert_eq!( + cmd_data_ib.find_tag(1).unwrap().enter().unwrap().next(), + None + ); + } + + #[test] + fn test_read_past_end_of_container() { + let b = [0x15, 0x35, 0x0, 0x24, 0x1, 0x2, 0x18, 0x24, 0x0, 0x2, 0x18]; + + let mut sub_root_iter = get_root_node_struct(&b) + .unwrap() + .find_tag(0) + .unwrap() + .enter() + .unwrap(); + assert_eq!( + sub_root_iter.next(), + Some(TLVElement { + tag_type: TagType::Context(1), + element_type: ElementType::U8(2), + }) + ); + assert_eq!(sub_root_iter.next(), None); + // Call next, even after the first next returns None + assert_eq!(sub_root_iter.next(), None); + assert_eq!(sub_root_iter.next(), None); + } + + #[test] + fn test_basic_list_iterator() { + // This is the input we have + let b = [ + 0x15, 0x36, 0x0, 0x15, 0x37, 0x0, 0x24, 0x0, 0x2, 0x24, 0x2, 0x6, 0x24, 0x3, 0x1, 0x18, + 0x35, 0x1, 0x18, 0x18, 0x18, 0x18, + ]; + + let dummy_pointer = Pointer { + buf: &b, + current: 1, + left: 21, + }; + // These are the decoded elements that we expect from this input + let verify_matrix: [(TagType, ElementType); 13] = [ + (TagType::Anonymous, ElementType::Struct(dummy_pointer)), + (TagType::Context(0), ElementType::Array(dummy_pointer)), + (TagType::Anonymous, ElementType::Struct(dummy_pointer)), + (TagType::Context(0), ElementType::List(dummy_pointer)), + (TagType::Context(0), ElementType::U8(2)), + (TagType::Context(2), ElementType::U8(6)), + (TagType::Context(3), ElementType::U8(1)), + (TagType::Anonymous, ElementType::EndCnt), + (TagType::Context(1), ElementType::Struct(dummy_pointer)), + (TagType::Anonymous, ElementType::EndCnt), + (TagType::Anonymous, ElementType::EndCnt), + (TagType::Anonymous, ElementType::EndCnt), + (TagType::Anonymous, ElementType::EndCnt), + ]; + + let mut list_iter = TLVList::new(&b).iter(); + let mut index = 0; + loop { + let element = list_iter.next(); + match element { + None => break, + Some(a) => { + assert_eq!(a.tag_type, verify_matrix[index].0); + assert_eq!( + std::mem::discriminant(&a.element_type), + std::mem::discriminant(&verify_matrix[index].1) + ); + } + } + index += 1; + } + // After the end, purposefully try a few more next + assert_eq!(list_iter.next(), None); + assert_eq!(list_iter.next(), None); + } +} diff --git a/matter/src/tlv/traits.rs b/matter/src/tlv/traits.rs new file mode 100644 index 0000000..a4d2ae3 --- /dev/null +++ b/matter/src/tlv/traits.rs @@ -0,0 +1,506 @@ +/* + * + * 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::{ElementType, TLVContainerIterator, TLVElement, TLVWriter, TagType}; +use crate::error::Error; +use core::slice::Iter; +use log::error; + +pub trait FromTLV<'a> { + fn from_tlv(t: &TLVElement<'a>) -> Result + where + Self: Sized; + + // I don't think anybody except Option will define this + fn tlv_not_found() -> Result + where + Self: Sized, + { + Err(Error::TLVNotFound) + } +} + +impl<'a, T: Default + FromTLV<'a> + Copy, const N: usize> FromTLV<'a> for [T; N] { + fn from_tlv(t: &TLVElement<'a>) -> Result + where + Self: Sized, + { + t.confirm_array()?; + let mut a: [T; N] = [Default::default(); N]; + let mut index = 0; + if let Some(tlv_iter) = t.enter() { + for element in tlv_iter { + if index < N { + a[index] = T::from_tlv(&element)?; + index += 1; + } else { + error!("Received TLV Array with elements larger than current size"); + break; + } + } + } + Ok(a) + } +} + +macro_rules! fromtlv_for { + ($($t:ident)*) => { + $( + impl<'a> FromTLV<'a> for $t { + fn from_tlv(t: &TLVElement) -> Result { + t.$t() + } + } + )* + }; +} + +fromtlv_for!(u8 u16 u32 u64 bool); + +pub trait ToTLV { + fn to_tlv(&self, tw: &mut TLVWriter, tag: TagType) -> Result<(), Error>; +} + +macro_rules! totlv_for { + ($($t:ident)*) => { + $( + impl ToTLV for $t { + fn to_tlv(&self, tw: &mut TLVWriter, tag: TagType) -> Result<(), Error> { + tw.$t(tag, *self) + } + } + )* + }; +} + +impl<'a, T: ToTLV, const N: usize> ToTLV for [T; N] { + fn to_tlv(&self, tw: &mut TLVWriter, tag: TagType) -> Result<(), Error> { + tw.start_array(tag)?; + for i in self { + i.to_tlv(tw, TagType::Anonymous)?; + } + tw.end_container() + } +} + +// Generate ToTLV for standard data types +totlv_for!(i8 u8 u16 u32 u64 bool); + +// We define a few common data types that will be required here +// +// - UtfStr, OctetStr: These are versions that map to utfstr and ostr in the TLV spec +// - These only have references into the original list +// - String, Vec: Is the owned version of utfstr and ostr, data is cloned into this +// - String is only partially implemented +// +// - TLVArray: Is an array of entries, with reference within the original list +// - TLVArrayOwned: Is the owned version of this, data is cloned into this + +/// Implements UTFString from the spec +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct UtfStr<'a>(pub &'a [u8]); + +impl<'a> UtfStr<'a> { + pub fn new(str: &'a [u8]) -> Self { + Self(str) + } +} + +impl<'a> ToTLV for UtfStr<'a> { + fn to_tlv(&self, tw: &mut TLVWriter, tag: TagType) -> Result<(), Error> { + tw.utf16(tag, self.0) + } +} + +/// Implements OctetString from the spec +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct OctetStr<'a>(pub &'a [u8]); + +impl<'a> OctetStr<'a> { + pub fn new(str: &'a [u8]) -> Self { + Self(str) + } +} + +impl<'a> FromTLV<'a> for OctetStr<'a> { + fn from_tlv(t: &TLVElement<'a>) -> Result, Error> { + t.slice().map(OctetStr) + } +} + +impl<'a> ToTLV for OctetStr<'a> { + fn to_tlv(&self, tw: &mut TLVWriter, tag: TagType) -> Result<(), Error> { + tw.str16(tag, self.0) + } +} + +/// Implements the Owned version of Octet String +impl FromTLV<'_> for Vec { + fn from_tlv(t: &TLVElement) -> Result, Error> { + t.slice().map(|x| x.to_owned()) + } +} + +impl ToTLV for Vec { + fn to_tlv(&self, tw: &mut TLVWriter, tag: TagType) -> Result<(), Error> { + tw.str16(tag, self.as_slice()) + } +} + +/// Implements the Owned version of UTF String +impl FromTLV<'_> for String { + fn from_tlv(t: &TLVElement) -> Result { + match t.slice() { + Ok(x) => { + if let Ok(s) = String::from_utf8(x.to_vec()) { + Ok(s) + } else { + Err(Error::Invalid) + } + } + Err(e) => Err(e), + } + } +} + +impl ToTLV for String { + fn to_tlv(&self, tw: &mut TLVWriter, tag: TagType) -> Result<(), Error> { + tw.utf16(tag, self.as_bytes()) + } +} + +/// Applies to all the Option<> Processing +impl<'a, T: FromTLV<'a>> FromTLV<'a> for Option { + fn from_tlv(t: &TLVElement<'a>) -> Result, Error> { + Ok(Some(T::from_tlv(t)?)) + } + + fn tlv_not_found() -> Result + where + Self: Sized, + { + Ok(None) + } +} + +impl ToTLV for Option { + fn to_tlv(&self, tw: &mut TLVWriter, tag: TagType) -> Result<(), Error> { + match self { + Some(s) => (s.to_tlv(tw, tag)), + None => Ok(()), + } + } +} + +/// Represent a nullable value +/// +/// The value may be null or a valid value +/// Note: Null is different from Option. If the value is optional, include Option<> too. For +/// example, Option> +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum Nullable { + Null, + NotNull(T), +} + +impl Nullable { + pub fn is_null(&self) -> bool { + match self { + Nullable::Null => true, + Nullable::NotNull(_) => false, + } + } + + pub fn unwrap_notnull(self) -> Option { + match self { + Nullable::Null => None, + Nullable::NotNull(t) => Some(t), + } + } +} + +impl<'a, T: FromTLV<'a>> FromTLV<'a> for Nullable { + fn from_tlv(t: &TLVElement<'a>) -> Result, Error> { + match t.get_element_type() { + ElementType::Null => Ok(Nullable::Null), + _ => Ok(Nullable::NotNull(T::from_tlv(t)?)), + } + } +} + +impl ToTLV for Nullable { + fn to_tlv(&self, tw: &mut TLVWriter, tag: TagType) -> Result<(), Error> { + match self { + Nullable::Null => tw.null(tag), + Nullable::NotNull(s) => s.to_tlv(tw, tag), + } + } +} + +/// Owned version of a TLVArray +pub struct TLVArrayOwned(Vec); +impl<'a, T: FromTLV<'a>> FromTLV<'a> for TLVArrayOwned { + fn from_tlv(t: &TLVElement<'a>) -> Result { + t.confirm_array()?; + let mut vec = Vec::::new(); + if let Some(tlv_iter) = t.enter() { + for element in tlv_iter { + vec.push(T::from_tlv(&element)?); + } + } + Ok(Self(vec)) + } +} + +impl ToTLV for TLVArrayOwned { + fn to_tlv(&self, tw: &mut TLVWriter, tag_type: TagType) -> Result<(), Error> { + tw.start_array(tag_type)?; + for t in &self.0 { + t.to_tlv(tw, TagType::Anonymous)?; + } + tw.end_container() + } +} + +impl TLVArrayOwned { + pub fn iter(&self) -> Iter { + self.0.iter() + } +} + +pub enum TLVArray<'a, T> { + // This is used for the to-tlv path + Slice(&'a [T]), + // This is used for the from-tlv path + Ptr(TLVElement<'a>), +} + +pub enum TLVArrayIter<'a, T> { + Slice(Iter<'a, T>), + Ptr(Option>), +} + +impl<'a, T: ToTLV> TLVArray<'a, T> { + pub fn new(slice: &'a [T]) -> Self { + Self::Slice(slice) + } + + pub fn iter(&self) -> TLVArrayIter<'a, T> { + match *self { + Self::Slice(s) => TLVArrayIter::Slice(s.iter()), + Self::Ptr(p) => TLVArrayIter::Ptr(p.enter()), + } + } +} + +impl<'a, T: ToTLV + FromTLV<'a> + Copy> TLVArray<'a, T> { + pub fn get_index(&self, index: usize) -> T { + let mut curr = 0; + for element in self.iter() { + if curr == index { + return element; + } + curr += 1; + } + panic!("Out of bounds"); + } +} + +impl<'a, T: FromTLV<'a> + Copy> Iterator for TLVArrayIter<'a, T> { + type Item = T; + /* Code for going to the next Element */ + fn next(&mut self) -> Option { + match self { + Self::Slice(s_iter) => s_iter.next().copied(), + Self::Ptr(p_iter) => { + if let Some(tlv_iter) = p_iter.as_mut() { + let e = tlv_iter.next(); + if let Some(element) = e { + T::from_tlv(&element).ok() + } else { + None + } + } else { + None + } + } + } + } +} + +impl<'a, T: ToTLV> ToTLV for TLVArray<'a, T> { + fn to_tlv(&self, tw: &mut TLVWriter, tag_type: TagType) -> Result<(), Error> { + match *self { + Self::Slice(s) => { + tw.start_array(tag_type)?; + for a in s { + a.to_tlv(tw, TagType::Anonymous)?; + } + tw.end_container() + } + Self::Ptr(_) => Err(Error::Invalid), + } + } +} + +impl<'a, T> FromTLV<'a> for TLVArray<'a, T> { + fn from_tlv(t: &TLVElement<'a>) -> Result { + t.confirm_array()?; + Ok(Self::Ptr(*t)) + } +} + +#[cfg(test)] +mod tests { + use super::{FromTLV, OctetStr, TLVElement, TLVWriter, TagType, ToTLV}; + use crate::{error::Error, tlv::TLVList, utils::writebuf::WriteBuf}; + use matter_macro_derive::{FromTLV, ToTLV}; + + #[derive(ToTLV)] + struct TestDerive { + a: u16, + b: u32, + } + #[test] + fn test_derive_totlv() { + let mut buf: [u8; 20] = [0; 20]; + let buf_len = buf.len(); + let mut writebuf = WriteBuf::new(&mut buf, buf_len); + let mut tw = TLVWriter::new(&mut writebuf); + + let abc = TestDerive { + a: 0x1010, + b: 0x20202020, + }; + abc.to_tlv(&mut tw, TagType::Anonymous).unwrap(); + assert_eq!( + buf, + [21, 37, 0, 0x10, 0x10, 38, 1, 0x20, 0x20, 0x20, 0x20, 24, 0, 0, 0, 0, 0, 0, 0, 0] + ); + } + + #[derive(FromTLV)] + struct TestDeriveSimple { + a: u16, + b: u32, + } + + #[test] + fn test_derive_fromtlv() { + let b = [ + 21, 37, 0, 10, 0, 38, 1, 20, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + let root = TLVList::new(&b).iter().next().unwrap(); + let test = TestDeriveSimple::from_tlv(&root).unwrap(); + assert_eq!(test.a, 10); + assert_eq!(test.b, 20); + } + + #[derive(FromTLV)] + #[tlvargs(lifetime = "'a")] + struct TestDeriveStr<'a> { + a: u16, + b: OctetStr<'a>, + } + + #[test] + fn test_derive_fromtlv_str() { + let b = [21, 37, 0, 10, 0, 0x30, 0x01, 0x03, 10, 11, 12, 0]; + let root = TLVList::new(&b).iter().next().unwrap(); + let test = TestDeriveStr::from_tlv(&root).unwrap(); + assert_eq!(test.a, 10); + assert_eq!(test.b, OctetStr(&[10, 11, 12])); + } + + #[derive(FromTLV, Debug)] + struct TestDeriveOption { + a: u16, + b: Option, + c: Option, + } + + #[test] + fn test_derive_fromtlv_option() { + let b = [21, 37, 0, 10, 0, 37, 2, 11, 0]; + let root = TLVList::new(&b).iter().next().unwrap(); + let test = TestDeriveOption::from_tlv(&root).unwrap(); + assert_eq!(test.a, 10); + assert_eq!(test.b, None); + assert_eq!(test.c, Some(11)); + } + + #[derive(FromTLV, ToTLV, Debug)] + struct TestDeriveFabScoped { + a: u16, + #[tagval(0xFE)] + fab_idx: u16, + } + #[test] + fn test_derive_fromtlv_fab_scoped() { + let b = [21, 37, 0, 10, 0, 37, 0xFE, 11, 0]; + let root = TLVList::new(&b).iter().next().unwrap(); + let test = TestDeriveFabScoped::from_tlv(&root).unwrap(); + assert_eq!(test.a, 10); + assert_eq!(test.fab_idx, 11); + } + + #[test] + fn test_derive_totlv_fab_scoped() { + let mut buf: [u8; 20] = [0; 20]; + let buf_len = buf.len(); + let mut writebuf = WriteBuf::new(&mut buf, buf_len); + let mut tw = TLVWriter::new(&mut writebuf); + + let abc = TestDeriveFabScoped { a: 20, fab_idx: 3 }; + + abc.to_tlv(&mut tw, TagType::Anonymous).unwrap(); + assert_eq!( + buf, + [21, 36, 0, 20, 36, 0xFE, 3, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ); + } + + #[derive(ToTLV, FromTLV, PartialEq, Debug)] + enum TestDeriveEnum { + ValueA(u32), + ValueB(u32), + } + + #[test] + fn test_derive_from_to_tlv_enum() { + // Test FromTLV + let b = [21, 36, 0, 100, 24, 0]; + let root = TLVList::new(&b).iter().next().unwrap(); + let mut enum_val = TestDeriveEnum::from_tlv(&root).unwrap(); + assert_eq!(enum_val, TestDeriveEnum::ValueA(100)); + + // Modify the value and test ToTLV + enum_val = TestDeriveEnum::ValueB(10); + + // Test ToTLV + let mut buf: [u8; 20] = [0; 20]; + let buf_len = buf.len(); + let mut writebuf = WriteBuf::new(&mut buf, buf_len); + let mut tw = TLVWriter::new(&mut writebuf); + + enum_val.to_tlv(&mut tw, TagType::Anonymous).unwrap(); + assert_eq!( + buf, + [21, 36, 1, 10, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ); + } +} diff --git a/matter/src/tlv/writer.rs b/matter/src/tlv/writer.rs new file mode 100644 index 0000000..5600237 --- /dev/null +++ b/matter/src/tlv/writer.rs @@ -0,0 +1,322 @@ +/* + * + * 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::{TagType, TAG_SHIFT_BITS, TAG_SIZE_MAP}; +use crate::{error::*, utils::writebuf::WriteBuf}; +use log::error; + +#[allow(dead_code)] +enum WriteElementType { + S8 = 0, + S16 = 1, + S32 = 2, + S64 = 3, + U8 = 4, + U16 = 5, + U32 = 6, + U64 = 7, + False = 8, + True = 9, + F32 = 10, + F64 = 11, + Utf8l = 12, + Utf16l = 13, + Utf32l = 14, + Utf64l = 15, + Str8l = 16, + Str16l = 17, + Str32l = 18, + Str64l = 19, + Null = 20, + Struct = 21, + Array = 22, + List = 23, + EndCnt = 24, + Last, +} + +pub struct TLVWriter<'a, 'b> { + buf: &'b mut WriteBuf<'a>, +} + +impl<'a, 'b> TLVWriter<'a, 'b> { + pub fn new(buf: &'b mut WriteBuf<'a>) -> Self { + TLVWriter { buf } + } + + // TODO: The current method of using writebuf's put methods force us to do + // at max 3 checks while writing a single TLV (once for control, once for tag, + // once for value), so do a single check and write the whole thing. + #[inline(always)] + fn put_control_tag( + &mut self, + tag_type: TagType, + val_type: WriteElementType, + ) -> Result<(), Error> { + let (tag_id, tag_val) = match tag_type { + TagType::Anonymous => (0_u8, 0), + TagType::Context(v) => (1, v as u64), + TagType::CommonPrf16(v) => (2, v as u64), + TagType::CommonPrf32(v) => (3, v as u64), + TagType::ImplPrf16(v) => (4, v as u64), + TagType::ImplPrf32(v) => (5, v as u64), + TagType::FullQual48(v) => (6, v as u64), + TagType::FullQual64(v) => (7, v as u64), + }; + self.buf + .le_u8(((tag_id) << TAG_SHIFT_BITS) | (val_type as u8))?; + if tag_type != TagType::Anonymous { + self.buf.le_uint(TAG_SIZE_MAP[tag_id as usize], tag_val)?; + } + Ok(()) + } + + pub fn i8(&mut self, tag_type: TagType, data: i8) -> Result<(), Error> { + self.put_control_tag(tag_type, WriteElementType::S8)?; + self.buf.le_i8(data) + } + + pub fn u8(&mut self, tag_type: TagType, data: u8) -> Result<(), Error> { + self.put_control_tag(tag_type, WriteElementType::U8)?; + self.buf.le_u8(data) + } + + pub fn u16(&mut self, tag_type: TagType, data: u16) -> Result<(), Error> { + if data <= 0xff { + self.u8(tag_type, data as u8) + } else { + self.put_control_tag(tag_type, WriteElementType::U16)?; + self.buf.le_u16(data) + } + } + + pub fn u32(&mut self, tag_type: TagType, data: u32) -> Result<(), Error> { + if data <= 0xff { + self.u8(tag_type, data as u8) + } else if data <= 0xffff { + self.u16(tag_type, data as u16) + } else { + self.put_control_tag(tag_type, WriteElementType::U32)?; + self.buf.le_u32(data) + } + } + + pub fn u64(&mut self, tag_type: TagType, data: u64) -> Result<(), Error> { + if data <= 0xff { + self.u8(tag_type, data as u8) + } else if data <= 0xffff { + self.u16(tag_type, data as u16) + } else if data <= 0xffffffff { + self.u32(tag_type, data as u32) + } else { + self.put_control_tag(tag_type, WriteElementType::U64)?; + self.buf.le_u64(data) + } + } + + pub fn str8(&mut self, tag_type: TagType, data: &[u8]) -> Result<(), Error> { + if data.len() > 256 { + error!("use put_str16() instead"); + return Err(Error::Invalid); + } + self.put_control_tag(tag_type, WriteElementType::Str8l)?; + self.buf.le_u8(data.len() as u8)?; + self.buf.copy_from_slice(data) + } + + pub fn str16(&mut self, tag_type: TagType, data: &[u8]) -> Result<(), Error> { + if data.len() <= 0xff { + self.str8(tag_type, data) + } else { + self.put_control_tag(tag_type, WriteElementType::Str16l)?; + self.buf.le_u16(data.len() as u16)?; + self.buf.copy_from_slice(data) + } + } + + // This is quite hacky + pub fn str16_as(&mut self, tag_type: TagType, data_gen: F) -> Result<(), Error> + where + F: FnOnce(&mut [u8]) -> Result, + { + let anchor = self.buf.get_tail(); + self.put_control_tag(tag_type, WriteElementType::Str16l)?; + + let wb = self.buf.empty_as_mut_slice(); + // Reserve 2 spaces for the control and length + let str = &mut wb[2..]; + let len = data_gen(str).unwrap_or_default(); + if len <= 0xff { + // Shift everything by 1 + let str = &mut wb[1..]; + for i in 0..len { + str[i] = str[i + 1]; + } + self.buf.rewind_tail_to(anchor); + self.put_control_tag(tag_type, WriteElementType::Str8l)?; + self.buf.le_u8(len as u8)?; + } else { + self.buf.le_u16(len as u16)?; + } + self.buf.forward_tail_by(len); + Ok(()) + } + + pub fn utf8(&mut self, tag_type: TagType, data: &[u8]) -> Result<(), Error> { + self.put_control_tag(tag_type, WriteElementType::Utf8l)?; + self.buf.le_u8(data.len() as u8)?; + self.buf.copy_from_slice(data) + } + + pub fn utf16(&mut self, tag_type: TagType, data: &[u8]) -> Result<(), Error> { + if data.len() <= 0xff { + self.utf8(tag_type, data) + } else { + self.put_control_tag(tag_type, WriteElementType::Utf16l)?; + self.buf.le_u16(data.len() as u16)?; + self.buf.copy_from_slice(data) + } + } + + fn no_val(&mut self, tag_type: TagType, element: WriteElementType) -> Result<(), Error> { + self.put_control_tag(tag_type, element) + } + + pub fn start_struct(&mut self, tag_type: TagType) -> Result<(), Error> { + self.no_val(tag_type, WriteElementType::Struct) + } + + pub fn start_array(&mut self, tag_type: TagType) -> Result<(), Error> { + self.no_val(tag_type, WriteElementType::Array) + } + + pub fn start_list(&mut self, tag_type: TagType) -> Result<(), Error> { + self.no_val(tag_type, WriteElementType::List) + } + + pub fn end_container(&mut self) -> Result<(), Error> { + self.no_val(TagType::Anonymous, WriteElementType::EndCnt) + } + + pub fn null(&mut self, tag_type: TagType) -> Result<(), Error> { + self.no_val(tag_type, WriteElementType::Null) + } + + pub fn bool(&mut self, tag_type: TagType, val: bool) -> Result<(), Error> { + if val { + self.no_val(tag_type, WriteElementType::True) + } else { + self.no_val(tag_type, WriteElementType::False) + } + } + + pub fn get_tail(&self) -> usize { + self.buf.get_tail() + } + + pub fn rewind_to(&mut self, anchor: usize) { + self.buf.rewind_tail_to(anchor); + } +} + +#[cfg(test)] +mod tests { + use super::{TLVWriter, TagType}; + use crate::utils::writebuf::WriteBuf; + + #[test] + fn test_write_success() { + let mut buf: [u8; 20] = [0; 20]; + let buf_len = buf.len(); + let mut writebuf = WriteBuf::new(&mut buf, buf_len); + let mut tw = TLVWriter::new(&mut writebuf); + + tw.start_struct(TagType::Anonymous).unwrap(); + tw.u8(TagType::Anonymous, 12).unwrap(); + tw.u8(TagType::Context(1), 13).unwrap(); + tw.u16(TagType::Anonymous, 0x1212).unwrap(); + tw.u16(TagType::Context(2), 0x1313).unwrap(); + tw.start_array(TagType::Context(3)).unwrap(); + tw.bool(TagType::Anonymous, true).unwrap(); + tw.end_container().unwrap(); + tw.end_container().unwrap(); + assert_eq!( + buf, + [21, 4, 12, 36, 1, 13, 5, 0x12, 0x012, 37, 2, 0x13, 0x13, 54, 3, 9, 24, 24, 0, 0] + ); + } + + #[test] + fn test_write_overflow() { + let mut buf: [u8; 6] = [0; 6]; + let buf_len = buf.len(); + let mut writebuf = WriteBuf::new(&mut buf, buf_len); + let mut tw = TLVWriter::new(&mut writebuf); + + tw.u8(TagType::Anonymous, 12).unwrap(); + tw.u8(TagType::Context(1), 13).unwrap(); + match tw.u16(TagType::Anonymous, 12) { + Ok(_) => panic!("This should have returned error"), + _ => (), + } + match tw.u16(TagType::Context(2), 13) { + Ok(_) => panic!("This should have returned error"), + _ => (), + } + assert_eq!(buf, [4, 12, 36, 1, 13, 4]); + } + + #[test] + fn test_put_str8() { + let mut buf: [u8; 20] = [0; 20]; + let buf_len = buf.len(); + let mut writebuf = WriteBuf::new(&mut buf, buf_len); + let mut tw = TLVWriter::new(&mut writebuf); + + tw.u8(TagType::Context(1), 13).unwrap(); + tw.str8(TagType::Anonymous, &[10, 11, 12, 13, 14]).unwrap(); + tw.u16(TagType::Context(2), 0x1313).unwrap(); + tw.str8(TagType::Context(3), &[20, 21, 22]).unwrap(); + assert_eq!( + buf, + [36, 1, 13, 16, 5, 10, 11, 12, 13, 14, 37, 2, 0x13, 0x13, 48, 3, 3, 20, 21, 22] + ); + } + + #[test] + fn test_put_str16_as() { + let mut buf: [u8; 20] = [0; 20]; + let buf_len = buf.len(); + let mut writebuf = WriteBuf::new(&mut buf, buf_len); + let mut tw = TLVWriter::new(&mut writebuf); + + tw.u8(TagType::Context(1), 13).unwrap(); + tw.str8(TagType::Context(2), &[10, 11, 12, 13, 14]).unwrap(); + tw.str16_as(TagType::Context(3), |buf| { + buf[0] = 10; + buf[1] = 11; + Ok(2) + }) + .unwrap(); + tw.u8(TagType::Context(4), 13).unwrap(); + + assert_eq!( + buf, + [36, 1, 13, 48, 2, 5, 10, 11, 12, 13, 14, 48, 3, 2, 10, 11, 36, 4, 13, 0] + ); + } +} diff --git a/matter/src/transport/exchange.rs b/matter/src/transport/exchange.rs new file mode 100644 index 0000000..1c181c5 --- /dev/null +++ b/matter/src/transport/exchange.rs @@ -0,0 +1,546 @@ +/* + * + * 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 boxslab::{BoxSlab, Slab}; +use colored::*; +use log::{error, info, trace}; +use std::any::Any; +use std::fmt; + +use crate::error::Error; +use crate::secure_channel; + +use heapless::LinearMap; + +use super::packet::PacketPool; +use super::session::CloneData; +use super::{mrp::ReliableMessage, packet::Packet, session::SessionHandle, session::SessionMgr}; + +pub struct ExchangeCtx<'a> { + pub exch: &'a mut Exchange, + pub sess: SessionHandle<'a>, +} + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub enum Role { + Initiator = 0, + Responder = 1, +} + +impl Default for Role { + fn default() -> Self { + Role::Initiator + } +} + +#[derive(Debug, PartialEq)] +enum State { + Open, + Close, +} + +impl Default for State { + fn default() -> Self { + State::Open + } +} + +#[derive(Debug, Default)] +pub struct Exchange { + id: u16, + sess_idx: usize, + role: Role, + state: State, + // Currently I see this primarily used in PASE and CASE. If that is the limited use + // of this, we might move this into a separate data structure, so as not to burden + // all 'exchanges'. + data: Option>, + mrp: ReliableMessage, +} + +impl Exchange { + pub fn new(id: u16, sess_idx: usize, role: Role) -> Exchange { + Exchange { + id, + sess_idx, + role, + state: State::Open, + data: None, + mrp: ReliableMessage::new(), + } + } + + pub fn close(&mut self) { + self.data = None; + self.state = State::Close; + } + + pub fn is_state_open(&self) -> bool { + self.state == State::Open + } + + pub fn is_purgeable(&self) -> bool { + // No Users, No pending ACKs/Retrans + self.state == State::Close && self.mrp.is_empty() + } + + pub fn get_id(&self) -> u16 { + self.id + } + + pub fn get_role(&self) -> Role { + self.role + } + + pub fn set_exchange_data(&mut self, data: Box) { + self.data = Some(data); + } + + pub fn clear_exchange_data(&mut self) { + self.data = None; + } + + pub fn get_exchange_data(&mut self) -> Option<&mut T> { + self.data.as_mut()?.downcast_mut::() + } + + pub fn take_exchange_data(&mut self) -> Option> { + self.data.take()?.downcast::().ok() + } + + fn send( + &mut self, + mut proto_tx: BoxSlab, + session: &mut SessionHandle, + ) -> Result<(), Error> { + trace!("payload: {:x?}", proto_tx.as_borrow_slice()); + info!( + "{} with proto id: {} opcode: {}", + "Sending".blue(), + proto_tx.get_proto_id(), + proto_tx.get_proto_opcode(), + ); + + proto_tx.proto.exch_id = self.id; + if self.role == Role::Initiator { + proto_tx.proto.set_initiator(); + } + + session.pre_send(&mut proto_tx)?; + self.mrp.pre_send(&mut proto_tx)?; + session.send(proto_tx) + } +} + +impl fmt::Display for Exchange { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "exch_id: {:?}, sess_index: {}, role: {:?}, data: {:?}, mrp: {:?}, state: {:?}", + self.id, self.sess_idx, self.role, self.data, self.mrp, self.state + ) + } +} + +pub fn get_role(is_initiator: bool) -> Role { + if is_initiator { + Role::Initiator + } else { + Role::Responder + } +} + +pub fn get_complementary_role(is_initiator: bool) -> Role { + if is_initiator { + Role::Responder + } else { + Role::Initiator + } +} + +const MAX_EXCHANGES: usize = 8; + +#[derive(Default)] +pub struct ExchangeMgr { + // keys: exch-id + exchanges: LinearMap, + sess_mgr: SessionMgr, +} + +pub const MAX_MRP_ENTRIES: usize = 4; + +impl ExchangeMgr { + pub fn new(sess_mgr: SessionMgr) -> Self { + Self { + sess_mgr, + exchanges: Default::default(), + } + } + + pub fn get_sess_mgr(&mut self) -> &mut SessionMgr { + &mut self.sess_mgr + } + + pub fn _get_with_id( + exchanges: &mut LinearMap, + exch_id: u16, + ) -> Option<&mut Exchange> { + exchanges.get_mut(&exch_id) + } + + pub fn get_with_id(&mut self, exch_id: u16) -> Option<&mut Exchange> { + ExchangeMgr::_get_with_id(&mut self.exchanges, exch_id) + } + + fn _get( + exchanges: &mut LinearMap, + sess_idx: usize, + id: u16, + role: Role, + create_new: bool, + ) -> Result<&mut Exchange, Error> { + // I don't prefer that we scan the list twice here (once for contains_key and other) + if !exchanges.contains_key(&(id)) { + if create_new { + // If an exchange doesn't exist, create a new one + info!("Creating new exchange"); + let e = Exchange::new(id, sess_idx, role); + if exchanges.insert(id, e).is_err() { + return Err(Error::NoSpace); + } + } else { + return Err(Error::NoSpace); + } + } + + // At this point, we would either have inserted the record if 'create_new' was set + // or it existed already + if let Some(result) = exchanges.get_mut(&id) { + if result.get_role() == role && sess_idx == result.sess_idx { + Ok(result) + } else { + Err(Error::NoExchange) + } + } else { + error!("This should never happen"); + Err(Error::NoSpace) + } + } + + /// The Exchange Mgr receive is like a big processing function + pub fn recv(&mut self) -> Result, ExchangeCtx)>, Error> { + // Get the session + let (mut proto_rx, index) = self.sess_mgr.recv()?; + + let index = match index { + Some(s) => s, + None => { + // The sessions were full, evict one session, and re-perform post-recv + let evict_index = self.sess_mgr.get_lru(); + self.evict_session(evict_index)?; + info!("Reattempting session creation"); + self.sess_mgr.post_recv(&proto_rx)?.ok_or(Error::Invalid)? + } + }; + let mut session = self.sess_mgr.get_session_handle(index); + + // Decrypt the message + session.recv(&mut proto_rx)?; + + // Get the exchange + let exch = ExchangeMgr::_get( + &mut self.exchanges, + index, + proto_rx.proto.exch_id, + get_complementary_role(proto_rx.proto.is_initiator()), + // We create a new exchange, only if the peer is the initiator + proto_rx.proto.is_initiator(), + )?; + + // Message Reliability Protocol + exch.mrp.recv(&proto_rx)?; + + if exch.is_state_open() { + Ok(Some(( + proto_rx, + ExchangeCtx { + exch, + sess: session, + }, + ))) + } else { + // Instead of an error, we send None here, because it is likely that + // we just processed an acknowledgement that cleared the exchange + Ok(None) + } + } + + pub fn send(&mut self, exch_id: u16, proto_tx: BoxSlab) -> Result<(), Error> { + let exchange = + ExchangeMgr::_get_with_id(&mut self.exchanges, exch_id).ok_or(Error::NoExchange)?; + let mut session = self.sess_mgr.get_session_handle(exchange.sess_idx); + exchange.send(proto_tx, &mut session) + } + + pub fn purge(&mut self) { + let mut to_purge: LinearMap = LinearMap::new(); + + for (exch_id, exchange) in self.exchanges.iter() { + if exchange.is_purgeable() { + let _ = to_purge.insert(*exch_id, ()); + } + } + for (exch_id, _) in to_purge.iter() { + self.exchanges.remove(&*exch_id); + } + } + + pub fn pending_acks(&mut self, expired_entries: &mut LinearMap) { + for (exch_id, exchange) in self.exchanges.iter() { + if exchange.mrp.is_ack_ready() { + expired_entries.insert(*exch_id, ()).unwrap(); + } + } + } + + pub fn evict_session(&mut self, index: usize) -> Result<(), Error> { + info!("Sessions full, vacating session with index: {}", index); + // If we enter here, we have an LRU session that needs to be reclaimed + // As per the spec, we need to send a CLOSE here + + let mut session = self.sess_mgr.get_session_handle(index); + let mut tx = Slab::::new(Packet::new_tx()?).ok_or(Error::NoSpace)?; + secure_channel::common::create_sc_status_report( + &mut tx, + secure_channel::common::SCStatusCodes::CloseSession, + None, + )?; + + if let Some((_, exchange)) = self.exchanges.iter_mut().find(|(_, e)| e.sess_idx == index) { + // Send Close_session on this exchange, and then close the session + // Should this be done for all exchanges? + error!("Sending Close Session"); + exchange.send(tx, &mut session)?; + // TODO: This wouldn't actually send it out, because 'transport' isn't owned yet. + } + + let remove_exchanges: Vec = self + .exchanges + .iter() + .filter_map(|(eid, e)| { + if e.sess_idx == index { + Some(*eid) + } else { + None + } + }) + .collect(); + info!( + "Terminating the following exchanges: {:?}", + remove_exchanges + ); + for exch_id in remove_exchanges { + // Remove from exchange list + self.exchanges.remove(&exch_id); + } + self.sess_mgr.remove(index); + Ok(()) + } + + pub fn add_session(&mut self, clone_data: CloneData) -> Result { + let sess_idx = match self.sess_mgr.clone_session(&clone_data) { + Ok(idx) => idx, + Err(Error::NoSpace) => { + let evict_index = self.sess_mgr.get_lru(); + self.evict_session(evict_index)?; + self.sess_mgr.clone_session(&clone_data)? + } + Err(e) => { + return Err(e); + } + }; + Ok(self.sess_mgr.get_session_handle(sess_idx)) + } +} + +impl fmt::Display for ExchangeMgr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "{{ Session Mgr: {},", self.sess_mgr)?; + writeln!(f, " Exchanges: [")?; + for s in &self.exchanges { + writeln!(f, "{{ {}, }},", s.1)?; + } + writeln!(f, " ]")?; + write!(f, "}}") + } +} + +#[cfg(test)] +mod tests { + + use crate::{ + error::Error, + transport::{ + network::{Address, NetworkInterface}, + session::{CloneData, SessionMgr, SessionMode, MAX_SESSIONS}, + }, + }; + + use super::{ExchangeMgr, Role}; + + #[test] + fn test_purge() { + let sess_mgr = SessionMgr::new(); + let mut mgr = ExchangeMgr::new(sess_mgr); + let _ = ExchangeMgr::_get(&mut mgr.exchanges, 1, 2, Role::Responder, true).unwrap(); + let _ = ExchangeMgr::_get(&mut mgr.exchanges, 1, 3, Role::Responder, true).unwrap(); + + mgr.purge(); + assert_eq!( + ExchangeMgr::_get(&mut mgr.exchanges, 1, 2, Role::Responder, false).is_ok(), + true + ); + assert_eq!( + ExchangeMgr::_get(&mut mgr.exchanges, 1, 3, Role::Responder, false).is_ok(), + true + ); + + // Close e1 + let e1 = ExchangeMgr::_get(&mut mgr.exchanges, 1, 2, Role::Responder, false).unwrap(); + e1.close(); + mgr.purge(); + assert_eq!( + ExchangeMgr::_get(&mut mgr.exchanges, 1, 2, Role::Responder, false).is_ok(), + false + ); + assert_eq!( + ExchangeMgr::_get(&mut mgr.exchanges, 1, 3, Role::Responder, false).is_ok(), + true + ); + } + + fn get_clone_data(peer_sess_id: u16, local_sess_id: u16) -> CloneData { + CloneData::new( + 12341234, + 43211234, + peer_sess_id, + local_sess_id, + Address::default(), + SessionMode::Pase, + ) + } + + fn fill_sessions(mgr: &mut ExchangeMgr, count: usize) { + let mut local_sess_id = 1; + let mut peer_sess_id = 100; + for _ in 1..count { + let clone_data = get_clone_data(peer_sess_id, local_sess_id); + match mgr.add_session(clone_data) { + Ok(s) => (assert_eq!(peer_sess_id, s.get_peer_sess_id())), + Err(Error::NoSpace) => break, + _ => { + panic!("Couldn't, create session"); + } + } + local_sess_id += 1; + peer_sess_id += 1; + } + } + + pub struct DummyNetwork; + impl DummyNetwork { + pub fn new() -> Self { + Self {} + } + } + + impl NetworkInterface for DummyNetwork { + fn recv(&self, _in_buf: &mut [u8]) -> Result<(usize, Address), Error> { + Ok((0, Address::default())) + } + + fn send(&self, _out_buf: &[u8], _addr: Address) -> Result { + Ok(0) + } + } + + #[test] + /// We purposefuly overflow the sessions + /// and when the overflow happens, we confirm that + /// - The sessions are evicted in LRU + /// - The exchanges associated with those sessions are evicted too + fn test_sess_evict() { + let mut sess_mgr = SessionMgr::new(); + let transport = Box::new(DummyNetwork::new()); + sess_mgr.add_network_interface(transport).unwrap(); + let mut mgr = ExchangeMgr::new(sess_mgr); + + fill_sessions(&mut mgr, MAX_SESSIONS + 1); + // Sessions are now full from local session id 1 to 16 + + // Create exchanges for sessions 2 (i.e. session index 1) and 3 (session index 2) + // Exchange IDs are 20 and 30 respectively + let _ = ExchangeMgr::_get(&mut mgr.exchanges, 1, 20, Role::Responder, true).unwrap(); + let _ = ExchangeMgr::_get(&mut mgr.exchanges, 2, 30, Role::Responder, true).unwrap(); + + // Confirm that session ids 1 to MAX_SESSIONS exists + for i in 1..(MAX_SESSIONS + 1) { + assert_eq!(mgr.sess_mgr.get_with_id(i as u16).is_none(), false); + } + // Confirm that the exchanges are around + assert_eq!(mgr.get_with_id(20).is_none(), false); + assert_eq!(mgr.get_with_id(30).is_none(), false); + let mut old_local_sess_id = 1; + let mut new_local_sess_id = 100; + let mut new_peer_sess_id = 200; + + for i in 1..(MAX_SESSIONS + 1) { + // Now purposefully overflow the sessions by adding another session + let session = mgr + .add_session(get_clone_data(new_peer_sess_id, new_local_sess_id)) + .unwrap(); + assert_eq!(session.get_peer_sess_id(), new_peer_sess_id); + + // This should have evicted session with local sess_id + assert_eq!(mgr.sess_mgr.get_with_id(old_local_sess_id).is_none(), true); + + new_local_sess_id += 1; + new_peer_sess_id += 1; + old_local_sess_id += 1; + + match i { + 1 => { + // Both exchanges should exist + assert_eq!(mgr.get_with_id(20).is_none(), false); + assert_eq!(mgr.get_with_id(30).is_none(), false); + } + 2 => { + // Exchange 20 would have been evicted + assert_eq!(mgr.get_with_id(20).is_none(), true); + assert_eq!(mgr.get_with_id(30).is_none(), false); + } + 3 => { + // Exchange 20 and 30 would have been evicted + assert_eq!(mgr.get_with_id(20).is_none(), true); + assert_eq!(mgr.get_with_id(30).is_none(), true); + } + _ => {} + } + } + // println!("Session mgr {}", mgr.sess_mgr); + } +} diff --git a/matter/src/transport/mgr.rs b/matter/src/transport/mgr.rs new file mode 100644 index 0000000..4a241f2 --- /dev/null +++ b/matter/src/transport/mgr.rs @@ -0,0 +1,175 @@ +/* + * + * 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 async_channel::Receiver; +use boxslab::{BoxSlab, Slab}; +use heapless::LinearMap; +use log::{debug, error, info}; + +use crate::error::*; + +use crate::transport::mrp::ReliableMessage; +use crate::transport::packet::PacketPool; +use crate::transport::{exchange, packet::Packet, proto_demux, queue, session, udp}; + +use super::proto_demux::ProtoCtx; +use super::queue::Msg; + +pub struct Mgr { + exch_mgr: exchange::ExchangeMgr, + proto_demux: proto_demux::ProtoDemux, + rx_q: Receiver, +} + +impl Mgr { + pub fn new() -> Result { + let mut sess_mgr = session::SessionMgr::new(); + let udp_transport = Box::new(udp::UdpListener::new()?); + sess_mgr.add_network_interface(udp_transport)?; + Ok(Mgr { + proto_demux: proto_demux::ProtoDemux::new(), + exch_mgr: exchange::ExchangeMgr::new(sess_mgr), + rx_q: queue::WorkQ::init()?, + }) + } + + // Allows registration of different protocols with the Transport/Protocol Demux + pub fn register_protocol( + &mut self, + proto_id_handle: Box, + ) -> Result<(), Error> { + self.proto_demux.register(proto_id_handle) + } + + fn send_to_exchange( + &mut self, + exch_id: u16, + proto_tx: BoxSlab, + ) -> Result<(), Error> { + self.exch_mgr.send(exch_id, proto_tx) + } + + fn handle_rxtx(&mut self) -> Result<(), Error> { + let result = self.exch_mgr.recv().map_err(|e| { + error!("Error in recv: {:?}", e); + e + })?; + + if result.is_none() { + // Nothing to process, return quietly + return Ok(()); + } + // result contains something worth processing, we can safely unwrap + // as we already checked for none above + let (rx, exch_ctx) = result.unwrap(); + + debug!("Exchange is {:?}", exch_ctx.exch); + let tx = Self::new_tx()?; + + let mut proto_ctx = ProtoCtx::new(exch_ctx, rx, tx); + // Proto Dispatch + match self.proto_demux.handle(&mut proto_ctx) { + Ok(r) => { + if let proto_demux::ResponseRequired::No = r { + // We need to send the Ack if reliability is enabled, in this case + return Ok(()); + } + } + Err(e) => { + error!("Error in proto_demux {:?}", e); + return Err(e); + } + } + + let ProtoCtx { + exch_ctx, + rx: _, + tx, + } = proto_ctx; + + // tx_ctx now contains the response payload, send the packet + let exch_id = exch_ctx.exch.get_id(); + self.send_to_exchange(exch_id, tx).map_err(|e| { + error!("Error in sending msg {:?}", e); + e + })?; + + Ok(()) + } + + fn handle_queue_msgs(&mut self) -> Result<(), Error> { + if let Ok(msg) = self.rx_q.try_recv() { + match msg { + Msg::NewSession(clone_data) => { + // If a new session was created, add it + let _ = self + .exch_mgr + .add_session(clone_data) + .map_err(|e| error!("Error adding new session {:?}", e)); + } + _ => { + error!("Queue Message Type not yet handled {:?}", msg); + } + } + } + Ok(()) + } + + pub fn start(&mut self) -> Result<(), Error> { + loop { + // Handle network operations + if self.handle_rxtx().is_err() { + error!("Error in handle_rxtx"); + continue; + } + + if self.handle_queue_msgs().is_err() { + error!("Error in handle_queue_msg"); + continue; + } + + // Handle any pending acknowledgement send + let mut acks_to_send: LinearMap = + LinearMap::new(); + self.exch_mgr.pending_acks(&mut acks_to_send); + for exch_id in acks_to_send.keys() { + info!("Sending MRP Standalone ACK for exch {}", exch_id); + let mut proto_tx = match Self::new_tx() { + Ok(p) => p, + Err(e) => { + error!("Error creating proto_tx {:?}", e); + break; + } + }; + ReliableMessage::prepare_ack(*exch_id, &mut proto_tx); + if let Err(e) = self.send_to_exchange(*exch_id, proto_tx) { + error!("Error in sending Ack {:?}", e); + } + } + + // Handle exchange purging + // This need not be done in each turn of the loop, maybe once in 5 times or so? + self.exch_mgr.purge(); + + info!("Exchange Mgr: {}", self.exch_mgr); + } + } + + fn new_tx() -> Result, Error> { + Slab::::new(Packet::new_tx()?).ok_or(Error::PacketPoolExhaust) + } +} diff --git a/matter/src/transport/mod.rs b/matter/src/transport/mod.rs new file mode 100644 index 0000000..228421e --- /dev/null +++ b/matter/src/transport/mod.rs @@ -0,0 +1,29 @@ +/* + * + * 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. + */ + + +pub mod exchange; +pub mod mgr; +pub mod mrp; +pub mod network; +pub mod packet; +pub mod plain_hdr; +pub mod proto_demux; +pub mod proto_hdr; +pub mod queue; +pub mod session; +pub mod udp; diff --git a/matter/src/transport/mrp.rs b/matter/src/transport/mrp.rs new file mode 100644 index 0000000..22cf9ad --- /dev/null +++ b/matter/src/transport/mrp.rs @@ -0,0 +1,160 @@ +/* + * + * 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::time::Duration; +use std::time::SystemTime; + +use crate::{error::*, secure_channel, transport::packet::Packet}; +use log::error; + +// 200 ms +const MRP_STANDALONE_ACK_TIMEOUT: u64 = 200; + +#[derive(Debug)] +pub struct RetransEntry { + // The msg counter that we are waiting to be acknowledged + msg_ctr: u32, + // This will additionally have retransmission count and periods once we implement it +} + +impl RetransEntry { + pub fn new(msg_ctr: u32) -> Self { + Self { msg_ctr } + } + + pub fn get_msg_ctr(&self) -> u32 { + self.msg_ctr + } +} + +#[derive(Debug, Copy, Clone)] +pub struct AckEntry { + // The msg counter that we should acknowledge + msg_ctr: u32, + // The max time after which this entry must be ACK + ack_timeout: SystemTime, +} + +impl AckEntry { + pub fn new(msg_ctr: u32) -> Result { + if let Some(ack_timeout) = + SystemTime::now().checked_add(Duration::from_millis(MRP_STANDALONE_ACK_TIMEOUT)) + { + Ok(Self { + msg_ctr, + ack_timeout, + }) + } else { + Err(Error::Invalid) + } + } + + pub fn get_msg_ctr(&self) -> u32 { + self.msg_ctr + } + + pub fn has_timed_out(&self) -> bool { + self.ack_timeout > SystemTime::now() + } +} + +#[derive(Default, Debug)] +pub struct ReliableMessage { + retrans: Option, + ack: Option, +} + +impl ReliableMessage { + pub fn new() -> Self { + Self { + ..Default::default() + } + } + + pub fn is_empty(&self) -> bool { + self.retrans.is_none() && self.ack.is_none() + } + + // Check any pending acknowledgements / retransmissions and take action + pub fn is_ack_ready(&self) -> bool { + // Acknowledgements + if let Some(ack_entry) = self.ack { + ack_entry.has_timed_out() + } else { + false + } + } + + pub fn prepare_ack(_exch_id: u16, proto_tx: &mut Packet) { + secure_channel::common::create_mrp_standalone_ack(proto_tx); + } + + pub fn pre_send(&mut self, proto_tx: &mut Packet) -> Result<(), Error> { + // Check if any acknowledgements are pending for this exchange, + + // if so, piggy back in the encoded header here + if let Some(ack_entry) = self.ack { + // Ack Entry exists, set ACK bit and remove from table + proto_tx.proto.set_ack(ack_entry.get_msg_ctr()); + self.ack = None; + } + + if !proto_tx.is_reliable() { + return Ok(()); + } + + if self.retrans.is_some() { + // This indicates there was some existing entry for same sess-id/exch-id, which shouldnt happen + error!("Previous retrans entry for this exchange already exists"); + return Err(Error::Invalid); + } + + self.retrans = Some(RetransEntry::new(proto_tx.plain.ctr)); + Ok(()) + } + + /* A note about Message ACKs, it is a bit asymmetric in the sense that: + * - there can be only one pending ACK per exchange (so this is per-exchange) + * - there can be only one pending retransmission per exchange (so this is per-exchange) + * - duplicate detection should happen per session (obviously), so that part is per-session + */ + pub fn recv(&mut self, proto_rx: &Packet) -> Result<(), Error> { + if proto_rx.proto.is_ack() { + // Handle received Acks + let ack_msg_ctr = proto_rx.proto.get_ack_msg_ctr().ok_or(Error::Invalid)?; + if let Some(entry) = &self.retrans { + if entry.get_msg_ctr() != ack_msg_ctr { + // TODO: XXX Fix this + error!("Mismatch in retrans-table's msg counter and received msg counter: received {}, expected {}. This is expected for the timebeing", ack_msg_ctr, entry.get_msg_ctr()); + } + self.retrans = None; + } + } + + if proto_rx.proto.is_reliable() { + if self.ack.is_some() { + // This indicates there was some existing entry for same sess-id/exch-id, which shouldnt happen + // TODO: As per the spec if this happens, we need to send out the previous ACK and note this new ACK + error!("Previous ACK entry for this exchange already exists"); + return Err(Error::Invalid); + } + + self.ack = Some(AckEntry::new(proto_rx.plain.ctr)?); + } + Ok(()) + } +} diff --git a/matter/src/transport/network.rs b/matter/src/transport/network.rs new file mode 100644 index 0000000..5b398ca --- /dev/null +++ b/matter/src/transport/network.rs @@ -0,0 +1,55 @@ +/* + * + * 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::{ + fmt::{Debug, Display}, + net::{IpAddr, Ipv4Addr, SocketAddr}, +}; + +use crate::error::Error; + +#[derive(PartialEq, Copy, Clone)] +pub enum Address { + Udp(SocketAddr), +} + +impl Default for Address { + fn default() -> Self { + Address::Udp(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8080)) + } +} + +impl Display for Address { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Address::Udp(addr) => writeln!(f, "{}", addr), + } + } +} + +impl Debug for Address { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Address::Udp(addr) => writeln!(f, "{}", addr), + } + } +} + +pub trait NetworkInterface { + fn recv(&self, in_buf: &mut [u8]) -> Result<(usize, Address), Error>; + fn send(&self, out_buf: &[u8], addr: Address) -> Result; +} diff --git a/matter/src/transport/packet.rs b/matter/src/transport/packet.rs new file mode 100644 index 0000000..ecfdcc5 --- /dev/null +++ b/matter/src/transport/packet.rs @@ -0,0 +1,239 @@ +/* + * + * 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 log::{error, trace}; +use std::sync::Mutex; + +use boxslab::box_slab; + +use crate::{ + error::Error, + sys::MAX_PACKET_POOL_SIZE, + utils::{parsebuf::ParseBuf, writebuf::WriteBuf}, +}; + +use super::{ + network::Address, + plain_hdr::{self, PlainHdr}, + proto_hdr::{self, ProtoHdr}, +}; + +pub const MAX_RX_BUF_SIZE: usize = 1583; +type Buffer = [u8; MAX_RX_BUF_SIZE]; + +// TODO: I am not very happy with this construction, need to find another way to do this +pub struct BufferPool { + buffers: [Option; MAX_PACKET_POOL_SIZE], +} + +impl BufferPool { + const INIT: Option = None; + fn get() -> &'static Mutex { + static mut BUFFER_HOLDER: Option> = None; + static ONCE: Once = Once::new(); + unsafe { + ONCE.call_once(|| { + BUFFER_HOLDER = Some(Mutex::new(BufferPool { + buffers: [BufferPool::INIT; MAX_PACKET_POOL_SIZE], + })); + }); + BUFFER_HOLDER.as_ref().unwrap() + } + } + + pub fn alloc() -> Option<(usize, &'static mut Buffer)> { + trace!("Buffer Alloc called\n"); + + let mut pool = BufferPool::get().lock().unwrap(); + for i in 0..MAX_PACKET_POOL_SIZE { + if pool.buffers[i].is_none() { + pool.buffers[i] = Some([0; MAX_RX_BUF_SIZE]); + // Sigh! to by-pass the borrow-checker telling us we are stealing a mutable reference + // from under the lock + // In this case the lock only protects against the setting of Some/None, + // the objects then are independently accessed in a unique way + let buffer = unsafe { &mut *(pool.buffers[i].as_mut().unwrap() as *mut Buffer) }; + return Some((i, buffer)); + } + } + None + } + + pub fn free(index: usize) { + trace!("Buffer Free called\n"); + let mut pool = BufferPool::get().lock().unwrap(); + if pool.buffers[index].is_some() { + pool.buffers[index] = None; + } + } +} + +#[derive(PartialEq)] +enum RxState { + Uninit, + PlainDecode, + ProtoDecode, +} + +enum Direction<'a> { + Tx(WriteBuf<'a>), + Rx(ParseBuf<'a>, RxState), +} + +pub struct Packet<'a> { + pub plain: PlainHdr, + pub proto: ProtoHdr, + pub peer: Address, + data: Direction<'a>, + buffer_index: usize, +} + +impl<'a> Packet<'a> { + const HDR_RESERVE: usize = plain_hdr::max_plain_hdr_len() + proto_hdr::max_proto_hdr_len(); + + pub fn new_rx() -> Result { + let (buffer_index, buffer) = BufferPool::alloc().ok_or(Error::NoSpace)?; + let buf_len = buffer.len(); + Ok(Self { + plain: Default::default(), + proto: Default::default(), + buffer_index, + peer: Address::default(), + data: Direction::Rx(ParseBuf::new(buffer, buf_len), RxState::Uninit), + }) + } + + pub fn new_tx() -> Result { + let (buffer_index, buffer) = BufferPool::alloc().ok_or(Error::NoSpace)?; + let buf_len = buffer.len(); + + let mut wb = WriteBuf::new(buffer, buf_len); + wb.reserve(Packet::HDR_RESERVE)?; + + let mut p = Self { + plain: Default::default(), + proto: Default::default(), + buffer_index, + peer: Address::default(), + data: Direction::Tx(wb), + }; + // Reliability on by default + p.proto.set_reliable(); + Ok(p) + } + + pub fn as_borrow_slice(&mut self) -> &mut [u8] { + match &mut self.data { + Direction::Rx(pb, _) => (pb.as_borrow_slice()), + Direction::Tx(wb) => (wb.as_mut_slice()), + } + } + + pub fn get_parsebuf(&mut self) -> Result<&mut ParseBuf<'a>, Error> { + if let Direction::Rx(pbuf, _) = &mut self.data { + Ok(pbuf) + } else { + Err(Error::Invalid) + } + } + + pub fn get_writebuf(&mut self) -> Result<&mut WriteBuf<'a>, Error> { + if let Direction::Tx(wbuf) = &mut self.data { + Ok(wbuf) + } else { + Err(Error::Invalid) + } + } + + pub fn get_proto_id(&self) -> u16 { + self.proto.proto_id + } + + pub fn set_proto_id(&mut self, proto_id: u16) { + self.proto.proto_id = proto_id; + } + + pub fn get_proto_opcode(&self) -> u8 { + self.proto.proto_opcode + } + + pub fn set_proto_opcode(&mut self, proto_opcode: u8) { + self.proto.proto_opcode = proto_opcode; + } + + pub fn set_reliable(&mut self) { + self.proto.set_reliable() + } + + pub fn unset_reliable(&mut self) { + self.proto.unset_reliable() + } + + pub fn is_reliable(&mut self) -> bool { + self.proto.is_reliable() + } + + pub fn proto_decode(&mut self, peer_nodeid: u64, dec_key: Option<&[u8]>) -> Result<(), Error> { + match &mut self.data { + Direction::Rx(pb, state) => { + if *state == RxState::PlainDecode { + *state = RxState::ProtoDecode; + self.proto + .decrypt_and_decode(&self.plain, pb, peer_nodeid, dec_key) + } else { + error!("Invalid state for proto_decode"); + Err(Error::InvalidState) + } + } + _ => Err(Error::InvalidState), + } + } + + pub fn is_plain_hdr_decoded(&self) -> Result { + match &self.data { + Direction::Rx(_, state) => match state { + RxState::Uninit => Ok(false), + _ => Ok(true), + }, + _ => Err(Error::InvalidState), + } + } + + pub fn plain_hdr_decode(&mut self) -> Result<(), Error> { + match &mut self.data { + Direction::Rx(pb, state) => { + if *state == RxState::Uninit { + *state = RxState::PlainDecode; + self.plain.decode(pb) + } else { + error!("Invalid state for plain_decode"); + Err(Error::InvalidState) + } + } + _ => Err(Error::InvalidState), + } + } +} + +impl<'a> Drop for Packet<'a> { + fn drop(&mut self) { + BufferPool::free(self.buffer_index); + trace!("Dropping Packet......"); + } +} + +box_slab!(PacketPool, Packet<'static>, MAX_PACKET_POOL_SIZE); diff --git a/matter/src/transport/plain_hdr.rs b/matter/src/transport/plain_hdr.rs new file mode 100644 index 0000000..5e54cd1 --- /dev/null +++ b/matter/src/transport/plain_hdr.rs @@ -0,0 +1,125 @@ +/* + * + * 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::error::*; +use crate::utils::parsebuf::ParseBuf; +use crate::utils::writebuf::WriteBuf; +use bitflags::bitflags; +use log::info; + +#[derive(Debug, PartialEq)] +pub enum SessionType { + None, + Encrypted, +} + +impl Default for SessionType { + fn default() -> SessionType { + SessionType::None + } +} + +bitflags! { + #[derive(Default)] + pub struct MsgFlags: u8 { + const DSIZ_UNICAST_NODEID = 0x01; + const DSIZ_GROUPCAST_NODEID = 0x02; + const SRC_ADDR_PRESENT = 0x04; + } +} + +// This is the unencrypted message +#[derive(Debug, Default)] +pub struct PlainHdr { + pub flags: MsgFlags, + pub sess_type: SessionType, + pub sess_id: u16, + pub ctr: u32, + peer_nodeid: Option, +} + +impl PlainHdr { + pub fn set_dest_u64(&mut self, id: u64) { + self.flags |= MsgFlags::DSIZ_UNICAST_NODEID; + self.peer_nodeid = Some(id); + } + + pub fn get_src_u64(&self) -> Option { + if self.flags.contains(MsgFlags::SRC_ADDR_PRESENT) { + self.peer_nodeid + } else { + None + } + } +} + +impl PlainHdr { + // it will have an additional 'message length' field first + pub fn decode(&mut self, msg: &mut ParseBuf) -> Result<(), Error> { + self.flags = MsgFlags::from_bits(msg.le_u8()?).ok_or(Error::Invalid)?; + self.sess_id = msg.le_u16()?; + let _sec_flags = msg.le_u8()?; + self.sess_type = if self.sess_id != 0 { + SessionType::Encrypted + } else { + SessionType::None + }; + self.ctr = msg.le_u32()?; + + if self.flags.contains(MsgFlags::SRC_ADDR_PRESENT) { + self.peer_nodeid = Some(msg.le_u64()?); + } + + info!( + "[decode] flags: {:?}, session type: {:#?}, sess_id: {}, ctr: {}", + self.flags, self.sess_type, self.sess_id, self.ctr + ); + Ok(()) + } + + pub fn encode(&mut self, resp_buf: &mut WriteBuf) -> Result<(), Error> { + resp_buf.le_u8(self.flags.bits())?; + resp_buf.le_u16(self.sess_id)?; + resp_buf.le_u8(0)?; + resp_buf.le_u32(self.ctr)?; + if let Some(d) = self.peer_nodeid { + resp_buf.le_u64(d)?; + } + Ok(()) + } + + pub fn is_encrypted(&self) -> bool { + self.sess_type == SessionType::Encrypted + } +} + +pub const fn max_plain_hdr_len() -> usize { + // [optional] msg len only for TCP + 2 + + // flags + 1 + + // security flags + 1 + + // session ID + 2 + + // message ctr + 4 + + // [optional] source node ID + 8 + + // [optional] destination node ID + 8 +} diff --git a/matter/src/transport/proto_demux.rs b/matter/src/transport/proto_demux.rs new file mode 100644 index 0000000..c2d5500 --- /dev/null +++ b/matter/src/transport/proto_demux.rs @@ -0,0 +1,95 @@ +/* + * + * 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 boxslab::BoxSlab; + +use crate::error::*; + +use super::exchange::ExchangeCtx; +use super::packet::PacketPool; + +const MAX_PROTOCOLS: usize = 4; + +#[derive(PartialEq)] +pub enum ResponseRequired { + Yes, + No, +} +pub struct ProtoDemux { + proto_id_handlers: [Option>; MAX_PROTOCOLS], +} + +/// This is the context in which a receive packet is being processed +pub struct ProtoCtx<'a> { + /// This is the exchange context, that includes the exchange and the session + pub exch_ctx: ExchangeCtx<'a>, + /// This is the received buffer for this transaction + pub rx: BoxSlab, + /// This is the transmit buffer for this transaction + pub tx: BoxSlab, +} + +impl<'a> ProtoCtx<'a> { + pub fn new( + exch_ctx: ExchangeCtx<'a>, + rx: BoxSlab, + tx: BoxSlab, + ) -> Self { + Self { exch_ctx, rx, tx } + } +} + +pub trait HandleProto { + fn handle_proto_id(&mut self, proto_ctx: &mut ProtoCtx) -> Result; + + fn get_proto_id(&self) -> usize; + + fn handle_session_event(&self) -> Result<(), Error> { + Ok(()) + } +} + +impl Default for ProtoDemux { + fn default() -> Self { + Self::new() + } +} + +impl ProtoDemux { + pub fn new() -> ProtoDemux { + ProtoDemux { + proto_id_handlers: [None, None, None, None], + } + } + + pub fn register(&mut self, proto_id_handle: Box) -> Result<(), Error> { + let proto_id = proto_id_handle.get_proto_id(); + self.proto_id_handlers[proto_id] = Some(proto_id_handle); + Ok(()) + } + + pub fn handle(&mut self, proto_ctx: &mut ProtoCtx) -> Result { + let proto_id = proto_ctx.rx.get_proto_id() as usize; + if proto_id >= MAX_PROTOCOLS { + return Err(Error::Invalid); + } + return self.proto_id_handlers[proto_id] + .as_mut() + .ok_or(Error::NoHandler)? + .handle_proto_id(proto_ctx); + } +} diff --git a/matter/src/transport/proto_hdr.rs b/matter/src/transport/proto_hdr.rs new file mode 100644 index 0000000..3eb8570 --- /dev/null +++ b/matter/src/transport/proto_hdr.rs @@ -0,0 +1,324 @@ +/* + * + * 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 bitflags::bitflags; +use std::fmt; + +use crate::transport::plain_hdr; +use crate::utils::parsebuf::ParseBuf; +use crate::utils::writebuf::WriteBuf; +use crate::{crypto, error::*}; + +use log::{info, trace}; + +bitflags! { + #[derive(Default)] + pub struct ExchFlags: u8 { + const VENDOR = 0x10; + const SECEX = 0x08; + const RELIABLE = 0x04; + const ACK = 0x02; + const INITIATOR = 0x01; + } +} + +#[derive(Default)] +pub struct ProtoHdr { + pub exch_id: u16, + pub exch_flags: ExchFlags, + pub proto_id: u16, + pub proto_opcode: u8, + pub proto_vendor_id: Option, + pub ack_msg_ctr: Option, +} + +impl ProtoHdr { + pub fn is_vendor(&self) -> bool { + self.exch_flags.contains(ExchFlags::VENDOR) + } + + pub fn set_vendor(&mut self, proto_vendor_id: u16) { + self.exch_flags |= ExchFlags::RELIABLE; + self.proto_vendor_id = Some(proto_vendor_id); + } + + pub fn is_security_ext(&self) -> bool { + self.exch_flags.contains(ExchFlags::SECEX) + } + + pub fn is_reliable(&self) -> bool { + self.exch_flags.contains(ExchFlags::RELIABLE) + } + + pub fn unset_reliable(&mut self) { + self.exch_flags.remove(ExchFlags::RELIABLE) + } + + pub fn set_reliable(&mut self) { + self.exch_flags |= ExchFlags::RELIABLE; + } + + pub fn is_ack(&self) -> bool { + self.exch_flags.contains(ExchFlags::ACK) + } + + pub fn get_ack_msg_ctr(&self) -> Option { + self.ack_msg_ctr + } + + pub fn set_ack(&mut self, ack_msg_ctr: u32) { + self.exch_flags |= ExchFlags::ACK; + self.ack_msg_ctr = Some(ack_msg_ctr); + } + + pub fn is_initiator(&self) -> bool { + self.exch_flags.contains(ExchFlags::INITIATOR) + } + + pub fn set_initiator(&mut self) { + self.exch_flags |= ExchFlags::INITIATOR; + } + + pub fn decrypt_and_decode( + &mut self, + plain_hdr: &plain_hdr::PlainHdr, + parsebuf: &mut ParseBuf, + peer_nodeid: u64, + dec_key: Option<&[u8]>, + ) -> Result<(), Error> { + if let Some(d) = dec_key { + // We decrypt only if the decryption key is valid + decrypt_in_place(plain_hdr.ctr, peer_nodeid, parsebuf, d)?; + } + + self.exch_flags = ExchFlags::from_bits(parsebuf.le_u8()?).ok_or(Error::Invalid)?; + self.proto_opcode = parsebuf.le_u8()?; + self.exch_id = parsebuf.le_u16()?; + self.proto_id = parsebuf.le_u16()?; + + info!("[decode] {} ", self); + if self.is_vendor() { + self.proto_vendor_id = Some(parsebuf.le_u16()?); + } + if self.is_ack() { + self.ack_msg_ctr = Some(parsebuf.le_u32()?); + } + trace!("[rx payload]: {:x?}", parsebuf.as_borrow_slice()); + Ok(()) + } + + pub fn encode(&mut self, resp_buf: &mut WriteBuf) -> Result<(), Error> { + info!("[encode] {}", self); + resp_buf.le_u8(self.exch_flags.bits())?; + resp_buf.le_u8(self.proto_opcode)?; + resp_buf.le_u16(self.exch_id)?; + resp_buf.le_u16(self.proto_id)?; + if self.is_vendor() { + resp_buf.le_u16(self.proto_vendor_id.ok_or(Error::Invalid)?)?; + } + if self.is_ack() { + resp_buf.le_u32(self.ack_msg_ctr.ok_or(Error::Invalid)?)?; + } + Ok(()) + } +} + +impl fmt::Display for ProtoHdr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut flag_str: String = "".to_owned(); + if self.is_vendor() { + flag_str.push_str("V|"); + } + if self.is_security_ext() { + flag_str.push_str("SX|"); + } + if self.is_reliable() { + flag_str.push_str("R|"); + } + if self.is_ack() { + flag_str.push_str("A|"); + } + if self.is_initiator() { + flag_str.push_str("I|"); + } + write!( + f, + "ExId: {}, Proto: {}, Opcode: {}, Flags: {}", + self.exch_id, self.proto_id, self.proto_opcode, flag_str + ) + } +} + +fn get_iv(recvd_ctr: u32, peer_nodeid: u64, iv: &mut [u8]) -> Result<(), Error> { + // The IV is the source address (64-bit) followed by the message counter (32-bit) + let mut write_buf = WriteBuf::new(iv, iv.len()); + // For some reason, this is 0 in the 'bypass' mode + write_buf.le_u8(0)?; + write_buf.le_u32(recvd_ctr)?; + write_buf.le_u64(peer_nodeid)?; + Ok(()) +} + +pub fn encrypt_in_place( + send_ctr: u32, + peer_nodeid: u64, + plain_hdr: &[u8], + writebuf: &mut WriteBuf, + key: &[u8], +) -> Result<(), Error> { + // IV + let mut iv = [0_u8; crypto::AEAD_NONCE_LEN_BYTES]; + get_iv(send_ctr, peer_nodeid, &mut iv)?; + + // Cipher Text + let tag_space = [0u8; crypto::AEAD_MIC_LEN_BYTES]; + writebuf.append(&tag_space)?; + let cipher_text = writebuf.as_mut_slice(); + + crypto::encrypt_in_place( + key, + &iv, + plain_hdr, + cipher_text, + cipher_text.len() - crypto::AEAD_MIC_LEN_BYTES, + )?; + //println!("Cipher Text: {:x?}", cipher_text); + + Ok(()) +} + +fn decrypt_in_place( + recvd_ctr: u32, + peer_nodeid: u64, + parsebuf: &mut ParseBuf, + key: &[u8], +) -> Result<(), Error> { + // AAD: + // the unencrypted header of this packet + let mut aad = [0_u8; crypto::AEAD_AAD_LEN_BYTES]; + let parsed_slice = parsebuf.parsed_as_slice(); + if parsed_slice.len() == aad.len() { + // The plain_header is variable sized in length, I wonder if the AAD is fixed at 8, or the variable size. + // If so, we need to handle it cleanly here. + aad.copy_from_slice(parsed_slice); + } else { + return Err(Error::InvalidAAD); + } + + // IV: + // the specific way for creating IV is in get_iv + let mut iv = [0_u8; crypto::AEAD_NONCE_LEN_BYTES]; + get_iv(recvd_ctr, peer_nodeid, &mut iv)?; + + let cipher_text = parsebuf.as_borrow_slice(); + //println!("AAD: {:x?}", aad); + //println!("Cipher Text: {:x?}", cipher_text); + //println!("IV: {:x?}", iv); + //println!("Key: {:x?}", key); + + crypto::decrypt_in_place(key, &iv, &aad, cipher_text)?; + // println!("Plain Text: {:x?}", cipher_text); + parsebuf.tail(crypto::AEAD_MIC_LEN_BYTES)?; + Ok(()) +} + +pub const fn max_proto_hdr_len() -> usize { + // exchange flags + 1 + + // protocol opcode + 1 + + // exchange ID + 2 + + // protocol ID + 2 + + // [optional] protocol vendor ID + 2 + + // [optional] acknowledged message counter + 4 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + pub fn test_decrypt_success() { + // These values are captured from an execution run of the chip-tool binary + let recvd_ctr = 15287282; + let mut input_buf: [u8; 71] = [ + 0x0, 0x2, 0x0, 0x0, 0xf2, 0x43, 0xe9, 0x0, 0x31, 0xb5, 0x66, 0xec, 0x8b, 0x5b, 0xf4, + 0x17, 0xe4, 0x80, 0xf3, 0xd5, 0x11, 0x59, 0x19, 0xb5, 0x23, 0x91, 0x35, 0x37, 0xb, + 0xf9, 0xbf, 0x69, 0x55, 0x11, 0x75, 0x87, 0x77, 0x19, 0xfc, 0xf3, 0x5d, 0x4b, 0x47, + 0x1f, 0xb0, 0x5e, 0xbe, 0xb5, 0x10, 0xad, 0xc6, 0x78, 0x94, 0x50, 0xe5, 0xd2, 0xe0, + 0x80, 0xef, 0xa8, 0x3a, 0xf0, 0xa6, 0xaf, 0x1b, 0x2, 0x35, 0xa7, 0xd1, 0xc6, 0x32, + ]; + let input_buf_len = input_buf.len(); + let mut parsebuf = ParseBuf::new(&mut input_buf, input_buf_len); + let key = [ + 0x66, 0x63, 0x31, 0x97, 0x43, 0x9c, 0x17, 0xb9, 0x7e, 0x10, 0xee, 0x47, 0xc8, 0x8, + 0x80, 0x4a, + ]; + + // decrypt_in_place() requires that the plain_text buffer of 8 bytes must be already parsed as AAD, we'll just fake it here + parsebuf.le_u32().unwrap(); + parsebuf.le_u32().unwrap(); + + decrypt_in_place(recvd_ctr, 0, &mut parsebuf, &key).unwrap(); + assert_eq!( + parsebuf.as_slice(), + [ + 0x5, 0x8, 0x70, 0x0, 0x1, 0x0, 0x15, 0x28, 0x0, 0x28, 0x1, 0x36, 0x2, 0x15, 0x37, + 0x0, 0x24, 0x0, 0x0, 0x24, 0x1, 0x30, 0x24, 0x2, 0x2, 0x18, 0x35, 0x1, 0x24, 0x0, + 0x0, 0x2c, 0x1, 0x2, 0x57, 0x57, 0x24, 0x2, 0x3, 0x25, 0x3, 0xb8, 0xb, 0x18, 0x18, + 0x18, 0x18 + ] + ); + } + + #[test] + pub fn test_encrypt_success() { + // These values are captured from an execution run of the chip-tool binary + let send_ctr = 41; + + let mut main_buf: [u8; 52] = [0; 52]; + let main_buf_len = main_buf.len(); + let mut writebuf = WriteBuf::new(&mut main_buf, main_buf_len); + + let plain_hdr: [u8; 8] = [0x0, 0x11, 0x0, 0x0, 0x29, 0x0, 0x0, 0x0]; + + let plain_text: [u8; 28] = [ + 5, 8, 0x58, 0x28, 0x01, 0x00, 0x15, 0x36, 0x00, 0x15, 0x37, 0x00, 0x24, 0x00, 0x01, + 0x24, 0x02, 0x06, 0x24, 0x03, 0x01, 0x18, 0x35, 0x01, 0x18, 0x18, 0x18, 0x18, + ]; + writebuf.append(&plain_text).unwrap(); + + let key = [ + 0x44, 0xd4, 0x3c, 0x91, 0xd2, 0x27, 0xf3, 0xba, 0x08, 0x24, 0xc5, 0xd8, 0x7c, 0xb8, + 0x1b, 0x33, + ]; + + encrypt_in_place(send_ctr, 0, &plain_hdr, &mut writebuf, &key).unwrap(); + assert_eq!( + writebuf.as_slice(), + [ + 189, 83, 250, 121, 38, 87, 97, 17, 153, 78, 243, 20, 36, 11, 131, 142, 136, 165, + 227, 107, 204, 129, 193, 153, 42, 131, 138, 254, 22, 190, 76, 244, 116, 45, 156, + 215, 229, 130, 215, 147, 73, 21, 88, 216 + ] + ); + } +} diff --git a/matter/src/transport/queue.rs b/matter/src/transport/queue.rs new file mode 100644 index 0000000..1507000 --- /dev/null +++ b/matter/src/transport/queue.rs @@ -0,0 +1,68 @@ +/* + * + * 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::sync::Once; + +use async_channel::{bounded, Receiver, Sender}; + +use crate::error::Error; + +use super::session::CloneData; + +#[derive(Debug)] +pub enum Msg { + Tx(), + Rx(), + NewSession(CloneData), +} + +#[derive(Clone)] +pub struct WorkQ { + tx: Sender, +} + +static mut G_WQ: Option = None; +static INIT: Once = Once::new(); + +impl WorkQ { + pub fn init() -> Result, Error> { + let (tx, rx) = bounded::(3); + WorkQ::configure(tx); + Ok(rx) + } + + fn configure(tx: Sender) { + unsafe { + INIT.call_once(|| { + G_WQ = Some(WorkQ { tx }); + }); + } + } + + pub fn get() -> Result { + unsafe { G_WQ.as_ref().cloned().ok_or(Error::Invalid) } + } + + pub fn sync_send(&self, msg: Msg) -> Result<(), Error> { + smol::block_on(self.send(msg)) + } + + pub async fn send(&self, msg: Msg) -> Result<(), Error> { + self.tx.send(msg).await.map_err(|e| e.into()) + } +} diff --git a/matter/src/transport/session.rs b/matter/src/transport/session.rs new file mode 100644 index 0000000..2b41b6e --- /dev/null +++ b/matter/src/transport/session.rs @@ -0,0 +1,587 @@ +/* + * + * 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 core::fmt; +use std::{ + any::Any, + ops::{Deref, DerefMut}, + time::SystemTime, +}; + +use crate::{ + error::*, + transport::{plain_hdr, proto_hdr}, + utils::writebuf::WriteBuf, +}; +use boxslab::{BoxSlab, Slab}; +use colored::*; +use log::{info, trace}; +use rand::Rng; + +use super::{ + network::{Address, NetworkInterface}, + packet::{Packet, PacketPool}, +}; + +const MATTER_AES128_KEY_SIZE: usize = 16; + +#[derive(Debug, PartialEq, Copy, Clone)] +pub enum SessionMode { + // The Case session will capture the local fabric index + Case(u8), + Pase, + PlainText, +} + +impl Default for SessionMode { + fn default() -> Self { + SessionMode::PlainText + } +} + +#[derive(Debug)] +pub struct Session { + peer_addr: Address, + local_nodeid: u64, + peer_nodeid: Option, + // I find the session initiator/responder role getting confused with exchange initiator/responder + // So, we might keep this as enc_key and dec_key for now + dec_key: [u8; MATTER_AES128_KEY_SIZE], + enc_key: [u8; MATTER_AES128_KEY_SIZE], + att_challenge: [u8; MATTER_AES128_KEY_SIZE], + local_sess_id: u16, + peer_sess_id: u16, + msg_ctr: u32, + mode: SessionMode, + data: Option>, + last_use: SystemTime, +} + +#[derive(Debug)] +pub struct CloneData { + pub dec_key: [u8; MATTER_AES128_KEY_SIZE], + pub enc_key: [u8; MATTER_AES128_KEY_SIZE], + pub att_challenge: [u8; MATTER_AES128_KEY_SIZE], + local_sess_id: u16, + peer_sess_id: u16, + local_nodeid: u64, + peer_nodeid: u64, + peer_addr: Address, + mode: SessionMode, +} +impl CloneData { + pub fn new( + local_nodeid: u64, + peer_nodeid: u64, + peer_sess_id: u16, + local_sess_id: u16, + peer_addr: Address, + mode: SessionMode, + ) -> CloneData { + CloneData { + dec_key: [0; MATTER_AES128_KEY_SIZE], + enc_key: [0; MATTER_AES128_KEY_SIZE], + att_challenge: [0; MATTER_AES128_KEY_SIZE], + local_nodeid, + peer_nodeid, + peer_addr, + peer_sess_id, + local_sess_id, + mode, + } + } +} + +const MATTER_MSG_CTR_RANGE: u32 = 0x0fffffff; + +impl Session { + pub fn new(peer_addr: Address, peer_nodeid: Option) -> Session { + Session { + peer_addr, + local_nodeid: 0, + peer_nodeid, + dec_key: [0; MATTER_AES128_KEY_SIZE], + enc_key: [0; MATTER_AES128_KEY_SIZE], + att_challenge: [0; MATTER_AES128_KEY_SIZE], + peer_sess_id: 0, + local_sess_id: 0, + msg_ctr: rand::thread_rng().gen_range(0..MATTER_MSG_CTR_RANGE), + mode: SessionMode::PlainText, + data: None, + last_use: SystemTime::now(), + } + } + + // A new encrypted session always clones from a previous 'new' session + pub fn clone(clone_from: &CloneData) -> Session { + Session { + peer_addr: clone_from.peer_addr, + local_nodeid: clone_from.local_nodeid, + peer_nodeid: Some(clone_from.peer_nodeid), + dec_key: clone_from.dec_key, + enc_key: clone_from.enc_key, + att_challenge: clone_from.att_challenge, + local_sess_id: clone_from.local_sess_id, + peer_sess_id: clone_from.peer_sess_id, + msg_ctr: rand::thread_rng().gen_range(0..MATTER_MSG_CTR_RANGE), + mode: clone_from.mode, + data: None, + last_use: SystemTime::now(), + } + } + + pub fn set_data(&mut self, data: Box) { + self.data = Some(data); + } + + pub fn clear_data(&mut self) { + self.data = None; + } + + pub fn get_data(&mut self) -> Option<&mut T> { + self.data.as_mut()?.downcast_mut::() + } + + pub fn take_data(&mut self) -> Option> { + self.data.take()?.downcast::().ok() + } + + pub fn get_local_sess_id(&self) -> u16 { + self.local_sess_id + } + + #[cfg(test)] + pub fn set_local_sess_id(&mut self, sess_id: u16) { + self.local_sess_id = sess_id; + } + + pub fn get_peer_sess_id(&self) -> u16 { + self.peer_sess_id + } + + pub fn get_peer_addr(&self) -> Address { + self.peer_addr + } + + pub fn is_encrypted(&self) -> bool { + match self.mode { + SessionMode::Case(_) | SessionMode::Pase => true, + SessionMode::PlainText => false, + } + } + + pub fn get_peer_node_id(&self) -> Option { + self.peer_nodeid + } + + pub fn get_local_fabric_idx(&self) -> Option { + match self.mode { + SessionMode::Case(a) => Some(a), + _ => None, + } + } + + pub fn get_session_mode(&self) -> SessionMode { + self.mode + } + + pub fn get_msg_ctr(&mut self) -> u32 { + let ctr = self.msg_ctr; + self.msg_ctr += 1; + ctr + } + + pub fn get_dec_key(&self) -> Option<&[u8]> { + match self.mode { + SessionMode::Case(_) | SessionMode::Pase => Some(&self.dec_key), + SessionMode::PlainText => None, + } + } + + pub fn get_enc_key(&self) -> Option<&[u8]> { + match self.mode { + SessionMode::Case(_) | SessionMode::Pase => Some(&self.enc_key), + SessionMode::PlainText => None, + } + } + + pub fn get_att_challenge(&self) -> &[u8] { + &self.att_challenge + } + + pub fn recv(&mut self, proto_rx: &mut Packet) -> Result<(), Error> { + self.last_use = SystemTime::now(); + proto_rx.proto_decode(self.peer_nodeid.unwrap_or_default(), self.get_dec_key()) + } + + pub fn pre_send(&mut self, proto_tx: &mut Packet) -> Result<(), Error> { + proto_tx.plain.sess_id = self.get_peer_sess_id(); + proto_tx.plain.ctr = self.get_msg_ctr(); + if self.is_encrypted() { + proto_tx.plain.sess_type = plain_hdr::SessionType::Encrypted; + } + Ok(()) + } + + // TODO: Most of this can now be moved into the 'Packet' module + fn do_send(&mut self, proto_tx: &mut Packet) -> Result<(), Error> { + self.last_use = SystemTime::now(); + proto_tx.peer = self.peer_addr; + + // Generate encrypted header + let mut tmp_buf: [u8; proto_hdr::max_proto_hdr_len()] = [0; proto_hdr::max_proto_hdr_len()]; + let mut write_buf = WriteBuf::new(&mut tmp_buf[..], proto_hdr::max_proto_hdr_len()); + proto_tx.proto.encode(&mut write_buf)?; + proto_tx.get_writebuf()?.prepend(write_buf.as_slice())?; + + // Generate plain-text header + if self.mode == SessionMode::PlainText { + if let Some(d) = self.peer_nodeid { + proto_tx.plain.set_dest_u64(d); + } + } + let mut tmp_buf: [u8; plain_hdr::max_plain_hdr_len()] = [0; plain_hdr::max_plain_hdr_len()]; + let mut write_buf = WriteBuf::new(&mut tmp_buf[..], plain_hdr::max_plain_hdr_len()); + proto_tx.plain.encode(&mut write_buf)?; + let plain_hdr_bytes = write_buf.as_slice(); + + trace!("unencrypted packet: {:x?}", proto_tx.as_borrow_slice()); + let ctr = proto_tx.plain.ctr; + let enc_key = self.get_enc_key(); + if let Some(e) = enc_key { + proto_hdr::encrypt_in_place( + ctr, + self.local_nodeid, + plain_hdr_bytes, + proto_tx.get_writebuf()?, + e, + )?; + } + + proto_tx.get_writebuf()?.prepend(plain_hdr_bytes)?; + trace!("Full encrypted packet: {:x?}", proto_tx.as_borrow_slice()); + Ok(()) + } +} + +impl fmt::Display for Session { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "peer: {:?}, peer_nodeid: {:?}, local: {}, remote: {}, msg_ctr: {}, mode: {:?}, ts: {:?}", + self.peer_addr, + self.peer_nodeid, + self.local_sess_id, + self.peer_sess_id, + self.msg_ctr, + self.mode, + self.last_use, + ) + } +} + +pub const MAX_SESSIONS: usize = 16; +pub struct SessionMgr { + next_sess_id: u16, + sessions: [Option; MAX_SESSIONS], + network: Option>, +} + +impl Default for SessionMgr { + fn default() -> Self { + Self::new() + } +} + +impl SessionMgr { + pub fn new() -> SessionMgr { + SessionMgr { + sessions: Default::default(), + next_sess_id: 1, + network: None, + } + } + + pub fn add_network_interface( + &mut self, + interface: Box, + ) -> Result<(), Error> { + if self.network.is_none() { + self.network = Some(interface); + Ok(()) + } else { + Err(Error::Invalid) + } + } + + pub fn mut_by_index(&mut self, index: usize) -> Option<&mut Session> { + self.sessions[index].as_mut() + } + + fn get_next_sess_id(&mut self) -> u16 { + let mut next_sess_id: u16; + loop { + next_sess_id = self.next_sess_id; + + // Increment next sess id + self.next_sess_id = self.next_sess_id.overflowing_add(1).0; + if self.next_sess_id == 0 { + self.next_sess_id = 1; + } + + // Ensure the currently selected id doesn't match any existing session + if self.get_with_id(next_sess_id).is_none() { + break; + } + } + next_sess_id + } + + fn get_empty_slot(&self) -> Option { + self.sessions.iter().position(|x| x.is_none()) + } + + pub fn get_lru(&mut self) -> usize { + let mut lru_index = 0; + let mut lru_ts = SystemTime::now(); + for i in 0..MAX_SESSIONS { + if let Some(s) = &self.sessions[i] { + if s.last_use < lru_ts { + lru_ts = s.last_use; + lru_index = i; + } + } + } + lru_index + } + + pub fn add(&mut self, peer_addr: Address, peer_nodeid: Option) -> Result { + let session = Session::new(peer_addr, peer_nodeid); + self.add_session(session) + } + + /// This assumes that the higher layer has taken care of doing anything required + /// as per the spec before the session is erased + pub fn remove(&mut self, idx: usize) { + self.sessions[idx] = None; + } + + /// We could have returned a SessionHandle here. But the borrow checker doesn't support + /// non-lexical lifetimes. This makes it harder for the caller of this function to take + /// action in the error return path + pub fn add_session(&mut self, session: Session) -> Result { + if let Some(index) = self.get_empty_slot() { + self.sessions[index] = Some(session); + Ok(index) + } else { + Err(Error::NoSpace) + } + } + + pub fn clone_session(&mut self, clone_data: &CloneData) -> Result { + let session = Session::clone(clone_data); + self.add_session(session) + } + + fn _get( + &self, + sess_id: u16, + peer_addr: Address, + peer_nodeid: Option, + is_encrypted: bool, + ) -> Option { + self.sessions.iter().position(|x| { + if let Some(x) = x { + let mut nodeid_matches = true; + if x.peer_nodeid.is_some() && peer_nodeid.is_some() && x.peer_nodeid != peer_nodeid + { + nodeid_matches = false; + } + x.local_sess_id == sess_id + && x.peer_addr == peer_addr + && x.is_encrypted() == is_encrypted + && nodeid_matches + } else { + false + } + }) + } + + pub fn get_with_id(&mut self, sess_id: u16) -> Option { + let index = self + .sessions + .iter_mut() + .position(|x| x.as_ref().map(|s| s.local_sess_id) == Some(sess_id))?; + Some(self.get_session_handle(index)) + } + + pub fn get_or_add( + &mut self, + sess_id: u16, + peer_addr: Address, + peer_nodeid: Option, + is_encrypted: bool, + ) -> Result { + if let Some(index) = self._get(sess_id, peer_addr, peer_nodeid, is_encrypted) { + Ok(index) + } else if sess_id == 0 && !is_encrypted { + // We must create a new session for this case + info!("Creating new session"); + self.add(peer_addr, peer_nodeid) + } else { + Err(Error::NotFound) + } + } + + // We will try to get a session for this Packet. If no session exists, we will try to add one + // If the session list is full we will return a None + pub fn post_recv(&mut self, rx: &Packet) -> Result, Error> { + let sess_index = match self.get_or_add( + rx.plain.sess_id, + rx.peer, + rx.plain.get_src_u64(), + rx.plain.is_encrypted(), + ) { + Ok(s) => Some(s), + Err(Error::NoSpace) => None, + Err(e) => { + return Err(e); + } + }; + Ok(sess_index) + } + + pub fn recv(&mut self) -> Result<(BoxSlab, Option), Error> { + let mut rx = Slab::::new(Packet::new_rx()?).ok_or(Error::PacketPoolExhaust)?; + + let network = self.network.as_ref().ok_or(Error::NoNetworkInterface)?; + + let (len, src) = network.recv(rx.as_borrow_slice())?; + rx.get_parsebuf()?.set_len(len); + rx.peer = src; + + info!("{} from src: {}", "Received".blue(), src); + trace!("payload: {:x?}", rx.as_borrow_slice()); + + // Read unencrypted packet header + rx.plain_hdr_decode()?; + + // Get session + let sess_handle = self.post_recv(&rx)?; + Ok((rx, sess_handle)) + } + + pub fn send( + &mut self, + sess_idx: usize, + mut proto_tx: BoxSlab, + ) -> Result<(), Error> { + self.sessions[sess_idx] + .as_mut() + .ok_or(Error::NoSession)? + .do_send(&mut proto_tx)?; + + let network = self.network.as_ref().ok_or(Error::NoNetworkInterface)?; + let peer = proto_tx.peer; + network.send(proto_tx.as_borrow_slice(), peer)?; + println!("Message Sent to {}", peer); + Ok(()) + } + + pub fn get_session_handle(&mut self, sess_idx: usize) -> SessionHandle { + SessionHandle { + sess_mgr: self, + sess_idx, + } + } +} + +impl fmt::Display for SessionMgr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "{{[")?; + for s in self.sessions.iter().flatten() { + writeln!(f, "{{ {}, }},", s)?; + } + write!(f, "], next_sess_id: {}", self.next_sess_id)?; + write!(f, "}}") + } +} + +pub struct SessionHandle<'a> { + sess_mgr: &'a mut SessionMgr, + sess_idx: usize, +} + +impl<'a> SessionHandle<'a> { + pub fn reserve_new_sess_id(&mut self) -> u16 { + self.sess_mgr.get_next_sess_id() + } + + pub fn send(&mut self, proto_tx: BoxSlab) -> Result<(), Error> { + self.sess_mgr.send(self.sess_idx, proto_tx) + } +} + +impl<'a> Deref for SessionHandle<'a> { + type Target = Session; + fn deref(&self) -> &Self::Target { + // There is no other option but to panic if this is None + self.sess_mgr.sessions[self.sess_idx].as_ref().unwrap() + } +} + +impl<'a> DerefMut for SessionHandle<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + // There is no other option but to panic if this is None + self.sess_mgr.sessions[self.sess_idx].as_mut().unwrap() + } +} + +#[cfg(test)] +mod tests { + + use crate::transport::network::Address; + + use super::SessionMgr; + + #[test] + fn test_next_sess_id_doesnt_reuse() { + let mut sm = SessionMgr::new(); + let sess_idx = sm.add(Address::default(), None).unwrap(); + let mut sess = sm.get_session_handle(sess_idx); + sess.set_local_sess_id(1); + assert_eq!(sm.get_next_sess_id(), 2); + assert_eq!(sm.get_next_sess_id(), 3); + let sess_idx = sm.add(Address::default(), None).unwrap(); + let mut sess = sm.get_session_handle(sess_idx); + sess.set_local_sess_id(4); + assert_eq!(sm.get_next_sess_id(), 5); + } + + #[test] + fn test_next_sess_id_overflows() { + let mut sm = SessionMgr::new(); + let sess_idx = sm.add(Address::default(), None).unwrap(); + let mut sess = sm.get_session_handle(sess_idx); + sess.set_local_sess_id(1); + assert_eq!(sm.get_next_sess_id(), 2); + sm.next_sess_id = 65534; + assert_eq!(sm.get_next_sess_id(), 65534); + assert_eq!(sm.get_next_sess_id(), 65535); + assert_eq!(sm.get_next_sess_id(), 2); + } +} diff --git a/matter/src/transport/udp.rs b/matter/src/transport/udp.rs new file mode 100644 index 0000000..8abe9af --- /dev/null +++ b/matter/src/transport/udp.rs @@ -0,0 +1,57 @@ +/* + * + * 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::error::*; +use smol::net::{Ipv6Addr, UdpSocket}; + +use super::network::{Address, NetworkInterface}; + +// We could get rid of the smol here, but keeping it around in case we have to process +// any other events in this thread's context +pub struct UdpListener { + socket: UdpSocket, +} + +// Currently matches with the one in connectedhomeip repo +pub const MAX_RX_BUF_SIZE: usize = 1583; + +/* The Matter Port */ +pub const MATTER_PORT: u16 = 5540; + +impl UdpListener { + pub fn new() -> Result { + Ok(UdpListener { + socket: smol::block_on(UdpSocket::bind((Ipv6Addr::UNSPECIFIED, MATTER_PORT)))?, + }) + } +} + +impl NetworkInterface for UdpListener { + fn recv(&self, in_buf: &mut [u8]) -> Result<(usize, Address), Error> { + let (size, addr) = smol::block_on(self.socket.recv_from(in_buf)).map_err(|e| { + println!("Error on the network: {:?}", e); + Error::Network + })?; + Ok((size, Address::Udp(addr))) + } + + fn send(&self, out_buf: &[u8], addr: Address) -> Result { + match addr { + Address::Udp(addr) => Ok(smol::block_on(self.socket.send_to(out_buf, addr))?), + } + } +} diff --git a/matter/src/utils/mod.rs b/matter/src/utils/mod.rs new file mode 100644 index 0000000..9fc44a8 --- /dev/null +++ b/matter/src/utils/mod.rs @@ -0,0 +1,19 @@ +/* + * + * 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. + */ + +pub mod parsebuf; +pub mod writebuf; diff --git a/matter/src/utils/parsebuf.rs b/matter/src/utils/parsebuf.rs new file mode 100644 index 0000000..e0dcbf8 --- /dev/null +++ b/matter/src/utils/parsebuf.rs @@ -0,0 +1,182 @@ +/* + * + * 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::error::*; +use byteorder::{ByteOrder, LittleEndian}; + +pub struct ParseBuf<'a> { + buf: &'a mut [u8], + read_off: usize, + left: usize, +} + +impl<'a> ParseBuf<'a> { + pub fn new(buf: &'a mut [u8], len: usize) -> ParseBuf<'a> { + ParseBuf { + buf: &mut buf[..len], + read_off: 0, + left: len, + } + } + + pub fn set_len(&mut self, left: usize) { + self.left = left; + } + + // Return the data that is valid as a slice, consume self + pub fn as_slice(self) -> &'a mut [u8] { + &mut self.buf[self.read_off..(self.read_off + self.left)] + } + + // Return the data that is valid as a slice + pub fn as_borrow_slice(&mut self) -> &mut [u8] { + &mut self.buf[self.read_off..(self.read_off + self.left)] + } + + pub fn parsed_as_slice(&self) -> &[u8] { + &self.buf[0..self.read_off] + } + + pub fn tail(&mut self, size: usize) -> Result<&[u8], Error> { + if size <= self.left { + let end_offset = self.read_off + self.left; + let tail = &self.buf[(end_offset - size)..end_offset]; + self.left -= size; + return Ok(tail); + } + Err(Error::TruncatedPacket) + } + + fn advance(&mut self, len: usize) { + self.read_off += len; + self.left -= len; + } + + pub fn parse_head_with(&mut self, size: usize, f: F) -> Result + where + F: FnOnce(&mut Self) -> T, + { + if self.left >= size { + let data: T = f(self); + self.advance(size); + return Ok(data); + } + Err(Error::TruncatedPacket) + } + + pub fn le_u8(&mut self) -> Result { + self.parse_head_with(1, |x| x.buf[x.read_off]) + } + + pub fn le_u16(&mut self) -> Result { + self.parse_head_with(2, |x| LittleEndian::read_u16(&x.buf[x.read_off..])) + } + + pub fn le_u32(&mut self) -> Result { + self.parse_head_with(4, |x| LittleEndian::read_u32(&x.buf[x.read_off..])) + } + + pub fn le_u64(&mut self) -> Result { + self.parse_head_with(8, |x| LittleEndian::read_u64(&x.buf[x.read_off..])) + } +} + +#[cfg(test)] +mod tests { + use crate::utils::parsebuf::*; + + #[test] + fn test_parse_with_success() { + let mut test_slice: [u8; 11] = [0x01, 65, 0, 0xbe, 0xba, 0xfe, 0xca, 0xa, 0xb, 0xc, 0xd]; + let mut buf = ParseBuf::new(&mut test_slice, 11); + + assert_eq!(buf.le_u8().unwrap(), 0x01); + assert_eq!(buf.le_u16().unwrap(), 65); + assert_eq!(buf.le_u32().unwrap(), 0xcafebabe); + assert_eq!(buf.as_slice(), [0xa, 0xb, 0xc, 0xd]); + } + + #[test] + fn test_parse_with_overrun() { + let mut test_slice: [u8; 2] = [0x01, 65]; + let mut buf = ParseBuf::new(&mut test_slice, 2); + + assert_eq!(buf.le_u8().unwrap(), 0x01); + + match buf.le_u16() { + Ok(_) => panic!("This should have returned error"), + Err(_) => (), + } + + match buf.le_u32() { + Ok(_) => panic!("This should have returned error"), + Err(_) => (), + } + + // Now consume the leftover byte + assert_eq!(buf.le_u8().unwrap(), 65); + + match buf.le_u8() { + Ok(_) => panic!("This should have returned error"), + Err(_) => (), + } + assert_eq!(buf.as_slice(), []); + } + + #[test] + fn test_tail_with_success() { + let mut test_slice: [u8; 11] = [0x01, 65, 0, 0xbe, 0xba, 0xfe, 0xca, 0xa, 0xb, 0xc, 0xd]; + let mut buf = ParseBuf::new(&mut test_slice, 11); + + assert_eq!(buf.le_u8().unwrap(), 0x01); + assert_eq!(buf.le_u16().unwrap(), 65); + assert_eq!(buf.le_u32().unwrap(), 0xcafebabe); + + assert_eq!(buf.tail(2).unwrap(), [0xc, 0xd]); + assert_eq!(buf.as_borrow_slice(), [0xa, 0xb]); + + assert_eq!(buf.tail(2).unwrap(), [0xa, 0xb]); + assert_eq!(buf.as_slice(), []); + } + + #[test] + fn test_tail_with_overrun() { + let mut test_slice: [u8; 11] = [0x01, 65, 0, 0xbe, 0xba, 0xfe, 0xca, 0xa, 0xb, 0xc, 0xd]; + let mut buf = ParseBuf::new(&mut test_slice, 11); + + assert_eq!(buf.le_u8().unwrap(), 0x01); + assert_eq!(buf.le_u16().unwrap(), 65); + assert_eq!(buf.le_u32().unwrap(), 0xcafebabe); + match buf.tail(5) { + Ok(_) => panic!("This should have returned error"), + Err(_) => (), + } + assert_eq!(buf.tail(2).unwrap(), [0xc, 0xd]); + } + + #[test] + fn test_parsed_as_slice() { + let mut test_slice: [u8; 11] = [0x01, 65, 0, 0xbe, 0xba, 0xfe, 0xca, 0xa, 0xb, 0xc, 0xd]; + let mut buf = ParseBuf::new(&mut test_slice, 11); + + assert_eq!(buf.parsed_as_slice(), []); + assert_eq!(buf.le_u8().unwrap(), 0x1); + assert_eq!(buf.le_u16().unwrap(), 65); + assert_eq!(buf.le_u32().unwrap(), 0xcafebabe); + assert_eq!(buf.parsed_as_slice(), [0x01, 65, 0, 0xbe, 0xba, 0xfe, 0xca]); + } +} diff --git a/matter/src/utils/writebuf.rs b/matter/src/utils/writebuf.rs new file mode 100644 index 0000000..1b075cc --- /dev/null +++ b/matter/src/utils/writebuf.rs @@ -0,0 +1,326 @@ +/* + * + * 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::error::*; +use byteorder::{ByteOrder, LittleEndian}; + +#[derive(Debug)] +pub struct WriteBuf<'a> { + buf: &'a mut [u8], + start: usize, + end: usize, +} + +impl<'a> WriteBuf<'a> { + pub fn new(buf: &'a mut [u8], len: usize) -> WriteBuf<'a> { + WriteBuf { + buf: &mut buf[..len], + start: 0, + end: 0, + } + } + + pub fn get_tail(&self) -> usize { + self.end + } + + pub fn rewind_tail_to(&mut self, new_end: usize) { + self.end = new_end; + } + + pub fn forward_tail_by(&mut self, new_offset: usize) { + self.end += new_offset + } + + pub fn as_borrow_slice(&self) -> &[u8] { + &self.buf[self.start..self.end] + } + + pub fn as_slice(self) -> &'a [u8] { + &self.buf[self.start..self.end] + } + + pub fn as_mut_slice(&mut self) -> &mut [u8] { + &mut self.buf[self.start..self.end] + } + + pub fn empty_as_mut_slice(&mut self) -> &mut [u8] { + &mut self.buf[self.end..] + } + + pub fn reset(&mut self, reserve: usize) { + self.start = reserve; + self.end = reserve; + } + + pub fn reserve(&mut self, reserve: usize) -> Result<(), Error> { + if self.end != 0 || self.start != 0 { + return Err(Error::Invalid); + } + self.reset(reserve); + Ok(()) + } + + pub fn prepend_with(&mut self, size: usize, f: F) -> Result<(), Error> + where + F: FnOnce(&mut Self), + { + if size <= self.start { + f(self); + self.start -= size; + return Ok(()); + } + Err(Error::NoSpace) + } + + pub fn prepend(&mut self, src: &[u8]) -> Result<(), Error> { + self.prepend_with(src.len(), |x| { + let dst_slice = &mut x.buf[(x.start - src.len())..x.start]; + dst_slice.copy_from_slice(src); + }) + } + + pub fn append_with(&mut self, size: usize, f: F) -> Result<(), Error> + where + F: FnOnce(&mut Self), + { + if self.end + size <= self.buf.len() { + f(self); + self.end += size; + return Ok(()); + } + Err(Error::NoSpace) + } + + pub fn append(&mut self, src: &[u8]) -> Result<(), Error> { + self.copy_from_slice(src) + } + + pub fn copy_from_slice(&mut self, src: &[u8]) -> Result<(), Error> { + self.append_with(src.len(), |x| { + x.buf[x.end..(x.end + src.len())].copy_from_slice(src); + }) + } + + pub fn le_i8(&mut self, data: i8) -> Result<(), Error> { + self.le_u8(data as u8) + } + + pub fn le_u8(&mut self, data: u8) -> Result<(), Error> { + self.append_with(1, |x| { + x.buf[x.end] = data; + }) + } + + pub fn le_u16(&mut self, data: u16) -> Result<(), Error> { + self.append_with(2, |x| { + LittleEndian::write_u16(&mut x.buf[x.end..], data); + }) + } + + pub fn le_u32(&mut self, data: u32) -> Result<(), Error> { + self.append_with(4, |x| { + LittleEndian::write_u32(&mut x.buf[x.end..], data); + }) + } + + pub fn le_u64(&mut self, data: u64) -> Result<(), Error> { + self.append_with(8, |x| { + LittleEndian::write_u64(&mut x.buf[x.end..], data); + }) + } + + pub fn le_uint(&mut self, nbytes: usize, data: u64) -> Result<(), Error> { + self.append_with(nbytes, |x| { + LittleEndian::write_uint(&mut x.buf[x.end..], data, nbytes); + }) + } +} + +#[cfg(test)] +mod tests { + use crate::utils::writebuf::*; + + #[test] + fn test_append_le_with_success() { + let mut test_slice: [u8; 22] = [0; 22]; + let test_slice_len = test_slice.len(); + let mut buf = WriteBuf::new(&mut test_slice, test_slice_len); + buf.reserve(5).unwrap(); + + buf.le_u8(1).unwrap(); + buf.le_u16(65).unwrap(); + buf.le_u32(0xcafebabe).unwrap(); + buf.le_u64(0xcafebabecafebabe).unwrap(); + buf.le_uint(2, 64).unwrap(); + assert_eq!( + test_slice, + [ + 0, 0, 0, 0, 0, 1, 65, 0, 0xbe, 0xba, 0xfe, 0xca, 0xbe, 0xba, 0xfe, 0xca, 0xbe, + 0xba, 0xfe, 0xca, 64, 0 + ] + ); + } + + #[test] + fn test_len_param() { + let mut test_slice: [u8; 20] = [0; 20]; + let mut buf = WriteBuf::new(&mut test_slice, 5); + buf.reserve(5).unwrap(); + + let _ = buf.le_u8(1); + let _ = buf.le_u16(65); + let _ = buf.le_u32(0xcafebabe); + let _ = buf.le_u64(0xcafebabecafebabe); + // All of the above must return error, and hence the slice shouldn't change + assert_eq!(test_slice, [0; 20]); + } + + #[test] + fn test_overrun() { + let mut test_slice: [u8; 20] = [0; 20]; + let mut buf = WriteBuf::new(&mut test_slice, 20); + buf.reserve(4).unwrap(); + buf.le_u64(0xcafebabecafebabe).unwrap(); + buf.le_u64(0xcafebabecafebabe).unwrap(); + // Now the buffer is fully filled up, so no further puts will happen + + match buf.le_u8(1) { + Ok(_) => panic!("Should return error"), + _ => (), + } + + match buf.le_u16(65) { + Ok(_) => panic!("Should return error"), + _ => (), + } + + match buf.le_u32(0xcafebabe) { + Ok(_) => panic!("Should return error"), + _ => (), + } + + match buf.le_u64(0xcafebabecafebabe) { + Ok(_) => panic!("Should return error"), + _ => (), + } + } + + #[test] + fn test_as_slice() { + let mut test_slice: [u8; 20] = [0; 20]; + let mut buf = WriteBuf::new(&mut test_slice, 20); + buf.reserve(5).unwrap(); + + buf.le_u8(1).unwrap(); + buf.le_u16(65).unwrap(); + buf.le_u32(0xcafebabe).unwrap(); + buf.le_u64(0xcafebabecafebabe).unwrap(); + + let new_slice: [u8; 3] = [0xa, 0xb, 0xc]; + buf.prepend(&new_slice).unwrap(); + + assert_eq!( + buf.as_slice(), + [ + 0xa, 0xb, 0xc, 1, 65, 0, 0xbe, 0xba, 0xfe, 0xca, 0xbe, 0xba, 0xfe, 0xca, 0xbe, + 0xba, 0xfe, 0xca + ] + ); + } + + #[test] + fn test_copy_as_slice() { + let mut test_slice: [u8; 20] = [0; 20]; + let mut buf = WriteBuf::new(&mut test_slice, 20); + buf.reserve(5).unwrap(); + + buf.le_u16(65).unwrap(); + let new_slice: [u8; 5] = [0xaa, 0xbb, 0xcc, 0xdd, 0xee]; + buf.copy_from_slice(&new_slice).unwrap(); + buf.le_u32(65).unwrap(); + assert_eq!( + test_slice, + [0, 0, 0, 0, 0, 65, 0, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 65, 0, 0, 0, 0, 0, 0, 0] + ); + } + + #[test] + fn test_copy_as_slice_overrun() { + let mut test_slice: [u8; 20] = [0; 20]; + let mut buf = WriteBuf::new(&mut test_slice, 7); + buf.reserve(5).unwrap(); + + buf.le_u16(65).unwrap(); + let new_slice: [u8; 5] = [0xaa, 0xbb, 0xcc, 0xdd, 0xee]; + match buf.copy_from_slice(&new_slice) { + Ok(_) => panic!("This should have returned error"), + Err(_) => (), + } + } + + #[test] + fn test_prepend() { + let mut test_slice: [u8; 20] = [0; 20]; + let mut buf = WriteBuf::new(&mut test_slice, 20); + buf.reserve(5).unwrap(); + + buf.le_u16(65).unwrap(); + let new_slice: [u8; 5] = [0xaa, 0xbb, 0xcc, 0xdd, 0xee]; + buf.prepend(&new_slice).unwrap(); + assert_eq!( + test_slice, + [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ); + } + + #[test] + fn test_prepend_overrun() { + let mut test_slice: [u8; 20] = [0; 20]; + let mut buf = WriteBuf::new(&mut test_slice, 20); + buf.reserve(5).unwrap(); + + buf.le_u16(65).unwrap(); + let new_slice: [u8; 6] = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]; + match buf.prepend(&new_slice) { + Ok(_) => panic!("Prepend should return error"), + Err(_) => (), + } + } + + #[test] + fn test_rewind_tail() { + let mut test_slice: [u8; 20] = [0; 20]; + let mut buf = WriteBuf::new(&mut test_slice, 20); + buf.reserve(5).unwrap(); + + buf.le_u16(65).unwrap(); + + let anchor = buf.get_tail(); + + let new_slice: [u8; 5] = [0xaa, 0xbb, 0xcc, 0xdd, 0xee]; + buf.copy_from_slice(&new_slice).unwrap(); + assert_eq!( + buf.as_borrow_slice(), + [65, 0, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,] + ); + + buf.rewind_tail_to(anchor); + buf.le_u16(66).unwrap(); + assert_eq!(buf.as_borrow_slice(), [65, 0, 66, 0,]); + } +} diff --git a/matter/tests/common/attributes.rs b/matter/tests/common/attributes.rs new file mode 100644 index 0000000..42d4c83 --- /dev/null +++ b/matter/tests/common/attributes.rs @@ -0,0 +1,77 @@ +/* + * + * 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::interaction_model::{messages::ib::AttrResp, messages::msg::ReportDataMsg}; + +/// Assert that the data received in the outbuf matches our expectations +pub fn assert_attr_report(received: &ReportDataMsg, expected: &[AttrResp]) { + let mut index = 0; + + for inv_response in received.attr_reports.as_ref().unwrap().iter() { + println!("Validating index {}", index); + match expected[index] { + AttrResp::Data(e_d) => match inv_response { + AttrResp::Data(d) => { + // We don't match the data-version + assert_eq!(e_d.path, d.path); + assert_eq!(e_d.data, d.data); + } + _ => { + panic!("Invalid response, expected AttrRespIn::Data"); + } + }, + AttrResp::Status(e_s) => match inv_response { + AttrResp::Status(s) => { + assert_eq!(e_s, s); + } + _ => { + panic!("Invalid response, expected AttrRespIn::Status"); + } + }, + } + println!("Index {} success", index); + index += 1; + } + assert_eq!(index, expected.len()); +} + +// We have to hard-code this here, and it should match the tag +// of the 'data' part in AttrData +pub const ATTR_DATA_TAG_DATA: u8 = 2; + +#[macro_export] +macro_rules! attr_data { + ($path:expr, $data:expr) => { + AttrResp::Data(AttrData { + data_ver: None, + path: AttrPath { + endpoint: $path.endpoint, + cluster: $path.cluster, + attr: $path.leaf.map(|x| x as u16), + ..Default::default() + }, + data: EncodeValue::Tlv(TLVElement::new(TagType::Context(ATTR_DATA_TAG_DATA), $data)), + }) + }; +} + +#[macro_export] +macro_rules! attr_status { + ($path:expr, $status:expr) => { + AttrResp::Status(AttrStatus::new($path, $status, 0)) + }; +} diff --git a/matter/tests/common/echo_cluster.rs b/matter/tests/common/echo_cluster.rs new file mode 100644 index 0000000..381bc6f --- /dev/null +++ b/matter/tests/common/echo_cluster.rs @@ -0,0 +1,250 @@ +/* + * + * 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::sync::{Arc, Mutex, Once}; + +use matter::{ + data_model::objects::{ + Access, AttrDetails, AttrValue, Attribute, Cluster, ClusterType, EncodeValue, Encoder, + Quality, + }, + error::Error, + interaction_model::{ + command::CommandReq, + core::IMStatusCode, + messages::ib::{self, attr_list_write, ListOperation}, + }, + tlv::{TLVElement, TLVWriter, TagType, ToTLV}, +}; +use num_derive::FromPrimitive; + +pub const ID: u32 = 0xABCD; + +#[derive(FromPrimitive)] +pub enum Commands { + EchoReq = 0x00, + EchoResp = 0x01, +} + +/// 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; WRITE_LIST_MAX], +} + +static mut G_TEST_CHECKER: Option>> = 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>, 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 base: Cluster, + pub multiplier: u8, +} + +#[derive(FromPrimitive)] +pub enum Attributes { + Att1 = 0, + Att2 = 1, + AttWrite = 2, + AttCustom = 3, + AttWriteList = 4, +} + +pub const ATTR_CUSTOM_VALUE: u32 = 0xcafebeef; +pub const ATTR_WRITE_DEFAULT_VALUE: u16 = 0xcafe; + +impl ClusterType for EchoCluster { + fn base(&self) -> &Cluster { + &self.base + } + + fn base_mut(&mut self) -> &mut Cluster { + &mut self.base + } + + fn read_custom_attribute(&self, encoder: &mut dyn Encoder, attr: &AttrDetails) { + match num::FromPrimitive::from_u16(attr.attr_id) { + Some(Attributes::AttCustom) => encoder.encode(EncodeValue::Closure(&|tag, tw| { + let _ = tw.u32(tag, ATTR_CUSTOM_VALUE); + })), + Some(Attributes::AttWriteList) => { + let tc_handle = TestChecker::get().unwrap(); + let tc = tc_handle.lock().unwrap(); + encoder.encode(EncodeValue::Closure(&|tag, tw| { + let _ = tw.start_array(tag); + for i in tc.write_list.iter().flatten() { + let _ = tw.u16(TagType::Anonymous, *i); + } + let _ = tw.end_container(); + })) + } + _ => (), + } + } + + fn write_attribute( + &mut self, + attr: &AttrDetails, + data: &TLVElement, + ) -> Result<(), IMStatusCode> { + match num::FromPrimitive::from_u16(attr.attr_id) { + Some(Attributes::AttWriteList) => { + attr_list_write(attr, data, |op, data| self.write_attr_list(op, data)) + } + _ => self.base.write_attribute_from_tlv(attr.attr_id, data), + } + } + + fn handle_command(&mut self, cmd_req: &mut CommandReq) -> Result<(), IMStatusCode> { + let cmd = cmd_req + .cmd + .path + .leaf + .map(|c| num::FromPrimitive::from_u32(c)) + .ok_or(IMStatusCode::UnsupportedCommand)? + .ok_or(IMStatusCode::UnsupportedCommand)?; + match cmd { + // This will generate an echo response on the same endpoint + // with data multiplied by the multiplier + Commands::EchoReq => { + let a = cmd_req.data.u8().unwrap(); + let mut echo_response = cmd_req.cmd; + echo_response.path.leaf = Some(Commands::EchoResp as u32); + + let cmd_data = |tag: TagType, t: &mut TLVWriter| { + let _ = t.start_struct(tag); + // Echo = input * self.multiplier + let _ = t.u8(TagType::Context(0), a * self.multiplier); + let _ = t.end_container(); + }; + + let invoke_resp = ib::InvResp::Cmd(ib::CmdData::new( + echo_response, + EncodeValue::Closure(&cmd_data), + )); + let _ = invoke_resp.to_tlv(cmd_req.resp, TagType::Anonymous); + cmd_req.trans.complete(); + } + _ => { + return Err(IMStatusCode::UnsupportedCommand); + } + } + Ok(()) + } +} + +impl EchoCluster { + pub fn new(multiplier: u8) -> Result, Error> { + let mut c = Box::new(Self { + base: Cluster::new(ID)?, + multiplier, + }); + c.base.add_attribute(Attribute::new( + Attributes::Att1 as u16, + AttrValue::Uint16(0x1234), + Access::RV, + Quality::NONE, + )?)?; + c.base.add_attribute(Attribute::new( + Attributes::Att2 as u16, + AttrValue::Uint16(0x5678), + Access::RV, + Quality::NONE, + )?)?; + c.base.add_attribute(Attribute::new( + Attributes::AttWrite as u16, + AttrValue::Uint16(ATTR_WRITE_DEFAULT_VALUE), + Access::WRITE | Access::NEED_ADMIN, + Quality::NONE, + )?)?; + c.base.add_attribute(Attribute::new( + Attributes::AttCustom as u16, + AttrValue::Custom, + Access::READ | Access::NEED_VIEW, + Quality::NONE, + )?)?; + c.base.add_attribute(Attribute::new( + Attributes::AttWriteList as u16, + AttrValue::Custom, + Access::WRITE | Access::NEED_ADMIN, + Quality::NONE, + )?)?; + Ok(c) + } + + fn write_attr_list( + &mut self, + op: ListOperation, + data: &TLVElement, + ) -> Result<(), IMStatusCode> { + let tc_handle = TestChecker::get().unwrap(); + let mut tc = tc_handle.lock().unwrap(); + match op { + ListOperation::AddItem => { + let data = data.u16().map_err(|_| IMStatusCode::Failure)?; + for i in 0..WRITE_LIST_MAX { + if tc.write_list[i].is_none() { + tc.write_list[i] = Some(data); + return Ok(()); + } + } + Err(IMStatusCode::ResourceExhausted) + } + ListOperation::EditItem(index) => { + let data = data.u16().map_err(|_| IMStatusCode::Failure)?; + if tc.write_list[index as usize].is_some() { + tc.write_list[index as usize] = Some(data); + Ok(()) + } else { + Err(IMStatusCode::InvalidAction) + } + } + ListOperation::DeleteItem(index) => { + if tc.write_list[index as usize].is_some() { + tc.write_list[index as usize] = None; + Ok(()) + } else { + Err(IMStatusCode::InvalidAction) + } + } + ListOperation::DeleteList => { + for i in 0..WRITE_LIST_MAX { + tc.write_list[i] = None; + } + Ok(()) + } + } + } +} diff --git a/matter/tests/common/im_engine.rs b/matter/tests/common/im_engine.rs new file mode 100644 index 0000000..1d1ac38 --- /dev/null +++ b/matter/tests/common/im_engine.rs @@ -0,0 +1,201 @@ +/* + * + * 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::common::echo_cluster; +use boxslab::Slab; +use matter::{ + acl::{AclEntry, AclMgr, AuthMode}, + data_model::{ + cluster_basic_information::BasicInfoConfig, + core::DataModel, + device_types::device_type_add_on_off_light, + objects::Privilege, + sdm::dev_att::{DataType, DevAttDataFetcher}, + }, + error::Error, + fabric::FabricMgr, + interaction_model::{core::OpCode, messages::ib::CmdPath, messages::msg, InteractionModel}, + tlv::{TLVWriter, TagType, ToTLV}, + transport::packet::Packet, + transport::proto_demux::HandleProto, + transport::{ + exchange::{self, Exchange, ExchangeCtx}, + network::Address, + packet::PacketPool, + proto_demux::ProtoCtx, + session::{CloneData, SessionMgr, SessionMode}, + }, + utils::writebuf::WriteBuf, +}; +use std::{ + net::{Ipv4Addr, SocketAddr}, + sync::Arc, +}; + +pub struct DummyDevAtt {} +impl DevAttDataFetcher for DummyDevAtt { + fn get_devatt_data(&self, _data_type: DataType, _data: &mut [u8]) -> Result { + Ok(2) + } +} + +/// An Interaction Model Engine to facilitate easy testing +pub struct ImEngine { + pub dm: DataModel, + pub acl_mgr: Arc, + pub im: Box, +} + +pub struct ImInput<'a> { + action: OpCode, + data_in: &'a [u8], + peer_id: u64, +} + +pub const IM_ENGINE_PEER_ID: u64 = 445566; +impl<'a> ImInput<'a> { + pub fn new(action: OpCode, data_in: &'a [u8]) -> Self { + Self { + action, + data_in, + peer_id: IM_ENGINE_PEER_ID, + } + } + + pub fn set_peer_node_id(&mut self, peer: u64) { + self.peer_id = peer; + } +} + +impl ImEngine { + /// Create the interaction model engine + pub fn new() -> Self { + let dev_det = BasicInfoConfig { + vid: 10, + pid: 11, + hw_ver: 12, + sw_ver: 13, + }; + let dev_att = Box::new(DummyDevAtt {}); + let fabric_mgr = Arc::new(FabricMgr::new().unwrap()); + let acl_mgr = Arc::new(AclMgr::new_with(false).unwrap()); + acl_mgr.erase_all(); + let mut default_acl = AclEntry::new(1, Privilege::ADMIN, AuthMode::Case); + // Only allow the standard peer node id of the IM Engine + default_acl.add_subject(IM_ENGINE_PEER_ID).unwrap(); + acl_mgr.add(default_acl).unwrap(); + let dm = DataModel::new(dev_det, dev_att, fabric_mgr.clone(), acl_mgr.clone()).unwrap(); + + { + let mut d = dm.node.write().unwrap(); + let light_endpoint = device_type_add_on_off_light(&mut d).unwrap(); + d.add_cluster(0, echo_cluster::EchoCluster::new(2).unwrap()) + .unwrap(); + d.add_cluster(light_endpoint, echo_cluster::EchoCluster::new(3).unwrap()) + .unwrap(); + } + + let im = Box::new(InteractionModel::new(Box::new(dm.clone()))); + + Self { dm, acl_mgr, im } + } + + /// Run a transaction through the interaction model engine + pub fn process(&mut self, input: &ImInput, data_out: &mut [u8]) -> usize { + let mut exch = Exchange::new(1, 0, exchange::Role::Responder); + + let mut sess_mgr: SessionMgr = Default::default(); + + let clone_data = CloneData::new( + 123456, + input.peer_id, + 10, + 30, + Address::Udp(SocketAddr::new( + std::net::IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + 5542, + )), + SessionMode::Case(1), + ); + let sess_idx = sess_mgr.clone_session(&clone_data).unwrap(); + let sess = sess_mgr.get_session_handle(sess_idx); + let exch_ctx = ExchangeCtx { + exch: &mut exch, + sess, + }; + let mut rx = Slab::::new(Packet::new_rx().unwrap()).unwrap(); + let tx = Slab::::new(Packet::new_tx().unwrap()).unwrap(); + // Create fake rx packet + rx.set_proto_id(0x01); + rx.set_proto_opcode(input.action as u8); + rx.peer = Address::default(); + let in_data_len = input.data_in.len(); + let rx_buf = rx.as_borrow_slice(); + rx_buf[..in_data_len].copy_from_slice(input.data_in); + rx.get_parsebuf().unwrap().set_len(in_data_len); + + let mut ctx = ProtoCtx::new(exch_ctx, rx, tx); + self.im.handle_proto_id(&mut ctx).unwrap(); + let out_data_len = ctx.tx.as_borrow_slice().len(); + data_out[..out_data_len].copy_from_slice(ctx.tx.as_borrow_slice()); + out_data_len + } +} + +// Create an Interaction Model, Data Model and run a rx/tx transaction through it +pub fn im_engine(action: OpCode, data_in: &[u8], data_out: &mut [u8]) -> (DataModel, usize) { + let mut engine = ImEngine::new(); + let input = ImInput::new(action, data_in); + let output_len = engine.process(&input, data_out); + (engine.dm, output_len) +} + +pub struct TestData<'a, 'b> { + tw: TLVWriter<'a, 'b>, +} + +impl<'a, 'b> TestData<'a, 'b> { + pub fn new(buf: &'b mut WriteBuf<'a>) -> Self { + Self { + tw: TLVWriter::new(buf), + } + } + + pub fn commands(&mut self, cmds: &[(CmdPath, Option)]) -> Result<(), Error> { + self.tw.start_struct(TagType::Anonymous)?; + self.tw.bool( + TagType::Context(msg::InvReqTag::SupressResponse as u8), + false, + )?; + self.tw + .bool(TagType::Context(msg::InvReqTag::TimedReq as u8), false)?; + self.tw + .start_array(TagType::Context(msg::InvReqTag::InvokeRequests as u8))?; + + for (cmd, data) in cmds { + self.tw.start_struct(TagType::Anonymous)?; + cmd.to_tlv(&mut self.tw, TagType::Context(0))?; + if let Some(d) = *data { + self.tw.u8(TagType::Context(1), d)?; + } + self.tw.end_container()?; + } + + self.tw.end_container()?; + self.tw.end_container() + } +} diff --git a/matter/tests/common/mod.rs b/matter/tests/common/mod.rs new file mode 100644 index 0000000..652a9d4 --- /dev/null +++ b/matter/tests/common/mod.rs @@ -0,0 +1,20 @@ +/* + * + * 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. + */ + +pub mod attributes; +pub mod echo_cluster; +pub mod im_engine; diff --git a/matter/tests/data_model/acl_and_dataver.rs b/matter/tests/data_model/acl_and_dataver.rs new file mode 100644 index 0000000..e44c536 --- /dev/null +++ b/matter/tests/data_model/acl_and_dataver.rs @@ -0,0 +1,685 @@ +/* + * + * 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, ToTLV}, + utils::writebuf::WriteBuf, +}; + +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 buf = [0u8; 400]; + let buf_len = buf.len(); + let mut wb = WriteBuf::new(&mut buf, buf_len); + let mut tw = TLVWriter::new(&mut wb); + + let mut read_req = ReadReq::new(true).set_attr_requests(input); + read_req.dataver_filters = dataver_filters; + read_req.to_tlv(&mut tw, TagType::Anonymous).unwrap(); + + let mut input = ImInput::new(OpCode::ReadRequest, wb.as_borrow_slice()); + input.set_peer_node_id(peer_node_id); + + let out_buf_len = im.process(&input, out_buf); + let out_buf = &out_buf[..out_buf_len]; + + 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 buf = [0u8; 400]; + let mut out_buf = [0u8; 400]; + + let buf_len = buf.len(); + let mut wb = WriteBuf::new(&mut buf, buf_len); + let mut tw = TLVWriter::new(&mut wb); + + let write_req = WriteReq::new(false, input); + write_req.to_tlv(&mut tw, TagType::Anonymous).unwrap(); + + let mut input = ImInput::new(OpCode::WriteRequest, wb.as_borrow_slice()); + input.set_peer_node_id(peer_node_id); + let out_buf_len = im.process(&input, &mut out_buf); + + let out_buf = &out_buf[..out_buf_len]; + 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); +} diff --git a/matter/tests/data_model/attribute_lists.rs b/matter/tests/data_model/attribute_lists.rs new file mode 100644 index 0000000..399d9a0 --- /dev/null +++ b/matter/tests/data_model/attribute_lists.rs @@ -0,0 +1,166 @@ +/* + * + * 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::{ + data_model::{core::DataModel, objects::EncodeValue}, + interaction_model::{ + core::{IMStatusCode, OpCode}, + messages::{ + ib::{AttrData, AttrPath, AttrStatus}, + msg::WriteReq, + }, + messages::{msg, GenericPath}, + }, + tlv::{self, FromTLV, Nullable, TLVWriter, TagType, ToTLV}, + utils::writebuf::WriteBuf, +}; + +use crate::common::{ + echo_cluster::{self, TestChecker}, + im_engine::im_engine, +}; + +// Helper for handling Write Attribute sequences +fn handle_write_reqs(input: &[AttrData], expected: &[AttrStatus]) -> DataModel { + let mut buf = [0u8; 400]; + let mut out_buf = [0u8; 400]; + + let buf_len = buf.len(); + let mut wb = WriteBuf::new(&mut buf, buf_len); + let mut tw = TLVWriter::new(&mut wb); + + let write_req = WriteReq::new(false, input); + write_req.to_tlv(&mut tw, TagType::Anonymous).unwrap(); + + let (dm, out_buf_len) = im_engine(OpCode::WriteRequest, wb.as_borrow_slice(), &mut out_buf); + let out_buf = &out_buf[..out_buf_len]; + 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()); + dm +} + +#[test] +/// This tests all the attribute list operations +/// add item, edit item, delete item, overwrite list, delete list +fn attr_list_ops() { + let val0: u16 = 10; + let val1: u16 = 15; + let tc_handle = TestChecker::get().unwrap(); + + let _ = env_logger::try_init(); + + let delete_item = EncodeValue::Closure(&|tag, t| { + let _ = t.null(tag); + }); + let delete_all = EncodeValue::Closure(&|tag, t| { + let _ = t.start_array(tag); + let _ = t.end_container(); + }); + + let att_data = GenericPath::new( + Some(0), + Some(echo_cluster::ID), + Some(echo_cluster::Attributes::AttWriteList as u32), + ); + let mut att_path = AttrPath::new(&att_data); + + // Test 1: Add Operation - add val0 + let input = &[AttrData::new(None, att_path, EncodeValue::Value(&val0))]; + let expected = &[AttrStatus::new(&att_data, IMStatusCode::Sucess, 0)]; + let _ = handle_write_reqs(input, expected); + + { + let tc = tc_handle.lock().unwrap(); + assert_eq!([Some(val0), None, None, None, None], tc.write_list); + } + + // Test 2: Another Add Operation - add val1 + let input = &[AttrData::new(None, att_path, EncodeValue::Value(&val1))]; + let expected = &[AttrStatus::new(&att_data, IMStatusCode::Sucess, 0)]; + let _ = handle_write_reqs(input, expected); + + { + let tc = tc_handle.lock().unwrap(); + assert_eq!([Some(val0), Some(val1), None, None, None], tc.write_list); + } + + // Test 3: Edit Operation - edit val1 to val0 + att_path.list_index = Some(Nullable::NotNull(1)); + let input = &[AttrData::new(None, att_path, EncodeValue::Value(&val0))]; + let expected = &[AttrStatus::new(&att_data, IMStatusCode::Sucess, 0)]; + let _ = handle_write_reqs(input, expected); + + { + let tc = tc_handle.lock().unwrap(); + assert_eq!([Some(val0), Some(val0), None, None, None], tc.write_list); + } + + // Test 4: Delete Operation - delete index 0 + att_path.list_index = Some(Nullable::NotNull(0)); + let input = &[AttrData::new(None, att_path, delete_item)]; + let expected = &[AttrStatus::new(&att_data, IMStatusCode::Sucess, 0)]; + let _ = handle_write_reqs(input, expected); + + { + let tc = tc_handle.lock().unwrap(); + assert_eq!([None, Some(val0), None, None, None], tc.write_list); + } + + // Test 5: Overwrite Operation - overwrite first 2 entries + let overwrite_val: [u32; 2] = [20, 21]; + att_path.list_index = None; + let input = &[AttrData::new( + None, + att_path, + EncodeValue::Value(&overwrite_val), + )]; + let expected = &[AttrStatus::new(&att_data, IMStatusCode::Sucess, 0)]; + let _ = handle_write_reqs(input, expected); + + { + let tc = tc_handle.lock().unwrap(); + assert_eq!([Some(20), Some(21), None, None, None], tc.write_list); + } + + // Test 6: Overwrite Operation - delete whole list + att_path.list_index = None; + let input = &[AttrData::new(None, att_path, delete_all)]; + let expected = &[AttrStatus::new(&att_data, IMStatusCode::Sucess, 0)]; + let _ = handle_write_reqs(input, expected); + + { + let tc = tc_handle.lock().unwrap(); + assert_eq!([None, None, None, None, None], tc.write_list); + } +} diff --git a/matter/tests/data_model/attributes.rs b/matter/tests/data_model/attributes.rs new file mode 100644 index 0000000..aaa9a74 --- /dev/null +++ b/matter/tests/data_model/attributes.rs @@ -0,0 +1,566 @@ +/* + * + * 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::{ + data_model::{ + cluster_on_off, + core::DataModel, + objects::{AttrValue, EncodeValue, GlobalElements}, + }, + interaction_model::{ + core::{IMStatusCode, OpCode}, + messages::{ + ib::{AttrData, AttrPath, AttrResp, AttrStatus}, + msg::{ReadReq, ReportDataMsg, WriteReq}, + }, + messages::{msg, GenericPath}, + }, + tlv::{self, ElementType, FromTLV, TLVElement, TLVList, TLVWriter, TagType, ToTLV}, + utils::writebuf::WriteBuf, +}; + +use crate::{ + attr_data, attr_status, + common::{attributes::*, echo_cluster, im_engine::im_engine}, +}; + +fn handle_read_reqs(input: &[AttrPath], expected: &[AttrResp]) { + let mut out_buf = [0u8; 400]; + let received = gen_read_reqs_output(input, &mut out_buf); + assert_attr_report(&received, expected) +} + +// Helper for handling Read Req sequences +fn gen_read_reqs_output<'a>(input: &[AttrPath], out_buf: &'a mut [u8]) -> ReportDataMsg<'a> { + let mut buf = [0u8; 400]; + let buf_len = buf.len(); + let mut wb = WriteBuf::new(&mut buf, buf_len); + let mut tw = TLVWriter::new(&mut wb); + + let read_req = ReadReq::new(true).set_attr_requests(input); + read_req.to_tlv(&mut tw, TagType::Anonymous).unwrap(); + + let (_, out_buf_len) = im_engine(OpCode::ReadRequest, wb.as_borrow_slice(), out_buf); + let out_buf = &out_buf[..out_buf_len]; + 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(input: &[AttrData], expected: &[AttrStatus]) -> DataModel { + let mut buf = [0u8; 400]; + let mut out_buf = [0u8; 400]; + + let buf_len = buf.len(); + let mut wb = WriteBuf::new(&mut buf, buf_len); + let mut tw = TLVWriter::new(&mut wb); + + let write_req = WriteReq::new(false, input); + write_req.to_tlv(&mut tw, TagType::Anonymous).unwrap(); + + let (dm, out_buf_len) = im_engine(OpCode::WriteRequest, wb.as_borrow_slice(), &mut out_buf); + let out_buf = &out_buf[..out_buf_len]; + 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()); + dm +} + +#[test] +fn test_read_success() { + // 3 Attr Read Requests + // - first on endpoint 0, att1 + // - second on endpoint 1, att2 + // - third on endpoint 1, attcustom a custom attribute + let _ = env_logger::try_init(); + + let ep0_att1 = GenericPath::new( + Some(0), + Some(echo_cluster::ID), + Some(echo_cluster::Attributes::Att1 as u32), + ); + let ep1_att2 = GenericPath::new( + Some(1), + Some(echo_cluster::ID), + Some(echo_cluster::Attributes::Att2 as u32), + ); + let ep1_attcustom = GenericPath::new( + Some(1), + Some(echo_cluster::ID), + Some(echo_cluster::Attributes::AttCustom as u32), + ); + let input = &[ + AttrPath::new(&ep0_att1), + AttrPath::new(&ep1_att2), + AttrPath::new(&ep1_attcustom), + ]; + let expected = &[ + attr_data!(ep0_att1, ElementType::U16(0x1234)), + attr_data!(ep1_att2, ElementType::U16(0x5678)), + attr_data!( + ep1_attcustom, + ElementType::U32(echo_cluster::ATTR_CUSTOM_VALUE) + ), + ]; + handle_read_reqs(input, expected); +} + +#[test] +fn test_read_unsupported_fields() { + // 6 reads + // - endpoint doesn't exist - UnsupportedEndpoint + // - cluster doesn't exist - UnsupportedCluster + // - cluster doesn't exist and endpoint is wildcard - Silently ignore + // - attribute doesn't exist - UnsupportedAttribute + // - attribute doesn't exist and endpoint is wildcard - Silently ignore + // - attribute doesn't exist and cluster is wildcard - Silently ignore + let _ = env_logger::try_init(); + + let invalid_endpoint = GenericPath::new( + Some(2), + Some(echo_cluster::ID), + Some(echo_cluster::Attributes::Att1 as u32), + ); + let invalid_cluster = GenericPath::new( + Some(0), + Some(0x1234), + Some(echo_cluster::Attributes::Att1 as u32), + ); + let invalid_cluster_wc_endpoint = GenericPath::new( + None, + Some(0x1234), + Some(echo_cluster::Attributes::AttCustom as u32), + ); + let invalid_attribute = GenericPath::new(Some(0), Some(echo_cluster::ID), Some(0x1234)); + let invalid_attribute_wc_endpoint = + GenericPath::new(None, Some(echo_cluster::ID), Some(0x1234)); + let invalid_attribute_wc_cluster = GenericPath::new(Some(0), None, Some(0x1234)); + let input = &[ + AttrPath::new(&invalid_endpoint), + AttrPath::new(&invalid_cluster), + AttrPath::new(&invalid_cluster_wc_endpoint), + AttrPath::new(&invalid_attribute), + AttrPath::new(&invalid_attribute_wc_endpoint), + AttrPath::new(&invalid_attribute_wc_cluster), + ]; + + let expected = &[ + attr_status!(&invalid_endpoint, IMStatusCode::UnsupportedEndpoint), + attr_status!(&invalid_cluster, IMStatusCode::UnsupportedCluster), + attr_status!(&invalid_attribute, IMStatusCode::UnsupportedAttribute), + ]; + handle_read_reqs(input, expected); +} + +#[test] +fn test_read_wc_endpoint_all_have_clusters() { + // 1 Attr Read Requests + // - wildcard endpoint, att1 + // - 2 responses are expected + let _ = env_logger::try_init(); + + 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) + ), + ]; + handle_read_reqs(input, expected); +} + +#[test] +fn test_read_wc_endpoint_only_1_has_cluster() { + // 1 Attr Read Requests + // - wildcard endpoint, on/off Cluster OnOff Attribute + // - 1 response are expected + let _ = env_logger::try_init(); + + let wc_ep_onoff = GenericPath::new( + None, + Some(cluster_on_off::ID), + Some(cluster_on_off::Attributes::OnOff as u32), + ); + let input = &[AttrPath::new(&wc_ep_onoff)]; + + let expected = &[attr_data!( + GenericPath::new( + Some(1), + Some(cluster_on_off::ID), + Some(cluster_on_off::Attributes::OnOff as u32) + ), + ElementType::False + )]; + handle_read_reqs(input, expected); +} + +fn get_tlvs<'a>(buf: &'a mut [u8], data: &[u16]) -> TLVElement<'a> { + let buf_len = buf.len(); + let mut wb = WriteBuf::new(buf, buf_len); + let mut tw = TLVWriter::new(&mut wb); + let _ = tw.start_array(TagType::Context(2)); + for e in data { + let _ = tw.u16(TagType::Anonymous, *e); + } + let _ = tw.end_container(); + let tlv_array = TLVList::new(wb.as_slice()).iter().next().unwrap(); + tlv_array +} + +#[test] +fn test_read_wc_endpoint_wc_attribute() { + // 1 Attr Read Request + // - wildcard endpoint, wildcard attribute + // - 8 responses are expected, 1+3 attributes on endpoint 0, 1+3 on endpoint 1 + let _ = env_logger::try_init(); + let wc_ep_wc_attr = GenericPath::new(None, Some(echo_cluster::ID), None); + let input = &[AttrPath::new(&wc_ep_wc_attr)]; + + let mut buf = [0u8; 100]; + let attr_list_tlvs = get_tlvs( + &mut buf, + &[ + GlobalElements::AttributeList as u16, + echo_cluster::Attributes::Att1 as u16, + echo_cluster::Attributes::Att2 as u16, + echo_cluster::Attributes::AttWrite as u16, + echo_cluster::Attributes::AttCustom as u16, + ], + ); + + let expected = &[ + attr_data!( + GenericPath::new( + Some(0), + Some(echo_cluster::ID), + Some(GlobalElements::AttributeList as u32), + ), + attr_list_tlvs.get_element_type() + ), + 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(0), + Some(echo_cluster::ID), + Some(echo_cluster::Attributes::Att2 as u32), + ), + ElementType::U16(0x5678) + ), + attr_data!( + GenericPath::new( + Some(0), + Some(echo_cluster::ID), + Some(echo_cluster::Attributes::AttCustom as u32), + ), + ElementType::U32(echo_cluster::ATTR_CUSTOM_VALUE) + ), + attr_data!( + GenericPath::new( + Some(1), + Some(echo_cluster::ID), + Some(GlobalElements::AttributeList as u32), + ), + attr_list_tlvs.get_element_type() + ), + attr_data!( + GenericPath::new( + Some(1), + 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::Att2 as u32), + ), + ElementType::U16(0x5678) + ), + attr_data!( + GenericPath::new( + Some(1), + Some(echo_cluster::ID), + Some(echo_cluster::Attributes::AttCustom as u32), + ), + ElementType::U32(echo_cluster::ATTR_CUSTOM_VALUE) + ), + ]; + handle_read_reqs(input, expected); +} + +#[test] +fn test_write_success() { + // 2 Attr Write Request + // - first on endpoint 0, AttWrite + // - second on endpoint 1, AttWrite + let val0 = 10; + let val1 = 15; + let _ = env_logger::try_init(); + 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 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 input = &[ + AttrData::new( + None, + AttrPath::new(&ep0_att), + EncodeValue::Closure(&attr_data0), + ), + AttrData::new( + None, + AttrPath::new(&ep1_att), + EncodeValue::Closure(&attr_data1), + ), + ]; + let expected = &[ + AttrStatus::new(&ep0_att, IMStatusCode::Sucess, 0), + AttrStatus::new(&ep1_att, IMStatusCode::Sucess, 0), + ]; + + let dm = handle_write_reqs(input, expected); + let node = dm.node.read().unwrap(); + let echo = node.get_cluster(0, echo_cluster::ID).unwrap(); + assert_eq!( + AttrValue::Uint16(val0), + *echo + .base() + .read_attribute_raw(echo_cluster::Attributes::AttWrite as u16) + .unwrap() + ); + let echo = node.get_cluster(1, echo_cluster::ID).unwrap(); + assert_eq!( + AttrValue::Uint16(val1), + *echo + .base() + .read_attribute_raw(echo_cluster::Attributes::AttWrite as u16) + .unwrap() + ); +} + +#[test] +fn test_write_wc_endpoint() { + // 1 Attr Write Request + // - wildcard endpoint, AttWrite + let val0 = 10; + let _ = env_logger::try_init(); + let attr_data0 = |tag, t: &mut TLVWriter| { + let _ = t.u16(tag, val0); + }; + + let ep_att = GenericPath::new( + None, + Some(echo_cluster::ID), + Some(echo_cluster::Attributes::AttWrite as u32), + ); + let input = &[AttrData::new( + None, + AttrPath::new(&ep_att), + EncodeValue::Closure(&attr_data0), + )]; + + 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 expected = &[ + AttrStatus::new(&ep0_att, IMStatusCode::Sucess, 0), + AttrStatus::new(&ep1_att, IMStatusCode::Sucess, 0), + ]; + + let dm = handle_write_reqs(input, expected); + assert_eq!( + AttrValue::Uint16(val0), + dm.read_attribute_raw( + 0, + echo_cluster::ID, + echo_cluster::Attributes::AttWrite as u16 + ) + .unwrap() + ); + assert_eq!( + AttrValue::Uint16(val0), + dm.read_attribute_raw( + 0, + echo_cluster::ID, + echo_cluster::Attributes::AttWrite as u16 + ) + .unwrap() + ); +} + +#[test] +fn test_write_unsupported_fields() { + // 7 writes + // - endpoint doesn't exist - UnsupportedEndpoint + // - cluster doesn't exist - UnsupportedCluster + // - attribute doesn't exist - UnsupportedAttribute + // - cluster doesn't exist and endpoint is wildcard - Silently ignore + // - attribute doesn't exist and endpoint is wildcard - Silently ignore + // - cluster is wildcard - Cluster cannot be wildcard - UnsupportedCluster + // - attribute is wildcard - Attribute cannot be wildcard - UnsupportedAttribute + let _ = env_logger::try_init(); + + let val0 = 50; + let attr_data0 = |tag, t: &mut TLVWriter| { + let _ = t.u16(tag, val0); + }; + + let invalid_endpoint = GenericPath::new( + Some(4), + Some(echo_cluster::ID), + Some(echo_cluster::Attributes::AttWrite as u32), + ); + let invalid_cluster = GenericPath::new( + Some(0), + Some(0x1234), + Some(echo_cluster::Attributes::AttWrite as u32), + ); + let invalid_attribute = GenericPath::new(Some(0), Some(echo_cluster::ID), Some(0x1234)); + let wc_endpoint_invalid_cluster = GenericPath::new( + None, + Some(0x1234), + Some(echo_cluster::Attributes::AttWrite as u32), + ); + let wc_endpoint_invalid_attribute = + GenericPath::new(None, Some(echo_cluster::ID), Some(0x1234)); + let wc_cluster = GenericPath::new( + Some(0), + None, + Some(echo_cluster::Attributes::AttWrite as u32), + ); + let wc_attribute = GenericPath::new(Some(0), Some(echo_cluster::ID), None); + + let input = &[ + AttrData::new( + None, + AttrPath::new(&invalid_endpoint), + EncodeValue::Closure(&attr_data0), + ), + AttrData::new( + None, + AttrPath::new(&invalid_cluster), + EncodeValue::Closure(&attr_data0), + ), + AttrData::new( + None, + AttrPath::new(&invalid_attribute), + EncodeValue::Closure(&attr_data0), + ), + AttrData::new( + None, + AttrPath::new(&wc_endpoint_invalid_cluster), + EncodeValue::Closure(&attr_data0), + ), + AttrData::new( + None, + AttrPath::new(&wc_endpoint_invalid_attribute), + EncodeValue::Closure(&attr_data0), + ), + AttrData::new( + None, + AttrPath::new(&wc_cluster), + EncodeValue::Closure(&attr_data0), + ), + AttrData::new( + None, + AttrPath::new(&wc_attribute), + EncodeValue::Closure(&attr_data0), + ), + ]; + let expected = &[ + AttrStatus::new(&invalid_endpoint, IMStatusCode::UnsupportedEndpoint, 0), + AttrStatus::new(&invalid_cluster, IMStatusCode::UnsupportedCluster, 0), + AttrStatus::new(&invalid_attribute, IMStatusCode::UnsupportedAttribute, 0), + AttrStatus::new(&wc_cluster, IMStatusCode::UnsupportedCluster, 0), + AttrStatus::new(&wc_attribute, IMStatusCode::UnsupportedAttribute, 0), + ]; + let dm = handle_write_reqs(input, expected); + assert_eq!( + AttrValue::Uint16(echo_cluster::ATTR_WRITE_DEFAULT_VALUE), + dm.read_attribute_raw( + 0, + echo_cluster::ID, + echo_cluster::Attributes::AttWrite as u16 + ) + .unwrap() + ); +} diff --git a/matter/tests/data_model/commands.rs b/matter/tests/data_model/commands.rs new file mode 100644 index 0000000..8fdc838 --- /dev/null +++ b/matter/tests/data_model/commands.rs @@ -0,0 +1,230 @@ +/* + * + * 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::{ + data_model::{cluster_on_off, objects::EncodeValue}, + interaction_model::{ + core::{IMStatusCode, OpCode}, + messages::ib::{CmdPath, CmdStatus, InvResp}, + messages::msg, + }, + tlv::{self, FromTLV}, + utils::writebuf::WriteBuf, +}; + +use crate::common::{ + echo_cluster, + im_engine::{im_engine, TestData}, +}; + +enum ExpectedInvResp { + Cmd(CmdPath, u8), + Status(CmdStatus), +} + +// Helper for handling Invoke Command sequences +fn handle_commands(input: &[(CmdPath, Option)], expected: &[ExpectedInvResp]) { + let mut buf = [0u8; 400]; + let mut out_buf = [0u8; 400]; + + let buf_len = buf.len(); + let mut wb = WriteBuf::new(&mut buf, buf_len); + let mut td = TestData::new(&mut wb); + + td.commands(input).unwrap(); + + let (_, out_buf_len) = im_engine(OpCode::InvokeRequest, wb.as_borrow_slice(), &mut out_buf); + let out_buf = &out_buf[..out_buf_len]; + tlv::print_tlv_list(out_buf); + let root = tlv::get_root_node_struct(out_buf).unwrap(); + + let mut index = 0; + let cmd_list_iter = root + .find_tag(msg::InvRespTag::InvokeResponses as u32) + .unwrap() + .confirm_array() + .unwrap() + .enter() + .unwrap(); + for response in cmd_list_iter { + println!("Validating index {}", index); + let inv_response = InvResp::from_tlv(&response).unwrap(); + match expected[index] { + ExpectedInvResp::Cmd(e_c, e_d) => match inv_response { + InvResp::Cmd(c) => { + assert_eq!(e_c, c.path); + match c.data { + EncodeValue::Tlv(t) => { + assert_eq!(e_d, t.find_tag(0).unwrap().u8().unwrap()) + } + _ => panic!("Incorrect CmdDataType"), + } + } + _ => { + panic!("Invalid response, expected InvResponse::Cmd"); + } + }, + ExpectedInvResp::Status(e_status) => match inv_response { + InvResp::Status(status) => { + assert_eq!(e_status, status); + } + _ => { + panic!("Invalid response, expected InvResponse::Status"); + } + }, + } + println!("Index {} success", index); + index += 1; + } + assert_eq!(index, expected.len()); +} + +macro_rules! echo_req { + ($endpoint:literal, $data:literal) => { + ( + CmdPath::new( + Some($endpoint), + Some(echo_cluster::ID), + Some(echo_cluster::Commands::EchoReq as u16), + ), + Some($data), + ) + }; +} + +macro_rules! echo_resp { + ($endpoint:literal, $data:literal) => { + ExpectedInvResp::Cmd( + CmdPath::new( + Some($endpoint), + Some(echo_cluster::ID), + Some(echo_cluster::Commands::EchoResp as u16), + ), + $data, + ) + }; +} + +#[test] +fn test_invoke_cmds_success() { + // 2 echo Requests + // - one on endpoint 0 with data 5, + // - another on endpoint 1 with data 10 + let _ = env_logger::try_init(); + + let input = &[echo_req!(0, 5), echo_req!(1, 10)]; + let expected = &[echo_resp!(0, 10), echo_resp!(1, 30)]; + handle_commands(input, expected); +} + +#[test] +fn test_invoke_cmds_unsupported_fields() { + // 5 commands + // - endpoint doesn't exist - UnsupportedEndpoint + // - cluster doesn't exist - UnsupportedCluster + // - cluster doesn't exist and endpoint is wildcard - UnsupportedCluster + // - command doesn't exist - UnsupportedCommand + // - command doesn't exist and endpoint is wildcard - UnsupportedCommand + let _ = env_logger::try_init(); + + let invalid_endpoint = CmdPath::new( + Some(2), + Some(echo_cluster::ID), + Some(echo_cluster::Commands::EchoReq as u16), + ); + let invalid_cluster = CmdPath::new( + Some(0), + Some(0x1234), + Some(echo_cluster::Commands::EchoReq as u16), + ); + let invalid_cluster_wc_endpoint = CmdPath::new( + None, + Some(0x1234), + Some(echo_cluster::Commands::EchoReq as u16), + ); + let invalid_command = CmdPath::new(Some(0), Some(echo_cluster::ID), Some(0x1234)); + let invalid_command_wc_endpoint = CmdPath::new(None, Some(echo_cluster::ID), Some(0x1234)); + let input = &[ + (invalid_endpoint, Some(5)), + (invalid_cluster, Some(5)), + (invalid_cluster_wc_endpoint, Some(5)), + (invalid_command, Some(5)), + (invalid_command_wc_endpoint, Some(5)), + ]; + + let expected = &[ + ExpectedInvResp::Status(CmdStatus::new( + invalid_endpoint, + IMStatusCode::UnsupportedEndpoint, + 0, + )), + ExpectedInvResp::Status(CmdStatus::new( + invalid_cluster, + IMStatusCode::UnsupportedCluster, + 0, + )), + ExpectedInvResp::Status(CmdStatus::new( + invalid_command, + IMStatusCode::UnsupportedCommand, + 0, + )), + ]; + handle_commands(input, expected); +} + +#[test] +fn test_invoke_cmd_wc_endpoint_all_have_clusters() { + // 1 echo Request with wildcard endpoint + // should generate 2 responses from the echo clusters on both + let _ = env_logger::try_init(); + + let input = &[( + CmdPath::new( + None, + Some(echo_cluster::ID), + Some(echo_cluster::Commands::EchoReq as u16), + ), + Some(5), + )]; + let expected = &[echo_resp!(0, 10), echo_resp!(1, 15)]; + handle_commands(input, expected); +} + +#[test] +fn test_invoke_cmd_wc_endpoint_only_1_has_cluster() { + // 1 on command for on/off cluster with wildcard endpoint + // should generate 1 response from the on-off cluster + let _ = env_logger::try_init(); + + let target = CmdPath::new( + None, + Some(cluster_on_off::ID), + Some(cluster_on_off::Commands::On as u16), + ); + let expected_path = CmdPath::new( + Some(1), + Some(cluster_on_off::ID), + Some(cluster_on_off::Commands::On as u16), + ); + let input = &[(target, Some(1))]; + let expected = &[ExpectedInvResp::Status(CmdStatus::new( + expected_path, + IMStatusCode::Sucess, + 0, + ))]; + handle_commands(input, expected); +} diff --git a/matter/tests/data_model_tests.rs b/matter/tests/data_model_tests.rs new file mode 100644 index 0000000..318906e --- /dev/null +++ b/matter/tests/data_model_tests.rs @@ -0,0 +1,25 @@ +/* + * + * 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. + */ + +mod common; + +mod data_model { + mod acl_and_dataver; + mod attribute_lists; + mod attributes; + mod commands; +} diff --git a/matter/tests/interaction_model.rs b/matter/tests/interaction_model.rs new file mode 100644 index 0000000..06d31a1 --- /dev/null +++ b/matter/tests/interaction_model.rs @@ -0,0 +1,176 @@ +/* + * + * 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 boxslab::Slab; +use matter::error::Error; +use matter::interaction_model::core::OpCode; +use matter::interaction_model::messages::msg::InvReq; +use matter::interaction_model::messages::msg::ReadReq; +use matter::interaction_model::messages::msg::WriteReq; +use matter::interaction_model::InteractionConsumer; +use matter::interaction_model::InteractionModel; +use matter::interaction_model::Transaction; +use matter::tlv::TLVWriter; +use matter::transport::exchange::Exchange; +use matter::transport::exchange::ExchangeCtx; +use matter::transport::network::Address; +use matter::transport::packet::Packet; +use matter::transport::packet::PacketPool; +use matter::transport::proto_demux::HandleProto; +use matter::transport::proto_demux::ProtoCtx; +use matter::transport::session::SessionMgr; +use std::net::Ipv4Addr; +use std::net::SocketAddr; +use std::sync::{Arc, Mutex}; + +struct Node { + pub endpoint: u16, + pub cluster: u32, + pub command: u16, + pub variable: u8, +} + +struct DataModel { + node: Arc>, +} + +impl DataModel { + pub fn new(node: Node) -> Self { + DataModel { + node: Arc::new(Mutex::new(node)), + } + } +} + +impl Clone for DataModel { + fn clone(&self) -> Self { + Self { + node: self.node.clone(), + } + } +} + +impl InteractionConsumer for DataModel { + fn consume_invoke_cmd( + &self, + inv_req_msg: &InvReq, + _trans: &mut Transaction, + _tlvwriter: &mut TLVWriter, + ) -> Result<(), Error> { + if let Some(inv_requests) = &inv_req_msg.inv_requests { + for i in inv_requests.iter() { + let data = if let Some(data) = i.data.unwrap_tlv() { + data + } else { + continue; + }; + let cmd_path_ib = i.path; + let mut common_data = self.node.lock().unwrap(); + common_data.endpoint = cmd_path_ib.path.endpoint.unwrap_or(1); + common_data.cluster = cmd_path_ib.path.cluster.unwrap_or(0); + common_data.command = cmd_path_ib.path.leaf.unwrap_or(0) as u16; + data.confirm_struct().unwrap(); + common_data.variable = data.find_tag(0).unwrap().u8().unwrap(); + } + } + + Ok(()) + } + + fn consume_read_attr( + &self, + _req: &ReadReq, + _trans: &mut Transaction, + _tlvwriter: &mut TLVWriter, + ) -> Result<(), Error> { + Ok(()) + } + + fn consume_write_attr( + &self, + _req: &WriteReq, + _trans: &mut Transaction, + _tlvwriter: &mut TLVWriter, + ) -> Result<(), Error> { + Ok(()) + } +} + +fn handle_data(action: OpCode, data_in: &[u8], data_out: &mut [u8]) -> (DataModel, usize) { + let data_model = DataModel::new(Node { + endpoint: 0, + cluster: 0, + command: 0, + variable: 0, + }); + let mut interaction_model = InteractionModel::new(Box::new(data_model.clone())); + let mut exch: Exchange = Default::default(); + let mut sess_mgr: SessionMgr = Default::default(); + let sess_idx = sess_mgr + .get_or_add( + 0, + Address::Udp(SocketAddr::new( + std::net::IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + 5542, + )), + None, + false, + ) + .unwrap(); + let sess = sess_mgr.get_session_handle(sess_idx); + let exch_ctx = ExchangeCtx { + exch: &mut exch, + sess, + }; + let mut rx = Slab::::new(Packet::new_rx().unwrap()).unwrap(); + let tx = Slab::::new(Packet::new_tx().unwrap()).unwrap(); + // Create fake rx packet + rx.set_proto_id(0x01); + rx.set_proto_opcode(action as u8); + rx.peer = Address::default(); + let in_data_len = data_in.len(); + let rx_buf = rx.as_borrow_slice(); + rx_buf[..in_data_len].copy_from_slice(data_in); + + let mut ctx = ProtoCtx::new(exch_ctx, rx, tx); + + interaction_model.handle_proto_id(&mut ctx).unwrap(); + + let out_len = ctx.tx.as_borrow_slice().len(); + data_out[..out_len].copy_from_slice(ctx.tx.as_borrow_slice()); + (data_model, out_len) +} + +#[test] +fn test_valid_invoke_cmd() -> Result<(), Error> { + // An invoke command for endpoint 0, cluster 49, command 12 and a u8 variable value of 0x05 + + let b = [ + 0x15, 0x28, 0x00, 0x28, 0x01, 0x36, 0x02, 0x15, 0x37, 0x00, 0x24, 0x00, 0x00, 0x24, 0x01, + 0x31, 0x24, 0x02, 0x0c, 0x18, 0x35, 0x01, 0x24, 0x00, 0x05, 0x18, 0x18, 0x18, 0x18, + ]; + + let mut out_buf: [u8; 20] = [0; 20]; + + let (data_model, _) = handle_data(OpCode::InvokeRequest, &b, &mut out_buf); + let data = data_model.node.lock().unwrap(); + assert_eq!(data.endpoint, 0); + assert_eq!(data.cluster, 49); + assert_eq!(data.command, 12); + assert_eq!(data.variable, 5); + Ok(()) +} diff --git a/matter_macro_derive/Cargo.toml b/matter_macro_derive/Cargo.toml new file mode 100644 index 0000000..249f152 --- /dev/null +++ b/matter_macro_derive/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "matter_macro_derive" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +proc-macro = true + +[dependencies] +syn = { version = "*", features = ["extra-traits"]} +quote = "*" +proc-macro2 = "*" diff --git a/matter_macro_derive/src/lib.rs b/matter_macro_derive/src/lib.rs new file mode 100644 index 0000000..91f2734 --- /dev/null +++ b/matter_macro_derive/src/lib.rs @@ -0,0 +1,434 @@ +/* + * + * 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 proc_macro::TokenStream; +use proc_macro2::Span; +use quote::{format_ident, quote}; +use syn::Lit::{Int, Str}; +use syn::NestedMeta::{Lit, Meta}; +use syn::{parse_macro_input, DeriveInput, Lifetime}; +use syn::{ + Meta::{List, NameValue}, + MetaList, MetaNameValue, Type, +}; + +struct TlvArgs { + start: u8, + datatype: String, + unordered: bool, + lifetime: syn::Lifetime, +} + +impl Default for TlvArgs { + fn default() -> Self { + Self { + start: 0, + datatype: "struct".to_string(), + unordered: false, + lifetime: Lifetime::new("'_", Span::call_site()), + } + } +} + +fn parse_tlvargs(ast: &DeriveInput) -> TlvArgs { + let mut tlvargs: TlvArgs = Default::default(); + + if ast.attrs.len() > 0 { + if let List(MetaList { + path, + paren_token: _, + nested, + }) = ast.attrs[0].parse_meta().unwrap() + { + if path.is_ident("tlvargs") { + for a in nested { + if let Meta(NameValue(MetaNameValue { + path: key_path, + eq_token: _, + lit: key_val, + })) = a + { + if key_path.is_ident("start") { + if let Int(litint) = key_val { + tlvargs.start = litint.base10_parse::().unwrap(); + } + } else if key_path.is_ident("lifetime") { + if let Str(litstr) = key_val { + tlvargs.lifetime = + Lifetime::new(&litstr.value(), Span::call_site()); + } + } else if key_path.is_ident("datatype") { + if let Str(litstr) = key_val { + tlvargs.datatype = litstr.value(); + } + } else if key_path.is_ident("unordered") { + tlvargs.unordered = true; + } + } + } + } + } + } + tlvargs +} + +fn parse_tag_val(field: &syn::Field) -> Option { + if field.attrs.len() > 0 { + if let List(MetaList { + path, + paren_token: _, + nested, + }) = field.attrs[0].parse_meta().unwrap() + { + if path.is_ident("tagval") { + for a in nested { + if let Lit(Int(litint)) = a { + return Some(litint.base10_parse::().unwrap()); + } + } + } + } + } + None +} + +/// Generate a ToTlv implementation for a structure +fn gen_totlv_for_struct( + fields: &syn::FieldsNamed, + struct_name: &proc_macro2::Ident, + tlvargs: TlvArgs, + generics: syn::Generics, +) -> TokenStream { + let mut tag_start = tlvargs.start; + let datatype = format_ident!("start_{}", tlvargs.datatype); + + let mut idents = Vec::new(); + let mut tags = Vec::new(); + + for field in fields.named.iter() { + // let field_name: &syn::Ident = field.ident.as_ref().unwrap(); + // let name: String = field_name.to_string(); + // let literal_key_str = syn::LitStr::new(&name, field.span()); + // let type_name = &field.ty; + // keys.push(quote! { #literal_key_str }); + idents.push(&field.ident); + // types.push(type_name.to_token_stream()); + if let Some(a) = parse_tag_val(&field) { + tags.push(a); + } else { + tags.push(tag_start); + tag_start += 1; + } + } + + let expanded = quote! { + impl #generics ToTLV for #struct_name #generics { + fn to_tlv(&self, tw: &mut TLVWriter, tag_type: TagType) -> Result<(), Error> { + tw. #datatype (tag_type)?; + #( + self.#idents.to_tlv(tw, TagType::Context(#tags))?; + )* + tw.end_container() + } + } + }; + // panic!("The generated code is {}", expanded); + expanded.into() +} + +/// Generate a ToTlv implementation for an enum +fn gen_totlv_for_enum( + data_enum: syn::DataEnum, + enum_name: &proc_macro2::Ident, + tlvargs: TlvArgs, + generics: syn::Generics, +) -> TokenStream { + let mut tag_start = tlvargs.start; + + let mut variant_names = Vec::new(); + let mut types = Vec::new(); + let mut tags = Vec::new(); + + for v in data_enum.variants.iter() { + variant_names.push(&v.ident); + if let syn::Fields::Unnamed(fields) = &v.fields { + if let Type::Path(path) = &fields.unnamed[0].ty { + types.push(&path.path.segments[0].ident); + } else { + panic!("Path not found {:?}", v.fields); + } + } else { + panic!("Unnamed field not found {:?}", v.fields); + } + tags.push(tag_start); + tag_start += 1; + } + + let expanded = quote! { + impl #generics ToTLV for #enum_name #generics { + fn to_tlv(&self, tw: &mut TLVWriter, tag_type: TagType) -> Result<(), Error> { + tw.start_struct(tag_type)?; + match self { + #( + Self::#variant_names(c) => { c.to_tlv(tw, TagType::Context(#tags))?; }, + )* + } + tw.end_container() + } + } + }; + + // panic!("Expanded to {}", expanded); + expanded.into() +} + +/// Derive ToTLV Macro +/// +/// This macro works for structures. It will create an implementation +/// of the ToTLV trait for that structure. All the members of the +/// structure, sequentially, will get Context tags starting from 0 +/// Some configurations are possible through the 'tlvargs' attributes. +/// For example: +/// #[tlvargs(start = 1, datatype = "list")] +/// +/// start: This can be used to override the default tag from which the +/// encoding starts (Default: 0) +/// datatype: This can be used to define whether this data structure is +/// to be encoded as a structure or list. Possible values: list +/// (Default: struct) +/// +/// Additionally, structure members can use the tagval attribute to +/// define a specific tag to be used +/// For example: +/// #[argval(22)] +/// name: u8, +/// In the above case, the 'name' attribute will be encoded/decoded with +/// the tag 22 + +#[proc_macro_derive(ToTLV, attributes(tlvargs, tagval))] +pub fn derive_totlv(item: TokenStream) -> TokenStream { + let ast = parse_macro_input!(item as DeriveInput); + let name = &ast.ident; + + let tlvargs = parse_tlvargs(&ast); + let generics = ast.generics; + + if let syn::Data::Struct(syn::DataStruct { + fields: syn::Fields::Named(ref fields), + .. + }) = ast.data + { + gen_totlv_for_struct(fields, name, tlvargs, generics) + } else if let syn::Data::Enum(data_enum) = ast.data { + gen_totlv_for_enum(data_enum, name, tlvargs, generics) + } else { + panic!( + "Derive ToTLV - Only supported Struct for now {:?}", + ast.data + ); + } +} + +/// Generate a FromTlv implementation for a structure +fn gen_fromtlv_for_struct( + fields: &syn::FieldsNamed, + struct_name: &proc_macro2::Ident, + tlvargs: TlvArgs, + generics: syn::Generics, +) -> TokenStream { + let mut tag_start = tlvargs.start; + let lifetime = tlvargs.lifetime; + let datatype = format_ident!("confirm_{}", tlvargs.datatype); + + let mut idents = Vec::new(); + let mut types = Vec::new(); + let mut tags = Vec::new(); + + for field in fields.named.iter() { + let type_name = &field.ty; + if let Some(a) = parse_tag_val(&field) { + // TODO: The current limitation with this is that a hard-coded integer + // value has to be mentioned in the tagval attribute. This is because + // our tags vector is for integers, and pushing an 'identifier' on it + // wouldn't work. + tags.push(a); + } else { + tags.push(tag_start); + tag_start += 1; + } + idents.push(&field.ident); + + if let Type::Path(path) = type_name { + types.push(&path.path.segments[0].ident); + } else { + panic!("Don't know what to do {:?}", type_name); + } + } + + // Currently we don't use find_tag() because the tags come in sequential + // order. If ever the tags start coming out of order, we can use find_tag() + // instead + let expanded = if !tlvargs.unordered { + quote! { + impl #generics FromTLV <#lifetime> for #struct_name #generics { + fn from_tlv(t: &TLVElement<#lifetime>) -> Result { + let mut t_iter = t.#datatype ()?.enter().ok_or(Error::Invalid)?; + let mut item = t_iter.next(); + #( + let #idents = if Some(true) == item.map(|x| x.check_ctx_tag(#tags)) { + let backup = item; + item = t_iter.next(); + #types::from_tlv(&backup.unwrap()) + } else { + #types::tlv_not_found() + }?; + )* + Ok(Self { + #(#idents, + )* + }) + } + } + } + } else { + quote! { + impl #generics FromTLV <#lifetime> for #struct_name #generics { + fn from_tlv(t: &TLVElement<#lifetime>) -> Result { + #( + let #idents = if let Ok(s) = t.find_tag(#tags as u32) { + #types::from_tlv(&s) + } else { + #types::tlv_not_found() + }?; + )* + + Ok(Self { + #(#idents, + )* + }) + } + } + } + }; + // panic!("The generated code is {}", expanded); + expanded.into() +} + +/// Generate a FromTlv implementation for an enum +fn gen_fromtlv_for_enum( + data_enum: syn::DataEnum, + enum_name: &proc_macro2::Ident, + tlvargs: TlvArgs, + generics: syn::Generics, +) -> TokenStream { + let mut tag_start = tlvargs.start; + let lifetime = tlvargs.lifetime; + + let mut variant_names = Vec::new(); + let mut types = Vec::new(); + let mut tags = Vec::new(); + + for v in data_enum.variants.iter() { + variant_names.push(&v.ident); + if let syn::Fields::Unnamed(fields) = &v.fields { + if let Type::Path(path) = &fields.unnamed[0].ty { + types.push(&path.path.segments[0].ident); + } else { + panic!("Path not found {:?}", v.fields); + } + } else { + panic!("Unnamed field not found {:?}", v.fields); + } + tags.push(tag_start); + tag_start += 1; + } + + let expanded = quote! { + impl #generics FromTLV <#lifetime> for #enum_name #generics { + fn from_tlv(t: &TLVElement<#lifetime>) -> Result { + let mut t_iter = t.confirm_struct()?.enter().ok_or(Error::Invalid)?; + let mut item = t_iter.next().ok_or(Error::Invalid)?; + if let TagType::Context(tag) = item.get_tag() { + match tag { + #( + #tags => Ok(Self::#variant_names(#types::from_tlv(&item)?)), + )* + _ => Err(Error::Invalid), + } + } else { + Err(Error::TLVTypeMismatch) + } + } + } + }; + + // panic!("Expanded to {}", expanded); + expanded.into() +} + +/// Derive FromTLV Macro +/// +/// This macro works for structures. It will create an implementation +/// of the FromTLV trait for that structure. All the members of the +/// structure, sequentially, will get Context tags starting from 0 +/// Some configurations are possible through the 'tlvargs' attributes. +/// For example: +/// #[tlvargs(lifetime = "'a", start = 1, datatype = "list", unordered)] +/// +/// start: This can be used to override the default tag from which the +/// decoding starts (Default: 0) +/// datatype: This can be used to define whether this data structure is +/// to be decoded as a structure or list. Possible values: list +/// (Default: struct) +/// lifetime: If the structure has a lifetime annotation, use this variable +/// to indicate that. The 'impl' will then use that lifetime +/// indicator. +/// unordered: By default, the decoder expects that the tags are in +/// sequentially increasing order. Set this if that is not the case. +/// +/// Additionally, structure members can use the tagval attribute to +/// define a specific tag to be used +/// For example: +/// #[argval(22)] +/// name: u8, +/// In the above case, the 'name' attribute will be encoded/decoded with +/// the tag 22 + +#[proc_macro_derive(FromTLV, attributes(tlvargs, tagval))] +pub fn derive_fromtlv(item: TokenStream) -> TokenStream { + let ast = parse_macro_input!(item as DeriveInput); + let name = &ast.ident; + + let tlvargs = parse_tlvargs(&ast); + + let generics = ast.generics; + + if let syn::Data::Struct(syn::DataStruct { + fields: syn::Fields::Named(ref fields), + .. + }) = ast.data + { + gen_fromtlv_for_struct(fields, name, tlvargs, generics) + } else if let syn::Data::Enum(data_enum) = ast.data { + gen_fromtlv_for_enum(data_enum, name, tlvargs, generics) + } else { + panic!( + "Derive FromTLV - Only supported Struct for now {:?}", + ast.data + ) + } +} diff --git a/tools/tlv_tool/Cargo.toml b/tools/tlv_tool/Cargo.toml new file mode 100644 index 0000000..2aaabe8 --- /dev/null +++ b/tools/tlv_tool/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "tlv_tool" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +matter-iot= { path = "../../matter" } +log = {version = "0.4.14", features = ["max_level_trace", "release_max_level_warn"]} +simple_logger = "1.16.0" +clap = "2.34" diff --git a/tools/tlv_tool/README.md b/tools/tlv_tool/README.md new file mode 100644 index 0000000..de81c13 --- /dev/null +++ b/tools/tlv_tool/README.md @@ -0,0 +1,10 @@ +# TLV Tool +A simple tool for printing Matter TLVs or Matter-encoded certificates. + +``` +$ # For printing a Matter TLV List +$ tlv_tool --hex "15, 24, 0, 1, 18" + +$ # For printing a Matter encoded certificate +$ tlv_tool --cert "0x15, 0x00" +``` diff --git a/tools/tlv_tool/src/main.rs b/tools/tlv_tool/src/main.rs new file mode 100644 index 0000000..43b2440 --- /dev/null +++ b/tools/tlv_tool/src/main.rs @@ -0,0 +1,94 @@ +/* + * + * 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. + */ + +extern crate clap; +use clap::{App, Arg}; +use matter::cert; +use matter::tlv; +use simple_logger::SimpleLogger; +use std::process; +use std::u8; + +fn main() { + SimpleLogger::new() + .with_level(log::LevelFilter::Trace) + .with_colors(true) + .without_timestamps() + .init() + .unwrap(); + + let m = App::new("tlv_tool") + .arg( + Arg::with_name("hex") + .short("h") + .long("hex") + .help("The input is in Hexadecimal (Default)"), + ) + .arg( + Arg::with_name("dec") + .short("d") + .long("dec") + .help("The input is in Decimal"), + ) + .arg( + Arg::with_name("cert") + .long("cert") + .help("The input is a Matter-encoded Certificate"), + ) + .arg(Arg::with_name("tlvs").help("List of TLVs").required(true)) + .get_matches(); + + // Assume hexadecimal by-default + let base = if m.is_present("hex") { + 16 + } else if m.is_present("dec") { + 10 + } else { + 16 + }; + + let list: String = m + .value_of("tlvs") + .unwrap() + .chars() + .filter(|c| !c.is_whitespace()) + .collect(); + let list = list.split(','); + let mut tlv_list: [u8; 1024] = [0; 1024]; + let mut index = 0; + for byte in list { + let byte = byte.strip_prefix("0x").unwrap_or(byte); + if let Ok(b) = u8::from_str_radix(byte, base) { + tlv_list[index] = b; + index += 1; + } else { + eprintln!("Skipping unknown byte: {}", byte); + } + if index >= 1024 { + eprintln!("Input too long"); + process::exit(1); + } + } + + // println!("Decoding: {:x?}", &tlv_list[..index]); + if m.is_present("cert") { + let cert = cert::Cert::new(&tlv_list[..index]).unwrap(); + println!("{}", cert); + } else { + tlv::print_tlv_list(&tlv_list[..index]); + } +}