Remove allocations from Base38 and QR calc

This commit is contained in:
ivmarkov 2023-04-23 14:53:48 +00:00
parent 26fb6b01c5
commit f7a887c1d2
6 changed files with 236 additions and 245 deletions

View file

@ -15,7 +15,7 @@ name = "matter"
path = "src/lib.rs"
[features]
default = ["std", "crypto_mbedtls", "nightly"]
default = ["std", "crypto_mbedtls"]
std = []
nightly = []
crypto_openssl = ["openssl", "foreign-types", "hmac", "sha2"]

View file

@ -17,10 +17,6 @@
//! Base38 encoding and decoding functions.
extern crate alloc;
use alloc::{string::String, vec::Vec};
use crate::error::Error;
const BASE38_CHARS: [char; 38] = [
@ -81,60 +77,68 @@ const DECODE_BASE38: [u8; 46] = [
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 result = String::new();
pub fn encode_string<const N: usize>(bytes: &[u8]) -> Result<heapless::String<N>, Error> {
let mut string = heapless::String::new();
for c in encode(bytes) {
string.push(c).map_err(|_| Error::NoSpace)?;
}
// 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);
Ok(string)
}
while offset < length {
let remaining = length - offset;
match remaining.cmp(&2) {
core::cmp::Ordering::Greater => {
result.push_str(&encode_base38(
pub fn encode(bytes: &[u8]) -> impl Iterator<Item = char> + '_ {
(0..bytes.len() / 3)
.flat_map(move |index| {
let offset = index * 3;
encode_base38(
((bytes[offset + 2] as u32) << 16)
| ((bytes[offset + 1] as u32) << 8)
| (bytes[offset] as u32),
5,
));
offset += 3;
}
core::cmp::Ordering::Equal => {
result.push_str(&encode_base38(
)
})
.chain(
core::iter::once(bytes.len() % 3).flat_map(move |remainder| {
let offset = bytes.len() / 3 * 3;
match remainder {
2 => encode_base38(
((bytes[offset + 1] as u32) << 8) | (bytes[offset] as u32),
4,
));
break;
}
core::cmp::Ordering::Less => {
result.push_str(&encode_base38(bytes[offset] as u32, 2));
break;
}
),
1 => encode_base38(bytes[offset] as u32, 2),
_ => encode_base38(0, 0),
}
}),
)
}
result
fn encode_base38(mut value: u32, repeat: usize) -> impl Iterator<Item = char> {
(0..repeat).map(move |_| {
let remainder = value % RADIX;
let c = BASE38_CHARS[remainder as usize];
value = (value - remainder) / RADIX;
c
})
}
fn encode_base38(mut value: u32, char_count: u8) -> String {
let mut result = String::new();
for _ in 0..char_count {
let remainder = value % 38;
result.push(BASE38_CHARS[remainder as usize]);
value = (value - remainder) / 38;
pub fn decode_vec<const N: usize>(base38_str: &str) -> Result<heapless::Vec<u8, N>, Error> {
let mut vec = heapless::Vec::new();
for byte in decode(base38_str) {
vec.push(byte?).map_err(|_| Error::NoSpace)?;
}
result
Ok(vec)
}
/// Decode a base38-encoded string into a byte slice
@ -142,57 +146,64 @@ fn encode_base38(mut value: u32, char_count: u8) -> String {
/// # 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;
/// Fails if the string contains invalid characters or if the supplied buffer is too small to fit the decoded data
pub fn decode(base38_str: &str) -> impl Iterator<Item = Result<u8, Error>> + '_ {
let stru = base38_str.as_bytes();
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);
(0..stru.len() / 5)
.flat_map(move |index| {
let offset = index * 5;
decode_base38(&stru[offset..offset + 5])
})
.chain({
let offset = stru.len() / 5 * 5;
decode_base38(&stru[offset..])
})
.take_while(Result::is_ok)
}
fn decode_base38(chars: &[u8]) -> impl Iterator<Item = Result<u8, Error>> {
let mut value = 0u32;
let mut cerr = None;
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())?;
let repeat = match chars.len() {
5 => 3,
4 => 2,
2 => 1,
0 => 0,
_ => -1,
};
value = value * RADIX + v as u32;
if repeat >= 0 {
for c in chars.iter().rev() {
match decode_char(*c) {
Ok(v) => value = value * RADIX + v as u32,
Err(err) => {
cerr = Some(err);
break;
}
}
}
} else {
cerr = Some(Error::InvalidData)
}
decoded_base38_characters += base38_characters_in_chunk;
base38_characters_number -= base38_characters_in_chunk;
(0..repeat)
.map(move |_| {
if let Some(err) = cerr {
Err(err)
} else {
let byte = (value & 0xff) as u8;
for _i in 0..bytes_in_decoded_chunk {
result.push(value as u8);
value >>= 8;
Ok(byte)
}
})
.take_while(Result::is_ok)
}
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;
fn decode_char(c: u8) -> Result<u8, Error> {
if !(45..=90).contains(&c) {
return Err(Error::InvalidData);
}
@ -215,15 +226,17 @@ mod tests {
#[test]
fn can_base38_encode() {
assert_eq!(encode(&DECODED, None), ENCODED);
assert_eq!(encode(&DECODED, Some(11)), ENCODED);
// length is greater than the length of the byte array
assert_eq!(encode(&DECODED, Some(12)), ENCODED);
assert_eq!(
encode_string::<{ ENCODED.len() }>(&DECODED).unwrap(),
ENCODED
);
}
#[test]
fn can_base38_decode() {
assert_eq!(decode(ENCODED).expect("can not decode base38"), DECODED);
assert_eq!(
decode_vec::<{ DECODED.len() }>(ENCODED).expect("Cannot decode base38"),
DECODED
);
}
}

View file

@ -84,10 +84,19 @@ impl<'a> Matter<'a> {
self.dev_det
}
pub fn start(&mut self, dev_comm: CommissioningData) -> Result<(), Error> {
pub fn start<const N: usize>(
&mut self,
dev_comm: CommissioningData,
buf: &mut [u8],
) -> Result<(), Error> {
let open_comm_window = self.fabric_mgr.borrow().is_empty();
if open_comm_window {
print_pairing_code_and_qr(self.dev_det, &dev_comm, DiscoveryCapabilities::default());
print_pairing_code_and_qr::<N>(
self.dev_det,
&dev_comm,
DiscoveryCapabilities::default(),
buf,
);
self.pase_mgr.borrow_mut().enable_pase_session(
dev_comm.verifier,

View file

@ -15,38 +15,8 @@
* limitations under the License.
*/
use alloc::sync::Arc;
use std::sync::{Mutex, Once};
use crate::{crypto, error::Error};
extern crate alloc;
// This is just makeshift implementation for now, not used anywhere
pub struct GroupKeys {}
static mut G_GRP_KEYS: Option<Arc<Mutex<GroupKeys>>> = None;
static INIT: Once = Once::new();
impl GroupKeys {
fn new() -> Self {
Self {}
}
pub fn get() -> Result<Arc<Mutex<Self>>, Error> {
unsafe {
INIT.call_once(|| {
G_GRP_KEYS = Some(Arc::new(Mutex::new(GroupKeys::new())));
});
Ok(G_GRP_KEYS.as_ref().ok_or(Error::Invalid)?.clone())
}
}
pub fn insert_key() -> Result<(), Error> {
Ok(())
}
}
#[derive(Debug, Default)]
pub struct KeySet {
pub epoch_key: [u8; crypto::SYMM_KEY_LEN_BYTES],

View file

@ -82,14 +82,16 @@ impl DiscoveryCapabilities {
}
/// Prepares and prints the pairing code and the QR code for easy pairing.
pub fn print_pairing_code_and_qr(
pub fn print_pairing_code_and_qr<const N: usize>(
dev_det: &BasicInfoConfig,
comm_data: &CommissioningData,
discovery_capabilities: DiscoveryCapabilities,
buf: &mut [u8],
) {
let pairing_code = compute_pairing_code(comm_data);
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::<N>(&qr_code_data, buf).expect("Failed to encode");
pretty_print_pairing_code(&pairing_code);
print_qr_code(&data_str);

View file

@ -15,8 +15,6 @@
* limitations under the License.
*/
use heapless::FnvIndexMap;
use crate::{
tlv::{TLVWriter, TagType},
utils::writebuf::WriteBuf,
@ -45,6 +43,7 @@ const TOTAL_PAYLOAD_DATA_SIZE_IN_BITS: usize = VERSION_FIELD_LENGTH_IN_BITS
+ PAYLOAD_DISCRIMINATOR_FIELD_LENGTH_IN_BITS
+ SETUP_PINCODE_FIELD_LENGTH_IN_BITS
+ PADDING_FIELD_LENGTH_IN_BITS;
const TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES: usize = TOTAL_PAYLOAD_DATA_SIZE_IN_BITS / 8;
// Spec 5.1.4.2 CHIP-Common Reserved Tags
@ -80,8 +79,8 @@ pub struct QrSetupPayload<'data> {
discovery_capabilities: DiscoveryCapabilities,
dev_det: &'data BasicInfoConfig<'data>,
comm_data: &'data CommissioningData,
// we use a BTreeMap to keep the order of the optional data stable
optional_data: heapless::FnvIndexMap<u8, OptionalQRCodeInfo, 16>,
// The vec is ordered by the tag of OptionalQRCodeInfo
optional_data: heapless::Vec<OptionalQRCodeInfo, 16>,
}
impl<'data> QrSetupPayload<'data> {
@ -98,7 +97,7 @@ impl<'data> QrSetupPayload<'data> {
discovery_capabilities,
dev_det,
comm_data,
optional_data: FnvIndexMap::new(),
optional_data: heapless::Vec::new(),
};
if !dev_det.serial_no.is_empty() {
@ -132,15 +131,11 @@ impl<'data> QrSetupPayload<'data> {
/// * `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);
if is_vendor_tag(tag) {
self.add_optional_data(tag, data)
} else {
Err(Error::InvalidArgument)
}
self.optional_data
.insert(tag, OptionalQRCodeInfo { tag, data })
.map_err(|_| Error::NoSpace)?;
Ok(())
}
/// A function to add an optional QR Code info CHIP object
@ -152,18 +147,26 @@ impl<'data> QrSetupPayload<'data> {
tag: u8,
data: QRCodeInfoType,
) -> Result<(), Error> {
if !is_common_tag(tag) {
return Err(Error::InvalidArgument);
if is_common_tag(tag) {
self.add_optional_data(tag, data)
} else {
Err(Error::InvalidArgument)
}
}
self.optional_data
.insert(tag, OptionalQRCodeInfo { tag, data })
.map_err(|_| Error::NoSpace)?;
fn add_optional_data(&mut self, tag: u8, data: QRCodeInfoType) -> Result<(), Error> {
let item = OptionalQRCodeInfo { tag, data };
let index = self.optional_data.iter().position(|info| tag < info.tag);
Ok(())
if let Some(index) = index {
self.optional_data.insert(index, item)
} else {
self.optional_data.push(item)
}
.map_err(|_| Error::NoSpace)
}
pub fn get_all_optional_data(&self) -> &FnvIndexMap<u8, OptionalQRCodeInfo, 16> {
pub fn get_all_optional_data(&self) -> &[OptionalQRCodeInfo] {
&self.optional_data
}
@ -249,35 +252,26 @@ pub enum CommissionningFlowType {
Custom = 2,
}
struct TlvData {
max_data_length_in_bytes: u32,
data_length_in_bytes: Option<usize>,
data: Option<Vec<u8>>,
}
pub(super) fn payload_base38_representation<const N: usize>(
payload: &QrSetupPayload,
buf: &mut [u8],
) -> Result<heapless::String<N>, Error> {
if payload.is_valid() {
let (bits_buf, tlv_buf) = if payload.has_tlv() {
let (bits_buf, tlv_buf) = buf.split_at_mut(buf.len() / 2);
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 {
max_data_length_in_bytes: buffer_size as u32,
data_length_in_bytes: None,
data: None,
}),
)
(bits_buf, Some(tlv_buf))
} else {
(vec![0; TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES], None)
(buf, None)
};
if !payload.is_valid() {
return Err(Error::InvalidArgument);
payload_base38_representation_with_tlv(payload, bits_buf, tlv_buf)
} else {
Err(Error::InvalidArgument)
}
}
payload_base38_representation_with_tlv(payload, &mut bits, tlv_data)
}
fn estimate_buffer_size(payload: &QrSetupPayload) -> Result<usize, Error> {
pub 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;
@ -298,10 +292,9 @@ fn estimate_buffer_size(payload: &QrSetupPayload) -> Result<usize, Error> {
size
};
let vendor_data = payload.get_all_optional_data();
vendor_data.values().for_each(|data| {
for data in payload.get_all_optional_data() {
estimate += data_item_size_estimate(&data.data);
});
}
estimate = estimate_struct_overhead(estimate);
@ -372,70 +365,72 @@ fn populate_bits(
Ok(())
}
fn payload_base38_representation_with_tlv(
fn payload_base38_representation_with_tlv<const N: usize>(
payload: &QrSetupPayload,
bits: &mut [u8],
mut tlv_data: Option<TlvData>,
) -> Result<String, Error> {
if let Some(tlv_data) = tlv_data.as_mut() {
generate_tlv_from_optional_data(payload, tlv_data)?;
bits_buf: &mut [u8],
tlv_buf: Option<&mut [u8]>,
) -> Result<heapless::String<N>, Error> {
let tlv_data = if let Some(tlv_buf) = tlv_buf {
Some(generate_tlv_from_optional_data(payload, tlv_buf)?)
} else {
None
};
let bits = generate_bit_set(payload, bits_buf, tlv_data)?;
let mut base38_encoded: heapless::String<N> = "MT:".into();
for c in base38::encode(bits) {
base38_encoded.push(c).map_err(|_| Error::NoSpace)?;
}
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(base38_encoded)
}
fn generate_tlv_from_optional_data(
fn generate_tlv_from_optional_data<'a>(
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);
tlv_buf: &'a mut [u8],
) -> Result<&'a [u8], Error> {
let mut wb = WriteBuf::new(tlv_buf);
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)?,
for info in payload.get_all_optional_data() {
match &info.data {
QRCodeInfoType::String(data) => tw.utf8(TagType::Context(info.tag), data.as_bytes())?,
QRCodeInfoType::Int32(data) => tw.i32(TagType::Context(info.tag), *data)?,
QRCodeInfoType::Int64(data) => tw.i64(TagType::Context(info.tag), *data)?,
QRCodeInfoType::UInt32(data) => tw.u32(TagType::Context(info.tag), *data)?,
QRCodeInfoType::UInt64(data) => tw.u64(TagType::Context(info.tag), *data)?,
}
}
tw.end_container()?;
tlv_data.data_length_in_bytes = Some(tw.get_tail());
tlv_data.data = Some(tlv_buffer);
Ok(())
let tail = tw.get_tail();
Ok(&tlv_buf[..tail])
}
fn generate_bit_set(
fn generate_bit_set<'a>(
payload: &QrSetupPayload,
bits: &mut [u8],
tlv_data: Option<TlvData>,
) -> Result<usize, Error> {
let mut offset: usize = 0;
bits_buf: &'a mut [u8],
tlv_data: Option<&[u8]>,
) -> Result<&'a [u8], Error> {
let total_payload_size_in_bits =
TOTAL_PAYLOAD_DATA_SIZE_IN_BITS + tlv_data.map(|tlv_data| tlv_data.len() * 8).unwrap_or(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.unwrap_or_default() * 8)
} else {
TOTAL_PAYLOAD_DATA_SIZE_IN_BITS
};
if bits.len() * 8 < total_payload_size_in_bits {
if bits_buf.len() * 8 < total_payload_size_in_bits {
return Err(Error::BufferTooSmall);
};
let passwd = passwd_from_comm_data(payload.comm_data);
let mut offset: usize = 0;
populate_bits(
bits,
bits_buf,
&mut offset,
payload.version as u64,
VERSION_FIELD_LENGTH_IN_BITS,
@ -443,7 +438,7 @@ fn generate_bit_set(
)?;
populate_bits(
bits,
bits_buf,
&mut offset,
payload.dev_det.vid as u64,
VENDOR_IDFIELD_LENGTH_IN_BITS,
@ -451,7 +446,7 @@ fn generate_bit_set(
)?;
populate_bits(
bits,
bits_buf,
&mut offset,
payload.dev_det.pid as u64,
PRODUCT_IDFIELD_LENGTH_IN_BITS,
@ -459,7 +454,7 @@ fn generate_bit_set(
)?;
populate_bits(
bits,
bits_buf,
&mut offset,
payload.flow_type as u64,
COMMISSIONING_FLOW_FIELD_LENGTH_IN_BITS,
@ -467,7 +462,7 @@ fn generate_bit_set(
)?;
populate_bits(
bits,
bits_buf,
&mut offset,
payload.discovery_capabilities.as_bits() as u64,
RENDEZVOUS_INFO_FIELD_LENGTH_IN_BITS,
@ -475,7 +470,7 @@ fn generate_bit_set(
)?;
populate_bits(
bits,
bits_buf,
&mut offset,
payload.comm_data.discriminator as u64,
PAYLOAD_DISCRIMINATOR_FIELD_LENGTH_IN_BITS,
@ -483,7 +478,7 @@ fn generate_bit_set(
)?;
populate_bits(
bits,
bits_buf,
&mut offset,
passwd as u64,
SETUP_PINCODE_FIELD_LENGTH_IN_BITS,
@ -491,7 +486,7 @@ fn generate_bit_set(
)?;
populate_bits(
bits,
bits_buf,
&mut offset,
0,
PADDING_FIELD_LENGTH_IN_BITS,
@ -499,26 +494,22 @@ fn generate_bit_set(
)?;
if let Some(tlv_data) = tlv_data {
populate_tlv_bits(bits, &mut offset, tlv_data, total_payload_size_in_bits)?;
populate_tlv_bits(bits_buf, &mut offset, tlv_data, total_payload_size_in_bits)?;
}
let bytes_written = (offset + 7) / 8;
Ok(bytes_written)
Ok(&bits_buf[..bytes_written])
}
fn populate_tlv_bits(
bits: &mut [u8],
bits_buf: &mut [u8],
offset: &mut usize,
tlv_data: TlvData,
tlv_data: &[u8],
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);
for b in tlv_data {
populate_bits(bits_buf, offset, *b as u64, 8, total_payload_size_in_bits)?;
}
Ok(())
@ -555,7 +546,9 @@ mod tests {
let disc_cap = DiscoveryCapabilities::new(false, true, false);
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 mut buf = [0; 1024];
let data_str = payload_base38_representation::<128>(&qr_code_data, &mut buf)
.expect("Failed to encode");
assert_eq!(data_str, QR_CODE)
}
@ -576,7 +569,9 @@ mod tests {
let disc_cap = DiscoveryCapabilities::new(true, false, false);
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 mut buf = [0; 1024];
let data_str = payload_base38_representation::<128>(&qr_code_data, &mut buf)
.expect("Failed to encode");
assert_eq!(data_str, QR_CODE)
}
@ -620,7 +615,9 @@ mod tests {
)
.expect("Failed to add optional data");
let data_str = payload_base38_representation(&qr_code_data).expect("Failed to encode");
let mut buf = [0; 1024];
let data_str = payload_base38_representation::<{ QR_CODE.len() }>(&qr_code_data, &mut buf)
.expect("Failed to encode");
assert_eq!(data_str, QR_CODE)
}
}