1#![forbid(unsafe_code)]
4
5use std::sync::Arc;
6
7use matter_cert::time::MatterTime;
8
9use crate::attestation::PaaTrustStore;
10use crate::noc::{FabricRecord, NocRng};
11use crate::setup::SetupPayload;
12use crate::state_machine::action::{Action, Expectation};
13use crate::state_machine::error::CommissioningError;
14use crate::state_machine::stage::Stage;
15
16#[cfg(feature = "tracing")]
17use tracing::instrument;
18
19pub(crate) const DEFAULT_CONNECT_MAX_TIME_SECONDS: u16 = 90;
26
27#[derive(Clone, PartialEq, Eq)]
37pub struct WiFiCredentials {
38 pub ssid: Vec<u8>,
40 pub credentials: Vec<u8>,
43}
44
45impl core::fmt::Debug for WiFiCredentials {
46 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
47 f.debug_struct("WiFiCredentials")
48 .field("ssid", &format_args!("<{} bytes>", self.ssid.len()))
49 .field(
50 "credentials",
51 &format_args!("<redacted, {} bytes>", self.credentials.len()),
52 )
53 .finish()
54 }
55}
56
57#[derive(Clone, Debug, PartialEq, Eq)]
75pub enum NetworkCredentials {
76 WiFi(WiFiCredentials),
78 Thread(crate::thread_dataset::ThreadDataset),
80 AlreadyOnNetwork,
82}
83
84pub struct CommissionerConfig<'a> {
96 pub pase_attestation_challenge: [u8; 16],
101 pub fabric: &'a FabricRecord,
104 pub setup_payload: &'a SetupPayload,
108 pub paa_trust_store: &'a PaaTrustStore,
110 pub cd_signing_roots: &'a crate::attestation::CdSigningRoots,
114 pub commissioner_node_id: u64,
117 pub assigned_node_id: u64,
121 pub ipk_epoch_key: [u8; 16],
125 pub case_admin_subject: u64,
128 pub admin_vendor_id: u16,
130 pub now: MatterTime,
134 pub rng: Arc<dyn NocRng>,
136 pub network: NetworkCredentials,
147}
148
149#[allow(clippy::struct_field_names)]
158pub struct Commissioner {
159 stage: Stage,
160
161 #[allow(dead_code)] pase_attestation_challenge: [u8; 16],
168 #[allow(dead_code)] fabric: FabricRecord,
170 #[allow(dead_code)] paa_trust_store: PaaTrustStore,
172 cd_signing_roots: crate::attestation::CdSigningRoots,
173 #[allow(dead_code)] setup_payload: SetupPayload,
175 #[allow(dead_code)] commissioner_node_id: u64,
177 #[allow(dead_code)] assigned_node_id: u64,
179 #[allow(dead_code)] ipk_epoch_key: [u8; 16],
181 #[allow(dead_code)] case_admin_subject: u64,
183 #[allow(dead_code)] admin_vendor_id: u16,
185 #[allow(dead_code)] now: MatterTime,
187 #[allow(dead_code)] rng: Arc<dyn NocRng>,
189
190 pai_der: Option<Vec<u8>>,
194 dac_der: Option<Vec<u8>>,
195 attestation_nonce: Option<[u8; 32]>,
196 attestation_response: Option<crate::attestation::AttestationResponse>,
197
198 csr_nonce: Option<[u8; 32]>,
202 csr_response: Option<crate::noc::CsrResponse>,
203 verified_csr: Option<crate::noc::VerifiedCsr>,
204 issued_noc: Option<matter_cert::MatterCertificate>,
205 issued_noc_public_key: Option<[u8; 65]>,
206
207 network: NetworkCredentials,
213
214 failsafe_expiry_seconds: u16,
220
221 connect_max_time_seconds: u16,
230
231 breadcrumb_counter: u64,
237
238 awaiting_case_session: bool,
243
244 awaiting: Option<Expectation>,
248
249 pending_action: Option<Action>,
252
253 last_failure: Option<String>,
258}
259
260impl Commissioner {
261 pub fn new(cfg: CommissionerConfig<'_>) -> Result<Self, CommissioningError> {
270 if cfg.commissioner_node_id == 0 {
271 return Err(CommissioningError::InvalidConfig(
272 "commissioner_node_id must be non-zero",
273 ));
274 }
275 if cfg.assigned_node_id == 0 {
276 return Err(CommissioningError::InvalidConfig(
277 "assigned_node_id must be non-zero",
278 ));
279 }
280 if cfg.assigned_node_id == cfg.commissioner_node_id {
281 return Err(CommissioningError::InvalidConfig(
282 "assigned_node_id must differ from commissioner_node_id",
283 ));
284 }
285 if cfg.ipk_epoch_key == [0u8; 16] {
286 return Err(CommissioningError::InvalidConfig(
287 "ipk_epoch_key must not be all-zero",
288 ));
289 }
290 if let NetworkCredentials::WiFi(creds) = &cfg.network {
294 if creds.ssid.is_empty() {
295 return Err(CommissioningError::InvalidConfig(
296 "network: Wi-Fi ssid must not be empty",
297 ));
298 }
299 if creds.ssid.len() > 32 {
300 return Err(CommissioningError::InvalidConfig(
301 "network: Wi-Fi ssid must be ≤32 bytes",
302 ));
303 }
304 if creds.credentials.len() > 64 {
305 return Err(CommissioningError::InvalidConfig(
306 "network: Wi-Fi credentials must be ≤64 bytes",
307 ));
308 }
309 }
310 Ok(Self {
311 stage: Stage::SecurePairing,
312 pase_attestation_challenge: cfg.pase_attestation_challenge,
313 fabric: cfg.fabric.clone(),
314 paa_trust_store: cfg.paa_trust_store.clone(),
315 cd_signing_roots: cfg.cd_signing_roots.clone(),
316 setup_payload: cfg.setup_payload.clone(),
317 commissioner_node_id: cfg.commissioner_node_id,
318 assigned_node_id: cfg.assigned_node_id,
319 ipk_epoch_key: cfg.ipk_epoch_key,
320 case_admin_subject: cfg.case_admin_subject,
321 admin_vendor_id: cfg.admin_vendor_id,
322 now: cfg.now,
323 rng: cfg.rng,
324 pai_der: None,
325 dac_der: None,
326 attestation_nonce: None,
327 attestation_response: None,
328 csr_nonce: None,
329 csr_response: None,
330 verified_csr: None,
331 issued_noc: None,
332 issued_noc_public_key: None,
333 network: cfg.network,
334 failsafe_expiry_seconds: 60,
335 connect_max_time_seconds: 0,
336 breadcrumb_counter: 1,
337 awaiting_case_session: false,
338 awaiting: None,
339 pending_action: None,
340 last_failure: None,
341 })
342 }
343
344 #[must_use]
346 pub fn stage(&self) -> Stage {
347 self.stage
348 }
349
350 #[allow(dead_code)] pub(crate) fn network(&self) -> &NetworkCredentials {
355 &self.network
356 }
357
358 #[cfg(feature = "__test_shortcuts")]
369 #[must_use]
370 pub fn position_at_stage_for_test(mut self, stage: Stage, seeds: TestStateSeeds) -> Self {
371 self.stage = stage;
372 if let Some(pk) = seeds.synthetic_noc_pubkey {
373 self.issued_noc_public_key = Some(pk);
374 }
375 self
376 }
377
378 #[cfg_attr(feature = "tracing", instrument(skip(self), fields(stage = ?self.stage)))]
391 pub fn poll(&mut self) -> Result<Action, CommissioningError> {
392 if let Some(act) = self.pending_action.clone() {
393 return Ok(act);
394 }
395 let action = self.dispatch_stage()?;
396 self.pending_action = Some(action.clone());
397 Ok(action)
398 }
399
400 fn network_enable_failsafe_seconds(&self) -> u16 {
407 if self.connect_max_time_seconds > 0 {
408 self.connect_max_time_seconds
409 } else {
410 DEFAULT_CONNECT_MAX_TIME_SECONDS
411 }
412 }
413
414 #[cfg(feature = "driver")]
427 pub(crate) fn set_connect_max_time_seconds(&mut self, seconds: u16) {
428 self.connect_max_time_seconds = seconds;
429 }
430
431 #[cfg(feature = "driver")]
443 #[must_use]
444 pub(crate) fn connect_max_time_seconds(&self) -> u16 {
445 self.connect_max_time_seconds
446 }
447
448 fn arm_failsafe_action(&mut self) -> Action {
456 use crate::clusters::general_commissioning as gc;
457 use crate::state_machine::action::SessionContext;
458 let expiry_seconds = if self.stage == Stage::FailsafeBeforeNetworkEnable {
459 self.network_enable_failsafe_seconds()
460 } else {
461 self.failsafe_expiry_seconds
462 };
463 let breadcrumb = self.next_breadcrumb();
464 let payload = gc::encode_arm_fail_safe(expiry_seconds, breadcrumb);
465 self.awaiting = Some(Expectation::ArmFailsafeResponse);
466 Action::Invoke {
467 session: SessionContext::Pase,
468 endpoint: 0,
469 cluster: gc::CLUSTER_ID,
470 command: gc::command_id::ARM_FAIL_SAFE,
471 payload,
472 expect: Expectation::ArmFailsafeResponse,
473 }
474 }
475
476 #[allow(clippy::too_many_lines)]
488 fn dispatch_stage(&mut self) -> Result<Action, CommissioningError> {
489 use crate::clusters::general_commissioning as gc;
490 use crate::state_machine::action::SessionContext;
491 match self.stage {
492 Stage::SecurePairing => {
493 self.stage = Stage::ReadCommissioningInfo;
495 self.dispatch_stage()
496 }
497 Stage::ReadCommissioningInfo => {
498 self.awaiting = Some(Expectation::CommissioningInfo);
499 Ok(Action::ReadAttribute {
500 session: SessionContext::Pase,
501 endpoint: 0,
502 cluster: gc::CLUSTER_ID,
503 attributes: &[
504 0x0000, 0x0001, 0x0002, 0x0004, ],
511 expect: Expectation::CommissioningInfo,
512 })
513 }
514 Stage::ArmFailsafe | Stage::FailsafeBeforeNetworkEnable => {
515 Ok(self.arm_failsafe_action())
516 }
517 Stage::ConfigRegulatory => {
518 let breadcrumb = self.next_breadcrumb();
519 let payload = gc::encode_set_regulatory_config(
526 gc::RegulatoryLocation::Indoor,
527 "XX",
528 breadcrumb,
529 );
530 self.awaiting = Some(Expectation::SetRegulatoryConfigResponse);
531 Ok(Action::Invoke {
532 session: SessionContext::Pase,
533 endpoint: 0,
534 cluster: gc::CLUSTER_ID,
535 command: gc::command_id::SET_REGULATORY_CONFIG,
536 payload,
537 expect: Expectation::SetRegulatoryConfigResponse,
538 })
539 }
540 Stage::SendPaiCertRequest => {
541 use crate::noc::{encode_certificate_chain_request, CertChainType};
542 let payload = encode_certificate_chain_request(CertChainType::Pai);
543 self.awaiting = Some(Expectation::PaiCertChainResponse);
544 Ok(Action::Invoke {
545 session: SessionContext::Pase,
546 endpoint: 0,
547 cluster: 0x003E,
548 command: 0x02,
549 payload,
550 expect: Expectation::PaiCertChainResponse,
551 })
552 }
553 Stage::SendDacCertRequest => {
554 use crate::noc::{encode_certificate_chain_request, CertChainType};
555 let payload = encode_certificate_chain_request(CertChainType::Dac);
556 self.awaiting = Some(Expectation::DacCertChainResponse);
557 Ok(Action::Invoke {
558 session: SessionContext::Pase,
559 endpoint: 0,
560 cluster: 0x003E,
561 command: 0x02,
562 payload,
563 expect: Expectation::DacCertChainResponse,
564 })
565 }
566 Stage::SendAttestationRequest => {
567 use crate::noc::encode_attestation_request;
568 let mut nonce = [0u8; 32];
569 self.rng
570 .fill(&mut nonce)
571 .map_err(CommissioningError::from)?;
572 let payload = encode_attestation_request(&nonce);
573 self.attestation_nonce = Some(nonce);
574 self.awaiting = Some(Expectation::AttestationResponse);
575 Ok(Action::Invoke {
576 session: SessionContext::Pase,
577 endpoint: 0,
578 cluster: 0x003E,
579 command: 0x00,
580 payload,
581 expect: Expectation::AttestationResponse,
582 })
583 }
584 Stage::AttestationVerification => {
585 match self.run_attestation_verification() {
586 Ok(()) => {
587 self.advance(Stage::SendOpCertSigningRequest);
588 self.dispatch_stage()
589 }
590 Err(err) => {
591 self.last_failure = Some(err.to_string());
595 self.stage = Stage::Failed;
596 self.awaiting = None;
597 self.pending_action = None;
598 Err(err)
599 }
600 }
601 }
602 Stage::SendOpCertSigningRequest => {
603 use crate::noc::encode_csr_request;
604 let mut nonce = [0u8; 32];
605 self.rng
606 .fill(&mut nonce)
607 .map_err(CommissioningError::from)?;
608 let payload = encode_csr_request(&nonce, false);
611 self.csr_nonce = Some(nonce);
612 self.awaiting = Some(Expectation::CsrResponse);
613 Ok(Action::Invoke {
614 session: SessionContext::Pase,
615 endpoint: 0,
616 cluster: 0x003E,
617 command: 0x04,
618 payload,
619 expect: Expectation::CsrResponse,
620 })
621 }
622 Stage::ValidateCsr => {
623 self.run_validate_csr()?;
625 self.advance(Stage::GenerateNocChain);
626 self.dispatch_stage()
627 }
628 Stage::GenerateNocChain => {
629 self.run_generate_noc_chain()?;
631 self.advance(Stage::SendTrustedRootCert);
632 self.dispatch_stage()
633 }
634 Stage::SendTrustedRootCert => {
635 use crate::noc::encode_add_trusted_root;
636 let rcac_tlv =
643 self.fabric.root_cert.to_tlv().map_err(|e| {
644 CommissioningError::from(crate::noc::NocError::CertBuild(e))
645 })?;
646 #[cfg(feature = "tracing")]
647 tracing::debug!(
648 rcac_tlv = %crate::hexdump::hex(&rcac_tlv),
649 "sending AddTrustedRootCertificate"
650 );
651 let payload = encode_add_trusted_root(&rcac_tlv);
652 self.awaiting = Some(Expectation::AddTrustedRootResponse);
653 Ok(Action::Invoke {
654 session: SessionContext::Pase,
655 endpoint: 0,
656 cluster: 0x003E,
657 command: 0x0B,
658 payload,
659 expect: Expectation::AddTrustedRootResponse,
660 })
661 }
662 Stage::SendNoc => {
663 use crate::noc::encode_add_noc;
664 let noc = self
665 .issued_noc
666 .as_ref()
667 .ok_or(CommissioningError::OutOfOrderResponse(self.stage))?;
668 let noc_tlv = noc
669 .to_tlv()
670 .map_err(|e| CommissioningError::from(crate::noc::NocError::CertBuild(e)))?;
671 let icac_tlv = match self.fabric.icac_cert.as_ref() {
681 Some(icac) => Some(icac.to_tlv().map_err(|e| {
682 CommissioningError::from(crate::noc::NocError::CertBuild(e))
683 })?),
684 None => None,
685 };
686 let payload = encode_add_noc(
687 &noc_tlv,
688 icac_tlv.as_deref(),
689 &self.ipk_epoch_key,
690 self.case_admin_subject,
691 self.admin_vendor_id,
692 );
693 self.awaiting = Some(Expectation::NocResponse);
694 Ok(Action::Invoke {
695 session: SessionContext::Pase,
696 endpoint: 0,
697 cluster: 0x003E,
698 command: 0x06,
699 payload,
700 expect: Expectation::NocResponse,
701 })
702 }
703 Stage::ReadNetworkCommissioningInfo => {
704 self.awaiting = Some(Expectation::NetworkCommissioningInfo);
705 Ok(Action::ReadAttribute {
706 session: SessionContext::Pase,
707 endpoint: 0,
708 cluster: crate::clusters::network_commissioning::CLUSTER_ID,
709 attributes: &[
710 crate::clusters::network_commissioning::attribute_id::FEATURE_MAP,
711 crate::clusters::network_commissioning::attribute_id::CONNECT_MAX_TIME_SECONDS,
715 ],
716 expect: Expectation::NetworkCommissioningInfo,
717 })
718 }
719 Stage::NetworkSetup => {
720 use crate::clusters::network_commissioning as nc;
721 let breadcrumb = self.next_breadcrumb();
729 let (command, payload) = match &self.network {
730 NetworkCredentials::WiFi(creds) => (
731 nc::command_id::ADD_OR_UPDATE_WIFI_NETWORK,
732 nc::encode_add_or_update_wifi_network(
733 &creds.ssid,
734 &creds.credentials,
735 breadcrumb,
736 ),
737 ),
738 NetworkCredentials::Thread(dataset) => (
739 nc::command_id::ADD_OR_UPDATE_THREAD_NETWORK,
740 nc::encode_add_or_update_thread_network(dataset.as_bytes(), breadcrumb),
741 ),
742 NetworkCredentials::AlreadyOnNetwork => {
743 return Err(CommissioningError::OutOfOrderResponse(self.stage));
744 }
745 };
746 self.awaiting = Some(Expectation::NetworkConfigResponse);
747 Ok(Action::Invoke {
748 session: SessionContext::Pase,
749 endpoint: 0,
750 cluster: nc::CLUSTER_ID,
751 command,
752 payload,
753 expect: Expectation::NetworkConfigResponse,
754 })
755 }
756 Stage::NetworkEnable => {
757 use crate::clusters::network_commissioning as nc;
758 let breadcrumb = self.next_breadcrumb();
761 let payload = match &self.network {
762 NetworkCredentials::WiFi(creds) => {
763 nc::encode_connect_network(&creds.ssid, breadcrumb)
764 }
765 NetworkCredentials::Thread(dataset) => {
766 nc::encode_connect_network(&dataset.ext_pan_id(), breadcrumb)
767 }
768 NetworkCredentials::AlreadyOnNetwork => {
769 return Err(CommissioningError::OutOfOrderResponse(self.stage));
770 }
771 };
772 self.awaiting = Some(Expectation::ConnectNetworkResponse);
773 Ok(Action::Invoke {
774 session: SessionContext::Pase,
775 endpoint: 0,
776 cluster: nc::CLUSTER_ID,
777 command: nc::command_id::CONNECT_NETWORK,
778 payload,
779 expect: Expectation::ConnectNetworkResponse,
780 })
781 }
782 Stage::EvictPreviousCaseSessions => {
783 self.advance(Stage::FindOperationalForComplete);
787 self.dispatch_stage()
788 }
789 Stage::FindOperationalForComplete => {
790 self.awaiting_case_session = true;
791 Ok(Action::EstablishCase {
792 fabric_id: self.fabric.fabric_id,
793 peer_node_id: self.assigned_node_id,
794 })
795 }
796 Stage::SendComplete => {
797 let payload = gc::encode_commissioning_complete();
798 self.awaiting = Some(Expectation::CommissioningCompleteResponse);
799 Ok(Action::Invoke {
800 session: SessionContext::Case,
801 endpoint: 0,
802 cluster: gc::CLUSTER_ID,
803 command: gc::command_id::COMMISSIONING_COMPLETE,
804 payload,
805 expect: Expectation::CommissioningCompleteResponse,
806 })
807 }
808 Stage::Cleanup => {
809 let public_key = self
810 .issued_noc_public_key
811 .ok_or(CommissioningError::OutOfOrderResponse(self.stage))?;
812 Ok(Action::Done(crate::state_machine::CommissionedFabric {
813 fabric: self.fabric.clone(),
814 peer_node_id: self.assigned_node_id,
815 peer_root_public_key: public_key,
816 terminated_at: Stage::Cleanup,
817 }))
818 }
819 Stage::Failed => {
820 self.awaiting = None;
823 let reason = self
824 .last_failure
825 .clone()
826 .unwrap_or_else(|| "commissioning aborted".to_string());
827 Ok(Action::Abort {
828 send_disarm_failsafe: true,
829 reason,
830 })
831 } }
835 }
836
837 #[cfg_attr(feature = "tracing", instrument(skip(self, payload), fields(stage = ?self.stage, expectation = ?expect)))]
859 pub fn on_response(
860 &mut self,
861 expect: Expectation,
862 payload: &[u8],
863 ) -> Result<(), CommissioningError> {
864 if expect == Expectation::CaseFailed {
865 if !self.awaiting_case_session {
870 return Err(CommissioningError::OutOfOrderResponse(self.stage));
871 }
872 self.awaiting_case_session = false;
873 self.stage = Stage::Failed;
874 self.awaiting = None;
875 self.pending_action = None;
876 self.last_failure = Some(CommissioningError::CaseEstablishmentFailed.to_string());
877 return Err(CommissioningError::CaseEstablishmentFailed);
878 }
879 let Some(awaiting) = self.awaiting else {
880 return Err(CommissioningError::OutOfOrderResponse(self.stage));
881 };
882 if awaiting != expect {
883 return Err(CommissioningError::UnexpectedResponseKind {
884 expected: awaiting,
885 got: expect,
886 });
887 }
888 match self.handle_response(expect, payload) {
889 Ok(()) => Ok(()),
890 Err(err) => {
891 self.last_failure = Some(err.to_string());
892 self.stage = Stage::Failed;
893 self.awaiting = None;
894 self.pending_action = None;
895 Err(err)
896 }
897 }
898 }
899
900 #[cfg_attr(feature = "tracing", instrument(skip(self)))]
912 pub fn on_case_established(&mut self) -> Result<(), CommissioningError> {
913 if !self.awaiting_case_session {
914 return Err(CommissioningError::OutOfOrderResponse(self.stage));
915 }
916 self.awaiting_case_session = false;
917 self.advance(Stage::SendComplete);
918 Ok(())
919 }
920
921 #[allow(clippy::too_many_lines)]
922 fn handle_response(
923 &mut self,
924 expect: Expectation,
925 payload: &[u8],
926 ) -> Result<(), CommissioningError> {
927 use crate::clusters::general_commissioning as gc;
928 match expect {
929 Expectation::CommissioningInfo => {
930 Self::assert_tlv_well_formed(self.stage, payload)?;
931 if let Some(info) = gc::decode_basic_commissioning_info(payload) {
935 if info.failsafe_expiry_length_seconds > 0 {
936 self.failsafe_expiry_seconds = info.failsafe_expiry_length_seconds;
937 }
938 if info.max_cumulative_failsafe_seconds > 0 {
943 self.failsafe_expiry_seconds = self
944 .failsafe_expiry_seconds
945 .min(info.max_cumulative_failsafe_seconds);
946 }
947 }
948 self.advance(Stage::ArmFailsafe);
949 Ok(())
950 }
951 Expectation::ArmFailsafeResponse => {
952 let resp = gc::decode_arm_fail_safe_response(payload)?;
953 if resp.error_code != 0 {
954 return Err(CommissioningError::DeviceImStatus {
955 stage: self.stage,
956 im_status: u16::from(resp.error_code),
957 });
958 }
959 let next = match self.stage {
960 Stage::ArmFailsafe => Stage::ConfigRegulatory,
961 Stage::FailsafeBeforeNetworkEnable => Stage::NetworkEnable,
962 other => {
963 return Err(CommissioningError::OutOfOrderResponse(other));
964 }
965 };
966 self.advance(next);
967 Ok(())
968 }
969 Expectation::SetRegulatoryConfigResponse => {
970 let resp = gc::decode_set_regulatory_config_response(payload)?;
971 if resp.error_code != 0 {
972 return Err(CommissioningError::DeviceImStatus {
973 stage: Stage::ConfigRegulatory,
974 im_status: u16::from(resp.error_code),
975 });
976 }
977 self.advance(Stage::SendPaiCertRequest);
978 Ok(())
979 }
980 Expectation::PaiCertChainResponse => {
981 let resp = crate::noc::decode_certificate_chain_response(payload)?;
982 self.pai_der = Some(resp.certificate);
983 self.advance(Stage::SendDacCertRequest);
984 Ok(())
985 }
986 Expectation::DacCertChainResponse => {
987 let resp = crate::noc::decode_certificate_chain_response(payload)?;
988 self.dac_der = Some(resp.certificate);
989 self.advance(Stage::SendAttestationRequest);
990 Ok(())
991 }
992 Expectation::AttestationResponse => {
993 let resp = crate::noc::decode_attestation_response(payload)?;
994 self.attestation_response = Some(resp);
995 self.advance(Stage::AttestationVerification);
996 Ok(())
997 }
998 Expectation::CsrResponse => {
999 let resp = crate::noc::decode_csr_response(payload)?;
1000 self.csr_response = Some(resp);
1001 self.advance(Stage::ValidateCsr);
1002 Ok(())
1003 }
1004 Expectation::AddTrustedRootResponse => {
1005 if payload.first() != Some(&0u8) {
1010 return Err(CommissioningError::DeviceImStatus {
1011 stage: Stage::SendTrustedRootCert,
1012 im_status: u16::from(payload.first().copied().unwrap_or(0xFF)),
1013 });
1014 }
1015 self.advance(Stage::SendNoc);
1016 Ok(())
1017 }
1018 Expectation::NocResponse => {
1019 let resp = crate::noc::decode_noc_response(payload)?;
1020 if resp.status != 0 {
1021 return Err(CommissioningError::DeviceImStatus {
1022 stage: Stage::SendNoc,
1023 im_status: u16::from(resp.status),
1024 });
1025 }
1026 self.advance(Stage::ReadNetworkCommissioningInfo);
1027 Ok(())
1028 }
1029 Expectation::NetworkCommissioningInfo => {
1030 use crate::clusters::network_commissioning as nc;
1031 use crate::state_machine::NetworkKind;
1032 if payload.is_empty() {
1043 match &self.network {
1044 NetworkCredentials::AlreadyOnNetwork => {
1045 self.advance(Stage::EvictPreviousCaseSessions);
1046 return Ok(());
1047 }
1048 NetworkCredentials::WiFi(_) => {
1049 return Err(CommissioningError::NetworkFeatureUnsupported {
1050 needed: NetworkKind::WiFi,
1051 });
1052 }
1053 NetworkCredentials::Thread(_) => {
1054 return Err(CommissioningError::NetworkFeatureUnsupported {
1055 needed: NetworkKind::Thread,
1056 });
1057 }
1058 }
1059 }
1060 let features = nc::decode_feature_map(payload)?;
1061 if features.is_empty() {
1066 return Err(CommissioningError::MalformedResponse(
1067 Stage::ReadNetworkCommissioningInfo,
1068 ));
1069 }
1070 match &self.network {
1078 NetworkCredentials::WiFi(_) => {
1079 if !features.contains(nc::NetworkCommissioningFeature::WIFI) {
1080 return Err(CommissioningError::NetworkFeatureUnsupported {
1081 needed: NetworkKind::WiFi,
1082 });
1083 }
1084 self.advance(Stage::NetworkSetup);
1085 }
1086 NetworkCredentials::Thread(_) => {
1087 if !features.contains(nc::NetworkCommissioningFeature::THREAD) {
1088 return Err(CommissioningError::NetworkFeatureUnsupported {
1089 needed: NetworkKind::Thread,
1090 });
1091 }
1092 self.advance(Stage::NetworkSetup);
1093 }
1094 NetworkCredentials::AlreadyOnNetwork => {
1095 self.advance(Stage::EvictPreviousCaseSessions);
1104 }
1105 }
1106 Ok(())
1107 }
1108 Expectation::NetworkConfigResponse => {
1109 use crate::clusters::network_commissioning as nc;
1110 let resp = nc::decode_network_config_response(Stage::NetworkSetup, payload)?;
1111 if resp.networking_status != 0 {
1112 return Err(CommissioningError::NetworkRejected {
1113 stage: Stage::NetworkSetup,
1114 networking_status: resp.networking_status,
1115 debug_text: resp.debug_text,
1116 remediation_hint: nc::remediation_for(resp.networking_status),
1117 });
1118 }
1119 self.advance(Stage::FailsafeBeforeNetworkEnable);
1120 Ok(())
1121 }
1122 Expectation::ConnectNetworkResponse => {
1123 use crate::clusters::network_commissioning as nc;
1124 let resp = nc::decode_connect_network_response(Stage::NetworkEnable, payload)?;
1125 if resp.networking_status != 0 {
1126 return Err(CommissioningError::NetworkRejected {
1127 stage: Stage::NetworkEnable,
1128 networking_status: resp.networking_status,
1129 debug_text: resp.debug_text,
1130 remediation_hint: nc::remediation_for(resp.networking_status),
1131 });
1132 }
1133 self.advance(Stage::EvictPreviousCaseSessions);
1134 Ok(())
1135 }
1136 Expectation::CommissioningCompleteResponse => {
1137 let (error_code, _debug) =
1138 gc::decode_commissioning_error_response(Stage::SendComplete, payload)?;
1139 if error_code != 0 {
1140 return Err(CommissioningError::DeviceImStatus {
1141 stage: Stage::SendComplete,
1142 im_status: u16::from(error_code),
1143 });
1144 }
1145 self.advance(Stage::Cleanup);
1146 Ok(())
1147 }
1148 _ => Err(CommissioningError::OutOfOrderResponse(self.stage)),
1151 }
1152 }
1153
1154 fn next_breadcrumb(&mut self) -> u64 {
1155 let b = self.breadcrumb_counter;
1156 self.breadcrumb_counter = b.saturating_add(1);
1157 b
1158 }
1159
1160 fn advance(&mut self, next: Stage) {
1161 self.stage = next;
1162 self.awaiting = None;
1163 self.pending_action = None;
1164 }
1165
1166 fn run_attestation_verification(&mut self) -> Result<(), CommissioningError> {
1186 use crate::attestation::profile::{verify_attestation_cert_format, CertRole};
1187 use crate::attestation::{
1188 extract_attestation_elements_fields, verify_attestation_response, verify_chain,
1189 AttestationError, Dac, Pai,
1190 };
1191
1192 let pai_der = self
1193 .pai_der
1194 .as_ref()
1195 .ok_or(CommissioningError::OutOfOrderResponse(self.stage))?;
1196 let dac_der = self
1197 .dac_der
1198 .as_ref()
1199 .ok_or(CommissioningError::OutOfOrderResponse(self.stage))?;
1200 let response = self
1201 .attestation_response
1202 .as_ref()
1203 .ok_or(CommissioningError::OutOfOrderResponse(self.stage))?;
1204 let expected_nonce = self
1205 .attestation_nonce
1206 .ok_or(CommissioningError::OutOfOrderResponse(self.stage))?;
1207
1208 let pai = Pai::from_der(pai_der)?;
1210 let dac = Dac::from_der(dac_der)?;
1211 #[cfg(feature = "tracing")]
1212 tracing::debug!(
1213 dac_der = %crate::hexdump::hex(dac_der),
1214 pai_der = %crate::hexdump::hex(pai_der),
1215 "verifying attestation chain"
1216 );
1217
1218 verify_attestation_cert_format(pai.der(), CertRole::Pai)?;
1226 verify_attestation_cert_format(dac.der(), CertRole::Dac)?;
1227
1228 let chain = verify_chain(&dac, &pai, &self.paa_trust_store, self.now)?;
1235
1236 verify_attestation_response(response, &self.pase_attestation_challenge, dac.public_key())?;
1238
1239 let fields = extract_attestation_elements_fields(&response.attestation_elements)?;
1242 if fields.attestation_nonce != expected_nonce {
1243 return Err(CommissioningError::Attestation(
1244 AttestationError::ResponseElementsMalformed,
1245 ));
1246 }
1247
1248 #[cfg(feature = "tracing")]
1252 tracing::debug!(
1253 cd_cms = %crate::hexdump::hex(&fields.certification_declaration),
1254 "verifying certification declaration"
1255 );
1256 crate::attestation::verify_certification_declaration_with_paa(
1257 &fields.certification_declaration,
1258 chain.vendor_id,
1259 chain.product_id,
1260 &self.cd_signing_roots,
1261 chain.paa_skid.as_deref(),
1262 )?;
1263 Ok(())
1264 }
1265
1266 fn run_validate_csr(&mut self) -> Result<(), CommissioningError> {
1277 use crate::attestation::Dac;
1278 use crate::noc::verify_csr_response;
1279
1280 let resp = self
1281 .csr_response
1282 .as_ref()
1283 .ok_or(CommissioningError::OutOfOrderResponse(self.stage))?;
1284 let dac_der = self
1285 .dac_der
1286 .as_ref()
1287 .ok_or(CommissioningError::OutOfOrderResponse(self.stage))?;
1288 let csr_nonce = self
1289 .csr_nonce
1290 .ok_or(CommissioningError::OutOfOrderResponse(self.stage))?;
1291
1292 let dac = Dac::from_der(dac_der)?;
1293 let verified = verify_csr_response(
1294 &resp.nocsr_elements,
1295 &resp.attestation_signature,
1296 &csr_nonce,
1297 &self.pase_attestation_challenge,
1298 dac.public_key(),
1299 )?;
1300 self.verified_csr = Some(verified);
1301 Ok(())
1302 }
1303
1304 fn run_generate_noc_chain(&mut self) -> Result<(), CommissioningError> {
1318 use crate::noc::issue_noc;
1319
1320 let verified = self
1321 .verified_csr
1322 .as_ref()
1323 .ok_or(CommissioningError::OutOfOrderResponse(self.stage))?;
1324 let noc = issue_noc(
1325 &self.fabric,
1326 verified,
1327 self.assigned_node_id,
1328 &[],
1329 (self.now, MatterTime::NO_EXPIRY),
1330 self.rng.as_ref(),
1331 )?;
1332 self.issued_noc_public_key = Some(*verified.public_key.as_bytes());
1337 self.issued_noc = Some(noc);
1338 Ok(())
1339 }
1340
1341 fn assert_tlv_well_formed(stage: Stage, payload: &[u8]) -> Result<(), CommissioningError> {
1342 use matter_codec::{ContainerKind, Element, Tag, TlvReader};
1343 let mut reader = TlvReader::new(payload);
1344 match reader
1345 .next()
1346 .map_err(|_| CommissioningError::MalformedResponse(stage))?
1347 {
1348 Some(Element::ContainerStart {
1349 tag: Tag::Anonymous,
1350 kind: ContainerKind::Structure,
1351 }) => {}
1352 _ => return Err(CommissioningError::MalformedResponse(stage)),
1353 }
1354 loop {
1356 match reader
1357 .next()
1358 .map_err(|_| CommissioningError::MalformedResponse(stage))?
1359 {
1360 None => return Err(CommissioningError::MalformedResponse(stage)),
1361 Some(Element::ContainerEnd) => return Ok(()),
1362 Some(_) => {}
1363 }
1364 }
1365 }
1366}
1367
1368#[cfg(feature = "__test_shortcuts")]
1373#[derive(Default, Debug, Clone, Copy)]
1374pub struct TestStateSeeds {
1375 pub synthetic_noc_pubkey: Option<[u8; 65]>,
1380}
1381
1382#[cfg(test)]
1383mod tests {
1384 #![allow(clippy::unwrap_used, clippy::expect_used)]
1386
1387 use super::*;
1388 use crate::attestation::CdSigningRoots;
1389 use crate::noc::{FabricRecord, NocRng, SystemNocRng};
1390 use crate::setup::{
1391 CommissioningFlow, DiscoveryCapabilities, Discriminator, Passcode, SetupPayload,
1392 };
1393 use crate::state_machine::{Action, Expectation};
1394 use crate::PaaTrustStore;
1395 use matter_cert::time::MatterTime;
1396 use matter_crypto::{RingSigner, Signer};
1397 use std::sync::Arc;
1398
1399 fn make_setup_payload() -> SetupPayload {
1400 SetupPayload {
1401 version: 0,
1402 vendor_id: Some(0xFFF1),
1403 product_id: Some(0x8000),
1404 commissioning_flow: CommissioningFlow::Standard,
1405 discovery_capabilities: DiscoveryCapabilities::ON_NETWORK,
1406 discriminator: Discriminator::new(0x0F00).expect("valid discriminator"),
1407 passcode: Passcode::new(20_202_021).expect("valid passcode"),
1408 }
1409 }
1410
1411 fn make_fabric_record() -> FabricRecord {
1412 let (signer, _pkcs8) = RingSigner::generate().unwrap();
1413 let signer: Arc<dyn Signer> = Arc::new(signer);
1414 FabricRecord::new_root_only(
1415 0x0000_0000_0000_0001,
1416 signer,
1417 MatterTime::from_unix_secs(1_704_067_200),
1418 MatterTime::from_unix_secs(1_735_689_600),
1419 42,
1420 &SystemNocRng,
1421 )
1422 .unwrap()
1423 }
1424
1425 fn make_fabric_record_with_icac() -> FabricRecord {
1430 let mut fabric = make_fabric_record();
1431 let (icac_signer, _pkcs8) = RingSigner::generate().unwrap();
1432 let icac_public_key =
1433 matter_cert::PublicKey::from_slice(icac_signer.public_key().as_bytes()).unwrap();
1434 let icac = crate::noc::issue_icac(
1435 &fabric,
1436 7,
1437 &icac_public_key,
1438 (
1439 MatterTime::from_unix_secs(1_704_067_200),
1440 MatterTime::NO_EXPIRY,
1441 ),
1442 &SystemNocRng,
1443 )
1444 .unwrap();
1445 fabric.icac_signer = Some(Arc::new(icac_signer));
1446 fabric.icac_cert = Some(icac);
1447 fabric
1448 }
1449
1450 fn add_noc_payload_has_icac(payload: &[u8]) -> bool {
1455 use matter_codec::{Element, Tag, TlvReader};
1456 let mut r = TlvReader::new(payload);
1457 match r.next().unwrap() {
1458 Some(Element::ContainerStart { .. }) => {}
1459 other => panic!("expected an AddNOC struct, got {other:?}"),
1460 }
1461 while let Some(el) = r.next().unwrap() {
1462 match el {
1463 Element::ContainerEnd => break,
1464 Element::Scalar {
1465 tag: Tag::Context(1),
1466 ..
1467 } => return true,
1468 _ => {}
1469 }
1470 }
1471 false
1472 }
1473
1474 #[test]
1475 fn send_noc_includes_icac_for_three_tier_fabric() {
1476 let fabric = make_fabric_record_with_icac();
1477 let setup = make_setup_payload();
1478 let paa = PaaTrustStore::with_example_device_roots();
1479 let cd = CdSigningRoots::with_example_device_roots();
1480 let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
1481 let mut sm =
1482 Commissioner::new(base_config(&fabric, &setup, &paa, &cd, rng)).expect("valid config");
1483 sm.issued_noc = Some(fabric.root_cert.clone());
1487 sm.stage = Stage::SendNoc;
1488 match sm.dispatch_stage().expect("SendNoc dispatch") {
1489 Action::Invoke {
1490 cluster,
1491 command,
1492 payload,
1493 ..
1494 } => {
1495 assert_eq!(cluster, 0x003E);
1496 assert_eq!(command, 0x06);
1497 assert!(
1498 add_noc_payload_has_icac(&payload),
1499 "AddNOC must carry the ICAC (ctx1) when the fabric runs a 3-tier chain"
1500 );
1501 }
1502 other => panic!("expected Invoke, got {other:?}"),
1503 }
1504 }
1505
1506 #[test]
1507 fn send_noc_omits_icac_for_flat_fabric() {
1508 let fabric = make_fabric_record();
1509 let setup = make_setup_payload();
1510 let paa = PaaTrustStore::with_example_device_roots();
1511 let cd = CdSigningRoots::with_example_device_roots();
1512 let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
1513 let mut sm =
1514 Commissioner::new(base_config(&fabric, &setup, &paa, &cd, rng)).expect("valid config");
1515 sm.issued_noc = Some(fabric.root_cert.clone());
1516 sm.stage = Stage::SendNoc;
1517 match sm.dispatch_stage().expect("SendNoc dispatch") {
1518 Action::Invoke { payload, .. } => {
1519 assert!(
1520 !add_noc_payload_has_icac(&payload),
1521 "flat RCAC->NOC AddNOC must omit the ICAC field"
1522 );
1523 }
1524 other => panic!("expected Invoke, got {other:?}"),
1525 }
1526 }
1527
1528 fn base_config<'a>(
1529 fabric: &'a FabricRecord,
1530 setup: &'a SetupPayload,
1531 paa: &'a PaaTrustStore,
1532 cd: &'a crate::attestation::CdSigningRoots,
1533 rng: Arc<dyn NocRng>,
1534 ) -> CommissionerConfig<'a> {
1535 CommissionerConfig {
1536 pase_attestation_challenge: [0u8; 16],
1537 fabric,
1538 setup_payload: setup,
1539 paa_trust_store: paa,
1540 cd_signing_roots: cd,
1541 commissioner_node_id: 0x1,
1542 assigned_node_id: 0x2,
1543 ipk_epoch_key: [0x42_u8; 16],
1544 case_admin_subject: 0x1,
1545 admin_vendor_id: 0xFFF1,
1546 now: MatterTime::from_unix_secs(1_704_067_200),
1547 rng,
1548 network: NetworkCredentials::AlreadyOnNetwork,
1549 }
1550 }
1551
1552 #[test]
1553 fn new_rejects_zero_commissioner_node_id() {
1554 let fabric = make_fabric_record();
1555 let setup = make_setup_payload();
1556 let paa = PaaTrustStore::with_example_device_roots();
1557 let cd = CdSigningRoots::with_example_device_roots();
1558 let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
1559 let mut cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1560 cfg.commissioner_node_id = 0;
1561 let Err(err) = Commissioner::new(cfg) else {
1564 panic!("zero commissioner_node_id should fail");
1565 };
1566 assert!(
1567 matches!(err, CommissioningError::InvalidConfig(_)),
1568 "got {err:?}"
1569 );
1570 }
1571
1572 #[test]
1573 fn new_rejects_zero_assigned_node_id() {
1574 let fabric = make_fabric_record();
1575 let setup = make_setup_payload();
1576 let paa = PaaTrustStore::with_example_device_roots();
1577 let cd = CdSigningRoots::with_example_device_roots();
1578 let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
1579 let mut cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1580 cfg.assigned_node_id = 0;
1581 let Err(err) = Commissioner::new(cfg) else {
1582 panic!("zero assigned_node_id should fail");
1583 };
1584 assert!(
1585 matches!(err, CommissioningError::InvalidConfig(_)),
1586 "got {err:?}"
1587 );
1588 }
1589
1590 #[test]
1591 fn new_rejects_equal_commissioner_and_assigned_ids() {
1592 let fabric = make_fabric_record();
1593 let setup = make_setup_payload();
1594 let paa = PaaTrustStore::with_example_device_roots();
1595 let cd = CdSigningRoots::with_example_device_roots();
1596 let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
1597 let mut cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1598 cfg.commissioner_node_id = 0x42;
1599 cfg.assigned_node_id = 0x42;
1600 let Err(err) = Commissioner::new(cfg) else {
1601 panic!("equal IDs should fail");
1602 };
1603 assert!(
1604 matches!(err, CommissioningError::InvalidConfig(_)),
1605 "got {err:?}"
1606 );
1607 }
1608
1609 #[test]
1610 fn new_rejects_zero_ipk_epoch_key() {
1611 let fabric = make_fabric_record();
1612 let setup = make_setup_payload();
1613 let paa = PaaTrustStore::with_example_device_roots();
1614 let cd = CdSigningRoots::with_example_device_roots();
1615 let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
1616 let mut cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1617 cfg.ipk_epoch_key = [0u8; 16];
1618 let Err(err) = Commissioner::new(cfg) else {
1619 panic!("zero IPK should fail");
1620 };
1621 assert!(
1622 matches!(err, CommissioningError::InvalidConfig(_)),
1623 "got {err:?}"
1624 );
1625 }
1626
1627 #[test]
1628 fn new_returns_secure_pairing_stage() {
1629 let fabric = make_fabric_record();
1630 let setup = make_setup_payload();
1631 let paa = PaaTrustStore::with_example_device_roots();
1632 let cd = CdSigningRoots::with_example_device_roots();
1633 let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
1634 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1635 let sm = Commissioner::new(cfg).expect("valid config should construct");
1636 assert_eq!(sm.stage(), Stage::SecurePairing);
1637 }
1638
1639 #[test]
1640 fn poll_from_secure_pairing_emits_read_commissioning_info() {
1641 let fabric = make_fabric_record();
1642 let setup = make_setup_payload();
1643 let paa = PaaTrustStore::with_example_device_roots();
1644 let cd = CdSigningRoots::with_example_device_roots();
1645 let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
1646 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1647 let mut sm = Commissioner::new(cfg).expect("valid config");
1648 let act = sm.poll().expect("poll succeeds");
1649 match act {
1650 Action::ReadAttribute {
1651 session,
1652 endpoint,
1653 cluster,
1654 attributes,
1655 expect,
1656 } => {
1657 assert_eq!(session, crate::state_machine::SessionContext::Pase);
1658 assert_eq!(endpoint, 0);
1659 assert_eq!(cluster, 0x0030);
1660 assert_eq!(expect, Expectation::CommissioningInfo);
1661 assert!(!attributes.is_empty());
1662 }
1663 other => panic!("expected ReadAttribute, got {other:?}"),
1664 }
1665 assert_eq!(sm.stage(), Stage::ReadCommissioningInfo);
1666 }
1667
1668 #[test]
1669 fn poll_is_idempotent_between_responses() {
1670 let fabric = make_fabric_record();
1671 let setup = make_setup_payload();
1672 let paa = PaaTrustStore::with_example_device_roots();
1673 let cd = CdSigningRoots::with_example_device_roots();
1674 let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
1675 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1676 let mut sm = Commissioner::new(cfg).expect("valid config");
1677 let act1 = sm.poll().expect("first poll");
1678 let act2 = sm.poll().expect("second poll");
1679 match (act1, act2) {
1680 (
1681 Action::ReadAttribute {
1682 cluster: c1,
1683 expect: e1,
1684 ..
1685 },
1686 Action::ReadAttribute {
1687 cluster: c2,
1688 expect: e2,
1689 ..
1690 },
1691 ) => {
1692 assert_eq!(c1, c2);
1693 assert_eq!(e1, e2);
1694 }
1695 other => panic!("idempotent poll returned different variants: {other:?}"),
1696 }
1697 }
1698
1699 #[test]
1700 fn full_happy_path_through_config_regulatory_lands_on_send_pai_cert_request() {
1701 let fabric = make_fabric_record();
1702 let setup = make_setup_payload();
1703 let paa = PaaTrustStore::with_example_device_roots();
1704 let cd = CdSigningRoots::with_example_device_roots();
1705 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
1706 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1707 let mut sm = Commissioner::new(cfg).expect("valid config");
1708
1709 let _ = sm.poll().expect("poll #1");
1711 let canned_info = encode_read_commissioning_info_response();
1712 sm.on_response(Expectation::CommissioningInfo, &canned_info)
1713 .expect("commissioning info accepted");
1714 assert_eq!(sm.stage(), Stage::ArmFailsafe);
1715
1716 let _ = sm.poll().expect("poll #2");
1718 sm.on_response(
1719 Expectation::ArmFailsafeResponse,
1720 &[0x15, 0x24, 0x00, 0x00, 0x18],
1721 )
1722 .expect("arm failsafe ok");
1723 assert_eq!(sm.stage(), Stage::ConfigRegulatory);
1724
1725 let _ = sm.poll().expect("poll #3");
1727 sm.on_response(
1728 Expectation::SetRegulatoryConfigResponse,
1729 &[0x15, 0x24, 0x00, 0x00, 0x18],
1730 )
1731 .expect("config regulatory ok");
1732 assert_eq!(sm.stage(), Stage::SendPaiCertRequest);
1733
1734 match sm.poll().expect("poll #4") {
1736 Action::Invoke {
1737 cluster,
1738 command,
1739 expect,
1740 ..
1741 } => {
1742 assert_eq!(cluster, 0x003E);
1743 assert_eq!(command, 0x02);
1744 assert_eq!(expect, Expectation::PaiCertChainResponse);
1745 }
1746 other => panic!("expected Invoke, got {other:?}"),
1747 }
1748 }
1749
1750 #[test]
1751 fn arm_failsafe_busy_response_aborts_with_device_im_status() {
1752 let fabric = make_fabric_record();
1753 let setup = make_setup_payload();
1754 let paa = PaaTrustStore::with_example_device_roots();
1755 let cd = CdSigningRoots::with_example_device_roots();
1756 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
1757 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1758 let mut sm = Commissioner::new(cfg).expect("valid config");
1759 let _ = sm.poll().expect("poll info");
1760 sm.on_response(
1761 Expectation::CommissioningInfo,
1762 &encode_read_commissioning_info_response(),
1763 )
1764 .expect("commissioning info ok");
1765 let _ = sm.poll().expect("poll arm failsafe");
1766 let err = sm
1768 .on_response(
1769 Expectation::ArmFailsafeResponse,
1770 &[0x15, 0x24, 0x00, 0x04, 0x18],
1771 )
1772 .expect_err("busy should fail");
1773 assert!(matches!(
1774 err,
1775 CommissioningError::DeviceImStatus {
1776 stage: Stage::ArmFailsafe,
1777 im_status: 4,
1778 }
1779 ));
1780 assert_eq!(sm.stage(), Stage::Failed);
1781 match sm.poll().expect("abort emission") {
1782 Action::Abort {
1783 send_disarm_failsafe,
1784 reason,
1785 } => {
1786 assert!(send_disarm_failsafe);
1787 assert!(reason.contains("ArmFailsafe"), "reason was {reason}");
1788 assert!(reason.contains("0x4"), "reason was {reason}");
1789 }
1790 other => panic!("expected Abort, got {other:?}"),
1791 }
1792 }
1793
1794 #[test]
1795 fn out_of_order_response_returns_error_without_advancing() {
1796 let fabric = make_fabric_record();
1797 let setup = make_setup_payload();
1798 let paa = PaaTrustStore::with_example_device_roots();
1799 let cd = CdSigningRoots::with_example_device_roots();
1800 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
1801 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1802 let mut sm = Commissioner::new(cfg).expect("valid config");
1803 let err = sm
1805 .on_response(Expectation::ArmFailsafeResponse, &[])
1806 .expect_err("should reject out-of-order");
1807 assert!(matches!(err, CommissioningError::OutOfOrderResponse(_)));
1808 assert_eq!(sm.stage(), Stage::SecurePairing);
1809 }
1810
1811 #[test]
1812 fn wrong_expectation_returns_unexpected_response_kind() {
1813 let fabric = make_fabric_record();
1814 let setup = make_setup_payload();
1815 let paa = PaaTrustStore::with_example_device_roots();
1816 let cd = CdSigningRoots::with_example_device_roots();
1817 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
1818 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1819 let mut sm = Commissioner::new(cfg).expect("valid config");
1820 let _ = sm.poll().expect("poll");
1821 let err = sm
1822 .on_response(Expectation::ArmFailsafeResponse, &[])
1823 .expect_err("wrong kind should fail");
1824 assert!(matches!(
1825 err,
1826 CommissioningError::UnexpectedResponseKind {
1827 expected: Expectation::CommissioningInfo,
1828 got: Expectation::ArmFailsafeResponse,
1829 }
1830 ));
1831 assert_eq!(sm.stage(), Stage::ReadCommissioningInfo);
1833 }
1834
1835 fn encode_read_commissioning_info_response() -> Vec<u8> {
1836 vec![0x15, 0x18]
1839 }
1840
1841 fn drive_to_send_pai_cert_request(sm: &mut Commissioner) {
1844 let _ = sm.poll().expect("poll info");
1845 sm.on_response(Expectation::CommissioningInfo, &[0x15, 0x18])
1846 .expect("info ok");
1847 let _ = sm.poll().expect("poll arm failsafe");
1848 sm.on_response(
1849 Expectation::ArmFailsafeResponse,
1850 &[0x15, 0x24, 0x00, 0x00, 0x18],
1851 )
1852 .expect("arm ok");
1853 let _ = sm.poll().expect("poll config regulatory");
1854 sm.on_response(
1855 Expectation::SetRegulatoryConfigResponse,
1856 &[0x15, 0x24, 0x00, 0x00, 0x18],
1857 )
1858 .expect("regulatory ok");
1859 }
1860
1861 fn synthetic_cert_chain_response(cert: &[u8]) -> Vec<u8> {
1862 use matter_codec::{Tag, TlvWriter};
1863 let mut buf = Vec::new();
1864 let mut w = TlvWriter::new(&mut buf);
1865 w.start_structure(Tag::Anonymous).expect("infallible");
1866 w.put_bytes(Tag::Context(0), cert).expect("infallible");
1867 w.end_container().expect("infallible");
1868 buf
1869 }
1870
1871 fn nonce_from_attestation_invoke(act: &Action) -> [u8; 32] {
1872 match act {
1873 Action::Invoke { payload, .. } => {
1874 use matter_codec::{Element, Tag, TlvReader, Value};
1875 let mut r = TlvReader::new(payload);
1876 let _ = r.next().expect("reader").expect("anon-struct-start");
1877 loop {
1878 match r.next().expect("reader") {
1879 Some(Element::Scalar {
1880 tag: Tag::Context(0),
1881 value: Value::Bytes(b),
1882 }) => {
1883 return b.as_slice().try_into().expect("32 bytes");
1884 }
1885 Some(_) => {}
1886 None => panic!("no nonce found"),
1887 }
1888 }
1889 }
1890 other => panic!("expected Invoke, got {other:?}"),
1891 }
1892 }
1893
1894 #[test]
1895 fn poll_at_send_pai_emits_certificate_chain_request_pai() {
1896 let fabric = make_fabric_record();
1897 let setup = make_setup_payload();
1898 let paa = PaaTrustStore::with_example_device_roots();
1899 let cd = CdSigningRoots::with_example_device_roots();
1900 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
1901 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1902 let mut sm = Commissioner::new(cfg).expect("valid config");
1903 drive_to_send_pai_cert_request(&mut sm);
1904 assert_eq!(sm.stage(), Stage::SendPaiCertRequest);
1905 match sm.poll().expect("poll PAI") {
1906 Action::Invoke {
1907 cluster,
1908 command,
1909 expect,
1910 payload,
1911 ..
1912 } => {
1913 assert_eq!(cluster, 0x003E);
1914 assert_eq!(command, 0x02);
1915 assert_eq!(expect, Expectation::PaiCertChainResponse);
1916 assert_eq!(payload, vec![0x15, 0x24, 0x00, 0x02, 0x18]);
1918 }
1919 other => panic!("expected Invoke, got {other:?}"),
1920 }
1921 }
1922
1923 #[test]
1924 fn poll_at_send_dac_emits_certificate_chain_request_dac() {
1925 let fabric = make_fabric_record();
1926 let setup = make_setup_payload();
1927 let paa = PaaTrustStore::with_example_device_roots();
1928 let cd = CdSigningRoots::with_example_device_roots();
1929 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
1930 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1931 let mut sm = Commissioner::new(cfg).expect("valid config");
1932 drive_to_send_pai_cert_request(&mut sm);
1933 let _ = sm.poll().expect("poll PAI");
1934 let pai_response = synthetic_cert_chain_response(&[0xAA, 0xBB, 0xCC]);
1935 sm.on_response(Expectation::PaiCertChainResponse, &pai_response)
1936 .expect("PAI accepted");
1937 assert_eq!(sm.stage(), Stage::SendDacCertRequest);
1938 match sm.poll().expect("poll DAC") {
1939 Action::Invoke {
1940 cluster,
1941 command,
1942 expect,
1943 payload,
1944 ..
1945 } => {
1946 assert_eq!(cluster, 0x003E);
1947 assert_eq!(command, 0x02);
1948 assert_eq!(expect, Expectation::DacCertChainResponse);
1949 assert_eq!(payload, vec![0x15, 0x24, 0x00, 0x01, 0x18]);
1951 }
1952 other => panic!("expected Invoke, got {other:?}"),
1953 }
1954 }
1955
1956 #[test]
1957 fn send_attestation_request_uses_fresh_random_nonce_each_time() {
1958 let fabric = make_fabric_record();
1959 let setup = make_setup_payload();
1960 let paa = PaaTrustStore::with_example_device_roots();
1961 let cd = CdSigningRoots::with_example_device_roots();
1962
1963 let rng_a: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
1964 let cfg_a = base_config(&fabric, &setup, &paa, &cd, rng_a);
1965 let mut sm_a = Commissioner::new(cfg_a).expect("valid config");
1966 drive_to_send_pai_cert_request(&mut sm_a);
1967 let _ = sm_a.poll().expect("poll PAI a");
1968 let pai_response = synthetic_cert_chain_response(&[0xAA]);
1969 sm_a.on_response(Expectation::PaiCertChainResponse, &pai_response)
1970 .expect("ok");
1971 let _ = sm_a.poll().expect("poll DAC a");
1972 let dac_response = synthetic_cert_chain_response(&[0xBB]);
1973 sm_a.on_response(Expectation::DacCertChainResponse, &dac_response)
1974 .expect("ok");
1975 let nonce_a = nonce_from_attestation_invoke(&sm_a.poll().expect("poll att a"));
1976
1977 let rng_b: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
1978 let cfg_b = base_config(&fabric, &setup, &paa, &cd, rng_b);
1979 let mut sm_b = Commissioner::new(cfg_b).expect("valid config");
1980 drive_to_send_pai_cert_request(&mut sm_b);
1981 let _ = sm_b.poll().expect("poll PAI b");
1982 sm_b.on_response(Expectation::PaiCertChainResponse, &pai_response)
1983 .expect("ok");
1984 let _ = sm_b.poll().expect("poll DAC b");
1985 sm_b.on_response(Expectation::DacCertChainResponse, &dac_response)
1986 .expect("ok");
1987 let nonce_b = nonce_from_attestation_invoke(&sm_b.poll().expect("poll att b"));
1988
1989 assert_ne!(
1990 nonce_a, nonce_b,
1991 "two independent runs should use different random nonces"
1992 );
1993 }
1994
1995 fn nonce_from_csr_invoke(act: &Action) -> [u8; 32] {
2001 match act {
2002 Action::Invoke { payload, .. } => {
2003 use matter_codec::{Element, Tag, TlvReader, Value};
2004 let mut r = TlvReader::new(payload);
2005 let _ = r.next().expect("reader").expect("anon-struct-start");
2006 loop {
2007 match r.next().expect("reader") {
2008 Some(Element::Scalar {
2009 tag: Tag::Context(0),
2010 value: Value::Bytes(b),
2011 }) => {
2012 return b.as_slice().try_into().expect("32 bytes");
2013 }
2014 Some(_) => {}
2015 None => panic!("no nonce found"),
2016 }
2017 }
2018 }
2019 other => panic!("expected Invoke, got {other:?}"),
2020 }
2021 }
2022
2023 #[test]
2030 fn send_op_cert_signing_request_emits_csr_with_random_nonce() {
2031 let fabric = make_fabric_record();
2032 let setup = make_setup_payload();
2033 let paa = PaaTrustStore::with_example_device_roots();
2034 let cd = CdSigningRoots::with_example_device_roots();
2035
2036 let rng_a: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2037 let cfg_a = base_config(&fabric, &setup, &paa, &cd, rng_a);
2038 let mut sm_a = Commissioner::new(cfg_a).expect("valid config");
2039 sm_a.stage = Stage::SendOpCertSigningRequest;
2043 sm_a.dac_der = Some(vec![0xAA, 0xBB]);
2044 let act_a = sm_a.poll().expect("poll csr a");
2045 let nonce_a = nonce_from_csr_invoke(&act_a);
2046
2047 let rng_b: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2048 let cfg_b = base_config(&fabric, &setup, &paa, &cd, rng_b);
2049 let mut sm_b = Commissioner::new(cfg_b).expect("valid config");
2050 sm_b.stage = Stage::SendOpCertSigningRequest;
2051 sm_b.dac_der = Some(vec![0xAA, 0xBB]);
2052 let act_b = sm_b.poll().expect("poll csr b");
2053 let nonce_b = nonce_from_csr_invoke(&act_b);
2054
2055 assert_ne!(
2056 nonce_a, nonce_b,
2057 "two independent runs should use different CSR nonces"
2058 );
2059
2060 match act_a {
2061 Action::Invoke {
2062 cluster,
2063 command,
2064 expect,
2065 ..
2066 } => {
2067 assert_eq!(cluster, 0x003E);
2068 assert_eq!(command, 0x04);
2069 assert_eq!(expect, Expectation::CsrResponse);
2070 }
2071 other => panic!("expected Invoke, got {other:?}"),
2072 }
2073 }
2074
2075 #[test]
2082 fn drive_through_send_noc_with_synthetic_noc_response() {
2083 use matter_cert::{
2084 BasicConstraints, DistinguishedName, DnAttribute, Extensions, MatterCertificate,
2085 PublicKey,
2086 };
2087
2088 let fabric = make_fabric_record();
2089 let setup = make_setup_payload();
2090 let paa = PaaTrustStore::with_example_device_roots();
2091 let cd = CdSigningRoots::with_example_device_roots();
2092 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2093 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2094 let mut sm = Commissioner::new(cfg).expect("valid config");
2095
2096 let mut key_bytes = [0u8; 65];
2102 key_bytes[0] = 0x04;
2103 let synthetic_noc = MatterCertificate::builder()
2104 .serial(vec![1, 2, 3])
2105 .issuer(fabric.root_cert.subject().clone())
2106 .subject(DistinguishedName::new(vec![
2107 DnAttribute::FabricId(fabric.fabric_id),
2108 DnAttribute::NodeId(0x2),
2109 ]))
2110 .validity(
2111 MatterTime::from_unix_secs(1_704_067_200),
2112 MatterTime::NO_EXPIRY,
2113 )
2114 .public_key(PublicKey::new(key_bytes).expect("valid sec1 prefix"))
2115 .extensions(
2116 Extensions::builder()
2117 .basic_constraints(Some(BasicConstraints::new(false, None)))
2118 .build(),
2119 )
2120 .build_unsigned()
2121 .expect("builder")
2122 .assemble([0u8; 64]);
2123
2124 sm.stage = Stage::SendNoc;
2125 sm.issued_noc = Some(synthetic_noc);
2126 sm.issued_noc_public_key = Some(key_bytes);
2127
2128 match sm.poll().expect("poll SendNoc") {
2129 Action::Invoke {
2130 cluster,
2131 command,
2132 expect,
2133 ..
2134 } => {
2135 assert_eq!(cluster, 0x003E);
2136 assert_eq!(command, 0x06);
2137 assert_eq!(expect, Expectation::NocResponse);
2138 }
2139 other => panic!("expected Invoke, got {other:?}"),
2140 }
2141
2142 let mut noc_response = Vec::new();
2144 {
2145 use matter_codec::{Tag, TlvWriter};
2146 let mut w = TlvWriter::new(&mut noc_response);
2147 w.start_structure(Tag::Anonymous).expect("infallible");
2148 w.put_uint(Tag::Context(0), 0).expect("infallible"); w.put_uint(Tag::Context(1), 1).expect("infallible"); w.end_container().expect("infallible");
2151 }
2152 sm.on_response(Expectation::NocResponse, &noc_response)
2153 .expect("NocResponse accepted");
2154 assert_eq!(sm.stage(), Stage::ReadNetworkCommissioningInfo);
2155 }
2156
2157 #[test]
2161 fn send_noc_failure_status_aborts_with_device_im_status() {
2162 use matter_cert::{
2163 BasicConstraints, DistinguishedName, DnAttribute, Extensions, MatterCertificate,
2164 PublicKey,
2165 };
2166
2167 let fabric = make_fabric_record();
2168 let setup = make_setup_payload();
2169 let paa = PaaTrustStore::with_example_device_roots();
2170 let cd = CdSigningRoots::with_example_device_roots();
2171 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2172 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2173 let mut sm = Commissioner::new(cfg).expect("valid config");
2174
2175 let mut key_bytes = [0u8; 65];
2176 key_bytes[0] = 0x04;
2177 let synthetic_noc = MatterCertificate::builder()
2178 .serial(vec![9])
2179 .issuer(fabric.root_cert.subject().clone())
2180 .subject(DistinguishedName::new(vec![
2181 DnAttribute::FabricId(fabric.fabric_id),
2182 DnAttribute::NodeId(0x2),
2183 ]))
2184 .validity(
2185 MatterTime::from_unix_secs(1_704_067_200),
2186 MatterTime::NO_EXPIRY,
2187 )
2188 .public_key(PublicKey::new(key_bytes).expect("valid sec1 prefix"))
2189 .extensions(
2190 Extensions::builder()
2191 .basic_constraints(Some(BasicConstraints::new(false, None)))
2192 .build(),
2193 )
2194 .build_unsigned()
2195 .expect("builder")
2196 .assemble([0u8; 64]);
2197
2198 sm.stage = Stage::SendNoc;
2199 sm.issued_noc = Some(synthetic_noc);
2200 sm.issued_noc_public_key = Some(key_bytes);
2201
2202 let _ = sm.poll().expect("poll SendNoc");
2203
2204 let mut bad_response = Vec::new();
2206 {
2207 use matter_codec::{Tag, TlvWriter};
2208 let mut w = TlvWriter::new(&mut bad_response);
2209 w.start_structure(Tag::Anonymous).expect("infallible");
2210 w.put_uint(Tag::Context(0), 9).expect("infallible");
2211 w.end_container().expect("infallible");
2212 }
2213 let err = sm
2214 .on_response(Expectation::NocResponse, &bad_response)
2215 .expect_err("non-zero NOC status should fail");
2216 assert!(matches!(
2217 err,
2218 CommissioningError::DeviceImStatus {
2219 stage: Stage::SendNoc,
2220 im_status: 9,
2221 }
2222 ));
2223 assert_eq!(sm.stage(), Stage::Failed);
2224 }
2225
2226 #[test]
2231 fn send_trusted_root_cert_emits_invoke_and_status_ack_advances() {
2232 let fabric = make_fabric_record();
2233 let setup = make_setup_payload();
2234 let paa = PaaTrustStore::with_example_device_roots();
2235 let cd = CdSigningRoots::with_example_device_roots();
2236 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2237 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2238 let mut sm = Commissioner::new(cfg).expect("valid config");
2239
2240 sm.stage = Stage::SendTrustedRootCert;
2241
2242 match sm.poll().expect("poll SendTrustedRootCert") {
2243 Action::Invoke {
2244 cluster,
2245 command,
2246 expect,
2247 payload,
2248 ..
2249 } => {
2250 assert_eq!(cluster, 0x003E);
2251 assert_eq!(command, 0x0B);
2252 assert_eq!(expect, Expectation::AddTrustedRootResponse);
2253 assert!(payload.len() > 16, "RCAC TLV too short: {}", payload.len());
2256 assert_eq!(payload[0], 0x15); }
2258 other => panic!("expected Invoke, got {other:?}"),
2259 }
2260
2261 sm.on_response(Expectation::AddTrustedRootResponse, &[0x00])
2263 .expect("status-ack accepted");
2264 assert_eq!(sm.stage(), Stage::SendNoc);
2265 }
2266
2267 #[test]
2270 fn find_operational_for_complete_emits_establish_case() {
2271 let fabric = make_fabric_record();
2272 let setup = make_setup_payload();
2273 let paa = PaaTrustStore::with_example_device_roots();
2274 let cd = crate::attestation::CdSigningRoots::with_example_device_roots();
2275 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2276 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2277 let mut sm = Commissioner::new(cfg).expect("valid config");
2278 sm.stage = Stage::FindOperationalForComplete;
2279 match sm.poll().expect("poll establish case") {
2280 Action::EstablishCase {
2281 fabric_id,
2282 peer_node_id,
2283 } => {
2284 assert_eq!(fabric_id, fabric.fabric_id);
2285 assert_eq!(peer_node_id, 0x2); }
2287 other => panic!("expected EstablishCase, got {other:?}"),
2288 }
2289 assert!(sm.awaiting_case_session);
2290 }
2291
2292 #[test]
2293 fn on_case_established_advances_to_send_complete() {
2294 let fabric = make_fabric_record();
2295 let setup = make_setup_payload();
2296 let paa = PaaTrustStore::with_example_device_roots();
2297 let cd = crate::attestation::CdSigningRoots::with_example_device_roots();
2298 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2299 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2300 let mut sm = Commissioner::new(cfg).expect("valid config");
2301 sm.stage = Stage::FindOperationalForComplete;
2302 let _ = sm.poll().expect("emit EstablishCase");
2303 sm.on_case_established().expect("case established");
2304 assert_eq!(sm.stage(), Stage::SendComplete);
2305 assert!(!sm.awaiting_case_session);
2306 }
2307
2308 #[test]
2309 fn on_case_established_without_pending_emits_out_of_order() {
2310 let fabric = make_fabric_record();
2311 let setup = make_setup_payload();
2312 let paa = PaaTrustStore::with_example_device_roots();
2313 let cd = crate::attestation::CdSigningRoots::with_example_device_roots();
2314 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2315 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2316 let mut sm = Commissioner::new(cfg).expect("valid config");
2317 let err = sm.on_case_established().expect_err("no pending establish");
2318 assert!(matches!(err, CommissioningError::OutOfOrderResponse(_)));
2319 }
2320
2321 #[test]
2322 fn send_complete_emits_invoke_over_case_session() {
2323 let fabric = make_fabric_record();
2324 let setup = make_setup_payload();
2325 let paa = PaaTrustStore::with_example_device_roots();
2326 let cd = crate::attestation::CdSigningRoots::with_example_device_roots();
2327 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2328 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2329 let mut sm = Commissioner::new(cfg).expect("valid config");
2330 sm.stage = Stage::SendComplete;
2331 match sm.poll().expect("poll send complete") {
2332 Action::Invoke {
2333 session,
2334 cluster,
2335 command,
2336 expect,
2337 payload,
2338 ..
2339 } => {
2340 assert_eq!(session, crate::state_machine::SessionContext::Case);
2341 assert_eq!(cluster, 0x0030);
2342 assert_eq!(command, 0x04); assert_eq!(expect, Expectation::CommissioningCompleteResponse);
2344 assert_eq!(payload, vec![0x15, 0x18]);
2346 }
2347 other => panic!("expected Invoke, got {other:?}"),
2348 }
2349 }
2350
2351 #[test]
2352 fn send_complete_success_advances_to_cleanup() {
2353 let fabric = make_fabric_record();
2354 let setup = make_setup_payload();
2355 let paa = PaaTrustStore::with_example_device_roots();
2356 let cd = crate::attestation::CdSigningRoots::with_example_device_roots();
2357 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2358 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2359 let mut sm = Commissioner::new(cfg).expect("valid config");
2360 sm.stage = Stage::SendComplete;
2361 let _ = sm.poll().expect("emit invoke");
2362 sm.on_response(
2363 Expectation::CommissioningCompleteResponse,
2364 &[0x15, 0x24, 0x00, 0x00, 0x18], )
2366 .expect("complete ok");
2367 assert_eq!(sm.stage(), Stage::Cleanup);
2368 }
2369
2370 #[test]
2371 fn cleanup_emits_done_with_noc_public_key() {
2372 let fabric = make_fabric_record();
2373 let setup = make_setup_payload();
2374 let paa = PaaTrustStore::with_example_device_roots();
2375 let cd = crate::attestation::CdSigningRoots::with_example_device_roots();
2376 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2377 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2378 let mut sm = Commissioner::new(cfg).expect("valid config");
2379 sm.stage = Stage::Cleanup;
2380 sm.issued_noc_public_key = Some([0xCA; 65]);
2381 match sm.poll().expect("poll cleanup") {
2382 Action::Done(cf) => {
2383 assert_eq!(cf.peer_node_id, 0x2);
2384 assert_eq!(cf.peer_root_public_key, [0xCA; 65]);
2385 assert_eq!(cf.terminated_at, Stage::Cleanup);
2386 assert_eq!(cf.fabric.fabric_id, fabric.fabric_id);
2387 }
2388 other => panic!("expected Done, got {other:?}"),
2389 }
2390 }
2391
2392 #[test]
2395 fn case_failed_response_aborts_with_case_establishment_failed() {
2396 let fabric = make_fabric_record();
2397 let setup = make_setup_payload();
2398 let paa = PaaTrustStore::with_example_device_roots();
2399 let cd = crate::attestation::CdSigningRoots::with_example_device_roots();
2400 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2401 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2402 let mut sm = Commissioner::new(cfg).expect("valid config");
2403
2404 sm.stage = Stage::FindOperationalForComplete;
2407 let _ = sm.poll().expect("emit EstablishCase");
2408 assert!(sm.awaiting_case_session);
2409
2410 let err = sm
2412 .on_response(Expectation::CaseFailed, &[])
2413 .expect_err("CaseFailed should error");
2414 assert!(matches!(err, CommissioningError::CaseEstablishmentFailed));
2415 assert_eq!(sm.stage(), Stage::Failed);
2416 assert!(!sm.awaiting_case_session);
2417
2418 match sm.poll().expect("emit abort") {
2420 Action::Abort {
2421 send_disarm_failsafe,
2422 reason,
2423 } => {
2424 assert!(send_disarm_failsafe);
2425 assert!(
2426 reason.contains("CASE"),
2427 "abort reason should mention CASE: {reason}"
2428 );
2429 }
2430 other => panic!("expected Abort, got {other:?}"),
2431 }
2432 }
2433
2434 #[test]
2435 fn case_failed_when_not_awaiting_returns_out_of_order() {
2436 let fabric = make_fabric_record();
2437 let setup = make_setup_payload();
2438 let paa = PaaTrustStore::with_example_device_roots();
2439 let cd = crate::attestation::CdSigningRoots::with_example_device_roots();
2440 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2441 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2442 let mut sm = Commissioner::new(cfg).expect("valid config");
2443 let err = sm
2444 .on_response(Expectation::CaseFailed, &[])
2445 .expect_err("CaseFailed without pending should error");
2446 assert!(matches!(err, CommissioningError::OutOfOrderResponse(_)));
2447 }
2448
2449 fn sample_valid_config() -> CommissionerConfig<'static> {
2453 use std::sync::OnceLock;
2454 static FABRIC: OnceLock<FabricRecord> = OnceLock::new();
2455 static SETUP: OnceLock<SetupPayload> = OnceLock::new();
2456 static PAA: OnceLock<PaaTrustStore> = OnceLock::new();
2457 static CD: OnceLock<crate::attestation::CdSigningRoots> = OnceLock::new();
2458
2459 let fabric = FABRIC.get_or_init(make_fabric_record);
2460 let setup = SETUP.get_or_init(make_setup_payload);
2461 let paa = PAA.get_or_init(PaaTrustStore::with_example_device_roots);
2462 let cd = CD.get_or_init(crate::attestation::CdSigningRoots::with_example_device_roots);
2463 let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
2464
2465 CommissionerConfig {
2466 pase_attestation_challenge: [0u8; 16],
2467 fabric,
2468 setup_payload: setup,
2469 paa_trust_store: paa,
2470 cd_signing_roots: cd,
2471 commissioner_node_id: 0x1,
2472 assigned_node_id: 0x2,
2473 ipk_epoch_key: [0x42_u8; 16],
2474 case_admin_subject: 0x1,
2475 admin_vendor_id: 0xFFF1,
2476 now: MatterTime::from_unix_secs(1_704_067_200),
2477 rng,
2478 network: NetworkCredentials::AlreadyOnNetwork,
2479 }
2480 }
2481
2482 #[test]
2483 fn empty_ssid_is_rejected() {
2484 let mut config = sample_valid_config();
2485 config.network = NetworkCredentials::WiFi(WiFiCredentials {
2486 ssid: vec![],
2487 credentials: vec![],
2488 });
2489 let Err(err) = Commissioner::new(config) else {
2490 panic!("empty ssid should fail");
2491 };
2492 assert!(
2493 matches!(err, CommissioningError::InvalidConfig(m) if m.contains("ssid")),
2494 "got {err:?}",
2495 );
2496 }
2497
2498 #[test]
2499 fn oversize_ssid_is_rejected() {
2500 let mut config = sample_valid_config();
2501 config.network = NetworkCredentials::WiFi(WiFiCredentials {
2502 ssid: vec![b'a'; 33],
2503 credentials: vec![],
2504 });
2505 let Err(err) = Commissioner::new(config) else {
2506 panic!("33-byte ssid should fail");
2507 };
2508 assert!(
2509 matches!(err, CommissioningError::InvalidConfig(m) if m.contains("≤32")),
2510 "got {err:?}",
2511 );
2512 }
2513
2514 #[test]
2515 fn oversize_credentials_is_rejected() {
2516 let mut config = sample_valid_config();
2517 config.network = NetworkCredentials::WiFi(WiFiCredentials {
2518 ssid: b"matter".to_vec(),
2519 credentials: vec![0u8; 65],
2520 });
2521 let Err(err) = Commissioner::new(config) else {
2522 panic!("65-byte credentials should fail");
2523 };
2524 assert!(
2525 matches!(err, CommissioningError::InvalidConfig(m) if m.contains("≤64")),
2526 "got {err:?}",
2527 );
2528 }
2529
2530 #[test]
2531 fn wifi_credentials_none_is_accepted() {
2532 let mut config = sample_valid_config();
2533 config.network = NetworkCredentials::AlreadyOnNetwork;
2534 Commissioner::new(config).expect("AlreadyOnNetwork should pass validation");
2535 }
2536
2537 #[test]
2545 fn network_commissioning_cluster_absent_skips_provisioning_when_already_on_network() {
2546 let mut config = sample_valid_config();
2547 config.network = NetworkCredentials::AlreadyOnNetwork;
2548 let mut sm = Commissioner::new(config).expect("valid config");
2549 sm.stage = Stage::ReadNetworkCommissioningInfo;
2550 match sm.poll().expect("poll emits the read") {
2551 Action::ReadAttribute { expect, .. } => {
2552 assert_eq!(expect, Expectation::NetworkCommissioningInfo);
2553 }
2554 other => panic!("expected ReadAttribute, got {other:?}"),
2555 }
2556
2557 sm.on_response(Expectation::NetworkCommissioningInfo, &[])
2558 .expect("absent cluster is acceptable when already on network");
2559 assert_eq!(sm.stage(), Stage::EvictPreviousCaseSessions);
2560 }
2561
2562 #[test]
2567 fn network_commissioning_cluster_absent_rejects_wifi_provisioning() {
2568 use crate::state_machine::NetworkKind;
2569
2570 let mut config = sample_valid_config();
2571 config.network = NetworkCredentials::WiFi(WiFiCredentials {
2572 ssid: b"matter".to_vec(),
2573 credentials: b"hunter22".to_vec(),
2574 });
2575 let mut sm = Commissioner::new(config).expect("valid config");
2576 sm.stage = Stage::ReadNetworkCommissioningInfo;
2577 let _ = sm.poll().expect("poll emits the read");
2578
2579 let Err(err) = sm.on_response(Expectation::NetworkCommissioningInfo, &[]) else {
2580 panic!("absent cluster with Wi-Fi credentials must fail");
2581 };
2582 assert!(
2583 matches!(
2584 err,
2585 CommissioningError::NetworkFeatureUnsupported {
2586 needed: NetworkKind::WiFi,
2587 }
2588 ),
2589 "got {err:?}",
2590 );
2591 }
2592
2593 #[test]
2594 fn network_credentials_thread_variant_accepted() {
2595 let ds =
2598 crate::thread_dataset::ThreadDataset::new(vec![0x02, 0x08, 0, 0, 0, 0, 0, 0, 0, 0])
2599 .expect("minimal ext-pan-id dataset is valid");
2600 let mut config = sample_valid_config();
2601 config.network = NetworkCredentials::Thread(ds);
2602 let c = Commissioner::new(config).expect("Thread network should pass validation");
2603 assert!(matches!(c.network(), NetworkCredentials::Thread(_)));
2604 }
2605
2606 #[test]
2607 fn wifi_credentials_debug_redacts_passphrase() {
2608 let creds = WiFiCredentials {
2609 ssid: b"matter".to_vec(),
2610 credentials: b"hunter22".to_vec(),
2611 };
2612 let rendered = format!("{creds:?}");
2613 assert!(
2614 !rendered.contains("hunter22"),
2615 "Debug must not contain credentials bytes: {rendered}",
2616 );
2617 assert!(rendered.contains("redacted"), "got {rendered}");
2618 assert!(
2619 rendered.contains('8'),
2620 "credentials length should appear: {rendered}"
2621 );
2622 assert!(
2623 rendered.contains('6'),
2624 "ssid length should appear: {rendered}"
2625 );
2626 }
2627
2628 #[test]
2631 fn failsafe_expiry_derives_from_basic_commissioning_info() {
2632 let mut sm = Commissioner::new(sample_valid_config()).expect("valid config");
2633 let _initial = sm.poll().expect("initial poll");
2635 let response = vec![
2636 0x15, 0x25, 0x00, 0x78, 0x00, 0x18,
2638 ];
2639 sm.on_response(Expectation::CommissioningInfo, &response)
2640 .expect("commissioning info accepted");
2641 let action = sm.poll().expect("arm-failsafe poll");
2643 match action {
2644 Action::Invoke {
2645 payload,
2646 cluster,
2647 command,
2648 ..
2649 } => {
2650 assert_eq!(cluster, 0x0030);
2651 assert_eq!(command, 0x00);
2652 assert!(
2655 payload.windows(3).any(|w| w == [0x24, 0x00, 0x78]),
2656 "ArmFailSafe payload should carry expiry=120: {payload:02x?}",
2657 );
2658 }
2659 other => panic!("expected Invoke, got {other:?}"),
2660 }
2661 }
2662
2663 #[test]
2664 fn failsafe_expiry_capped_at_max_cumulative_failsafe_seconds() {
2665 let mut sm = Commissioner::new(sample_valid_config()).expect("valid config");
2666 let _initial = sm.poll().expect("initial poll");
2667 let response = vec![
2671 0x15, 0x25, 0x00, 0x78, 0x00, 0x25, 0x01, 0x5A, 0x00, 0x18,
2675 ];
2676 sm.on_response(Expectation::CommissioningInfo, &response)
2677 .expect("commissioning info accepted");
2678 let action = sm.poll().expect("arm-failsafe poll");
2679 match action {
2680 Action::Invoke { payload, .. } => {
2681 assert!(
2682 payload.windows(3).any(|w| w == [0x24, 0x00, 0x5A]),
2683 "ArmFailSafe expiry must be capped to 90: {payload:02x?}",
2684 );
2685 assert!(
2686 !payload.windows(3).any(|w| w == [0x24, 0x00, 0x78]),
2687 "the uncapped 120 must NOT be sent: {payload:02x?}",
2688 );
2689 }
2690 other => panic!("expected Invoke, got {other:?}"),
2691 }
2692 }
2693
2694 #[test]
2695 fn failsafe_expiry_falls_back_to_60_on_empty_basic_commissioning_info() {
2696 let mut sm = Commissioner::new(sample_valid_config()).expect("valid config");
2697 let _initial = sm.poll().expect("initial poll");
2698 sm.on_response(Expectation::CommissioningInfo, &[0x15, 0x18])
2702 .expect("empty struct accepted");
2703 let action = sm.poll().expect("arm-failsafe poll");
2704 if let Action::Invoke { payload, .. } = action {
2705 assert!(
2707 payload.windows(3).any(|w| w == [0x24, 0x00, 0x3C]),
2708 "ArmFailSafe payload should carry expiry=60 fallback: {payload:02x?}",
2709 );
2710 }
2711 }
2712
2713 #[test]
2716 fn breadcrumb_increases_across_commands() {
2717 fn extract_uint_at_tag(payload: &[u8], tag_num: u8) -> Option<u64> {
2718 use matter_codec::{Element, Tag, TlvReader, Value};
2719 let mut reader = TlvReader::new(payload);
2720 while let Ok(Some(elem)) = reader.next() {
2721 if let Element::Scalar {
2722 tag: Tag::Context(t),
2723 value: Value::Uint(v),
2724 } = elem
2725 {
2726 if t == tag_num {
2727 return Some(v);
2728 }
2729 }
2730 }
2731 None
2732 }
2733
2734 let mut sm = Commissioner::new(sample_valid_config()).expect("valid config");
2735 let mut breadcrumbs: Vec<u64> = Vec::new();
2736
2737 let _ = sm.poll().expect("read commissioning info");
2739 sm.on_response(Expectation::CommissioningInfo, &[0x15, 0x18])
2740 .expect("info accepted");
2741
2742 let action = sm.poll().expect("arm-failsafe");
2744 if let Action::Invoke { payload, .. } = action {
2745 if let Some(b) = extract_uint_at_tag(&payload, 1) {
2746 breadcrumbs.push(b);
2747 }
2748 }
2749 sm.on_response(
2750 Expectation::ArmFailsafeResponse,
2751 &[0x15, 0x24, 0x00, 0x00, 0x18],
2752 )
2753 .expect("arm-failsafe ok");
2754
2755 let action = sm.poll().expect("set-regulatory");
2757 if let Action::Invoke { payload, .. } = action {
2758 if let Some(b) = extract_uint_at_tag(&payload, 2) {
2759 breadcrumbs.push(b);
2760 }
2761 }
2762
2763 assert_eq!(
2764 breadcrumbs.len(),
2765 2,
2766 "should have extracted exactly two breadcrumbs, got {breadcrumbs:?}",
2767 );
2768 assert!(
2769 breadcrumbs[0] < breadcrumbs[1],
2770 "breadcrumbs should be strictly increasing: {breadcrumbs:?}",
2771 );
2772 }
2773}