Merge branch 'mdns-discovery' into mdns-disco-multi-admin
This commit is contained in:
commit
4653b6d3f9
6 changed files with 516 additions and 35 deletions
|
@ -15,16 +15,85 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//! Base38 encoding functions.
|
//! Base38 encoding and decoding functions.
|
||||||
|
|
||||||
const BASE38_CHARS: &str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-.";
|
use crate::error::Error;
|
||||||
|
|
||||||
/// Encodes a byte array into a base38 string.
|
const BASE38_CHARS: [char; 38] = [
|
||||||
pub fn encode(bytes: &[u8]) -> String {
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
|
||||||
let length = bytes.len();
|
'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '-', '.',
|
||||||
|
];
|
||||||
|
|
||||||
|
const UNUSED: u8 = 255;
|
||||||
|
|
||||||
|
// map of base38 charater to numeric value
|
||||||
|
// subtract 45 from the character, then index into this array, if possible
|
||||||
|
const DECODE_BASE38: [u8; 46] = [
|
||||||
|
36, // '-', =45
|
||||||
|
37, // '.', =46
|
||||||
|
UNUSED, // '/', =47
|
||||||
|
0, // '0', =48
|
||||||
|
1, // '1', =49
|
||||||
|
2, // '2', =50
|
||||||
|
3, // '3', =51
|
||||||
|
4, // '4', =52
|
||||||
|
5, // '5', =53
|
||||||
|
6, // '6', =54
|
||||||
|
7, // '7', =55
|
||||||
|
8, // '8', =56
|
||||||
|
9, // '9', =57
|
||||||
|
UNUSED, // ':', =58
|
||||||
|
UNUSED, // ';', =59
|
||||||
|
UNUSED, // '<', =50
|
||||||
|
UNUSED, // '=', =61
|
||||||
|
UNUSED, // '>', =62
|
||||||
|
UNUSED, // '?', =63
|
||||||
|
UNUSED, // '@', =64
|
||||||
|
10, // 'A', =65
|
||||||
|
11, // 'B', =66
|
||||||
|
12, // 'C', =67
|
||||||
|
13, // 'D', =68
|
||||||
|
14, // 'E', =69
|
||||||
|
15, // 'F', =70
|
||||||
|
16, // 'G', =71
|
||||||
|
17, // 'H', =72
|
||||||
|
18, // 'I', =73
|
||||||
|
19, // 'J', =74
|
||||||
|
20, // 'K', =75
|
||||||
|
21, // 'L', =76
|
||||||
|
22, // 'M', =77
|
||||||
|
23, // 'N', =78
|
||||||
|
24, // 'O', =79
|
||||||
|
25, // 'P', =80
|
||||||
|
26, // 'Q', =81
|
||||||
|
27, // 'R', =82
|
||||||
|
28, // 'S', =83
|
||||||
|
29, // 'T', =84
|
||||||
|
30, // 'U', =85
|
||||||
|
31, // 'V', =86
|
||||||
|
32, // 'W', =87
|
||||||
|
33, // 'X', =88
|
||||||
|
34, // 'Y', =89
|
||||||
|
35, // 'Z', =90
|
||||||
|
];
|
||||||
|
|
||||||
|
const BASE38_CHARACTERS_NEEDED_IN_NBYTES_CHUNK: [u8; 3] = [2, 4, 5];
|
||||||
|
const RADIX: u32 = BASE38_CHARS.len() as u32;
|
||||||
|
|
||||||
|
/// Encode a byte array into a base38 string.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `bytes` - byte array to encode
|
||||||
|
/// * `length` - optional length of the byte array to encode. If not specified, the entire byte array is encoded.
|
||||||
|
pub fn encode(bytes: &[u8], length: Option<usize>) -> String {
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
|
|
||||||
|
// if length is specified, use it, otherwise use the length of the byte array
|
||||||
|
// if length is specified but is greater than the length of the byte array, use the length of the byte array
|
||||||
|
let b_len = bytes.len();
|
||||||
|
let length = length.map(|l| l.min(b_len)).unwrap_or(b_len);
|
||||||
|
|
||||||
while offset < length {
|
while offset < length {
|
||||||
let remaining = length - offset;
|
let remaining = length - offset;
|
||||||
match remaining.cmp(&2) {
|
match remaining.cmp(&2) {
|
||||||
|
@ -57,24 +126,100 @@ pub fn encode(bytes: &[u8]) -> String {
|
||||||
fn encode_base38(mut value: u32, char_count: u8) -> String {
|
fn encode_base38(mut value: u32, char_count: u8) -> String {
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
for _ in 0..char_count {
|
for _ in 0..char_count {
|
||||||
let mut chars = BASE38_CHARS.chars();
|
|
||||||
let remainder = value % 38;
|
let remainder = value % 38;
|
||||||
result.push(chars.nth(remainder as usize).unwrap());
|
result.push(BASE38_CHARS[remainder as usize]);
|
||||||
value = (value - remainder) / 38;
|
value = (value - remainder) / 38;
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decode a base38-encoded string into a byte slice
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `base38_str` - base38-encoded string to decode
|
||||||
|
///
|
||||||
|
/// Fails if the string contains invalid characters
|
||||||
|
pub fn decode(base38_str: &str) -> Result<Vec<u8>, Error> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
let mut base38_characters_number: usize = base38_str.len();
|
||||||
|
let mut decoded_base38_characters: usize = 0;
|
||||||
|
|
||||||
|
while base38_characters_number > 0 {
|
||||||
|
let base38_characters_in_chunk: usize;
|
||||||
|
let bytes_in_decoded_chunk: usize;
|
||||||
|
|
||||||
|
if base38_characters_number >= BASE38_CHARACTERS_NEEDED_IN_NBYTES_CHUNK[2] as usize {
|
||||||
|
base38_characters_in_chunk = BASE38_CHARACTERS_NEEDED_IN_NBYTES_CHUNK[2] as usize;
|
||||||
|
bytes_in_decoded_chunk = 3;
|
||||||
|
} else if base38_characters_number == BASE38_CHARACTERS_NEEDED_IN_NBYTES_CHUNK[1] as usize {
|
||||||
|
base38_characters_in_chunk = BASE38_CHARACTERS_NEEDED_IN_NBYTES_CHUNK[1] as usize;
|
||||||
|
bytes_in_decoded_chunk = 2;
|
||||||
|
} else if base38_characters_number == BASE38_CHARACTERS_NEEDED_IN_NBYTES_CHUNK[0] as usize {
|
||||||
|
base38_characters_in_chunk = BASE38_CHARACTERS_NEEDED_IN_NBYTES_CHUNK[0] as usize;
|
||||||
|
bytes_in_decoded_chunk = 1;
|
||||||
|
} else {
|
||||||
|
return Err(Error::InvalidData);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut value = 0u32;
|
||||||
|
|
||||||
|
for i in (1..=base38_characters_in_chunk).rev() {
|
||||||
|
let mut base38_chars = base38_str.chars();
|
||||||
|
let v = decode_char(base38_chars.nth(decoded_base38_characters + i - 1).unwrap())?;
|
||||||
|
|
||||||
|
value = value * RADIX + v as u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
decoded_base38_characters += base38_characters_in_chunk;
|
||||||
|
base38_characters_number -= base38_characters_in_chunk;
|
||||||
|
|
||||||
|
for _i in 0..bytes_in_decoded_chunk {
|
||||||
|
result.push(value as u8);
|
||||||
|
value >>= 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
if value > 0 {
|
||||||
|
// encoded value is too big to represent a correct chunk of size 1, 2 or 3 bytes
|
||||||
|
return Err(Error::InvalidArgument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_char(c: char) -> Result<u8, Error> {
|
||||||
|
let c = c as u8;
|
||||||
|
if !(45..=90).contains(&c) {
|
||||||
|
return Err(Error::InvalidData);
|
||||||
|
}
|
||||||
|
|
||||||
|
let c = DECODE_BASE38[c as usize - 45];
|
||||||
|
if c == UNUSED {
|
||||||
|
return Err(Error::InvalidData);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(c)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
const ENCODED: &str = "-MOA57ZU02IT2L2BJ00";
|
||||||
|
const DECODED: [u8; 11] = [
|
||||||
|
0x88, 0xff, 0xa7, 0x91, 0x50, 0x40, 0x00, 0x47, 0x51, 0xdd, 0x02,
|
||||||
|
];
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_base38_encode() {
|
fn can_base38_encode() {
|
||||||
const ENCODED: &str = "-MOA57ZU02IT2L2BJ00";
|
assert_eq!(encode(&DECODED, None), ENCODED);
|
||||||
const DECODED: [u8; 11] = [
|
assert_eq!(encode(&DECODED, Some(11)), ENCODED);
|
||||||
0x88, 0xff, 0xa7, 0x91, 0x50, 0x40, 0x00, 0x47, 0x51, 0xdd, 0x02,
|
|
||||||
];
|
// length is greater than the length of the byte array
|
||||||
assert_eq!(encode(&DECODED), ENCODED);
|
assert_eq!(encode(&DECODED, Some(12)), ENCODED);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_base38_decode() {
|
||||||
|
assert_eq!(decode(ENCODED).expect("can not decode base38"), DECODED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,19 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
tlv::{TLVWriter, TagType},
|
||||||
|
utils::writebuf::WriteBuf,
|
||||||
|
};
|
||||||
|
|
||||||
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 +47,44 @@ 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,
|
||||||
|
// we use a BTreeMap to keep the order of the optional data stable
|
||||||
|
optional_data: BTreeMap<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 +92,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: BTreeMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,6 +121,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) -> &BTreeMap<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 {
|
||||||
|
@ -137,6 +224,10 @@ impl<'data> QrCodeData<'data> {
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn has_tlv(&self) -> bool {
|
||||||
|
!self.optional_data.is_empty()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
|
@ -148,17 +239,75 @@ pub enum CommissionningFlowType {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TlvData {
|
struct TlvData {
|
||||||
data_length_in_bytes: u32,
|
max_data_length_in_bytes: u32,
|
||||||
|
data_length_in_bytes: Option<usize>,
|
||||||
|
data: Option<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
max_data_length_in_bytes: buffer_size as u32,
|
||||||
|
data_length_in_bytes: None,
|
||||||
|
data: None,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
} 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; initialize with the size of the standard payload.
|
||||||
|
let mut estimate = TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES;
|
||||||
|
|
||||||
|
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.as_bytes().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.
|
||||||
|
//
|
||||||
|
// The struct itself has a control byte and an end-of-struct marker.
|
||||||
|
first_field_size + 4 + 2
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn print_qr_code(qr_data: &str) {
|
pub(super) fn print_qr_code(qr_data: &str) {
|
||||||
|
@ -202,23 +351,57 @@ 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>,
|
mut tlv_data: Option<TlvData>,
|
||||||
) -> Result<String, Error> {
|
) -> Result<String, Error> {
|
||||||
generate_bit_set(payload, bits, tlv_data)?;
|
if let Some(tlv_data) = tlv_data.as_mut() {
|
||||||
let base38_encoded = base38::encode(&*bits);
|
generate_tlv_from_optional_data(payload, tlv_data)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bytes_written = generate_bit_set(payload, bits, tlv_data)?;
|
||||||
|
let base38_encoded = base38::encode(&*bits, Some(bytes_written));
|
||||||
Ok(format!("MT:{}", base38_encoded))
|
Ok(format!("MT:{}", base38_encoded))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_bit_set(
|
fn generate_tlv_from_optional_data(
|
||||||
payload: &QrCodeData,
|
payload: &QrSetupPayload,
|
||||||
bits: &mut [u8; TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES],
|
tlv_data: &mut TlvData,
|
||||||
tlv_data: Option<&TlvData>,
|
|
||||||
) -> Result<(), Error> {
|
) -> 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) => tw.utf8(TagType::Context(*tag), data.as_bytes())?,
|
||||||
|
QRCodeInfoType::Int32(data) => tw.i32(TagType::Context(*tag), *data)?,
|
||||||
|
QRCodeInfoType::Int64(data) => tw.i64(TagType::Context(*tag), *data)?,
|
||||||
|
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<TlvData>,
|
||||||
|
) -> Result<usize, Error> {
|
||||||
let mut offset: usize = 0;
|
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 {
|
} else {
|
||||||
TOTAL_PAYLOAD_DATA_SIZE_IN_BITS
|
TOTAL_PAYLOAD_DATA_SIZE_IN_BITS
|
||||||
};
|
};
|
||||||
|
@ -293,12 +476,42 @@ fn generate_bit_set(
|
||||||
total_payload_size_in_bits,
|
total_payload_size_in_bits,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// todo: add tlv data
|
if let Some(tlv_data) = tlv_data {
|
||||||
// ReturnErrorOnFailure(populateTLVBits(bits.data(), offset, tlvDataStart, tlvDataLengthInBytes, totalPayloadSizeInBits));
|
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 b in data.iter().take(data_length_in_bytes) {
|
||||||
|
populate_bits(bits, offset, *b as u64, 8, total_payload_size_in_bits)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(Error::InvalidArgument);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
|
@ -320,7 +533,79 @@ 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");
|
||||||
|
assert_eq!(data_str, QR_CODE)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_base38_encode_with_vendor_data() {
|
||||||
|
const QR_CODE: &str = "MT:-24J0AFN00KA064IJ3P0IXZB0DK5N1K8SQ1RYCU1-A40";
|
||||||
|
|
||||||
|
let comm_data = CommissioningData {
|
||||||
|
passwd: 20202021,
|
||||||
|
discriminator: 3840,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let dev_det = BasicInfoConfig {
|
||||||
|
vid: 65521,
|
||||||
|
pid: 32769,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let disc_cap = DiscoveryCapabilities::new(true, false, false);
|
||||||
|
let mut qr_code_data = QrSetupPayload::new(&dev_det, &comm_data, disc_cap);
|
||||||
|
qr_code_data
|
||||||
|
.add_serial_number(SerialNumber::String("1234567890".to_string()))
|
||||||
|
.expect("Failed to add serial number");
|
||||||
|
|
||||||
|
let data_str = payload_base38_representation(&qr_code_data).expect("Failed to encode");
|
||||||
|
assert_eq!(data_str, QR_CODE)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_base38_encode_with_optional_data() {
|
||||||
|
const QR_CODE: &str =
|
||||||
|
"MT:-24J0AFN00KA064IJ3P0IXZB0DK5N1K8SQ1RYCU1UXH34YY0V3KY.O3DKN440F710Q940";
|
||||||
|
const OPTIONAL_DEFAULT_STRING_TAG: u8 = 0x82; // Vendor "test" tag
|
||||||
|
const OPTIONAL_DEFAULT_STRING_VALUE: &str = "myData";
|
||||||
|
|
||||||
|
const OPTIONAL_DEFAULT_INT_TAG: u8 = 0x83; // Vendor "test" tag
|
||||||
|
const OPTIONAL_DEFAULT_INT_VALUE: i32 = 65550;
|
||||||
|
|
||||||
|
let comm_data = CommissioningData {
|
||||||
|
passwd: 20202021,
|
||||||
|
discriminator: 3840,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let dev_det = BasicInfoConfig {
|
||||||
|
vid: 65521,
|
||||||
|
pid: 32769,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let disc_cap = DiscoveryCapabilities::new(true, false, false);
|
||||||
|
let mut qr_code_data = QrSetupPayload::new(&dev_det, &comm_data, disc_cap);
|
||||||
|
qr_code_data
|
||||||
|
.add_serial_number(SerialNumber::String("1234567890".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");
|
||||||
|
|
||||||
|
// todo: check why unsigned ints are not accepted by 'chip-tool payload parse-setup-payload'
|
||||||
|
|
||||||
|
qr_code_data
|
||||||
|
.add_optional_vendor_data(
|
||||||
|
OPTIONAL_DEFAULT_INT_TAG,
|
||||||
|
QRCodeInfoType::Int32(OPTIONAL_DEFAULT_INT_VALUE),
|
||||||
|
)
|
||||||
|
.expect("Failed to add optional data");
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,6 +95,15 @@ impl<'a, 'b> TLVWriter<'a, 'b> {
|
||||||
self.buf.le_u8(data)
|
self.buf.le_u8(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn i16(&mut self, tag_type: TagType, data: i16) -> Result<(), Error> {
|
||||||
|
if data >= i8::MIN as i16 && data <= i8::MAX as i16 {
|
||||||
|
self.i8(tag_type, data as i8)
|
||||||
|
} else {
|
||||||
|
self.put_control_tag(tag_type, WriteElementType::S16)?;
|
||||||
|
self.buf.le_i16(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn u16(&mut self, tag_type: TagType, data: u16) -> Result<(), Error> {
|
pub fn u16(&mut self, tag_type: TagType, data: u16) -> Result<(), Error> {
|
||||||
if data <= 0xff {
|
if data <= 0xff {
|
||||||
self.u8(tag_type, data as u8)
|
self.u8(tag_type, data as u8)
|
||||||
|
@ -104,6 +113,17 @@ impl<'a, 'b> TLVWriter<'a, 'b> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn i32(&mut self, tag_type: TagType, data: i32) -> Result<(), Error> {
|
||||||
|
if data >= i8::MIN as i32 && data <= i8::MAX as i32 {
|
||||||
|
self.i8(tag_type, data as i8)
|
||||||
|
} else if data >= i16::MIN as i32 && data <= i16::MAX as i32 {
|
||||||
|
self.i16(tag_type, data as i16)
|
||||||
|
} else {
|
||||||
|
self.put_control_tag(tag_type, WriteElementType::S32)?;
|
||||||
|
self.buf.le_i32(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn u32(&mut self, tag_type: TagType, data: u32) -> Result<(), Error> {
|
pub fn u32(&mut self, tag_type: TagType, data: u32) -> Result<(), Error> {
|
||||||
if data <= 0xff {
|
if data <= 0xff {
|
||||||
self.u8(tag_type, data as u8)
|
self.u8(tag_type, data as u8)
|
||||||
|
@ -115,6 +135,19 @@ impl<'a, 'b> TLVWriter<'a, 'b> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn i64(&mut self, tag_type: TagType, data: i64) -> Result<(), Error> {
|
||||||
|
if data >= i8::MIN as i64 && data <= i8::MAX as i64 {
|
||||||
|
self.i8(tag_type, data as i8)
|
||||||
|
} else if data >= i16::MIN as i64 && data <= i16::MAX as i64 {
|
||||||
|
self.i16(tag_type, data as i16)
|
||||||
|
} else if data >= i32::MIN as i64 && data <= i32::MAX as i64 {
|
||||||
|
self.i32(tag_type, data as i32)
|
||||||
|
} else {
|
||||||
|
self.put_control_tag(tag_type, WriteElementType::S64)?;
|
||||||
|
self.buf.le_i64(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn u64(&mut self, tag_type: TagType, data: u64) -> Result<(), Error> {
|
pub fn u64(&mut self, tag_type: TagType, data: u64) -> Result<(), Error> {
|
||||||
if data <= 0xff {
|
if data <= 0xff {
|
||||||
self.u8(tag_type, data as u8)
|
self.u8(tag_type, data as u8)
|
||||||
|
|
|
@ -131,6 +131,11 @@ impl<'a> WriteBuf<'a> {
|
||||||
LittleEndian::write_u16(&mut x.buf[x.end..], data);
|
LittleEndian::write_u16(&mut x.buf[x.end..], data);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
pub fn le_i16(&mut self, data: i16) -> Result<(), Error> {
|
||||||
|
self.append_with(2, |x| {
|
||||||
|
LittleEndian::write_i16(&mut x.buf[x.end..], data);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn le_u32(&mut self, data: u32) -> Result<(), Error> {
|
pub fn le_u32(&mut self, data: u32) -> Result<(), Error> {
|
||||||
self.append_with(4, |x| {
|
self.append_with(4, |x| {
|
||||||
|
@ -138,12 +143,24 @@ impl<'a> WriteBuf<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn le_i32(&mut self, data: i32) -> Result<(), Error> {
|
||||||
|
self.append_with(4, |x| {
|
||||||
|
LittleEndian::write_i32(&mut x.buf[x.end..], data);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn le_u64(&mut self, data: u64) -> Result<(), Error> {
|
pub fn le_u64(&mut self, data: u64) -> Result<(), Error> {
|
||||||
self.append_with(8, |x| {
|
self.append_with(8, |x| {
|
||||||
LittleEndian::write_u64(&mut x.buf[x.end..], data);
|
LittleEndian::write_u64(&mut x.buf[x.end..], data);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn le_i64(&mut self, data: i64) -> Result<(), Error> {
|
||||||
|
self.append_with(8, |x| {
|
||||||
|
LittleEndian::write_i64(&mut x.buf[x.end..], data);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn le_uint(&mut self, nbytes: usize, data: u64) -> Result<(), Error> {
|
pub fn le_uint(&mut self, nbytes: usize, data: u64) -> Result<(), Error> {
|
||||||
self.append_with(nbytes, |x| {
|
self.append_with(nbytes, |x| {
|
||||||
LittleEndian::write_uint(&mut x.buf[x.end..], data, nbytes);
|
LittleEndian::write_uint(&mut x.buf[x.end..], data, nbytes);
|
||||||
|
|
Loading…
Add table
Reference in a new issue