use hap_tlv8::Tlv8Writer;
pub(crate) const THREAD_CONTROL_POINT_UUID: &str = "00000704-0000-1000-8000-0026bb765291";
#[derive(Clone)]
pub struct ThreadDataset {
pub network_name: String,
pub channel: u8,
pub pan_id: u16,
pub ext_pan_id: [u8; 8],
pub network_key: [u8; 16],
}
impl core::fmt::Debug for ThreadDataset {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ThreadDataset")
.field("network_name", &self.network_name)
.field("channel", &self.channel)
.field("pan_id", &format_args!("{:#06x}", self.pan_id))
.field(
"ext_pan_id",
&format_args!("{}", hex_lower(&self.ext_pan_id)),
)
.field("network_key", &"<redacted>")
.finish()
}
}
pub(crate) fn encode_query() -> Vec<u8> {
let mut out = Vec::new();
let mut w = Tlv8Writer::new(&mut out);
w.push(1, &[0x03]);
out
}
pub(crate) fn encode_provision(dataset: &ThreadDataset) -> Vec<u8> {
let mut inner = Vec::new();
let mut iw = Tlv8Writer::new(&mut inner);
iw.push(1, dataset.network_name.as_bytes());
iw.push(2, &[dataset.channel]);
iw.push(3, &dataset.pan_id.to_le_bytes());
iw.push(4, &dataset.ext_pan_id);
iw.push(5, &dataset.network_key);
let mut out = Vec::new();
let mut w = Tlv8Writer::new(&mut out);
w.push(1, &[0x01]);
w.push(2, &inner);
w.push(3, &[0x01]);
out
}
fn hex_lower(bytes: &[u8]) -> String {
let mut s = String::with_capacity(bytes.len() * 2);
for b in bytes {
s.push(char::from_digit(u32::from(b >> 4), 16).unwrap_or('0'));
s.push(char::from_digit(u32::from(b & 0xf), 16).unwrap_or('0'));
}
s
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
fn test_dataset() -> ThreadDataset {
ThreadDataset {
network_name: "OpenThread-test".into(),
channel: 15,
pan_id: 0x1234,
ext_pan_id: [0xde, 0xad, 0xbe, 0xef, 0x0b, 0xad, 0xf0, 0x0d],
network_key: [
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff,
],
}
}
const QUERY_HEX: &str = "010103";
const PROVISION_HEX: &str = "0101010234010f4f70656e5468726561642d7465737402010f030234120408deadbeef0badf00d051000112233445566778899aabbccddeeff030101";
#[test]
fn query_matches_aiohomekit() {
assert_eq!(hex_lower(&encode_query()), QUERY_HEX);
}
#[test]
fn provision_matches_aiohomekit_byte_for_byte() {
assert_eq!(hex_lower(&encode_provision(&test_dataset())), PROVISION_HEX);
}
#[test]
fn debug_redacts_the_network_key() {
let dbg = format!("{:?}", test_dataset());
assert!(dbg.contains("<redacted>"), "network key must be redacted");
assert!(
!dbg.contains("00112233"),
"the key bytes must not appear in Debug"
);
}
}