From f39cff0bc24ba45904be927b628449669fbd3b0c Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 12 Jan 2023 15:42:21 +0100 Subject: [PATCH] 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) + } }