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 pub(crate) fn set_connect_max_time_seconds(&mut self, seconds: u16) {
422 self.connect_max_time_seconds = seconds;
423 }
424
425 #[must_use]
435 pub(crate) fn connect_max_time_seconds(&self) -> u16 {
436 self.connect_max_time_seconds
437 }
438
439 fn arm_failsafe_action(&mut self) -> Action {
447 use crate::clusters::general_commissioning as gc;
448 use crate::state_machine::action::SessionContext;
449 let expiry_seconds = if self.stage == Stage::FailsafeBeforeNetworkEnable {
450 self.network_enable_failsafe_seconds()
451 } else {
452 self.failsafe_expiry_seconds
453 };
454 let breadcrumb = self.next_breadcrumb();
455 let payload = gc::encode_arm_fail_safe(expiry_seconds, breadcrumb);
456 self.awaiting = Some(Expectation::ArmFailsafeResponse);
457 Action::Invoke {
458 session: SessionContext::Pase,
459 endpoint: 0,
460 cluster: gc::CLUSTER_ID,
461 command: gc::command_id::ARM_FAIL_SAFE,
462 payload,
463 expect: Expectation::ArmFailsafeResponse,
464 }
465 }
466
467 #[allow(clippy::too_many_lines)]
479 fn dispatch_stage(&mut self) -> Result<Action, CommissioningError> {
480 use crate::clusters::general_commissioning as gc;
481 use crate::state_machine::action::SessionContext;
482 match self.stage {
483 Stage::SecurePairing => {
484 self.stage = Stage::ReadCommissioningInfo;
486 self.dispatch_stage()
487 }
488 Stage::ReadCommissioningInfo => {
489 self.awaiting = Some(Expectation::CommissioningInfo);
490 Ok(Action::ReadAttribute {
491 session: SessionContext::Pase,
492 endpoint: 0,
493 cluster: gc::CLUSTER_ID,
494 attributes: &[
495 0x0000, 0x0001, 0x0002, 0x0004, ],
502 expect: Expectation::CommissioningInfo,
503 })
504 }
505 Stage::ArmFailsafe | Stage::FailsafeBeforeNetworkEnable => {
506 Ok(self.arm_failsafe_action())
507 }
508 Stage::ConfigRegulatory => {
509 let breadcrumb = self.next_breadcrumb();
510 let payload = gc::encode_set_regulatory_config(
517 gc::RegulatoryLocation::Indoor,
518 "XX",
519 breadcrumb,
520 );
521 self.awaiting = Some(Expectation::SetRegulatoryConfigResponse);
522 Ok(Action::Invoke {
523 session: SessionContext::Pase,
524 endpoint: 0,
525 cluster: gc::CLUSTER_ID,
526 command: gc::command_id::SET_REGULATORY_CONFIG,
527 payload,
528 expect: Expectation::SetRegulatoryConfigResponse,
529 })
530 }
531 Stage::SendPaiCertRequest => {
532 use crate::noc::{encode_certificate_chain_request, CertChainType};
533 let payload = encode_certificate_chain_request(CertChainType::Pai);
534 self.awaiting = Some(Expectation::PaiCertChainResponse);
535 Ok(Action::Invoke {
536 session: SessionContext::Pase,
537 endpoint: 0,
538 cluster: 0x003E,
539 command: 0x02,
540 payload,
541 expect: Expectation::PaiCertChainResponse,
542 })
543 }
544 Stage::SendDacCertRequest => {
545 use crate::noc::{encode_certificate_chain_request, CertChainType};
546 let payload = encode_certificate_chain_request(CertChainType::Dac);
547 self.awaiting = Some(Expectation::DacCertChainResponse);
548 Ok(Action::Invoke {
549 session: SessionContext::Pase,
550 endpoint: 0,
551 cluster: 0x003E,
552 command: 0x02,
553 payload,
554 expect: Expectation::DacCertChainResponse,
555 })
556 }
557 Stage::SendAttestationRequest => {
558 use crate::noc::encode_attestation_request;
559 let mut nonce = [0u8; 32];
560 self.rng
561 .fill(&mut nonce)
562 .map_err(CommissioningError::from)?;
563 let payload = encode_attestation_request(&nonce);
564 self.attestation_nonce = Some(nonce);
565 self.awaiting = Some(Expectation::AttestationResponse);
566 Ok(Action::Invoke {
567 session: SessionContext::Pase,
568 endpoint: 0,
569 cluster: 0x003E,
570 command: 0x00,
571 payload,
572 expect: Expectation::AttestationResponse,
573 })
574 }
575 Stage::AttestationVerification => {
576 match self.run_attestation_verification() {
577 Ok(()) => {
578 self.advance(Stage::SendOpCertSigningRequest);
579 self.dispatch_stage()
580 }
581 Err(err) => {
582 self.last_failure = Some(err.to_string());
586 self.stage = Stage::Failed;
587 self.awaiting = None;
588 self.pending_action = None;
589 Err(err)
590 }
591 }
592 }
593 Stage::SendOpCertSigningRequest => {
594 use crate::noc::encode_csr_request;
595 let mut nonce = [0u8; 32];
596 self.rng
597 .fill(&mut nonce)
598 .map_err(CommissioningError::from)?;
599 let payload = encode_csr_request(&nonce, false);
602 self.csr_nonce = Some(nonce);
603 self.awaiting = Some(Expectation::CsrResponse);
604 Ok(Action::Invoke {
605 session: SessionContext::Pase,
606 endpoint: 0,
607 cluster: 0x003E,
608 command: 0x04,
609 payload,
610 expect: Expectation::CsrResponse,
611 })
612 }
613 Stage::ValidateCsr => {
614 self.run_validate_csr()?;
616 self.advance(Stage::GenerateNocChain);
617 self.dispatch_stage()
618 }
619 Stage::GenerateNocChain => {
620 self.run_generate_noc_chain()?;
622 self.advance(Stage::SendTrustedRootCert);
623 self.dispatch_stage()
624 }
625 Stage::SendTrustedRootCert => {
626 use crate::noc::encode_add_trusted_root;
627 let rcac_tlv =
634 self.fabric.root_cert.to_tlv().map_err(|e| {
635 CommissioningError::from(crate::noc::NocError::CertBuild(e))
636 })?;
637 #[cfg(feature = "tracing")]
638 tracing::debug!(
639 rcac_tlv = %crate::hexdump::hex(&rcac_tlv),
640 "sending AddTrustedRootCertificate"
641 );
642 let payload = encode_add_trusted_root(&rcac_tlv);
643 self.awaiting = Some(Expectation::AddTrustedRootResponse);
644 Ok(Action::Invoke {
645 session: SessionContext::Pase,
646 endpoint: 0,
647 cluster: 0x003E,
648 command: 0x0B,
649 payload,
650 expect: Expectation::AddTrustedRootResponse,
651 })
652 }
653 Stage::SendNoc => {
654 use crate::noc::encode_add_noc;
655 let noc = self
656 .issued_noc
657 .as_ref()
658 .ok_or(CommissioningError::OutOfOrderResponse(self.stage))?;
659 let noc_tlv = noc
660 .to_tlv()
661 .map_err(|e| CommissioningError::from(crate::noc::NocError::CertBuild(e)))?;
662 let icac_tlv = match self.fabric.icac_cert.as_ref() {
672 Some(icac) => Some(icac.to_tlv().map_err(|e| {
673 CommissioningError::from(crate::noc::NocError::CertBuild(e))
674 })?),
675 None => None,
676 };
677 let payload = encode_add_noc(
678 &noc_tlv,
679 icac_tlv.as_deref(),
680 &self.ipk_epoch_key,
681 self.case_admin_subject,
682 self.admin_vendor_id,
683 );
684 self.awaiting = Some(Expectation::NocResponse);
685 Ok(Action::Invoke {
686 session: SessionContext::Pase,
687 endpoint: 0,
688 cluster: 0x003E,
689 command: 0x06,
690 payload,
691 expect: Expectation::NocResponse,
692 })
693 }
694 Stage::ReadNetworkCommissioningInfo => {
695 self.awaiting = Some(Expectation::NetworkCommissioningInfo);
696 Ok(Action::ReadAttribute {
697 session: SessionContext::Pase,
698 endpoint: 0,
699 cluster: crate::clusters::network_commissioning::CLUSTER_ID,
700 attributes: &[
701 crate::clusters::network_commissioning::attribute_id::FEATURE_MAP,
702 crate::clusters::network_commissioning::attribute_id::CONNECT_MAX_TIME_SECONDS,
706 ],
707 expect: Expectation::NetworkCommissioningInfo,
708 })
709 }
710 Stage::NetworkSetup => {
711 use crate::clusters::network_commissioning as nc;
712 let breadcrumb = self.next_breadcrumb();
720 let (command, payload) = match &self.network {
721 NetworkCredentials::WiFi(creds) => (
722 nc::command_id::ADD_OR_UPDATE_WIFI_NETWORK,
723 nc::encode_add_or_update_wifi_network(
724 &creds.ssid,
725 &creds.credentials,
726 breadcrumb,
727 ),
728 ),
729 NetworkCredentials::Thread(dataset) => (
730 nc::command_id::ADD_OR_UPDATE_THREAD_NETWORK,
731 nc::encode_add_or_update_thread_network(dataset.as_bytes(), breadcrumb),
732 ),
733 NetworkCredentials::AlreadyOnNetwork => {
734 return Err(CommissioningError::OutOfOrderResponse(self.stage));
735 }
736 };
737 self.awaiting = Some(Expectation::NetworkConfigResponse);
738 Ok(Action::Invoke {
739 session: SessionContext::Pase,
740 endpoint: 0,
741 cluster: nc::CLUSTER_ID,
742 command,
743 payload,
744 expect: Expectation::NetworkConfigResponse,
745 })
746 }
747 Stage::NetworkEnable => {
748 use crate::clusters::network_commissioning as nc;
749 let breadcrumb = self.next_breadcrumb();
752 let payload = match &self.network {
753 NetworkCredentials::WiFi(creds) => {
754 nc::encode_connect_network(&creds.ssid, breadcrumb)
755 }
756 NetworkCredentials::Thread(dataset) => {
757 nc::encode_connect_network(&dataset.ext_pan_id(), breadcrumb)
758 }
759 NetworkCredentials::AlreadyOnNetwork => {
760 return Err(CommissioningError::OutOfOrderResponse(self.stage));
761 }
762 };
763 self.awaiting = Some(Expectation::ConnectNetworkResponse);
764 Ok(Action::Invoke {
765 session: SessionContext::Pase,
766 endpoint: 0,
767 cluster: nc::CLUSTER_ID,
768 command: nc::command_id::CONNECT_NETWORK,
769 payload,
770 expect: Expectation::ConnectNetworkResponse,
771 })
772 }
773 Stage::EvictPreviousCaseSessions => {
774 self.advance(Stage::FindOperationalForComplete);
778 self.dispatch_stage()
779 }
780 Stage::FindOperationalForComplete => {
781 self.awaiting_case_session = true;
782 Ok(Action::EstablishCase {
783 fabric_id: self.fabric.fabric_id,
784 peer_node_id: self.assigned_node_id,
785 })
786 }
787 Stage::SendComplete => {
788 let payload = gc::encode_commissioning_complete();
789 self.awaiting = Some(Expectation::CommissioningCompleteResponse);
790 Ok(Action::Invoke {
791 session: SessionContext::Case,
792 endpoint: 0,
793 cluster: gc::CLUSTER_ID,
794 command: gc::command_id::COMMISSIONING_COMPLETE,
795 payload,
796 expect: Expectation::CommissioningCompleteResponse,
797 })
798 }
799 Stage::Cleanup => {
800 let public_key = self
801 .issued_noc_public_key
802 .ok_or(CommissioningError::OutOfOrderResponse(self.stage))?;
803 Ok(Action::Done(crate::state_machine::CommissionedFabric {
804 fabric: self.fabric.clone(),
805 peer_node_id: self.assigned_node_id,
806 peer_root_public_key: public_key,
807 terminated_at: Stage::Cleanup,
808 }))
809 }
810 Stage::Failed => {
811 self.awaiting = None;
814 let reason = self
815 .last_failure
816 .clone()
817 .unwrap_or_else(|| "commissioning aborted".to_string());
818 Ok(Action::Abort {
819 send_disarm_failsafe: true,
820 reason,
821 })
822 } }
826 }
827
828 #[cfg_attr(feature = "tracing", instrument(skip(self, payload), fields(stage = ?self.stage, expectation = ?expect)))]
850 pub fn on_response(
851 &mut self,
852 expect: Expectation,
853 payload: &[u8],
854 ) -> Result<(), CommissioningError> {
855 if expect == Expectation::CaseFailed {
856 if !self.awaiting_case_session {
861 return Err(CommissioningError::OutOfOrderResponse(self.stage));
862 }
863 self.awaiting_case_session = false;
864 self.stage = Stage::Failed;
865 self.awaiting = None;
866 self.pending_action = None;
867 self.last_failure = Some(CommissioningError::CaseEstablishmentFailed.to_string());
868 return Err(CommissioningError::CaseEstablishmentFailed);
869 }
870 let Some(awaiting) = self.awaiting else {
871 return Err(CommissioningError::OutOfOrderResponse(self.stage));
872 };
873 if awaiting != expect {
874 return Err(CommissioningError::UnexpectedResponseKind {
875 expected: awaiting,
876 got: expect,
877 });
878 }
879 match self.handle_response(expect, payload) {
880 Ok(()) => Ok(()),
881 Err(err) => {
882 self.last_failure = Some(err.to_string());
883 self.stage = Stage::Failed;
884 self.awaiting = None;
885 self.pending_action = None;
886 Err(err)
887 }
888 }
889 }
890
891 #[cfg_attr(feature = "tracing", instrument(skip(self)))]
903 pub fn on_case_established(&mut self) -> Result<(), CommissioningError> {
904 if !self.awaiting_case_session {
905 return Err(CommissioningError::OutOfOrderResponse(self.stage));
906 }
907 self.awaiting_case_session = false;
908 self.advance(Stage::SendComplete);
909 Ok(())
910 }
911
912 #[allow(clippy::too_many_lines)]
913 fn handle_response(
914 &mut self,
915 expect: Expectation,
916 payload: &[u8],
917 ) -> Result<(), CommissioningError> {
918 use crate::clusters::general_commissioning as gc;
919 match expect {
920 Expectation::CommissioningInfo => {
921 Self::assert_tlv_well_formed(self.stage, payload)?;
922 if let Some(info) = gc::decode_basic_commissioning_info(payload) {
926 if info.failsafe_expiry_length_seconds > 0 {
927 self.failsafe_expiry_seconds = info.failsafe_expiry_length_seconds;
928 }
929 if info.max_cumulative_failsafe_seconds > 0 {
934 self.failsafe_expiry_seconds = self
935 .failsafe_expiry_seconds
936 .min(info.max_cumulative_failsafe_seconds);
937 }
938 }
939 self.advance(Stage::ArmFailsafe);
940 Ok(())
941 }
942 Expectation::ArmFailsafeResponse => {
943 let resp = gc::decode_arm_fail_safe_response(payload)?;
944 if resp.error_code != 0 {
945 return Err(CommissioningError::DeviceImStatus {
946 stage: self.stage,
947 im_status: u16::from(resp.error_code),
948 });
949 }
950 let next = match self.stage {
951 Stage::ArmFailsafe => Stage::ConfigRegulatory,
952 Stage::FailsafeBeforeNetworkEnable => Stage::NetworkEnable,
953 other => {
954 return Err(CommissioningError::OutOfOrderResponse(other));
955 }
956 };
957 self.advance(next);
958 Ok(())
959 }
960 Expectation::SetRegulatoryConfigResponse => {
961 let resp = gc::decode_set_regulatory_config_response(payload)?;
962 if resp.error_code != 0 {
963 return Err(CommissioningError::DeviceImStatus {
964 stage: Stage::ConfigRegulatory,
965 im_status: u16::from(resp.error_code),
966 });
967 }
968 self.advance(Stage::SendPaiCertRequest);
969 Ok(())
970 }
971 Expectation::PaiCertChainResponse => {
972 let resp = crate::noc::decode_certificate_chain_response(payload)?;
973 self.pai_der = Some(resp.certificate);
974 self.advance(Stage::SendDacCertRequest);
975 Ok(())
976 }
977 Expectation::DacCertChainResponse => {
978 let resp = crate::noc::decode_certificate_chain_response(payload)?;
979 self.dac_der = Some(resp.certificate);
980 self.advance(Stage::SendAttestationRequest);
981 Ok(())
982 }
983 Expectation::AttestationResponse => {
984 let resp = crate::noc::decode_attestation_response(payload)?;
985 self.attestation_response = Some(resp);
986 self.advance(Stage::AttestationVerification);
987 Ok(())
988 }
989 Expectation::CsrResponse => {
990 let resp = crate::noc::decode_csr_response(payload)?;
991 self.csr_response = Some(resp);
992 self.advance(Stage::ValidateCsr);
993 Ok(())
994 }
995 Expectation::AddTrustedRootResponse => {
996 if payload.first() != Some(&0u8) {
1001 return Err(CommissioningError::DeviceImStatus {
1002 stage: Stage::SendTrustedRootCert,
1003 im_status: u16::from(payload.first().copied().unwrap_or(0xFF)),
1004 });
1005 }
1006 self.advance(Stage::SendNoc);
1007 Ok(())
1008 }
1009 Expectation::NocResponse => {
1010 let resp = crate::noc::decode_noc_response(payload)?;
1011 if resp.status != 0 {
1012 return Err(CommissioningError::DeviceImStatus {
1013 stage: Stage::SendNoc,
1014 im_status: u16::from(resp.status),
1015 });
1016 }
1017 self.advance(Stage::ReadNetworkCommissioningInfo);
1018 Ok(())
1019 }
1020 Expectation::NetworkCommissioningInfo => {
1021 use crate::clusters::network_commissioning as nc;
1022 use crate::state_machine::NetworkKind;
1023 let features = nc::decode_feature_map(payload)?;
1024 if features.is_empty() {
1029 return Err(CommissioningError::MalformedResponse(
1030 Stage::ReadNetworkCommissioningInfo,
1031 ));
1032 }
1033 match &self.network {
1041 NetworkCredentials::WiFi(_) => {
1042 if !features.contains(nc::NetworkCommissioningFeature::WIFI) {
1043 return Err(CommissioningError::NetworkFeatureUnsupported {
1044 needed: NetworkKind::WiFi,
1045 });
1046 }
1047 self.advance(Stage::NetworkSetup);
1048 }
1049 NetworkCredentials::Thread(_) => {
1050 if !features.contains(nc::NetworkCommissioningFeature::THREAD) {
1051 return Err(CommissioningError::NetworkFeatureUnsupported {
1052 needed: NetworkKind::Thread,
1053 });
1054 }
1055 self.advance(Stage::NetworkSetup);
1056 }
1057 NetworkCredentials::AlreadyOnNetwork => {
1058 self.advance(Stage::EvictPreviousCaseSessions);
1067 }
1068 }
1069 Ok(())
1070 }
1071 Expectation::NetworkConfigResponse => {
1072 use crate::clusters::network_commissioning as nc;
1073 let resp = nc::decode_network_config_response(Stage::NetworkSetup, payload)?;
1074 if resp.networking_status != 0 {
1075 return Err(CommissioningError::NetworkRejected {
1076 stage: Stage::NetworkSetup,
1077 networking_status: resp.networking_status,
1078 debug_text: resp.debug_text,
1079 remediation_hint: nc::remediation_for(resp.networking_status),
1080 });
1081 }
1082 self.advance(Stage::FailsafeBeforeNetworkEnable);
1083 Ok(())
1084 }
1085 Expectation::ConnectNetworkResponse => {
1086 use crate::clusters::network_commissioning as nc;
1087 let resp = nc::decode_connect_network_response(Stage::NetworkEnable, payload)?;
1088 if resp.networking_status != 0 {
1089 return Err(CommissioningError::NetworkRejected {
1090 stage: Stage::NetworkEnable,
1091 networking_status: resp.networking_status,
1092 debug_text: resp.debug_text,
1093 remediation_hint: nc::remediation_for(resp.networking_status),
1094 });
1095 }
1096 self.advance(Stage::EvictPreviousCaseSessions);
1097 Ok(())
1098 }
1099 Expectation::CommissioningCompleteResponse => {
1100 let (error_code, _debug) =
1101 gc::decode_commissioning_error_response(Stage::SendComplete, payload)?;
1102 if error_code != 0 {
1103 return Err(CommissioningError::DeviceImStatus {
1104 stage: Stage::SendComplete,
1105 im_status: u16::from(error_code),
1106 });
1107 }
1108 self.advance(Stage::Cleanup);
1109 Ok(())
1110 }
1111 _ => Err(CommissioningError::OutOfOrderResponse(self.stage)),
1114 }
1115 }
1116
1117 fn next_breadcrumb(&mut self) -> u64 {
1118 let b = self.breadcrumb_counter;
1119 self.breadcrumb_counter = b.saturating_add(1);
1120 b
1121 }
1122
1123 fn advance(&mut self, next: Stage) {
1124 self.stage = next;
1125 self.awaiting = None;
1126 self.pending_action = None;
1127 }
1128
1129 fn run_attestation_verification(&mut self) -> Result<(), CommissioningError> {
1149 use crate::attestation::profile::{verify_attestation_cert_format, CertRole};
1150 use crate::attestation::{
1151 extract_attestation_elements_fields, verify_attestation_response, verify_chain,
1152 AttestationError, Dac, Pai,
1153 };
1154
1155 let pai_der = self
1156 .pai_der
1157 .as_ref()
1158 .ok_or(CommissioningError::OutOfOrderResponse(self.stage))?;
1159 let dac_der = self
1160 .dac_der
1161 .as_ref()
1162 .ok_or(CommissioningError::OutOfOrderResponse(self.stage))?;
1163 let response = self
1164 .attestation_response
1165 .as_ref()
1166 .ok_or(CommissioningError::OutOfOrderResponse(self.stage))?;
1167 let expected_nonce = self
1168 .attestation_nonce
1169 .ok_or(CommissioningError::OutOfOrderResponse(self.stage))?;
1170
1171 let pai = Pai::from_der(pai_der)?;
1173 let dac = Dac::from_der(dac_der)?;
1174 #[cfg(feature = "tracing")]
1175 tracing::debug!(
1176 dac_der = %crate::hexdump::hex(dac_der),
1177 pai_der = %crate::hexdump::hex(pai_der),
1178 "verifying attestation chain"
1179 );
1180
1181 verify_attestation_cert_format(pai.der(), CertRole::Pai)?;
1189 verify_attestation_cert_format(dac.der(), CertRole::Dac)?;
1190
1191 let chain = verify_chain(&dac, &pai, &self.paa_trust_store, self.now)?;
1198
1199 verify_attestation_response(response, &self.pase_attestation_challenge, dac.public_key())?;
1201
1202 let fields = extract_attestation_elements_fields(&response.attestation_elements)?;
1205 if fields.attestation_nonce != expected_nonce {
1206 return Err(CommissioningError::Attestation(
1207 AttestationError::ResponseElementsMalformed,
1208 ));
1209 }
1210
1211 #[cfg(feature = "tracing")]
1215 tracing::debug!(
1216 cd_cms = %crate::hexdump::hex(&fields.certification_declaration),
1217 "verifying certification declaration"
1218 );
1219 crate::attestation::verify_certification_declaration_with_paa(
1220 &fields.certification_declaration,
1221 chain.vendor_id,
1222 chain.product_id,
1223 &self.cd_signing_roots,
1224 chain.paa_skid.as_deref(),
1225 )?;
1226 Ok(())
1227 }
1228
1229 fn run_validate_csr(&mut self) -> Result<(), CommissioningError> {
1240 use crate::attestation::Dac;
1241 use crate::noc::verify_csr_response;
1242
1243 let resp = self
1244 .csr_response
1245 .as_ref()
1246 .ok_or(CommissioningError::OutOfOrderResponse(self.stage))?;
1247 let dac_der = self
1248 .dac_der
1249 .as_ref()
1250 .ok_or(CommissioningError::OutOfOrderResponse(self.stage))?;
1251 let csr_nonce = self
1252 .csr_nonce
1253 .ok_or(CommissioningError::OutOfOrderResponse(self.stage))?;
1254
1255 let dac = Dac::from_der(dac_der)?;
1256 let verified = verify_csr_response(
1257 &resp.nocsr_elements,
1258 &resp.attestation_signature,
1259 &csr_nonce,
1260 &self.pase_attestation_challenge,
1261 dac.public_key(),
1262 )?;
1263 self.verified_csr = Some(verified);
1264 Ok(())
1265 }
1266
1267 fn run_generate_noc_chain(&mut self) -> Result<(), CommissioningError> {
1281 use crate::noc::issue_noc;
1282
1283 let verified = self
1284 .verified_csr
1285 .as_ref()
1286 .ok_or(CommissioningError::OutOfOrderResponse(self.stage))?;
1287 let noc = issue_noc(
1288 &self.fabric,
1289 verified,
1290 self.assigned_node_id,
1291 &[],
1292 (self.now, MatterTime::NO_EXPIRY),
1293 self.rng.as_ref(),
1294 )?;
1295 self.issued_noc_public_key = Some(*verified.public_key.as_bytes());
1300 self.issued_noc = Some(noc);
1301 Ok(())
1302 }
1303
1304 fn assert_tlv_well_formed(stage: Stage, payload: &[u8]) -> Result<(), CommissioningError> {
1305 use matter_codec::{ContainerKind, Element, Tag, TlvReader};
1306 let mut reader = TlvReader::new(payload);
1307 match reader
1308 .next()
1309 .map_err(|_| CommissioningError::MalformedResponse(stage))?
1310 {
1311 Some(Element::ContainerStart {
1312 tag: Tag::Anonymous,
1313 kind: ContainerKind::Structure,
1314 }) => {}
1315 _ => return Err(CommissioningError::MalformedResponse(stage)),
1316 }
1317 loop {
1319 match reader
1320 .next()
1321 .map_err(|_| CommissioningError::MalformedResponse(stage))?
1322 {
1323 None => return Err(CommissioningError::MalformedResponse(stage)),
1324 Some(Element::ContainerEnd) => return Ok(()),
1325 Some(_) => {}
1326 }
1327 }
1328 }
1329}
1330
1331#[cfg(feature = "__test_shortcuts")]
1336#[derive(Default, Debug, Clone, Copy)]
1337pub struct TestStateSeeds {
1338 pub synthetic_noc_pubkey: Option<[u8; 65]>,
1343}
1344
1345#[cfg(test)]
1346mod tests {
1347 #![allow(clippy::unwrap_used, clippy::expect_used)]
1349
1350 use super::*;
1351 use crate::attestation::CdSigningRoots;
1352 use crate::noc::{FabricRecord, NocRng, SystemNocRng};
1353 use crate::setup::{
1354 CommissioningFlow, DiscoveryCapabilities, Discriminator, Passcode, SetupPayload,
1355 };
1356 use crate::state_machine::{Action, Expectation};
1357 use crate::PaaTrustStore;
1358 use matter_cert::time::MatterTime;
1359 use matter_crypto::{RingSigner, Signer};
1360 use std::sync::Arc;
1361
1362 fn make_setup_payload() -> SetupPayload {
1363 SetupPayload {
1364 version: 0,
1365 vendor_id: Some(0xFFF1),
1366 product_id: Some(0x8000),
1367 commissioning_flow: CommissioningFlow::Standard,
1368 discovery_capabilities: DiscoveryCapabilities::ON_NETWORK,
1369 discriminator: Discriminator::new(0x0F00).expect("valid discriminator"),
1370 passcode: Passcode::new(20_202_021).expect("valid passcode"),
1371 }
1372 }
1373
1374 fn make_fabric_record() -> FabricRecord {
1375 let (signer, _pkcs8) = RingSigner::generate().unwrap();
1376 let signer: Arc<dyn Signer> = Arc::new(signer);
1377 FabricRecord::new_root_only(
1378 0x0000_0000_0000_0001,
1379 signer,
1380 MatterTime::from_unix_secs(1_704_067_200),
1381 MatterTime::from_unix_secs(1_735_689_600),
1382 42,
1383 &SystemNocRng,
1384 )
1385 .unwrap()
1386 }
1387
1388 fn make_fabric_record_with_icac() -> FabricRecord {
1393 let mut fabric = make_fabric_record();
1394 let (icac_signer, _pkcs8) = RingSigner::generate().unwrap();
1395 let icac_public_key =
1396 matter_cert::PublicKey::from_slice(icac_signer.public_key().as_bytes()).unwrap();
1397 let icac = crate::noc::issue_icac(
1398 &fabric,
1399 7,
1400 &icac_public_key,
1401 (
1402 MatterTime::from_unix_secs(1_704_067_200),
1403 MatterTime::NO_EXPIRY,
1404 ),
1405 &SystemNocRng,
1406 )
1407 .unwrap();
1408 fabric.icac_signer = Some(Arc::new(icac_signer));
1409 fabric.icac_cert = Some(icac);
1410 fabric
1411 }
1412
1413 fn add_noc_payload_has_icac(payload: &[u8]) -> bool {
1418 use matter_codec::{Element, Tag, TlvReader};
1419 let mut r = TlvReader::new(payload);
1420 match r.next().unwrap() {
1421 Some(Element::ContainerStart { .. }) => {}
1422 other => panic!("expected an AddNOC struct, got {other:?}"),
1423 }
1424 while let Some(el) = r.next().unwrap() {
1425 match el {
1426 Element::ContainerEnd => break,
1427 Element::Scalar {
1428 tag: Tag::Context(1),
1429 ..
1430 } => return true,
1431 _ => {}
1432 }
1433 }
1434 false
1435 }
1436
1437 #[test]
1438 fn send_noc_includes_icac_for_three_tier_fabric() {
1439 let fabric = make_fabric_record_with_icac();
1440 let setup = make_setup_payload();
1441 let paa = PaaTrustStore::with_example_device_roots();
1442 let cd = CdSigningRoots::with_example_device_roots();
1443 let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
1444 let mut sm =
1445 Commissioner::new(base_config(&fabric, &setup, &paa, &cd, rng)).expect("valid config");
1446 sm.issued_noc = Some(fabric.root_cert.clone());
1450 sm.stage = Stage::SendNoc;
1451 match sm.dispatch_stage().expect("SendNoc dispatch") {
1452 Action::Invoke {
1453 cluster,
1454 command,
1455 payload,
1456 ..
1457 } => {
1458 assert_eq!(cluster, 0x003E);
1459 assert_eq!(command, 0x06);
1460 assert!(
1461 add_noc_payload_has_icac(&payload),
1462 "AddNOC must carry the ICAC (ctx1) when the fabric runs a 3-tier chain"
1463 );
1464 }
1465 other => panic!("expected Invoke, got {other:?}"),
1466 }
1467 }
1468
1469 #[test]
1470 fn send_noc_omits_icac_for_flat_fabric() {
1471 let fabric = make_fabric_record();
1472 let setup = make_setup_payload();
1473 let paa = PaaTrustStore::with_example_device_roots();
1474 let cd = CdSigningRoots::with_example_device_roots();
1475 let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
1476 let mut sm =
1477 Commissioner::new(base_config(&fabric, &setup, &paa, &cd, rng)).expect("valid config");
1478 sm.issued_noc = Some(fabric.root_cert.clone());
1479 sm.stage = Stage::SendNoc;
1480 match sm.dispatch_stage().expect("SendNoc dispatch") {
1481 Action::Invoke { payload, .. } => {
1482 assert!(
1483 !add_noc_payload_has_icac(&payload),
1484 "flat RCAC->NOC AddNOC must omit the ICAC field"
1485 );
1486 }
1487 other => panic!("expected Invoke, got {other:?}"),
1488 }
1489 }
1490
1491 fn base_config<'a>(
1492 fabric: &'a FabricRecord,
1493 setup: &'a SetupPayload,
1494 paa: &'a PaaTrustStore,
1495 cd: &'a crate::attestation::CdSigningRoots,
1496 rng: Arc<dyn NocRng>,
1497 ) -> CommissionerConfig<'a> {
1498 CommissionerConfig {
1499 pase_attestation_challenge: [0u8; 16],
1500 fabric,
1501 setup_payload: setup,
1502 paa_trust_store: paa,
1503 cd_signing_roots: cd,
1504 commissioner_node_id: 0x1,
1505 assigned_node_id: 0x2,
1506 ipk_epoch_key: [0x42_u8; 16],
1507 case_admin_subject: 0x1,
1508 admin_vendor_id: 0xFFF1,
1509 now: MatterTime::from_unix_secs(1_704_067_200),
1510 rng,
1511 network: NetworkCredentials::AlreadyOnNetwork,
1512 }
1513 }
1514
1515 #[test]
1516 fn new_rejects_zero_commissioner_node_id() {
1517 let fabric = make_fabric_record();
1518 let setup = make_setup_payload();
1519 let paa = PaaTrustStore::with_example_device_roots();
1520 let cd = CdSigningRoots::with_example_device_roots();
1521 let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
1522 let mut cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1523 cfg.commissioner_node_id = 0;
1524 let Err(err) = Commissioner::new(cfg) else {
1527 panic!("zero commissioner_node_id should fail");
1528 };
1529 assert!(
1530 matches!(err, CommissioningError::InvalidConfig(_)),
1531 "got {err:?}"
1532 );
1533 }
1534
1535 #[test]
1536 fn new_rejects_zero_assigned_node_id() {
1537 let fabric = make_fabric_record();
1538 let setup = make_setup_payload();
1539 let paa = PaaTrustStore::with_example_device_roots();
1540 let cd = CdSigningRoots::with_example_device_roots();
1541 let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
1542 let mut cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1543 cfg.assigned_node_id = 0;
1544 let Err(err) = Commissioner::new(cfg) else {
1545 panic!("zero assigned_node_id should fail");
1546 };
1547 assert!(
1548 matches!(err, CommissioningError::InvalidConfig(_)),
1549 "got {err:?}"
1550 );
1551 }
1552
1553 #[test]
1554 fn new_rejects_equal_commissioner_and_assigned_ids() {
1555 let fabric = make_fabric_record();
1556 let setup = make_setup_payload();
1557 let paa = PaaTrustStore::with_example_device_roots();
1558 let cd = CdSigningRoots::with_example_device_roots();
1559 let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
1560 let mut cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1561 cfg.commissioner_node_id = 0x42;
1562 cfg.assigned_node_id = 0x42;
1563 let Err(err) = Commissioner::new(cfg) else {
1564 panic!("equal IDs should fail");
1565 };
1566 assert!(
1567 matches!(err, CommissioningError::InvalidConfig(_)),
1568 "got {err:?}"
1569 );
1570 }
1571
1572 #[test]
1573 fn new_rejects_zero_ipk_epoch_key() {
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.ipk_epoch_key = [0u8; 16];
1581 let Err(err) = Commissioner::new(cfg) else {
1582 panic!("zero IPK should fail");
1583 };
1584 assert!(
1585 matches!(err, CommissioningError::InvalidConfig(_)),
1586 "got {err:?}"
1587 );
1588 }
1589
1590 #[test]
1591 fn new_returns_secure_pairing_stage() {
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 cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1598 let sm = Commissioner::new(cfg).expect("valid config should construct");
1599 assert_eq!(sm.stage(), Stage::SecurePairing);
1600 }
1601
1602 #[test]
1603 fn poll_from_secure_pairing_emits_read_commissioning_info() {
1604 let fabric = make_fabric_record();
1605 let setup = make_setup_payload();
1606 let paa = PaaTrustStore::with_example_device_roots();
1607 let cd = CdSigningRoots::with_example_device_roots();
1608 let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
1609 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1610 let mut sm = Commissioner::new(cfg).expect("valid config");
1611 let act = sm.poll().expect("poll succeeds");
1612 match act {
1613 Action::ReadAttribute {
1614 session,
1615 endpoint,
1616 cluster,
1617 attributes,
1618 expect,
1619 } => {
1620 assert_eq!(session, crate::state_machine::SessionContext::Pase);
1621 assert_eq!(endpoint, 0);
1622 assert_eq!(cluster, 0x0030);
1623 assert_eq!(expect, Expectation::CommissioningInfo);
1624 assert!(!attributes.is_empty());
1625 }
1626 other => panic!("expected ReadAttribute, got {other:?}"),
1627 }
1628 assert_eq!(sm.stage(), Stage::ReadCommissioningInfo);
1629 }
1630
1631 #[test]
1632 fn poll_is_idempotent_between_responses() {
1633 let fabric = make_fabric_record();
1634 let setup = make_setup_payload();
1635 let paa = PaaTrustStore::with_example_device_roots();
1636 let cd = CdSigningRoots::with_example_device_roots();
1637 let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
1638 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1639 let mut sm = Commissioner::new(cfg).expect("valid config");
1640 let act1 = sm.poll().expect("first poll");
1641 let act2 = sm.poll().expect("second poll");
1642 match (act1, act2) {
1643 (
1644 Action::ReadAttribute {
1645 cluster: c1,
1646 expect: e1,
1647 ..
1648 },
1649 Action::ReadAttribute {
1650 cluster: c2,
1651 expect: e2,
1652 ..
1653 },
1654 ) => {
1655 assert_eq!(c1, c2);
1656 assert_eq!(e1, e2);
1657 }
1658 other => panic!("idempotent poll returned different variants: {other:?}"),
1659 }
1660 }
1661
1662 #[test]
1663 fn full_happy_path_through_config_regulatory_lands_on_send_pai_cert_request() {
1664 let fabric = make_fabric_record();
1665 let setup = make_setup_payload();
1666 let paa = PaaTrustStore::with_example_device_roots();
1667 let cd = CdSigningRoots::with_example_device_roots();
1668 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
1669 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1670 let mut sm = Commissioner::new(cfg).expect("valid config");
1671
1672 let _ = sm.poll().expect("poll #1");
1674 let canned_info = encode_read_commissioning_info_response();
1675 sm.on_response(Expectation::CommissioningInfo, &canned_info)
1676 .expect("commissioning info accepted");
1677 assert_eq!(sm.stage(), Stage::ArmFailsafe);
1678
1679 let _ = sm.poll().expect("poll #2");
1681 sm.on_response(
1682 Expectation::ArmFailsafeResponse,
1683 &[0x15, 0x24, 0x00, 0x00, 0x18],
1684 )
1685 .expect("arm failsafe ok");
1686 assert_eq!(sm.stage(), Stage::ConfigRegulatory);
1687
1688 let _ = sm.poll().expect("poll #3");
1690 sm.on_response(
1691 Expectation::SetRegulatoryConfigResponse,
1692 &[0x15, 0x24, 0x00, 0x00, 0x18],
1693 )
1694 .expect("config regulatory ok");
1695 assert_eq!(sm.stage(), Stage::SendPaiCertRequest);
1696
1697 match sm.poll().expect("poll #4") {
1699 Action::Invoke {
1700 cluster,
1701 command,
1702 expect,
1703 ..
1704 } => {
1705 assert_eq!(cluster, 0x003E);
1706 assert_eq!(command, 0x02);
1707 assert_eq!(expect, Expectation::PaiCertChainResponse);
1708 }
1709 other => panic!("expected Invoke, got {other:?}"),
1710 }
1711 }
1712
1713 #[test]
1714 fn arm_failsafe_busy_response_aborts_with_device_im_status() {
1715 let fabric = make_fabric_record();
1716 let setup = make_setup_payload();
1717 let paa = PaaTrustStore::with_example_device_roots();
1718 let cd = CdSigningRoots::with_example_device_roots();
1719 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
1720 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1721 let mut sm = Commissioner::new(cfg).expect("valid config");
1722 let _ = sm.poll().expect("poll info");
1723 sm.on_response(
1724 Expectation::CommissioningInfo,
1725 &encode_read_commissioning_info_response(),
1726 )
1727 .expect("commissioning info ok");
1728 let _ = sm.poll().expect("poll arm failsafe");
1729 let err = sm
1731 .on_response(
1732 Expectation::ArmFailsafeResponse,
1733 &[0x15, 0x24, 0x00, 0x04, 0x18],
1734 )
1735 .expect_err("busy should fail");
1736 assert!(matches!(
1737 err,
1738 CommissioningError::DeviceImStatus {
1739 stage: Stage::ArmFailsafe,
1740 im_status: 4,
1741 }
1742 ));
1743 assert_eq!(sm.stage(), Stage::Failed);
1744 match sm.poll().expect("abort emission") {
1745 Action::Abort {
1746 send_disarm_failsafe,
1747 reason,
1748 } => {
1749 assert!(send_disarm_failsafe);
1750 assert!(reason.contains("ArmFailsafe"), "reason was {reason}");
1751 assert!(reason.contains("0x4"), "reason was {reason}");
1752 }
1753 other => panic!("expected Abort, got {other:?}"),
1754 }
1755 }
1756
1757 #[test]
1758 fn out_of_order_response_returns_error_without_advancing() {
1759 let fabric = make_fabric_record();
1760 let setup = make_setup_payload();
1761 let paa = PaaTrustStore::with_example_device_roots();
1762 let cd = CdSigningRoots::with_example_device_roots();
1763 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
1764 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1765 let mut sm = Commissioner::new(cfg).expect("valid config");
1766 let err = sm
1768 .on_response(Expectation::ArmFailsafeResponse, &[])
1769 .expect_err("should reject out-of-order");
1770 assert!(matches!(err, CommissioningError::OutOfOrderResponse(_)));
1771 assert_eq!(sm.stage(), Stage::SecurePairing);
1772 }
1773
1774 #[test]
1775 fn wrong_expectation_returns_unexpected_response_kind() {
1776 let fabric = make_fabric_record();
1777 let setup = make_setup_payload();
1778 let paa = PaaTrustStore::with_example_device_roots();
1779 let cd = CdSigningRoots::with_example_device_roots();
1780 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
1781 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1782 let mut sm = Commissioner::new(cfg).expect("valid config");
1783 let _ = sm.poll().expect("poll");
1784 let err = sm
1785 .on_response(Expectation::ArmFailsafeResponse, &[])
1786 .expect_err("wrong kind should fail");
1787 assert!(matches!(
1788 err,
1789 CommissioningError::UnexpectedResponseKind {
1790 expected: Expectation::CommissioningInfo,
1791 got: Expectation::ArmFailsafeResponse,
1792 }
1793 ));
1794 assert_eq!(sm.stage(), Stage::ReadCommissioningInfo);
1796 }
1797
1798 fn encode_read_commissioning_info_response() -> Vec<u8> {
1799 vec![0x15, 0x18]
1802 }
1803
1804 fn drive_to_send_pai_cert_request(sm: &mut Commissioner) {
1807 let _ = sm.poll().expect("poll info");
1808 sm.on_response(Expectation::CommissioningInfo, &[0x15, 0x18])
1809 .expect("info ok");
1810 let _ = sm.poll().expect("poll arm failsafe");
1811 sm.on_response(
1812 Expectation::ArmFailsafeResponse,
1813 &[0x15, 0x24, 0x00, 0x00, 0x18],
1814 )
1815 .expect("arm ok");
1816 let _ = sm.poll().expect("poll config regulatory");
1817 sm.on_response(
1818 Expectation::SetRegulatoryConfigResponse,
1819 &[0x15, 0x24, 0x00, 0x00, 0x18],
1820 )
1821 .expect("regulatory ok");
1822 }
1823
1824 fn synthetic_cert_chain_response(cert: &[u8]) -> Vec<u8> {
1825 use matter_codec::{Tag, TlvWriter};
1826 let mut buf = Vec::new();
1827 let mut w = TlvWriter::new(&mut buf);
1828 w.start_structure(Tag::Anonymous).expect("infallible");
1829 w.put_bytes(Tag::Context(0), cert).expect("infallible");
1830 w.end_container().expect("infallible");
1831 buf
1832 }
1833
1834 fn nonce_from_attestation_invoke(act: &Action) -> [u8; 32] {
1835 match act {
1836 Action::Invoke { payload, .. } => {
1837 use matter_codec::{Element, Tag, TlvReader, Value};
1838 let mut r = TlvReader::new(payload);
1839 let _ = r.next().expect("reader").expect("anon-struct-start");
1840 loop {
1841 match r.next().expect("reader") {
1842 Some(Element::Scalar {
1843 tag: Tag::Context(0),
1844 value: Value::Bytes(b),
1845 }) => {
1846 return b.as_slice().try_into().expect("32 bytes");
1847 }
1848 Some(_) => {}
1849 None => panic!("no nonce found"),
1850 }
1851 }
1852 }
1853 other => panic!("expected Invoke, got {other:?}"),
1854 }
1855 }
1856
1857 #[test]
1858 fn poll_at_send_pai_emits_certificate_chain_request_pai() {
1859 let fabric = make_fabric_record();
1860 let setup = make_setup_payload();
1861 let paa = PaaTrustStore::with_example_device_roots();
1862 let cd = CdSigningRoots::with_example_device_roots();
1863 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
1864 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1865 let mut sm = Commissioner::new(cfg).expect("valid config");
1866 drive_to_send_pai_cert_request(&mut sm);
1867 assert_eq!(sm.stage(), Stage::SendPaiCertRequest);
1868 match sm.poll().expect("poll PAI") {
1869 Action::Invoke {
1870 cluster,
1871 command,
1872 expect,
1873 payload,
1874 ..
1875 } => {
1876 assert_eq!(cluster, 0x003E);
1877 assert_eq!(command, 0x02);
1878 assert_eq!(expect, Expectation::PaiCertChainResponse);
1879 assert_eq!(payload, vec![0x15, 0x24, 0x00, 0x02, 0x18]);
1881 }
1882 other => panic!("expected Invoke, got {other:?}"),
1883 }
1884 }
1885
1886 #[test]
1887 fn poll_at_send_dac_emits_certificate_chain_request_dac() {
1888 let fabric = make_fabric_record();
1889 let setup = make_setup_payload();
1890 let paa = PaaTrustStore::with_example_device_roots();
1891 let cd = CdSigningRoots::with_example_device_roots();
1892 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
1893 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1894 let mut sm = Commissioner::new(cfg).expect("valid config");
1895 drive_to_send_pai_cert_request(&mut sm);
1896 let _ = sm.poll().expect("poll PAI");
1897 let pai_response = synthetic_cert_chain_response(&[0xAA, 0xBB, 0xCC]);
1898 sm.on_response(Expectation::PaiCertChainResponse, &pai_response)
1899 .expect("PAI accepted");
1900 assert_eq!(sm.stage(), Stage::SendDacCertRequest);
1901 match sm.poll().expect("poll DAC") {
1902 Action::Invoke {
1903 cluster,
1904 command,
1905 expect,
1906 payload,
1907 ..
1908 } => {
1909 assert_eq!(cluster, 0x003E);
1910 assert_eq!(command, 0x02);
1911 assert_eq!(expect, Expectation::DacCertChainResponse);
1912 assert_eq!(payload, vec![0x15, 0x24, 0x00, 0x01, 0x18]);
1914 }
1915 other => panic!("expected Invoke, got {other:?}"),
1916 }
1917 }
1918
1919 #[test]
1920 fn send_attestation_request_uses_fresh_random_nonce_each_time() {
1921 let fabric = make_fabric_record();
1922 let setup = make_setup_payload();
1923 let paa = PaaTrustStore::with_example_device_roots();
1924 let cd = CdSigningRoots::with_example_device_roots();
1925
1926 let rng_a: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
1927 let cfg_a = base_config(&fabric, &setup, &paa, &cd, rng_a);
1928 let mut sm_a = Commissioner::new(cfg_a).expect("valid config");
1929 drive_to_send_pai_cert_request(&mut sm_a);
1930 let _ = sm_a.poll().expect("poll PAI a");
1931 let pai_response = synthetic_cert_chain_response(&[0xAA]);
1932 sm_a.on_response(Expectation::PaiCertChainResponse, &pai_response)
1933 .expect("ok");
1934 let _ = sm_a.poll().expect("poll DAC a");
1935 let dac_response = synthetic_cert_chain_response(&[0xBB]);
1936 sm_a.on_response(Expectation::DacCertChainResponse, &dac_response)
1937 .expect("ok");
1938 let nonce_a = nonce_from_attestation_invoke(&sm_a.poll().expect("poll att a"));
1939
1940 let rng_b: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
1941 let cfg_b = base_config(&fabric, &setup, &paa, &cd, rng_b);
1942 let mut sm_b = Commissioner::new(cfg_b).expect("valid config");
1943 drive_to_send_pai_cert_request(&mut sm_b);
1944 let _ = sm_b.poll().expect("poll PAI b");
1945 sm_b.on_response(Expectation::PaiCertChainResponse, &pai_response)
1946 .expect("ok");
1947 let _ = sm_b.poll().expect("poll DAC b");
1948 sm_b.on_response(Expectation::DacCertChainResponse, &dac_response)
1949 .expect("ok");
1950 let nonce_b = nonce_from_attestation_invoke(&sm_b.poll().expect("poll att b"));
1951
1952 assert_ne!(
1953 nonce_a, nonce_b,
1954 "two independent runs should use different random nonces"
1955 );
1956 }
1957
1958 fn nonce_from_csr_invoke(act: &Action) -> [u8; 32] {
1964 match act {
1965 Action::Invoke { payload, .. } => {
1966 use matter_codec::{Element, Tag, TlvReader, Value};
1967 let mut r = TlvReader::new(payload);
1968 let _ = r.next().expect("reader").expect("anon-struct-start");
1969 loop {
1970 match r.next().expect("reader") {
1971 Some(Element::Scalar {
1972 tag: Tag::Context(0),
1973 value: Value::Bytes(b),
1974 }) => {
1975 return b.as_slice().try_into().expect("32 bytes");
1976 }
1977 Some(_) => {}
1978 None => panic!("no nonce found"),
1979 }
1980 }
1981 }
1982 other => panic!("expected Invoke, got {other:?}"),
1983 }
1984 }
1985
1986 #[test]
1993 fn send_op_cert_signing_request_emits_csr_with_random_nonce() {
1994 let fabric = make_fabric_record();
1995 let setup = make_setup_payload();
1996 let paa = PaaTrustStore::with_example_device_roots();
1997 let cd = CdSigningRoots::with_example_device_roots();
1998
1999 let rng_a: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2000 let cfg_a = base_config(&fabric, &setup, &paa, &cd, rng_a);
2001 let mut sm_a = Commissioner::new(cfg_a).expect("valid config");
2002 sm_a.stage = Stage::SendOpCertSigningRequest;
2006 sm_a.dac_der = Some(vec![0xAA, 0xBB]);
2007 let act_a = sm_a.poll().expect("poll csr a");
2008 let nonce_a = nonce_from_csr_invoke(&act_a);
2009
2010 let rng_b: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2011 let cfg_b = base_config(&fabric, &setup, &paa, &cd, rng_b);
2012 let mut sm_b = Commissioner::new(cfg_b).expect("valid config");
2013 sm_b.stage = Stage::SendOpCertSigningRequest;
2014 sm_b.dac_der = Some(vec![0xAA, 0xBB]);
2015 let act_b = sm_b.poll().expect("poll csr b");
2016 let nonce_b = nonce_from_csr_invoke(&act_b);
2017
2018 assert_ne!(
2019 nonce_a, nonce_b,
2020 "two independent runs should use different CSR nonces"
2021 );
2022
2023 match act_a {
2024 Action::Invoke {
2025 cluster,
2026 command,
2027 expect,
2028 ..
2029 } => {
2030 assert_eq!(cluster, 0x003E);
2031 assert_eq!(command, 0x04);
2032 assert_eq!(expect, Expectation::CsrResponse);
2033 }
2034 other => panic!("expected Invoke, got {other:?}"),
2035 }
2036 }
2037
2038 #[test]
2045 fn drive_through_send_noc_with_synthetic_noc_response() {
2046 use matter_cert::{
2047 BasicConstraints, DistinguishedName, DnAttribute, Extensions, MatterCertificate,
2048 PublicKey,
2049 };
2050
2051 let fabric = make_fabric_record();
2052 let setup = make_setup_payload();
2053 let paa = PaaTrustStore::with_example_device_roots();
2054 let cd = CdSigningRoots::with_example_device_roots();
2055 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2056 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2057 let mut sm = Commissioner::new(cfg).expect("valid config");
2058
2059 let mut key_bytes = [0u8; 65];
2065 key_bytes[0] = 0x04;
2066 let synthetic_noc = MatterCertificate::builder()
2067 .serial(vec![1, 2, 3])
2068 .issuer(fabric.root_cert.subject().clone())
2069 .subject(DistinguishedName::new(vec![
2070 DnAttribute::FabricId(fabric.fabric_id),
2071 DnAttribute::NodeId(0x2),
2072 ]))
2073 .validity(
2074 MatterTime::from_unix_secs(1_704_067_200),
2075 MatterTime::NO_EXPIRY,
2076 )
2077 .public_key(PublicKey::new(key_bytes).expect("valid sec1 prefix"))
2078 .extensions(
2079 Extensions::builder()
2080 .basic_constraints(Some(BasicConstraints::new(false, None)))
2081 .build(),
2082 )
2083 .build_unsigned()
2084 .expect("builder")
2085 .assemble([0u8; 64]);
2086
2087 sm.stage = Stage::SendNoc;
2088 sm.issued_noc = Some(synthetic_noc);
2089 sm.issued_noc_public_key = Some(key_bytes);
2090
2091 match sm.poll().expect("poll SendNoc") {
2092 Action::Invoke {
2093 cluster,
2094 command,
2095 expect,
2096 ..
2097 } => {
2098 assert_eq!(cluster, 0x003E);
2099 assert_eq!(command, 0x06);
2100 assert_eq!(expect, Expectation::NocResponse);
2101 }
2102 other => panic!("expected Invoke, got {other:?}"),
2103 }
2104
2105 let mut noc_response = Vec::new();
2107 {
2108 use matter_codec::{Tag, TlvWriter};
2109 let mut w = TlvWriter::new(&mut noc_response);
2110 w.start_structure(Tag::Anonymous).expect("infallible");
2111 w.put_uint(Tag::Context(0), 0).expect("infallible"); w.put_uint(Tag::Context(1), 1).expect("infallible"); w.end_container().expect("infallible");
2114 }
2115 sm.on_response(Expectation::NocResponse, &noc_response)
2116 .expect("NocResponse accepted");
2117 assert_eq!(sm.stage(), Stage::ReadNetworkCommissioningInfo);
2118 }
2119
2120 #[test]
2124 fn send_noc_failure_status_aborts_with_device_im_status() {
2125 use matter_cert::{
2126 BasicConstraints, DistinguishedName, DnAttribute, Extensions, MatterCertificate,
2127 PublicKey,
2128 };
2129
2130 let fabric = make_fabric_record();
2131 let setup = make_setup_payload();
2132 let paa = PaaTrustStore::with_example_device_roots();
2133 let cd = CdSigningRoots::with_example_device_roots();
2134 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2135 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2136 let mut sm = Commissioner::new(cfg).expect("valid config");
2137
2138 let mut key_bytes = [0u8; 65];
2139 key_bytes[0] = 0x04;
2140 let synthetic_noc = MatterCertificate::builder()
2141 .serial(vec![9])
2142 .issuer(fabric.root_cert.subject().clone())
2143 .subject(DistinguishedName::new(vec![
2144 DnAttribute::FabricId(fabric.fabric_id),
2145 DnAttribute::NodeId(0x2),
2146 ]))
2147 .validity(
2148 MatterTime::from_unix_secs(1_704_067_200),
2149 MatterTime::NO_EXPIRY,
2150 )
2151 .public_key(PublicKey::new(key_bytes).expect("valid sec1 prefix"))
2152 .extensions(
2153 Extensions::builder()
2154 .basic_constraints(Some(BasicConstraints::new(false, None)))
2155 .build(),
2156 )
2157 .build_unsigned()
2158 .expect("builder")
2159 .assemble([0u8; 64]);
2160
2161 sm.stage = Stage::SendNoc;
2162 sm.issued_noc = Some(synthetic_noc);
2163 sm.issued_noc_public_key = Some(key_bytes);
2164
2165 let _ = sm.poll().expect("poll SendNoc");
2166
2167 let mut bad_response = Vec::new();
2169 {
2170 use matter_codec::{Tag, TlvWriter};
2171 let mut w = TlvWriter::new(&mut bad_response);
2172 w.start_structure(Tag::Anonymous).expect("infallible");
2173 w.put_uint(Tag::Context(0), 9).expect("infallible");
2174 w.end_container().expect("infallible");
2175 }
2176 let err = sm
2177 .on_response(Expectation::NocResponse, &bad_response)
2178 .expect_err("non-zero NOC status should fail");
2179 assert!(matches!(
2180 err,
2181 CommissioningError::DeviceImStatus {
2182 stage: Stage::SendNoc,
2183 im_status: 9,
2184 }
2185 ));
2186 assert_eq!(sm.stage(), Stage::Failed);
2187 }
2188
2189 #[test]
2194 fn send_trusted_root_cert_emits_invoke_and_status_ack_advances() {
2195 let fabric = make_fabric_record();
2196 let setup = make_setup_payload();
2197 let paa = PaaTrustStore::with_example_device_roots();
2198 let cd = CdSigningRoots::with_example_device_roots();
2199 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2200 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2201 let mut sm = Commissioner::new(cfg).expect("valid config");
2202
2203 sm.stage = Stage::SendTrustedRootCert;
2204
2205 match sm.poll().expect("poll SendTrustedRootCert") {
2206 Action::Invoke {
2207 cluster,
2208 command,
2209 expect,
2210 payload,
2211 ..
2212 } => {
2213 assert_eq!(cluster, 0x003E);
2214 assert_eq!(command, 0x0B);
2215 assert_eq!(expect, Expectation::AddTrustedRootResponse);
2216 assert!(payload.len() > 16, "RCAC TLV too short: {}", payload.len());
2219 assert_eq!(payload[0], 0x15); }
2221 other => panic!("expected Invoke, got {other:?}"),
2222 }
2223
2224 sm.on_response(Expectation::AddTrustedRootResponse, &[0x00])
2226 .expect("status-ack accepted");
2227 assert_eq!(sm.stage(), Stage::SendNoc);
2228 }
2229
2230 #[test]
2233 fn find_operational_for_complete_emits_establish_case() {
2234 let fabric = make_fabric_record();
2235 let setup = make_setup_payload();
2236 let paa = PaaTrustStore::with_example_device_roots();
2237 let cd = crate::attestation::CdSigningRoots::with_example_device_roots();
2238 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2239 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2240 let mut sm = Commissioner::new(cfg).expect("valid config");
2241 sm.stage = Stage::FindOperationalForComplete;
2242 match sm.poll().expect("poll establish case") {
2243 Action::EstablishCase {
2244 fabric_id,
2245 peer_node_id,
2246 } => {
2247 assert_eq!(fabric_id, fabric.fabric_id);
2248 assert_eq!(peer_node_id, 0x2); }
2250 other => panic!("expected EstablishCase, got {other:?}"),
2251 }
2252 assert!(sm.awaiting_case_session);
2253 }
2254
2255 #[test]
2256 fn on_case_established_advances_to_send_complete() {
2257 let fabric = make_fabric_record();
2258 let setup = make_setup_payload();
2259 let paa = PaaTrustStore::with_example_device_roots();
2260 let cd = crate::attestation::CdSigningRoots::with_example_device_roots();
2261 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2262 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2263 let mut sm = Commissioner::new(cfg).expect("valid config");
2264 sm.stage = Stage::FindOperationalForComplete;
2265 let _ = sm.poll().expect("emit EstablishCase");
2266 sm.on_case_established().expect("case established");
2267 assert_eq!(sm.stage(), Stage::SendComplete);
2268 assert!(!sm.awaiting_case_session);
2269 }
2270
2271 #[test]
2272 fn on_case_established_without_pending_emits_out_of_order() {
2273 let fabric = make_fabric_record();
2274 let setup = make_setup_payload();
2275 let paa = PaaTrustStore::with_example_device_roots();
2276 let cd = crate::attestation::CdSigningRoots::with_example_device_roots();
2277 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2278 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2279 let mut sm = Commissioner::new(cfg).expect("valid config");
2280 let err = sm.on_case_established().expect_err("no pending establish");
2281 assert!(matches!(err, CommissioningError::OutOfOrderResponse(_)));
2282 }
2283
2284 #[test]
2285 fn send_complete_emits_invoke_over_case_session() {
2286 let fabric = make_fabric_record();
2287 let setup = make_setup_payload();
2288 let paa = PaaTrustStore::with_example_device_roots();
2289 let cd = crate::attestation::CdSigningRoots::with_example_device_roots();
2290 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2291 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2292 let mut sm = Commissioner::new(cfg).expect("valid config");
2293 sm.stage = Stage::SendComplete;
2294 match sm.poll().expect("poll send complete") {
2295 Action::Invoke {
2296 session,
2297 cluster,
2298 command,
2299 expect,
2300 payload,
2301 ..
2302 } => {
2303 assert_eq!(session, crate::state_machine::SessionContext::Case);
2304 assert_eq!(cluster, 0x0030);
2305 assert_eq!(command, 0x04); assert_eq!(expect, Expectation::CommissioningCompleteResponse);
2307 assert_eq!(payload, vec![0x15, 0x18]);
2309 }
2310 other => panic!("expected Invoke, got {other:?}"),
2311 }
2312 }
2313
2314 #[test]
2315 fn send_complete_success_advances_to_cleanup() {
2316 let fabric = make_fabric_record();
2317 let setup = make_setup_payload();
2318 let paa = PaaTrustStore::with_example_device_roots();
2319 let cd = crate::attestation::CdSigningRoots::with_example_device_roots();
2320 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2321 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2322 let mut sm = Commissioner::new(cfg).expect("valid config");
2323 sm.stage = Stage::SendComplete;
2324 let _ = sm.poll().expect("emit invoke");
2325 sm.on_response(
2326 Expectation::CommissioningCompleteResponse,
2327 &[0x15, 0x24, 0x00, 0x00, 0x18], )
2329 .expect("complete ok");
2330 assert_eq!(sm.stage(), Stage::Cleanup);
2331 }
2332
2333 #[test]
2334 fn cleanup_emits_done_with_noc_public_key() {
2335 let fabric = make_fabric_record();
2336 let setup = make_setup_payload();
2337 let paa = PaaTrustStore::with_example_device_roots();
2338 let cd = crate::attestation::CdSigningRoots::with_example_device_roots();
2339 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2340 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2341 let mut sm = Commissioner::new(cfg).expect("valid config");
2342 sm.stage = Stage::Cleanup;
2343 sm.issued_noc_public_key = Some([0xCA; 65]);
2344 match sm.poll().expect("poll cleanup") {
2345 Action::Done(cf) => {
2346 assert_eq!(cf.peer_node_id, 0x2);
2347 assert_eq!(cf.peer_root_public_key, [0xCA; 65]);
2348 assert_eq!(cf.terminated_at, Stage::Cleanup);
2349 assert_eq!(cf.fabric.fabric_id, fabric.fabric_id);
2350 }
2351 other => panic!("expected Done, got {other:?}"),
2352 }
2353 }
2354
2355 #[test]
2358 fn case_failed_response_aborts_with_case_establishment_failed() {
2359 let fabric = make_fabric_record();
2360 let setup = make_setup_payload();
2361 let paa = PaaTrustStore::with_example_device_roots();
2362 let cd = crate::attestation::CdSigningRoots::with_example_device_roots();
2363 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2364 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2365 let mut sm = Commissioner::new(cfg).expect("valid config");
2366
2367 sm.stage = Stage::FindOperationalForComplete;
2370 let _ = sm.poll().expect("emit EstablishCase");
2371 assert!(sm.awaiting_case_session);
2372
2373 let err = sm
2375 .on_response(Expectation::CaseFailed, &[])
2376 .expect_err("CaseFailed should error");
2377 assert!(matches!(err, CommissioningError::CaseEstablishmentFailed));
2378 assert_eq!(sm.stage(), Stage::Failed);
2379 assert!(!sm.awaiting_case_session);
2380
2381 match sm.poll().expect("emit abort") {
2383 Action::Abort {
2384 send_disarm_failsafe,
2385 reason,
2386 } => {
2387 assert!(send_disarm_failsafe);
2388 assert!(
2389 reason.contains("CASE"),
2390 "abort reason should mention CASE: {reason}"
2391 );
2392 }
2393 other => panic!("expected Abort, got {other:?}"),
2394 }
2395 }
2396
2397 #[test]
2398 fn case_failed_when_not_awaiting_returns_out_of_order() {
2399 let fabric = make_fabric_record();
2400 let setup = make_setup_payload();
2401 let paa = PaaTrustStore::with_example_device_roots();
2402 let cd = crate::attestation::CdSigningRoots::with_example_device_roots();
2403 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2404 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2405 let mut sm = Commissioner::new(cfg).expect("valid config");
2406 let err = sm
2407 .on_response(Expectation::CaseFailed, &[])
2408 .expect_err("CaseFailed without pending should error");
2409 assert!(matches!(err, CommissioningError::OutOfOrderResponse(_)));
2410 }
2411
2412 fn sample_valid_config() -> CommissionerConfig<'static> {
2416 use std::sync::OnceLock;
2417 static FABRIC: OnceLock<FabricRecord> = OnceLock::new();
2418 static SETUP: OnceLock<SetupPayload> = OnceLock::new();
2419 static PAA: OnceLock<PaaTrustStore> = OnceLock::new();
2420 static CD: OnceLock<crate::attestation::CdSigningRoots> = OnceLock::new();
2421
2422 let fabric = FABRIC.get_or_init(make_fabric_record);
2423 let setup = SETUP.get_or_init(make_setup_payload);
2424 let paa = PAA.get_or_init(PaaTrustStore::with_example_device_roots);
2425 let cd = CD.get_or_init(crate::attestation::CdSigningRoots::with_example_device_roots);
2426 let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
2427
2428 CommissionerConfig {
2429 pase_attestation_challenge: [0u8; 16],
2430 fabric,
2431 setup_payload: setup,
2432 paa_trust_store: paa,
2433 cd_signing_roots: cd,
2434 commissioner_node_id: 0x1,
2435 assigned_node_id: 0x2,
2436 ipk_epoch_key: [0x42_u8; 16],
2437 case_admin_subject: 0x1,
2438 admin_vendor_id: 0xFFF1,
2439 now: MatterTime::from_unix_secs(1_704_067_200),
2440 rng,
2441 network: NetworkCredentials::AlreadyOnNetwork,
2442 }
2443 }
2444
2445 #[test]
2446 fn empty_ssid_is_rejected() {
2447 let mut config = sample_valid_config();
2448 config.network = NetworkCredentials::WiFi(WiFiCredentials {
2449 ssid: vec![],
2450 credentials: vec![],
2451 });
2452 let Err(err) = Commissioner::new(config) else {
2453 panic!("empty ssid should fail");
2454 };
2455 assert!(
2456 matches!(err, CommissioningError::InvalidConfig(m) if m.contains("ssid")),
2457 "got {err:?}",
2458 );
2459 }
2460
2461 #[test]
2462 fn oversize_ssid_is_rejected() {
2463 let mut config = sample_valid_config();
2464 config.network = NetworkCredentials::WiFi(WiFiCredentials {
2465 ssid: vec![b'a'; 33],
2466 credentials: vec![],
2467 });
2468 let Err(err) = Commissioner::new(config) else {
2469 panic!("33-byte ssid should fail");
2470 };
2471 assert!(
2472 matches!(err, CommissioningError::InvalidConfig(m) if m.contains("≤32")),
2473 "got {err:?}",
2474 );
2475 }
2476
2477 #[test]
2478 fn oversize_credentials_is_rejected() {
2479 let mut config = sample_valid_config();
2480 config.network = NetworkCredentials::WiFi(WiFiCredentials {
2481 ssid: b"matter".to_vec(),
2482 credentials: vec![0u8; 65],
2483 });
2484 let Err(err) = Commissioner::new(config) else {
2485 panic!("65-byte credentials should fail");
2486 };
2487 assert!(
2488 matches!(err, CommissioningError::InvalidConfig(m) if m.contains("≤64")),
2489 "got {err:?}",
2490 );
2491 }
2492
2493 #[test]
2494 fn wifi_credentials_none_is_accepted() {
2495 let mut config = sample_valid_config();
2496 config.network = NetworkCredentials::AlreadyOnNetwork;
2497 Commissioner::new(config).expect("AlreadyOnNetwork should pass validation");
2498 }
2499
2500 #[test]
2501 fn network_credentials_thread_variant_accepted() {
2502 let ds =
2505 crate::thread_dataset::ThreadDataset::new(vec![0x02, 0x08, 0, 0, 0, 0, 0, 0, 0, 0])
2506 .expect("minimal ext-pan-id dataset is valid");
2507 let mut config = sample_valid_config();
2508 config.network = NetworkCredentials::Thread(ds);
2509 let c = Commissioner::new(config).expect("Thread network should pass validation");
2510 assert!(matches!(c.network(), NetworkCredentials::Thread(_)));
2511 }
2512
2513 #[test]
2514 fn wifi_credentials_debug_redacts_passphrase() {
2515 let creds = WiFiCredentials {
2516 ssid: b"matter".to_vec(),
2517 credentials: b"hunter22".to_vec(),
2518 };
2519 let rendered = format!("{creds:?}");
2520 assert!(
2521 !rendered.contains("hunter22"),
2522 "Debug must not contain credentials bytes: {rendered}",
2523 );
2524 assert!(rendered.contains("redacted"), "got {rendered}");
2525 assert!(
2526 rendered.contains('8'),
2527 "credentials length should appear: {rendered}"
2528 );
2529 assert!(
2530 rendered.contains('6'),
2531 "ssid length should appear: {rendered}"
2532 );
2533 }
2534
2535 #[test]
2538 fn failsafe_expiry_derives_from_basic_commissioning_info() {
2539 let mut sm = Commissioner::new(sample_valid_config()).expect("valid config");
2540 let _initial = sm.poll().expect("initial poll");
2542 let response = vec![
2543 0x15, 0x25, 0x00, 0x78, 0x00, 0x18,
2545 ];
2546 sm.on_response(Expectation::CommissioningInfo, &response)
2547 .expect("commissioning info accepted");
2548 let action = sm.poll().expect("arm-failsafe poll");
2550 match action {
2551 Action::Invoke {
2552 payload,
2553 cluster,
2554 command,
2555 ..
2556 } => {
2557 assert_eq!(cluster, 0x0030);
2558 assert_eq!(command, 0x00);
2559 assert!(
2562 payload.windows(3).any(|w| w == [0x24, 0x00, 0x78]),
2563 "ArmFailSafe payload should carry expiry=120: {payload:02x?}",
2564 );
2565 }
2566 other => panic!("expected Invoke, got {other:?}"),
2567 }
2568 }
2569
2570 #[test]
2571 fn failsafe_expiry_capped_at_max_cumulative_failsafe_seconds() {
2572 let mut sm = Commissioner::new(sample_valid_config()).expect("valid config");
2573 let _initial = sm.poll().expect("initial poll");
2574 let response = vec![
2578 0x15, 0x25, 0x00, 0x78, 0x00, 0x25, 0x01, 0x5A, 0x00, 0x18,
2582 ];
2583 sm.on_response(Expectation::CommissioningInfo, &response)
2584 .expect("commissioning info accepted");
2585 let action = sm.poll().expect("arm-failsafe poll");
2586 match action {
2587 Action::Invoke { payload, .. } => {
2588 assert!(
2589 payload.windows(3).any(|w| w == [0x24, 0x00, 0x5A]),
2590 "ArmFailSafe expiry must be capped to 90: {payload:02x?}",
2591 );
2592 assert!(
2593 !payload.windows(3).any(|w| w == [0x24, 0x00, 0x78]),
2594 "the uncapped 120 must NOT be sent: {payload:02x?}",
2595 );
2596 }
2597 other => panic!("expected Invoke, got {other:?}"),
2598 }
2599 }
2600
2601 #[test]
2602 fn failsafe_expiry_falls_back_to_60_on_empty_basic_commissioning_info() {
2603 let mut sm = Commissioner::new(sample_valid_config()).expect("valid config");
2604 let _initial = sm.poll().expect("initial poll");
2605 sm.on_response(Expectation::CommissioningInfo, &[0x15, 0x18])
2609 .expect("empty struct accepted");
2610 let action = sm.poll().expect("arm-failsafe poll");
2611 if let Action::Invoke { payload, .. } = action {
2612 assert!(
2614 payload.windows(3).any(|w| w == [0x24, 0x00, 0x3C]),
2615 "ArmFailSafe payload should carry expiry=60 fallback: {payload:02x?}",
2616 );
2617 }
2618 }
2619
2620 #[test]
2623 fn breadcrumb_increases_across_commands() {
2624 fn extract_uint_at_tag(payload: &[u8], tag_num: u8) -> Option<u64> {
2625 use matter_codec::{Element, Tag, TlvReader, Value};
2626 let mut reader = TlvReader::new(payload);
2627 while let Ok(Some(elem)) = reader.next() {
2628 if let Element::Scalar {
2629 tag: Tag::Context(t),
2630 value: Value::Uint(v),
2631 } = elem
2632 {
2633 if t == tag_num {
2634 return Some(v);
2635 }
2636 }
2637 }
2638 None
2639 }
2640
2641 let mut sm = Commissioner::new(sample_valid_config()).expect("valid config");
2642 let mut breadcrumbs: Vec<u64> = Vec::new();
2643
2644 let _ = sm.poll().expect("read commissioning info");
2646 sm.on_response(Expectation::CommissioningInfo, &[0x15, 0x18])
2647 .expect("info accepted");
2648
2649 let action = sm.poll().expect("arm-failsafe");
2651 if let Action::Invoke { payload, .. } = action {
2652 if let Some(b) = extract_uint_at_tag(&payload, 1) {
2653 breadcrumbs.push(b);
2654 }
2655 }
2656 sm.on_response(
2657 Expectation::ArmFailsafeResponse,
2658 &[0x15, 0x24, 0x00, 0x00, 0x18],
2659 )
2660 .expect("arm-failsafe ok");
2661
2662 let action = sm.poll().expect("set-regulatory");
2664 if let Action::Invoke { payload, .. } = action {
2665 if let Some(b) = extract_uint_at_tag(&payload, 2) {
2666 breadcrumbs.push(b);
2667 }
2668 }
2669
2670 assert_eq!(
2671 breadcrumbs.len(),
2672 2,
2673 "should have extracted exactly two breadcrumbs, got {breadcrumbs:?}",
2674 );
2675 assert!(
2676 breadcrumbs[0] < breadcrumbs[1],
2677 "breadcrumbs should be strictly increasing: {breadcrumbs:?}",
2678 );
2679 }
2680}