1use hap_tlv8::Tlv8Writer;
15
16pub(crate) const THREAD_CONTROL_POINT_UUID: &str = "00000704-0000-1000-8000-0026bb765291";
18
19#[derive(Clone)]
25pub struct ThreadDataset {
26 pub network_name: String,
28 pub channel: u8,
30 pub pan_id: u16,
32 pub ext_pan_id: [u8; 8],
34 pub network_key: [u8; 16],
36}
37
38impl core::fmt::Debug for ThreadDataset {
39 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
41 f.debug_struct("ThreadDataset")
42 .field("network_name", &self.network_name)
43 .field("channel", &self.channel)
44 .field("pan_id", &format_args!("{:#06x}", self.pan_id))
45 .field(
46 "ext_pan_id",
47 &format_args!("{}", hex_lower(&self.ext_pan_id)),
48 )
49 .field("network_key", &"<redacted>")
50 .finish()
51 }
52}
53
54pub(crate) fn encode_query() -> Vec<u8> {
57 let mut out = Vec::new();
58 let mut w = Tlv8Writer::new(&mut out);
59 w.push(1, &[0x03]);
60 out
61}
62
63pub(crate) fn encode_provision(dataset: &ThreadDataset) -> Vec<u8> {
70 let mut inner = Vec::new();
71 let mut iw = Tlv8Writer::new(&mut inner);
72 iw.push(1, dataset.network_name.as_bytes());
73 iw.push(2, &[dataset.channel]);
74 iw.push(3, &dataset.pan_id.to_le_bytes());
75 iw.push(4, &dataset.ext_pan_id);
76 iw.push(5, &dataset.network_key);
77
78 let mut out = Vec::new();
79 let mut w = Tlv8Writer::new(&mut out);
80 w.push(1, &[0x01]);
81 w.push(2, &inner);
82 w.push(3, &[0x01]);
83 out
84}
85
86fn hex_lower(bytes: &[u8]) -> String {
88 let mut s = String::with_capacity(bytes.len() * 2);
89 for b in bytes {
90 s.push(char::from_digit(u32::from(b >> 4), 16).unwrap_or('0'));
91 s.push(char::from_digit(u32::from(b & 0xf), 16).unwrap_or('0'));
92 }
93 s
94}
95
96#[cfg(test)]
97#[allow(clippy::unwrap_used, clippy::expect_used)]
98mod tests {
99 use super::*;
100
101 fn test_dataset() -> ThreadDataset {
103 ThreadDataset {
104 network_name: "OpenThread-test".into(),
105 channel: 15,
106 pan_id: 0x1234,
107 ext_pan_id: [0xde, 0xad, 0xbe, 0xef, 0x0b, 0xad, 0xf0, 0x0d],
108 network_key: [
109 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
110 0xee, 0xff,
111 ],
112 }
113 }
114
115 const QUERY_HEX: &str = "010103";
118 const PROVISION_HEX: &str = "0101010234010f4f70656e5468726561642d7465737402010f030234120408deadbeef0badf00d051000112233445566778899aabbccddeeff030101";
119
120 #[test]
121 fn query_matches_aiohomekit() {
122 assert_eq!(hex_lower(&encode_query()), QUERY_HEX);
123 }
124
125 #[test]
126 fn provision_matches_aiohomekit_byte_for_byte() {
127 assert_eq!(hex_lower(&encode_provision(&test_dataset())), PROVISION_HEX);
128 }
129
130 #[test]
131 fn debug_redacts_the_network_key() {
132 let dbg = format!("{:?}", test_dataset());
133 assert!(dbg.contains("<redacted>"), "network key must be redacted");
134 assert!(
135 !dbg.contains("00112233"),
136 "the key bytes must not appear in Debug"
137 );
138 }
139}