commit
24fa1be4fe
18 changed files with 1149 additions and 17 deletions
|
@ -36,6 +36,7 @@ fn main() {
|
|||
hw_ver: 2,
|
||||
sw_ver: 1,
|
||||
serial_no: "aabbccdd".to_string(),
|
||||
device_name: "OnOff Light".to_string(),
|
||||
};
|
||||
let dev_att = Box::new(dev_att::HardCodedDevAtt::new());
|
||||
|
||||
|
|
|
@ -47,6 +47,12 @@ 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"
|
||||
|
||||
# print QR code
|
||||
qrcode = { version = "0.12", default-features = false }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
astro-dnssd = "0.3"
|
||||
|
||||
|
|
225
matter/src/codec/base38.rs
Normal file
225
matter/src/codec/base38.rs
Normal file
|
@ -0,0 +1,225 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (c) 2020-2022 Project CHIP Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
//! Base38 encoding and decoding functions.
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
const BASE38_CHARS: [char; 38] = [
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
|
||||
'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '-', '.',
|
||||
];
|
||||
|
||||
const UNUSED: u8 = 255;
|
||||
|
||||
// map of base38 charater to numeric value
|
||||
// subtract 45 from the character, then index into this array, if possible
|
||||
const DECODE_BASE38: [u8; 46] = [
|
||||
36, // '-', =45
|
||||
37, // '.', =46
|
||||
UNUSED, // '/', =47
|
||||
0, // '0', =48
|
||||
1, // '1', =49
|
||||
2, // '2', =50
|
||||
3, // '3', =51
|
||||
4, // '4', =52
|
||||
5, // '5', =53
|
||||
6, // '6', =54
|
||||
7, // '7', =55
|
||||
8, // '8', =56
|
||||
9, // '9', =57
|
||||
UNUSED, // ':', =58
|
||||
UNUSED, // ';', =59
|
||||
UNUSED, // '<', =50
|
||||
UNUSED, // '=', =61
|
||||
UNUSED, // '>', =62
|
||||
UNUSED, // '?', =63
|
||||
UNUSED, // '@', =64
|
||||
10, // 'A', =65
|
||||
11, // 'B', =66
|
||||
12, // 'C', =67
|
||||
13, // 'D', =68
|
||||
14, // 'E', =69
|
||||
15, // 'F', =70
|
||||
16, // 'G', =71
|
||||
17, // 'H', =72
|
||||
18, // 'I', =73
|
||||
19, // 'J', =74
|
||||
20, // 'K', =75
|
||||
21, // 'L', =76
|
||||
22, // 'M', =77
|
||||
23, // 'N', =78
|
||||
24, // 'O', =79
|
||||
25, // 'P', =80
|
||||
26, // 'Q', =81
|
||||
27, // 'R', =82
|
||||
28, // 'S', =83
|
||||
29, // 'T', =84
|
||||
30, // 'U', =85
|
||||
31, // 'V', =86
|
||||
32, // 'W', =87
|
||||
33, // 'X', =88
|
||||
34, // 'Y', =89
|
||||
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();
|
||||
|
||||
// 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);
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Decode a base38-encoded string into a byte slice
|
||||
///
|
||||
/// # 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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
let mut value = 0u32;
|
||||
|
||||
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())?;
|
||||
|
||||
value = value * RADIX + v as u32;
|
||||
}
|
||||
|
||||
decoded_base38_characters += base38_characters_in_chunk;
|
||||
base38_characters_number -= base38_characters_in_chunk;
|
||||
|
||||
for _i in 0..bytes_in_decoded_chunk {
|
||||
result.push(value as u8);
|
||||
value >>= 8;
|
||||
}
|
||||
|
||||
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;
|
||||
if !(45..=90).contains(&c) {
|
||||
return Err(Error::InvalidData);
|
||||
}
|
||||
|
||||
let c = DECODE_BASE38[c as usize - 45];
|
||||
if c == UNUSED {
|
||||
return Err(Error::InvalidData);
|
||||
}
|
||||
|
||||
Ok(c)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
const ENCODED: &str = "-MOA57ZU02IT2L2BJ00";
|
||||
const DECODED: [u8; 11] = [
|
||||
0x88, 0xff, 0xa7, 0x91, 0x50, 0x40, 0x00, 0x47, 0x51, 0xdd, 0x02,
|
||||
];
|
||||
|
||||
#[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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_base38_decode() {
|
||||
assert_eq!(decode(ENCODED).expect("can not decode base38"), DECODED);
|
||||
}
|
||||
}
|
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::{print_pairing_code_and_qr, DiscoveryCapabilities},
|
||||
secure_channel::{core::SecureChannel, pake::PaseMgr, spake2p::VerifierData},
|
||||
transport,
|
||||
};
|
||||
|
@ -58,7 +59,9 @@ impl Matter {
|
|||
dev_comm: CommissioningData,
|
||||
) -> Result<Box<Matter>, Error> {
|
||||
let mdns = Mdns::get()?;
|
||||
mdns.set_values(dev_det.vid, dev_det.pid);
|
||||
mdns.set_values(dev_det.vid, dev_det.pid, &dev_det.device_name);
|
||||
|
||||
print_pairing_code_and_qr(&dev_det, &dev_comm, DiscoveryCapabilities::default());
|
||||
|
||||
let fabric_mgr = Arc::new(FabricMgr::new()?);
|
||||
let acl_mgr = Arc::new(AclMgr::new()?);
|
||||
|
|
|
@ -31,12 +31,15 @@ enum Attributes {
|
|||
SerialNo = 0x0f,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct BasicInfoConfig {
|
||||
pub vid: u16,
|
||||
pub pid: u16,
|
||||
pub hw_ver: u16,
|
||||
pub sw_ver: u32,
|
||||
pub serial_no: String,
|
||||
/// Device name; up to 32 characters
|
||||
pub device_name: String,
|
||||
}
|
||||
|
||||
fn attr_dm_rev_new() -> Result<Attribute, Error> {
|
||||
|
|
|
@ -24,6 +24,7 @@ use log::error;
|
|||
pub enum Error {
|
||||
AttributeNotFound,
|
||||
AttributeIsCustom,
|
||||
BufferTooSmall,
|
||||
ClusterNotFound,
|
||||
CommandNotFound,
|
||||
EndpointNotFound,
|
||||
|
@ -38,6 +39,7 @@ pub enum Error {
|
|||
NoHandler,
|
||||
NoNetworkInterface,
|
||||
NoNodeId,
|
||||
NoMemory,
|
||||
NoSession,
|
||||
NoSpace,
|
||||
NoSpaceAckTable,
|
||||
|
@ -58,6 +60,7 @@ pub enum Error {
|
|||
InvalidSignature,
|
||||
InvalidState,
|
||||
InvalidTime,
|
||||
InvalidArgument,
|
||||
RwLock,
|
||||
TLVNotFound,
|
||||
TLVTypeMismatch,
|
||||
|
|
|
@ -381,7 +381,7 @@ impl FabricMgr {
|
|||
pub fn set_label(&self, index: u8, label: String) -> Result<(), Error> {
|
||||
let index = index as usize;
|
||||
let mut mgr = self.inner.write()?;
|
||||
if label != "" {
|
||||
if !label.is_empty() {
|
||||
for i in 1..MAX_SUPPORTED_FABRICS {
|
||||
if let Some(fabric) = &mgr.fabrics[i] {
|
||||
if fabric.label == label {
|
||||
|
|
|
@ -41,7 +41,6 @@
|
|||
//! let comm_data = CommissioningData {
|
||||
//! verifier: VerifierData::new_with_pw(123456),
|
||||
//! discriminator: 250,
|
||||
//!
|
||||
//! };
|
||||
//!
|
||||
//! /// The basic information about this device
|
||||
|
@ -51,6 +50,7 @@
|
|||
//! hw_ver: 2,
|
||||
//! sw_ver: 1,
|
||||
//! serial_no: "aabbcc".to_string(),
|
||||
//! device_name: "OnOff Light".to_string(),
|
||||
//! };
|
||||
//!
|
||||
//! /// Get the Matter Object
|
||||
|
@ -69,6 +69,7 @@
|
|||
|
||||
pub mod acl;
|
||||
pub mod cert;
|
||||
pub mod codec;
|
||||
pub mod core;
|
||||
pub mod crypto;
|
||||
pub mod data_model;
|
||||
|
@ -77,6 +78,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;
|
||||
|
|
|
@ -30,19 +30,20 @@ pub struct MdnsInner {
|
|||
vid: u16,
|
||||
/// Product ID
|
||||
pid: u16,
|
||||
/// Device name
|
||||
device_name: String,
|
||||
}
|
||||
|
||||
pub struct Mdns {
|
||||
inner: Mutex<MdnsInner>,
|
||||
}
|
||||
|
||||
const SHORT_DISCRIMINATOR_MASK: u16 = 0xf00;
|
||||
const SHORT_DISCRIMINATOR_MASK: u16 = 0xF00;
|
||||
const SHORT_DISCRIMINATOR_SHIFT: u16 = 8;
|
||||
|
||||
static mut G_MDNS: Option<Arc<Mdns>> = None;
|
||||
static INIT: Once = Once::new();
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum ServiceMode {
|
||||
/// The commissioned state
|
||||
Commissioned,
|
||||
|
@ -72,28 +73,60 @@ impl Mdns {
|
|||
/// Set mDNS service specific values
|
||||
/// Values like vid, pid, discriminator etc
|
||||
// TODO: More things like device-type etc can be added here
|
||||
pub fn set_values(&self, vid: u16, pid: u16) {
|
||||
pub fn set_values(&self, vid: u16, pid: u16, device_name: &str) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.vid = vid;
|
||||
inner.pid = pid;
|
||||
inner.device_name = device_name.chars().take(32).collect();
|
||||
}
|
||||
|
||||
/// Publish a mDNS service
|
||||
/// name - is the service name (comma separated subtypes may follow)
|
||||
/// mode - the current service mode
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn publish_service(&self, name: &str, mode: ServiceMode) -> Result<SysMdnsService, Error> {
|
||||
match mode {
|
||||
ServiceMode::Commissioned => {
|
||||
sys_publish_service(name, "_matter._tcp", MATTER_PORT, &[])
|
||||
}
|
||||
ServiceMode::Commissionable(discriminator) => {
|
||||
let short = (discriminator & SHORT_DISCRIMINATOR_MASK) >> SHORT_DISCRIMINATOR_SHIFT;
|
||||
let inner = self.inner.lock().unwrap();
|
||||
let short = compute_short_discriminator(discriminator);
|
||||
let serv_type = format!("_matterc._udp,_S{},_L{}", short, discriminator);
|
||||
|
||||
let str_discriminator = format!("{}", discriminator);
|
||||
let txt_kvs = [["D", &str_discriminator], ["CM", "1"]];
|
||||
let txt_kvs = [
|
||||
["D", &str_discriminator],
|
||||
["CM", "1"],
|
||||
["DN", &inner.device_name],
|
||||
["VP", &format!("{}+{}", inner.vid, inner.pid)],
|
||||
["SII", "5000"], /* Sleepy Idle Interval */
|
||||
["SAI", "300"], /* Sleepy Active Interval */
|
||||
["PH", "33"], /* Pairing Hint */
|
||||
["PI", ""], /* Pairing Instruction */
|
||||
];
|
||||
sys_publish_service(name, &serv_type, MATTER_PORT, &txt_kvs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_short_discriminator(discriminator: u16) -> u16 {
|
||||
(discriminator & SHORT_DISCRIMINATOR_MASK) >> SHORT_DISCRIMINATOR_SHIFT
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn can_compute_short_discriminator() {
|
||||
let discriminator: u16 = 0b0000_1111_0000_0000;
|
||||
let short = compute_short_discriminator(discriminator);
|
||||
assert_eq!(short, 0b1111);
|
||||
|
||||
let discriminator: u16 = 840;
|
||||
let short = compute_short_discriminator(discriminator);
|
||||
assert_eq!(short, 3);
|
||||
}
|
||||
}
|
||||
|
|
73
matter/src/pairing/code.rs
Normal file
73
matter/src/pairing/code.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (c) 2020-2022 Project CHIP Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use super::*;
|
||||
|
||||
pub(super) 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 passwd = passwd_from_comm_data(comm_data);
|
||||
let CommissioningData { discriminator, .. } = 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(super) 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);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::secure_channel::spake2p::VerifierData;
|
||||
|
||||
#[test]
|
||||
fn can_compute_pairing_code() {
|
||||
let comm_data = CommissioningData {
|
||||
verifier: VerifierData::new_with_pw(123456),
|
||||
discriminator: 250,
|
||||
};
|
||||
let pairing_code = compute_pairing_code(&comm_data);
|
||||
assert_eq!(pairing_code, "00876800071");
|
||||
|
||||
let comm_data = CommissioningData {
|
||||
verifier: VerifierData::new_with_pw(34567890),
|
||||
discriminator: 2976,
|
||||
};
|
||||
let pairing_code = compute_pairing_code(&comm_data);
|
||||
assert_eq!(pairing_code, "26318621095");
|
||||
}
|
||||
}
|
104
matter/src/pairing/mod.rs
Normal file
104
matter/src/pairing/mod.rs
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (c) 2020-2022 Project CHIP Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
//! This module contains the logic for generating the pairing code and the QR code for easy pairing.
|
||||
|
||||
pub mod code;
|
||||
pub mod qr;
|
||||
pub mod vendor_identifiers;
|
||||
|
||||
use log::info;
|
||||
use qrcode::{render::unicode, QrCode, Version};
|
||||
use verhoeff::Verhoeff;
|
||||
|
||||
use crate::{
|
||||
codec::base38, data_model::cluster_basic_information::BasicInfoConfig, error::Error,
|
||||
secure_channel::spake2p::VerifierOption, CommissioningData,
|
||||
};
|
||||
|
||||
use self::{
|
||||
code::{compute_pairing_code, pretty_print_pairing_code},
|
||||
qr::{payload_base38_representation, print_qr_code, QrSetupPayload},
|
||||
};
|
||||
|
||||
pub struct DiscoveryCapabilities {
|
||||
on_ip_network: bool,
|
||||
ble: bool,
|
||||
soft_access_point: bool,
|
||||
}
|
||||
|
||||
impl DiscoveryCapabilities {
|
||||
pub fn new(on_ip_network: bool, ble: bool, soft_access_point: bool) -> Self {
|
||||
DiscoveryCapabilities {
|
||||
on_ip_network,
|
||||
ble,
|
||||
soft_access_point,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_value(&self) -> bool {
|
||||
self.on_ip_network || self.ble || self.soft_access_point
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DiscoveryCapabilities {
|
||||
fn default() -> Self {
|
||||
DiscoveryCapabilities {
|
||||
on_ip_network: true,
|
||||
ble: false,
|
||||
soft_access_point: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DiscoveryCapabilities {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepares and prints the pairing code and the QR code for easy pairing.
|
||||
pub fn print_pairing_code_and_qr(
|
||||
dev_det: &BasicInfoConfig,
|
||||
comm_data: &CommissioningData,
|
||||
discovery_capabilities: DiscoveryCapabilities,
|
||||
) {
|
||||
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");
|
||||
|
||||
pretty_print_pairing_code(&pairing_code);
|
||||
print_qr_code(&data_str);
|
||||
}
|
||||
|
||||
pub(self) fn passwd_from_comm_data(comm_data: &CommissioningData) -> u32 {
|
||||
// todo: should this be part of the comm_data implementation?
|
||||
match comm_data.verifier.data {
|
||||
VerifierOption::Password(pwd) => pwd,
|
||||
VerifierOption::Verifier(_) => 0,
|
||||
}
|
||||
}
|
623
matter/src/pairing/qr.rs
Normal file
623
matter/src/pairing/qr.rs
Normal file
|
@ -0,0 +1,623 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (c) 2020-2022 Project CHIP Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::{
|
||||
tlv::{TLVWriter, TagType},
|
||||
utils::writebuf::WriteBuf,
|
||||
};
|
||||
|
||||
use super::{
|
||||
vendor_identifiers::{is_vendor_id_valid_operationally, VendorId},
|
||||
*,
|
||||
};
|
||||
|
||||
// See section 5.1.2. QR Code in the Matter specification
|
||||
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;
|
||||
const TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES: usize = TOTAL_PAYLOAD_DATA_SIZE_IN_BITS / 8;
|
||||
|
||||
// Spec 5.1.4.2 CHIP-Common Reserved Tags
|
||||
const SERIAL_NUMBER_TAG: u8 = 0x00;
|
||||
// const PBKDFITERATIONS_TAG: u8 = 0x01;
|
||||
// const BPKFSALT_TAG: u8 = 0x02;
|
||||
// const NUMBER_OFDEVICES_TAG: u8 = 0x03;
|
||||
// const COMMISSIONING_TIMEOUT_TAG: u8 = 0x04;
|
||||
|
||||
pub enum QRCodeInfoType {
|
||||
String(String),
|
||||
Int32(i32),
|
||||
Int64(i64),
|
||||
UInt32(u32),
|
||||
UInt64(u64),
|
||||
}
|
||||
|
||||
pub enum SerialNumber {
|
||||
String(String),
|
||||
UInt32(u32),
|
||||
}
|
||||
|
||||
pub struct OptionalQRCodeInfo {
|
||||
// the tag number of the optional info
|
||||
pub tag: u8,
|
||||
// the data of the optional info
|
||||
pub data: QRCodeInfoType,
|
||||
}
|
||||
|
||||
pub struct QrSetupPayload<'data> {
|
||||
version: u8,
|
||||
flow_type: CommissionningFlowType,
|
||||
discovery_capabilities: DiscoveryCapabilities,
|
||||
dev_det: &'data BasicInfoConfig,
|
||||
comm_data: &'data CommissioningData,
|
||||
// we use a BTreeMap to keep the order of the optional data stable
|
||||
optional_data: BTreeMap<u8, OptionalQRCodeInfo>,
|
||||
}
|
||||
|
||||
impl<'data> QrSetupPayload<'data> {
|
||||
pub fn new(
|
||||
dev_det: &'data BasicInfoConfig,
|
||||
comm_data: &'data CommissioningData,
|
||||
discovery_capabilities: DiscoveryCapabilities,
|
||||
) -> Self {
|
||||
const DEFAULT_VERSION: u8 = 0;
|
||||
|
||||
let mut result = QrSetupPayload {
|
||||
version: DEFAULT_VERSION,
|
||||
flow_type: CommissionningFlowType::Standard,
|
||||
discovery_capabilities,
|
||||
dev_det,
|
||||
comm_data,
|
||||
optional_data: BTreeMap::new(),
|
||||
};
|
||||
|
||||
if !dev_det.serial_no.is_empty() {
|
||||
result.add_serial_number(SerialNumber::String(dev_det.serial_no.clone()));
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn is_valid(&self) -> bool {
|
||||
let passwd = passwd_from_comm_data(self.comm_data);
|
||||
|
||||
// 3-bit value specifying the QR code payload version.
|
||||
if self.version >= 1 << VERSION_FIELD_LENGTH_IN_BITS {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !self.discovery_capabilities.has_value() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if passwd >= 1 << SETUP_PINCODE_FIELD_LENGTH_IN_BITS {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.check_payload_common_constraints()
|
||||
}
|
||||
|
||||
/// A function to add an optional vendor data
|
||||
/// # Arguments
|
||||
/// * `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);
|
||||
}
|
||||
|
||||
self.optional_data
|
||||
.insert(tag, OptionalQRCodeInfo { tag, data });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A function to add an optional QR Code info CHIP object
|
||||
/// # Arguments
|
||||
/// * `tag` - one of the CHIP-Common Reserved Tags
|
||||
/// * `data` - Data to add
|
||||
pub fn add_optional_extension_data(
|
||||
&mut self,
|
||||
tag: u8,
|
||||
data: QRCodeInfoType,
|
||||
) -> Result<(), Error> {
|
||||
if !is_common_tag(tag) {
|
||||
return Err(Error::InvalidArgument);
|
||||
}
|
||||
|
||||
self.optional_data
|
||||
.insert(tag, OptionalQRCodeInfo { tag, data });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_all_optional_data(&self) -> &BTreeMap<u8, OptionalQRCodeInfo> {
|
||||
&self.optional_data
|
||||
}
|
||||
|
||||
pub fn add_serial_number(&mut self, serial_number: SerialNumber) {
|
||||
match serial_number {
|
||||
SerialNumber::String(serial_number) => self.add_optional_extension_data(
|
||||
SERIAL_NUMBER_TAG,
|
||||
QRCodeInfoType::String(serial_number),
|
||||
),
|
||||
SerialNumber::UInt32(serial_number) => self.add_optional_extension_data(
|
||||
SERIAL_NUMBER_TAG,
|
||||
QRCodeInfoType::UInt32(serial_number),
|
||||
),
|
||||
}
|
||||
.expect("can not add serial number");
|
||||
}
|
||||
|
||||
fn check_payload_common_constraints(&self) -> bool {
|
||||
// A version not equal to 0 would be invalid for v1 and would indicate new format (e.g. version 2)
|
||||
if self.version != 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let passwd = passwd_from_comm_data(self.comm_data);
|
||||
|
||||
if !Self::is_valid_setup_pin(passwd) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// VendorID must be unspecified (0) or in valid range expected.
|
||||
if !is_vendor_id_valid_operationally(self.dev_det.vid)
|
||||
&& (self.dev_det.vid != VendorId::CommonOrUnspecified as u16)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// A value of 0x0000 SHALL NOT be assigned to a product since Product ID = 0x0000 is used for these specific cases:
|
||||
// * To announce an anonymized Product ID as part of device discovery
|
||||
// * To indicate an OTA software update file applies to multiple Product IDs equally.
|
||||
// * To avoid confusion when presenting the Onboarding Payload for ECM with multiple nodes
|
||||
if self.dev_det.pid == 0 && self.dev_det.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;
|
||||
|
||||
// SHALL be restricted to the values 0x0000001 to 0x5F5E0FE (00000001 to 99999998 in decimal), excluding the invalid Passcode
|
||||
// values.
|
||||
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
|
||||
}
|
||||
|
||||
fn has_tlv(&self) -> bool {
|
||||
!self.optional_data.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum CommissionningFlowType {
|
||||
Standard = 0,
|
||||
UserIntent = 1,
|
||||
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(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,
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
(vec![0; TOTAL_PAYLOAD_DATA_SIZE_IN_BYTES], None)
|
||||
};
|
||||
|
||||
if !payload.is_valid() {
|
||||
return Err(Error::InvalidArgument);
|
||||
}
|
||||
|
||||
payload_base38_representation_with_tlv(payload, &mut bits, tlv_data)
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
let data_item_size_estimate = |info: &QRCodeInfoType| {
|
||||
// Each data item needs a control byte and a context tag.
|
||||
let mut size: usize = 2;
|
||||
|
||||
if let QRCodeInfoType::String(data) = info {
|
||||
// We'll need to encode the string length and then the string data.
|
||||
// Length is at most 8 bytes.
|
||||
size += 8;
|
||||
size += data.as_bytes().len()
|
||||
} else {
|
||||
// Integer. Assume it might need up to 8 bytes, for simplicity.
|
||||
size += 8;
|
||||
}
|
||||
|
||||
size
|
||||
};
|
||||
|
||||
let vendor_data = payload.get_all_optional_data();
|
||||
vendor_data.values().for_each(|data| {
|
||||
estimate += data_item_size_estimate(&data.data);
|
||||
});
|
||||
|
||||
estimate = estimate_struct_overhead(estimate);
|
||||
|
||||
if estimate > u32::MAX as usize {
|
||||
return Err(Error::NoMemory);
|
||||
}
|
||||
|
||||
Ok(estimate)
|
||||
}
|
||||
|
||||
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.
|
||||
//
|
||||
// The struct itself has a control byte and an end-of-struct marker.
|
||||
first_field_size + 4 + 2
|
||||
}
|
||||
|
||||
pub(super) fn print_qr_code(qr_data: &str) {
|
||||
let needed_version = compute_qr_version(qr_data);
|
||||
let code =
|
||||
QrCode::with_version(qr_data, Version::Normal(needed_version), qrcode::EcLevel::M).unwrap();
|
||||
let image = code
|
||||
.render::<unicode::Dense1x2>()
|
||||
.dark_color(unicode::Dense1x2::Light)
|
||||
.light_color(unicode::Dense1x2::Dark)
|
||||
.build();
|
||||
info!("\n{}", image);
|
||||
}
|
||||
|
||||
fn compute_qr_version(qr_data: &str) -> i16 {
|
||||
match qr_data.len() {
|
||||
0..=38 => 2,
|
||||
39..=61 => 3,
|
||||
62..=90 => 4,
|
||||
_ => 5,
|
||||
}
|
||||
}
|
||||
|
||||
fn populate_bits(
|
||||
bits: &mut [u8],
|
||||
offset: &mut usize,
|
||||
mut input: u64,
|
||||
number_of_bits: usize,
|
||||
total_payload_data_size_in_bits: usize,
|
||||
) -> Result<(), Error> {
|
||||
if *offset + number_of_bits > total_payload_data_size_in_bits {
|
||||
return Err(Error::InvalidArgument);
|
||||
}
|
||||
|
||||
if input >= 1u64 << number_of_bits {
|
||||
return Err(Error::InvalidArgument);
|
||||
}
|
||||
|
||||
let mut index = *offset;
|
||||
*offset += number_of_bits;
|
||||
|
||||
while input != 0 {
|
||||
if input & 1 == 1 {
|
||||
let mask = (1 << (index % 8)) as u8;
|
||||
bits[index / 8] |= mask;
|
||||
}
|
||||
index += 1;
|
||||
input >>= 1;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn payload_base38_representation_with_tlv(
|
||||
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)?;
|
||||
}
|
||||
|
||||
let bytes_written = generate_bit_set(payload, bits, tlv_data)?;
|
||||
let base38_encoded = base38::encode(&*bits, Some(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) => 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)?,
|
||||
}
|
||||
}
|
||||
|
||||
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<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.unwrap_or_default() * 8)
|
||||
} else {
|
||||
TOTAL_PAYLOAD_DATA_SIZE_IN_BITS
|
||||
};
|
||||
|
||||
if bits.len() * 8 < total_payload_size_in_bits {
|
||||
return Err(Error::BufferTooSmall);
|
||||
};
|
||||
|
||||
let passwd = passwd_from_comm_data(payload.comm_data);
|
||||
|
||||
populate_bits(
|
||||
bits,
|
||||
&mut offset,
|
||||
payload.version as u64,
|
||||
VERSION_FIELD_LENGTH_IN_BITS,
|
||||
total_payload_size_in_bits,
|
||||
)?;
|
||||
|
||||
populate_bits(
|
||||
bits,
|
||||
&mut offset,
|
||||
payload.dev_det.vid as u64,
|
||||
VENDOR_IDFIELD_LENGTH_IN_BITS,
|
||||
total_payload_size_in_bits,
|
||||
)?;
|
||||
|
||||
populate_bits(
|
||||
bits,
|
||||
&mut offset,
|
||||
payload.dev_det.pid as u64,
|
||||
PRODUCT_IDFIELD_LENGTH_IN_BITS,
|
||||
total_payload_size_in_bits,
|
||||
)?;
|
||||
|
||||
populate_bits(
|
||||
bits,
|
||||
&mut offset,
|
||||
payload.flow_type as u64,
|
||||
COMMISSIONING_FLOW_FIELD_LENGTH_IN_BITS,
|
||||
total_payload_size_in_bits,
|
||||
)?;
|
||||
|
||||
populate_bits(
|
||||
bits,
|
||||
&mut offset,
|
||||
payload.discovery_capabilities.as_bits() as u64,
|
||||
RENDEZVOUS_INFO_FIELD_LENGTH_IN_BITS,
|
||||
total_payload_size_in_bits,
|
||||
)?;
|
||||
|
||||
populate_bits(
|
||||
bits,
|
||||
&mut offset,
|
||||
payload.comm_data.discriminator as u64,
|
||||
PAYLOAD_DISCRIMINATOR_FIELD_LENGTH_IN_BITS,
|
||||
total_payload_size_in_bits,
|
||||
)?;
|
||||
|
||||
populate_bits(
|
||||
bits,
|
||||
&mut offset,
|
||||
passwd as u64,
|
||||
SETUP_PINCODE_FIELD_LENGTH_IN_BITS,
|
||||
total_payload_size_in_bits,
|
||||
)?;
|
||||
|
||||
populate_bits(
|
||||
bits,
|
||||
&mut offset,
|
||||
0,
|
||||
PADDING_FIELD_LENGTH_IN_BITS,
|
||||
total_payload_size_in_bits,
|
||||
)?;
|
||||
|
||||
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 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);
|
||||
}
|
||||
|
||||
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::*;
|
||||
use crate::secure_channel::spake2p::VerifierData;
|
||||
|
||||
#[test]
|
||||
fn can_base38_encode() {
|
||||
const QR_CODE: &str = "MT:YNJV7VSC00CMVH7SR00";
|
||||
|
||||
let comm_data = CommissioningData {
|
||||
verifier: VerifierData::new_with_pw(34567890),
|
||||
discriminator: 2976,
|
||||
};
|
||||
let dev_det = BasicInfoConfig {
|
||||
vid: 9050,
|
||||
pid: 65279,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
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");
|
||||
assert_eq!(data_str, QR_CODE)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_base38_encode_with_vendor_data() {
|
||||
const QR_CODE: &str = "MT:-24J0AFN00KA064IJ3P0IXZB0DK5N1K8SQ1RYCU1-A40";
|
||||
|
||||
let comm_data = CommissioningData {
|
||||
verifier: VerifierData::new_with_pw(20202021),
|
||||
discriminator: 3840,
|
||||
};
|
||||
let dev_det = BasicInfoConfig {
|
||||
vid: 65521,
|
||||
pid: 32769,
|
||||
serial_no: "1234567890".to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
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");
|
||||
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; // Vendor "test" tag
|
||||
const OPTIONAL_DEFAULT_STRING_VALUE: &str = "myData";
|
||||
|
||||
const OPTIONAL_DEFAULT_INT_TAG: u8 = 0x83; // Vendor "test" tag
|
||||
const OPTIONAL_DEFAULT_INT_VALUE: i32 = 65550;
|
||||
|
||||
let comm_data = CommissioningData {
|
||||
verifier: VerifierData::new_with_pw(20202021),
|
||||
discriminator: 3840,
|
||||
};
|
||||
let dev_det = BasicInfoConfig {
|
||||
vid: 65521,
|
||||
pid: 32769,
|
||||
serial_no: "1234567890".to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let disc_cap = DiscoveryCapabilities::new(true, false, false);
|
||||
let mut qr_code_data = QrSetupPayload::new(&dev_det, &comm_data, disc_cap);
|
||||
|
||||
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");
|
||||
|
||||
// todo: check why unsigned ints are not accepted by 'chip-tool payload parse-setup-payload'
|
||||
|
||||
qr_code_data
|
||||
.add_optional_vendor_data(
|
||||
OPTIONAL_DEFAULT_INT_TAG,
|
||||
QRCodeInfoType::Int32(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)
|
||||
}
|
||||
}
|
10
matter/src/pairing/vendor_identifiers.rs
Normal file
10
matter/src/pairing/vendor_identifiers.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
#[repr(u16)]
|
||||
pub enum VendorId {
|
||||
CommonOrUnspecified = 0x0000,
|
||||
TestVendor4 = 0xFFF4,
|
||||
}
|
||||
|
||||
pub fn is_vendor_id_valid_operationally(vendor_id: u16) -> bool {
|
||||
(vendor_id != VendorId::CommonOrUnspecified as u16)
|
||||
&& (vendor_id <= VendorId::TestVendor4 as u16)
|
||||
}
|
|
@ -115,7 +115,7 @@ impl CryptoSpake2 for CryptoMbedTLS {
|
|||
}
|
||||
|
||||
fn set_L(&mut self, l: &[u8]) -> Result<(), Error> {
|
||||
self.L = EcPoint::from_binary(&mut self.group, l)?;
|
||||
self.L = EcPoint::from_binary(&self.group, l)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -95,6 +95,15 @@ impl<'a, 'b> TLVWriter<'a, 'b> {
|
|||
self.buf.le_u8(data)
|
||||
}
|
||||
|
||||
pub fn i16(&mut self, tag_type: TagType, data: i16) -> Result<(), Error> {
|
||||
if data >= i8::MIN as i16 && data <= i8::MAX as i16 {
|
||||
self.i8(tag_type, data as i8)
|
||||
} else {
|
||||
self.put_control_tag(tag_type, WriteElementType::S16)?;
|
||||
self.buf.le_i16(data)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn u16(&mut self, tag_type: TagType, data: u16) -> Result<(), Error> {
|
||||
if data <= 0xff {
|
||||
self.u8(tag_type, data as u8)
|
||||
|
@ -104,6 +113,17 @@ impl<'a, 'b> TLVWriter<'a, 'b> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn i32(&mut self, tag_type: TagType, data: i32) -> Result<(), Error> {
|
||||
if data >= i8::MIN as i32 && data <= i8::MAX as i32 {
|
||||
self.i8(tag_type, data as i8)
|
||||
} else if data >= i16::MIN as i32 && data <= i16::MAX as i32 {
|
||||
self.i16(tag_type, data as i16)
|
||||
} else {
|
||||
self.put_control_tag(tag_type, WriteElementType::S32)?;
|
||||
self.buf.le_i32(data)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn u32(&mut self, tag_type: TagType, data: u32) -> Result<(), Error> {
|
||||
if data <= 0xff {
|
||||
self.u8(tag_type, data as u8)
|
||||
|
@ -115,6 +135,19 @@ impl<'a, 'b> TLVWriter<'a, 'b> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn i64(&mut self, tag_type: TagType, data: i64) -> Result<(), Error> {
|
||||
if data >= i8::MIN as i64 && data <= i8::MAX as i64 {
|
||||
self.i8(tag_type, data as i8)
|
||||
} else if data >= i16::MIN as i64 && data <= i16::MAX as i64 {
|
||||
self.i16(tag_type, data as i16)
|
||||
} else if data >= i32::MIN as i64 && data <= i32::MAX as i64 {
|
||||
self.i32(tag_type, data as i32)
|
||||
} else {
|
||||
self.put_control_tag(tag_type, WriteElementType::S64)?;
|
||||
self.buf.le_i64(data)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn u64(&mut self, tag_type: TagType, data: u64) -> Result<(), Error> {
|
||||
if data <= 0xff {
|
||||
self.u8(tag_type, data as u8)
|
||||
|
|
|
@ -131,6 +131,11 @@ impl<'a> WriteBuf<'a> {
|
|||
LittleEndian::write_u16(&mut x.buf[x.end..], data);
|
||||
})
|
||||
}
|
||||
pub fn le_i16(&mut self, data: i16) -> Result<(), Error> {
|
||||
self.append_with(2, |x| {
|
||||
LittleEndian::write_i16(&mut x.buf[x.end..], data);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn le_u32(&mut self, data: u32) -> Result<(), Error> {
|
||||
self.append_with(4, |x| {
|
||||
|
@ -138,12 +143,24 @@ impl<'a> WriteBuf<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn le_i32(&mut self, data: i32) -> Result<(), Error> {
|
||||
self.append_with(4, |x| {
|
||||
LittleEndian::write_i32(&mut x.buf[x.end..], data);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn le_u64(&mut self, data: u64) -> Result<(), Error> {
|
||||
self.append_with(8, |x| {
|
||||
LittleEndian::write_u64(&mut x.buf[x.end..], data);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn le_i64(&mut self, data: i64) -> Result<(), Error> {
|
||||
self.append_with(8, |x| {
|
||||
LittleEndian::write_i64(&mut x.buf[x.end..], data);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn le_uint(&mut self, nbytes: usize, data: u64) -> Result<(), Error> {
|
||||
self.append_with(nbytes, |x| {
|
||||
LittleEndian::write_uint(&mut x.buf[x.end..], data, nbytes);
|
||||
|
|
|
@ -95,7 +95,9 @@ impl ImEngine {
|
|||
hw_ver: 12,
|
||||
sw_ver: 13,
|
||||
serial_no: "aabbccdd".to_string(),
|
||||
device_name: "Test Device".to_string(),
|
||||
};
|
||||
|
||||
let dev_att = Box::new(DummyDevAtt {});
|
||||
let fabric_mgr = Arc::new(FabricMgr::new().unwrap());
|
||||
let acl_mgr = Arc::new(AclMgr::new_with(false).unwrap());
|
||||
|
@ -105,14 +107,7 @@ impl ImEngine {
|
|||
// Only allow the standard peer node id of the IM Engine
|
||||
default_acl.add_subject(IM_ENGINE_PEER_ID).unwrap();
|
||||
acl_mgr.add(default_acl).unwrap();
|
||||
let dm = DataModel::new(
|
||||
dev_det,
|
||||
dev_att,
|
||||
fabric_mgr.clone(),
|
||||
acl_mgr.clone(),
|
||||
pase_mgr,
|
||||
)
|
||||
.unwrap();
|
||||
let dm = DataModel::new(dev_det, dev_att, fabric_mgr, acl_mgr.clone(), pase_mgr).unwrap();
|
||||
|
||||
{
|
||||
let mut d = dm.node.write().unwrap();
|
||||
|
|
Loading…
Add table
Reference in a new issue