use core::iter::Empty;
use qrcodegen_no_heap::{QrCode, QrCodeEcc, Version};
use verhoeff::Verhoeff;
use crate::error::ErrorCode;
use crate::tlv::{EitherIter, TLVElement, TLVTag, TLV};
use crate::transport::network::mdns::CommissionableFilter;
use crate::utils::codec::base38;
use crate::utils::storage::WriteBuf;
use super::*;
#[cfg(feature = "qr-scan")]
pub mod scan;
pub const QR_PREFIX: &str = "MT:";
const LONG_BITS: usize = 12;
const VERSION_FIELD_LENGTH_IN_BITS: usize = 3;
const VENDOR_IDFIELD_LENGTH_IN_BITS: usize = 16;
const PRODUCT_IDFIELD_LENGTH_IN_BITS: usize = 16;
const COMMISSIONING_FLOW_FIELD_LENGTH_IN_BITS: usize = 2;
const RENDEZVOUS_INFO_FIELD_LENGTH_IN_BITS: usize = 8;
const PAYLOAD_DISCRIMINATOR_FIELD_LENGTH_IN_BITS: usize = LONG_BITS;
const SETUP_PINCODE_FIELD_LENGTH_IN_BITS: usize = 27;
const PADDING_FIELD_LENGTH_IN_BITS: usize = 4;
const TOTAL_PAYLOAD_DATA_SIZE_IN_BITS: usize = VERSION_FIELD_LENGTH_IN_BITS
+ VENDOR_IDFIELD_LENGTH_IN_BITS
+ PRODUCT_IDFIELD_LENGTH_IN_BITS
+ COMMISSIONING_FLOW_FIELD_LENGTH_IN_BITS
+ RENDEZVOUS_INFO_FIELD_LENGTH_IN_BITS
+ PAYLOAD_DISCRIMINATOR_FIELD_LENGTH_IN_BITS
+ SETUP_PINCODE_FIELD_LENGTH_IN_BITS
+ PADDING_FIELD_LENGTH_IN_BITS;
pub const TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES: usize = TOTAL_PAYLOAD_DATA_SIZE_IN_BITS / 8;
pub const SERIAL_NUMBER_TAG: u8 = 0x00;
pub const PBKDFITERATIONS_TAG: u8 = 0x01;
pub const BPKFSALT_TAG: u8 = 0x02;
pub const NUMBER_OFDEVICES_TAG: u8 = 0x03;
pub const COMMISSIONING_TIMEOUT_TAG: u8 = 0x04;
#[repr(u8)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CommFlowType {
Standard = 0,
UserIntent = 1,
Custom = 2,
}
pub type NoOptionalData = fn() -> Empty<Result<u8, Error>>;
pub fn no_optional_data() -> Empty<Result<u8, Error>> {
core::iter::empty()
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct QrPayload<'a, T> {
version: u8,
discovery_capabilities: DiscoveryCapabilities,
comm_flow: CommFlowType,
comm_data: BasicCommData,
vid: u16,
pid: u16,
serial_no: &'a str,
optional_data: T,
}
impl<'a, T, I> QrPayload<'a, T>
where
T: Fn() -> I,
I: Iterator<Item = Result<u8, Error>> + 'a,
{
pub const fn new_from_basic_info(
discovery_capabilities: DiscoveryCapabilities,
comm_flow: CommFlowType,
comm_data: BasicCommData,
dev_det: &'a BasicInfoConfig,
optional_data: T,
) -> Self {
Self::new(
discovery_capabilities,
comm_flow,
comm_data,
dev_det.vid,
dev_det.pid,
dev_det.serial_no,
optional_data,
)
}
pub const fn new(
discovery_capabilities: DiscoveryCapabilities,
comm_flow: CommFlowType,
comm_data: BasicCommData,
vid: u16,
pid: u16,
serial_no: &'a str,
optional_data: T,
) -> Self {
const DEFAULT_VERSION: u8 = 0;
Self {
version: DEFAULT_VERSION,
discovery_capabilities,
comm_flow,
comm_data,
vid,
pid,
serial_no,
optional_data,
}
}
pub fn is_valid(&self) -> bool {
if self.version >= 1 << VERSION_FIELD_LENGTH_IN_BITS {
return false;
}
if self.discovery_capabilities.is_empty() {
return false;
}
let password = u32::from_le_bytes(*self.comm_data.password.access());
if password >= 1 << SETUP_PINCODE_FIELD_LENGTH_IN_BITS {
return false;
}
self.check_payload_common_constraints()
}
fn check_payload_common_constraints(&self) -> bool {
#[repr(u16)]
enum VendorId {
CommonOrUnspecified = 0x0000,
TestVendor4 = 0xFFF4,
}
impl VendorId {
fn is_valid_operationally(vendor_id: u16) -> bool {
(vendor_id != Self::CommonOrUnspecified as u16)
&& (vendor_id <= Self::TestVendor4 as u16)
}
}
if self.version != 0 {
return false;
}
if !Self::is_valid_setup_pin(u32::from_le_bytes(*self.comm_data.password.access())) {
return false;
}
if VendorId::is_valid_operationally(self.vid)
&& (self.vid != VendorId::CommonOrUnspecified as u16)
{
return false;
}
if self.pid == 0 && self.vid != VendorId::CommonOrUnspecified as u16 {
return false;
}
true
}
fn is_valid_setup_pin(setup_pin: u32) -> bool {
const SETUP_PINCODE_MAXIMUM_VALUE: u32 = 99999998;
const SETUP_PINCODE_UNDEFINED_VALUE: u32 = 0;
if setup_pin == SETUP_PINCODE_UNDEFINED_VALUE
|| setup_pin > SETUP_PINCODE_MAXIMUM_VALUE
|| setup_pin == 11111111
|| setup_pin == 22222222
|| setup_pin == 33333333
|| setup_pin == 44444444
|| setup_pin == 55555555
|| setup_pin == 66666666
|| setup_pin == 77777777
|| setup_pin == 88888888
|| setup_pin == 12345678
|| setup_pin == 87654321
{
return false;
}
true
}
pub fn as_str<'b>(&self, buf: &'b mut [u8]) -> Result<(&'b str, &'b mut [u8]), Error> {
let str_len = self.emit_chars().count();
let (str_buf, remaining_buf) = buf.split_at_mut(str_len);
let mut wb = WriteBuf::new(str_buf);
for ch in self.emit_chars() {
wb.le_u8(ch? as u8)?;
}
let str = unwrap!(core::str::from_utf8(str_buf).map_err(|_| ErrorCode::InvalidData));
Ok((str, remaining_buf))
}
pub fn emit_chars(&self) -> impl Iterator<Item = Result<char, Error>> + '_ {
struct PackedBitsIterator<I>(I);
impl<I> Iterator for PackedBitsIterator<I>
where
I: Iterator<Item = Result<bool, Error>>,
{
type Item = Result<(u32, u8), Error>;
fn next(&mut self) -> Option<Self::Item> {
let mut chunk = 0;
let mut packed_bits = 0;
for index in 0..24 {
if let Some(bit) = self.0.next() {
let bit = match bit {
Ok(bit) => bit,
Err(err) => return Some(Err(err)),
};
chunk |= (bit as u32) << index;
packed_bits += 1;
} else {
break;
}
}
if packed_bits > 0 {
assert!(packed_bits % 8 == 0);
Some(Ok((chunk, packed_bits)))
} else {
None
}
}
}
"MT:"
.chars()
.map(Result::Ok)
.chain(
PackedBitsIterator(self.emit_all_bits()).flat_map(|bits| match bits {
Ok((bits, bits_count)) => {
EitherIter::First(base38::encode_bits(bits, bits_count).map(Result::Ok))
}
Err(err) => EitherIter::Second(core::iter::once(Err(err))),
}),
)
}
fn emit_all_bits(&self) -> impl Iterator<Item = Result<bool, Error>> + '_ {
Self::emit_bits(self.version as _, VERSION_FIELD_LENGTH_IN_BITS)
.chain(Self::emit_bits(
self.vid as _,
VENDOR_IDFIELD_LENGTH_IN_BITS,
))
.chain(Self::emit_bits(
self.pid as _,
PRODUCT_IDFIELD_LENGTH_IN_BITS,
))
.chain(Self::emit_bits(
self.comm_flow as _,
COMMISSIONING_FLOW_FIELD_LENGTH_IN_BITS,
))
.chain(Self::emit_bits(
self.discovery_capabilities.bits() as _,
RENDEZVOUS_INFO_FIELD_LENGTH_IN_BITS,
))
.chain(Self::emit_bits(
self.comm_data.discriminator as _,
PAYLOAD_DISCRIMINATOR_FIELD_LENGTH_IN_BITS,
))
.chain(Self::emit_bits(
u32::from_le_bytes(*self.comm_data.password.access()),
SETUP_PINCODE_FIELD_LENGTH_IN_BITS,
))
.chain(Self::emit_bits(0, PADDING_FIELD_LENGTH_IN_BITS))
.chain(
self.emit_optional_tlv_data()
.flat_map(|bits| Self::emit_maybe_bits(bits.map(|bits| (bits as _, 8)))),
)
}
fn emit_bits(input: u32, len: usize) -> impl Iterator<Item = Result<bool, Error>> {
(0..len).map(move |i| Ok((input >> i) & 1 == 1))
}
fn emit_maybe_bits(
bits: Result<(u32, usize), Error>,
) -> impl Iterator<Item = Result<bool, Error>> {
match bits {
Ok((input, len)) => EitherIter::First(Self::emit_bits(input, len)),
Err(err) => EitherIter::Second(core::iter::once(Err(err))),
}
}
fn emit_optional_tlv_data(&self) -> impl Iterator<Item = Result<u8, Error>> + '_ {
if self.serial_no.is_empty() && (self.optional_data)().next().is_none() {
return EitherIter::First(core::iter::empty());
}
let serial_no = if self.serial_no.is_empty() {
EitherIter::First(core::iter::empty())
} else {
EitherIter::Second(
TLV::utf8(TLVTag::Context(SERIAL_NUMBER_TAG), self.serial_no).into_tlv_iter(),
)
};
EitherIter::Second(
TLV::structure(TLVTag::Anonymous)
.into_tlv_iter()
.chain(serial_no)
.flat_map(TLV::result_into_bytes_iter)
.chain((self.optional_data)())
.chain(
TLV::end_container()
.into_tlv_iter()
.flat_map(TLV::result_into_bytes_iter),
),
)
}
}
impl<'a> QrPayload<'a, &'a [u8]> {
pub fn parse(qr: &str, buf: &'a mut [u8]) -> Result<Self, Error> {
let body = qr.strip_prefix(QR_PREFIX).ok_or(ErrorCode::InvalidData)?;
let mut len = 0;
for byte in base38::decode(body) {
let byte = byte?;
*buf.get_mut(len).ok_or(ErrorCode::BufferTooSmall)? = byte;
len += 1;
}
let decoded = &buf[..len];
if decoded.len() < TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES {
return Err(ErrorCode::InvalidData.into());
}
let mut reader = BitReader::new(decoded);
let version = reader.read(VERSION_FIELD_LENGTH_IN_BITS)? as u8;
let vid = reader.read(VENDOR_IDFIELD_LENGTH_IN_BITS)? as u16;
let pid = reader.read(PRODUCT_IDFIELD_LENGTH_IN_BITS)? as u16;
let comm_flow =
CommFlowType::from_bits(reader.read(COMMISSIONING_FLOW_FIELD_LENGTH_IN_BITS)? as u8)?;
let discovery_capabilities = DiscoveryCapabilities::from_bits_truncate(
reader.read(RENDEZVOUS_INFO_FIELD_LENGTH_IN_BITS)? as u8,
);
let discriminator = reader.read(PAYLOAD_DISCRIMINATOR_FIELD_LENGTH_IN_BITS)? as u16;
let passcode = reader.read(SETUP_PINCODE_FIELD_LENGTH_IN_BITS)?;
let _ = reader.read(PADDING_FIELD_LENGTH_IN_BITS)?;
let optional_data = &decoded[TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES..];
let serial_no = Self::parse_serial_no(optional_data).unwrap_or("");
let payload = Self {
version,
discovery_capabilities,
comm_flow,
comm_data: BasicCommData {
password: passcode.to_le_bytes().into(),
discriminator,
},
vid,
pid,
serial_no,
optional_data,
};
Ok(payload)
}
fn parse_serial_no(optional_data: &'a [u8]) -> Option<&'a str> {
if optional_data.is_empty() {
return None;
}
let root = TLVElement::new(optional_data);
let serial = root.structure().ok()?.find_ctx(SERIAL_NUMBER_TAG).ok()?;
serial.utf8().ok()
}
pub const fn version(&self) -> u8 {
self.version
}
pub const fn discovery_capabilities(&self) -> DiscoveryCapabilities {
self.discovery_capabilities
}
pub const fn comm_flow(&self) -> CommFlowType {
self.comm_flow
}
pub const fn discriminator(&self) -> u16 {
self.comm_data.discriminator
}
pub fn passcode(&self) -> u32 {
u32::from_le_bytes(*self.comm_data.password.access())
}
pub const fn vid(&self) -> u16 {
self.vid
}
pub const fn pid(&self) -> u16 {
self.pid
}
pub const fn serial_no(&self) -> &'a str {
self.serial_no
}
pub const fn optional_data(&self) -> &'a [u8] {
self.optional_data
}
pub fn commissionable_filter(&self) -> CommissionableFilter {
CommissionableFilter {
discriminator: Some(self.discriminator()),
..Default::default()
}
}
}
impl<'a> QrPayload<'a, ()> {
pub fn parse_pairing_code(code: &str) -> Result<Self, Error> {
const SHORT_CODE_LEN: usize = 11;
const LONG_CODE_LEN: usize = 21;
let mut digits: heapless::String<LONG_CODE_LEN> = heapless::String::new();
for ch in code.chars() {
if matches!(ch, '-' | ' ') {
continue;
}
if !ch.is_ascii_digit() {
return Err(ErrorCode::InvalidData.into());
}
digits
.push(ch)
.map_err(|_| Error::from(ErrorCode::InvalidData))?;
}
let long_form = match digits.len() {
SHORT_CODE_LEN => false,
LONG_CODE_LEN => true,
_ => return Err(ErrorCode::InvalidData.into()),
};
if !digits.validate_verhoeff_check_digit() {
return Err(ErrorCode::InvalidData.into());
}
let digit1 = Self::digits_at(&digits, 0, 1)?;
if digit1 > 7 {
return Err(ErrorCode::InvalidData.into());
}
let vid_pid_present = digit1 >> 2 == 1;
if vid_pid_present != long_form {
return Err(ErrorCode::InvalidData.into());
}
let disc_bits_11_10 = (digit1 & 0x3) as u16;
let group = Self::digits_at(&digits, 1, 5)?;
if group > 0xFFFF {
return Err(ErrorCode::InvalidData.into());
}
let disc_bits_9_8 = ((group >> 14) & 0x3) as u16;
let passcode_low = group & 0x3FFF;
let passcode_high = Self::digits_at(&digits, 6, 4)?;
if passcode_high > 0x1FFF {
return Err(ErrorCode::InvalidData.into());
}
let passcode = (passcode_high << 14) | passcode_low;
let short_discriminator = (disc_bits_11_10 << 2) | disc_bits_9_8;
let (vid, pid) = if long_form {
let vid = Self::digits_at(&digits, 10, 5)?;
let pid = Self::digits_at(&digits, 15, 5)?;
if vid > 0xFFFF || pid > 0xFFFF {
return Err(ErrorCode::InvalidData.into());
}
(vid as u16, pid as u16)
} else {
(0, 0)
};
Ok(Self {
version: 0,
discovery_capabilities: DiscoveryCapabilities::empty(),
comm_flow: if long_form {
CommFlowType::Custom
} else {
CommFlowType::Standard
},
comm_data: BasicCommData {
password: passcode.to_le_bytes().into(),
discriminator: short_discriminator,
},
vid,
pid,
serial_no: "",
optional_data: (),
})
}
fn digits_at(digits: &str, offset: usize, len: usize) -> Result<u32, Error> {
digits
.get(offset..offset + len)
.ok_or(ErrorCode::InvalidData)?
.parse()
.map_err(|_| ErrorCode::InvalidData.into())
}
pub const fn short_discriminator(&self) -> u8 {
self.comm_data.discriminator as u8
}
pub fn passcode(&self) -> u32 {
u32::from_le_bytes(*self.comm_data.password.access())
}
pub fn vid_pid(&self) -> Option<(u16, u16)> {
(!matches!(self.comm_flow, CommFlowType::Standard)).then_some((self.vid, self.pid))
}
pub fn comm_flow(&self) -> Option<CommFlowType> {
matches!(self.comm_flow, CommFlowType::Standard).then_some(CommFlowType::Standard)
}
pub fn commissionable_filter(&self) -> CommissionableFilter {
CommissionableFilter {
short_discriminator: Some(self.short_discriminator()),
..Default::default()
}
}
}
impl CommFlowType {
fn from_bits(bits: u8) -> Result<Self, Error> {
match bits {
0 => Ok(Self::Standard),
1 => Ok(Self::UserIntent),
2 => Ok(Self::Custom),
_ => Err(ErrorCode::InvalidData.into()),
}
}
}
struct BitReader<'a> {
data: &'a [u8],
pos: usize,
}
impl<'a> BitReader<'a> {
const fn new(data: &'a [u8]) -> Self {
Self { data, pos: 0 }
}
fn read(&mut self, len: usize) -> Result<u32, Error> {
debug_assert!(len <= 32);
if self.pos + len > self.data.len() * 8 {
return Err(ErrorCode::InvalidData.into());
}
let mut value = 0u32;
for i in 0..len {
let bit_pos = self.pos + i;
let byte = self.data[bit_pos / 8];
let bit = (byte >> (bit_pos % 8)) & 1;
value |= (bit as u32) << i;
}
self.pos += len;
Ok(value)
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum QrTextType {
Ascii,
Ansi,
Unicode,
}
pub struct Qr<'a>(QrCode<'a>);
impl<'a> Qr<'a> {
pub fn compute(text: &str, tmp_buf: &mut [u8], out_buf: &'a mut [u8]) -> Result<Self, Error> {
let needed_version = Version::new(Self::version(text));
let qr = QrCode::encode_text(
text,
tmp_buf,
out_buf,
QrCodeEcc::Medium,
needed_version,
needed_version,
None,
false,
)
.map_err(|_| ErrorCode::BufferTooSmall)?;
Ok(Self(qr))
}
pub fn size(&self) -> u32 {
self.0.size() as _
}
pub fn get_module(&self, x: i32, y: i32) -> bool {
self.0.get_module(x, y)
}
pub fn as_str<'b>(
&self,
text_type: QrTextType,
border: u8,
invert: bool,
out_buf: &'b mut [u8],
) -> Result<(&'b str, &'b mut [u8]), Error> {
let mut offset = 0;
for c in self.emit_chars(text_type, border, invert) {
let mut dst = [0; 4];
let bytes = c.encode_utf8(&mut dst).as_bytes();
if offset + bytes.len() > out_buf.len() {
return Err(ErrorCode::BufferTooSmall.into());
} else {
out_buf[offset..offset + bytes.len()].copy_from_slice(bytes);
offset += bytes.len();
}
}
let (str_buf, remaining_buf) = out_buf.split_at_mut(offset);
let str = unwrap!(core::str::from_utf8(str_buf).map_err(|_| ErrorCode::InvalidData));
Ok((str, remaining_buf))
}
pub fn line_as_str<'b>(
&self,
text_type: QrTextType,
border: u8,
invert: bool,
nl: bool,
y: i32,
out_buf: &'b mut [u8],
) -> Result<(&'b str, &'b mut [u8]), Error> {
let mut offset = 0;
for c in self.emit_line_chars(text_type, border, invert, nl, y) {
let mut dst = [0; 4];
let bytes = c.encode_utf8(&mut dst).as_bytes();
if offset + bytes.len() > out_buf.len() {
return Err(ErrorCode::BufferTooSmall.into());
} else {
out_buf[offset..offset + bytes.len()].copy_from_slice(bytes);
offset += bytes.len();
}
}
let (str_buf, remaining_buf) = out_buf.split_at_mut(offset);
let str = unwrap!(core::str::from_utf8(str_buf).map_err(|_| ErrorCode::InvalidData));
Ok((str, remaining_buf))
}
pub fn lines_range(
&self,
text_type: QrTextType,
border: u8,
) -> impl Iterator<Item = i32> + '_ + 'a {
let iborder: i32 = border as _;
(-iborder..self.size() as i32 + iborder)
.filter(move |y| !matches!(text_type, QrTextType::Unicode) || (*y - -iborder) % 2 == 0)
}
pub fn emit_chars(
&self,
text_type: QrTextType,
border: u8,
invert: bool,
) -> impl Iterator<Item = char> + use<'_, 'a> {
self.lines_range(text_type, border)
.flat_map(move |y| self.emit_line_chars(text_type, border, invert, true, y))
}
pub fn emit_line_chars(
&self,
text_type: QrTextType,
border: u8,
invert: bool,
nl: bool,
y: i32,
) -> impl Iterator<Item = char> + use<'_, 'a> {
let border: i32 = border as _;
(-border..self.size() as i32 + border + 1)
.map(move |x| (x, y))
.map(move |(x, y)| {
if x < self.size() as i32 + border {
let white = !self.get_module(x, y) ^ invert;
match text_type {
QrTextType::Ascii => {
if white {
"#"
} else {
" "
}
}
QrTextType::Ansi => {
let prev_white = if x > -border {
Some(self.get_module(x - 1, y))
} else {
None
}
.map(|prev_white| !prev_white ^ invert);
if prev_white != Some(white) {
if white {
"\x1b[47m "
} else {
"\x1b[40m "
}
} else {
" "
}
}
QrTextType::Unicode => {
if white == !self.get_module(x, y + 1) ^ invert {
if white {
"\u{2588}"
} else {
" "
}
} else if white {
"\u{2580}"
} else {
"\u{2584}"
}
}
}
} else {
match text_type {
QrTextType::Ascii => {
if nl {
"\n"
} else {
""
}
}
_ => {
if nl {
"\x1b[0m\n"
} else {
"\x1b[0m"
}
}
}
}
})
.flat_map(str::chars)
}
fn version(qr_code_text: &str) -> u8 {
match qr_code_text.len() {
0..=38 => 2,
39..=61 => 3,
62..=90 => 4,
_ => 5,
}
}
}
pub enum QrTextRenderer<'a> {
Ascii(Qr<'a>),
Ansi(Qr<'a>),
Unicode(Qr<'a>),
}
impl<'a> QrTextRenderer<'a> {
pub fn render<'b>(
&self,
border: u8,
invert: bool,
out_buf: &'b mut [u8],
) -> Result<(&'b str, &'b mut [u8]), Error> {
let mut offset = 0;
for c in self.render_iter(border, invert) {
let mut dst = [0; 4];
let bytes = c.encode_utf8(&mut dst).as_bytes();
if offset + bytes.len() > out_buf.len() {
return Err(ErrorCode::BufferTooSmall.into());
} else {
out_buf[offset..offset + bytes.len()].copy_from_slice(bytes);
offset += bytes.len();
}
}
let (str_buf, remaining_buf) = out_buf.split_at_mut(offset);
let str = unwrap!(core::str::from_utf8(str_buf).map_err(|_| ErrorCode::InvalidData));
Ok((str, remaining_buf))
}
pub fn render_line<'b>(
&self,
border: u8,
invert: bool,
nl: bool,
y: i32,
out_buf: &'b mut [u8],
) -> Result<(&'b str, &'b mut [u8]), Error> {
let mut offset = 0;
for c in self.render_line_iter(border, invert, nl, y) {
let mut dst = [0; 4];
let bytes = c.encode_utf8(&mut dst).as_bytes();
if offset + bytes.len() > out_buf.len() {
return Err(ErrorCode::BufferTooSmall.into());
} else {
out_buf[offset..offset + bytes.len()].copy_from_slice(bytes);
offset += bytes.len();
}
}
let (str_buf, remaining_buf) = out_buf.split_at_mut(offset);
let str = unwrap!(core::str::from_utf8(str_buf).map_err(|_| ErrorCode::InvalidData));
Ok((str, remaining_buf))
}
pub fn lines_range(&self, border: u8) -> impl Iterator<Item = i32> + '_ + 'a {
let unicode = matches!(self, Self::Unicode(_));
let iborder: i32 = border as _;
(-iborder..self.qr().size() as i32 + iborder)
.filter(move |y| !unicode || (*y - -iborder) % 2 == 0)
}
pub fn render_iter(
&self,
border: u8,
invert: bool,
) -> impl Iterator<Item = char> + use<'_, 'a> {
self.lines_range(border)
.flat_map(move |y| self.render_line_iter(border, invert, true, y))
}
pub fn render_line_iter(
&self,
border: u8,
invert: bool,
nl: bool,
y: i32,
) -> impl Iterator<Item = char> + use<'_, 'a> {
let border: i32 = border as _;
(-border..self.qr().size() as i32 + border + 1)
.map(move |x| (x, y))
.map(move |(x, y)| {
if x < self.qr().size() as i32 + border {
let white = !self.qr().get_module(x, y) ^ invert;
match self {
Self::Ascii(_) => {
if white {
"#"
} else {
" "
}
}
Self::Ansi(_) => {
let prev_white = if x > -border {
Some(self.qr().get_module(x - 1, y))
} else {
None
}
.map(|prev_white| !prev_white ^ invert);
if prev_white != Some(white) {
if white {
"\x1b[47m "
} else {
"\x1b[40m "
}
} else {
" "
}
}
Self::Unicode(_) => {
if white == !self.qr().get_module(x, y + 1) ^ invert {
if white {
"\u{2588}"
} else {
" "
}
} else if white {
"\u{2580}"
} else {
"\u{2584}"
}
}
}
} else {
match self {
Self::Ascii(_) => {
if nl {
"\n"
} else {
""
}
}
_ => {
if nl {
"\x1b[0m\n"
} else {
"\x1b[0m"
}
}
}
}
})
.flat_map(str::chars)
}
#[inline(always)]
pub fn qr(&self) -> &Qr<'a> {
match self {
Self::Ascii(qr) => qr,
Self::Ansi(qr) => qr,
Self::Unicode(qr) => qr,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_base38_encode() {
const QR_CODE: &str = "MT:YNJV7VSC00CMVH7SR00";
let comm_data = BasicCommData {
password: 34567890_u32.to_le_bytes().into(),
discriminator: 2976,
};
let dev_det = BasicInfoConfig {
vid: 9050,
pid: 65279,
..Default::default()
};
let disc_cap = DiscoveryCapabilities::BLE;
let qr_code_data = QrPayload::new_from_basic_info(
disc_cap,
CommFlowType::Standard,
comm_data,
&dev_det,
no_optional_data,
);
let mut buf = [0; 1024];
let data_str = unwrap!(qr_code_data.as_str(&mut buf), "Failed to encode").0;
assert_eq!(data_str, QR_CODE)
}
#[test]
fn can_base38_encode_with_vendor_data() {
const QR_CODE: &str = "MT:-24J0AFN00KA064IJ3P0IXZB0DK5N1K8SQ1RYCU1-A40";
let comm_data = BasicCommData {
password: 20202021_u32.to_le_bytes().into(),
discriminator: 3840,
};
let dev_det = BasicInfoConfig {
vid: 65521,
pid: 32769,
serial_no: "1234567890",
..Default::default()
};
let disc_cap = DiscoveryCapabilities::IP;
let qr_code_data = QrPayload::new_from_basic_info(
disc_cap,
CommFlowType::Standard,
comm_data,
&dev_det,
no_optional_data,
);
let mut buf = [0; 1024];
let data_str = unwrap!(qr_code_data.as_str(&mut buf), "Failed to encode").0;
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; const OPTIONAL_DEFAULT_STRING_VALUE: &str = "myData";
const OPTIONAL_DEFAULT_INT_TAG: u8 = 0x83; const OPTIONAL_DEFAULT_INT_VALUE: i32 = 65550;
let comm_data = BasicCommData {
password: 20202021_u32.to_le_bytes().into(),
discriminator: 3840,
};
let dev_det = BasicInfoConfig {
vid: 65521,
pid: 32769,
serial_no: "1234567890",
..Default::default()
};
let disc_cap = DiscoveryCapabilities::IP;
let optional_data = || {
TLV::utf8(
TLVTag::Context(OPTIONAL_DEFAULT_STRING_TAG),
OPTIONAL_DEFAULT_STRING_VALUE,
)
.into_tlv_iter()
.chain(
TLV::i32(
TLVTag::Context(OPTIONAL_DEFAULT_INT_TAG),
OPTIONAL_DEFAULT_INT_VALUE,
)
.into_tlv_iter(),
)
.flat_map(TLV::result_into_bytes_iter)
};
let qr_code_data = QrPayload::new_from_basic_info(
disc_cap,
CommFlowType::Standard,
comm_data,
&dev_det,
optional_data,
);
let mut buf = [0; 1024];
let data_str = unwrap!(qr_code_data.as_str(&mut buf), "Failed to encode").0;
assert_eq!(data_str, QR_CODE)
}
#[test]
fn parse_qr_basic() {
const QR_CODE: &str = "MT:YNJV7VSC00CMVH7SR00";
let mut buf = [0; 128];
let payload = unwrap!(QrPayload::parse(QR_CODE, &mut buf), "Failed to parse");
assert_eq!(payload.version(), 0);
assert_eq!(payload.vid(), 9050);
assert_eq!(payload.pid(), 65279);
assert_eq!(payload.comm_flow(), CommFlowType::Standard);
assert_eq!(payload.discovery_capabilities(), DiscoveryCapabilities::BLE);
assert_eq!(payload.discriminator(), 2976);
assert_eq!(payload.passcode(), 34567890);
assert_eq!(payload.serial_no(), "");
assert!(payload.optional_data().is_empty());
}
#[test]
fn parse_qr_with_serial_no() {
const QR_CODE: &str = "MT:-24J0AFN00KA064IJ3P0IXZB0DK5N1K8SQ1RYCU1-A40";
let mut buf = [0; 128];
let payload = unwrap!(QrPayload::parse(QR_CODE, &mut buf), "Failed to parse");
assert_eq!(payload.vid(), 65521);
assert_eq!(payload.pid(), 32769);
assert_eq!(payload.discovery_capabilities(), DiscoveryCapabilities::IP);
assert_eq!(payload.discriminator(), 3840);
assert_eq!(payload.passcode(), 20202021);
assert_eq!(payload.serial_no(), "1234567890");
assert!(!payload.optional_data().is_empty());
}
#[test]
fn parse_qr_round_trips_through_encode() {
const QR_CODE: &str = "MT:-24J0AFN00KA064IJ3P0IXZB0DK5N1K8SQ1RYCU1-A40";
let mut buf = [0; 128];
let payload = unwrap!(QrPayload::parse(QR_CODE, &mut buf), "Failed to parse");
let reencoded = QrPayload::new(
payload.discovery_capabilities(),
payload.comm_flow(),
payload.comm_data.clone(),
payload.vid(),
payload.pid(),
payload.serial_no(),
no_optional_data,
);
let mut out = [0; 256];
let s = unwrap!(reencoded.as_str(&mut out), "Failed to encode").0;
assert_eq!(s, QR_CODE);
}
#[test]
fn parse_pairing_code_round_trips_with_encoder() {
for (code, passcode, discriminator) in [
("00876800071", 123456_u32, 250_u16),
("26318621095", 34567890, 2976),
] {
let payload = unwrap!(QrPayload::parse_pairing_code(code), "Failed to parse");
assert_eq!(payload.passcode(), passcode);
assert_eq!(payload.short_discriminator(), (discriminator >> 8) as u8);
assert_eq!(payload.comm_flow(), Some(CommFlowType::Standard));
assert_eq!(payload.vid_pid(), None);
assert!(payload.discovery_capabilities.is_empty());
let comm_data = BasicCommData {
password: payload.passcode().to_le_bytes().into(),
discriminator,
};
assert_eq!(comm_data.compute_pairing_code(), code);
}
}
#[test]
fn commissionable_filter_uses_the_right_discriminator_field() {
let mut buf = [0; 128];
let qr = unwrap!(
QrPayload::parse("MT:-24J0AFN00KA064IJ3P0IXZB0DK5N1K8SQ1RYCU1-A40", &mut buf),
"Failed to parse"
);
let filter = qr.commissionable_filter();
assert_eq!(filter.discriminator, Some(3840));
assert_eq!(filter.short_discriminator, None);
let manual = unwrap!(QrPayload::parse_pairing_code("34970112332"), "Failed");
let filter = manual.commissionable_filter();
assert_eq!(filter.short_discriminator, Some(15));
assert_eq!(filter.discriminator, None);
assert_eq!(filter.vendor_id, None);
assert_eq!(filter.product_id, None);
}
#[test]
fn parse_pairing_code_accepts_pretty_form() {
let compact = unwrap!(QrPayload::parse_pairing_code("34970112332"), "compact");
let pretty = unwrap!(QrPayload::parse_pairing_code("3497-0112-332"), "pretty");
assert_eq!(compact.passcode(), 20202021);
assert_eq!(compact.short_discriminator(), 15); assert_eq!(pretty.passcode(), compact.passcode());
assert_eq!(pretty.short_discriminator(), compact.short_discriminator());
}
#[test]
fn parse_pairing_code_rejects_bad_input() {
assert!(QrPayload::parse_pairing_code("34970112333").is_err());
assert!(QrPayload::parse_pairing_code("3497011233X").is_err());
assert!(QrPayload::parse_pairing_code("3497011233").is_err());
assert!(QrPayload::parse_pairing_code("94970112332").is_err());
assert!(QrPayload::parse_pairing_code("74970112332").is_err());
}
#[test]
fn parse_qr_rejects_bad_input() {
let mut buf = [0; 128];
assert!(QrPayload::parse("YNJV7VSC00CMVH7SR00", &mut buf).is_err());
assert!(QrPayload::parse("MT:YNJV7VSC00CMVH7SR0!", &mut buf).is_err());
assert!(QrPayload::parse("MT:00", &mut buf).is_err());
}
}