mod common;
mod manual;
mod qr;
pub use common::CommissioningFlow;
use crate::base38;
use crate::bit_utils::{bits_to_u64_be, bytes_to_bits_be};
use crate::error::{PayloadError, Result};
use crate::verhoeff::calculate_checksum;
use deku::prelude::*;
use manual::ManualCodeData;
use qr::QrCodeData;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SetupPayload {
pub long_discriminator: Option<u16>,
pub short_discriminator: u8,
pub pincode: u32,
pub discovery: Option<u8>,
pub flow: CommissioningFlow,
pub vid: Option<u16>,
pub pid: Option<u16>,
}
impl SetupPayload {
pub fn new(
discriminator: u16,
pincode: u32,
rendezvous: Option<u8>,
flow: Option<CommissioningFlow>,
vid: Option<u16>,
pid: Option<u16>,
) -> Self {
let long_discriminator = if discriminator == 0 {
None
} else {
Some(discriminator)
};
let short_discriminator = (discriminator >> 8) as u8;
let discovery = rendezvous.filter(|&d| d != 0);
SetupPayload {
long_discriminator,
short_discriminator,
pincode,
discovery,
flow: flow.unwrap_or(CommissioningFlow::Standard),
vid,
pid,
}
}
pub fn parse_str(payload_str: &str) -> Result<Self> {
if payload_str.starts_with("MT:") {
let container = QrCodeData::parse_from_str(payload_str)?;
Ok(SetupPayload::new(
container.discriminator,
container.pincode,
Some(container.discovery),
Some(container.flow),
Some(container.vid),
Some(container.pid),
))
} else {
let container = ManualCodeData::parse_from_str(payload_str)?;
let mut payload = SetupPayload::new(
container.discriminator.into(),
((container.pincode_msb as u32) << 14) | (container.pincode_lsb as u32),
None,
if container.vid_pid_present != 0 {
Some(CommissioningFlow::Custom)
} else {
None
},
if container.vid_pid_present != 0 {
container.vid
} else {
None
},
if container.vid_pid_present != 0 {
container.pid
} else {
None
},
);
payload.short_discriminator = container.discriminator;
payload.long_discriminator = None;
payload.discovery = None;
Ok(payload)
}
}
pub fn to_qr_code_str(&self) -> Result<String> {
let qr_data = QrCodeData {
version: 0,
vid: self.vid.expect("VID is required for QR code generation"),
pid: self.pid.expect("PID is required for QR code generation"),
flow: self.flow,
discovery: self
.discovery
.expect("Discovery is required for QR code generation"),
discriminator: self
.long_discriminator
.expect("Long discriminator is required for QR code generation"),
pincode: self.pincode,
padding: 0,
};
let mut bytes = qr_data.to_bytes()?;
bytes.reverse();
let encoded = base38::encode(&bytes);
Ok(format!("MT:{}", encoded))
}
pub fn to_manual_code_str(&self) -> Result<String> {
let discriminator_val =
if self.short_discriminator == 0 && self.long_discriminator.unwrap_or(0) <= 15 {
self.long_discriminator.unwrap_or(0) as u8
} else {
self.short_discriminator
};
if discriminator_val > 15 {
return Err(PayloadError::DiscriminatorOutOfRange(discriminator_val).into());
}
let manual_code = ManualCodeData {
version: 0, vid_pid_present: if self.flow == CommissioningFlow::Standard {
0
} else {
1
},
discriminator: discriminator_val,
pincode_lsb: (self.pincode & 0x3FFF) as u16,
pincode_msb: ((self.pincode >> 14) & 0x1FFF) as u16,
vid: if self.flow == CommissioningFlow::Standard {
Some(0)
} else {
self.vid
},
pid: if self.flow == CommissioningFlow::Standard {
Some(0)
} else {
self.pid
},
padding: 0,
};
let packed_bytes = manual_code.to_bytes()?;
let bits = bytes_to_bits_be(&packed_bytes);
let c1 = bits_to_u64_be(&bits[0..4]);
let c2 = bits_to_u64_be(&bits[4..20]);
let c3 = bits_to_u64_be(&bits[20..33]);
let mut code_string = format!("{}{:05}{:04}", c1, c2, c3);
let checksum_digit = calculate_checksum(&code_string)?;
code_string.push(std::char::from_digit(checksum_digit as u32, 10).unwrap());
Ok(code_string)
}
}
#[cfg(test)]
mod tests {
use crate::MatterPayloadError;
use super::*;
fn standard_payload() -> SetupPayload {
SetupPayload {
short_discriminator: 4,
long_discriminator: Some(1132),
pincode: 69414998,
vid: Some(0xfff1),
pid: Some(0x8000),
flow: CommissioningFlow::Standard,
discovery: Some(4),
}
}
#[test]
fn test_qr_code_roundtrip() {
let original_payload = standard_payload();
let qr_str = original_payload.to_qr_code_str().unwrap();
assert_eq!(qr_str, "MT:Y.K904QI143LH13SH10");
let parsed_payload = SetupPayload::parse_str(&qr_str).unwrap();
assert_eq!(original_payload, parsed_payload);
}
#[test]
fn test_manual_code_roundtrip() {
let original_payload = standard_payload();
let manual_str = original_payload.to_manual_code_str().unwrap();
assert_eq!(manual_str, "11237442363");
let parsed_payload = SetupPayload::parse_str(&manual_str).unwrap();
assert_eq!(
original_payload.short_discriminator,
parsed_payload.short_discriminator
);
assert_eq!(original_payload.pincode, parsed_payload.pincode);
}
#[test]
fn test_short_manual_code() {
let payload = SetupPayload {
short_discriminator: 4,
long_discriminator: None,
vid: None,
pid: None,
pincode: 69414998,
flow: CommissioningFlow::Standard,
discovery: Some(0),
};
let manual_str = payload.to_manual_code_str().unwrap();
assert_eq!(manual_str, "11237442363");
let parsed = SetupPayload::parse_str(&manual_str).unwrap();
assert_eq!(payload.short_discriminator, parsed.short_discriminator);
assert_eq!(payload.pincode, parsed.pincode);
}
#[test]
fn test_invalid_manual_code_errors() {
let err = SetupPayload::parse_str("12345").unwrap_err();
assert!(matches!(
err,
MatterPayloadError::Payload(PayloadError::InvalidManualCodeLength(5))
));
let err = SetupPayload::parse_str("20000000031").unwrap_err();
assert!(matches!(
err,
MatterPayloadError::Payload(PayloadError::InvalidManualCodeChecksum)
));
}
}