1use anyhow::{Context, Result};
2use rand::RngCore;
3
4use crate::{
5 cert_matter, cert_x509, certmanager, controller::auth_sigma, fabric::Fabric, messages,
6 retransmit, session, tlv, transport::ConnectionTrait,
7};
8
9const CLUSTER_OPERATIONAL_CREDENTIALS: u32 = 0x3e;
10const CMD_OPERATIONAL_CREDENTIALS_ADDTRUSTEDROOTCERTIFICATE: u32 = 0xb;
11const CMD_OPERATIONAL_CREDENTIALS_ADDNOC: u32 = 0x6;
12const CMD_OPERATIONAL_CSRREQUEST: u32 = 0x4;
13const CLUSTER_GENERAL_COMMISSIONING: u32 = 0x30;
17const CMD_GENERAL_COMMISSIONING_ARMFAILSAFE: u32 = 0;
18const CMD_GENERAL_COMMISSIONING_COMMISSIONINGCOMPLETE: u32 = 4;
20
21#[cfg(feature = "ble")]
22const CLUSTER_NETWORK_COMMISSIONING: u32 = 0x31;
23#[cfg(feature = "ble")]
24const CMD_NETWORK_ADD_OR_UPDATE_WIFI: u32 = 2;
25#[cfg(feature = "ble")]
26const CMD_NETWORK_ADD_OR_UPDATE_THREAD: u32 = 3;
27#[cfg(feature = "ble")]
28const CMD_NETWORK_CONNECT: u32 = 6;
29
30#[derive(Clone)]
32pub enum NetworkCreds {
33 WiFi { ssid: Vec<u8>, creds: Vec<u8> },
35 Thread { dataset: Vec<u8> },
37 AlreadyOnNetwork,
40}
41
42
43async fn push_ca_cert(
101 retrcrx: &mut retransmit::RetrContext<'_>,
102 cm: &dyn certmanager::CertManager,
103 exchange_id: u16,
104) -> Result<()> {
105 let ca_pubkey = cm.get_ca_key()?.public_key().to_sec1_bytes();
106 let ca_cert = cm.get_ca_cert()?;
107 let mcert = cert_matter::convert_x509_bytes_to_matter(&ca_cert, &ca_pubkey)?;
108 log::debug!("AddTrustedRootCertificate: matter cert TLV ({} bytes): {}", mcert.len(),
109 mcert.iter().map(|b| format!("{:02x}", b)).collect::<Vec<_>>().join(""));
110 let mut tlv = tlv::TlvBuffer::new();
111 tlv.write_octetstring(0, &mcert)?;
112 let t1 = messages::im_invoke_request(
113 0,
114 CLUSTER_OPERATIONAL_CREDENTIALS,
115 CMD_OPERATIONAL_CREDENTIALS_ADDTRUSTEDROOTCERTIFICATE,
116 exchange_id,
117 &tlv.data,
118 false,
119 )?;
120 retrcrx.send(&t1).await?;
121
122 let resp = retrcrx.get_next_message().await?;
124 let noc_status = {
125 resp.tlv
126 .get_int(&[1, 0, 1, 1, 0])
127 .context("can't get status for AddTrustedRootCertificate")?
128 };
129 if noc_status != 0 {
130 return Err(anyhow::anyhow!(
131 "AddTrustedRootCertificate failed with status {}/{}",
132 noc_status,
133 noc_status_to_str(noc_status)
134 ));
135 }
136 Ok(())
137}
138
139fn noc_status_to_str(status: u64) -> &'static str {
140 match status {
141 0 => "Success",
142 1 => "InvalidPublicKey",
143 2 => "InvalidNodeOpId",
144 3 => "InvalidNOC",
145 4 => "MissingCsr",
146 5 => "TableFull",
147 6 => "InvalidAdminSubject",
148 7 => "?",
149 8 => "?",
150 9 => "FabricConflict",
151 10 => "LabelConflict",
152 11 => "InvalidFabricIndex",
153 _ => "UnknownStatus",
154 }
155}
156
157async fn push_device_cert(
158 retrcrx: &mut retransmit::RetrContext<'_>,
159 cm: &dyn certmanager::CertManager,
160 csrd: x509_cert::request::CertReq,
161 node_id: u64,
162 controller_id: u64,
163 fabric: &Fabric,
164 exchange_id: u16,
165) -> Result<()> {
166 let ca_id = fabric.ca_id;
167 let ca_pubkey = cm.get_ca_key()?.public_key().to_sec1_bytes();
168 let node_public_key = csrd
169 .info
170 .public_key
171 .subject_public_key
172 .as_bytes()
173 .context("can't extract pubkey from csr")?;
174 let ca_private = cm.get_ca_key()?;
175 let noc_x509 = cert_x509::encode_x509(
176 node_public_key,
177 node_id,
178 cm.get_fabric_id(),
179 ca_id,
180 &ca_private,
181 false,
182 )?;
183 let noc = cert_matter::convert_x509_bytes_to_matter(&noc_x509, &ca_pubkey)?;
184 let mut tlv = tlv::TlvBuffer::new();
185 tlv.write_octetstring(0, &noc)?;
186 tlv.write_octetstring(2, &fabric.ipk_epoch_key)?;
187 tlv.write_uint64(3, controller_id)?;
188 tlv.write_uint16(4, 101)?;
189 let t1 = messages::im_invoke_request(
190 0,
191 CLUSTER_OPERATIONAL_CREDENTIALS,
192 CMD_OPERATIONAL_CREDENTIALS_ADDNOC,
193 exchange_id,
194 &tlv.data,
195 false,
196 )?;
197 retrcrx.send(&t1).await?;
198
199 let resp = retrcrx.get_next_message().await?;
200 let noc_status = {
201 resp.tlv
202 .get_int(&[1, 0, 0, 1, 0])
203 .context("can't get status for AddNOC")?
204 };
205 if noc_status != 0 {
206 return Err(anyhow::anyhow!("AddNOC failed with status {}/{}", noc_status, noc_status_to_str(noc_status)));
207 }
208 Ok(())
209}
210
211async fn send_csr(
212 retrcrx: &mut retransmit::RetrContext<'_>,
213 exchange_id: u16,
214) -> Result<x509_cert::request::CertReq> {
215 let mut tlv = tlv::TlvBuffer::new();
216 let mut random_csr_nonce = vec![0; 32];
217 rand::thread_rng().fill_bytes(&mut random_csr_nonce);
218 tlv.write_octetstring(0, &random_csr_nonce)?;
219 let csr_request = messages::im_invoke_request(
220 0,
221 CLUSTER_OPERATIONAL_CREDENTIALS,
222 CMD_OPERATIONAL_CSRREQUEST,
223 exchange_id,
224 &tlv.data,
225 false,
226 )?;
227 retrcrx.send(&csr_request).await?;
228
229 let csr_msg = retrcrx.get_next_message().await?;
230
231 let csr_tlve = csr_msg
232 .tlv
233 .get_octet_string(&[1, 0, 0, 1, 0])
234 .context("csr tlv missing")?;
235 let csr_t = tlv::decode_tlv(csr_tlve).context("csr tlv can't decode")?;
236 let csr = csr_t
237 .get_octet_string(&[1])
238 .context("csr tlv in tlv missing")?;
239 let csrd = x509_cert::request::CertReq::try_from(csr)?;
240 Ok(csrd)
241}
242
243async fn commissioning_complete(
244 connection: &dyn ConnectionTrait,
245 cm: &dyn certmanager::CertManager,
246 node_id: u64,
247 controller_id: u64,
248 fabric: &Fabric,
249) -> Result<session::Session> {
250 let (ses, _resumption) = auth_sigma(connection, fabric, cm, node_id, controller_id).await?;
252 let t1 = messages::im_invoke_request(
253 0,
254 CLUSTER_GENERAL_COMMISSIONING,
255 CMD_GENERAL_COMMISSIONING_COMMISSIONINGCOMPLETE,
256 30,
257 &[],
258 false,
259 )?;
260 let mut retrctx = retransmit::RetrContext::new(connection, &ses);
261
262 retrctx.send(&t1).await?;
263 let resp = retrctx.get_next_message().await?;
264 let comresp_status = {
265 resp.tlv
266 .get_int(&[1, 0, 0, 1, 0])
267 .context("can't get status from CommissioningCompleteResponse")?
268 };
269 if comresp_status != 0 {
270 return Err(anyhow::anyhow!(
271 "CommissioningComplete failed with status {}",
272 comresp_status
273 ));
274 }
275 Ok(ses)
276}
277
278pub(crate) async fn commission(
279 connection: &dyn ConnectionTrait,
280 session: &mut session::Session,
281 fabric: &Fabric,
282 cm: &dyn certmanager::CertManager,
283 node_id: u64,
284 controller_id: u64,
285) -> Result<session::Session> {
286 let mut retrctx = retransmit::RetrContext::new(connection, session);
288 let base: u16 = rand::random();
289
290 arm_failsafe(&mut retrctx, 60, base).await?;
291
292 let csrd = send_csr(&mut retrctx, base.wrapping_add(1)).await?;
293
294 push_ca_cert(&mut retrctx, cm, base.wrapping_add(2)).await?;
295
296 push_device_cert(&mut retrctx, cm, csrd, node_id, controller_id, fabric, base.wrapping_add(3)).await?;
297
298 let ses = commissioning_complete(connection, cm, node_id, controller_id, fabric).await?;
299
300 Ok(ses)
301}
302
303async fn arm_failsafe(
304 retrctx: &mut retransmit::RetrContext<'_>,
305 timeout_secs: u16,
306 exchange_id: u16,
307) -> Result<()> {
308 let mut tlv_buf = tlv::TlvBuffer::new();
309 tlv_buf.write_uint16(0, timeout_secs)?;
310 tlv_buf.write_uint64(1, 0)?; let req = messages::im_invoke_request(
312 0,
313 CLUSTER_GENERAL_COMMISSIONING,
314 CMD_GENERAL_COMMISSIONING_ARMFAILSAFE,
315 exchange_id,
316 &tlv_buf.data,
317 false,
318 )?;
319 retrctx.send(&req).await?;
320 let resp = retrctx.get_next_message().await?;
321 let status = resp.tlv.get_int(&[1, 0, 0, 1, 0]).context("ArmFailSafe: status missing")?;
322 if status != 0 {
323 return Err(anyhow::anyhow!("ArmFailSafe failed with status {}", status));
324 }
325 Ok(())
326}
327
328#[cfg(feature = "ble")]
354async fn add_or_update_wifi(
355 retrctx: &mut retransmit::RetrContext<'_>,
356 ssid: &[u8],
357 creds: &[u8],
358 exchange_id: u16,
359) -> Result<Vec<u8>> {
360 let mut tlv_buf = tlv::TlvBuffer::new();
361 tlv_buf.write_octetstring(0, ssid)?;
362 tlv_buf.write_octetstring(1, creds)?;
363 let req = messages::im_invoke_request(
364 0,
365 CLUSTER_NETWORK_COMMISSIONING,
366 CMD_NETWORK_ADD_OR_UPDATE_WIFI,
367 exchange_id,
368 &tlv_buf.data,
369 false,
370 )?;
371 retrctx.send(&req).await?;
372 let resp = retrctx.get_next_message().await?;
373 let status = resp.tlv.get_int(&[1, 0, 0, 1, 0]).context("AddOrUpdateWifiNetwork: status missing")?;
374 if status != 0 {
375 return Err(anyhow::anyhow!("AddOrUpdateWifiNetwork failed with status {}", status));
376 }
377 Ok(ssid.to_vec())
378}
379
380#[cfg(feature = "ble")]
381fn extract_thread_extended_pan_id(dataset: &[u8]) -> Option<&[u8]> {
384 let mut i = 0;
385 while i + 2 <= dataset.len() {
386 let tlv_type = dataset[i];
387 let tlv_len = dataset[i + 1] as usize;
388 if i + 2 + tlv_len > dataset.len() {
389 break;
390 }
391 if tlv_type == 0x02 && tlv_len == 8 {
392 return Some(&dataset[i + 2..i + 2 + tlv_len]);
393 }
394 i += 2 + tlv_len;
395 }
396 None
397}
398
399#[cfg(feature = "ble")]
400async fn add_or_update_thread(
401 retrctx: &mut retransmit::RetrContext<'_>,
402 dataset: &[u8],
403 exchange_id: u16,
404) -> Result<Vec<u8>> {
405 let mut tlv_buf = tlv::TlvBuffer::new();
406 tlv_buf.write_octetstring(0, dataset)?;
407 let req = messages::im_invoke_request(
408 0,
409 CLUSTER_NETWORK_COMMISSIONING,
410 CMD_NETWORK_ADD_OR_UPDATE_THREAD,
411 exchange_id,
412 &tlv_buf.data,
413 false,
414 )?;
415 retrctx.send(&req).await?;
416 let resp = retrctx.get_next_message().await?;
417 let status = resp.tlv.get_int(&[1, 0, 0, 1, 0]).context("AddOrUpdateThreadNetwork: status missing")?;
418 if status != 0 {
419 return Err(anyhow::anyhow!("AddOrUpdateThreadNetwork failed with status {}", status));
420 }
421 let ext_pan_id = extract_thread_extended_pan_id(dataset)
422 .ok_or_else(|| anyhow::anyhow!("Thread dataset missing Extended PAN ID (TLV type 0x02)"))?
423 .to_vec();
424 Ok(ext_pan_id)
425}
426
427#[cfg(feature = "ble")]
428async fn connect_network(
429 retrctx: &mut retransmit::RetrContext<'_>,
430 network_id: &[u8],
431 exchange_id: u16,
432) -> Result<()> {
433 let mut tlv_buf = tlv::TlvBuffer::new();
434 tlv_buf.write_octetstring(0, network_id)?;
435 tlv_buf.write_uint64(1, 0)?; let req = messages::im_invoke_request(
437 0,
438 CLUSTER_NETWORK_COMMISSIONING,
439 CMD_NETWORK_CONNECT,
440 exchange_id,
441 &tlv_buf.data,
442 false,
443 )?;
444 retrctx.send(&req).await?;
445 let resp = retrctx.get_next_message().await?;
446 let status = resp.tlv.get_int(&[1, 0, 0, 1, 0]).context("ConnectNetwork: status missing")?;
447 if status != 0 {
448 return Err(anyhow::anyhow!("ConnectNetwork failed with status {}", status));
449 }
450 Ok(())
451}
452
453#[cfg(feature = "ble")]
454pub(crate) async fn commission_ble_phase(
455 ble_connection: &dyn ConnectionTrait,
456 pase_session: &mut session::Session,
457 fabric: &Fabric,
458 cm: &dyn certmanager::CertManager,
459 node_id: u64,
460 controller_id: u64,
461 network_creds: &NetworkCreds,
462) -> Result<()> {
463 let mut retrctx = retransmit::RetrContext::new(ble_connection, pase_session);
464 let base: u16 = rand::random();
465 let e_arm = base;
466 let e_csr = base.wrapping_add(5);
470 let e_ca = base.wrapping_add(6);
471 let e_noc = base.wrapping_add(7);
472 let e_net1 = base.wrapping_add(8);
473 let e_net2 = base.wrapping_add(9);
474
475 arm_failsafe(&mut retrctx, 60, e_arm).await.context("ArmFailSafe")?;
476 log::debug!("Failsafe armed for 60 seconds");
477
478 let csrd = send_csr(&mut retrctx, e_csr).await?;
485 log::debug!("CSR received");
486 push_ca_cert(&mut retrctx, cm, e_ca).await?;
487 log::debug!("CA certificate pushed");
488 push_device_cert(&mut retrctx, cm, csrd, node_id, controller_id, fabric, e_noc).await?;
489 log::debug!("Device certificate pushed");
490
491 match network_creds {
492 NetworkCreds::WiFi { ssid, creds } => {
493 let net_id = add_or_update_wifi(&mut retrctx, ssid, creds, e_net1).await?;
494 connect_network(&mut retrctx, &net_id, e_net2).await?;
495 log::debug!("WiFi network connected");
496 }
499 NetworkCreds::Thread { dataset } => {
500 let net_id = add_or_update_thread(&mut retrctx, dataset, e_net1).await?;
501 connect_network(&mut retrctx, &net_id, e_net2).await?;
502 log::debug!("Thread network connected");
503 }
505 NetworkCreds::AlreadyOnNetwork => {}
506 }
507
508 Ok(())
509}
510
511#[cfg(feature = "ble")]
512pub(crate) async fn commissioning_complete_udp(
513 udp_connection: &dyn ConnectionTrait,
514 cm: &dyn certmanager::CertManager,
515 node_id: u64,
516 controller_id: u64,
517 fabric: &Fabric,
518) -> Result<session::Session> {
519 commissioning_complete(udp_connection, cm, node_id, controller_id, fabric).await
520}