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,
|
NoHandler,
|
||||||
NoNetworkInterface,
|
NoNetworkInterface,
|
||||||
NoNodeId,
|
NoNodeId,
|
||||||
|
NoMemory,
|
||||||
NoSession,
|
NoSession,
|
||||||
NoSpace,
|
NoSpace,
|
||||||
NoSpaceAckTable,
|
NoSpaceAckTable,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue