Encode optional data fields

This commit is contained in:
Marcel 2023-01-12 15:42:21 +01:00
parent b74ae4ff01
commit f39cff0bc2
2 changed files with 148 additions and 27 deletions

View file

@ -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);
}
}

View file

@ -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<u8, OptionalQRCodeInfo>,
// we use a BTreeMap to keep the order of the optional data stable
optional_data: BTreeMap<u8, OptionalQRCodeInfo>,
}
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<u8, OptionalQRCodeInfo> {
pub fn get_all_optional_data(&self) -> &BTreeMap<u8, OptionalQRCodeInfo> {
&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<usize>,
data: Option<Vec<u8>>,
}
pub(super) fn payload_base38_representation(payload: &QrSetupPayload) -> Result<String, Error> {
@ -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<usize, Error> {
// 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<TlvData>,
mut tlv_data: Option<TlvData>,
) -> Result<String, Error> {
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<TlvData>,
) -> Result<(), Error> {
) -> Result<usize, Error> {
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)
}
}