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;
17#[cfg(feature = "ble")]
18const CMD_GENERAL_COMMISSIONING_ARMFAILSAFE: u32 = 0;
19const CMD_GENERAL_COMMISSIONING_COMMISSIONINGCOMPLETE: u32 = 4;
21
22#[cfg(feature = "ble")]
23const CLUSTER_NETWORK_COMMISSIONING: u32 = 0x31;
24#[cfg(feature = "ble")]
25const CMD_NETWORK_ADD_OR_UPDATE_WIFI: u32 = 2;
26#[cfg(feature = "ble")]
27const CMD_NETWORK_ADD_OR_UPDATE_THREAD: u32 = 3;
28#[cfg(feature = "ble")]
29const CMD_NETWORK_CONNECT: u32 = 6;
30
31#[derive(Clone)]
33pub enum NetworkCreds {
34 WiFi { ssid: Vec<u8>, creds: Vec<u8> },
36 Thread { dataset: Vec<u8> },
38 AlreadyOnNetwork,
41}
42
43
44async fn push_ca_cert(
102 retrcrx: &mut retransmit::RetrContext<'_>,
103 cm: &dyn certmanager::CertManager,
104 exchange_id: u16,
105) -> Result<()> {
106 let ca_pubkey = cm.get_ca_key()?.public_key().to_sec1_bytes();
107 let ca_cert = cm.get_ca_cert()?;
108 let mcert = cert_matter::convert_x509_bytes_to_matter(&ca_cert, &ca_pubkey)?;
109 log::debug!("AddTrustedRootCertificate: matter cert TLV ({} bytes): {}", mcert.len(),
110 mcert.iter().map(|b| format!("{:02x}", b)).collect::<Vec<_>>().join(""));
111 let mut tlv = tlv::TlvBuffer::new();
112 tlv.write_octetstring(0, &mcert)?;
113 let t1 = messages::im_invoke_request(
114 0,
115 CLUSTER_OPERATIONAL_CREDENTIALS,
116 CMD_OPERATIONAL_CREDENTIALS_ADDTRUSTEDROOTCERTIFICATE,
117 exchange_id,
118 &tlv.data,
119 false,
120 )?;
121 retrcrx.send(&t1).await?;
122
123 let resp = retrcrx.get_next_message().await?;
125 let noc_status = {
126 resp.tlv
127 .get_int(&[1, 0, 1, 1, 0])
128 .context("can't get status for AddTrustedRootCertificate")?
129 };
130 if noc_status != 0 {
131 return Err(anyhow::anyhow!(
132 "AddTrustedRootCertificate failed with status {}/{}",
133 noc_status,
134 noc_status_to_str(noc_status)
135 ));
136 }
137 Ok(())
138}
139
140fn noc_status_to_str(status: u64) -> &'static str {
141 match status {
142 0 => "Success",
143 1 => "InvalidPublicKey",
144 2 => "InvalidNodeOpId",
145 3 => "InvalidNOC",
146 4 => "MissingCsr",
147 5 => "TableFull",
148 6 => "InvalidAdminSubject",
149 7 => "?",
150 8 => "?",
151 9 => "FabricConflict",
152 10 => "LabelConflict",
153 11 => "InvalidFabricIndex",
154 _ => "UnknownStatus",
155 }
156}
157
158async fn push_device_cert(
159 retrcrx: &mut retransmit::RetrContext<'_>,
160 cm: &dyn certmanager::CertManager,
161 csrd: x509_cert::request::CertReq,
162 node_id: u64,
163 controller_id: u64,
164 fabric: &Fabric,
165 exchange_id: u16,
166) -> Result<()> {
167 let ca_id = fabric.ca_id;
168 let ca_pubkey = cm.get_ca_key()?.public_key().to_sec1_bytes();
169 let node_public_key = csrd
170 .info
171 .public_key
172 .subject_public_key
173 .as_bytes()
174 .context("can't extract pubkey from csr")?;
175 let ca_private = cm.get_ca_key()?;
176 let noc_x509 = cert_x509::encode_x509(
177 node_public_key,
178 node_id,
179 cm.get_fabric_id(),
180 ca_id,
181 &ca_private,
182 false,
183 )?;
184 let noc = cert_matter::convert_x509_bytes_to_matter(&noc_x509, &ca_pubkey)?;
185 let mut tlv = tlv::TlvBuffer::new();
186 tlv.write_octetstring(0, &noc)?;
187 tlv.write_octetstring(2, &fabric.ipk_epoch_key)?;
188 tlv.write_uint64(3, controller_id)?;
189 tlv.write_uint64(4, 101)?;
190 let t1 = messages::im_invoke_request(
191 0,
192 CLUSTER_OPERATIONAL_CREDENTIALS,
193 CMD_OPERATIONAL_CREDENTIALS_ADDNOC,
194 exchange_id,
195 &tlv.data,
196 false,
197 )?;
198 retrcrx.send(&t1).await?;
199
200 let resp = retrcrx.get_next_message().await?;
201 let noc_status = {
202 resp.tlv
203 .get_int(&[1, 0, 0, 1, 0])
204 .context("can't get status for AddNOC")?
205 };
206 if noc_status != 0 {
207 return Err(anyhow::anyhow!("AddNOC failed with status {}/{}", noc_status, noc_status_to_str(noc_status)));
208 }
209 Ok(())
210}
211
212async fn send_csr(
213 retrcrx: &mut retransmit::RetrContext<'_>,
214 exchange_id: u16,
215) -> Result<x509_cert::request::CertReq> {
216 let mut tlv = tlv::TlvBuffer::new();
217 let mut random_csr_nonce = vec![0; 32];
218 rand::thread_rng().fill_bytes(&mut random_csr_nonce);
219 tlv.write_octetstring(0, &random_csr_nonce)?;
220 let csr_request = messages::im_invoke_request(
221 0,
222 CLUSTER_OPERATIONAL_CREDENTIALS,
223 CMD_OPERATIONAL_CSRREQUEST,
224 exchange_id,
225 &tlv.data,
226 false,
227 )?;
228 retrcrx.send(&csr_request).await?;
229
230 let csr_msg = retrcrx.get_next_message().await?;
231
232 let csr_tlve = csr_msg
233 .tlv
234 .get_octet_string(&[1, 0, 0, 1, 0])
235 .context("csr tlv missing")?;
236 let csr_t = tlv::decode_tlv(csr_tlve).context("csr tlv can't decode")?;
237 let csr = csr_t
238 .get_octet_string(&[1])
239 .context("csr tlv in tlv missing")?;
240 let csrd = x509_cert::request::CertReq::try_from(csr)?;
241 Ok(csrd)
242}
243
244async fn commissioning_complete(
245 connection: &dyn ConnectionTrait,
246 cm: &dyn certmanager::CertManager,
247 node_id: u64,
248 controller_id: u64,
249 fabric: &Fabric,
250) -> Result<session::Session> {
251 let ses = 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 let csrd = send_csr(&mut retrctx, base).await?;
291
292 push_ca_cert(&mut retrctx, cm, base.wrapping_add(1)).await?;
293
294 push_device_cert(&mut retrctx, cm, csrd, node_id, controller_id, fabric, base.wrapping_add(2)).await?;
295
296 let ses = commissioning_complete(connection, cm, node_id, controller_id, fabric).await?;
297
298 Ok(ses)
299}
300
301#[cfg(feature = "ble")]
302async fn arm_failsafe(
303 retrctx: &mut retransmit::RetrContext<'_>,
304 timeout_secs: u16,
305 exchange_id: u16,
306) -> Result<()> {
307 let mut tlv_buf = tlv::TlvBuffer::new();
308 tlv_buf.write_uint16(0, timeout_secs)?;
309 tlv_buf.write_uint64(1, 0)?; let req = messages::im_invoke_request(
311 0,
312 CLUSTER_GENERAL_COMMISSIONING,
313 CMD_GENERAL_COMMISSIONING_ARMFAILSAFE,
314 exchange_id,
315 &tlv_buf.data,
316 false,
317 )?;
318 retrctx.send(&req).await?;
319 let resp = retrctx.get_next_message().await?;
320 let status = resp.tlv.get_int(&[1, 0, 0, 1, 0]).context("ArmFailSafe: status missing")?;
321 if status != 0 {
322 return Err(anyhow::anyhow!("ArmFailSafe failed with status {}", status));
323 }
324 Ok(())
325}
326
327#[cfg(feature = "ble")]
353async fn add_or_update_wifi(
354 retrctx: &mut retransmit::RetrContext<'_>,
355 ssid: &[u8],
356 creds: &[u8],
357 exchange_id: u16,
358) -> Result<Vec<u8>> {
359 let mut tlv_buf = tlv::TlvBuffer::new();
360 tlv_buf.write_octetstring(0, ssid)?;
361 tlv_buf.write_octetstring(1, creds)?;
362 let req = messages::im_invoke_request(
363 0,
364 CLUSTER_NETWORK_COMMISSIONING,
365 CMD_NETWORK_ADD_OR_UPDATE_WIFI,
366 exchange_id,
367 &tlv_buf.data,
368 false,
369 )?;
370 retrctx.send(&req).await?;
371 let resp = retrctx.get_next_message().await?;
372 let status = resp.tlv.get_int(&[1, 0, 0, 1, 0]).context("AddOrUpdateWifiNetwork: status missing")?;
373 if status != 0 {
374 return Err(anyhow::anyhow!("AddOrUpdateWifiNetwork failed with status {}", status));
375 }
376 Ok(ssid.to_vec())
377}
378
379#[cfg(feature = "ble")]
380async fn add_or_update_thread(
381 retrctx: &mut retransmit::RetrContext<'_>,
382 dataset: &[u8],
383 exchange_id: u16,
384) -> Result<Vec<u8>> {
385 let mut tlv_buf = tlv::TlvBuffer::new();
386 tlv_buf.write_octetstring(0, dataset)?;
387 let req = messages::im_invoke_request(
388 0,
389 CLUSTER_NETWORK_COMMISSIONING,
390 CMD_NETWORK_ADD_OR_UPDATE_THREAD,
391 exchange_id,
392 &tlv_buf.data,
393 false,
394 )?;
395 retrctx.send(&req).await?;
396 let resp = retrctx.get_next_message().await?;
397 let status = resp.tlv.get_int(&[1, 0, 0, 1, 0]).context("AddOrUpdateThreadNetwork: status missing")?;
398 if status != 0 {
399 return Err(anyhow::anyhow!("AddOrUpdateThreadNetwork failed with status {}", status));
400 }
401 Ok(dataset[..dataset.len().min(8)].to_vec())
403}
404
405#[cfg(feature = "ble")]
406async fn connect_network(
407 retrctx: &mut retransmit::RetrContext<'_>,
408 network_id: &[u8],
409 exchange_id: u16,
410) -> Result<()> {
411 let mut tlv_buf = tlv::TlvBuffer::new();
412 tlv_buf.write_octetstring(0, network_id)?;
413 tlv_buf.write_uint64(1, 0)?; let req = messages::im_invoke_request(
415 0,
416 CLUSTER_NETWORK_COMMISSIONING,
417 CMD_NETWORK_CONNECT,
418 exchange_id,
419 &tlv_buf.data,
420 false,
421 )?;
422 retrctx.send(&req).await?;
423 let resp = retrctx.get_next_message().await?;
424 let status = resp.tlv.get_int(&[1, 0, 0, 1, 0]).context("ConnectNetwork: status missing")?;
425 if status != 0 {
426 return Err(anyhow::anyhow!("ConnectNetwork failed with status {}", status));
427 }
428 Ok(())
429}
430
431#[cfg(feature = "ble")]
432pub(crate) async fn commission_ble_phase(
433 ble_connection: &dyn ConnectionTrait,
434 pase_session: &mut session::Session,
435 fabric: &Fabric,
436 cm: &dyn certmanager::CertManager,
437 node_id: u64,
438 controller_id: u64,
439 network_creds: &NetworkCreds,
440) -> Result<()> {
441 let mut retrctx = retransmit::RetrContext::new(ble_connection, pase_session);
442 let base: u16 = rand::random();
443 let e_arm = base;
444 let e_csr = base.wrapping_add(5);
448 let e_ca = base.wrapping_add(6);
449 let e_noc = base.wrapping_add(7);
450 let e_net1 = base.wrapping_add(8);
451 let e_net2 = base.wrapping_add(9);
452
453 arm_failsafe(&mut retrctx, 60, e_arm).await.context("ArmFailSafe")?;
454 log::debug!("Failsafe armed for 60 seconds");
455
456 let csrd = send_csr(&mut retrctx, e_csr).await?;
463 log::debug!("CSR received");
464 push_ca_cert(&mut retrctx, cm, e_ca).await?;
465 log::debug!("CA certificate pushed");
466 push_device_cert(&mut retrctx, cm, csrd, node_id, controller_id, fabric, e_noc).await?;
467 log::debug!("Device certificate pushed");
468
469 match network_creds {
470 NetworkCreds::WiFi { ssid, creds } => {
471 let net_id = add_or_update_wifi(&mut retrctx, ssid, creds, e_net1).await?;
472 connect_network(&mut retrctx, &net_id, e_net2).await?;
473 log::debug!("WiFi network connected");
474 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
476 }
477 NetworkCreds::Thread { dataset } => {
478 let net_id = add_or_update_thread(&mut retrctx, dataset, e_net1).await?;
479 connect_network(&mut retrctx, &net_id, e_net2).await?;
480 log::debug!("Thread network connected");
481 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
482 }
483 NetworkCreds::AlreadyOnNetwork => {}
484 }
485
486 Ok(())
487}
488
489#[cfg(feature = "ble")]
490pub(crate) async fn commissioning_complete_udp(
491 udp_connection: &dyn ConnectionTrait,
492 cm: &dyn certmanager::CertManager,
493 node_id: u64,
494 controller_id: u64,
495 fabric: &Fabric,
496) -> Result<session::Session> {
497 commissioning_complete(udp_connection, cm, node_id, controller_id, fabric).await
498}