Prep for optional TLV data

This commit is contained in:
Marcel 2023-01-12 13:13:50 +01:00
parent 0f0b87a369
commit b74ae4ff01
3 changed files with 164 additions and 15 deletions

View file

@ -39,6 +39,7 @@ pub enum Error {
NoHandler, NoHandler,
NoNetworkInterface, NoNetworkInterface,
NoNodeId, NoNodeId,
NoMemory,
NoSession, NoSession,
NoSpace, NoSpace,
NoSpaceAckTable, NoSpaceAckTable,

View file

@ -32,7 +32,7 @@ use crate::{
use self::{ use self::{
code::{compute_pairing_code, pretty_print_pairing_code}, 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 { pub struct DiscoveryCapabilities {
@ -88,7 +88,7 @@ pub fn print_pairing_code_and_qr(
discovery_capabilities: DiscoveryCapabilities, discovery_capabilities: DiscoveryCapabilities,
) { ) {
let pairing_code = compute_pairing_code(comm_data); 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"); let data_str = payload_base38_representation(&qr_code_data).expect("Failed to encode");
pretty_print_pairing_code(&pairing_code); pretty_print_pairing_code(&pairing_code);

View file

@ -15,11 +15,14 @@
* limitations under the License. * limitations under the License.
*/ */
use std::collections::HashMap;
use super::{ use super::{
vendor_identifiers::{is_vendor_id_valid_operationally, VendorId}, 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 LONG_BITS: usize = 12;
const VERSION_FIELD_LENGTH_IN_BITS: usize = 3; const VERSION_FIELD_LENGTH_IN_BITS: usize = 3;
const VENDOR_IDFIELD_LENGTH_IN_BITS: usize = 16; 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; + PADDING_FIELD_LENGTH_IN_BITS;
const TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES: usize = TOTAL_PAYLOAD_DATA_SIZE_IN_BITS / 8; 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, version: u8,
flow_type: CommissionningFlowType, flow_type: CommissionningFlowType,
discovery_capabilities: DiscoveryCapabilities, discovery_capabilities: DiscoveryCapabilities,
dev_det: &'data BasicInfoConfig, dev_det: &'data BasicInfoConfig,
comm_data: &'data CommissioningData, comm_data: &'data CommissioningData,
optional_data: HashMap<u8, OptionalQRCodeInfo>,
} }
impl<'data> QrCodeData<'data> { impl<'data> QrSetupPayload<'data> {
pub fn new( pub fn new(
dev_det: &'data BasicInfoConfig, dev_det: &'data BasicInfoConfig,
comm_data: &'data CommissioningData, comm_data: &'data CommissioningData,
@ -55,12 +86,13 @@ impl<'data> QrCodeData<'data> {
) -> Self { ) -> Self {
const DEFAULT_VERSION: u8 = 0; const DEFAULT_VERSION: u8 = 0;
QrCodeData { QrSetupPayload {
version: DEFAULT_VERSION, version: DEFAULT_VERSION,
flow_type: CommissionningFlowType::Standard, flow_type: CommissionningFlowType::Standard,
discovery_capabilities, discovery_capabilities,
dev_det, dev_det,
comm_data, comm_data,
optional_data: HashMap::new(),
} }
} }
@ -81,6 +113,55 @@ impl<'data> QrCodeData<'data> {
self.check_payload_common_constraints() 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<u8, OptionalQRCodeInfo> {
&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 { 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) // A version not equal to 0 would be invalid for v1 and would indicate new format (e.g. version 2)
if self.version != 0 { if self.version != 0 {
@ -133,6 +214,10 @@ impl<'data> QrCodeData<'data> {
true true
} }
fn has_tlv(&self) -> bool {
!self.optional_data.is_empty()
}
} }
#[repr(u8)] #[repr(u8)]
@ -147,14 +232,67 @@ struct TlvData {
data_length_in_bytes: u32, data_length_in_bytes: u32,
} }
pub(super) fn payload_base38_representation(payload: &QrCodeData) -> Result<String, Error> { pub(super) fn payload_base38_representation(payload: &QrSetupPayload) -> Result<String, Error> {
let mut bits: [u8; TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES] = [0; TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES]; 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() { if !payload.is_valid() {
return Err(Error::InvalidArgument); 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<usize, Error> {
// 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) { pub(super) fn print_qr_code(qr_data: &str) {
@ -198,9 +336,9 @@ fn populate_bits(
} }
fn payload_base38_representation_with_tlv( fn payload_base38_representation_with_tlv(
payload: &QrCodeData, payload: &QrSetupPayload,
bits: &mut [u8; TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES], bits: &mut [u8],
tlv_data: Option<&TlvData>, tlv_data: Option<TlvData>,
) -> Result<String, Error> { ) -> Result<String, Error> {
generate_bit_set(payload, bits, tlv_data)?; generate_bit_set(payload, bits, tlv_data)?;
let base38_encoded = base38::encode(&*bits); let base38_encoded = base38::encode(&*bits);
@ -208,9 +346,9 @@ fn payload_base38_representation_with_tlv(
} }
fn generate_bit_set( fn generate_bit_set(
payload: &QrCodeData, payload: &QrSetupPayload,
bits: &mut [u8; TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES], bits: &mut [u8],
tlv_data: Option<&TlvData>, tlv_data: Option<TlvData>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let mut offset: usize = 0; 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 {
@ -313,8 +451,18 @@ mod tests {
}; };
let disc_cap = DiscoveryCapabilities::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 qr_code_data = QrSetupPayload::new(&dev_det, &comm_data, disc_cap);
let data_str = payload_base38_representation(&qr_code_data).expect("Failed to encode"); let data_str = payload_base38_representation(&qr_code_data).expect("Failed to encode");
assert_eq!(data_str, QR_CODE) 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
}