From f135e2dbf8f40b2c9832b4f4800d6e3a83610204 Mon Sep 17 00:00:00 2001 From: Kedar Sovani Date: Sat, 7 Jan 2023 21:47:33 +0530 Subject: [PATCH 01/30] AdminCommissioning: Baseline support Just add all the 3 attributes, and the command open-commissioning-window that simply sets a variable for now --- matter/src/data_model/device_types.rs | 2 + matter/src/data_model/objects/endpoint.rs | 2 +- .../src/data_model/sdm/admin_commissioning.rs | 163 ++++++++++++++++++ matter/src/data_model/sdm/mod.rs | 1 + 4 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 matter/src/data_model/sdm/admin_commissioning.rs diff --git a/matter/src/data_model/device_types.rs b/matter/src/data_model/device_types.rs index 62b81f5..71a2cf4 100644 --- a/matter/src/data_model/device_types.rs +++ b/matter/src/data_model/device_types.rs @@ -19,6 +19,7 @@ use super::cluster_basic_information::BasicInfoCluster; use super::cluster_basic_information::BasicInfoConfig; use super::cluster_on_off::OnOffCluster; use super::objects::*; +use super::sdm::admin_commissioning::AdminCommCluster; use super::sdm::dev_att::DevAttDataFetcher; use super::sdm::general_commissioning::GenCommCluster; use super::sdm::noc::NocCluster; @@ -51,6 +52,7 @@ pub fn device_type_add_root_node( let failsafe = general_commissioning.failsafe(); node.add_cluster(0, general_commissioning)?; node.add_cluster(0, NwCommCluster::new()?)?; + node.add_cluster(0, AdminCommCluster::new()?)?; node.add_cluster( 0, NocCluster::new(dev_att, fabric_mgr, acl_mgr.clone(), failsafe)?, diff --git a/matter/src/data_model/objects/endpoint.rs b/matter/src/data_model/objects/endpoint.rs index a790fc6..ae5ed4c 100644 --- a/matter/src/data_model/objects/endpoint.rs +++ b/matter/src/data_model/objects/endpoint.rs @@ -19,7 +19,7 @@ use crate::{data_model::objects::ClusterType, error::*, interaction_model::core: use std::fmt; -pub const CLUSTERS_PER_ENDPT: usize = 7; +pub const CLUSTERS_PER_ENDPT: usize = 9; pub struct Endpoint { clusters: Vec>, diff --git a/matter/src/data_model/sdm/admin_commissioning.rs b/matter/src/data_model/sdm/admin_commissioning.rs new file mode 100644 index 0000000..6e7d7b7 --- /dev/null +++ b/matter/src/data_model/sdm/admin_commissioning.rs @@ -0,0 +1,163 @@ +/* + * + * Copyright (c) 2023 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::interaction_model::core::IMStatusCode; +use crate::tlv::{FromTLV, Nullable, OctetStr, TLVElement}; +use crate::{error::*, interaction_model::command::CommandReq}; +use log::{error, info}; +use num_derive::FromPrimitive; + +pub const ID: u32 = 0x003C; + +#[derive(FromPrimitive, Debug, Copy, Clone, PartialEq)] +pub enum WindowStatus { + WindowNotOpen = 0, + EnhancedWindowOpen = 1, + BasicWindowOpen = 2, +} + +#[derive(FromPrimitive)] +pub enum Attributes { + WindowStatus = 0, + AdminFabricIndex = 1, + AdminVendorId = 2, +} + +#[derive(FromPrimitive)] +pub enum Commands { + OpenCommWindow = 0x00, + OpenBasicCommWindow = 0x01, + RevokeComm = 0x02, +} + +fn attr_window_status_new() -> Result { + Attribute::new( + Attributes::WindowStatus as u16, + AttrValue::Custom, + Access::RV, + Quality::NONE, + ) +} + +fn attr_admin_fabid_new() -> Result { + Attribute::new( + Attributes::AdminFabricIndex as u16, + AttrValue::Custom, + Access::RV, + Quality::NULLABLE, + ) +} + +fn attr_admin_vid_new() -> Result { + Attribute::new( + Attributes::AdminVendorId as u16, + AttrValue::Custom, + Access::RV, + Quality::NULLABLE, + ) +} + +pub struct AdminCommCluster { + window_status: WindowStatus, + base: Cluster, +} + +impl ClusterType for AdminCommCluster { + 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::WindowStatus) => { + let status = self.window_status as u8; + encoder.encode(EncodeValue::Value(&status)) + } + Some(Attributes::AdminVendorId) => { + let vid = if self.window_status == WindowStatus::WindowNotOpen { + Nullable::Null + } else { + Nullable::NotNull(1_u8) + }; + encoder.encode(EncodeValue::Value(&vid)) + } + Some(Attributes::AdminFabricIndex) => { + let vid = if self.window_status == WindowStatus::WindowNotOpen { + Nullable::Null + } else { + Nullable::NotNull(1_u8) + }; + encoder.encode(EncodeValue::Value(&vid)) + } + _ => { + 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::OpenCommWindow => self.handle_command_opencomm_win(cmd_req), + _ => Err(IMStatusCode::UnsupportedCommand), + } + } +} + +impl AdminCommCluster { + pub fn new() -> Result, Error> { + let mut c = Box::new(AdminCommCluster { + window_status: WindowStatus::WindowNotOpen, + base: Cluster::new(ID)?, + }); + c.base.add_attribute(attr_window_status_new()?)?; + c.base.add_attribute(attr_admin_fabid_new()?)?; + c.base.add_attribute(attr_admin_vid_new()?)?; + Ok(c) + } + + fn handle_command_opencomm_win( + &mut self, + cmd_req: &mut CommandReq, + ) -> Result<(), IMStatusCode> { + cmd_enter!("Open Commissioning Window"); + let _req = + OpenCommWindowReq::from_tlv(&cmd_req.data).map_err(|_| IMStatusCode::InvalidCommand)?; + self.window_status = WindowStatus::EnhancedWindowOpen; + Err(IMStatusCode::Sucess) + } +} + +#[derive(FromTLV)] +#[tlvargs(lifetime = "'a")] +pub struct OpenCommWindowReq<'a> { + _timeout: u16, + _verifier: OctetStr<'a>, + _discriminator: u16, + _iterations: u32, + _salt: OctetStr<'a>, +} diff --git a/matter/src/data_model/sdm/mod.rs b/matter/src/data_model/sdm/mod.rs index cec166d..1ce25ad 100644 --- a/matter/src/data_model/sdm/mod.rs +++ b/matter/src/data_model/sdm/mod.rs @@ -15,6 +15,7 @@ * limitations under the License. */ +pub mod admin_commissioning; pub mod dev_att; pub mod failsafe; pub mod general_commissioning; From e9ea342bf7453ad683fad7934023558beecce54d Mon Sep 17 00:00:00 2001 From: Kedar Sovani Date: Sun, 8 Jan 2023 11:13:12 +0530 Subject: [PATCH 02/30] mdns: Discriminator is a property of the 'commissionable' state --- matter/src/core.rs | 8 ++++++-- matter/src/mdns.rs | 19 ++++++++----------- matter/src/secure_channel/core.rs | 10 ++++++++-- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/matter/src/core.rs b/matter/src/core.rs index 0706413..c5eb5b0 100644 --- a/matter/src/core.rs +++ b/matter/src/core.rs @@ -62,7 +62,7 @@ impl Matter { dev_comm: CommissioningData, ) -> Result, Error> { let mdns = Mdns::get()?; - mdns.set_values(dev_det.vid, dev_det.pid, dev_comm.discriminator); + mdns.set_values(dev_det.vid, dev_det.pid); let fabric_mgr = Arc::new(FabricMgr::new()?); let acl_mgr = Arc::new(AclMgr::new()?); @@ -78,7 +78,11 @@ impl Matter { 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)?; + secure_channel.open_comm_window( + &dev_comm.salt, + dev_comm.passwd, + dev_comm.discriminator, + )?; } matter.transport_mgr.register_protocol(secure_channel)?; diff --git a/matter/src/mdns.rs b/matter/src/mdns.rs index f5aeb7c..287eece 100644 --- a/matter/src/mdns.rs +++ b/matter/src/mdns.rs @@ -30,8 +30,6 @@ pub struct MdnsInner { vid: u16, /// Product ID pid: u16, - /// Discriminator - discriminator: u16, } pub struct Mdns { @@ -45,8 +43,10 @@ static mut G_MDNS: Option> = None; static INIT: Once = Once::new(); pub enum ServiceMode { + /// The commissioned state Commissioned, - Commissionable, + /// The commissionable state with the discriminator that should be used + Commissionable(u16), } impl Mdns { @@ -71,11 +71,10 @@ impl Mdns { /// 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) { + pub fn set_values(&self, vid: u16, pid: u16) { let mut inner = self.inner.lock().unwrap(); inner.vid = vid; inner.pid = pid; - inner.discriminator = discriminator; } /// Publish a mDNS service @@ -86,13 +85,11 @@ impl Mdns { 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); + ServiceMode::Commissionable(discriminator) => { + let short = (discriminator & SHORT_DISCRIMINATOR_MASK) >> SHORT_DISCRIMINATOR_SHIFT; + let serv_type = format!("_matterc._udp,_S{},_L{}", short, discriminator); - let str_discriminator = format!("{}", inner.discriminator); + let str_discriminator = format!("{}", 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/core.rs b/matter/src/secure_channel/core.rs index 8a2774c..86ae55b 100644 --- a/matter/src/secure_channel/core.rs +++ b/matter/src/secure_channel/core.rs @@ -48,10 +48,16 @@ impl SecureChannel { } } - pub fn open_comm_window(&mut self, salt: &[u8; 16], passwd: u32) -> Result<(), Error> { + pub fn open_comm_window( + &mut self, + salt: &[u8; 16], + passwd: u32, + discriminator: u16, + ) -> 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)?; + let mdns = Mdns::get()? + .publish_service(&name, mdns::ServiceMode::Commissionable(discriminator))?; self.pake = Some((PAKE::new(salt, passwd), mdns)); Ok(()) } From ebaf5438cd20db7d3bbb74ecb87738cfddc83e39 Mon Sep 17 00:00:00 2001 From: Kedar Sovani Date: Sun, 8 Jan 2023 12:27:27 +0530 Subject: [PATCH 03/30] pase: Create provision to accept either password or verifier for configuring PASE --- examples/onoff_light/src/main.rs | 8 +- matter/src/core.rs | 16 +--- matter/src/lib.rs | 10 +-- matter/src/secure_channel/core.rs | 7 +- matter/src/secure_channel/crypto.rs | 4 +- matter/src/secure_channel/crypto_mbedtls.rs | 7 +- matter/src/secure_channel/crypto_openssl.rs | 7 +- matter/src/secure_channel/pake.rs | 21 ++---- matter/src/secure_channel/spake2p.rs | 82 ++++++++++++++++++--- 9 files changed, 109 insertions(+), 53 deletions(-) diff --git a/examples/onoff_light/src/main.rs b/examples/onoff_light/src/main.rs index 15bfc4f..15c6949 100644 --- a/examples/onoff_light/src/main.rs +++ b/examples/onoff_light/src/main.rs @@ -19,17 +19,15 @@ 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::*; +use matter::secure_channel::spake2p::VerifierData; fn main() { env_logger::init(); - let mut comm_data = CommissioningData { + let comm_data = CommissioningData { // TODO: Hard-coded for now - passwd: 123456, + verifier: VerifierData::new_with_pw(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 { diff --git a/matter/src/core.rs b/matter/src/core.rs index c5eb5b0..9dedb6b 100644 --- a/matter/src/core.rs +++ b/matter/src/core.rs @@ -25,19 +25,15 @@ use crate::{ fabric::FabricMgr, interaction_model::InteractionModel, mdns::Mdns, - secure_channel::core::SecureChannel, + secure_channel::{core::SecureChannel, spake2p::VerifierData}, 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 data like password or verifier that is required to authenticate + pub verifier: VerifierData, /// The 12-bit discriminator used to differentiate between multiple devices pub discriminator: u16, } @@ -78,11 +74,7 @@ impl Matter { 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, - dev_comm.discriminator, - )?; + secure_channel.open_comm_window(dev_comm.verifier, dev_comm.discriminator)?; } matter.transport_mgr.register_protocol(secure_channel)?; diff --git a/matter/src/lib.rs b/matter/src/lib.rs index 728c484..5d39f6b 100644 --- a/matter/src/lib.rs +++ b/matter/src/lib.rs @@ -15,7 +15,6 @@ * limitations under the License. */ - //! Native Rust Implementation of Matter (Smart-Home) //! //! This crate implements the Matter specification that can be run on embedded devices @@ -28,7 +27,7 @@ //! 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::secure_channel::spake2p::VerifierData; //! //! # use matter::data_model::sdm::dev_att::{DataType, DevAttDataFetcher}; //! # use matter::error::Error; @@ -39,12 +38,11 @@ //! # let dev_att = Box::new(DevAtt{}); //! //! /// The commissioning data for this device -//! let mut comm_data = CommissioningData { -//! passwd: 123456, +//! let comm_data = CommissioningData { +//! verifier: VerifierData::new_with_pw(123456), //! discriminator: 250, -//! ..Default::default() +//! //! }; -//! rand::thread_rng().fill_bytes(&mut comm_data.salt); //! //! /// The basic information about this device //! let dev_info = BasicInfoConfig { diff --git a/matter/src/secure_channel/core.rs b/matter/src/secure_channel/core.rs index 86ae55b..c5f04a8 100644 --- a/matter/src/secure_channel/core.rs +++ b/matter/src/secure_channel/core.rs @@ -30,7 +30,7 @@ use log::{error, info}; use num; use rand::prelude::*; -use super::case::Case; +use super::{case::Case, spake2p::VerifierData}; /* Handle messages related to the Secure Channel */ @@ -50,15 +50,14 @@ impl SecureChannel { pub fn open_comm_window( &mut self, - salt: &[u8; 16], - passwd: u32, + verifier: VerifierData, discriminator: u16, ) -> 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(discriminator))?; - self.pake = Some((PAKE::new(salt, passwd), mdns)); + self.pake = Some((PAKE::new(verifier), mdns)); Ok(()) } diff --git a/matter/src/secure_channel/crypto.rs b/matter/src/secure_channel/crypto.rs index 68dac8d..f9481ba 100644 --- a/matter/src/secure_channel/crypto.rs +++ b/matter/src/secure_channel/crypto.rs @@ -38,7 +38,9 @@ pub trait CryptoSpake2 { fn set_w1(&mut self, w1: &[u8]) -> Result<(), Error>; #[allow(non_snake_case)] - fn set_L(&mut self, w1s: &[u8]) -> Result<(), Error>; + fn set_L(&mut self, l: &[u8]) -> Result<(), Error>; + #[allow(non_snake_case)] + fn set_L_from_w1s(&mut self, w1s: &[u8]) -> Result<(), Error>; #[allow(non_snake_case)] fn get_pB(&mut self, pB: &mut [u8]) -> Result<(), Error>; #[allow(non_snake_case)] diff --git a/matter/src/secure_channel/crypto_mbedtls.rs b/matter/src/secure_channel/crypto_mbedtls.rs index 57eeff2..7231f63 100644 --- a/matter/src/secure_channel/crypto_mbedtls.rs +++ b/matter/src/secure_channel/crypto_mbedtls.rs @@ -114,9 +114,14 @@ impl CryptoSpake2 for CryptoMbedTLS { Ok(()) } + fn set_L(&mut self, l: &[u8]) -> Result<(), Error> { + self.L = EcPoint::from_binary(&mut self.group, l)?; + Ok(()) + } + #[allow(non_snake_case)] #[allow(dead_code)] - fn set_L(&mut self, w1s: &[u8]) -> Result<(), Error> { + fn set_L_from_w1s(&mut self, w1s: &[u8]) -> Result<(), Error> { // From the Matter spec, // L = w1 * P // where P is the generator of the underlying elliptic curve diff --git a/matter/src/secure_channel/crypto_openssl.rs b/matter/src/secure_channel/crypto_openssl.rs index 0f80f4c..84d6793 100644 --- a/matter/src/secure_channel/crypto_openssl.rs +++ b/matter/src/secure_channel/crypto_openssl.rs @@ -117,9 +117,14 @@ impl CryptoSpake2 for CryptoOpenSSL { Ok(()) } + fn set_L(&mut self, l: &[u8]) -> Result<(), Error> { + self.L = EcPoint::from_bytes(&self.group, l, &mut self.bn_ctx)?; + Ok(()) + } + #[allow(non_snake_case)] #[allow(dead_code)] - fn set_L(&mut self, w1s: &[u8]) -> Result<(), Error> { + fn set_L_from_w1s(&mut self, w1s: &[u8]) -> Result<(), Error> { // From the Matter spec, // L = w1 * P // where P is the generator of the underlying elliptic curve diff --git a/matter/src/secure_channel/pake.rs b/matter/src/secure_channel/pake.rs index 008f193..3163f56 100644 --- a/matter/src/secure_channel/pake.rs +++ b/matter/src/secure_channel/pake.rs @@ -19,12 +19,11 @@ use std::time::{Duration, SystemTime}; use super::{ common::{create_sc_status_report, SCStatusCodes}, - spake2p::Spake2P, + spake2p::{Spake2P, VerifierData}, }; use crate::{ crypto, error::Error, - sys::SPAKE2_ITERATION_COUNT, tlv::{self, get_root_node_struct, FromTLV, OctetStr, TLVElement, TLVWriter, TagType, ToTLV}, transport::{ exchange::ExchangeCtx, @@ -111,20 +110,17 @@ impl Default for PakeState { } } -#[derive(Default)] pub struct PAKE { - salt: [u8; 16], - passwd: u32, + verifier: VerifierData, state: PakeState, } impl PAKE { - pub fn new(salt: &[u8; 16], passwd: u32) -> Self { + pub fn new(verifier: VerifierData) -> Self { // TODO: Can any PBKDF2 calculation be pre-computed here PAKE { - passwd, - salt: *salt, - ..Default::default() + verifier, + state: Default::default(), } } @@ -176,8 +172,7 @@ impl PAKE { 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.start_verifier(&self.verifier)?; sd.spake2p.handle_pA(pA, &mut pB, &mut cB)?; let mut tw = TLVWriter::new(ctx.tx.get_writebuf()?); @@ -231,8 +226,8 @@ impl PAKE { }; if !a.has_params { let params_resp = PBKDFParamRespParams { - count: SPAKE2_ITERATION_COUNT, - salt: OctetStr(&self.salt), + count: self.verifier.count, + salt: OctetStr(&self.verifier.salt), }; resp.params = Some(params_resp); } diff --git a/matter/src/secure_channel/spake2p.rs b/matter/src/secure_channel/spake2p.rs index 3870bcd..5e82f9f 100644 --- a/matter/src/secure_channel/spake2p.rs +++ b/matter/src/secure_channel/spake2p.rs @@ -15,8 +15,13 @@ * limitations under the License. */ -use crate::crypto::{self, HmacSha256}; +use crate::{ + crypto::{self, HmacSha256}, + sys, +}; use byteorder::{ByteOrder, LittleEndian}; +use log::error; +use rand::prelude::*; use subtle::ConstantTimeEq; use crate::{ @@ -74,6 +79,10 @@ 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; +const CRYPTO_PUBLIC_KEY_SIZE_BYTES: usize = (2 * CRYPTO_GROUP_SIZE_BYTES) + 1; + +const MAX_SALT_SIZE_BYTES: usize = 32; +const VERIFIER_SIZE_BYTES: usize = CRYPTO_W_SIZE_BYTES + CRYPTO_PUBLIC_KEY_SIZE_BYTES; #[cfg(feature = "crypto_openssl")] fn crypto_spake2_new() -> Result, Error> { @@ -96,6 +105,45 @@ impl Default for Spake2P { } } +pub struct VerifierData { + pub data: VerifierOption, + // For the VerifierOption::Verifier, the following fields only serve + // information purposes + pub salt: [u8; MAX_SALT_SIZE_BYTES], + pub count: u32, +} + +pub enum VerifierOption { + /// With Password + Password(u32), + /// With Verifier + Verifier([u8; VERIFIER_SIZE_BYTES]), +} + +impl VerifierData { + pub fn new_with_pw(pw: u32) -> Self { + let mut s = Self { + salt: [0; MAX_SALT_SIZE_BYTES], + count: sys::SPAKE2_ITERATION_COUNT, + data: VerifierOption::Password(pw), + }; + rand::thread_rng().fill_bytes(&mut s.salt); + s + } + + pub fn new( + verifier: [u8; VERIFIER_SIZE_BYTES], + count: u32, + salt: [u8; MAX_SALT_SIZE_BYTES], + ) -> Self { + Self { + data: VerifierOption::Verifier(verifier), + count, + salt, + } + } +} + impl Spake2P { pub fn new() -> Self { Spake2P { @@ -132,17 +180,31 @@ impl Spake2P { 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()?); + pub fn start_verifier(&mut self, verifier: &VerifierData) -> Result<(), Error> { + match verifier.data { + VerifierOption::Password(pw) => { + // Derive w0 and L from the password + let mut w0w1s: [u8; (2 * CRYPTO_W_SIZE_BYTES)] = [0; (2 * CRYPTO_W_SIZE_BYTES)]; + Spake2P::get_w0w1s(pw, verifier.count, &verifier.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..])?; + 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_from_w1s(&w0w1s[w0s_len..])?; + } + } + VerifierOption::Verifier(v) => { + // Extract w0 and L from the verifier + if v.len() != CRYPTO_GROUP_SIZE_BYTES + CRYPTO_PUBLIC_KEY_SIZE_BYTES { + error!("Verifier of invalid length"); + } + if let Some(crypto_spake2) = &mut self.crypto_spake2 { + crypto_spake2.set_w0(&v[0..CRYPTO_GROUP_SIZE_BYTES])?; + crypto_spake2.set_L(&v[CRYPTO_GROUP_SIZE_BYTES..])?; + } + } } - self.mode = Spake2Mode::Verifier(Spake2VerifierState::Init); Ok(()) } From 95c74ec2983a44e191e17aca90471728f53c0d88 Mon Sep 17 00:00:00 2001 From: Kedar Sovani Date: Tue, 10 Jan 2023 10:48:20 +0530 Subject: [PATCH 04/30] pase: Split out a separate Pase Mgr - This is required so that the AdminCommissioning Cluster has a reference to the Pase Mgr - The Cluster can then open commissioning window accordingly - Right now, for the error paths in the PASE/CASE sessions, I have set ResponseRequired to No, but I am not quite sure if this is the expected behaviour. Need to check --- matter/src/core.rs | 8 ++- matter/src/secure_channel/case.rs | 24 +++---- matter/src/secure_channel/core.rs | 100 ++++-------------------------- matter/src/secure_channel/pake.rs | 83 ++++++++++++++++++++++++- 4 files changed, 112 insertions(+), 103 deletions(-) diff --git a/matter/src/core.rs b/matter/src/core.rs index 9dedb6b..71eb6c3 100644 --- a/matter/src/core.rs +++ b/matter/src/core.rs @@ -25,7 +25,7 @@ use crate::{ fabric::FabricMgr, interaction_model::InteractionModel, mdns::Mdns, - secure_channel::{core::SecureChannel, spake2p::VerifierData}, + secure_channel::{core::SecureChannel, pake::PaseMgr, spake2p::VerifierData}, transport, }; use std::sync::Arc; @@ -72,11 +72,13 @@ impl Matter { 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())); + + let mut pase = PaseMgr::new(); if open_comm_window { - secure_channel.open_comm_window(dev_comm.verifier, dev_comm.discriminator)?; + pase.enable_pase_session(dev_comm.verifier, dev_comm.discriminator)?; } + let secure_channel = Box::new(SecureChannel::new(pase.clone(), matter.fabric_mgr.clone())); matter.transport_mgr.register_protocol(secure_channel)?; Ok(matter) } diff --git a/matter/src/secure_channel/case.rs b/matter/src/secure_channel/case.rs index 75d1fc9..33c8e47 100644 --- a/matter/src/secure_channel/case.rs +++ b/matter/src/secure_channel/case.rs @@ -26,12 +26,12 @@ use crate::{ crypto::{self, CryptoKeyPair, KeyPair, Sha256}, error::Error, fabric::{Fabric, FabricMgr, FabricMgrInner}, - secure_channel::common, secure_channel::common::SCStatusCodes, + secure_channel::common::{self, OpCode}, tlv::{get_root_node_struct, FromTLV, OctetStr, TLVElement, TLVWriter, TagType}, transport::{ network::Address, - proto_demux::ProtoCtx, + proto_demux::{ProtoCtx, ResponseRequired}, queue::{Msg, WorkQ}, session::{CloneData, SessionMode}, }, @@ -78,7 +78,7 @@ impl Case { Self { fabric_mgr } } - pub fn handle_casesigma3(&mut self, ctx: &mut ProtoCtx) -> Result<(), Error> { + pub fn casesigma3_handler(&mut self, ctx: &mut ProtoCtx) -> Result { let mut case_session = ctx .exch_ctx .exch @@ -97,7 +97,7 @@ impl Case { None, )?; ctx.exch_ctx.exch.close(); - return Ok(()); + return Ok(ResponseRequired::Yes); } // Safe to unwrap here let fabric = fabric.as_ref().as_ref().unwrap(); @@ -132,7 +132,7 @@ impl Case { None, )?; ctx.exch_ctx.exch.close(); - return Ok(()); + return Ok(ResponseRequired::Yes); } if Case::validate_sigma3_sign( @@ -151,7 +151,7 @@ impl Case { None, )?; ctx.exch_ctx.exch.close(); - return Ok(()); + return Ok(ResponseRequired::Yes); } // Only now do we add this message to the TT Hash @@ -174,10 +174,12 @@ impl Case { ctx.exch_ctx.exch.clear_data_boxed(); ctx.exch_ctx.exch.close(); - Ok(()) + Ok(ResponseRequired::Yes) } - pub fn handle_casesigma1(&mut self, ctx: &mut ProtoCtx) -> Result<(), Error> { + pub fn casesigma1_handler(&mut self, ctx: &mut ProtoCtx) -> Result { + ctx.tx.set_proto_opcode(OpCode::CASESigma2 as u8); + let rx_buf = ctx.rx.as_borrow_slice(); let root = get_root_node_struct(rx_buf)?; let r = Sigma1Req::from_tlv(&root)?; @@ -193,7 +195,7 @@ impl Case { None, )?; ctx.exch_ctx.exch.close(); - return Ok(()); + return Ok(ResponseRequired::Yes); } let local_sessid = ctx.exch_ctx.sess.reserve_new_sess_id(); @@ -239,7 +241,7 @@ impl Case { None, )?; ctx.exch_ctx.exch.close(); - return Ok(()); + return Ok(ResponseRequired::Yes); } let sign_len = Case::get_sigma2_sign( @@ -270,7 +272,7 @@ impl Case { tw.end_container()?; case_session.tt_hash.update(ctx.tx.as_borrow_slice())?; ctx.exch_ctx.exch.set_data_boxed(case_session); - Ok(()) + Ok(ResponseRequired::Yes) } fn get_session_clone_data( diff --git a/matter/src/secure_channel/core.rs b/matter/src/secure_channel/core.rs index c5f04a8..8d11134 100644 --- a/matter/src/secure_channel/core.rs +++ b/matter/src/secure_channel/core.rs @@ -20,105 +20,30 @@ use std::sync::Arc; use crate::{ error::*, fabric::FabricMgr, - mdns::{self, Mdns}, - secure_channel::{common::*, pake::PAKE}, - sys::SysMdnsService, + secure_channel::common::*, tlv, transport::proto_demux::{self, ProtoCtx, ResponseRequired}, }; use log::{error, info}; use num; -use rand::prelude::*; -use super::{case::Case, spake2p::VerifierData}; +use super::{case::Case, pake::PaseMgr}; /* Handle messages related to the Secure Channel */ pub struct SecureChannel { case: Case, - pake: Option<(PAKE, SysMdnsService)>, + pase: PaseMgr, } impl SecureChannel { - pub fn new(fabric_mgr: Arc) -> SecureChannel { + pub fn new(pase: PaseMgr, fabric_mgr: Arc) -> SecureChannel { SecureChannel { - pake: None, + pase, case: Case::new(fabric_mgr), } } - - pub fn open_comm_window( - &mut self, - verifier: VerifierData, - discriminator: u16, - ) -> 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(discriminator))?; - self.pake = Some((PAKE::new(verifier), 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 { @@ -126,15 +51,16 @@ impl proto_demux::HandleProto for SecureChannel { 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); - info!("Received Data"); + info!("Received Opcode: {:?}", proto_opcode); + info!("Received Data:"); tlv::print_tlv_list(ctx.rx.as_borrow_slice()); let result = 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), + OpCode::MRPStandAloneAck => Ok(ResponseRequired::No), + OpCode::PBKDFParamRequest => self.pase.pbkdfparamreq_handler(ctx), + OpCode::PASEPake1 => self.pase.pasepake1_handler(ctx), + OpCode::PASEPake3 => self.pase.pasepake3_handler(ctx), + OpCode::CASESigma1 => self.case.casesigma1_handler(ctx), + OpCode::CASESigma3 => self.case.casesigma3_handler(ctx), _ => { error!("OpCode Not Handled: {:?}", proto_opcode); Err(Error::InvalidOpcode) diff --git a/matter/src/secure_channel/pake.rs b/matter/src/secure_channel/pake.rs index 3163f56..2bf7acd 100644 --- a/matter/src/secure_channel/pake.rs +++ b/matter/src/secure_channel/pake.rs @@ -15,7 +15,10 @@ * limitations under the License. */ -use std::time::{Duration, SystemTime}; +use std::{ + sync::{Arc, Mutex}, + time::{Duration, SystemTime}, +}; use super::{ common::{create_sc_status_report, SCStatusCodes}, @@ -24,11 +27,14 @@ use super::{ use crate::{ crypto, error::Error, + mdns::{self, Mdns}, + secure_channel::common::OpCode, + sys::SysMdnsService, tlv::{self, get_root_node_struct, FromTLV, OctetStr, TLVElement, TLVWriter, TagType, ToTLV}, transport::{ exchange::ExchangeCtx, network::Address, - proto_demux::ProtoCtx, + proto_demux::{ProtoCtx, ResponseRequired}, queue::{Msg, WorkQ}, session::{CloneData, SessionMode}, }, @@ -36,6 +42,79 @@ use crate::{ use log::{error, info}; use rand::prelude::*; +enum PaseSessionState { + Enabled(PAKE, SysMdnsService), + Disabled, +} + +pub struct PaseMgrInternal { + state: PaseSessionState, +} + +#[derive(Clone)] +// Could this lock be avoided? +pub struct PaseMgr(Arc>); + +impl PaseMgr { + pub fn new() -> Self { + Self(Arc::new(Mutex::new(PaseMgrInternal { + state: PaseSessionState::Disabled, + }))) + } + + pub fn enable_pase_session( + &mut self, + verifier: VerifierData, + discriminator: u16, + ) -> Result<(), Error> { + let mut s = self.0.lock().unwrap(); + 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(discriminator))?; + s.state = PaseSessionState::Enabled(PAKE::new(verifier), mdns); + Ok(()) + } + + pub fn disable_pase_session(&mut self) { + let mut s = self.0.lock().unwrap(); + s.state = PaseSessionState::Disabled; + } + + /// If the PASE Session is enabled, execute the closure, + /// if not enabled, generate SC Status Report + fn if_enabled(&mut self, ctx: &mut ProtoCtx, f: F) -> Result<(), Error> + where + F: FnOnce(&mut PAKE, &mut ProtoCtx) -> Result<(), Error>, + { + let mut s = self.0.lock().unwrap(); + if let PaseSessionState::Enabled(pake, _) = &mut s.state { + f(pake, ctx) + } else { + error!("PASE Not enabled"); + create_sc_status_report(&mut ctx.tx, SCStatusCodes::InvalidParameter, None) + } + } + + pub fn pbkdfparamreq_handler(&mut self, ctx: &mut ProtoCtx) -> Result { + ctx.tx.set_proto_opcode(OpCode::PBKDFParamResponse as u8); + self.if_enabled(ctx, |pake, ctx| pake.handle_pbkdfparamrequest(ctx))?; + Ok(ResponseRequired::Yes) + } + + pub fn pasepake1_handler(&mut self, ctx: &mut ProtoCtx) -> Result { + ctx.tx.set_proto_opcode(OpCode::PASEPake2 as u8); + self.if_enabled(ctx, |pake, ctx| pake.handle_pasepake1(ctx))?; + Ok(ResponseRequired::Yes) + } + + pub fn pasepake3_handler(&mut self, ctx: &mut ProtoCtx) -> Result { + self.if_enabled(ctx, |pake, ctx| pake.handle_pasepake3(ctx))?; + self.disable_pase_session(); + Ok(ResponseRequired::Yes) + } +} + // 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 From ba524e1190dc43f2b6d693e1ad83c419cfd9dc75 Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 10 Jan 2023 13:14:20 +0100 Subject: [PATCH 05/30] Extend service discovery information --- examples/onoff_light/src/main.rs | 1 + matter/src/core.rs | 7 ++- .../data_model/cluster_basic_information.rs | 2 + matter/src/lib.rs | 1 + matter/src/mdns.rs | 61 ++++++++++++------- matter/tests/common/im_engine.rs | 2 + 6 files changed, 50 insertions(+), 24 deletions(-) diff --git a/examples/onoff_light/src/main.rs b/examples/onoff_light/src/main.rs index ad36f07..000bc0e 100644 --- a/examples/onoff_light/src/main.rs +++ b/examples/onoff_light/src/main.rs @@ -37,6 +37,7 @@ fn main() { pid: 0x8002, hw_ver: 2, sw_ver: 1, + device_name: "OnOff Light".to_string(), }; let dev_att = Box::new(dev_att::HardCodedDevAtt::new()); diff --git a/matter/src/core.rs b/matter/src/core.rs index 6aee3ce..30dcf9d 100644 --- a/matter/src/core.rs +++ b/matter/src/core.rs @@ -62,7 +62,12 @@ impl Matter { dev_comm: &CommissioningData, ) -> Result, Error> { let mdns = Mdns::get()?; - mdns.set_values(dev_det.vid, dev_det.pid, dev_comm.discriminator); + mdns.set_values( + dev_det.vid, + dev_det.pid, + dev_comm.discriminator, + &dev_det.device_name, + ); let fabric_mgr = Arc::new(FabricMgr::new()?); let acl_mgr = Arc::new(AclMgr::new()?); diff --git a/matter/src/data_model/cluster_basic_information.rs b/matter/src/data_model/cluster_basic_information.rs index c480f48..10a2b34 100644 --- a/matter/src/data_model/cluster_basic_information.rs +++ b/matter/src/data_model/cluster_basic_information.rs @@ -31,6 +31,8 @@ pub struct BasicInfoConfig { pub pid: u16, pub hw_ver: u16, pub sw_ver: u32, + /// Device name; up to 32 characters + pub device_name: String, } fn attr_vid_new(vid: u16) -> Result { diff --git a/matter/src/lib.rs b/matter/src/lib.rs index ad677b5..2d2bee2 100644 --- a/matter/src/lib.rs +++ b/matter/src/lib.rs @@ -51,6 +51,7 @@ //! pid: 0xFFF1, //! hw_ver: 2, //! sw_ver: 1, +//! device_name: "OnOff Light".to_string(), //! }; //! //! /// Get the Matter Object diff --git a/matter/src/mdns.rs b/matter/src/mdns.rs index c1180a6..07f464a 100644 --- a/matter/src/mdns.rs +++ b/matter/src/mdns.rs @@ -1,20 +1,3 @@ -/* - * - * 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::{ @@ -32,19 +15,20 @@ pub struct MdnsInner { pid: u16, /// Discriminator discriminator: u16, + /// Device name + device_name: String, } pub struct Mdns { inner: Mutex, } -const SHORT_DISCRIMINATOR_MASK: u16 = 0x700; +const SHORT_DISCRIMINATOR_MASK: u16 = 0xF00; const SHORT_DISCRIMINATOR_SHIFT: u16 = 8; static mut G_MDNS: Option> = None; static INIT: Once = Once::new(); -#[derive(Clone, Copy)] pub enum ServiceMode { Commissioned, Commissionable, @@ -72,16 +56,18 @@ impl Mdns { /// 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) { + pub fn set_values(&self, vid: u16, pid: u16, discriminator: u16, device_name: &str) { let mut inner = self.inner.lock().unwrap(); inner.vid = vid; inner.pid = pid; inner.discriminator = discriminator; + inner.device_name = device_name.chars().take(32).collect(); } /// Publish a mDNS service /// name - is the service name (comma separated subtypes may follow) /// mode - the current service mode + #[allow(clippy::needless_pass_by_value)] pub fn publish_service(&self, name: &str, mode: ServiceMode) -> Result { match mode { ServiceMode::Commissioned => { @@ -89,14 +75,43 @@ impl Mdns { } ServiceMode::Commissionable => { let inner = self.inner.lock().unwrap(); - let short = - (inner.discriminator & SHORT_DISCRIMINATOR_MASK) >> SHORT_DISCRIMINATOR_SHIFT; + let short = compute_short_discriminator(inner.discriminator); 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"]]; + let txt_kvs = [ + ["D", &str_discriminator], + ["CM", "1"], + ["DN", &inner.device_name], + ["VP", &format!("{}+{}", inner.vid, inner.pid)], + ["SII", "5000"], /* Sleepy Idle Interval */ + ["SAI", "300"], /* Sleepy Active Interval */ + ["T", "1"], /* TCP supported */ + ["PH", "33"], /* Pairing Hint */ + ["PI", ""], /* Pairing Instruction */ + ]; sys_publish_service(name, &serv_type, MATTER_PORT, &txt_kvs) } } } } + +fn compute_short_discriminator(discriminator: u16) -> u16 { + (discriminator & SHORT_DISCRIMINATOR_MASK) >> SHORT_DISCRIMINATOR_SHIFT +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn can_compute_short_discriminator() { + let discriminator: u16 = 0b0000_1111_0000_0000; + let short = compute_short_discriminator(discriminator); + assert_eq!(short, 0b1111); + + let discriminator: u16 = 840; + let short = compute_short_discriminator(discriminator); + assert_eq!(short, 3); + } +} diff --git a/matter/tests/common/im_engine.rs b/matter/tests/common/im_engine.rs index 81c2c74..19cb6af 100644 --- a/matter/tests/common/im_engine.rs +++ b/matter/tests/common/im_engine.rs @@ -93,7 +93,9 @@ impl ImEngine { pid: 11, hw_ver: 12, sw_ver: 13, + device_name: "Test Device".to_string(), }; + let dev_att = Box::new(DummyDevAtt {}); let fabric_mgr = Arc::new(FabricMgr::new().unwrap()); let acl_mgr = Arc::new(AclMgr::new_with(false).unwrap()); From 548f1a42b1c0fa1ad79c8f38c7e0ee5df70e39bf Mon Sep 17 00:00:00 2001 From: Kedar Sovani Date: Tue, 10 Jan 2023 18:37:28 +0530 Subject: [PATCH 06/30] mdns: Fix incorrect mask for short discriminator --- matter/src/mdns.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matter/src/mdns.rs b/matter/src/mdns.rs index 287eece..3e969de 100644 --- a/matter/src/mdns.rs +++ b/matter/src/mdns.rs @@ -36,7 +36,7 @@ pub struct Mdns { inner: Mutex, } -const SHORT_DISCRIMINATOR_MASK: u16 = 0x700; +const SHORT_DISCRIMINATOR_MASK: u16 = 0xf00; const SHORT_DISCRIMINATOR_SHIFT: u16 = 8; static mut G_MDNS: Option> = None; From b24c9acc7d7c199c06ec7305a51c842394101639 Mon Sep 17 00:00:00 2001 From: Kedar Sovani Date: Tue, 10 Jan 2023 18:39:14 +0530 Subject: [PATCH 07/30] spake2p: Fix issues - Verifier length was incorrect - spake2 was not being initialised in the 'Verifier' Option - change method for new() --- matter/src/secure_channel/spake2p.rs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/matter/src/secure_channel/spake2p.rs b/matter/src/secure_channel/spake2p.rs index 5e82f9f..ad0cac5 100644 --- a/matter/src/secure_channel/spake2p.rs +++ b/matter/src/secure_channel/spake2p.rs @@ -82,7 +82,7 @@ const CRYPTO_W_SIZE_BYTES: usize = CRYPTO_GROUP_SIZE_BYTES + 8; const CRYPTO_PUBLIC_KEY_SIZE_BYTES: usize = (2 * CRYPTO_GROUP_SIZE_BYTES) + 1; const MAX_SALT_SIZE_BYTES: usize = 32; -const VERIFIER_SIZE_BYTES: usize = CRYPTO_W_SIZE_BYTES + CRYPTO_PUBLIC_KEY_SIZE_BYTES; +const VERIFIER_SIZE_BYTES: usize = CRYPTO_GROUP_SIZE_BYTES + CRYPTO_PUBLIC_KEY_SIZE_BYTES; #[cfg(feature = "crypto_openssl")] fn crypto_spake2_new() -> Result, Error> { @@ -131,15 +131,20 @@ impl VerifierData { s } - pub fn new( - verifier: [u8; VERIFIER_SIZE_BYTES], - count: u32, - salt: [u8; MAX_SALT_SIZE_BYTES], - ) -> Self { + pub fn new(verifier: &[u8], count: u32, salt: &[u8]) -> Self { + let mut v = [0_u8; VERIFIER_SIZE_BYTES]; + let mut s = [0_u8; MAX_SALT_SIZE_BYTES]; + + let slice = &mut v[..verifier.len()]; + slice.copy_from_slice(verifier); + + let slice = &mut s[..salt.len()]; + slice.copy_from_slice(salt); + Self { - data: VerifierOption::Verifier(verifier), + data: VerifierOption::Verifier(v), count, - salt, + salt: s, } } } @@ -181,12 +186,12 @@ impl Spake2P { } pub fn start_verifier(&mut self, verifier: &VerifierData) -> Result<(), Error> { + self.crypto_spake2 = Some(crypto_spake2_new()?); match verifier.data { VerifierOption::Password(pw) => { // Derive w0 and L from the password let mut w0w1s: [u8; (2 * CRYPTO_W_SIZE_BYTES)] = [0; (2 * CRYPTO_W_SIZE_BYTES)]; Spake2P::get_w0w1s(pw, verifier.count, &verifier.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 { @@ -226,6 +231,7 @@ impl Spake2P { 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); From 1645754b48fdc7e751ae55d6036546b8ea9ec743 Mon Sep 17 00:00:00 2001 From: Kedar Sovani Date: Tue, 10 Jan 2023 18:40:52 +0530 Subject: [PATCH 08/30] admin-commissioning: Link PaseMgr with Admin Commissioning Adding the second commissioner now works successfully through iOS! Yay! --- matter/src/core.rs | 7 ++-- matter/src/data_model/core.rs | 11 +++++- matter/src/data_model/device_types.rs | 4 +- .../src/data_model/sdm/admin_commissioning.rs | 37 +++++++++---------- matter/src/secure_channel/pake.rs | 14 +++---- matter/tests/common/im_engine.rs | 11 +++++- 6 files changed, 51 insertions(+), 33 deletions(-) diff --git a/matter/src/core.rs b/matter/src/core.rs index 71eb6c3..39a903f 100644 --- a/matter/src/core.rs +++ b/matter/src/core.rs @@ -62,8 +62,10 @@ impl Matter { let fabric_mgr = Arc::new(FabricMgr::new()?); let acl_mgr = Arc::new(AclMgr::new()?); + let mut pase = PaseMgr::new(); let open_comm_window = fabric_mgr.is_empty(); - let data_model = DataModel::new(dev_det, dev_att, fabric_mgr.clone(), acl_mgr)?; + let data_model = + DataModel::new(dev_det, dev_att, fabric_mgr.clone(), acl_mgr, pase.clone())?; let mut matter = Box::new(Matter { transport_mgr: transport::mgr::Mgr::new()?, data_model, @@ -73,12 +75,11 @@ impl Matter { Box::new(InteractionModel::new(Box::new(matter.data_model.clone()))); matter.transport_mgr.register_protocol(interaction_model)?; - let mut pase = PaseMgr::new(); if open_comm_window { pase.enable_pase_session(dev_comm.verifier, dev_comm.discriminator)?; } - let secure_channel = Box::new(SecureChannel::new(pase.clone(), matter.fabric_mgr.clone())); + let secure_channel = Box::new(SecureChannel::new(pase, matter.fabric_mgr.clone())); matter.transport_mgr.register_protocol(secure_channel)?; Ok(matter) } diff --git a/matter/src/data_model/core.rs b/matter/src/data_model/core.rs index 4b0db23..19516fb 100644 --- a/matter/src/data_model/core.rs +++ b/matter/src/data_model/core.rs @@ -36,6 +36,7 @@ use crate::{ }, InteractionConsumer, Transaction, }, + secure_channel::pake::PaseMgr, tlv::{TLVArray, TLVWriter, TagType, ToTLV}, transport::session::{Session, SessionMode}, }; @@ -54,6 +55,7 @@ impl DataModel { dev_att: Box, fabric_mgr: Arc, acl_mgr: Arc, + pase_mgr: PaseMgr, ) -> Result { let dm = DataModel { node: Arc::new(RwLock::new(Node::new()?)), @@ -62,7 +64,14 @@ impl DataModel { { 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)?; + device_type_add_root_node( + &mut node, + dev_details, + dev_att, + fabric_mgr, + acl_mgr, + pase_mgr, + )?; } Ok(dm) } diff --git a/matter/src/data_model/device_types.rs b/matter/src/data_model/device_types.rs index 71a2cf4..4d68f9c 100644 --- a/matter/src/data_model/device_types.rs +++ b/matter/src/data_model/device_types.rs @@ -28,6 +28,7 @@ use super::system_model::access_control::AccessControlCluster; use crate::acl::AclMgr; use crate::error::*; use crate::fabric::FabricMgr; +use crate::secure_channel::pake::PaseMgr; use std::sync::Arc; use std::sync::RwLockWriteGuard; @@ -39,6 +40,7 @@ pub fn device_type_add_root_node( dev_att: Box, fabric_mgr: Arc, acl_mgr: Arc, + pase_mgr: PaseMgr, ) -> Result { // Add the root endpoint let endpoint = node.add_endpoint()?; @@ -52,7 +54,7 @@ pub fn device_type_add_root_node( let failsafe = general_commissioning.failsafe(); node.add_cluster(0, general_commissioning)?; node.add_cluster(0, NwCommCluster::new()?)?; - node.add_cluster(0, AdminCommCluster::new()?)?; + node.add_cluster(0, AdminCommCluster::new(pase_mgr)?)?; node.add_cluster( 0, NocCluster::new(dev_att, fabric_mgr, acl_mgr.clone(), failsafe)?, diff --git a/matter/src/data_model/sdm/admin_commissioning.rs b/matter/src/data_model/sdm/admin_commissioning.rs index 6e7d7b7..af8149b 100644 --- a/matter/src/data_model/sdm/admin_commissioning.rs +++ b/matter/src/data_model/sdm/admin_commissioning.rs @@ -18,6 +18,8 @@ use crate::cmd_enter; use crate::data_model::objects::*; use crate::interaction_model::core::IMStatusCode; +use crate::secure_channel::pake::PaseMgr; +use crate::secure_channel::spake2p::VerifierData; use crate::tlv::{FromTLV, Nullable, OctetStr, TLVElement}; use crate::{error::*, interaction_model::command::CommandReq}; use log::{error, info}; @@ -74,7 +76,7 @@ fn attr_admin_vid_new() -> Result { } pub struct AdminCommCluster { - window_status: WindowStatus, + pase_mgr: PaseMgr, base: Cluster, } @@ -89,23 +91,16 @@ impl ClusterType for AdminCommCluster { fn read_custom_attribute(&self, encoder: &mut dyn Encoder, attr: &AttrDetails) { match num::FromPrimitive::from_u16(attr.attr_id) { Some(Attributes::WindowStatus) => { - let status = self.window_status as u8; + let status = 1_u8; encoder.encode(EncodeValue::Value(&status)) } Some(Attributes::AdminVendorId) => { - let vid = if self.window_status == WindowStatus::WindowNotOpen { - Nullable::Null - } else { - Nullable::NotNull(1_u8) - }; + let vid = Nullable::NotNull(1_u8); + encoder.encode(EncodeValue::Value(&vid)) } Some(Attributes::AdminFabricIndex) => { - let vid = if self.window_status == WindowStatus::WindowNotOpen { - Nullable::Null - } else { - Nullable::NotNull(1_u8) - }; + let vid = Nullable::NotNull(1_u8); encoder.encode(EncodeValue::Value(&vid)) } _ => { @@ -129,9 +124,9 @@ impl ClusterType for AdminCommCluster { } impl AdminCommCluster { - pub fn new() -> Result, Error> { + pub fn new(pase_mgr: PaseMgr) -> Result, Error> { let mut c = Box::new(AdminCommCluster { - window_status: WindowStatus::WindowNotOpen, + pase_mgr, base: Cluster::new(ID)?, }); c.base.add_attribute(attr_window_status_new()?)?; @@ -145,9 +140,11 @@ impl AdminCommCluster { cmd_req: &mut CommandReq, ) -> Result<(), IMStatusCode> { cmd_enter!("Open Commissioning Window"); - let _req = + let req = OpenCommWindowReq::from_tlv(&cmd_req.data).map_err(|_| IMStatusCode::InvalidCommand)?; - self.window_status = WindowStatus::EnhancedWindowOpen; + let verifier = VerifierData::new(req.verifier.0, req.iterations, req.salt.0); + self.pase_mgr + .enable_pase_session(verifier, req.discriminator)?; Err(IMStatusCode::Sucess) } } @@ -156,8 +153,8 @@ impl AdminCommCluster { #[tlvargs(lifetime = "'a")] pub struct OpenCommWindowReq<'a> { _timeout: u16, - _verifier: OctetStr<'a>, - _discriminator: u16, - _iterations: u32, - _salt: OctetStr<'a>, + verifier: OctetStr<'a>, + discriminator: u16, + iterations: u32, + salt: OctetStr<'a>, } diff --git a/matter/src/secure_channel/pake.rs b/matter/src/secure_channel/pake.rs index 2bf7acd..b006d30 100644 --- a/matter/src/secure_channel/pake.rs +++ b/matter/src/secure_channel/pake.rs @@ -42,13 +42,13 @@ use crate::{ use log::{error, info}; use rand::prelude::*; -enum PaseSessionState { +enum PaseMgrState { Enabled(PAKE, SysMdnsService), Disabled, } pub struct PaseMgrInternal { - state: PaseSessionState, + state: PaseMgrState, } #[derive(Clone)] @@ -58,7 +58,7 @@ pub struct PaseMgr(Arc>); impl PaseMgr { pub fn new() -> Self { Self(Arc::new(Mutex::new(PaseMgrInternal { - state: PaseSessionState::Disabled, + state: PaseMgrState::Disabled, }))) } @@ -72,13 +72,13 @@ impl PaseMgr { let name = format!("{:016X}", name); let mdns = Mdns::get()? .publish_service(&name, mdns::ServiceMode::Commissionable(discriminator))?; - s.state = PaseSessionState::Enabled(PAKE::new(verifier), mdns); + s.state = PaseMgrState::Enabled(PAKE::new(verifier), mdns); Ok(()) } pub fn disable_pase_session(&mut self) { let mut s = self.0.lock().unwrap(); - s.state = PaseSessionState::Disabled; + s.state = PaseMgrState::Disabled; } /// If the PASE Session is enabled, execute the closure, @@ -88,7 +88,7 @@ impl PaseMgr { F: FnOnce(&mut PAKE, &mut ProtoCtx) -> Result<(), Error>, { let mut s = self.0.lock().unwrap(); - if let PaseSessionState::Enabled(pake, _) = &mut s.state { + if let PaseMgrState::Enabled(pake, _) = &mut s.state { f(pake, ctx) } else { error!("PASE Not enabled"); @@ -190,7 +190,7 @@ impl Default for PakeState { } pub struct PAKE { - verifier: VerifierData, + pub verifier: VerifierData, state: PakeState, } diff --git a/matter/tests/common/im_engine.rs b/matter/tests/common/im_engine.rs index ddd8b30..e9bedf0 100644 --- a/matter/tests/common/im_engine.rs +++ b/matter/tests/common/im_engine.rs @@ -29,6 +29,7 @@ use matter::{ error::Error, fabric::FabricMgr, interaction_model::{core::OpCode, InteractionModel}, + secure_channel::pake::PaseMgr, tlv::{TLVWriter, TagType, ToTLV}, transport::packet::Packet, transport::proto_demux::HandleProto, @@ -97,12 +98,20 @@ impl ImEngine { let dev_att = Box::new(DummyDevAtt {}); let fabric_mgr = Arc::new(FabricMgr::new().unwrap()); let acl_mgr = Arc::new(AclMgr::new_with(false).unwrap()); + let pase_mgr = PaseMgr::new(); 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 dm = DataModel::new( + dev_det, + dev_att, + fabric_mgr.clone(), + acl_mgr.clone(), + pase_mgr, + ) + .unwrap(); { let mut d = dm.node.write().unwrap(); From 317d657fbaa8b2fe2e4feb8c95bdb98bc28df1f7 Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 10 Jan 2023 21:01:02 +0100 Subject: [PATCH 09/30] Compute QR Code --- matter/Cargo.toml | 10 + matter/src/codec/base38.rs | 46 ++++ matter/src/codec/mod.rs | 1 + matter/src/core.rs | 3 + .../data_model/cluster_basic_information.rs | 1 + matter/src/lib.rs | 2 + matter/src/pairing.rs | 225 ++++++++++++++++++ 7 files changed, 288 insertions(+) create mode 100644 matter/src/codec/base38.rs create mode 100644 matter/src/codec/mod.rs create mode 100644 matter/src/pairing.rs diff --git a/matter/Cargo.toml b/matter/Cargo.toml index 32974f8..5d33cf5 100644 --- a/matter/Cargo.toml +++ b/matter/Cargo.toml @@ -47,6 +47,16 @@ safemem = "0.3.3" chrono = { version = "0.4.19", default-features = false, features = ["clock", "std"] } async-channel = "1.6" +# to compute the check digit +verhoeff = "1" + +# needed to compute base38 packed binary data Structure +base-encode = "0.3" +packed_struct = "0.10" + +# print QR code +qrcode = { version = "0.12", default-features = false } + [target.'cfg(target_os = "macos")'.dependencies] astro-dnssd = "0.3" diff --git a/matter/src/codec/base38.rs b/matter/src/codec/base38.rs new file mode 100644 index 0000000..237e9b5 --- /dev/null +++ b/matter/src/codec/base38.rs @@ -0,0 +1,46 @@ +const BASE38_CHARS: &str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-."; + +fn encode_base38(mut value: u32, char_count: u8) -> String { + let mut result = String::new(); + for _ in 0..char_count { + let mut chars = BASE38_CHARS.chars(); + let remainder = value % 38; + result.push(chars.nth(remainder as usize).unwrap()); + value = (value - remainder) / 38; + } + result +} + +pub fn encode(bytes: &[u8]) -> String { + let length = bytes.len(); + let mut offset = 0; + let mut result = String::new(); + + while offset < length { + let remaining = length - offset; + match remaining.cmp(&2) { + std::cmp::Ordering::Greater => { + result.push_str(&encode_base38( + ((bytes[offset + 2] as u32) << 16) + | ((bytes[offset + 1] as u32) << 8) + | (bytes[offset] as u32), + 5, + )); + offset += 3; + } + std::cmp::Ordering::Equal => { + result.push_str(&encode_base38( + ((bytes[offset + 1] as u32) << 8) | (bytes[offset] as u32), + 4, + )); + break; + } + std::cmp::Ordering::Less => { + result.push_str(&encode_base38(bytes[offset] as u32, 2)); + break; + } + } + } + + result +} diff --git a/matter/src/codec/mod.rs b/matter/src/codec/mod.rs new file mode 100644 index 0000000..fdada5c --- /dev/null +++ b/matter/src/codec/mod.rs @@ -0,0 +1 @@ +pub mod base38; diff --git a/matter/src/core.rs b/matter/src/core.rs index 30dcf9d..1ba31ac 100644 --- a/matter/src/core.rs +++ b/matter/src/core.rs @@ -25,6 +25,7 @@ use crate::{ fabric::FabricMgr, interaction_model::InteractionModel, mdns::Mdns, + pairing::compute_and_print_pairing_code, secure_channel::core::SecureChannel, transport, }; @@ -69,6 +70,8 @@ impl Matter { &dev_det.device_name, ); + compute_and_print_pairing_code(dev_det, dev_comm); + let fabric_mgr = Arc::new(FabricMgr::new()?); let acl_mgr = Arc::new(AclMgr::new()?); let open_comm_window = fabric_mgr.is_empty(); diff --git a/matter/src/data_model/cluster_basic_information.rs b/matter/src/data_model/cluster_basic_information.rs index 10a2b34..149096a 100644 --- a/matter/src/data_model/cluster_basic_information.rs +++ b/matter/src/data_model/cluster_basic_information.rs @@ -26,6 +26,7 @@ enum Attributes { SwVer = 9, } +#[derive(Default)] pub struct BasicInfoConfig { pub vid: u16, pub pid: u16, diff --git a/matter/src/lib.rs b/matter/src/lib.rs index 2d2bee2..d96d9d7 100644 --- a/matter/src/lib.rs +++ b/matter/src/lib.rs @@ -70,6 +70,7 @@ pub mod acl; pub mod cert; +pub mod codec; pub mod core; pub mod crypto; pub mod data_model; @@ -78,6 +79,7 @@ pub mod fabric; pub mod group_keys; pub mod interaction_model; pub mod mdns; +pub mod pairing; pub mod secure_channel; pub mod sys; pub mod tlv; diff --git a/matter/src/pairing.rs b/matter/src/pairing.rs new file mode 100644 index 0000000..fd7d906 --- /dev/null +++ b/matter/src/pairing.rs @@ -0,0 +1,225 @@ +use log::info; +use packed_struct::prelude::*; +use qrcode::{render::unicode, QrCode, Version}; +use verhoeff::Verhoeff; + +use crate::{ + codec::base38, data_model::cluster_basic_information::BasicInfoConfig, CommissioningData, +}; + +#[repr(u8)] +#[derive(Clone, Copy)] +pub enum CommissionningFlowType { + Standard = 0, + UserIntent = 1, + Custom = 2, +} + +pub struct DiscoveryCapabilitiesSchema { + on_ip_network: bool, + ble: bool, + soft_access_point: bool, +} + +impl DiscoveryCapabilitiesSchema { + fn as_bits(&self) -> u8 { + let mut bits = 0; + if self.soft_access_point { + bits |= 1 << 0; + } + if self.ble { + bits |= 1 << 1; + } + if self.on_ip_network { + bits |= 1 << 2; + } + bits + } +} + +pub struct QrCodeData<'data> { + flow_type: CommissionningFlowType, + discovery_capabilities: DiscoveryCapabilitiesSchema, + dev_det: &'data BasicInfoConfig, + comm_data: &'data CommissioningData, +} + +impl<'data> QrCodeData<'data> { + pub fn new( + dev_det: &'data BasicInfoConfig, + comm_data: &'data CommissioningData, + discovery_capabilities: DiscoveryCapabilitiesSchema, + ) -> Self { + QrCodeData { + flow_type: CommissionningFlowType::Standard, + discovery_capabilities, + dev_det, + comm_data, + } + } +} + +#[derive(PackedStruct, Debug)] +#[packed_struct(bit_numbering = "msb0", size_bytes = "11", endian = "msb")] +pub struct PackedQrData { + #[packed_field(bits = "0..3")] + version: Integer>, + #[packed_field(bits = "3..19")] + vid: Integer>, + #[packed_field(bits = "19..35")] + pid: Integer>, + #[packed_field(bits = "35..37")] + commissionning_flow_type: Integer>, + #[packed_field(bits = "37")] + soft_access_point: bool, + #[packed_field(bits = "38")] + ble: bool, + #[packed_field(bits = "39")] + on_ip_network: bool, + #[packed_field(bits = "40..45")] + _reserved: Integer>, + #[packed_field(bits = "45..57")] + discriminator: Integer>, + #[packed_field(bits = "57..84")] + passcode: Integer>, + #[packed_field(bits = "84..88")] + _padding: Integer>, +} + +pub fn compute_and_print_pairing_code(dev_det: &BasicInfoConfig, comm_data: &CommissioningData) { + let pairing_code = compute_pairing_code(comm_data); + pretty_print_pairing_code(&pairing_code); + print_qr_code(&pairing_code, dev_det, comm_data); +} + +fn compute_pairing_code(comm_data: &CommissioningData) -> String { + // 0: no Vendor ID and Product ID present in Manual Pairing Code + const VID_PID_PRESENT: u8 = 0; + + let CommissioningData { + discriminator, + passwd, + .. + } = comm_data; + + let mut digits = String::new(); + digits.push_str(&((VID_PID_PRESENT << 2) | (discriminator >> 10) as u8).to_string()); + digits.push_str(&format!( + "{:0>5}", + ((discriminator & 0x300) << 6) | (passwd & 0x3FFF) as u16 + )); + digits.push_str(&format!("{:0>4}", passwd >> 14)); + + let check_digit = digits.calculate_verhoeff_check_digit(); + digits.push_str(&check_digit.to_string()); + + digits +} + +pub fn pretty_print_pairing_code(pairing_code: &str) { + assert!(pairing_code.len() == 11); + let mut pretty = String::new(); + pretty.push_str(&pairing_code[..4]); + pretty.push('-'); + pretty.push_str(&pairing_code[4..8]); + pretty.push('-'); + pretty.push_str(&pairing_code[8..]); + info!("Pairing Code: {}", pretty); +} + +fn print_qr_code(pairing_code: &str, dev_det: &BasicInfoConfig, comm_data: &CommissioningData) { + let code = QrCode::with_version(pairing_code, Version::Normal(2), qrcode::EcLevel::M).unwrap(); + let image = code + .render::() + .dark_color(unicode::Dense1x2::Light) + .light_color(unicode::Dense1x2::Dark) + .build(); + println!("{}", image); +} + +fn base38_encode_qr(qr_data: &QrCodeData) -> String { + let QrCodeData { + flow_type, + discovery_capabilities, + dev_det, + comm_data, + } = &qr_data; + + let BasicInfoConfig { vid, pid, .. } = dev_det; + const VERSION: u8 = 0; // 3-bit value specifying the QR code payload version. SHALL be 000. + + let packed_qr_data = PackedQrData { + version: VERSION.into(), + vid: (*vid).reverse_bits().into(), + pid: (*pid).reverse_bits().into(), + commissionning_flow_type: ((*flow_type) as u8).into(), + soft_access_point: discovery_capabilities.soft_access_point, + ble: discovery_capabilities.ble, + on_ip_network: discovery_capabilities.on_ip_network, + _reserved: 0u8.into(), + discriminator: comm_data.discriminator.reverse_bits().into(), + passcode: comm_data.passwd.reverse_bits().into(), + _padding: 0u8.into(), + }; + + println!("{:?}", packed_qr_data); + println!("{}", packed_qr_data); + + let data = packed_qr_data.pack().unwrap(); + let data = data + .into_iter() + .map(|b| b.reverse_bits()) + .collect::>(); + + let base38 = base38::encode(&data); + format!("MT:{}", base38) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn can_compute_pairing_code() { + let comm_data = CommissioningData { + passwd: 123456, + discriminator: 250, + ..Default::default() + }; + let pairing_code = compute_pairing_code(&comm_data); + assert_eq!(pairing_code, "00876800071"); + + let comm_data = CommissioningData { + passwd: 34567890, + discriminator: 2976, + ..Default::default() + }; + let pairing_code = compute_pairing_code(&comm_data); + assert_eq!(pairing_code, "26318621095"); + } + + #[test] + fn can_base38_encode() { + const QR_CODE: &str = "MT:YNJV7VSC00CMVH7SR00"; + + let comm_data = CommissioningData { + passwd: 34567890, + discriminator: 2976, + ..Default::default() + }; + let dev_det = BasicInfoConfig { + vid: 9050, + pid: 65279, + ..Default::default() + }; + let disc_cap = DiscoveryCapabilitiesSchema { + on_ip_network: false, + ble: true, + soft_access_point: false, + }; + + let qr_code_data = QrCodeData::new(&dev_det, &comm_data, disc_cap); + let data_str = base38_encode_qr(&qr_code_data); + assert_eq!(data_str, QR_CODE) + } +} From b7b19d07788f131015f5e8854212166a7148d18a Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 10 Jan 2023 21:25:05 +0100 Subject: [PATCH 10/30] Test Base38 encoding --- matter/src/codec/base38.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/matter/src/codec/base38.rs b/matter/src/codec/base38.rs index 237e9b5..d6d0520 100644 --- a/matter/src/codec/base38.rs +++ b/matter/src/codec/base38.rs @@ -44,3 +44,17 @@ pub fn encode(bytes: &[u8]) -> String { result } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn can_base38_encode() { + const ENCODED: &str = "-MOA57ZU02IT2L2BJ00"; + const DECODED: [u8; 11] = [ + 0x88, 0xff, 0xa7, 0x91, 0x50, 0x40, 0x00, 0x47, 0x51, 0xdd, 0x02, + ]; + assert_eq!(encode(&DECODED), ENCODED); + } +} From 76997c1a3ca6d915e1b0c8dec536348508d3c77f Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 11 Jan 2023 09:00:18 +0100 Subject: [PATCH 11/30] Compute QR code --- matter/Cargo.toml | 4 - matter/src/error.rs | 2 + matter/src/pairing.rs | 228 ++++++++++++++++++++++++++++++------------ 3 files changed, 167 insertions(+), 67 deletions(-) diff --git a/matter/Cargo.toml b/matter/Cargo.toml index 5d33cf5..fb6c7f7 100644 --- a/matter/Cargo.toml +++ b/matter/Cargo.toml @@ -50,10 +50,6 @@ async-channel = "1.6" # to compute the check digit verhoeff = "1" -# needed to compute base38 packed binary data Structure -base-encode = "0.3" -packed_struct = "0.10" - # print QR code qrcode = { version = "0.12", default-features = false } diff --git a/matter/src/error.rs b/matter/src/error.rs index aa326d0..68e7f21 100644 --- a/matter/src/error.rs +++ b/matter/src/error.rs @@ -24,6 +24,7 @@ use log::error; pub enum Error { AttributeNotFound, AttributeIsCustom, + BufferTooSmall, ClusterNotFound, CommandNotFound, EndpointNotFound, @@ -58,6 +59,7 @@ pub enum Error { InvalidSignature, InvalidState, InvalidTime, + InvalidArgument, RwLock, TLVNotFound, TLVTypeMismatch, diff --git a/matter/src/pairing.rs b/matter/src/pairing.rs index fd7d906..ec5d891 100644 --- a/matter/src/pairing.rs +++ b/matter/src/pairing.rs @@ -1,10 +1,10 @@ use log::info; -use packed_struct::prelude::*; use qrcode::{render::unicode, QrCode, Version}; use verhoeff::Verhoeff; use crate::{ - codec::base38, data_model::cluster_basic_information::BasicInfoConfig, CommissioningData, + codec::base38, data_model::cluster_basic_information::BasicInfoConfig, error::Error, + CommissioningData, }; #[repr(u8)] @@ -59,37 +59,19 @@ impl<'data> QrCodeData<'data> { } } -#[derive(PackedStruct, Debug)] -#[packed_struct(bit_numbering = "msb0", size_bytes = "11", endian = "msb")] -pub struct PackedQrData { - #[packed_field(bits = "0..3")] - version: Integer>, - #[packed_field(bits = "3..19")] - vid: Integer>, - #[packed_field(bits = "19..35")] - pid: Integer>, - #[packed_field(bits = "35..37")] - commissionning_flow_type: Integer>, - #[packed_field(bits = "37")] - soft_access_point: bool, - #[packed_field(bits = "38")] - ble: bool, - #[packed_field(bits = "39")] - on_ip_network: bool, - #[packed_field(bits = "40..45")] - _reserved: Integer>, - #[packed_field(bits = "45..57")] - discriminator: Integer>, - #[packed_field(bits = "57..84")] - passcode: Integer>, - #[packed_field(bits = "84..88")] - _padding: Integer>, -} - pub fn compute_and_print_pairing_code(dev_det: &BasicInfoConfig, comm_data: &CommissioningData) { let pairing_code = compute_pairing_code(comm_data); pretty_print_pairing_code(&pairing_code); - print_qr_code(&pairing_code, dev_det, comm_data); + + let disc_cap = DiscoveryCapabilitiesSchema { + on_ip_network: true, + ble: false, + soft_access_point: false, + }; + + let qr_code_data = QrCodeData::new(dev_det, comm_data, disc_cap); + let data_str = payload_base38_representation(&qr_code_data).expect("Failed to encode"); + print_qr_code(&data_str); } fn compute_pairing_code(comm_data: &CommissioningData) -> String { @@ -127,8 +109,8 @@ pub fn pretty_print_pairing_code(pairing_code: &str) { info!("Pairing Code: {}", pretty); } -fn print_qr_code(pairing_code: &str, dev_det: &BasicInfoConfig, comm_data: &CommissioningData) { - let code = QrCode::with_version(pairing_code, Version::Normal(2), qrcode::EcLevel::M).unwrap(); +fn print_qr_code(qr_data: &str) { + let code = QrCode::with_version(qr_data, Version::Normal(2), qrcode::EcLevel::M).unwrap(); let image = code .render::() .dark_color(unicode::Dense1x2::Light) @@ -137,42 +119,162 @@ fn print_qr_code(pairing_code: &str, dev_det: &BasicInfoConfig, comm_data: &Comm println!("{}", image); } -fn base38_encode_qr(qr_data: &QrCodeData) -> String { - let QrCodeData { - flow_type, - discovery_capabilities, - dev_det, - comm_data, - } = &qr_data; +fn populate_bits( + bits: &mut [u8], + offset: &mut usize, + mut input: u64, + number_of_bits: usize, + total_payload_data_size_in_bits: usize, +) -> Result<(), Error> { + if *offset + number_of_bits > total_payload_data_size_in_bits { + return Err(Error::InvalidArgument); + } - let BasicInfoConfig { vid, pid, .. } = dev_det; - const VERSION: u8 = 0; // 3-bit value specifying the QR code payload version. SHALL be 000. + if input >= 1u64 << number_of_bits { + return Err(Error::InvalidArgument); + } - let packed_qr_data = PackedQrData { - version: VERSION.into(), - vid: (*vid).reverse_bits().into(), - pid: (*pid).reverse_bits().into(), - commissionning_flow_type: ((*flow_type) as u8).into(), - soft_access_point: discovery_capabilities.soft_access_point, - ble: discovery_capabilities.ble, - on_ip_network: discovery_capabilities.on_ip_network, - _reserved: 0u8.into(), - discriminator: comm_data.discriminator.reverse_bits().into(), - passcode: comm_data.passwd.reverse_bits().into(), - _padding: 0u8.into(), + let mut index = *offset; + *offset += number_of_bits; + + while input != 0 { + if input & 1 == 1 { + let mask = (1 << (index % 8)) as u8; + bits[index / 8] |= mask; + } + index += 1; + input >>= 1; + } + + Ok(()) +} +const LONG_BITS: usize = 12; +const VERSION_FIELD_LENGTH_IN_BITS: usize = 3; +const VENDOR_IDFIELD_LENGTH_IN_BITS: usize = 16; +const PRODUCT_IDFIELD_LENGTH_IN_BITS: usize = 16; +const COMMISSIONING_FLOW_FIELD_LENGTH_IN_BITS: usize = 2; +const RENDEZVOUS_INFO_FIELD_LENGTH_IN_BITS: usize = 8; +const PAYLOAD_DISCRIMINATOR_FIELD_LENGTH_IN_BITS: usize = LONG_BITS; +const SETUP_PINCODE_FIELD_LENGTH_IN_BITS: usize = 27; +const PADDING_FIELD_LENGTH_IN_BITS: usize = 4; +const RAW_VENDOR_TAG_LENGTH_IN_BITS: usize = 7; +const TOTAL_PAYLOAD_DATA_SIZE_IN_BITS: usize = VERSION_FIELD_LENGTH_IN_BITS + + VENDOR_IDFIELD_LENGTH_IN_BITS + + PRODUCT_IDFIELD_LENGTH_IN_BITS + + COMMISSIONING_FLOW_FIELD_LENGTH_IN_BITS + + RENDEZVOUS_INFO_FIELD_LENGTH_IN_BITS + + PAYLOAD_DISCRIMINATOR_FIELD_LENGTH_IN_BITS + + SETUP_PINCODE_FIELD_LENGTH_IN_BITS + + PADDING_FIELD_LENGTH_IN_BITS; +const TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES: usize = TOTAL_PAYLOAD_DATA_SIZE_IN_BITS / 8; + +struct TlvData { + data_length_in_bytes: u32, +} + +fn payload_base38_representation_with_tlv( + payload: &QrCodeData, + bits: &mut [u8; TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES], + tlv_data: Option, +) -> Result { + generate_bit_set(payload, bits, tlv_data)?; + let base38_encoded = base38::encode(&*bits); + Ok(format!("MT:{}", base38_encoded)) +} + +fn payload_base38_representation(payload: &QrCodeData) -> Result { + let mut bits: [u8; TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES] = [0; TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES]; + + // VerifyOrReturnError(mPayload.isValidQRCodePayload(), CHIP_ERROR_INVALID_ARGUMENT); + + payload_base38_representation_with_tlv(payload, &mut bits, None) +} + +fn generate_bit_set( + payload: &QrCodeData, + bits: &mut [u8; TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES], + tlv_data: Option, +) -> Result<(), Error> { + let mut offset: usize = 0; + let total_payload_size_in_bits = if let Some(tlv_data) = &tlv_data { + TOTAL_PAYLOAD_DATA_SIZE_IN_BITS + (tlv_data.data_length_in_bytes * 8) as usize + } else { + TOTAL_PAYLOAD_DATA_SIZE_IN_BITS }; - println!("{:?}", packed_qr_data); - println!("{}", packed_qr_data); + if bits.len() * 8 < total_payload_size_in_bits { + return Err(Error::BufferTooSmall); + }; - let data = packed_qr_data.pack().unwrap(); - let data = data - .into_iter() - .map(|b| b.reverse_bits()) - .collect::>(); + const VERSION: u64 = 0; + populate_bits( + bits, + &mut offset, + VERSION, + VERSION_FIELD_LENGTH_IN_BITS, + total_payload_size_in_bits, + )?; - let base38 = base38::encode(&data); - format!("MT:{}", base38) + populate_bits( + bits, + &mut offset, + payload.dev_det.vid as u64, + VENDOR_IDFIELD_LENGTH_IN_BITS, + total_payload_size_in_bits, + )?; + + populate_bits( + bits, + &mut offset, + payload.dev_det.pid as u64, + PRODUCT_IDFIELD_LENGTH_IN_BITS, + total_payload_size_in_bits, + )?; + + populate_bits( + bits, + &mut offset, + payload.flow_type as u64, + COMMISSIONING_FLOW_FIELD_LENGTH_IN_BITS, + total_payload_size_in_bits, + )?; + + populate_bits( + bits, + &mut offset, + payload.discovery_capabilities.as_bits() as u64, + RENDEZVOUS_INFO_FIELD_LENGTH_IN_BITS, + total_payload_size_in_bits, + )?; + + populate_bits( + bits, + &mut offset, + payload.comm_data.discriminator as u64, + PAYLOAD_DISCRIMINATOR_FIELD_LENGTH_IN_BITS, + total_payload_size_in_bits, + )?; + + populate_bits( + bits, + &mut offset, + payload.comm_data.passwd as u64, + SETUP_PINCODE_FIELD_LENGTH_IN_BITS, + total_payload_size_in_bits, + )?; + + populate_bits( + bits, + &mut offset, + 0, + PADDING_FIELD_LENGTH_IN_BITS, + total_payload_size_in_bits, + )?; + + // todo: add tlv data + // ReturnErrorOnFailure(populateTLVBits(bits.data(), offset, tlvDataStart, tlvDataLengthInBytes, totalPayloadSizeInBits)); + + Ok(()) } #[cfg(test)] @@ -219,7 +321,7 @@ mod tests { }; let qr_code_data = QrCodeData::new(&dev_det, &comm_data, disc_cap); - let data_str = base38_encode_qr(&qr_code_data); + let data_str = payload_base38_representation(&qr_code_data).expect("Failed to encode"); assert_eq!(data_str, QR_CODE) } } From f2dad9132bf367626b8f18c10fbc3e848944c5d5 Mon Sep 17 00:00:00 2001 From: Kedar Sovani Date: Wed, 11 Jan 2023 13:34:59 +0530 Subject: [PATCH 12/30] noc: Support UpdateFabricLabel command --- matter/src/data_model/sdm/noc.rs | 69 ++++++++++++++++++++++++-------- matter/src/fabric.rs | 52 +++++++++++++++++++++--- matter/src/tlv/traits.rs | 10 +++++ 3 files changed, 108 insertions(+), 23 deletions(-) diff --git a/matter/src/data_model/sdm/noc.rs b/matter/src/data_model/sdm/noc.rs index 68432dc..ebd3ac2 100644 --- a/matter/src/data_model/sdm/noc.rs +++ b/matter/src/data_model/sdm/noc.rs @@ -27,7 +27,7 @@ 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::tlv::{FromTLV, OctetStr, TLVElement, TLVWriter, TagType, ToTLV, UtfStr}; use crate::transport::session::SessionMode; use crate::utils::writebuf::WriteBuf; use crate::{cmd_enter, error::*}; @@ -75,6 +75,8 @@ pub enum Commands { CSRResp = 0x05, AddNOC = 0x06, NOCResp = 0x08, + UpdateFabricLabel = 0x09, + RemoveFabric = 0x0a, AddTrustedRootCert = 0x0b, } @@ -183,19 +185,55 @@ impl NocCluster { if self.failsafe.record_add_noc(fab_idx).is_err() { error!("Failed to record NoC in the FailSafe, what to do?"); } + NocCluster::create_nocresponse(cmd_req.resp, NocStatus::Ok, fab_idx, "".to_owned()); + cmd_req.trans.complete(); + Ok(()) + } + fn create_nocresponse( + tw: &mut TLVWriter, + status_code: NocStatus, + fab_idx: u8, + debug_txt: String, + ) { let cmd_data = NocResp { - status_code: NocStatus::Ok as u8, + status_code: status_code as u8, fab_idx, - debug_txt: "".to_owned(), + debug_txt, }; - let resp = ib::InvResp::cmd_new( + let invoke_resp = ib::InvResp::cmd_new( 0, ID, Commands::NOCResp as u16, EncodeValue::Value(&cmd_data), ); - let _ = resp.to_tlv(cmd_req.resp, TagType::Anonymous); + let _ = invoke_resp.to_tlv(tw, TagType::Anonymous); + } + + fn handle_command_updatefablabel( + &mut self, + cmd_req: &mut CommandReq, + ) -> Result<(), IMStatusCode> { + cmd_enter!("Update Fabric Label"); + let req = UpdateFabricLabelReq::from_tlv(&cmd_req.data) + .map_err(|_| IMStatusCode::InvalidDataType)?; + let label = req + .label + .to_string() + .map_err(|_| IMStatusCode::InvalidDataType)?; + + let (result, fab_idx) = + if let SessionMode::Case(fab_idx) = cmd_req.trans.session.get_session_mode() { + if self.fabric_mgr.set_label(fab_idx, label).is_err() { + (NocStatus::LabelConflict, fab_idx) + } else { + (NocStatus::Ok, fab_idx) + } + } else { + // Update Fabric Label not allowed + (NocStatus::InvalidFabricIndex, 0) + }; + NocCluster::create_nocresponse(cmd_req.resp, result, fab_idx, "".to_string()); cmd_req.trans.complete(); Ok(()) } @@ -203,18 +241,8 @@ impl NocCluster { 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); + //TODO: Fab-idx 0? + NocCluster::create_nocresponse(cmd_req.resp, e, 0, "".to_owned()); cmd_req.trans.complete(); } Ok(()) @@ -401,6 +429,7 @@ impl ClusterType for NocCluster { Commands::AddTrustedRootCert => self.handle_command_addtrustedrootcert(cmd_req), Commands::AttReq => self.handle_command_attrequest(cmd_req), Commands::CertChainReq => self.handle_command_certchainrequest(cmd_req), + Commands::UpdateFabricLabel => self.handle_command_updatefablabel(cmd_req), _ => Err(IMStatusCode::UnsupportedCommand), } } @@ -515,6 +544,12 @@ struct CommonReq<'a> { str: OctetStr<'a>, } +#[derive(FromTLV)] +#[tlvargs(lifetime = "'a")] +struct UpdateFabricLabelReq<'a> { + label: UtfStr<'a>, +} + #[derive(FromTLV)] struct CertChainReq { cert_type: u8, diff --git a/matter/src/fabric.rs b/matter/src/fabric.rs index c39bb25..9336da5 100644 --- a/matter/src/fabric.rs +++ b/matter/src/fabric.rs @@ -18,7 +18,7 @@ use std::sync::{Arc, Mutex, MutexGuard, RwLock}; use byteorder::{BigEndian, ByteOrder, LittleEndian}; -use log::info; +use log::{error, info}; use owning_ref::RwLockReadGuardRef; use crate::{ @@ -45,6 +45,7 @@ const ST_RCA: &str = "rca"; const ST_ICA: &str = "ica"; const ST_NOC: &str = "noc"; const ST_IPK: &str = "ipk"; +const ST_LBL: &str = "label"; const ST_PBKEY: &str = "pubkey"; const ST_PRKEY: &str = "privkey"; @@ -58,6 +59,7 @@ pub struct Fabric { pub icac: Option, pub noc: Cert, pub ipk: KeySet, + label: String, compressed_id: [u8; COMPRESSED_FABRIC_ID_LEN], mdns_service: Option, } @@ -97,6 +99,7 @@ impl Fabric { noc, ipk: KeySet::default(), compressed_id: [0; COMPRESSED_FABRIC_ID_LEN], + label: "".into(), mdns_service: None, }; Fabric::get_compressed_id(f.root_ca.get_pubkey(), fabric_id, &mut f.compressed_id)?; @@ -129,6 +132,7 @@ impl Fabric { icac: Some(Cert::default()), noc: Cert::default(), ipk: KeySet::default(), + label: "".into(), compressed_id: [0; COMPRESSED_FABRIC_ID_LEN], mdns_service: None, }) @@ -186,7 +190,7 @@ impl Fabric { vendor_id: self.vendor_id, fabric_id: self.fabric_id, node_id: self.node_id, - label: UtfStr::new(b""), + label: UtfStr(self.label.as_bytes()), fab_idx: Some(fab_idx), } } @@ -206,6 +210,7 @@ impl Fabric { 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())?; + psm.set_kv_slice(fb_key!(index, ST_LBL), self.label.as_bytes())?; let mut key = [0_u8; crypto::EC_POINT_LEN_BYTES]; let len = self.key_pair.get_public_key(&mut key)?; @@ -217,7 +222,7 @@ impl Fabric { let key = &key[..len]; psm.set_kv_slice(fb_key!(index, ST_PRKEY), key)?; - psm.set_kv_u64(ST_VID, self.vendor_id.into())?; + psm.set_kv_u64(fb_key!(index, ST_VID), self.vendor_id.into())?; Ok(()) } @@ -241,6 +246,13 @@ impl Fabric { let mut ipk = Vec::new(); psm.get_kv_slice(fb_key!(index, ST_IPK), &mut ipk)?; + let mut label = Vec::new(); + psm.get_kv_slice(fb_key!(index, ST_LBL), &mut label)?; + let label = String::from_utf8(label).map_err(|_| { + error!("Couldn't read label"); + Error::Invalid + })?; + let mut pub_key = Vec::new(); psm.get_kv_slice(fb_key!(index, ST_PBKEY), &mut pub_key)?; let mut priv_key = Vec::new(); @@ -248,16 +260,20 @@ impl Fabric { let keypair = KeyPair::new_from_components(pub_key.as_slice(), priv_key.as_slice())?; let mut vendor_id = 0; - psm.get_kv_u64(ST_VID, &mut vendor_id)?; + psm.get_kv_u64(fb_key!(index, ST_VID), &mut vendor_id)?; - Fabric::new( + let f = Fabric::new( keypair, root_ca, icac, noc, ipk.as_slice(), vendor_id as u16, - ) + ); + f.map(|mut f| { + f.label = label; + f + }) } } @@ -361,4 +377,28 @@ impl FabricMgr { } Ok(()) } + + pub fn set_label(&self, index: u8, label: String) -> Result<(), Error> { + let index = index as usize; + let mut mgr = self.inner.write()?; + if label != "" { + for i in 1..MAX_SUPPORTED_FABRICS { + if let Some(fabric) = &mgr.fabrics[i] { + if fabric.label == label { + return Err(Error::Invalid); + } + } + } + } + if let Some(fabric) = &mut mgr.fabrics[index] { + let old = fabric.label.clone(); + fabric.label = label; + let psm = self.psm.lock().unwrap(); + if fabric.store(index, &psm).is_err() { + fabric.label = old; + return Err(Error::StdIoError); + } + } + Ok(()) + } } diff --git a/matter/src/tlv/traits.rs b/matter/src/tlv/traits.rs index ab03a63..88be95a 100644 --- a/matter/src/tlv/traits.rs +++ b/matter/src/tlv/traits.rs @@ -119,6 +119,10 @@ impl<'a> UtfStr<'a> { pub fn new(str: &'a [u8]) -> Self { Self(str) } + + pub fn to_string(self) -> Result { + String::from_utf8(self.0.to_vec()).map_err(|_| Error::Invalid) + } } impl<'a> ToTLV for UtfStr<'a> { @@ -127,6 +131,12 @@ impl<'a> ToTLV for UtfStr<'a> { } } +impl<'a> FromTLV<'a> for UtfStr<'a> { + fn from_tlv(t: &TLVElement<'a>) -> Result, Error> { + t.slice().map(UtfStr) + } +} + /// Implements OctetString from the spec #[derive(Debug, Copy, Clone, PartialEq)] pub struct OctetStr<'a>(pub &'a [u8]); From 82ae21309c846a1bc38ca799a290b250d7e6fb7b Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 11 Jan 2023 09:54:12 +0100 Subject: [PATCH 13/30] Clean-up --- matter/src/codec/base38.rs | 24 ++++++----- matter/src/core.rs | 4 +- matter/src/pairing.rs | 88 ++++++++++++++++++++------------------ 3 files changed, 62 insertions(+), 54 deletions(-) diff --git a/matter/src/codec/base38.rs b/matter/src/codec/base38.rs index d6d0520..bd6113f 100644 --- a/matter/src/codec/base38.rs +++ b/matter/src/codec/base38.rs @@ -1,16 +1,7 @@ +//! Base38 encoding functions. const BASE38_CHARS: &str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-."; -fn encode_base38(mut value: u32, char_count: u8) -> String { - let mut result = String::new(); - for _ in 0..char_count { - let mut chars = BASE38_CHARS.chars(); - let remainder = value % 38; - result.push(chars.nth(remainder as usize).unwrap()); - value = (value - remainder) / 38; - } - result -} - +/// Encodes a byte array into a base38 string. pub fn encode(bytes: &[u8]) -> String { let length = bytes.len(); let mut offset = 0; @@ -45,6 +36,17 @@ pub fn encode(bytes: &[u8]) -> String { result } +fn encode_base38(mut value: u32, char_count: u8) -> String { + let mut result = String::new(); + for _ in 0..char_count { + let mut chars = BASE38_CHARS.chars(); + let remainder = value % 38; + result.push(chars.nth(remainder as usize).unwrap()); + value = (value - remainder) / 38; + } + result +} + #[cfg(test)] mod tests { use super::*; diff --git a/matter/src/core.rs b/matter/src/core.rs index 1ba31ac..97143d1 100644 --- a/matter/src/core.rs +++ b/matter/src/core.rs @@ -25,7 +25,7 @@ use crate::{ fabric::FabricMgr, interaction_model::InteractionModel, mdns::Mdns, - pairing::compute_and_print_pairing_code, + pairing::print_pairing_code_and_qr, secure_channel::core::SecureChannel, transport, }; @@ -70,7 +70,7 @@ impl Matter { &dev_det.device_name, ); - compute_and_print_pairing_code(dev_det, dev_comm); + print_pairing_code_and_qr(dev_det, dev_comm); let fabric_mgr = Arc::new(FabricMgr::new()?); let acl_mgr = Arc::new(AclMgr::new()?); diff --git a/matter/src/pairing.rs b/matter/src/pairing.rs index ec5d891..a367ba4 100644 --- a/matter/src/pairing.rs +++ b/matter/src/pairing.rs @@ -1,3 +1,5 @@ +//! This module contains the logic for generating the pairing code and the QR code for easy pairing. + use log::info; use qrcode::{render::unicode, QrCode, Version}; use verhoeff::Verhoeff; @@ -7,6 +9,25 @@ use crate::{ CommissioningData, }; +const LONG_BITS: usize = 12; +const VERSION_FIELD_LENGTH_IN_BITS: usize = 3; +const VENDOR_IDFIELD_LENGTH_IN_BITS: usize = 16; +const PRODUCT_IDFIELD_LENGTH_IN_BITS: usize = 16; +const COMMISSIONING_FLOW_FIELD_LENGTH_IN_BITS: usize = 2; +const RENDEZVOUS_INFO_FIELD_LENGTH_IN_BITS: usize = 8; +const PAYLOAD_DISCRIMINATOR_FIELD_LENGTH_IN_BITS: usize = LONG_BITS; +const SETUP_PINCODE_FIELD_LENGTH_IN_BITS: usize = 27; +const PADDING_FIELD_LENGTH_IN_BITS: usize = 4; +const TOTAL_PAYLOAD_DATA_SIZE_IN_BITS: usize = VERSION_FIELD_LENGTH_IN_BITS + + VENDOR_IDFIELD_LENGTH_IN_BITS + + PRODUCT_IDFIELD_LENGTH_IN_BITS + + COMMISSIONING_FLOW_FIELD_LENGTH_IN_BITS + + RENDEZVOUS_INFO_FIELD_LENGTH_IN_BITS + + PAYLOAD_DISCRIMINATOR_FIELD_LENGTH_IN_BITS + + SETUP_PINCODE_FIELD_LENGTH_IN_BITS + + PADDING_FIELD_LENGTH_IN_BITS; +const TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES: usize = TOTAL_PAYLOAD_DATA_SIZE_IN_BITS / 8; + #[repr(u8)] #[derive(Clone, Copy)] pub enum CommissionningFlowType { @@ -21,6 +42,16 @@ pub struct DiscoveryCapabilitiesSchema { soft_access_point: bool, } +impl DiscoveryCapabilitiesSchema { + pub fn new(on_ip_network: bool, ble: bool, soft_access_point: bool) -> Self { + DiscoveryCapabilitiesSchema { + on_ip_network, + ble, + soft_access_point, + } + } +} + impl DiscoveryCapabilitiesSchema { fn as_bits(&self) -> u8 { let mut bits = 0; @@ -59,18 +90,20 @@ impl<'data> QrCodeData<'data> { } } -pub fn compute_and_print_pairing_code(dev_det: &BasicInfoConfig, comm_data: &CommissioningData) { +struct TlvData { + data_length_in_bytes: u32, +} + +/// Prepares and prints the pairing code and the QR code for easy pairing. +pub fn print_pairing_code_and_qr(dev_det: &BasicInfoConfig, comm_data: &CommissioningData) { let pairing_code = compute_pairing_code(comm_data); - pretty_print_pairing_code(&pairing_code); - - let disc_cap = DiscoveryCapabilitiesSchema { - on_ip_network: true, - ble: false, - soft_access_point: false, - }; + // todo: allow the discovery capabilities to be passed in + let disc_cap = DiscoveryCapabilitiesSchema::new(true, false, false); let qr_code_data = QrCodeData::new(dev_det, comm_data, disc_cap); let data_str = payload_base38_representation(&qr_code_data).expect("Failed to encode"); + + pretty_print_pairing_code(&pairing_code); print_qr_code(&data_str); } @@ -98,7 +131,7 @@ fn compute_pairing_code(comm_data: &CommissioningData) -> String { digits } -pub fn pretty_print_pairing_code(pairing_code: &str) { +fn pretty_print_pairing_code(pairing_code: &str) { assert!(pairing_code.len() == 11); let mut pretty = String::new(); pretty.push_str(&pairing_code[..4]); @@ -116,7 +149,7 @@ fn print_qr_code(qr_data: &str) { .dark_color(unicode::Dense1x2::Light) .light_color(unicode::Dense1x2::Dark) .build(); - println!("{}", image); + info!("\n{}", image); } fn populate_bits( @@ -148,34 +181,11 @@ fn populate_bits( Ok(()) } -const LONG_BITS: usize = 12; -const VERSION_FIELD_LENGTH_IN_BITS: usize = 3; -const VENDOR_IDFIELD_LENGTH_IN_BITS: usize = 16; -const PRODUCT_IDFIELD_LENGTH_IN_BITS: usize = 16; -const COMMISSIONING_FLOW_FIELD_LENGTH_IN_BITS: usize = 2; -const RENDEZVOUS_INFO_FIELD_LENGTH_IN_BITS: usize = 8; -const PAYLOAD_DISCRIMINATOR_FIELD_LENGTH_IN_BITS: usize = LONG_BITS; -const SETUP_PINCODE_FIELD_LENGTH_IN_BITS: usize = 27; -const PADDING_FIELD_LENGTH_IN_BITS: usize = 4; -const RAW_VENDOR_TAG_LENGTH_IN_BITS: usize = 7; -const TOTAL_PAYLOAD_DATA_SIZE_IN_BITS: usize = VERSION_FIELD_LENGTH_IN_BITS - + VENDOR_IDFIELD_LENGTH_IN_BITS - + PRODUCT_IDFIELD_LENGTH_IN_BITS - + COMMISSIONING_FLOW_FIELD_LENGTH_IN_BITS - + RENDEZVOUS_INFO_FIELD_LENGTH_IN_BITS - + PAYLOAD_DISCRIMINATOR_FIELD_LENGTH_IN_BITS - + SETUP_PINCODE_FIELD_LENGTH_IN_BITS - + PADDING_FIELD_LENGTH_IN_BITS; -const TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES: usize = TOTAL_PAYLOAD_DATA_SIZE_IN_BITS / 8; - -struct TlvData { - data_length_in_bytes: u32, -} fn payload_base38_representation_with_tlv( payload: &QrCodeData, bits: &mut [u8; TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES], - tlv_data: Option, + tlv_data: Option<&TlvData>, ) -> Result { generate_bit_set(payload, bits, tlv_data)?; let base38_encoded = base38::encode(&*bits); @@ -193,10 +203,10 @@ fn payload_base38_representation(payload: &QrCodeData) -> Result fn generate_bit_set( payload: &QrCodeData, bits: &mut [u8; TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES], - tlv_data: Option, + tlv_data: Option<&TlvData>, ) -> Result<(), Error> { let mut offset: usize = 0; - let total_payload_size_in_bits = if let Some(tlv_data) = &tlv_data { + let total_payload_size_in_bits = if let Some(tlv_data) = tlv_data { TOTAL_PAYLOAD_DATA_SIZE_IN_BITS + (tlv_data.data_length_in_bytes * 8) as usize } else { TOTAL_PAYLOAD_DATA_SIZE_IN_BITS @@ -314,12 +324,8 @@ mod tests { pid: 65279, ..Default::default() }; - let disc_cap = DiscoveryCapabilitiesSchema { - on_ip_network: false, - ble: true, - soft_access_point: false, - }; + let disc_cap = DiscoveryCapabilitiesSchema::new(false, true, false); let qr_code_data = QrCodeData::new(&dev_det, &comm_data, disc_cap); let data_str = payload_base38_representation(&qr_code_data).expect("Failed to encode"); assert_eq!(data_str, QR_CODE) From 9288503a1ea2aa187555a4a501f19f1594fdd8d0 Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 11 Jan 2023 09:57:03 +0100 Subject: [PATCH 14/30] Inserted copyright headers --- matter/src/codec/base38.rs | 18 ++++++++++++++++++ matter/src/mdns.rs | 17 +++++++++++++++++ matter/src/pairing.rs | 17 +++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/matter/src/codec/base38.rs b/matter/src/codec/base38.rs index bd6113f..8bea287 100644 --- a/matter/src/codec/base38.rs +++ b/matter/src/codec/base38.rs @@ -1,4 +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. + */ + //! Base38 encoding functions. + const BASE38_CHARS: &str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-."; /// Encodes a byte array into a base38 string. diff --git a/matter/src/mdns.rs b/matter/src/mdns.rs index 07f464a..194dcc2 100644 --- a/matter/src/mdns.rs +++ b/matter/src/mdns.rs @@ -1,3 +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. + */ + use std::sync::{Arc, Mutex, Once}; use crate::{ diff --git a/matter/src/pairing.rs b/matter/src/pairing.rs index a367ba4..070fb0e 100644 --- a/matter/src/pairing.rs +++ b/matter/src/pairing.rs @@ -1,3 +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. + */ + //! This module contains the logic for generating the pairing code and the QR code for easy pairing. use log::info; From 2b4919bb56c5790e7b77ffd96f0bd00a79e7b2e8 Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 11 Jan 2023 13:35:25 +0100 Subject: [PATCH 15/30] Merged multi-admin --- examples/onoff_light/src/main.rs | 2 +- matter/src/core.rs | 13 +++------- matter/src/fabric.rs | 2 +- matter/src/mdns.rs | 13 ++++------ matter/src/pairing.rs | 27 ++++++++++++++------- matter/src/secure_channel/crypto_mbedtls.rs | 2 +- matter/tests/common/im_engine.rs | 9 +------ 7 files changed, 31 insertions(+), 37 deletions(-) diff --git a/examples/onoff_light/src/main.rs b/examples/onoff_light/src/main.rs index aadf5f7..50ae775 100644 --- a/examples/onoff_light/src/main.rs +++ b/examples/onoff_light/src/main.rs @@ -39,7 +39,7 @@ fn main() { }; let dev_att = Box::new(dev_att::HardCodedDevAtt::new()); - let mut matter = core::Matter::new(&dev_info, dev_att, &comm_data).unwrap(); + 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(); diff --git a/matter/src/core.rs b/matter/src/core.rs index 35b8648..41cf08c 100644 --- a/matter/src/core.rs +++ b/matter/src/core.rs @@ -26,7 +26,7 @@ use crate::{ interaction_model::InteractionModel, mdns::Mdns, pairing::print_pairing_code_and_qr, - secure_channel::core::SecureChannel, + secure_channel::{core::SecureChannel, pake::PaseMgr, spake2p::VerifierData}, transport, }; use std::sync::Arc; @@ -56,17 +56,12 @@ impl Matter { pub fn new( dev_det: &BasicInfoConfig, dev_att: Box, - dev_comm: &CommissioningData, + dev_comm: CommissioningData, ) -> Result, Error> { let mdns = Mdns::get()?; - mdns.set_values( - dev_det.vid, - dev_det.pid, - dev_comm.discriminator, - &dev_det.device_name, - ); + mdns.set_values(dev_det.vid, dev_det.pid, &dev_det.device_name); - print_pairing_code_and_qr(dev_det, dev_comm); + print_pairing_code_and_qr(dev_det, &dev_comm); let fabric_mgr = Arc::new(FabricMgr::new()?); let acl_mgr = Arc::new(AclMgr::new()?); diff --git a/matter/src/fabric.rs b/matter/src/fabric.rs index 1e91617..bb729ae 100644 --- a/matter/src/fabric.rs +++ b/matter/src/fabric.rs @@ -381,7 +381,7 @@ impl FabricMgr { pub fn set_label(&self, index: u8, label: String) -> Result<(), Error> { let index = index as usize; let mut mgr = self.inner.write()?; - if label != "" { + if !label.is_empty() { for i in 1..MAX_SUPPORTED_FABRICS { if let Some(fabric) = &mgr.fabrics[i] { if fabric.label == label { diff --git a/matter/src/mdns.rs b/matter/src/mdns.rs index 6f66db5..53d7a35 100644 --- a/matter/src/mdns.rs +++ b/matter/src/mdns.rs @@ -30,8 +30,6 @@ pub struct MdnsInner { vid: u16, /// Product ID pid: u16, - /// Discriminator - discriminator: u16, /// Device name device_name: String, } @@ -75,11 +73,10 @@ impl Mdns { /// 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, device_name: &str) { + pub fn set_values(&self, vid: u16, pid: u16, device_name: &str) { let mut inner = self.inner.lock().unwrap(); inner.vid = vid; inner.pid = pid; - inner.discriminator = discriminator; inner.device_name = device_name.chars().take(32).collect(); } @@ -92,12 +89,12 @@ impl Mdns { ServiceMode::Commissioned => { sys_publish_service(name, "_matter._tcp", MATTER_PORT, &[]) } - ServiceMode::Commissionable => { + ServiceMode::Commissionable(discriminator) => { let inner = self.inner.lock().unwrap(); - let short = compute_short_discriminator(inner.discriminator); - let serv_type = format!("_matterc._udp,_S{},_L{}", short, inner.discriminator); + let short = compute_short_discriminator(discriminator); + let serv_type = format!("_matterc._udp,_S{},_L{}", short, discriminator); - let str_discriminator = format!("{}", inner.discriminator); + let str_discriminator = format!("{}", discriminator); let txt_kvs = [ ["D", &str_discriminator], ["CM", "1"], diff --git a/matter/src/pairing.rs b/matter/src/pairing.rs index 070fb0e..a5e8487 100644 --- a/matter/src/pairing.rs +++ b/matter/src/pairing.rs @@ -23,7 +23,7 @@ use verhoeff::Verhoeff; use crate::{ codec::base38, data_model::cluster_basic_information::BasicInfoConfig, error::Error, - CommissioningData, + secure_channel::spake2p::VerifierOption, CommissioningData, }; const LONG_BITS: usize = 12; @@ -130,10 +130,15 @@ fn compute_pairing_code(comm_data: &CommissioningData) -> String { let CommissioningData { discriminator, - passwd, + verifier, .. } = comm_data; + let passwd = match verifier.data { + VerifierOption::Password(pwd) => pwd, + VerifierOption::Verifier(_) => todo!(), + }; + let mut digits = String::new(); digits.push_str(&((VID_PID_PRESENT << 2) | (discriminator >> 10) as u8).to_string()); digits.push_str(&format!( @@ -233,6 +238,11 @@ fn generate_bit_set( return Err(Error::BufferTooSmall); }; + let passwd = match payload.comm_data.verifier.data { + VerifierOption::Password(pwd) => pwd, + VerifierOption::Verifier(_) => todo!(), + }; + const VERSION: u64 = 0; populate_bits( bits, @@ -285,7 +295,7 @@ fn generate_bit_set( populate_bits( bits, &mut offset, - payload.comm_data.passwd as u64, + passwd as u64, SETUP_PINCODE_FIELD_LENGTH_IN_BITS, total_payload_size_in_bits, )?; @@ -306,22 +316,22 @@ fn generate_bit_set( #[cfg(test)] mod tests { + use crate::secure_channel::spake2p::VerifierData; + use super::*; #[test] fn can_compute_pairing_code() { let comm_data = CommissioningData { - passwd: 123456, + verifier: VerifierData::new_with_pw(123456), discriminator: 250, - ..Default::default() }; let pairing_code = compute_pairing_code(&comm_data); assert_eq!(pairing_code, "00876800071"); let comm_data = CommissioningData { - passwd: 34567890, + verifier: VerifierData::new_with_pw(34567890), discriminator: 2976, - ..Default::default() }; let pairing_code = compute_pairing_code(&comm_data); assert_eq!(pairing_code, "26318621095"); @@ -332,9 +342,8 @@ mod tests { const QR_CODE: &str = "MT:YNJV7VSC00CMVH7SR00"; let comm_data = CommissioningData { - passwd: 34567890, + verifier: VerifierData::new_with_pw(34567890), discriminator: 2976, - ..Default::default() }; let dev_det = BasicInfoConfig { vid: 9050, diff --git a/matter/src/secure_channel/crypto_mbedtls.rs b/matter/src/secure_channel/crypto_mbedtls.rs index 7231f63..7ac4c5a 100644 --- a/matter/src/secure_channel/crypto_mbedtls.rs +++ b/matter/src/secure_channel/crypto_mbedtls.rs @@ -115,7 +115,7 @@ impl CryptoSpake2 for CryptoMbedTLS { } fn set_L(&mut self, l: &[u8]) -> Result<(), Error> { - self.L = EcPoint::from_binary(&mut self.group, l)?; + self.L = EcPoint::from_binary(&self.group, l)?; Ok(()) } diff --git a/matter/tests/common/im_engine.rs b/matter/tests/common/im_engine.rs index 923d499..b1a69de 100644 --- a/matter/tests/common/im_engine.rs +++ b/matter/tests/common/im_engine.rs @@ -106,14 +106,7 @@ impl ImEngine { // 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(), - pase_mgr, - ) - .unwrap(); + let dm = DataModel::new(&dev_det, dev_att, fabric_mgr, acl_mgr.clone(), pase_mgr).unwrap(); { let mut d = dm.node.write().unwrap(); From 38b758398de22eef4755385ace201e1903243821 Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 11 Jan 2023 22:12:04 +0100 Subject: [PATCH 16/30] Clean-up --- matter/src/core.rs | 4 +- matter/src/pairing/code.rs | 60 ++++++ matter/src/pairing/mod.rs | 96 +++++++++ matter/src/{pairing.rs => pairing/qr.rs} | 239 +++++++++-------------- matter/src/pairing/vendor_identifiers.rs | 16 ++ 5 files changed, 270 insertions(+), 145 deletions(-) create mode 100644 matter/src/pairing/code.rs create mode 100644 matter/src/pairing/mod.rs rename matter/src/{pairing.rs => pairing/qr.rs} (57%) create mode 100644 matter/src/pairing/vendor_identifiers.rs diff --git a/matter/src/core.rs b/matter/src/core.rs index 97143d1..105b671 100644 --- a/matter/src/core.rs +++ b/matter/src/core.rs @@ -25,7 +25,7 @@ use crate::{ fabric::FabricMgr, interaction_model::InteractionModel, mdns::Mdns, - pairing::print_pairing_code_and_qr, + pairing::{print_pairing_code_and_qr, DiscoveryCapabilities}, secure_channel::core::SecureChannel, transport, }; @@ -70,7 +70,7 @@ impl Matter { &dev_det.device_name, ); - print_pairing_code_and_qr(dev_det, dev_comm); + print_pairing_code_and_qr(dev_det, dev_comm, DiscoveryCapabilities::default()); let fabric_mgr = Arc::new(FabricMgr::new()?); let acl_mgr = Arc::new(AclMgr::new()?); diff --git a/matter/src/pairing/code.rs b/matter/src/pairing/code.rs new file mode 100644 index 0000000..011dc35 --- /dev/null +++ b/matter/src/pairing/code.rs @@ -0,0 +1,60 @@ +use super::*; + +pub(super) fn compute_pairing_code(comm_data: &CommissioningData) -> String { + // 0: no Vendor ID and Product ID present in Manual Pairing Code + const VID_PID_PRESENT: u8 = 0; + + let CommissioningData { + discriminator, + passwd, + .. + } = comm_data; + + let mut digits = String::new(); + digits.push_str(&((VID_PID_PRESENT << 2) | (discriminator >> 10) as u8).to_string()); + digits.push_str(&format!( + "{:0>5}", + ((discriminator & 0x300) << 6) | (passwd & 0x3FFF) as u16 + )); + digits.push_str(&format!("{:0>4}", passwd >> 14)); + + let check_digit = digits.calculate_verhoeff_check_digit(); + digits.push_str(&check_digit.to_string()); + + digits +} + +pub(super) fn pretty_print_pairing_code(pairing_code: &str) { + assert!(pairing_code.len() == 11); + let mut pretty = String::new(); + pretty.push_str(&pairing_code[..4]); + pretty.push('-'); + pretty.push_str(&pairing_code[4..8]); + pretty.push('-'); + pretty.push_str(&pairing_code[8..]); + info!("Pairing Code: {}", pretty); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn can_compute_pairing_code() { + let comm_data = CommissioningData { + passwd: 123456, + discriminator: 250, + ..Default::default() + }; + let pairing_code = compute_pairing_code(&comm_data); + assert_eq!(pairing_code, "00876800071"); + + let comm_data = CommissioningData { + passwd: 34567890, + discriminator: 2976, + ..Default::default() + }; + let pairing_code = compute_pairing_code(&comm_data); + assert_eq!(pairing_code, "26318621095"); + } +} diff --git a/matter/src/pairing/mod.rs b/matter/src/pairing/mod.rs new file mode 100644 index 0000000..96233bc --- /dev/null +++ b/matter/src/pairing/mod.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. + */ + +//! This module contains the logic for generating the pairing code and the QR code for easy pairing. + +pub mod code; +pub mod qr; +pub mod vendor_identifiers; + +use log::info; +use qrcode::{render::unicode, QrCode, Version}; +use verhoeff::Verhoeff; + +use crate::{ + codec::base38, data_model::cluster_basic_information::BasicInfoConfig, error::Error, + CommissioningData, +}; + +use self::{ + code::{compute_pairing_code, pretty_print_pairing_code}, + qr::{payload_base38_representation, print_qr_code, QrCodeData}, +}; + +pub struct DiscoveryCapabilities { + on_ip_network: bool, + ble: bool, + soft_access_point: bool, +} + +impl DiscoveryCapabilities { + pub fn new(on_ip_network: bool, ble: bool, soft_access_point: bool) -> Self { + DiscoveryCapabilities { + on_ip_network, + ble, + soft_access_point, + } + } + + pub fn has_value(&self) -> bool { + self.on_ip_network || self.ble || self.soft_access_point + } +} + +impl Default for DiscoveryCapabilities { + fn default() -> Self { + DiscoveryCapabilities { + on_ip_network: true, + ble: false, + soft_access_point: false, + } + } +} + +impl DiscoveryCapabilities { + fn as_bits(&self) -> u8 { + let mut bits = 0; + if self.soft_access_point { + bits |= 1 << 0; + } + if self.ble { + bits |= 1 << 1; + } + if self.on_ip_network { + bits |= 1 << 2; + } + bits + } +} + +/// Prepares and prints the pairing code and the QR code for easy pairing. +pub fn print_pairing_code_and_qr( + dev_det: &BasicInfoConfig, + comm_data: &CommissioningData, + discovery_capabilities: DiscoveryCapabilities, +) { + let pairing_code = compute_pairing_code(comm_data); + let qr_code_data = QrCodeData::new(dev_det, comm_data, discovery_capabilities); + let data_str = payload_base38_representation(&qr_code_data).expect("Failed to encode"); + + pretty_print_pairing_code(&pairing_code); + print_qr_code(&data_str); +} diff --git a/matter/src/pairing.rs b/matter/src/pairing/qr.rs similarity index 57% rename from matter/src/pairing.rs rename to matter/src/pairing/qr.rs index 070fb0e..2d1f518 100644 --- a/matter/src/pairing.rs +++ b/matter/src/pairing/qr.rs @@ -1,29 +1,6 @@ -/* - * - * 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. - */ - -//! This module contains the logic for generating the pairing code and the QR code for easy pairing. - -use log::info; -use qrcode::{render::unicode, QrCode, Version}; -use verhoeff::Verhoeff; - -use crate::{ - codec::base38, data_model::cluster_basic_information::BasicInfoConfig, error::Error, - CommissioningData, +use super::{ + vendor_identifiers::{is_vendor_id_valid_operationally, VendorId}, + *, }; const LONG_BITS: usize = 12; @@ -45,49 +22,10 @@ const TOTAL_PAYLOAD_DATA_SIZE_IN_BITS: usize = VERSION_FIELD_LENGTH_IN_BITS + PADDING_FIELD_LENGTH_IN_BITS; const TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES: usize = TOTAL_PAYLOAD_DATA_SIZE_IN_BITS / 8; -#[repr(u8)] -#[derive(Clone, Copy)] -pub enum CommissionningFlowType { - Standard = 0, - UserIntent = 1, - Custom = 2, -} - -pub struct DiscoveryCapabilitiesSchema { - on_ip_network: bool, - ble: bool, - soft_access_point: bool, -} - -impl DiscoveryCapabilitiesSchema { - pub fn new(on_ip_network: bool, ble: bool, soft_access_point: bool) -> Self { - DiscoveryCapabilitiesSchema { - on_ip_network, - ble, - soft_access_point, - } - } -} - -impl DiscoveryCapabilitiesSchema { - fn as_bits(&self) -> u8 { - let mut bits = 0; - if self.soft_access_point { - bits |= 1 << 0; - } - if self.ble { - bits |= 1 << 1; - } - if self.on_ip_network { - bits |= 1 << 2; - } - bits - } -} - pub struct QrCodeData<'data> { + version: u8, flow_type: CommissionningFlowType, - discovery_capabilities: DiscoveryCapabilitiesSchema, + discovery_capabilities: DiscoveryCapabilities, dev_det: &'data BasicInfoConfig, comm_data: &'data CommissioningData, } @@ -96,70 +34,113 @@ impl<'data> QrCodeData<'data> { pub fn new( dev_det: &'data BasicInfoConfig, comm_data: &'data CommissioningData, - discovery_capabilities: DiscoveryCapabilitiesSchema, + discovery_capabilities: DiscoveryCapabilities, ) -> Self { + const DEFAULT_VERSION: u8 = 0; + QrCodeData { + version: DEFAULT_VERSION, flow_type: CommissionningFlowType::Standard, discovery_capabilities, dev_det, comm_data, } } + + fn is_valid(&self) -> bool { + // 3-bit value specifying the QR code payload version. + if self.version >= 1 << VERSION_FIELD_LENGTH_IN_BITS { + return false; + } + + if !self.discovery_capabilities.has_value() { + return false; + } + + if self.comm_data.passwd >= 1 << SETUP_PINCODE_FIELD_LENGTH_IN_BITS { + return false; + } + + self.check_payload_common_constraints() + } + + fn check_payload_common_constraints(&self) -> bool { + // A version not equal to 0 would be invalid for v1 and would indicate new format (e.g. version 2) + if self.version != 0 { + return false; + } + + if !Self::is_valid_setup_pin(self.comm_data.passwd) { + return false; + } + + // VendorID must be unspecified (0) or in valid range expected. + if !is_vendor_id_valid_operationally(self.dev_det.vid) + && (self.dev_det.vid != VendorId::CommonOrUnspecified as u16) + { + return false; + } + + // A value of 0x0000 SHALL NOT be assigned to a product since Product ID = 0x0000 is used for these specific cases: + // * To announce an anonymized Product ID as part of device discovery + // * To indicate an OTA software update file applies to multiple Product IDs equally. + // * To avoid confusion when presenting the Onboarding Payload for ECM with multiple nodes + if self.dev_det.pid == 0 && self.dev_det.vid != VendorId::CommonOrUnspecified as u16 { + return false; + } + + true + } + + fn is_valid_setup_pin(setup_pin: u32) -> bool { + const SETUP_PINCODE_MAXIMUM_VALUE: u32 = 99999998; + const SETUP_PINCODE_UNDEFINED_VALUE: u32 = 0; + + // SHALL be restricted to the values 0x0000001 to 0x5F5E0FE (00000001 to 99999998 in decimal), excluding the invalid Passcode + // values. + if setup_pin == SETUP_PINCODE_UNDEFINED_VALUE + || setup_pin > SETUP_PINCODE_MAXIMUM_VALUE + || setup_pin == 11111111 + || setup_pin == 22222222 + || setup_pin == 33333333 + || setup_pin == 44444444 + || setup_pin == 55555555 + || setup_pin == 66666666 + || setup_pin == 77777777 + || setup_pin == 88888888 + || setup_pin == 12345678 + || setup_pin == 87654321 + { + return false; + } + + true + } +} + +#[repr(u8)] +#[derive(Clone, Copy)] +pub enum CommissionningFlowType { + Standard = 0, + UserIntent = 1, + Custom = 2, } struct TlvData { data_length_in_bytes: u32, } -/// Prepares and prints the pairing code and the QR code for easy pairing. -pub fn print_pairing_code_and_qr(dev_det: &BasicInfoConfig, comm_data: &CommissioningData) { - let pairing_code = compute_pairing_code(comm_data); +pub(super) fn payload_base38_representation(payload: &QrCodeData) -> Result { + let mut bits: [u8; TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES] = [0; TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES]; - // todo: allow the discovery capabilities to be passed in - let disc_cap = DiscoveryCapabilitiesSchema::new(true, false, false); - let qr_code_data = QrCodeData::new(dev_det, comm_data, disc_cap); - let data_str = payload_base38_representation(&qr_code_data).expect("Failed to encode"); + if !payload.is_valid() { + return Err(Error::InvalidArgument); + } - pretty_print_pairing_code(&pairing_code); - print_qr_code(&data_str); + payload_base38_representation_with_tlv(payload, &mut bits, None) } -fn compute_pairing_code(comm_data: &CommissioningData) -> String { - // 0: no Vendor ID and Product ID present in Manual Pairing Code - const VID_PID_PRESENT: u8 = 0; - - let CommissioningData { - discriminator, - passwd, - .. - } = comm_data; - - let mut digits = String::new(); - digits.push_str(&((VID_PID_PRESENT << 2) | (discriminator >> 10) as u8).to_string()); - digits.push_str(&format!( - "{:0>5}", - ((discriminator & 0x300) << 6) | (passwd & 0x3FFF) as u16 - )); - digits.push_str(&format!("{:0>4}", passwd >> 14)); - - let check_digit = digits.calculate_verhoeff_check_digit(); - digits.push_str(&check_digit.to_string()); - - digits -} - -fn pretty_print_pairing_code(pairing_code: &str) { - assert!(pairing_code.len() == 11); - let mut pretty = String::new(); - pretty.push_str(&pairing_code[..4]); - pretty.push('-'); - pretty.push_str(&pairing_code[4..8]); - pretty.push('-'); - pretty.push_str(&pairing_code[8..]); - info!("Pairing Code: {}", pretty); -} - -fn print_qr_code(qr_data: &str) { +pub(super) fn print_qr_code(qr_data: &str) { let code = QrCode::with_version(qr_data, Version::Normal(2), qrcode::EcLevel::M).unwrap(); let image = code .render::() @@ -209,14 +190,6 @@ fn payload_base38_representation_with_tlv( Ok(format!("MT:{}", base38_encoded)) } -fn payload_base38_representation(payload: &QrCodeData) -> Result { - let mut bits: [u8; TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES] = [0; TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES]; - - // VerifyOrReturnError(mPayload.isValidQRCodePayload(), CHIP_ERROR_INVALID_ARGUMENT); - - payload_base38_representation_with_tlv(payload, &mut bits, None) -} - fn generate_bit_set( payload: &QrCodeData, bits: &mut [u8; TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES], @@ -233,11 +206,10 @@ fn generate_bit_set( return Err(Error::BufferTooSmall); }; - const VERSION: u64 = 0; populate_bits( bits, &mut offset, - VERSION, + payload.version as u64, VERSION_FIELD_LENGTH_IN_BITS, total_payload_size_in_bits, )?; @@ -308,25 +280,6 @@ fn generate_bit_set( mod tests { use super::*; - #[test] - fn can_compute_pairing_code() { - let comm_data = CommissioningData { - passwd: 123456, - discriminator: 250, - ..Default::default() - }; - let pairing_code = compute_pairing_code(&comm_data); - assert_eq!(pairing_code, "00876800071"); - - let comm_data = CommissioningData { - passwd: 34567890, - discriminator: 2976, - ..Default::default() - }; - let pairing_code = compute_pairing_code(&comm_data); - assert_eq!(pairing_code, "26318621095"); - } - #[test] fn can_base38_encode() { const QR_CODE: &str = "MT:YNJV7VSC00CMVH7SR00"; @@ -342,7 +295,7 @@ mod tests { ..Default::default() }; - let disc_cap = DiscoveryCapabilitiesSchema::new(false, true, false); + let disc_cap = DiscoveryCapabilities::new(false, true, false); let qr_code_data = QrCodeData::new(&dev_det, &comm_data, disc_cap); let data_str = payload_base38_representation(&qr_code_data).expect("Failed to encode"); assert_eq!(data_str, QR_CODE) diff --git a/matter/src/pairing/vendor_identifiers.rs b/matter/src/pairing/vendor_identifiers.rs new file mode 100644 index 0000000..1de0482 --- /dev/null +++ b/matter/src/pairing/vendor_identifiers.rs @@ -0,0 +1,16 @@ +#[repr(u16)] +pub enum VendorId { + CommonOrUnspecified = 0x0000, + Apple = 0x1349, + Google = 0x6006, + TestVendor1 = 0xFFF1, + TestVendor2 = 0xFFF2, + TestVendor3 = 0xFFF3, + TestVendor4 = 0xFFF4, + NotSpecified = 0xFFFF, +} + +pub fn is_vendor_id_valid_operationally(vendor_id: u16) -> bool { + (vendor_id != VendorId::CommonOrUnspecified as u16) + && (vendor_id <= VendorId::TestVendor4 as u16) +} From 13c522d4be1c8bf0222a8f9f7d9e67603afb03bf Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 11 Jan 2023 22:28:23 +0100 Subject: [PATCH 17/30] Clean-up --- matter/src/core.rs | 2 +- matter/src/lib.rs | 2 +- matter/src/pairing/code.rs | 14 +++++--------- matter/src/pairing/mod.rs | 10 +++++++++- matter/src/pairing/qr.rs | 17 ++++++++++++----- 5 files changed, 28 insertions(+), 17 deletions(-) diff --git a/matter/src/core.rs b/matter/src/core.rs index 2863c70..21b72fd 100644 --- a/matter/src/core.rs +++ b/matter/src/core.rs @@ -61,7 +61,7 @@ impl Matter { let mdns = Mdns::get()?; mdns.set_values(dev_det.vid, dev_det.pid, &dev_det.device_name); - print_pairing_code_and_qr(dev_det, dev_comm, DiscoveryCapabilities::default()); + print_pairing_code_and_qr(dev_det, &dev_comm, DiscoveryCapabilities::default()); let fabric_mgr = Arc::new(FabricMgr::new()?); let acl_mgr = Arc::new(AclMgr::new()?); diff --git a/matter/src/lib.rs b/matter/src/lib.rs index cd7aafc..2116822 100644 --- a/matter/src/lib.rs +++ b/matter/src/lib.rs @@ -55,7 +55,7 @@ //! //! /// 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 mut matter = Matter::new(&dev_info, dev_att, comm_data).unwrap(); //! let dm = matter.get_data_model(); //! { //! let mut node = dm.node.write().unwrap(); diff --git a/matter/src/pairing/code.rs b/matter/src/pairing/code.rs index 011dc35..7922d4e 100644 --- a/matter/src/pairing/code.rs +++ b/matter/src/pairing/code.rs @@ -4,11 +4,8 @@ pub(super) fn compute_pairing_code(comm_data: &CommissioningData) -> String { // 0: no Vendor ID and Product ID present in Manual Pairing Code const VID_PID_PRESENT: u8 = 0; - let CommissioningData { - discriminator, - passwd, - .. - } = comm_data; + let passwd = passwd_from_comm_data(comm_data); + let CommissioningData { discriminator, .. } = comm_data; let mut digits = String::new(); digits.push_str(&((VID_PID_PRESENT << 2) | (discriminator >> 10) as u8).to_string()); @@ -38,21 +35,20 @@ pub(super) fn pretty_print_pairing_code(pairing_code: &str) { #[cfg(test)] mod tests { use super::*; + use crate::secure_channel::spake2p::VerifierData; #[test] fn can_compute_pairing_code() { let comm_data = CommissioningData { - passwd: 123456, + verifier: VerifierData::new_with_pw(123456), discriminator: 250, - ..Default::default() }; let pairing_code = compute_pairing_code(&comm_data); assert_eq!(pairing_code, "00876800071"); let comm_data = CommissioningData { - passwd: 34567890, + verifier: VerifierData::new_with_pw(34567890), discriminator: 2976, - ..Default::default() }; let pairing_code = compute_pairing_code(&comm_data); assert_eq!(pairing_code, "26318621095"); diff --git a/matter/src/pairing/mod.rs b/matter/src/pairing/mod.rs index 96233bc..c7adc37 100644 --- a/matter/src/pairing/mod.rs +++ b/matter/src/pairing/mod.rs @@ -27,7 +27,7 @@ use verhoeff::Verhoeff; use crate::{ codec::base38, data_model::cluster_basic_information::BasicInfoConfig, error::Error, - CommissioningData, + secure_channel::spake2p::VerifierOption, CommissioningData, }; use self::{ @@ -94,3 +94,11 @@ pub fn print_pairing_code_and_qr( pretty_print_pairing_code(&pairing_code); print_qr_code(&data_str); } + +pub(self) fn passwd_from_comm_data(comm_data: &CommissioningData) -> u32 { + // todo: should this be part of the comm_data implementation? + match comm_data.verifier.data { + VerifierOption::Password(pwd) => pwd, + VerifierOption::Verifier(_) => 0, + } +} diff --git a/matter/src/pairing/qr.rs b/matter/src/pairing/qr.rs index 2d1f518..90f9ccd 100644 --- a/matter/src/pairing/qr.rs +++ b/matter/src/pairing/qr.rs @@ -48,6 +48,8 @@ impl<'data> QrCodeData<'data> { } fn is_valid(&self) -> bool { + let passwd = passwd_from_comm_data(self.comm_data); + // 3-bit value specifying the QR code payload version. if self.version >= 1 << VERSION_FIELD_LENGTH_IN_BITS { return false; @@ -57,7 +59,7 @@ impl<'data> QrCodeData<'data> { return false; } - if self.comm_data.passwd >= 1 << SETUP_PINCODE_FIELD_LENGTH_IN_BITS { + if passwd >= 1 << SETUP_PINCODE_FIELD_LENGTH_IN_BITS { return false; } @@ -70,7 +72,9 @@ impl<'data> QrCodeData<'data> { return false; } - if !Self::is_valid_setup_pin(self.comm_data.passwd) { + let passwd = passwd_from_comm_data(self.comm_data); + + if !Self::is_valid_setup_pin(passwd) { return false; } @@ -206,6 +210,8 @@ fn generate_bit_set( return Err(Error::BufferTooSmall); }; + let passwd = passwd_from_comm_data(payload.comm_data); + populate_bits( bits, &mut offset, @@ -257,7 +263,7 @@ fn generate_bit_set( populate_bits( bits, &mut offset, - payload.comm_data.passwd as u64, + passwd as u64, SETUP_PINCODE_FIELD_LENGTH_IN_BITS, total_payload_size_in_bits, )?; @@ -278,16 +284,17 @@ fn generate_bit_set( #[cfg(test)] mod tests { + use super::*; + use crate::secure_channel::spake2p::VerifierData; #[test] fn can_base38_encode() { const QR_CODE: &str = "MT:YNJV7VSC00CMVH7SR00"; let comm_data = CommissioningData { - passwd: 34567890, + verifier: VerifierData::new_with_pw(34567890), discriminator: 2976, - ..Default::default() }; let dev_det = BasicInfoConfig { vid: 9050, From 0f0b87a3690acb7e96d43b079cdde87dc89c8d6e Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 11 Jan 2023 22:30:40 +0100 Subject: [PATCH 18/30] Copyright notice --- matter/src/pairing/code.rs | 17 +++++++++++++++++ matter/src/pairing/qr.rs | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/matter/src/pairing/code.rs b/matter/src/pairing/code.rs index 011dc35..3de893d 100644 --- a/matter/src/pairing/code.rs +++ b/matter/src/pairing/code.rs @@ -1,3 +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. + */ + use super::*; pub(super) fn compute_pairing_code(comm_data: &CommissioningData) -> String { diff --git a/matter/src/pairing/qr.rs b/matter/src/pairing/qr.rs index 2d1f518..4e7bd29 100644 --- a/matter/src/pairing/qr.rs +++ b/matter/src/pairing/qr.rs @@ -1,3 +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. + */ + use super::{ vendor_identifiers::{is_vendor_id_valid_operationally, VendorId}, *, From b74ae4ff01d55f12b31ea7a5b3f407ed4e3cc0b7 Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 12 Jan 2023 13:13:50 +0100 Subject: [PATCH 19/30] Prep for optional TLV data --- matter/src/error.rs | 1 + matter/src/pairing/mod.rs | 4 +- matter/src/pairing/qr.rs | 174 +++++++++++++++++++++++++++++++++++--- 3 files changed, 164 insertions(+), 15 deletions(-) diff --git a/matter/src/error.rs b/matter/src/error.rs index 68e7f21..60e30a5 100644 --- a/matter/src/error.rs +++ b/matter/src/error.rs @@ -39,6 +39,7 @@ pub enum Error { NoHandler, NoNetworkInterface, NoNodeId, + NoMemory, NoSession, NoSpace, NoSpaceAckTable, diff --git a/matter/src/pairing/mod.rs b/matter/src/pairing/mod.rs index 96233bc..f27266d 100644 --- a/matter/src/pairing/mod.rs +++ b/matter/src/pairing/mod.rs @@ -32,7 +32,7 @@ use crate::{ use self::{ code::{compute_pairing_code, pretty_print_pairing_code}, - qr::{payload_base38_representation, print_qr_code, QrCodeData}, + qr::{payload_base38_representation, print_qr_code, QrSetupPayload}, }; pub struct DiscoveryCapabilities { @@ -88,7 +88,7 @@ pub fn print_pairing_code_and_qr( discovery_capabilities: DiscoveryCapabilities, ) { let pairing_code = compute_pairing_code(comm_data); - let qr_code_data = QrCodeData::new(dev_det, comm_data, discovery_capabilities); + let qr_code_data = QrSetupPayload::new(dev_det, comm_data, discovery_capabilities); let data_str = payload_base38_representation(&qr_code_data).expect("Failed to encode"); pretty_print_pairing_code(&pairing_code); diff --git a/matter/src/pairing/qr.rs b/matter/src/pairing/qr.rs index 4e7bd29..1bca10b 100644 --- a/matter/src/pairing/qr.rs +++ b/matter/src/pairing/qr.rs @@ -15,11 +15,14 @@ * limitations under the License. */ +use std::collections::HashMap; + use super::{ vendor_identifiers::{is_vendor_id_valid_operationally, VendorId}, *, }; +// See section 5.1.2. QR Code in the Matter specification const LONG_BITS: usize = 12; const VERSION_FIELD_LENGTH_IN_BITS: usize = 3; const VENDOR_IDFIELD_LENGTH_IN_BITS: usize = 16; @@ -39,15 +42,43 @@ const TOTAL_PAYLOAD_DATA_SIZE_IN_BITS: usize = VERSION_FIELD_LENGTH_IN_BITS + PADDING_FIELD_LENGTH_IN_BITS; const TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES: usize = TOTAL_PAYLOAD_DATA_SIZE_IN_BITS / 8; -pub struct QrCodeData<'data> { +// Spec 5.1.4.2 CHIP-Common Reserved Tags +const SERIAL_NUMBER_TAG: u8 = 0x00; +// const PBKDFITERATIONS_TAG: u8 = 0x01; +// const BPKFSALT_TAG: u8 = 0x02; +// const NUMBER_OFDEVICES_TAG: u8 = 0x03; +// const COMMISSIONING_TIMEOUT_TAG: u8 = 0x04; + +pub enum QRCodeInfoType { + String(String), + Int32(i32), + Int64(i64), + UInt32(u32), + UInt64(u64), +} + +pub enum SerialNumber { + String(String), + UInt32(u32), +} + +pub struct OptionalQRCodeInfo { + // the tag number of the optional info + pub tag: u8, + // the data of the optional info + pub data: QRCodeInfoType, +} + +pub struct QrSetupPayload<'data> { version: u8, flow_type: CommissionningFlowType, discovery_capabilities: DiscoveryCapabilities, dev_det: &'data BasicInfoConfig, comm_data: &'data CommissioningData, + optional_data: HashMap, } -impl<'data> QrCodeData<'data> { +impl<'data> QrSetupPayload<'data> { pub fn new( dev_det: &'data BasicInfoConfig, comm_data: &'data CommissioningData, @@ -55,12 +86,13 @@ impl<'data> QrCodeData<'data> { ) -> Self { const DEFAULT_VERSION: u8 = 0; - QrCodeData { + QrSetupPayload { version: DEFAULT_VERSION, flow_type: CommissionningFlowType::Standard, discovery_capabilities, dev_det, comm_data, + optional_data: HashMap::new(), } } @@ -81,6 +113,55 @@ impl<'data> QrCodeData<'data> { self.check_payload_common_constraints() } + /// A function to add an optional vendor data + /// # Arguments + /// * `tag` - tag number in the [0x80-0xFF] range + /// * `data` - Data to add + pub fn add_optional_vendor_data(&mut self, tag: u8, data: QRCodeInfoType) -> Result<(), Error> { + if !is_vendor_tag(tag) { + return Err(Error::InvalidArgument); + } + + self.optional_data + .insert(tag, OptionalQRCodeInfo { tag, data }); + Ok(()) + } + + /// A function to add an optional QR Code info CHIP object + /// # Arguments + /// * `tag` - one of the CHIP-Common Reserved Tags + /// * `data` - Data to add + pub fn add_optional_extension_data( + &mut self, + tag: u8, + data: QRCodeInfoType, + ) -> Result<(), Error> { + if !is_common_tag(tag) { + return Err(Error::InvalidArgument); + } + + self.optional_data + .insert(tag, OptionalQRCodeInfo { tag, data }); + Ok(()) + } + + pub fn get_all_optional_data(&self) -> &HashMap { + &self.optional_data + } + + pub fn add_serial_number(&mut self, serial_number: SerialNumber) -> Result<(), Error> { + match serial_number { + SerialNumber::String(serial_number) => self.add_optional_extension_data( + SERIAL_NUMBER_TAG, + QRCodeInfoType::String(serial_number), + ), + SerialNumber::UInt32(serial_number) => self.add_optional_extension_data( + SERIAL_NUMBER_TAG, + QRCodeInfoType::UInt32(serial_number), + ), + } + } + fn check_payload_common_constraints(&self) -> bool { // A version not equal to 0 would be invalid for v1 and would indicate new format (e.g. version 2) if self.version != 0 { @@ -133,6 +214,10 @@ impl<'data> QrCodeData<'data> { true } + + fn has_tlv(&self) -> bool { + !self.optional_data.is_empty() + } } #[repr(u8)] @@ -147,14 +232,67 @@ struct TlvData { data_length_in_bytes: u32, } -pub(super) fn payload_base38_representation(payload: &QrCodeData) -> Result { - let mut bits: [u8; TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES] = [0; TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES]; +pub(super) fn payload_base38_representation(payload: &QrSetupPayload) -> Result { + let (mut bits, tlv_data) = if payload.has_tlv() { + let buffer_size = estimate_buffer_size(payload)?; + ( + vec![0; buffer_size], + Some(TlvData { + data_length_in_bytes: buffer_size as u32, + }), + ) + } else { + (vec![0; TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES], None) + }; if !payload.is_valid() { return Err(Error::InvalidArgument); } - payload_base38_representation_with_tlv(payload, &mut bits, None) + payload_base38_representation_with_tlv(payload, &mut bits, tlv_data) +} + +fn estimate_buffer_size(payload: &QrSetupPayload) -> Result { + // Estimate the size of the needed buffer. + let mut estimate = 0; + + let data_item_size_estimate = |info: &QRCodeInfoType| { + // Each data item needs a control byte and a context tag. + let mut size: usize = 2; + + if let QRCodeInfoType::String(data) = info { + // We'll need to encode the string length and then the string data. + // Length is at most 8 bytes. + size += 8; + size += data.len(); + } else { + // Integer. Assume it might need up to 8 bytes, for simplicity. + size += 8; + } + + size + }; + + let vendor_data = payload.get_all_optional_data(); + vendor_data.values().for_each(|data| { + estimate += data_item_size_estimate(&data.data); + }); + + estimate = estimate_struct_overhead(estimate); + + if estimate > u32::MAX as usize { + return Err(Error::NoMemory); + } + + Ok(estimate) +} + +fn estimate_struct_overhead(first_field_size: usize) -> usize { + // Estimate 4 bytes of overhead per field. This can happen for a large + // octet string field: 1 byte control, 1 byte context tag, 2 bytes + // length. + // todo: recursive process other fields + first_field_size + 4 } pub(super) fn print_qr_code(qr_data: &str) { @@ -198,9 +336,9 @@ fn populate_bits( } fn payload_base38_representation_with_tlv( - payload: &QrCodeData, - bits: &mut [u8; TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES], - tlv_data: Option<&TlvData>, + payload: &QrSetupPayload, + bits: &mut [u8], + tlv_data: Option, ) -> Result { generate_bit_set(payload, bits, tlv_data)?; let base38_encoded = base38::encode(&*bits); @@ -208,9 +346,9 @@ fn payload_base38_representation_with_tlv( } fn generate_bit_set( - payload: &QrCodeData, - bits: &mut [u8; TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES], - tlv_data: Option<&TlvData>, + payload: &QrSetupPayload, + bits: &mut [u8], + tlv_data: Option, ) -> Result<(), Error> { let mut offset: usize = 0; let total_payload_size_in_bits = if let Some(tlv_data) = tlv_data { @@ -313,8 +451,18 @@ mod tests { }; let disc_cap = DiscoveryCapabilities::new(false, true, false); - let qr_code_data = QrCodeData::new(&dev_det, &comm_data, disc_cap); + let qr_code_data = QrSetupPayload::new(&dev_det, &comm_data, disc_cap); let data_str = payload_base38_representation(&qr_code_data).expect("Failed to encode"); assert_eq!(data_str, QR_CODE) } } + +/// Spec 5.1.4.1 Manufacture-specific tag numbers are in the range [0x80, 0xFF] +fn is_vendor_tag(tag: u8) -> bool { + !is_common_tag(tag) +} + +/// Spec 5.1.4.2 CHIPCommon tag numbers are in the range [0x00, 0x7F] +fn is_common_tag(tag: u8) -> bool { + tag < 0x80 +} From f39cff0bc24ba45904be927b628449669fbd3b0c Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 12 Jan 2023 15:42:21 +0100 Subject: [PATCH 20/30] Encode optional data fields --- matter/src/codec/base38.rs | 5 +- matter/src/pairing/qr.rs | 170 +++++++++++++++++++++++++++++++------ 2 files changed, 148 insertions(+), 27 deletions(-) diff --git a/matter/src/codec/base38.rs b/matter/src/codec/base38.rs index 8bea287..8beec25 100644 --- a/matter/src/codec/base38.rs +++ b/matter/src/codec/base38.rs @@ -20,8 +20,7 @@ const BASE38_CHARS: &str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-."; /// Encodes a byte array into a base38 string. -pub fn encode(bytes: &[u8]) -> String { - let length = bytes.len(); +pub fn encode(bytes: &[u8], length: usize) -> String { let mut offset = 0; let mut result = String::new(); @@ -75,6 +74,6 @@ mod tests { const DECODED: [u8; 11] = [ 0x88, 0xff, 0xa7, 0x91, 0x50, 0x40, 0x00, 0x47, 0x51, 0xdd, 0x02, ]; - assert_eq!(encode(&DECODED), ENCODED); + assert_eq!(encode(&DECODED, 11), ENCODED); } } diff --git a/matter/src/pairing/qr.rs b/matter/src/pairing/qr.rs index 1bca10b..3107e63 100644 --- a/matter/src/pairing/qr.rs +++ b/matter/src/pairing/qr.rs @@ -15,7 +15,12 @@ * limitations under the License. */ -use std::collections::HashMap; +use std::collections::BTreeMap; + +use crate::{ + tlv::{TLVWriter, TagType}, + utils::writebuf::WriteBuf, +}; use super::{ vendor_identifiers::{is_vendor_id_valid_operationally, VendorId}, @@ -75,7 +80,8 @@ pub struct QrSetupPayload<'data> { discovery_capabilities: DiscoveryCapabilities, dev_det: &'data BasicInfoConfig, comm_data: &'data CommissioningData, - optional_data: HashMap, + // we use a BTreeMap to keep the order of the optional data stable + optional_data: BTreeMap, } impl<'data> QrSetupPayload<'data> { @@ -92,7 +98,7 @@ impl<'data> QrSetupPayload<'data> { discovery_capabilities, dev_det, comm_data, - optional_data: HashMap::new(), + optional_data: BTreeMap::new(), } } @@ -145,7 +151,7 @@ impl<'data> QrSetupPayload<'data> { Ok(()) } - pub fn get_all_optional_data(&self) -> &HashMap { + pub fn get_all_optional_data(&self) -> &BTreeMap { &self.optional_data } @@ -229,7 +235,9 @@ pub enum CommissionningFlowType { } struct TlvData { - data_length_in_bytes: u32, + max_data_length_in_bytes: u32, + data_length_in_bytes: Option, + data: Option>, } pub(super) fn payload_base38_representation(payload: &QrSetupPayload) -> Result { @@ -238,7 +246,9 @@ pub(super) fn payload_base38_representation(payload: &QrSetupPayload) -> Result< ( vec![0; buffer_size], Some(TlvData { - data_length_in_bytes: buffer_size as u32, + max_data_length_in_bytes: buffer_size as u32, + data_length_in_bytes: None, + data: None, }), ) } else { @@ -264,7 +274,7 @@ fn estimate_buffer_size(payload: &QrSetupPayload) -> Result { // We'll need to encode the string length and then the string data. // Length is at most 8 bytes. size += 8; - size += data.len(); + size += data.as_bytes().len() } else { // Integer. Assume it might need up to 8 bytes, for simplicity. size += 8; @@ -291,7 +301,6 @@ fn estimate_struct_overhead(first_field_size: usize) -> usize { // Estimate 4 bytes of overhead per field. This can happen for a large // octet string field: 1 byte control, 1 byte context tag, 2 bytes // length. - // todo: recursive process other fields first_field_size + 4 } @@ -338,21 +347,63 @@ fn populate_bits( fn payload_base38_representation_with_tlv( payload: &QrSetupPayload, bits: &mut [u8], - tlv_data: Option, + mut tlv_data: Option, ) -> Result { - generate_bit_set(payload, bits, tlv_data)?; - let base38_encoded = base38::encode(&*bits); + if let Some(tlv_data) = tlv_data.as_mut() { + generate_tlv_from_optional_data(payload, tlv_data)?; + } + + let bytes_written = generate_bit_set(payload, bits, tlv_data)?; + let base38_encoded = base38::encode(&*bits, bytes_written); Ok(format!("MT:{}", base38_encoded)) } +fn generate_tlv_from_optional_data( + payload: &QrSetupPayload, + tlv_data: &mut TlvData, +) -> Result<(), Error> { + let size_needed = tlv_data.max_data_length_in_bytes as usize; + let mut tlv_buffer = vec![0u8; size_needed]; + let mut wb = WriteBuf::new(&mut tlv_buffer, size_needed); + let mut tw = TLVWriter::new(&mut wb); + + tw.start_struct(TagType::Anonymous)?; + let data = payload.get_all_optional_data(); + + for (tag, value) in data { + match &value.data { + QRCodeInfoType::String(data) => { + if data.len() > 256 { + tw.str16(TagType::Context(*tag), data.as_bytes())?; + } else { + tw.str8(TagType::Context(*tag), data.as_bytes())?; + } + } + // todo: check i32 -> u32?? + QRCodeInfoType::Int32(data) => tw.u32(TagType::Context(*tag), *data as u32)?, + // todo: check i64 -> u64?? + QRCodeInfoType::Int64(data) => tw.u64(TagType::Context(*tag), *data as u64)?, + QRCodeInfoType::UInt32(data) => tw.u32(TagType::Context(*tag), *data)?, + QRCodeInfoType::UInt64(data) => tw.u64(TagType::Context(*tag), *data)?, + } + } + + tw.end_container()?; + tlv_data.data_length_in_bytes = Some(tw.get_tail()); + tlv_data.data = Some(tlv_buffer); + + Ok(()) +} + fn generate_bit_set( payload: &QrSetupPayload, bits: &mut [u8], tlv_data: Option, -) -> Result<(), Error> { +) -> Result { let mut offset: usize = 0; - let total_payload_size_in_bits = if let Some(tlv_data) = tlv_data { - TOTAL_PAYLOAD_DATA_SIZE_IN_BITS + (tlv_data.data_length_in_bytes * 8) as usize + + let total_payload_size_in_bits = if let Some(tlv_data) = &tlv_data { + TOTAL_PAYLOAD_DATA_SIZE_IN_BITS + (tlv_data.data_length_in_bytes.unwrap_or_default() * 8) } else { TOTAL_PAYLOAD_DATA_SIZE_IN_BITS }; @@ -425,12 +476,48 @@ fn generate_bit_set( total_payload_size_in_bits, )?; - // todo: add tlv data - // ReturnErrorOnFailure(populateTLVBits(bits.data(), offset, tlvDataStart, tlvDataLengthInBytes, totalPayloadSizeInBits)); + if let Some(tlv_data) = tlv_data { + populate_tlv_bits(bits, &mut offset, tlv_data, total_payload_size_in_bits)?; + } + + let bytes_written = (offset + 7) / 8; + Ok(bytes_written) +} + +fn populate_tlv_bits( + bits: &mut [u8], + offset: &mut usize, + tlv_data: TlvData, + total_payload_size_in_bits: usize, +) -> Result<(), Error> { + if let (Some(data), Some(data_length_in_bytes)) = (tlv_data.data, tlv_data.data_length_in_bytes) + { + for pos in 0..data_length_in_bytes { + populate_bits( + bits, + offset, + data[pos] as u64, + 8, + total_payload_size_in_bits, + )?; + } + } else { + return Err(Error::InvalidArgument); + } Ok(()) } +/// Spec 5.1.4.1 Manufacture-specific tag numbers are in the range [0x80, 0xFF] +fn is_vendor_tag(tag: u8) -> bool { + !is_common_tag(tag) +} + +/// Spec 5.1.4.2 CHIPCommon tag numbers are in the range [0x00, 0x7F] +fn is_common_tag(tag: u8) -> bool { + tag < 0x80 +} + #[cfg(test)] mod tests { use super::*; @@ -455,14 +542,49 @@ mod tests { let data_str = payload_base38_representation(&qr_code_data).expect("Failed to encode"); assert_eq!(data_str, QR_CODE) } -} -/// Spec 5.1.4.1 Manufacture-specific tag numbers are in the range [0x80, 0xFF] -fn is_vendor_tag(tag: u8) -> bool { - !is_common_tag(tag) -} + #[test] + fn can_base38_encode_with_optional_data() { + // todo: this must be validated! + const QR_CODE: &str = "MT:YNJV7VSC00CMVH70V3P0-ISA0DK5N1K8SQ1RYCU1WET70.QT52B.E232XZE0O0"; + const OPTIONAL_DEFAULT_STRING_TAG: u8 = 0x82; // Vendor "test" tag + const OPTIONAL_DEFAULT_STRING_VALUE: &str = "myData"; -/// Spec 5.1.4.2 CHIPCommon tag numbers are in the range [0x00, 0x7F] -fn is_common_tag(tag: u8) -> bool { - tag < 0x80 + const OPTIONAL_DEFAULT_INT_TAG: u8 = 0x83; // Vendor "test" tag + const OPTIONAL_DEFAULT_INT_VALUE: u32 = 12; + + let comm_data = CommissioningData { + passwd: 34567890, + discriminator: 2976, + ..Default::default() + }; + let dev_det = BasicInfoConfig { + vid: 9050, + pid: 65279, + ..Default::default() + }; + + let disc_cap = DiscoveryCapabilities::new(false, true, false); + let mut qr_code_data = QrSetupPayload::new(&dev_det, &comm_data, disc_cap); + qr_code_data + .add_serial_number(SerialNumber::String("123456789".to_string())) + .expect("Failed to add serial number"); + + qr_code_data + .add_optional_vendor_data( + OPTIONAL_DEFAULT_STRING_TAG, + QRCodeInfoType::String(OPTIONAL_DEFAULT_STRING_VALUE.to_string()), + ) + .expect("Failed to add optional data"); + + qr_code_data + .add_optional_vendor_data( + OPTIONAL_DEFAULT_INT_TAG, + QRCodeInfoType::UInt32(OPTIONAL_DEFAULT_INT_VALUE), + ) + .expect("Failed to add optional data"); + + let data_str = payload_base38_representation(&qr_code_data).expect("Failed to encode"); + assert_eq!(data_str, QR_CODE) + } } From 0569cfc771f73392013b438a4a89cc53f70e259e Mon Sep 17 00:00:00 2001 From: Marcel Date: Fri, 13 Jan 2023 11:40:16 +0100 Subject: [PATCH 21/30] More idiomatic logic --- matter/src/codec/base38.rs | 5 +++-- matter/src/pairing/qr.rs | 10 ++-------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/matter/src/codec/base38.rs b/matter/src/codec/base38.rs index 8beec25..7536ca7 100644 --- a/matter/src/codec/base38.rs +++ b/matter/src/codec/base38.rs @@ -55,10 +55,11 @@ pub fn encode(bytes: &[u8], length: usize) -> String { fn encode_base38(mut value: u32, char_count: u8) -> String { let mut result = String::new(); + let chars = BASE38_CHARS.chars(); for _ in 0..char_count { - let mut chars = BASE38_CHARS.chars(); + let mut use_chars = chars.clone(); let remainder = value % 38; - result.push(chars.nth(remainder as usize).unwrap()); + result.push(use_chars.nth(remainder as usize).unwrap()); value = (value - remainder) / 38; } result diff --git a/matter/src/pairing/qr.rs b/matter/src/pairing/qr.rs index 3107e63..bc86e90 100644 --- a/matter/src/pairing/qr.rs +++ b/matter/src/pairing/qr.rs @@ -492,14 +492,8 @@ fn populate_tlv_bits( ) -> Result<(), Error> { if let (Some(data), Some(data_length_in_bytes)) = (tlv_data.data, tlv_data.data_length_in_bytes) { - for pos in 0..data_length_in_bytes { - populate_bits( - bits, - offset, - data[pos] as u64, - 8, - total_payload_size_in_bits, - )?; + for b in data.iter().take(data_length_in_bytes) { + populate_bits(bits, offset, *b as u64, 8, total_payload_size_in_bits)?; } } else { return Err(Error::InvalidArgument); From 563e0cbd9b57ed4f204513bbc6d6ee12c4c78bfa Mon Sep 17 00:00:00 2001 From: Marcel Date: Fri, 13 Jan 2023 12:12:57 +0100 Subject: [PATCH 22/30] Ditch the chars() iterator --- matter/src/codec/base38.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/matter/src/codec/base38.rs b/matter/src/codec/base38.rs index 7536ca7..975c5c9 100644 --- a/matter/src/codec/base38.rs +++ b/matter/src/codec/base38.rs @@ -17,7 +17,10 @@ //! Base38 encoding functions. -const BASE38_CHARS: &str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-."; +const BASE38_CHARS: [char; 38] = [ + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', + 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '-', '.', +]; /// Encodes a byte array into a base38 string. pub fn encode(bytes: &[u8], length: usize) -> String { @@ -55,11 +58,9 @@ pub fn encode(bytes: &[u8], length: usize) -> String { fn encode_base38(mut value: u32, char_count: u8) -> String { let mut result = String::new(); - let chars = BASE38_CHARS.chars(); for _ in 0..char_count { - let mut use_chars = chars.clone(); let remainder = value % 38; - result.push(use_chars.nth(remainder as usize).unwrap()); + result.push(BASE38_CHARS[remainder as usize]); value = (value - remainder) / 38; } result From 8605cf6e4167ee6a4610b14f63fa6025ac694303 Mon Sep 17 00:00:00 2001 From: Marcel Date: Fri, 13 Jan 2023 12:56:56 +0100 Subject: [PATCH 23/30] Base38 decoding --- matter/src/codec/base38.rs | 138 +++++++++++++++++++++++++++++++++++-- 1 file changed, 132 insertions(+), 6 deletions(-) diff --git a/matter/src/codec/base38.rs b/matter/src/codec/base38.rs index 975c5c9..ddaf199 100644 --- a/matter/src/codec/base38.rs +++ b/matter/src/codec/base38.rs @@ -15,14 +15,72 @@ * limitations under the License. */ -//! Base38 encoding functions. +//! Base38 encoding and decoding functions. + +use crate::error::Error; const BASE38_CHARS: [char; 38] = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '-', '.', ]; -/// Encodes a byte array into a base38 string. +const UNUSED: u8 = 255; + +// map of base38 charater to numeric value +// subtract 45 from the character, then index into this array, if possible +const DECODE_BASE38: [u8; 46] = [ + 36, // '-', =45 + 37, // '.', =46 + UNUSED, // '/', =47 + 0, // '0', =48 + 1, // '1', =49 + 2, // '2', =50 + 3, // '3', =51 + 4, // '4', =52 + 5, // '5', =53 + 6, // '6', =54 + 7, // '7', =55 + 8, // '8', =56 + 9, // '9', =57 + UNUSED, // ':', =58 + UNUSED, // ';', =59 + UNUSED, // '<', =50 + UNUSED, // '=', =61 + UNUSED, // '>', =62 + UNUSED, // '?', =63 + UNUSED, // '@', =64 + 10, // 'A', =65 + 11, // 'B', =66 + 12, // 'C', =67 + 13, // 'D', =68 + 14, // 'E', =69 + 15, // 'F', =70 + 16, // 'G', =71 + 17, // 'H', =72 + 18, // 'I', =73 + 19, // 'J', =74 + 20, // 'K', =75 + 21, // 'L', =76 + 22, // 'M', =77 + 23, // 'N', =78 + 24, // 'O', =79 + 25, // 'P', =80 + 26, // 'Q', =81 + 27, // 'R', =82 + 28, // 'S', =83 + 29, // 'T', =84 + 30, // 'U', =85 + 31, // 'V', =86 + 32, // 'W', =87 + 33, // 'X', =88 + 34, // 'Y', =89 + 35, // 'Z', =90 +]; + +const BASE38_CHARACTERS_NEEDED_IN_NBYTES_CHUNK: [u8; 3] = [2, 4, 5]; +const RADIX: u32 = BASE38_CHARS.len() as u32; + +/// Encode a byte array into a base38 string. pub fn encode(bytes: &[u8], length: usize) -> String { let mut offset = 0; let mut result = String::new(); @@ -66,16 +124,84 @@ fn encode_base38(mut value: u32, char_count: u8) -> String { result } +/// Decode a base38-encoded string into a byte slice +pub fn decode(base38_str: &str) -> Result, Error> { + let mut result = Vec::new(); + let mut base38_characters_number: usize = base38_str.len(); + let mut decoded_base38_characters: usize = 0; + + while base38_characters_number > 0 { + let base38_characters_in_chunk: usize; + let bytes_in_decoded_chunk: usize; + + if base38_characters_number >= BASE38_CHARACTERS_NEEDED_IN_NBYTES_CHUNK[2] as usize { + base38_characters_in_chunk = BASE38_CHARACTERS_NEEDED_IN_NBYTES_CHUNK[2] as usize; + bytes_in_decoded_chunk = 3; + } else if base38_characters_number == BASE38_CHARACTERS_NEEDED_IN_NBYTES_CHUNK[1] as usize { + base38_characters_in_chunk = BASE38_CHARACTERS_NEEDED_IN_NBYTES_CHUNK[1] as usize; + bytes_in_decoded_chunk = 2; + } else if base38_characters_number == BASE38_CHARACTERS_NEEDED_IN_NBYTES_CHUNK[0] as usize { + base38_characters_in_chunk = BASE38_CHARACTERS_NEEDED_IN_NBYTES_CHUNK[0] as usize; + bytes_in_decoded_chunk = 1; + } else { + return Err(Error::InvalidData); + } + + let mut value = 0u32; + + for i in (1..=base38_characters_in_chunk).rev() { + let mut base38_chars = base38_str.chars(); + let v = decode_char(base38_chars.nth(decoded_base38_characters + i - 1).unwrap())?; + + value = value * RADIX + v as u32; + } + + decoded_base38_characters += base38_characters_in_chunk; + base38_characters_number -= base38_characters_in_chunk; + + for _i in 0..bytes_in_decoded_chunk { + result.push(value as u8); + value >>= 8; + } + + if value > 0 { + // encoded value is too big to represent a correct chunk of size 1, 2 or 3 bytes + return Err(Error::InvalidArgument); + } + } + + Ok(result) +} + +fn decode_char(c: char) -> Result { + let c = c as u8; + if !(45..=90).contains(&c) { + return Err(Error::InvalidData); + } + + let c = DECODE_BASE38[c as usize - 45]; + if c == UNUSED { + return Err(Error::InvalidData); + } + + Ok(c) +} + #[cfg(test)] mod tests { use super::*; + const ENCODED: &str = "-MOA57ZU02IT2L2BJ00"; + const DECODED: [u8; 11] = [ + 0x88, 0xff, 0xa7, 0x91, 0x50, 0x40, 0x00, 0x47, 0x51, 0xdd, 0x02, + ]; #[test] fn can_base38_encode() { - const ENCODED: &str = "-MOA57ZU02IT2L2BJ00"; - const DECODED: [u8; 11] = [ - 0x88, 0xff, 0xa7, 0x91, 0x50, 0x40, 0x00, 0x47, 0x51, 0xdd, 0x02, - ]; assert_eq!(encode(&DECODED, 11), ENCODED); } + + #[test] + fn can_base38_decode() { + assert_eq!(decode(ENCODED).expect("can not decode base38"), DECODED); + } } From 8071a7b931c7ee50b0b12a76df361cf3071b0c75 Mon Sep 17 00:00:00 2001 From: Marcel Date: Fri, 13 Jan 2023 13:05:22 +0100 Subject: [PATCH 24/30] Optional length when encoding --- matter/src/codec/base38.rs | 22 ++++++++++++++++++++-- matter/src/pairing/qr.rs | 2 +- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/matter/src/codec/base38.rs b/matter/src/codec/base38.rs index ddaf199..d4c69c1 100644 --- a/matter/src/codec/base38.rs +++ b/matter/src/codec/base38.rs @@ -81,10 +81,19 @@ const BASE38_CHARACTERS_NEEDED_IN_NBYTES_CHUNK: [u8; 3] = [2, 4, 5]; const RADIX: u32 = BASE38_CHARS.len() as u32; /// Encode a byte array into a base38 string. -pub fn encode(bytes: &[u8], length: usize) -> String { +/// +/// # Arguments +/// * `bytes` - byte array to encode +/// * `length` - optional length of the byte array to encode. If not specified, the entire byte array is encoded. +pub fn encode(bytes: &[u8], length: Option) -> String { let mut offset = 0; let mut result = String::new(); + // if length is specified, use it, otherwise use the length of the byte array + // if length is specified but is greater than the length of the byte array, use the length of the byte array + let b_len = bytes.len(); + let length = length.map(|l| l.min(b_len)).unwrap_or(b_len); + while offset < length { let remaining = length - offset; match remaining.cmp(&2) { @@ -125,6 +134,11 @@ fn encode_base38(mut value: u32, char_count: u8) -> String { } /// Decode a base38-encoded string into a byte slice +/// +/// # Arguments +/// * `base38_str` - base38-encoded string to decode +/// +/// Fails if the string contains invalid characters pub fn decode(base38_str: &str) -> Result, Error> { let mut result = Vec::new(); let mut base38_characters_number: usize = base38_str.len(); @@ -197,7 +211,11 @@ mod tests { #[test] fn can_base38_encode() { - assert_eq!(encode(&DECODED, 11), ENCODED); + assert_eq!(encode(&DECODED, None), ENCODED); + assert_eq!(encode(&DECODED, Some(11)), ENCODED); + + // length is greater than the length of the byte array + assert_eq!(encode(&DECODED, Some(12)), ENCODED); } #[test] diff --git a/matter/src/pairing/qr.rs b/matter/src/pairing/qr.rs index bc86e90..01c8fd0 100644 --- a/matter/src/pairing/qr.rs +++ b/matter/src/pairing/qr.rs @@ -354,7 +354,7 @@ fn payload_base38_representation_with_tlv( } let bytes_written = generate_bit_set(payload, bits, tlv_data)?; - let base38_encoded = base38::encode(&*bits, bytes_written); + let base38_encoded = base38::encode(&*bits, Some(bytes_written)); Ok(format!("MT:{}", base38_encoded)) } From c8cc5b7ea1531c5e913471d8edbcac4880703dc1 Mon Sep 17 00:00:00 2001 From: Marcel Date: Fri, 13 Jan 2023 17:38:39 +0100 Subject: [PATCH 25/30] Fix buffer and write strings as utf8 --- matter/src/pairing/qr.rs | 83 +++++++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 30 deletions(-) diff --git a/matter/src/pairing/qr.rs b/matter/src/pairing/qr.rs index 01c8fd0..dce8229 100644 --- a/matter/src/pairing/qr.rs +++ b/matter/src/pairing/qr.rs @@ -263,8 +263,8 @@ pub(super) fn payload_base38_representation(payload: &QrSetupPayload) -> Result< } fn estimate_buffer_size(payload: &QrSetupPayload) -> Result { - // Estimate the size of the needed buffer. - let mut estimate = 0; + // Estimate the size of the needed buffer; initialize with the size of the standard payload. + let mut estimate = TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES; let data_item_size_estimate = |info: &QRCodeInfoType| { // Each data item needs a control byte and a context tag. @@ -301,7 +301,9 @@ fn estimate_struct_overhead(first_field_size: usize) -> usize { // Estimate 4 bytes of overhead per field. This can happen for a large // octet string field: 1 byte control, 1 byte context tag, 2 bytes // length. - first_field_size + 4 + // + // The struct itself has a control byte and an end-of-struct marker. + first_field_size + 4 + 2 } pub(super) fn print_qr_code(qr_data: &str) { @@ -371,14 +373,9 @@ fn generate_tlv_from_optional_data( let data = payload.get_all_optional_data(); for (tag, value) in data { + println!("tag: {tag:?}"); match &value.data { - QRCodeInfoType::String(data) => { - if data.len() > 256 { - tw.str16(TagType::Context(*tag), data.as_bytes())?; - } else { - tw.str8(TagType::Context(*tag), data.as_bytes())?; - } - } + QRCodeInfoType::String(data) => tw.utf8(TagType::Context(*tag), data.as_bytes())?, // todo: check i32 -> u32?? QRCodeInfoType::Int32(data) => tw.u32(TagType::Context(*tag), *data as u32)?, // todo: check i64 -> u64?? @@ -409,6 +406,7 @@ fn generate_bit_set( }; if bits.len() * 8 < total_payload_size_in_bits { + println!("{:?} vs {total_payload_size_in_bits}", bits.len() * 8); return Err(Error::BufferTooSmall); }; @@ -538,30 +536,54 @@ mod tests { } #[test] - fn can_base38_encode_with_optional_data() { - // todo: this must be validated! - const QR_CODE: &str = "MT:YNJV7VSC00CMVH70V3P0-ISA0DK5N1K8SQ1RYCU1WET70.QT52B.E232XZE0O0"; - const OPTIONAL_DEFAULT_STRING_TAG: u8 = 0x82; // Vendor "test" tag - const OPTIONAL_DEFAULT_STRING_VALUE: &str = "myData"; - - const OPTIONAL_DEFAULT_INT_TAG: u8 = 0x83; // Vendor "test" tag - const OPTIONAL_DEFAULT_INT_VALUE: u32 = 12; + fn can_base38_encode_with_vendor_data() { + const QR_CODE: &str = "MT:-24J0AFN00KA064IJ3P0IXZB0DK5N1K8SQ1RYCU1-A40"; let comm_data = CommissioningData { - passwd: 34567890, - discriminator: 2976, + passwd: 20202021, + discriminator: 3840, ..Default::default() }; let dev_det = BasicInfoConfig { - vid: 9050, - pid: 65279, + vid: 65521, + pid: 32769, ..Default::default() }; - let disc_cap = DiscoveryCapabilities::new(false, true, false); + let disc_cap = DiscoveryCapabilities::new(true, false, false); let mut qr_code_data = QrSetupPayload::new(&dev_det, &comm_data, disc_cap); qr_code_data - .add_serial_number(SerialNumber::String("123456789".to_string())) + .add_serial_number(SerialNumber::String("1234567890".to_string())) + .expect("Failed to add serial number"); + + let data_str = payload_base38_representation(&qr_code_data).expect("Failed to encode"); + assert_eq!(data_str, QR_CODE) + } + + #[test] + fn can_base38_encode_with_optional_data() { + const QR_CODE: &str = "MT:-24J0AFN00KA064IJ3P0IXZB0DK5N1K8SQ1RYCU1UXH34YY0V3KY.O39C40"; + const OPTIONAL_DEFAULT_STRING_TAG: u8 = 0x82; // Vendor "test" tag + const OPTIONAL_DEFAULT_STRING_VALUE: &str = "myData"; + + // const OPTIONAL_DEFAULT_INT_TAG: u8 = 0x83; // Vendor "test" tag + // const OPTIONAL_DEFAULT_INT_VALUE: u32 = 12; + + let comm_data = CommissioningData { + passwd: 20202021, + discriminator: 3840, + ..Default::default() + }; + let dev_det = BasicInfoConfig { + vid: 65521, + pid: 32769, + ..Default::default() + }; + + let disc_cap = DiscoveryCapabilities::new(true, false, false); + let mut qr_code_data = QrSetupPayload::new(&dev_det, &comm_data, disc_cap); + qr_code_data + .add_serial_number(SerialNumber::String("1234567890".to_string())) .expect("Failed to add serial number"); qr_code_data @@ -571,12 +593,13 @@ mod tests { ) .expect("Failed to add optional data"); - qr_code_data - .add_optional_vendor_data( - OPTIONAL_DEFAULT_INT_TAG, - QRCodeInfoType::UInt32(OPTIONAL_DEFAULT_INT_VALUE), - ) - .expect("Failed to add optional data"); + // todo: check why u32 is not accepted by 'chip-tool payload parse-setup-payload' + // qr_code_data + // .add_optional_vendor_data( + // OPTIONAL_DEFAULT_INT_TAG, + // QRCodeInfoType::UInt32(OPTIONAL_DEFAULT_INT_VALUE), + // ) + // .expect("Failed to add optional data"); let data_str = payload_base38_representation(&qr_code_data).expect("Failed to encode"); assert_eq!(data_str, QR_CODE) From 23b4473eba757849499db9674b6c6a392e41e4dc Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 14 Jan 2023 11:01:08 +0100 Subject: [PATCH 26/30] Support writing of signed int --- matter/src/pairing/qr.rs | 30 ++++++++++++++---------------- matter/src/tlv/writer.rs | 33 +++++++++++++++++++++++++++++++++ matter/src/utils/writebuf.rs | 17 +++++++++++++++++ 3 files changed, 64 insertions(+), 16 deletions(-) diff --git a/matter/src/pairing/qr.rs b/matter/src/pairing/qr.rs index dce8229..7dbf47b 100644 --- a/matter/src/pairing/qr.rs +++ b/matter/src/pairing/qr.rs @@ -373,13 +373,10 @@ fn generate_tlv_from_optional_data( let data = payload.get_all_optional_data(); for (tag, value) in data { - println!("tag: {tag:?}"); match &value.data { QRCodeInfoType::String(data) => tw.utf8(TagType::Context(*tag), data.as_bytes())?, - // todo: check i32 -> u32?? - QRCodeInfoType::Int32(data) => tw.u32(TagType::Context(*tag), *data as u32)?, - // todo: check i64 -> u64?? - QRCodeInfoType::Int64(data) => tw.u64(TagType::Context(*tag), *data as u64)?, + QRCodeInfoType::Int32(data) => tw.i32(TagType::Context(*tag), *data)?, + QRCodeInfoType::Int64(data) => tw.i64(TagType::Context(*tag), *data)?, QRCodeInfoType::UInt32(data) => tw.u32(TagType::Context(*tag), *data)?, QRCodeInfoType::UInt64(data) => tw.u64(TagType::Context(*tag), *data)?, } @@ -406,7 +403,6 @@ fn generate_bit_set( }; if bits.len() * 8 < total_payload_size_in_bits { - println!("{:?} vs {total_payload_size_in_bits}", bits.len() * 8); return Err(Error::BufferTooSmall); }; @@ -562,12 +558,13 @@ mod tests { #[test] fn can_base38_encode_with_optional_data() { - const QR_CODE: &str = "MT:-24J0AFN00KA064IJ3P0IXZB0DK5N1K8SQ1RYCU1UXH34YY0V3KY.O39C40"; + const QR_CODE: &str = + "MT:-24J0AFN00KA064IJ3P0IXZB0DK5N1K8SQ1RYCU1UXH34YY0V3KY.O3DKN440F710Q940"; const OPTIONAL_DEFAULT_STRING_TAG: u8 = 0x82; // Vendor "test" tag const OPTIONAL_DEFAULT_STRING_VALUE: &str = "myData"; - // const OPTIONAL_DEFAULT_INT_TAG: u8 = 0x83; // Vendor "test" tag - // const OPTIONAL_DEFAULT_INT_VALUE: u32 = 12; + const OPTIONAL_DEFAULT_INT_TAG: u8 = 0x83; // Vendor "test" tag + const OPTIONAL_DEFAULT_INT_VALUE: i32 = 65550; let comm_data = CommissioningData { passwd: 20202021, @@ -593,13 +590,14 @@ mod tests { ) .expect("Failed to add optional data"); - // todo: check why u32 is not accepted by 'chip-tool payload parse-setup-payload' - // qr_code_data - // .add_optional_vendor_data( - // OPTIONAL_DEFAULT_INT_TAG, - // QRCodeInfoType::UInt32(OPTIONAL_DEFAULT_INT_VALUE), - // ) - // .expect("Failed to add optional data"); + // todo: check why unsigned ints are not accepted by 'chip-tool payload parse-setup-payload' + + qr_code_data + .add_optional_vendor_data( + OPTIONAL_DEFAULT_INT_TAG, + QRCodeInfoType::Int32(OPTIONAL_DEFAULT_INT_VALUE), + ) + .expect("Failed to add optional data"); let data_str = payload_base38_representation(&qr_code_data).expect("Failed to encode"); assert_eq!(data_str, QR_CODE) diff --git a/matter/src/tlv/writer.rs b/matter/src/tlv/writer.rs index 9cefb0f..7459aa3 100644 --- a/matter/src/tlv/writer.rs +++ b/matter/src/tlv/writer.rs @@ -95,6 +95,15 @@ impl<'a, 'b> TLVWriter<'a, 'b> { self.buf.le_u8(data) } + pub fn i16(&mut self, tag_type: TagType, data: i16) -> Result<(), Error> { + if data >= i8::MIN as i16 && data <= i8::MAX as i16 { + self.i8(tag_type, data as i8) + } else { + self.put_control_tag(tag_type, WriteElementType::S16)?; + self.buf.le_i16(data) + } + } + pub fn u16(&mut self, tag_type: TagType, data: u16) -> Result<(), Error> { if data <= 0xff { self.u8(tag_type, data as u8) @@ -104,6 +113,17 @@ impl<'a, 'b> TLVWriter<'a, 'b> { } } + pub fn i32(&mut self, tag_type: TagType, data: i32) -> Result<(), Error> { + if data >= i8::MIN as i32 && data <= i8::MAX as i32 { + self.i8(tag_type, data as i8) + } else if data >= i16::MIN as i32 && data <= i16::MAX as i32 { + self.i16(tag_type, data as i16) + } else { + self.put_control_tag(tag_type, WriteElementType::S32)?; + self.buf.le_i32(data) + } + } + pub fn u32(&mut self, tag_type: TagType, data: u32) -> Result<(), Error> { if data <= 0xff { self.u8(tag_type, data as u8) @@ -115,6 +135,19 @@ impl<'a, 'b> TLVWriter<'a, 'b> { } } + pub fn i64(&mut self, tag_type: TagType, data: i64) -> Result<(), Error> { + if data >= i8::MIN as i64 && data <= i8::MAX as i64 { + self.i8(tag_type, data as i8) + } else if data >= i16::MIN as i64 && data <= i16::MAX as i64 { + self.i16(tag_type, data as i16) + } else if data >= i32::MIN as i64 && data <= i32::MAX as i64 { + self.i32(tag_type, data as i32) + } else { + self.put_control_tag(tag_type, WriteElementType::S64)?; + self.buf.le_i64(data) + } + } + pub fn u64(&mut self, tag_type: TagType, data: u64) -> Result<(), Error> { if data <= 0xff { self.u8(tag_type, data as u8) diff --git a/matter/src/utils/writebuf.rs b/matter/src/utils/writebuf.rs index b82bb02..00c5e88 100644 --- a/matter/src/utils/writebuf.rs +++ b/matter/src/utils/writebuf.rs @@ -131,6 +131,11 @@ impl<'a> WriteBuf<'a> { LittleEndian::write_u16(&mut x.buf[x.end..], data); }) } + pub fn le_i16(&mut self, data: i16) -> Result<(), Error> { + self.append_with(2, |x| { + LittleEndian::write_i16(&mut x.buf[x.end..], data); + }) + } pub fn le_u32(&mut self, data: u32) -> Result<(), Error> { self.append_with(4, |x| { @@ -138,12 +143,24 @@ impl<'a> WriteBuf<'a> { }) } + pub fn le_i32(&mut self, data: i32) -> Result<(), Error> { + self.append_with(4, |x| { + LittleEndian::write_i32(&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_i64(&mut self, data: i64) -> Result<(), Error> { + self.append_with(8, |x| { + LittleEndian::write_i64(&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); From 9364235f77669538c723e08a38d7b2cb1dd30775 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 14 Jan 2023 16:00:37 +0100 Subject: [PATCH 27/30] Merged upstream --- matter/src/core.rs | 2 +- matter/src/data_model/cluster_basic_information.rs | 4 ++-- matter/tests/common/im_engine.rs | 9 +-------- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/matter/src/core.rs b/matter/src/core.rs index 0161fbe..68c07f4 100644 --- a/matter/src/core.rs +++ b/matter/src/core.rs @@ -61,7 +61,7 @@ impl Matter { let mdns = Mdns::get()?; mdns.set_values(dev_det.vid, dev_det.pid, &dev_det.device_name); - print_pairing_code_and_qr(dev_det, &dev_comm, DiscoveryCapabilities::default()); + print_pairing_code_and_qr(&dev_det, &dev_comm, DiscoveryCapabilities::default()); let fabric_mgr = Arc::new(FabricMgr::new()?); let acl_mgr = Arc::new(AclMgr::new()?); diff --git a/matter/src/data_model/cluster_basic_information.rs b/matter/src/data_model/cluster_basic_information.rs index 164d11c..eedd826 100644 --- a/matter/src/data_model/cluster_basic_information.rs +++ b/matter/src/data_model/cluster_basic_information.rs @@ -38,6 +38,8 @@ pub struct BasicInfoConfig { pub hw_ver: u16, pub sw_ver: u32, pub serial_no: String, + /// Device name; up to 32 characters + pub device_name: String, } fn attr_dm_rev_new() -> Result { @@ -47,8 +49,6 @@ fn attr_dm_rev_new() -> Result { Access::RV, Quality::FIXED, ) - /// Device name; up to 32 characters - pub device_name: String, } fn attr_vid_new(vid: u16) -> Result { diff --git a/matter/tests/common/im_engine.rs b/matter/tests/common/im_engine.rs index 994bbaa..2ee55c8 100644 --- a/matter/tests/common/im_engine.rs +++ b/matter/tests/common/im_engine.rs @@ -107,14 +107,7 @@ impl ImEngine { // 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(), - pase_mgr, - ) - .unwrap(); + let dm = DataModel::new(dev_det, dev_att, fabric_mgr, acl_mgr.clone(), pase_mgr).unwrap(); { let mut d = dm.node.write().unwrap(); From 9ba0e017afe2ce4439fa5106aea99a54eeca73a6 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 14 Jan 2023 16:19:03 +0100 Subject: [PATCH 28/30] Clean-up --- matter/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/matter/src/lib.rs b/matter/src/lib.rs index 06c6bd2..4ca7e5a 100644 --- a/matter/src/lib.rs +++ b/matter/src/lib.rs @@ -41,7 +41,6 @@ //! let comm_data = CommissioningData { //! verifier: VerifierData::new_with_pw(123456), //! discriminator: 250, -//! //! }; //! //! /// The basic information about this device From e4fd1f51943f6e64ca80023893b1c7bba6e38665 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 14 Jan 2023 16:19:13 +0100 Subject: [PATCH 29/30] Compute qr version --- matter/src/pairing/qr.rs | 41 +++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/matter/src/pairing/qr.rs b/matter/src/pairing/qr.rs index 9e10a3d..0a3509d 100644 --- a/matter/src/pairing/qr.rs +++ b/matter/src/pairing/qr.rs @@ -92,14 +92,20 @@ impl<'data> QrSetupPayload<'data> { ) -> Self { const DEFAULT_VERSION: u8 = 0; - QrSetupPayload { + let mut result = QrSetupPayload { version: DEFAULT_VERSION, flow_type: CommissionningFlowType::Standard, discovery_capabilities, dev_det, comm_data, optional_data: BTreeMap::new(), + }; + + if !dev_det.serial_no.is_empty() { + result.add_serial_number(SerialNumber::String(dev_det.serial_no.clone())); } + + result } fn is_valid(&self) -> bool { @@ -157,7 +163,7 @@ impl<'data> QrSetupPayload<'data> { &self.optional_data } - pub fn add_serial_number(&mut self, serial_number: SerialNumber) -> Result<(), Error> { + pub fn add_serial_number(&mut self, serial_number: SerialNumber) { match serial_number { SerialNumber::String(serial_number) => self.add_optional_extension_data( SERIAL_NUMBER_TAG, @@ -168,6 +174,7 @@ impl<'data> QrSetupPayload<'data> { QRCodeInfoType::UInt32(serial_number), ), } + .expect("can not add serial number"); } fn check_payload_common_constraints(&self) -> bool { @@ -311,7 +318,9 @@ fn estimate_struct_overhead(first_field_size: usize) -> usize { } pub(super) fn print_qr_code(qr_data: &str) { - let code = QrCode::with_version(qr_data, Version::Normal(2), qrcode::EcLevel::M).unwrap(); + let needed_version = compute_qr_version(qr_data); + let code = + QrCode::with_version(qr_data, Version::Normal(needed_version), qrcode::EcLevel::M).unwrap(); let image = code .render::() .dark_color(unicode::Dense1x2::Light) @@ -320,6 +329,15 @@ pub(super) fn print_qr_code(qr_data: &str) { info!("\n{}", image); } +fn compute_qr_version(qr_data: &str) -> i16 { + match qr_data.len() { + 0..=38 => 2, + 39..=61 => 3, + 62..=90 => 4, + _ => 5, + } +} + fn populate_bits( bits: &mut [u8], offset: &mut usize, @@ -543,22 +561,18 @@ mod tests { const QR_CODE: &str = "MT:-24J0AFN00KA064IJ3P0IXZB0DK5N1K8SQ1RYCU1-A40"; let comm_data = CommissioningData { - passwd: 20202021, + verifier: VerifierData::new_with_pw(20202021), discriminator: 3840, - ..Default::default() }; let dev_det = BasicInfoConfig { vid: 65521, pid: 32769, + serial_no: "1234567890".to_string(), ..Default::default() }; let disc_cap = DiscoveryCapabilities::new(true, false, false); - let mut qr_code_data = QrSetupPayload::new(&dev_det, &comm_data, disc_cap); - qr_code_data - .add_serial_number(SerialNumber::String("1234567890".to_string())) - .expect("Failed to add serial number"); - + let qr_code_data = QrSetupPayload::new(&dev_det, &comm_data, disc_cap); let data_str = payload_base38_representation(&qr_code_data).expect("Failed to encode"); assert_eq!(data_str, QR_CODE) } @@ -574,21 +588,18 @@ mod tests { const OPTIONAL_DEFAULT_INT_VALUE: i32 = 65550; let comm_data = CommissioningData { - passwd: 20202021, + verifier: VerifierData::new_with_pw(20202021), discriminator: 3840, - ..Default::default() }; let dev_det = BasicInfoConfig { vid: 65521, pid: 32769, + serial_no: "1234567890".to_string(), ..Default::default() }; let disc_cap = DiscoveryCapabilities::new(true, false, false); let mut qr_code_data = QrSetupPayload::new(&dev_det, &comm_data, disc_cap); - qr_code_data - .add_serial_number(SerialNumber::String("1234567890".to_string())) - .expect("Failed to add serial number"); qr_code_data .add_optional_vendor_data( From 10b734da7d174c5f7150c6402549be2d92cf364d Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 15 Jan 2023 11:52:54 +0100 Subject: [PATCH 30/30] Fixed suggestions --- matter/src/mdns.rs | 1 - matter/src/pairing/vendor_identifiers.rs | 6 ------ 2 files changed, 7 deletions(-) diff --git a/matter/src/mdns.rs b/matter/src/mdns.rs index 53d7a35..f28bea0 100644 --- a/matter/src/mdns.rs +++ b/matter/src/mdns.rs @@ -102,7 +102,6 @@ impl Mdns { ["VP", &format!("{}+{}", inner.vid, inner.pid)], ["SII", "5000"], /* Sleepy Idle Interval */ ["SAI", "300"], /* Sleepy Active Interval */ - ["T", "1"], /* TCP supported */ ["PH", "33"], /* Pairing Hint */ ["PI", ""], /* Pairing Instruction */ ]; diff --git a/matter/src/pairing/vendor_identifiers.rs b/matter/src/pairing/vendor_identifiers.rs index 1de0482..62c07ac 100644 --- a/matter/src/pairing/vendor_identifiers.rs +++ b/matter/src/pairing/vendor_identifiers.rs @@ -1,13 +1,7 @@ #[repr(u16)] pub enum VendorId { CommonOrUnspecified = 0x0000, - Apple = 0x1349, - Google = 0x6006, - TestVendor1 = 0xFFF1, - TestVendor2 = 0xFFF2, - TestVendor3 = 0xFFF3, TestVendor4 = 0xFFF4, - NotSpecified = 0xFFFF, } pub fn is_vendor_id_valid_operationally(vendor_id: u16) -> bool {