Prep for optional TLV data
This commit is contained in:
parent
0f0b87a369
commit
b74ae4ff01
3 changed files with 164 additions and 15 deletions
|
@ -39,6 +39,7 @@ pub enum Error {
|
|||
NoHandler,
|
||||
NoNetworkInterface,
|
||||
NoNodeId,
|
||||
NoMemory,
|
||||
NoSession,
|
||||
NoSpace,
|
||||
NoSpaceAckTable,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<u8, OptionalQRCodeInfo>,
|
||||
}
|
||||
|
||||
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<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 {
|
||||
// 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<String, Error> {
|
||||
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<String, Error> {
|
||||
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<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) {
|
||||
|
@ -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<TlvData>,
|
||||
) -> Result<String, Error> {
|
||||
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<TlvData>,
|
||||
) -> 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
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue