From b74ae4ff01d55f12b31ea7a5b3f407ed4e3cc0b7 Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 12 Jan 2023 13:13:50 +0100 Subject: [PATCH] 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 +}