1use core::iter::Empty;
19
20use qrcodegen_no_heap::{QrCode, QrCodeEcc, Version};
21
22use verhoeff::Verhoeff;
23
24use crate::error::ErrorCode;
25use crate::tlv::{EitherIter, TLVElement, TLVTag, TLV};
26use crate::transport::network::mdns::CommissionableFilter;
27use crate::utils::codec::base38;
28use crate::utils::storage::WriteBuf;
29
30use super::*;
31
32#[cfg(feature = "qr-scan")]
33pub mod scan;
34
35pub const QR_PREFIX: &str = "MT:";
37
38const LONG_BITS: usize = 12;
40const VERSION_FIELD_LENGTH_IN_BITS: usize = 3;
41const VENDOR_IDFIELD_LENGTH_IN_BITS: usize = 16;
42const PRODUCT_IDFIELD_LENGTH_IN_BITS: usize = 16;
43const COMMISSIONING_FLOW_FIELD_LENGTH_IN_BITS: usize = 2;
44const RENDEZVOUS_INFO_FIELD_LENGTH_IN_BITS: usize = 8;
45const PAYLOAD_DISCRIMINATOR_FIELD_LENGTH_IN_BITS: usize = LONG_BITS;
46const SETUP_PINCODE_FIELD_LENGTH_IN_BITS: usize = 27;
47const PADDING_FIELD_LENGTH_IN_BITS: usize = 4;
48const TOTAL_PAYLOAD_DATA_SIZE_IN_BITS: usize = VERSION_FIELD_LENGTH_IN_BITS
49 + VENDOR_IDFIELD_LENGTH_IN_BITS
50 + PRODUCT_IDFIELD_LENGTH_IN_BITS
51 + COMMISSIONING_FLOW_FIELD_LENGTH_IN_BITS
52 + RENDEZVOUS_INFO_FIELD_LENGTH_IN_BITS
53 + PAYLOAD_DISCRIMINATOR_FIELD_LENGTH_IN_BITS
54 + SETUP_PINCODE_FIELD_LENGTH_IN_BITS
55 + PADDING_FIELD_LENGTH_IN_BITS;
56
57pub const TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES: usize = TOTAL_PAYLOAD_DATA_SIZE_IN_BITS / 8;
58
59pub const SERIAL_NUMBER_TAG: u8 = 0x00;
61pub const PBKDFITERATIONS_TAG: u8 = 0x01;
62pub const BPKFSALT_TAG: u8 = 0x02;
63pub const NUMBER_OFDEVICES_TAG: u8 = 0x03;
64pub const COMMISSIONING_TIMEOUT_TAG: u8 = 0x04;
65
66#[repr(u8)]
68#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
69#[cfg_attr(feature = "defmt", derive(defmt::Format))]
70pub enum CommFlowType {
71 Standard = 0,
73 UserIntent = 1,
75 Custom = 2,
77}
78
79pub type NoOptionalData = fn() -> Empty<Result<u8, Error>>;
81
82pub fn no_optional_data() -> Empty<Result<u8, Error>> {
84 core::iter::empty()
85}
86
87#[derive(Debug, Clone)]
89#[cfg_attr(feature = "defmt", derive(defmt::Format))]
90pub struct QrPayload<'a, T> {
91 version: u8,
93 discovery_capabilities: DiscoveryCapabilities,
95 comm_flow: CommFlowType,
97 comm_data: BasicCommData,
99 vid: u16,
101 pid: u16,
103 serial_no: &'a str,
105 optional_data: T,
108}
109
110impl<'a, T, I> QrPayload<'a, T>
111where
112 T: Fn() -> I,
113 I: Iterator<Item = Result<u8, Error>> + 'a,
114{
115 pub const fn new_from_basic_info(
125 discovery_capabilities: DiscoveryCapabilities,
126 comm_flow: CommFlowType,
127 comm_data: BasicCommData,
128 dev_det: &'a BasicInfoConfig,
129 optional_data: T,
130 ) -> Self {
131 Self::new(
132 discovery_capabilities,
133 comm_flow,
134 comm_data,
135 dev_det.vid,
136 dev_det.pid,
137 dev_det.serial_no,
138 optional_data,
139 )
140 }
141
142 pub const fn new(
154 discovery_capabilities: DiscoveryCapabilities,
155 comm_flow: CommFlowType,
156 comm_data: BasicCommData,
157 vid: u16,
158 pid: u16,
159 serial_no: &'a str,
160 optional_data: T,
161 ) -> Self {
162 const DEFAULT_VERSION: u8 = 0;
163
164 Self {
165 version: DEFAULT_VERSION,
166 discovery_capabilities,
167 comm_flow,
168 comm_data,
169 vid,
170 pid,
171 serial_no,
172 optional_data,
173 }
174 }
175
176 pub fn is_valid(&self) -> bool {
182 if self.version >= 1 << VERSION_FIELD_LENGTH_IN_BITS {
184 return false;
185 }
186
187 if self.discovery_capabilities.is_empty() {
188 return false;
189 }
190
191 let password = u32::from_le_bytes(*self.comm_data.password.access());
192 if password >= 1 << SETUP_PINCODE_FIELD_LENGTH_IN_BITS {
193 return false;
194 }
195
196 self.check_payload_common_constraints()
197 }
198
199 fn check_payload_common_constraints(&self) -> bool {
200 #[repr(u16)]
201 enum VendorId {
202 CommonOrUnspecified = 0x0000,
203 TestVendor4 = 0xFFF4,
204 }
205
206 impl VendorId {
207 fn is_valid_operationally(vendor_id: u16) -> bool {
208 (vendor_id != Self::CommonOrUnspecified as u16)
209 && (vendor_id <= Self::TestVendor4 as u16)
210 }
211 }
212
213 if self.version != 0 {
215 return false;
216 }
217
218 if !Self::is_valid_setup_pin(u32::from_le_bytes(*self.comm_data.password.access())) {
219 return false;
220 }
221
222 if VendorId::is_valid_operationally(self.vid)
224 && (self.vid != VendorId::CommonOrUnspecified as u16)
225 {
226 return false;
227 }
228
229 if self.pid == 0 && self.vid != VendorId::CommonOrUnspecified as u16 {
234 return false;
235 }
236
237 true
238 }
239
240 fn is_valid_setup_pin(setup_pin: u32) -> bool {
241 const SETUP_PINCODE_MAXIMUM_VALUE: u32 = 99999998;
242 const SETUP_PINCODE_UNDEFINED_VALUE: u32 = 0;
243
244 if setup_pin == SETUP_PINCODE_UNDEFINED_VALUE
247 || setup_pin > SETUP_PINCODE_MAXIMUM_VALUE
248 || setup_pin == 11111111
249 || setup_pin == 22222222
250 || setup_pin == 33333333
251 || setup_pin == 44444444
252 || setup_pin == 55555555
253 || setup_pin == 66666666
254 || setup_pin == 77777777
255 || setup_pin == 88888888
256 || setup_pin == 12345678
257 || setup_pin == 87654321
258 {
259 return false;
260 }
261
262 true
263 }
264
265 pub fn as_str<'b>(&self, buf: &'b mut [u8]) -> Result<(&'b str, &'b mut [u8]), Error> {
274 let str_len = self.emit_chars().count();
275
276 let (str_buf, remaining_buf) = buf.split_at_mut(str_len);
277
278 let mut wb = WriteBuf::new(str_buf);
279 for ch in self.emit_chars() {
280 wb.le_u8(ch? as u8)?;
281 }
282
283 let str = unwrap!(core::str::from_utf8(str_buf).map_err(|_| ErrorCode::InvalidData));
285
286 Ok((str, remaining_buf))
287 }
288
289 pub fn emit_chars(&self) -> impl Iterator<Item = Result<char, Error>> + '_ {
291 struct PackedBitsIterator<I>(I);
292
293 impl<I> Iterator for PackedBitsIterator<I>
294 where
295 I: Iterator<Item = Result<bool, Error>>,
296 {
297 type Item = Result<(u32, u8), Error>;
298
299 fn next(&mut self) -> Option<Self::Item> {
300 let mut chunk = 0;
301 let mut packed_bits = 0;
302
303 for index in 0..24 {
304 if let Some(bit) = self.0.next() {
306 let bit = match bit {
307 Ok(bit) => bit,
308 Err(err) => return Some(Err(err)),
309 };
310
311 chunk |= (bit as u32) << index;
312 packed_bits += 1;
313 } else {
314 break;
315 }
316 }
317
318 if packed_bits > 0 {
319 assert!(packed_bits % 8 == 0);
320
321 Some(Ok((chunk, packed_bits)))
322 } else {
323 None
324 }
325 }
326 }
327
328 "MT:"
329 .chars()
330 .map(Result::Ok)
331 .chain(
332 PackedBitsIterator(self.emit_all_bits()).flat_map(|bits| match bits {
333 Ok((bits, bits_count)) => {
334 EitherIter::First(base38::encode_bits(bits, bits_count).map(Result::Ok))
335 }
336 Err(err) => EitherIter::Second(core::iter::once(Err(err))),
337 }),
338 )
339 }
340
341 fn emit_all_bits(&self) -> impl Iterator<Item = Result<bool, Error>> + '_ {
342 Self::emit_bits(self.version as _, VERSION_FIELD_LENGTH_IN_BITS)
343 .chain(Self::emit_bits(
344 self.vid as _,
345 VENDOR_IDFIELD_LENGTH_IN_BITS,
346 ))
347 .chain(Self::emit_bits(
348 self.pid as _,
349 PRODUCT_IDFIELD_LENGTH_IN_BITS,
350 ))
351 .chain(Self::emit_bits(
352 self.comm_flow as _,
353 COMMISSIONING_FLOW_FIELD_LENGTH_IN_BITS,
354 ))
355 .chain(Self::emit_bits(
356 self.discovery_capabilities.bits() as _,
357 RENDEZVOUS_INFO_FIELD_LENGTH_IN_BITS,
358 ))
359 .chain(Self::emit_bits(
360 self.comm_data.discriminator as _,
361 PAYLOAD_DISCRIMINATOR_FIELD_LENGTH_IN_BITS,
362 ))
363 .chain(Self::emit_bits(
364 u32::from_le_bytes(*self.comm_data.password.access()),
365 SETUP_PINCODE_FIELD_LENGTH_IN_BITS,
366 ))
367 .chain(Self::emit_bits(0, PADDING_FIELD_LENGTH_IN_BITS))
368 .chain(
369 self.emit_optional_tlv_data()
370 .flat_map(|bits| Self::emit_maybe_bits(bits.map(|bits| (bits as _, 8)))),
371 )
372 }
373
374 fn emit_bits(input: u32, len: usize) -> impl Iterator<Item = Result<bool, Error>> {
375 (0..len).map(move |i| Ok((input >> i) & 1 == 1))
376 }
377
378 fn emit_maybe_bits(
379 bits: Result<(u32, usize), Error>,
380 ) -> impl Iterator<Item = Result<bool, Error>> {
381 match bits {
382 Ok((input, len)) => EitherIter::First(Self::emit_bits(input, len)),
383 Err(err) => EitherIter::Second(core::iter::once(Err(err))),
384 }
385 }
386
387 fn emit_optional_tlv_data(&self) -> impl Iterator<Item = Result<u8, Error>> + '_ {
388 if self.serial_no.is_empty() && (self.optional_data)().next().is_none() {
389 return EitherIter::First(core::iter::empty());
390 }
391
392 let serial_no = if self.serial_no.is_empty() {
393 EitherIter::First(core::iter::empty())
394 } else {
395 EitherIter::Second(
396 TLV::utf8(TLVTag::Context(SERIAL_NUMBER_TAG), self.serial_no).into_tlv_iter(),
397 )
398 };
399
400 EitherIter::Second(
401 TLV::structure(TLVTag::Anonymous)
402 .into_tlv_iter()
403 .chain(serial_no)
404 .flat_map(TLV::result_into_bytes_iter)
405 .chain((self.optional_data)())
406 .chain(
407 TLV::end_container()
408 .into_tlv_iter()
409 .flat_map(TLV::result_into_bytes_iter),
410 ),
411 )
412 }
413}
414
415impl<'a> QrPayload<'a, &'a [u8]> {
416 pub fn parse(qr: &str, buf: &'a mut [u8]) -> Result<Self, Error> {
436 let body = qr.strip_prefix(QR_PREFIX).ok_or(ErrorCode::InvalidData)?;
438
439 let mut len = 0;
441 for byte in base38::decode(body) {
442 let byte = byte?;
443 *buf.get_mut(len).ok_or(ErrorCode::BufferTooSmall)? = byte;
444 len += 1;
445 }
446 let decoded = &buf[..len];
447
448 if decoded.len() < TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES {
452 return Err(ErrorCode::InvalidData.into());
453 }
454
455 let mut reader = BitReader::new(decoded);
456
457 let version = reader.read(VERSION_FIELD_LENGTH_IN_BITS)? as u8;
460 let vid = reader.read(VENDOR_IDFIELD_LENGTH_IN_BITS)? as u16;
461 let pid = reader.read(PRODUCT_IDFIELD_LENGTH_IN_BITS)? as u16;
462 let comm_flow =
463 CommFlowType::from_bits(reader.read(COMMISSIONING_FLOW_FIELD_LENGTH_IN_BITS)? as u8)?;
464 let discovery_capabilities = DiscoveryCapabilities::from_bits_truncate(
465 reader.read(RENDEZVOUS_INFO_FIELD_LENGTH_IN_BITS)? as u8,
466 );
467 let discriminator = reader.read(PAYLOAD_DISCRIMINATOR_FIELD_LENGTH_IN_BITS)? as u16;
468 let passcode = reader.read(SETUP_PINCODE_FIELD_LENGTH_IN_BITS)?;
469 let _ = reader.read(PADDING_FIELD_LENGTH_IN_BITS)?;
471
472 let optional_data = &decoded[TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES..];
474
475 let serial_no = Self::parse_serial_no(optional_data).unwrap_or("");
478
479 let payload = Self {
480 version,
481 discovery_capabilities,
482 comm_flow,
483 comm_data: BasicCommData {
484 password: passcode.to_le_bytes().into(),
485 discriminator,
486 },
487 vid,
488 pid,
489 serial_no,
490 optional_data,
491 };
492
493 Ok(payload)
494 }
495
496 fn parse_serial_no(optional_data: &'a [u8]) -> Option<&'a str> {
499 if optional_data.is_empty() {
500 return None;
501 }
502
503 let root = TLVElement::new(optional_data);
504 let serial = root.structure().ok()?.find_ctx(SERIAL_NUMBER_TAG).ok()?;
505
506 serial.utf8().ok()
507 }
508
509 pub const fn version(&self) -> u8 {
511 self.version
512 }
513
514 pub const fn discovery_capabilities(&self) -> DiscoveryCapabilities {
516 self.discovery_capabilities
517 }
518
519 pub const fn comm_flow(&self) -> CommFlowType {
521 self.comm_flow
522 }
523
524 pub const fn discriminator(&self) -> u16 {
526 self.comm_data.discriminator
527 }
528
529 pub fn passcode(&self) -> u32 {
531 u32::from_le_bytes(*self.comm_data.password.access())
532 }
533
534 pub const fn vid(&self) -> u16 {
536 self.vid
537 }
538
539 pub const fn pid(&self) -> u16 {
541 self.pid
542 }
543
544 pub const fn serial_no(&self) -> &'a str {
546 self.serial_no
547 }
548
549 pub const fn optional_data(&self) -> &'a [u8] {
551 self.optional_data
552 }
553
554 pub fn commissionable_filter(&self) -> CommissionableFilter {
568 CommissionableFilter {
569 discriminator: Some(self.discriminator()),
570 ..Default::default()
571 }
572 }
573}
574
575impl<'a> QrPayload<'a, ()> {
576 pub fn parse_pairing_code(code: &str) -> Result<Self, Error> {
608 const SHORT_CODE_LEN: usize = 11;
610 const LONG_CODE_LEN: usize = 21;
612
613 let mut digits: heapless::String<LONG_CODE_LEN> = heapless::String::new();
615 for ch in code.chars() {
616 if matches!(ch, '-' | ' ') {
617 continue;
618 }
619
620 if !ch.is_ascii_digit() {
621 return Err(ErrorCode::InvalidData.into());
622 }
623
624 digits
625 .push(ch)
626 .map_err(|_| Error::from(ErrorCode::InvalidData))?;
627 }
628
629 let long_form = match digits.len() {
630 SHORT_CODE_LEN => false,
631 LONG_CODE_LEN => true,
632 _ => return Err(ErrorCode::InvalidData.into()),
633 };
634
635 if !digits.validate_verhoeff_check_digit() {
637 return Err(ErrorCode::InvalidData.into());
638 }
639
640 let digit1 = Self::digits_at(&digits, 0, 1)?;
645 if digit1 > 7 {
646 return Err(ErrorCode::InvalidData.into());
647 }
648
649 let vid_pid_present = digit1 >> 2 == 1;
650 if vid_pid_present != long_form {
652 return Err(ErrorCode::InvalidData.into());
653 }
654
655 let disc_bits_11_10 = (digit1 & 0x3) as u16;
657
658 let group = Self::digits_at(&digits, 1, 5)?;
660 if group > 0xFFFF {
661 return Err(ErrorCode::InvalidData.into());
662 }
663
664 let disc_bits_9_8 = ((group >> 14) & 0x3) as u16;
666 let passcode_low = group & 0x3FFF;
667
668 let passcode_high = Self::digits_at(&digits, 6, 4)?;
670 if passcode_high > 0x1FFF {
671 return Err(ErrorCode::InvalidData.into());
672 }
673
674 let passcode = (passcode_high << 14) | passcode_low;
675
676 let short_discriminator = (disc_bits_11_10 << 2) | disc_bits_9_8;
679
680 let (vid, pid) = if long_form {
682 let vid = Self::digits_at(&digits, 10, 5)?;
683 let pid = Self::digits_at(&digits, 15, 5)?;
684
685 if vid > 0xFFFF || pid > 0xFFFF {
686 return Err(ErrorCode::InvalidData.into());
687 }
688
689 (vid as u16, pid as u16)
690 } else {
691 (0, 0)
692 };
693
694 Ok(Self {
695 version: 0,
698 discovery_capabilities: DiscoveryCapabilities::empty(),
701 comm_flow: if long_form {
704 CommFlowType::Custom
705 } else {
706 CommFlowType::Standard
707 },
708 comm_data: BasicCommData {
709 password: passcode.to_le_bytes().into(),
710 discriminator: short_discriminator,
713 },
714 vid,
715 pid,
716 serial_no: "",
718 optional_data: (),
720 })
721 }
722
723 fn digits_at(digits: &str, offset: usize, len: usize) -> Result<u32, Error> {
725 digits
726 .get(offset..offset + len)
727 .ok_or(ErrorCode::InvalidData)?
728 .parse()
729 .map_err(|_| ErrorCode::InvalidData.into())
730 }
731
732 pub const fn short_discriminator(&self) -> u8 {
740 self.comm_data.discriminator as u8
741 }
742
743 pub fn passcode(&self) -> u32 {
745 u32::from_le_bytes(*self.comm_data.password.access())
746 }
747
748 pub fn vid_pid(&self) -> Option<(u16, u16)> {
751 (!matches!(self.comm_flow, CommFlowType::Standard)).then_some((self.vid, self.pid))
752 }
753
754 pub fn comm_flow(&self) -> Option<CommFlowType> {
765 matches!(self.comm_flow, CommFlowType::Standard).then_some(CommFlowType::Standard)
766 }
767
768 pub fn commissionable_filter(&self) -> CommissionableFilter {
781 CommissionableFilter {
782 short_discriminator: Some(self.short_discriminator()),
783 ..Default::default()
784 }
785 }
786}
787
788impl CommFlowType {
789 fn from_bits(bits: u8) -> Result<Self, Error> {
791 match bits {
792 0 => Ok(Self::Standard),
793 1 => Ok(Self::UserIntent),
794 2 => Ok(Self::Custom),
795 _ => Err(ErrorCode::InvalidData.into()),
796 }
797 }
798}
799
800struct BitReader<'a> {
803 data: &'a [u8],
804 pos: usize,
806}
807
808impl<'a> BitReader<'a> {
809 const fn new(data: &'a [u8]) -> Self {
810 Self { data, pos: 0 }
811 }
812
813 fn read(&mut self, len: usize) -> Result<u32, Error> {
815 debug_assert!(len <= 32);
816
817 if self.pos + len > self.data.len() * 8 {
818 return Err(ErrorCode::InvalidData.into());
819 }
820
821 let mut value = 0u32;
822 for i in 0..len {
823 let bit_pos = self.pos + i;
824 let byte = self.data[bit_pos / 8];
825 let bit = (byte >> (bit_pos % 8)) & 1;
826 value |= (bit as u32) << i;
827 }
828
829 self.pos += len;
830
831 Ok(value)
832 }
833}
834
835#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
839#[cfg_attr(feature = "defmt", derive(defmt::Format))]
840pub enum QrTextType {
841 Ascii,
844 Ansi,
846 Unicode,
848}
849
850pub struct Qr<'a>(QrCode<'a>);
852
853impl<'a> Qr<'a> {
854 pub fn compute(text: &str, tmp_buf: &mut [u8], out_buf: &'a mut [u8]) -> Result<Self, Error> {
865 let needed_version = Version::new(Self::version(text));
866
867 let qr = QrCode::encode_text(
868 text,
869 tmp_buf,
870 out_buf,
871 QrCodeEcc::Medium,
872 needed_version,
873 needed_version,
874 None,
875 false,
876 )
877 .map_err(|_| ErrorCode::BufferTooSmall)?;
878
879 Ok(Self(qr))
880 }
881
882 pub fn size(&self) -> u32 {
884 self.0.size() as _
885 }
886
887 pub fn get_module(&self, x: i32, y: i32) -> bool {
889 self.0.get_module(x, y)
890 }
891
892 pub fn as_str<'b>(
904 &self,
905 text_type: QrTextType,
906 border: u8,
907 invert: bool,
908 out_buf: &'b mut [u8],
909 ) -> Result<(&'b str, &'b mut [u8]), Error> {
910 let mut offset = 0;
911
912 for c in self.emit_chars(text_type, border, invert) {
913 let mut dst = [0; 4];
914 let bytes = c.encode_utf8(&mut dst).as_bytes();
915
916 if offset + bytes.len() > out_buf.len() {
917 return Err(ErrorCode::BufferTooSmall.into());
918 } else {
919 out_buf[offset..offset + bytes.len()].copy_from_slice(bytes);
920 offset += bytes.len();
921 }
922 }
923
924 let (str_buf, remaining_buf) = out_buf.split_at_mut(offset);
925
926 let str = unwrap!(core::str::from_utf8(str_buf).map_err(|_| ErrorCode::InvalidData));
928
929 Ok((str, remaining_buf))
930 }
931
932 pub fn line_as_str<'b>(
946 &self,
947 text_type: QrTextType,
948 border: u8,
949 invert: bool,
950 nl: bool,
951 y: i32,
952 out_buf: &'b mut [u8],
953 ) -> Result<(&'b str, &'b mut [u8]), Error> {
954 let mut offset = 0;
955
956 for c in self.emit_line_chars(text_type, border, invert, nl, y) {
957 let mut dst = [0; 4];
958 let bytes = c.encode_utf8(&mut dst).as_bytes();
959
960 if offset + bytes.len() > out_buf.len() {
961 return Err(ErrorCode::BufferTooSmall.into());
962 } else {
963 out_buf[offset..offset + bytes.len()].copy_from_slice(bytes);
964 offset += bytes.len();
965 }
966 }
967
968 let (str_buf, remaining_buf) = out_buf.split_at_mut(offset);
969
970 let str = unwrap!(core::str::from_utf8(str_buf).map_err(|_| ErrorCode::InvalidData));
972
973 Ok((str, remaining_buf))
974 }
975
976 pub fn lines_range(
982 &self,
983 text_type: QrTextType,
984 border: u8,
985 ) -> impl Iterator<Item = i32> + '_ + 'a {
986 let iborder: i32 = border as _;
987
988 (-iborder..self.size() as i32 + iborder)
989 .filter(move |y| !matches!(text_type, QrTextType::Unicode) || (*y - -iborder) % 2 == 0)
990 }
991
992 pub fn emit_chars(
1002 &self,
1003 text_type: QrTextType,
1004 border: u8,
1005 invert: bool,
1006 ) -> impl Iterator<Item = char> + use<'_, 'a> {
1007 self.lines_range(text_type, border)
1008 .flat_map(move |y| self.emit_line_chars(text_type, border, invert, true, y))
1009 }
1010
1011 pub fn emit_line_chars(
1023 &self,
1024 text_type: QrTextType,
1025 border: u8,
1026 invert: bool,
1027 nl: bool,
1028 y: i32,
1029 ) -> impl Iterator<Item = char> + use<'_, 'a> {
1030 let border: i32 = border as _;
1031
1032 (-border..self.size() as i32 + border + 1)
1033 .map(move |x| (x, y))
1034 .map(move |(x, y)| {
1035 if x < self.size() as i32 + border {
1036 let white = !self.get_module(x, y) ^ invert;
1037
1038 match text_type {
1039 QrTextType::Ascii => {
1040 if white {
1041 "#"
1042 } else {
1043 " "
1044 }
1045 }
1046 QrTextType::Ansi => {
1047 let prev_white = if x > -border {
1048 Some(self.get_module(x - 1, y))
1049 } else {
1050 None
1051 }
1052 .map(|prev_white| !prev_white ^ invert);
1053
1054 if prev_white != Some(white) {
1055 if white {
1056 "\x1b[47m "
1057 } else {
1058 "\x1b[40m "
1059 }
1060 } else {
1061 " "
1062 }
1063 }
1064 QrTextType::Unicode => {
1065 if white == !self.get_module(x, y + 1) ^ invert {
1066 if white {
1067 "\u{2588}"
1068 } else {
1069 " "
1070 }
1071 } else if white {
1072 "\u{2580}"
1073 } else {
1074 "\u{2584}"
1075 }
1076 }
1077 }
1078 } else {
1079 match text_type {
1080 QrTextType::Ascii => {
1081 if nl {
1082 "\n"
1083 } else {
1084 ""
1085 }
1086 }
1087 _ => {
1088 if nl {
1089 "\x1b[0m\n"
1090 } else {
1091 "\x1b[0m"
1092 }
1093 }
1094 }
1095 }
1096 })
1097 .flat_map(str::chars)
1098 }
1099
1100 fn version(qr_code_text: &str) -> u8 {
1101 match qr_code_text.len() {
1102 0..=38 => 2,
1103 39..=61 => 3,
1104 62..=90 => 4,
1105 _ => 5,
1106 }
1107 }
1108}
1109
1110pub enum QrTextRenderer<'a> {
1112 Ascii(Qr<'a>),
1114 Ansi(Qr<'a>),
1116 Unicode(Qr<'a>),
1118}
1119
1120impl<'a> QrTextRenderer<'a> {
1121 pub fn render<'b>(
1132 &self,
1133 border: u8,
1134 invert: bool,
1135 out_buf: &'b mut [u8],
1136 ) -> Result<(&'b str, &'b mut [u8]), Error> {
1137 let mut offset = 0;
1138
1139 for c in self.render_iter(border, invert) {
1140 let mut dst = [0; 4];
1141 let bytes = c.encode_utf8(&mut dst).as_bytes();
1142
1143 if offset + bytes.len() > out_buf.len() {
1144 return Err(ErrorCode::BufferTooSmall.into());
1145 } else {
1146 out_buf[offset..offset + bytes.len()].copy_from_slice(bytes);
1147 offset += bytes.len();
1148 }
1149 }
1150
1151 let (str_buf, remaining_buf) = out_buf.split_at_mut(offset);
1152
1153 let str = unwrap!(core::str::from_utf8(str_buf).map_err(|_| ErrorCode::InvalidData));
1155
1156 Ok((str, remaining_buf))
1157 }
1158
1159 pub fn render_line<'b>(
1172 &self,
1173 border: u8,
1174 invert: bool,
1175 nl: bool,
1176 y: i32,
1177 out_buf: &'b mut [u8],
1178 ) -> Result<(&'b str, &'b mut [u8]), Error> {
1179 let mut offset = 0;
1180
1181 for c in self.render_line_iter(border, invert, nl, y) {
1182 let mut dst = [0; 4];
1183 let bytes = c.encode_utf8(&mut dst).as_bytes();
1184
1185 if offset + bytes.len() > out_buf.len() {
1186 return Err(ErrorCode::BufferTooSmall.into());
1187 } else {
1188 out_buf[offset..offset + bytes.len()].copy_from_slice(bytes);
1189 offset += bytes.len();
1190 }
1191 }
1192
1193 let (str_buf, remaining_buf) = out_buf.split_at_mut(offset);
1194
1195 let str = unwrap!(core::str::from_utf8(str_buf).map_err(|_| ErrorCode::InvalidData));
1197
1198 Ok((str, remaining_buf))
1199 }
1200
1201 pub fn lines_range(&self, border: u8) -> impl Iterator<Item = i32> + '_ + 'a {
1206 let unicode = matches!(self, Self::Unicode(_));
1207 let iborder: i32 = border as _;
1208
1209 (-iborder..self.qr().size() as i32 + iborder)
1210 .filter(move |y| !unicode || (*y - -iborder) % 2 == 0)
1211 }
1212
1213 pub fn render_iter(
1222 &self,
1223 border: u8,
1224 invert: bool,
1225 ) -> impl Iterator<Item = char> + use<'_, 'a> {
1226 self.lines_range(border)
1227 .flat_map(move |y| self.render_line_iter(border, invert, true, y))
1228 }
1229
1230 pub fn render_line_iter(
1241 &self,
1242 border: u8,
1243 invert: bool,
1244 nl: bool,
1245 y: i32,
1246 ) -> impl Iterator<Item = char> + use<'_, 'a> {
1247 let border: i32 = border as _;
1248
1249 (-border..self.qr().size() as i32 + border + 1)
1250 .map(move |x| (x, y))
1251 .map(move |(x, y)| {
1252 if x < self.qr().size() as i32 + border {
1253 let white = !self.qr().get_module(x, y) ^ invert;
1254
1255 match self {
1256 Self::Ascii(_) => {
1257 if white {
1258 "#"
1259 } else {
1260 " "
1261 }
1262 }
1263 Self::Ansi(_) => {
1264 let prev_white = if x > -border {
1265 Some(self.qr().get_module(x - 1, y))
1266 } else {
1267 None
1268 }
1269 .map(|prev_white| !prev_white ^ invert);
1270
1271 if prev_white != Some(white) {
1272 if white {
1273 "\x1b[47m "
1274 } else {
1275 "\x1b[40m "
1276 }
1277 } else {
1278 " "
1279 }
1280 }
1281 Self::Unicode(_) => {
1282 if white == !self.qr().get_module(x, y + 1) ^ invert {
1283 if white {
1284 "\u{2588}"
1285 } else {
1286 " "
1287 }
1288 } else if white {
1289 "\u{2580}"
1290 } else {
1291 "\u{2584}"
1292 }
1293 }
1294 }
1295 } else {
1296 match self {
1297 Self::Ascii(_) => {
1298 if nl {
1299 "\n"
1300 } else {
1301 ""
1302 }
1303 }
1304 _ => {
1305 if nl {
1306 "\x1b[0m\n"
1307 } else {
1308 "\x1b[0m"
1309 }
1310 }
1311 }
1312 }
1313 })
1314 .flat_map(str::chars)
1315 }
1316
1317 #[inline(always)]
1318 pub fn qr(&self) -> &Qr<'a> {
1319 match self {
1320 Self::Ascii(qr) => qr,
1321 Self::Ansi(qr) => qr,
1322 Self::Unicode(qr) => qr,
1323 }
1324 }
1325}
1326
1327#[cfg(test)]
1328mod tests {
1329 use super::*;
1330
1331 #[test]
1332 fn can_base38_encode() {
1333 const QR_CODE: &str = "MT:YNJV7VSC00CMVH7SR00";
1334
1335 let comm_data = BasicCommData {
1336 password: 34567890_u32.to_le_bytes().into(),
1337 discriminator: 2976,
1338 };
1339 let dev_det = BasicInfoConfig {
1340 vid: 9050,
1341 pid: 65279,
1342 ..Default::default()
1343 };
1344
1345 let disc_cap = DiscoveryCapabilities::BLE;
1346 let qr_code_data = QrPayload::new_from_basic_info(
1347 disc_cap,
1348 CommFlowType::Standard,
1349 comm_data,
1350 &dev_det,
1351 no_optional_data,
1352 );
1353 let mut buf = [0; 1024];
1354 let data_str = unwrap!(qr_code_data.as_str(&mut buf), "Failed to encode").0;
1355 assert_eq!(data_str, QR_CODE)
1356 }
1357
1358 #[test]
1359 fn can_base38_encode_with_vendor_data() {
1360 const QR_CODE: &str = "MT:-24J0AFN00KA064IJ3P0IXZB0DK5N1K8SQ1RYCU1-A40";
1361
1362 let comm_data = BasicCommData {
1363 password: 20202021_u32.to_le_bytes().into(),
1364 discriminator: 3840,
1365 };
1366 let dev_det = BasicInfoConfig {
1367 vid: 65521,
1368 pid: 32769,
1369 serial_no: "1234567890",
1370 ..Default::default()
1371 };
1372
1373 let disc_cap = DiscoveryCapabilities::IP;
1374 let qr_code_data = QrPayload::new_from_basic_info(
1375 disc_cap,
1376 CommFlowType::Standard,
1377 comm_data,
1378 &dev_det,
1379 no_optional_data,
1380 );
1381 let mut buf = [0; 1024];
1382 let data_str = unwrap!(qr_code_data.as_str(&mut buf), "Failed to encode").0;
1383 assert_eq!(data_str, QR_CODE)
1384 }
1385
1386 #[test]
1387 fn can_base38_encode_with_optional_data() {
1388 const QR_CODE: &str =
1389 "MT:-24J0AFN00KA064IJ3P0IXZB0DK5N1K8SQ1RYCU1UXH34YY0V3KY.O3DKN440F710Q940";
1390 const OPTIONAL_DEFAULT_STRING_TAG: u8 = 0x82; const OPTIONAL_DEFAULT_STRING_VALUE: &str = "myData";
1392
1393 const OPTIONAL_DEFAULT_INT_TAG: u8 = 0x83; const OPTIONAL_DEFAULT_INT_VALUE: i32 = 65550;
1395
1396 let comm_data = BasicCommData {
1397 password: 20202021_u32.to_le_bytes().into(),
1398 discriminator: 3840,
1399 };
1400 let dev_det = BasicInfoConfig {
1401 vid: 65521,
1402 pid: 32769,
1403 serial_no: "1234567890",
1404 ..Default::default()
1405 };
1406
1407 let disc_cap = DiscoveryCapabilities::IP;
1408 let optional_data = || {
1409 TLV::utf8(
1410 TLVTag::Context(OPTIONAL_DEFAULT_STRING_TAG),
1411 OPTIONAL_DEFAULT_STRING_VALUE,
1412 )
1413 .into_tlv_iter()
1414 .chain(
1415 TLV::i32(
1416 TLVTag::Context(OPTIONAL_DEFAULT_INT_TAG),
1417 OPTIONAL_DEFAULT_INT_VALUE,
1418 )
1419 .into_tlv_iter(),
1420 )
1421 .flat_map(TLV::result_into_bytes_iter)
1422 };
1423
1424 let qr_code_data = QrPayload::new_from_basic_info(
1425 disc_cap,
1426 CommFlowType::Standard,
1427 comm_data,
1428 &dev_det,
1429 optional_data,
1430 );
1431
1432 let mut buf = [0; 1024];
1433 let data_str = unwrap!(qr_code_data.as_str(&mut buf), "Failed to encode").0;
1434 assert_eq!(data_str, QR_CODE)
1435 }
1436
1437 #[test]
1438 fn parse_qr_basic() {
1439 const QR_CODE: &str = "MT:YNJV7VSC00CMVH7SR00";
1441
1442 let mut buf = [0; 128];
1443 let payload = unwrap!(QrPayload::parse(QR_CODE, &mut buf), "Failed to parse");
1444
1445 assert_eq!(payload.version(), 0);
1446 assert_eq!(payload.vid(), 9050);
1447 assert_eq!(payload.pid(), 65279);
1448 assert_eq!(payload.comm_flow(), CommFlowType::Standard);
1449 assert_eq!(payload.discovery_capabilities(), DiscoveryCapabilities::BLE);
1450 assert_eq!(payload.discriminator(), 2976);
1451 assert_eq!(payload.passcode(), 34567890);
1452 assert_eq!(payload.serial_no(), "");
1453 assert!(payload.optional_data().is_empty());
1454 }
1455
1456 #[test]
1457 fn parse_qr_with_serial_no() {
1458 const QR_CODE: &str = "MT:-24J0AFN00KA064IJ3P0IXZB0DK5N1K8SQ1RYCU1-A40";
1460
1461 let mut buf = [0; 128];
1462 let payload = unwrap!(QrPayload::parse(QR_CODE, &mut buf), "Failed to parse");
1463
1464 assert_eq!(payload.vid(), 65521);
1465 assert_eq!(payload.pid(), 32769);
1466 assert_eq!(payload.discovery_capabilities(), DiscoveryCapabilities::IP);
1467 assert_eq!(payload.discriminator(), 3840);
1468 assert_eq!(payload.passcode(), 20202021);
1469 assert_eq!(payload.serial_no(), "1234567890");
1470 assert!(!payload.optional_data().is_empty());
1471 }
1472
1473 #[test]
1474 fn parse_qr_round_trips_through_encode() {
1475 const QR_CODE: &str = "MT:-24J0AFN00KA064IJ3P0IXZB0DK5N1K8SQ1RYCU1-A40";
1480
1481 let mut buf = [0; 128];
1482 let payload = unwrap!(QrPayload::parse(QR_CODE, &mut buf), "Failed to parse");
1483
1484 let reencoded = QrPayload::new(
1485 payload.discovery_capabilities(),
1486 payload.comm_flow(),
1487 payload.comm_data.clone(),
1488 payload.vid(),
1489 payload.pid(),
1490 payload.serial_no(),
1491 no_optional_data,
1492 );
1493
1494 let mut out = [0; 256];
1495 let s = unwrap!(reencoded.as_str(&mut out), "Failed to encode").0;
1496 assert_eq!(s, QR_CODE);
1497 }
1498
1499 #[test]
1500 fn parse_pairing_code_round_trips_with_encoder() {
1501 for (code, passcode, discriminator) in [
1503 ("00876800071", 123456_u32, 250_u16),
1504 ("26318621095", 34567890, 2976),
1505 ] {
1506 let payload = unwrap!(QrPayload::parse_pairing_code(code), "Failed to parse");
1507
1508 assert_eq!(payload.passcode(), passcode);
1509 assert_eq!(payload.short_discriminator(), (discriminator >> 8) as u8);
1511 assert_eq!(payload.comm_flow(), Some(CommFlowType::Standard));
1513 assert_eq!(payload.vid_pid(), None);
1514 assert!(payload.discovery_capabilities.is_empty());
1516
1517 let comm_data = BasicCommData {
1520 password: payload.passcode().to_le_bytes().into(),
1521 discriminator,
1522 };
1523 assert_eq!(comm_data.compute_pairing_code(), code);
1524 }
1525 }
1526
1527 #[test]
1528 fn commissionable_filter_uses_the_right_discriminator_field() {
1529 let mut buf = [0; 128];
1531 let qr = unwrap!(
1532 QrPayload::parse("MT:-24J0AFN00KA064IJ3P0IXZB0DK5N1K8SQ1RYCU1-A40", &mut buf),
1533 "Failed to parse"
1534 );
1535 let filter = qr.commissionable_filter();
1536
1537 assert_eq!(filter.discriminator, Some(3840));
1538 assert_eq!(filter.short_discriminator, None);
1539
1540 let manual = unwrap!(QrPayload::parse_pairing_code("34970112332"), "Failed");
1544 let filter = manual.commissionable_filter();
1545
1546 assert_eq!(filter.short_discriminator, Some(15));
1547 assert_eq!(filter.discriminator, None);
1548
1549 assert_eq!(filter.vendor_id, None);
1552 assert_eq!(filter.product_id, None);
1553 }
1554
1555 #[test]
1556 fn parse_pairing_code_accepts_pretty_form() {
1557 let compact = unwrap!(QrPayload::parse_pairing_code("34970112332"), "compact");
1559 let pretty = unwrap!(QrPayload::parse_pairing_code("3497-0112-332"), "pretty");
1560
1561 assert_eq!(compact.passcode(), 20202021);
1562 assert_eq!(compact.short_discriminator(), 15); assert_eq!(pretty.passcode(), compact.passcode());
1564 assert_eq!(pretty.short_discriminator(), compact.short_discriminator());
1565 }
1566
1567 #[test]
1568 fn parse_pairing_code_rejects_bad_input() {
1569 assert!(QrPayload::parse_pairing_code("34970112333").is_err());
1571 assert!(QrPayload::parse_pairing_code("3497011233X").is_err());
1573 assert!(QrPayload::parse_pairing_code("3497011233").is_err());
1575 assert!(QrPayload::parse_pairing_code("94970112332").is_err());
1577 assert!(QrPayload::parse_pairing_code("74970112332").is_err());
1579 }
1580
1581 #[test]
1582 fn parse_qr_rejects_bad_input() {
1583 let mut buf = [0; 128];
1584
1585 assert!(QrPayload::parse("YNJV7VSC00CMVH7SR00", &mut buf).is_err());
1587 assert!(QrPayload::parse("MT:YNJV7VSC00CMVH7SR0!", &mut buf).is_err());
1589 assert!(QrPayload::parse("MT:00", &mut buf).is_err());
1591 }
1592}