Compute QR Code
This commit is contained in:
parent
ba524e1190
commit
317d657fba
7 changed files with 288 additions and 0 deletions
|
@ -47,6 +47,16 @@ safemem = "0.3.3"
|
|||
chrono = { version = "0.4.19", default-features = false, features = ["clock", "std"] }
|
||||
async-channel = "1.6"
|
||||
|
||||
# to compute the check digit
|
||||
verhoeff = "1"
|
||||
|
||||
# needed to compute base38 packed binary data Structure
|
||||
base-encode = "0.3"
|
||||
packed_struct = "0.10"
|
||||
|
||||
# print QR code
|
||||
qrcode = { version = "0.12", default-features = false }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
astro-dnssd = "0.3"
|
||||
|
||||
|
|
46
matter/src/codec/base38.rs
Normal file
46
matter/src/codec/base38.rs
Normal file
|
@ -0,0 +1,46 @@
|
|||
const BASE38_CHARS: &str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-.";
|
||||
|
||||
fn encode_base38(mut value: u32, char_count: u8) -> String {
|
||||
let mut result = String::new();
|
||||
for _ in 0..char_count {
|
||||
let mut chars = BASE38_CHARS.chars();
|
||||
let remainder = value % 38;
|
||||
result.push(chars.nth(remainder as usize).unwrap());
|
||||
value = (value - remainder) / 38;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn encode(bytes: &[u8]) -> String {
|
||||
let length = bytes.len();
|
||||
let mut offset = 0;
|
||||
let mut result = String::new();
|
||||
|
||||
while offset < length {
|
||||
let remaining = length - offset;
|
||||
match remaining.cmp(&2) {
|
||||
std::cmp::Ordering::Greater => {
|
||||
result.push_str(&encode_base38(
|
||||
((bytes[offset + 2] as u32) << 16)
|
||||
| ((bytes[offset + 1] as u32) << 8)
|
||||
| (bytes[offset] as u32),
|
||||
5,
|
||||
));
|
||||
offset += 3;
|
||||
}
|
||||
std::cmp::Ordering::Equal => {
|
||||
result.push_str(&encode_base38(
|
||||
((bytes[offset + 1] as u32) << 8) | (bytes[offset] as u32),
|
||||
4,
|
||||
));
|
||||
break;
|
||||
}
|
||||
std::cmp::Ordering::Less => {
|
||||
result.push_str(&encode_base38(bytes[offset] as u32, 2));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
1
matter/src/codec/mod.rs
Normal file
1
matter/src/codec/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod base38;
|
|
@ -25,6 +25,7 @@ use crate::{
|
|||
fabric::FabricMgr,
|
||||
interaction_model::InteractionModel,
|
||||
mdns::Mdns,
|
||||
pairing::compute_and_print_pairing_code,
|
||||
secure_channel::core::SecureChannel,
|
||||
transport,
|
||||
};
|
||||
|
@ -69,6 +70,8 @@ impl Matter {
|
|||
&dev_det.device_name,
|
||||
);
|
||||
|
||||
compute_and_print_pairing_code(dev_det, dev_comm);
|
||||
|
||||
let fabric_mgr = Arc::new(FabricMgr::new()?);
|
||||
let acl_mgr = Arc::new(AclMgr::new()?);
|
||||
let open_comm_window = fabric_mgr.is_empty();
|
||||
|
|
|
@ -26,6 +26,7 @@ enum Attributes {
|
|||
SwVer = 9,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct BasicInfoConfig {
|
||||
pub vid: u16,
|
||||
pub pid: u16,
|
||||
|
|
|
@ -70,6 +70,7 @@
|
|||
|
||||
pub mod acl;
|
||||
pub mod cert;
|
||||
pub mod codec;
|
||||
pub mod core;
|
||||
pub mod crypto;
|
||||
pub mod data_model;
|
||||
|
@ -78,6 +79,7 @@ pub mod fabric;
|
|||
pub mod group_keys;
|
||||
pub mod interaction_model;
|
||||
pub mod mdns;
|
||||
pub mod pairing;
|
||||
pub mod secure_channel;
|
||||
pub mod sys;
|
||||
pub mod tlv;
|
||||
|
|
225
matter/src/pairing.rs
Normal file
225
matter/src/pairing.rs
Normal file
|
@ -0,0 +1,225 @@
|
|||
use log::info;
|
||||
use packed_struct::prelude::*;
|
||||
use qrcode::{render::unicode, QrCode, Version};
|
||||
use verhoeff::Verhoeff;
|
||||
|
||||
use crate::{
|
||||
codec::base38, data_model::cluster_basic_information::BasicInfoConfig, CommissioningData,
|
||||
};
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum CommissionningFlowType {
|
||||
Standard = 0,
|
||||
UserIntent = 1,
|
||||
Custom = 2,
|
||||
}
|
||||
|
||||
pub struct DiscoveryCapabilitiesSchema {
|
||||
on_ip_network: bool,
|
||||
ble: bool,
|
||||
soft_access_point: bool,
|
||||
}
|
||||
|
||||
impl DiscoveryCapabilitiesSchema {
|
||||
fn as_bits(&self) -> u8 {
|
||||
let mut bits = 0;
|
||||
if self.soft_access_point {
|
||||
bits |= 1 << 0;
|
||||
}
|
||||
if self.ble {
|
||||
bits |= 1 << 1;
|
||||
}
|
||||
if self.on_ip_network {
|
||||
bits |= 1 << 2;
|
||||
}
|
||||
bits
|
||||
}
|
||||
}
|
||||
|
||||
pub struct QrCodeData<'data> {
|
||||
flow_type: CommissionningFlowType,
|
||||
discovery_capabilities: DiscoveryCapabilitiesSchema,
|
||||
dev_det: &'data BasicInfoConfig,
|
||||
comm_data: &'data CommissioningData,
|
||||
}
|
||||
|
||||
impl<'data> QrCodeData<'data> {
|
||||
pub fn new(
|
||||
dev_det: &'data BasicInfoConfig,
|
||||
comm_data: &'data CommissioningData,
|
||||
discovery_capabilities: DiscoveryCapabilitiesSchema,
|
||||
) -> Self {
|
||||
QrCodeData {
|
||||
flow_type: CommissionningFlowType::Standard,
|
||||
discovery_capabilities,
|
||||
dev_det,
|
||||
comm_data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PackedStruct, Debug)]
|
||||
#[packed_struct(bit_numbering = "msb0", size_bytes = "11", endian = "msb")]
|
||||
pub struct PackedQrData {
|
||||
#[packed_field(bits = "0..3")]
|
||||
version: Integer<u8, packed_bits::Bits<3>>,
|
||||
#[packed_field(bits = "3..19")]
|
||||
vid: Integer<u16, packed_bits::Bits<16>>,
|
||||
#[packed_field(bits = "19..35")]
|
||||
pid: Integer<u16, packed_bits::Bits<16>>,
|
||||
#[packed_field(bits = "35..37")]
|
||||
commissionning_flow_type: Integer<u8, packed_bits::Bits<2>>,
|
||||
#[packed_field(bits = "37")]
|
||||
soft_access_point: bool,
|
||||
#[packed_field(bits = "38")]
|
||||
ble: bool,
|
||||
#[packed_field(bits = "39")]
|
||||
on_ip_network: bool,
|
||||
#[packed_field(bits = "40..45")]
|
||||
_reserved: Integer<u8, packed_bits::Bits<5>>,
|
||||
#[packed_field(bits = "45..57")]
|
||||
discriminator: Integer<u16, packed_bits::Bits<12>>,
|
||||
#[packed_field(bits = "57..84")]
|
||||
passcode: Integer<u32, packed_bits::Bits<27>>,
|
||||
#[packed_field(bits = "84..88")]
|
||||
_padding: Integer<u8, packed_bits::Bits<4>>,
|
||||
}
|
||||
|
||||
pub fn compute_and_print_pairing_code(dev_det: &BasicInfoConfig, comm_data: &CommissioningData) {
|
||||
let pairing_code = compute_pairing_code(comm_data);
|
||||
pretty_print_pairing_code(&pairing_code);
|
||||
print_qr_code(&pairing_code, dev_det, comm_data);
|
||||
}
|
||||
|
||||
fn compute_pairing_code(comm_data: &CommissioningData) -> String {
|
||||
// 0: no Vendor ID and Product ID present in Manual Pairing Code
|
||||
const VID_PID_PRESENT: u8 = 0;
|
||||
|
||||
let CommissioningData {
|
||||
discriminator,
|
||||
passwd,
|
||||
..
|
||||
} = comm_data;
|
||||
|
||||
let mut digits = String::new();
|
||||
digits.push_str(&((VID_PID_PRESENT << 2) | (discriminator >> 10) as u8).to_string());
|
||||
digits.push_str(&format!(
|
||||
"{:0>5}",
|
||||
((discriminator & 0x300) << 6) | (passwd & 0x3FFF) as u16
|
||||
));
|
||||
digits.push_str(&format!("{:0>4}", passwd >> 14));
|
||||
|
||||
let check_digit = digits.calculate_verhoeff_check_digit();
|
||||
digits.push_str(&check_digit.to_string());
|
||||
|
||||
digits
|
||||
}
|
||||
|
||||
pub fn pretty_print_pairing_code(pairing_code: &str) {
|
||||
assert!(pairing_code.len() == 11);
|
||||
let mut pretty = String::new();
|
||||
pretty.push_str(&pairing_code[..4]);
|
||||
pretty.push('-');
|
||||
pretty.push_str(&pairing_code[4..8]);
|
||||
pretty.push('-');
|
||||
pretty.push_str(&pairing_code[8..]);
|
||||
info!("Pairing Code: {}", pretty);
|
||||
}
|
||||
|
||||
fn print_qr_code(pairing_code: &str, dev_det: &BasicInfoConfig, comm_data: &CommissioningData) {
|
||||
let code = QrCode::with_version(pairing_code, Version::Normal(2), qrcode::EcLevel::M).unwrap();
|
||||
let image = code
|
||||
.render::<unicode::Dense1x2>()
|
||||
.dark_color(unicode::Dense1x2::Light)
|
||||
.light_color(unicode::Dense1x2::Dark)
|
||||
.build();
|
||||
println!("{}", image);
|
||||
}
|
||||
|
||||
fn base38_encode_qr(qr_data: &QrCodeData) -> String {
|
||||
let QrCodeData {
|
||||
flow_type,
|
||||
discovery_capabilities,
|
||||
dev_det,
|
||||
comm_data,
|
||||
} = &qr_data;
|
||||
|
||||
let BasicInfoConfig { vid, pid, .. } = dev_det;
|
||||
const VERSION: u8 = 0; // 3-bit value specifying the QR code payload version. SHALL be 000.
|
||||
|
||||
let packed_qr_data = PackedQrData {
|
||||
version: VERSION.into(),
|
||||
vid: (*vid).reverse_bits().into(),
|
||||
pid: (*pid).reverse_bits().into(),
|
||||
commissionning_flow_type: ((*flow_type) as u8).into(),
|
||||
soft_access_point: discovery_capabilities.soft_access_point,
|
||||
ble: discovery_capabilities.ble,
|
||||
on_ip_network: discovery_capabilities.on_ip_network,
|
||||
_reserved: 0u8.into(),
|
||||
discriminator: comm_data.discriminator.reverse_bits().into(),
|
||||
passcode: comm_data.passwd.reverse_bits().into(),
|
||||
_padding: 0u8.into(),
|
||||
};
|
||||
|
||||
println!("{:?}", packed_qr_data);
|
||||
println!("{}", packed_qr_data);
|
||||
|
||||
let data = packed_qr_data.pack().unwrap();
|
||||
let data = data
|
||||
.into_iter()
|
||||
.map(|b| b.reverse_bits())
|
||||
.collect::<Vec<u8>>();
|
||||
|
||||
let base38 = base38::encode(&data);
|
||||
format!("MT:{}", base38)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn can_compute_pairing_code() {
|
||||
let comm_data = CommissioningData {
|
||||
passwd: 123456,
|
||||
discriminator: 250,
|
||||
..Default::default()
|
||||
};
|
||||
let pairing_code = compute_pairing_code(&comm_data);
|
||||
assert_eq!(pairing_code, "00876800071");
|
||||
|
||||
let comm_data = CommissioningData {
|
||||
passwd: 34567890,
|
||||
discriminator: 2976,
|
||||
..Default::default()
|
||||
};
|
||||
let pairing_code = compute_pairing_code(&comm_data);
|
||||
assert_eq!(pairing_code, "26318621095");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_base38_encode() {
|
||||
const QR_CODE: &str = "MT:YNJV7VSC00CMVH7SR00";
|
||||
|
||||
let comm_data = CommissioningData {
|
||||
passwd: 34567890,
|
||||
discriminator: 2976,
|
||||
..Default::default()
|
||||
};
|
||||
let dev_det = BasicInfoConfig {
|
||||
vid: 9050,
|
||||
pid: 65279,
|
||||
..Default::default()
|
||||
};
|
||||
let disc_cap = DiscoveryCapabilitiesSchema {
|
||||
on_ip_network: false,
|
||||
ble: true,
|
||||
soft_access_point: false,
|
||||
};
|
||||
|
||||
let qr_code_data = QrCodeData::new(&dev_det, &comm_data, disc_cap);
|
||||
let data_str = base38_encode_qr(&qr_code_data);
|
||||
assert_eq!(data_str, QR_CODE)
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue