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 if payload.is_empty() {
1034 match &self.network {
1035 NetworkCredentials::AlreadyOnNetwork => {
1036 self.advance(Stage::EvictPreviousCaseSessions);
1037 return Ok(());
1038 }
1039 NetworkCredentials::WiFi(_) => {
1040 return Err(CommissioningError::NetworkFeatureUnsupported {
1041 needed: NetworkKind::WiFi,
1042 });
1043 }
1044 NetworkCredentials::Thread(_) => {
1045 return Err(CommissioningError::NetworkFeatureUnsupported {
1046 needed: NetworkKind::Thread,
1047 });
1048 }
1049 }
1050 }
1051 let features = nc::decode_feature_map(payload)?;
1052 if features.is_empty() {
1057 return Err(CommissioningError::MalformedResponse(
1058 Stage::ReadNetworkCommissioningInfo,
1059 ));
1060 }
1061 match &self.network {
1069 NetworkCredentials::WiFi(_) => {
1070 if !features.contains(nc::NetworkCommissioningFeature::WIFI) {
1071 return Err(CommissioningError::NetworkFeatureUnsupported {
1072 needed: NetworkKind::WiFi,
1073 });
1074 }
1075 self.advance(Stage::NetworkSetup);
1076 }
1077 NetworkCredentials::Thread(_) => {
1078 if !features.contains(nc::NetworkCommissioningFeature::THREAD) {
1079 return Err(CommissioningError::NetworkFeatureUnsupported {
1080 needed: NetworkKind::Thread,
1081 });
1082 }
1083 self.advance(Stage::NetworkSetup);
1084 }
1085 NetworkCredentials::AlreadyOnNetwork => {
1086 self.advance(Stage::EvictPreviousCaseSessions);
1095 }
1096 }
1097 Ok(())
1098 }
1099 Expectation::NetworkConfigResponse => {
1100 use crate::clusters::network_commissioning as nc;
1101 let resp = nc::decode_network_config_response(Stage::NetworkSetup, payload)?;
1102 if resp.networking_status != 0 {
1103 return Err(CommissioningError::NetworkRejected {
1104 stage: Stage::NetworkSetup,
1105 networking_status: resp.networking_status,
1106 debug_text: resp.debug_text,
1107 remediation_hint: nc::remediation_for(resp.networking_status),
1108 });
1109 }
1110 self.advance(Stage::FailsafeBeforeNetworkEnable);
1111 Ok(())
1112 }
1113 Expectation::ConnectNetworkResponse => {
1114 use crate::clusters::network_commissioning as nc;
1115 let resp = nc::decode_connect_network_response(Stage::NetworkEnable, payload)?;
1116 if resp.networking_status != 0 {
1117 return Err(CommissioningError::NetworkRejected {
1118 stage: Stage::NetworkEnable,
1119 networking_status: resp.networking_status,
1120 debug_text: resp.debug_text,
1121 remediation_hint: nc::remediation_for(resp.networking_status),
1122 });
1123 }
1124 self.advance(Stage::EvictPreviousCaseSessions);
1125 Ok(())
1126 }
1127 Expectation::CommissioningCompleteResponse => {
1128 let (error_code, _debug) =
1129 gc::decode_commissioning_error_response(Stage::SendComplete, payload)?;
1130 if error_code != 0 {
1131 return Err(CommissioningError::DeviceImStatus {
1132 stage: Stage::SendComplete,
1133 im_status: u16::from(error_code),
1134 });
1135 }
1136 self.advance(Stage::Cleanup);
1137 Ok(())
1138 }
1139 _ => Err(CommissioningError::OutOfOrderResponse(self.stage)),
1142 }
1143 }
1144
1145 fn next_breadcrumb(&mut self) -> u64 {
1146 let b = self.breadcrumb_counter;
1147 self.breadcrumb_counter = b.saturating_add(1);
1148 b
1149 }
1150
1151 fn advance(&mut self, next: Stage) {
1152 self.stage = next;
1153 self.awaiting = None;
1154 self.pending_action = None;
1155 }
1156
1157 fn run_attestation_verification(&mut self) -> Result<(), CommissioningError> {
1177 use crate::attestation::profile::{verify_attestation_cert_format, CertRole};
1178 use crate::attestation::{
1179 extract_attestation_elements_fields, verify_attestation_response, verify_chain,
1180 AttestationError, Dac, Pai,
1181 };
1182
1183 let pai_der = self
1184 .pai_der
1185 .as_ref()
1186 .ok_or(CommissioningError::OutOfOrderResponse(self.stage))?;
1187 let dac_der = self
1188 .dac_der
1189 .as_ref()
1190 .ok_or(CommissioningError::OutOfOrderResponse(self.stage))?;
1191 let response = self
1192 .attestation_response
1193 .as_ref()
1194 .ok_or(CommissioningError::OutOfOrderResponse(self.stage))?;
1195 let expected_nonce = self
1196 .attestation_nonce
1197 .ok_or(CommissioningError::OutOfOrderResponse(self.stage))?;
1198
1199 let pai = Pai::from_der(pai_der)?;
1201 let dac = Dac::from_der(dac_der)?;
1202 #[cfg(feature = "tracing")]
1203 tracing::debug!(
1204 dac_der = %crate::hexdump::hex(dac_der),
1205 pai_der = %crate::hexdump::hex(pai_der),
1206 "verifying attestation chain"
1207 );
1208
1209 verify_attestation_cert_format(pai.der(), CertRole::Pai)?;
1217 verify_attestation_cert_format(dac.der(), CertRole::Dac)?;
1218
1219 let chain = verify_chain(&dac, &pai, &self.paa_trust_store, self.now)?;
1226
1227 verify_attestation_response(response, &self.pase_attestation_challenge, dac.public_key())?;
1229
1230 let fields = extract_attestation_elements_fields(&response.attestation_elements)?;
1233 if fields.attestation_nonce != expected_nonce {
1234 return Err(CommissioningError::Attestation(
1235 AttestationError::ResponseElementsMalformed,
1236 ));
1237 }
1238
1239 #[cfg(feature = "tracing")]
1243 tracing::debug!(
1244 cd_cms = %crate::hexdump::hex(&fields.certification_declaration),
1245 "verifying certification declaration"
1246 );
1247 crate::attestation::verify_certification_declaration_with_paa(
1248 &fields.certification_declaration,
1249 chain.vendor_id,
1250 chain.product_id,
1251 &self.cd_signing_roots,
1252 chain.paa_skid.as_deref(),
1253 )?;
1254 Ok(())
1255 }
1256
1257 fn run_validate_csr(&mut self) -> Result<(), CommissioningError> {
1268 use crate::attestation::Dac;
1269 use crate::noc::verify_csr_response;
1270
1271 let resp = self
1272 .csr_response
1273 .as_ref()
1274 .ok_or(CommissioningError::OutOfOrderResponse(self.stage))?;
1275 let dac_der = self
1276 .dac_der
1277 .as_ref()
1278 .ok_or(CommissioningError::OutOfOrderResponse(self.stage))?;
1279 let csr_nonce = self
1280 .csr_nonce
1281 .ok_or(CommissioningError::OutOfOrderResponse(self.stage))?;
1282
1283 let dac = Dac::from_der(dac_der)?;
1284 let verified = verify_csr_response(
1285 &resp.nocsr_elements,
1286 &resp.attestation_signature,
1287 &csr_nonce,
1288 &self.pase_attestation_challenge,
1289 dac.public_key(),
1290 )?;
1291 self.verified_csr = Some(verified);
1292 Ok(())
1293 }
1294
1295 fn run_generate_noc_chain(&mut self) -> Result<(), CommissioningError> {
1309 use crate::noc::issue_noc;
1310
1311 let verified = self
1312 .verified_csr
1313 .as_ref()
1314 .ok_or(CommissioningError::OutOfOrderResponse(self.stage))?;
1315 let noc = issue_noc(
1316 &self.fabric,
1317 verified,
1318 self.assigned_node_id,
1319 &[],
1320 (self.now, MatterTime::NO_EXPIRY),
1321 self.rng.as_ref(),
1322 )?;
1323 self.issued_noc_public_key = Some(*verified.public_key.as_bytes());
1328 self.issued_noc = Some(noc);
1329 Ok(())
1330 }
1331
1332 fn assert_tlv_well_formed(stage: Stage, payload: &[u8]) -> Result<(), CommissioningError> {
1333 use matter_codec::{ContainerKind, Element, Tag, TlvReader};
1334 let mut reader = TlvReader::new(payload);
1335 match reader
1336 .next()
1337 .map_err(|_| CommissioningError::MalformedResponse(stage))?
1338 {
1339 Some(Element::ContainerStart {
1340 tag: Tag::Anonymous,
1341 kind: ContainerKind::Structure,
1342 }) => {}
1343 _ => return Err(CommissioningError::MalformedResponse(stage)),
1344 }
1345 loop {
1347 match reader
1348 .next()
1349 .map_err(|_| CommissioningError::MalformedResponse(stage))?
1350 {
1351 None => return Err(CommissioningError::MalformedResponse(stage)),
1352 Some(Element::ContainerEnd) => return Ok(()),
1353 Some(_) => {}
1354 }
1355 }
1356 }
1357}
1358
1359#[cfg(feature = "__test_shortcuts")]
1364#[derive(Default, Debug, Clone, Copy)]
1365pub struct TestStateSeeds {
1366 pub synthetic_noc_pubkey: Option<[u8; 65]>,
1371}
1372
1373#[cfg(test)]
1374mod tests {
1375 #![allow(clippy::unwrap_used, clippy::expect_used)]
1377
1378 use super::*;
1379 use crate::attestation::CdSigningRoots;
1380 use crate::noc::{FabricRecord, NocRng, SystemNocRng};
1381 use crate::setup::{
1382 CommissioningFlow, DiscoveryCapabilities, Discriminator, Passcode, SetupPayload,
1383 };
1384 use crate::state_machine::{Action, Expectation};
1385 use crate::PaaTrustStore;
1386 use matter_cert::time::MatterTime;
1387 use matter_crypto::{RingSigner, Signer};
1388 use std::sync::Arc;
1389
1390 fn make_setup_payload() -> SetupPayload {
1391 SetupPayload {
1392 version: 0,
1393 vendor_id: Some(0xFFF1),
1394 product_id: Some(0x8000),
1395 commissioning_flow: CommissioningFlow::Standard,
1396 discovery_capabilities: DiscoveryCapabilities::ON_NETWORK,
1397 discriminator: Discriminator::new(0x0F00).expect("valid discriminator"),
1398 passcode: Passcode::new(20_202_021).expect("valid passcode"),
1399 }
1400 }
1401
1402 fn make_fabric_record() -> FabricRecord {
1403 let (signer, _pkcs8) = RingSigner::generate().unwrap();
1404 let signer: Arc<dyn Signer> = Arc::new(signer);
1405 FabricRecord::new_root_only(
1406 0x0000_0000_0000_0001,
1407 signer,
1408 MatterTime::from_unix_secs(1_704_067_200),
1409 MatterTime::from_unix_secs(1_735_689_600),
1410 42,
1411 &SystemNocRng,
1412 )
1413 .unwrap()
1414 }
1415
1416 fn make_fabric_record_with_icac() -> FabricRecord {
1421 let mut fabric = make_fabric_record();
1422 let (icac_signer, _pkcs8) = RingSigner::generate().unwrap();
1423 let icac_public_key =
1424 matter_cert::PublicKey::from_slice(icac_signer.public_key().as_bytes()).unwrap();
1425 let icac = crate::noc::issue_icac(
1426 &fabric,
1427 7,
1428 &icac_public_key,
1429 (
1430 MatterTime::from_unix_secs(1_704_067_200),
1431 MatterTime::NO_EXPIRY,
1432 ),
1433 &SystemNocRng,
1434 )
1435 .unwrap();
1436 fabric.icac_signer = Some(Arc::new(icac_signer));
1437 fabric.icac_cert = Some(icac);
1438 fabric
1439 }
1440
1441 fn add_noc_payload_has_icac(payload: &[u8]) -> bool {
1446 use matter_codec::{Element, Tag, TlvReader};
1447 let mut r = TlvReader::new(payload);
1448 match r.next().unwrap() {
1449 Some(Element::ContainerStart { .. }) => {}
1450 other => panic!("expected an AddNOC struct, got {other:?}"),
1451 }
1452 while let Some(el) = r.next().unwrap() {
1453 match el {
1454 Element::ContainerEnd => break,
1455 Element::Scalar {
1456 tag: Tag::Context(1),
1457 ..
1458 } => return true,
1459 _ => {}
1460 }
1461 }
1462 false
1463 }
1464
1465 #[test]
1466 fn send_noc_includes_icac_for_three_tier_fabric() {
1467 let fabric = make_fabric_record_with_icac();
1468 let setup = make_setup_payload();
1469 let paa = PaaTrustStore::with_example_device_roots();
1470 let cd = CdSigningRoots::with_example_device_roots();
1471 let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
1472 let mut sm =
1473 Commissioner::new(base_config(&fabric, &setup, &paa, &cd, rng)).expect("valid config");
1474 sm.issued_noc = Some(fabric.root_cert.clone());
1478 sm.stage = Stage::SendNoc;
1479 match sm.dispatch_stage().expect("SendNoc dispatch") {
1480 Action::Invoke {
1481 cluster,
1482 command,
1483 payload,
1484 ..
1485 } => {
1486 assert_eq!(cluster, 0x003E);
1487 assert_eq!(command, 0x06);
1488 assert!(
1489 add_noc_payload_has_icac(&payload),
1490 "AddNOC must carry the ICAC (ctx1) when the fabric runs a 3-tier chain"
1491 );
1492 }
1493 other => panic!("expected Invoke, got {other:?}"),
1494 }
1495 }
1496
1497 #[test]
1498 fn send_noc_omits_icac_for_flat_fabric() {
1499 let fabric = make_fabric_record();
1500 let setup = make_setup_payload();
1501 let paa = PaaTrustStore::with_example_device_roots();
1502 let cd = CdSigningRoots::with_example_device_roots();
1503 let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
1504 let mut sm =
1505 Commissioner::new(base_config(&fabric, &setup, &paa, &cd, rng)).expect("valid config");
1506 sm.issued_noc = Some(fabric.root_cert.clone());
1507 sm.stage = Stage::SendNoc;
1508 match sm.dispatch_stage().expect("SendNoc dispatch") {
1509 Action::Invoke { payload, .. } => {
1510 assert!(
1511 !add_noc_payload_has_icac(&payload),
1512 "flat RCAC->NOC AddNOC must omit the ICAC field"
1513 );
1514 }
1515 other => panic!("expected Invoke, got {other:?}"),
1516 }
1517 }
1518
1519 fn base_config<'a>(
1520 fabric: &'a FabricRecord,
1521 setup: &'a SetupPayload,
1522 paa: &'a PaaTrustStore,
1523 cd: &'a crate::attestation::CdSigningRoots,
1524 rng: Arc<dyn NocRng>,
1525 ) -> CommissionerConfig<'a> {
1526 CommissionerConfig {
1527 pase_attestation_challenge: [0u8; 16],
1528 fabric,
1529 setup_payload: setup,
1530 paa_trust_store: paa,
1531 cd_signing_roots: cd,
1532 commissioner_node_id: 0x1,
1533 assigned_node_id: 0x2,
1534 ipk_epoch_key: [0x42_u8; 16],
1535 case_admin_subject: 0x1,
1536 admin_vendor_id: 0xFFF1,
1537 now: MatterTime::from_unix_secs(1_704_067_200),
1538 rng,
1539 network: NetworkCredentials::AlreadyOnNetwork,
1540 }
1541 }
1542
1543 #[test]
1544 fn new_rejects_zero_commissioner_node_id() {
1545 let fabric = make_fabric_record();
1546 let setup = make_setup_payload();
1547 let paa = PaaTrustStore::with_example_device_roots();
1548 let cd = CdSigningRoots::with_example_device_roots();
1549 let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
1550 let mut cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1551 cfg.commissioner_node_id = 0;
1552 let Err(err) = Commissioner::new(cfg) else {
1555 panic!("zero commissioner_node_id should fail");
1556 };
1557 assert!(
1558 matches!(err, CommissioningError::InvalidConfig(_)),
1559 "got {err:?}"
1560 );
1561 }
1562
1563 #[test]
1564 fn new_rejects_zero_assigned_node_id() {
1565 let fabric = make_fabric_record();
1566 let setup = make_setup_payload();
1567 let paa = PaaTrustStore::with_example_device_roots();
1568 let cd = CdSigningRoots::with_example_device_roots();
1569 let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
1570 let mut cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1571 cfg.assigned_node_id = 0;
1572 let Err(err) = Commissioner::new(cfg) else {
1573 panic!("zero assigned_node_id should fail");
1574 };
1575 assert!(
1576 matches!(err, CommissioningError::InvalidConfig(_)),
1577 "got {err:?}"
1578 );
1579 }
1580
1581 #[test]
1582 fn new_rejects_equal_commissioner_and_assigned_ids() {
1583 let fabric = make_fabric_record();
1584 let setup = make_setup_payload();
1585 let paa = PaaTrustStore::with_example_device_roots();
1586 let cd = CdSigningRoots::with_example_device_roots();
1587 let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
1588 let mut cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1589 cfg.commissioner_node_id = 0x42;
1590 cfg.assigned_node_id = 0x42;
1591 let Err(err) = Commissioner::new(cfg) else {
1592 panic!("equal IDs should fail");
1593 };
1594 assert!(
1595 matches!(err, CommissioningError::InvalidConfig(_)),
1596 "got {err:?}"
1597 );
1598 }
1599
1600 #[test]
1601 fn new_rejects_zero_ipk_epoch_key() {
1602 let fabric = make_fabric_record();
1603 let setup = make_setup_payload();
1604 let paa = PaaTrustStore::with_example_device_roots();
1605 let cd = CdSigningRoots::with_example_device_roots();
1606 let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
1607 let mut cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1608 cfg.ipk_epoch_key = [0u8; 16];
1609 let Err(err) = Commissioner::new(cfg) else {
1610 panic!("zero IPK should fail");
1611 };
1612 assert!(
1613 matches!(err, CommissioningError::InvalidConfig(_)),
1614 "got {err:?}"
1615 );
1616 }
1617
1618 #[test]
1619 fn new_returns_secure_pairing_stage() {
1620 let fabric = make_fabric_record();
1621 let setup = make_setup_payload();
1622 let paa = PaaTrustStore::with_example_device_roots();
1623 let cd = CdSigningRoots::with_example_device_roots();
1624 let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
1625 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1626 let sm = Commissioner::new(cfg).expect("valid config should construct");
1627 assert_eq!(sm.stage(), Stage::SecurePairing);
1628 }
1629
1630 #[test]
1631 fn poll_from_secure_pairing_emits_read_commissioning_info() {
1632 let fabric = make_fabric_record();
1633 let setup = make_setup_payload();
1634 let paa = PaaTrustStore::with_example_device_roots();
1635 let cd = CdSigningRoots::with_example_device_roots();
1636 let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
1637 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1638 let mut sm = Commissioner::new(cfg).expect("valid config");
1639 let act = sm.poll().expect("poll succeeds");
1640 match act {
1641 Action::ReadAttribute {
1642 session,
1643 endpoint,
1644 cluster,
1645 attributes,
1646 expect,
1647 } => {
1648 assert_eq!(session, crate::state_machine::SessionContext::Pase);
1649 assert_eq!(endpoint, 0);
1650 assert_eq!(cluster, 0x0030);
1651 assert_eq!(expect, Expectation::CommissioningInfo);
1652 assert!(!attributes.is_empty());
1653 }
1654 other => panic!("expected ReadAttribute, got {other:?}"),
1655 }
1656 assert_eq!(sm.stage(), Stage::ReadCommissioningInfo);
1657 }
1658
1659 #[test]
1660 fn poll_is_idempotent_between_responses() {
1661 let fabric = make_fabric_record();
1662 let setup = make_setup_payload();
1663 let paa = PaaTrustStore::with_example_device_roots();
1664 let cd = CdSigningRoots::with_example_device_roots();
1665 let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
1666 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1667 let mut sm = Commissioner::new(cfg).expect("valid config");
1668 let act1 = sm.poll().expect("first poll");
1669 let act2 = sm.poll().expect("second poll");
1670 match (act1, act2) {
1671 (
1672 Action::ReadAttribute {
1673 cluster: c1,
1674 expect: e1,
1675 ..
1676 },
1677 Action::ReadAttribute {
1678 cluster: c2,
1679 expect: e2,
1680 ..
1681 },
1682 ) => {
1683 assert_eq!(c1, c2);
1684 assert_eq!(e1, e2);
1685 }
1686 other => panic!("idempotent poll returned different variants: {other:?}"),
1687 }
1688 }
1689
1690 #[test]
1691 fn full_happy_path_through_config_regulatory_lands_on_send_pai_cert_request() {
1692 let fabric = make_fabric_record();
1693 let setup = make_setup_payload();
1694 let paa = PaaTrustStore::with_example_device_roots();
1695 let cd = CdSigningRoots::with_example_device_roots();
1696 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
1697 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1698 let mut sm = Commissioner::new(cfg).expect("valid config");
1699
1700 let _ = sm.poll().expect("poll #1");
1702 let canned_info = encode_read_commissioning_info_response();
1703 sm.on_response(Expectation::CommissioningInfo, &canned_info)
1704 .expect("commissioning info accepted");
1705 assert_eq!(sm.stage(), Stage::ArmFailsafe);
1706
1707 let _ = sm.poll().expect("poll #2");
1709 sm.on_response(
1710 Expectation::ArmFailsafeResponse,
1711 &[0x15, 0x24, 0x00, 0x00, 0x18],
1712 )
1713 .expect("arm failsafe ok");
1714 assert_eq!(sm.stage(), Stage::ConfigRegulatory);
1715
1716 let _ = sm.poll().expect("poll #3");
1718 sm.on_response(
1719 Expectation::SetRegulatoryConfigResponse,
1720 &[0x15, 0x24, 0x00, 0x00, 0x18],
1721 )
1722 .expect("config regulatory ok");
1723 assert_eq!(sm.stage(), Stage::SendPaiCertRequest);
1724
1725 match sm.poll().expect("poll #4") {
1727 Action::Invoke {
1728 cluster,
1729 command,
1730 expect,
1731 ..
1732 } => {
1733 assert_eq!(cluster, 0x003E);
1734 assert_eq!(command, 0x02);
1735 assert_eq!(expect, Expectation::PaiCertChainResponse);
1736 }
1737 other => panic!("expected Invoke, got {other:?}"),
1738 }
1739 }
1740
1741 #[test]
1742 fn arm_failsafe_busy_response_aborts_with_device_im_status() {
1743 let fabric = make_fabric_record();
1744 let setup = make_setup_payload();
1745 let paa = PaaTrustStore::with_example_device_roots();
1746 let cd = CdSigningRoots::with_example_device_roots();
1747 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
1748 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1749 let mut sm = Commissioner::new(cfg).expect("valid config");
1750 let _ = sm.poll().expect("poll info");
1751 sm.on_response(
1752 Expectation::CommissioningInfo,
1753 &encode_read_commissioning_info_response(),
1754 )
1755 .expect("commissioning info ok");
1756 let _ = sm.poll().expect("poll arm failsafe");
1757 let err = sm
1759 .on_response(
1760 Expectation::ArmFailsafeResponse,
1761 &[0x15, 0x24, 0x00, 0x04, 0x18],
1762 )
1763 .expect_err("busy should fail");
1764 assert!(matches!(
1765 err,
1766 CommissioningError::DeviceImStatus {
1767 stage: Stage::ArmFailsafe,
1768 im_status: 4,
1769 }
1770 ));
1771 assert_eq!(sm.stage(), Stage::Failed);
1772 match sm.poll().expect("abort emission") {
1773 Action::Abort {
1774 send_disarm_failsafe,
1775 reason,
1776 } => {
1777 assert!(send_disarm_failsafe);
1778 assert!(reason.contains("ArmFailsafe"), "reason was {reason}");
1779 assert!(reason.contains("0x4"), "reason was {reason}");
1780 }
1781 other => panic!("expected Abort, got {other:?}"),
1782 }
1783 }
1784
1785 #[test]
1786 fn out_of_order_response_returns_error_without_advancing() {
1787 let fabric = make_fabric_record();
1788 let setup = make_setup_payload();
1789 let paa = PaaTrustStore::with_example_device_roots();
1790 let cd = CdSigningRoots::with_example_device_roots();
1791 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
1792 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1793 let mut sm = Commissioner::new(cfg).expect("valid config");
1794 let err = sm
1796 .on_response(Expectation::ArmFailsafeResponse, &[])
1797 .expect_err("should reject out-of-order");
1798 assert!(matches!(err, CommissioningError::OutOfOrderResponse(_)));
1799 assert_eq!(sm.stage(), Stage::SecurePairing);
1800 }
1801
1802 #[test]
1803 fn wrong_expectation_returns_unexpected_response_kind() {
1804 let fabric = make_fabric_record();
1805 let setup = make_setup_payload();
1806 let paa = PaaTrustStore::with_example_device_roots();
1807 let cd = CdSigningRoots::with_example_device_roots();
1808 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
1809 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1810 let mut sm = Commissioner::new(cfg).expect("valid config");
1811 let _ = sm.poll().expect("poll");
1812 let err = sm
1813 .on_response(Expectation::ArmFailsafeResponse, &[])
1814 .expect_err("wrong kind should fail");
1815 assert!(matches!(
1816 err,
1817 CommissioningError::UnexpectedResponseKind {
1818 expected: Expectation::CommissioningInfo,
1819 got: Expectation::ArmFailsafeResponse,
1820 }
1821 ));
1822 assert_eq!(sm.stage(), Stage::ReadCommissioningInfo);
1824 }
1825
1826 fn encode_read_commissioning_info_response() -> Vec<u8> {
1827 vec![0x15, 0x18]
1830 }
1831
1832 fn drive_to_send_pai_cert_request(sm: &mut Commissioner) {
1835 let _ = sm.poll().expect("poll info");
1836 sm.on_response(Expectation::CommissioningInfo, &[0x15, 0x18])
1837 .expect("info ok");
1838 let _ = sm.poll().expect("poll arm failsafe");
1839 sm.on_response(
1840 Expectation::ArmFailsafeResponse,
1841 &[0x15, 0x24, 0x00, 0x00, 0x18],
1842 )
1843 .expect("arm ok");
1844 let _ = sm.poll().expect("poll config regulatory");
1845 sm.on_response(
1846 Expectation::SetRegulatoryConfigResponse,
1847 &[0x15, 0x24, 0x00, 0x00, 0x18],
1848 )
1849 .expect("regulatory ok");
1850 }
1851
1852 fn synthetic_cert_chain_response(cert: &[u8]) -> Vec<u8> {
1853 use matter_codec::{Tag, TlvWriter};
1854 let mut buf = Vec::new();
1855 let mut w = TlvWriter::new(&mut buf);
1856 w.start_structure(Tag::Anonymous).expect("infallible");
1857 w.put_bytes(Tag::Context(0), cert).expect("infallible");
1858 w.end_container().expect("infallible");
1859 buf
1860 }
1861
1862 fn nonce_from_attestation_invoke(act: &Action) -> [u8; 32] {
1863 match act {
1864 Action::Invoke { payload, .. } => {
1865 use matter_codec::{Element, Tag, TlvReader, Value};
1866 let mut r = TlvReader::new(payload);
1867 let _ = r.next().expect("reader").expect("anon-struct-start");
1868 loop {
1869 match r.next().expect("reader") {
1870 Some(Element::Scalar {
1871 tag: Tag::Context(0),
1872 value: Value::Bytes(b),
1873 }) => {
1874 return b.as_slice().try_into().expect("32 bytes");
1875 }
1876 Some(_) => {}
1877 None => panic!("no nonce found"),
1878 }
1879 }
1880 }
1881 other => panic!("expected Invoke, got {other:?}"),
1882 }
1883 }
1884
1885 #[test]
1886 fn poll_at_send_pai_emits_certificate_chain_request_pai() {
1887 let fabric = make_fabric_record();
1888 let setup = make_setup_payload();
1889 let paa = PaaTrustStore::with_example_device_roots();
1890 let cd = CdSigningRoots::with_example_device_roots();
1891 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
1892 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1893 let mut sm = Commissioner::new(cfg).expect("valid config");
1894 drive_to_send_pai_cert_request(&mut sm);
1895 assert_eq!(sm.stage(), Stage::SendPaiCertRequest);
1896 match sm.poll().expect("poll PAI") {
1897 Action::Invoke {
1898 cluster,
1899 command,
1900 expect,
1901 payload,
1902 ..
1903 } => {
1904 assert_eq!(cluster, 0x003E);
1905 assert_eq!(command, 0x02);
1906 assert_eq!(expect, Expectation::PaiCertChainResponse);
1907 assert_eq!(payload, vec![0x15, 0x24, 0x00, 0x02, 0x18]);
1909 }
1910 other => panic!("expected Invoke, got {other:?}"),
1911 }
1912 }
1913
1914 #[test]
1915 fn poll_at_send_dac_emits_certificate_chain_request_dac() {
1916 let fabric = make_fabric_record();
1917 let setup = make_setup_payload();
1918 let paa = PaaTrustStore::with_example_device_roots();
1919 let cd = CdSigningRoots::with_example_device_roots();
1920 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
1921 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
1922 let mut sm = Commissioner::new(cfg).expect("valid config");
1923 drive_to_send_pai_cert_request(&mut sm);
1924 let _ = sm.poll().expect("poll PAI");
1925 let pai_response = synthetic_cert_chain_response(&[0xAA, 0xBB, 0xCC]);
1926 sm.on_response(Expectation::PaiCertChainResponse, &pai_response)
1927 .expect("PAI accepted");
1928 assert_eq!(sm.stage(), Stage::SendDacCertRequest);
1929 match sm.poll().expect("poll DAC") {
1930 Action::Invoke {
1931 cluster,
1932 command,
1933 expect,
1934 payload,
1935 ..
1936 } => {
1937 assert_eq!(cluster, 0x003E);
1938 assert_eq!(command, 0x02);
1939 assert_eq!(expect, Expectation::DacCertChainResponse);
1940 assert_eq!(payload, vec![0x15, 0x24, 0x00, 0x01, 0x18]);
1942 }
1943 other => panic!("expected Invoke, got {other:?}"),
1944 }
1945 }
1946
1947 #[test]
1948 fn send_attestation_request_uses_fresh_random_nonce_each_time() {
1949 let fabric = make_fabric_record();
1950 let setup = make_setup_payload();
1951 let paa = PaaTrustStore::with_example_device_roots();
1952 let cd = CdSigningRoots::with_example_device_roots();
1953
1954 let rng_a: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
1955 let cfg_a = base_config(&fabric, &setup, &paa, &cd, rng_a);
1956 let mut sm_a = Commissioner::new(cfg_a).expect("valid config");
1957 drive_to_send_pai_cert_request(&mut sm_a);
1958 let _ = sm_a.poll().expect("poll PAI a");
1959 let pai_response = synthetic_cert_chain_response(&[0xAA]);
1960 sm_a.on_response(Expectation::PaiCertChainResponse, &pai_response)
1961 .expect("ok");
1962 let _ = sm_a.poll().expect("poll DAC a");
1963 let dac_response = synthetic_cert_chain_response(&[0xBB]);
1964 sm_a.on_response(Expectation::DacCertChainResponse, &dac_response)
1965 .expect("ok");
1966 let nonce_a = nonce_from_attestation_invoke(&sm_a.poll().expect("poll att a"));
1967
1968 let rng_b: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
1969 let cfg_b = base_config(&fabric, &setup, &paa, &cd, rng_b);
1970 let mut sm_b = Commissioner::new(cfg_b).expect("valid config");
1971 drive_to_send_pai_cert_request(&mut sm_b);
1972 let _ = sm_b.poll().expect("poll PAI b");
1973 sm_b.on_response(Expectation::PaiCertChainResponse, &pai_response)
1974 .expect("ok");
1975 let _ = sm_b.poll().expect("poll DAC b");
1976 sm_b.on_response(Expectation::DacCertChainResponse, &dac_response)
1977 .expect("ok");
1978 let nonce_b = nonce_from_attestation_invoke(&sm_b.poll().expect("poll att b"));
1979
1980 assert_ne!(
1981 nonce_a, nonce_b,
1982 "two independent runs should use different random nonces"
1983 );
1984 }
1985
1986 fn nonce_from_csr_invoke(act: &Action) -> [u8; 32] {
1992 match act {
1993 Action::Invoke { payload, .. } => {
1994 use matter_codec::{Element, Tag, TlvReader, Value};
1995 let mut r = TlvReader::new(payload);
1996 let _ = r.next().expect("reader").expect("anon-struct-start");
1997 loop {
1998 match r.next().expect("reader") {
1999 Some(Element::Scalar {
2000 tag: Tag::Context(0),
2001 value: Value::Bytes(b),
2002 }) => {
2003 return b.as_slice().try_into().expect("32 bytes");
2004 }
2005 Some(_) => {}
2006 None => panic!("no nonce found"),
2007 }
2008 }
2009 }
2010 other => panic!("expected Invoke, got {other:?}"),
2011 }
2012 }
2013
2014 #[test]
2021 fn send_op_cert_signing_request_emits_csr_with_random_nonce() {
2022 let fabric = make_fabric_record();
2023 let setup = make_setup_payload();
2024 let paa = PaaTrustStore::with_example_device_roots();
2025 let cd = CdSigningRoots::with_example_device_roots();
2026
2027 let rng_a: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2028 let cfg_a = base_config(&fabric, &setup, &paa, &cd, rng_a);
2029 let mut sm_a = Commissioner::new(cfg_a).expect("valid config");
2030 sm_a.stage = Stage::SendOpCertSigningRequest;
2034 sm_a.dac_der = Some(vec![0xAA, 0xBB]);
2035 let act_a = sm_a.poll().expect("poll csr a");
2036 let nonce_a = nonce_from_csr_invoke(&act_a);
2037
2038 let rng_b: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2039 let cfg_b = base_config(&fabric, &setup, &paa, &cd, rng_b);
2040 let mut sm_b = Commissioner::new(cfg_b).expect("valid config");
2041 sm_b.stage = Stage::SendOpCertSigningRequest;
2042 sm_b.dac_der = Some(vec![0xAA, 0xBB]);
2043 let act_b = sm_b.poll().expect("poll csr b");
2044 let nonce_b = nonce_from_csr_invoke(&act_b);
2045
2046 assert_ne!(
2047 nonce_a, nonce_b,
2048 "two independent runs should use different CSR nonces"
2049 );
2050
2051 match act_a {
2052 Action::Invoke {
2053 cluster,
2054 command,
2055 expect,
2056 ..
2057 } => {
2058 assert_eq!(cluster, 0x003E);
2059 assert_eq!(command, 0x04);
2060 assert_eq!(expect, Expectation::CsrResponse);
2061 }
2062 other => panic!("expected Invoke, got {other:?}"),
2063 }
2064 }
2065
2066 #[test]
2073 fn drive_through_send_noc_with_synthetic_noc_response() {
2074 use matter_cert::{
2075 BasicConstraints, DistinguishedName, DnAttribute, Extensions, MatterCertificate,
2076 PublicKey,
2077 };
2078
2079 let fabric = make_fabric_record();
2080 let setup = make_setup_payload();
2081 let paa = PaaTrustStore::with_example_device_roots();
2082 let cd = CdSigningRoots::with_example_device_roots();
2083 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2084 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2085 let mut sm = Commissioner::new(cfg).expect("valid config");
2086
2087 let mut key_bytes = [0u8; 65];
2093 key_bytes[0] = 0x04;
2094 let synthetic_noc = MatterCertificate::builder()
2095 .serial(vec![1, 2, 3])
2096 .issuer(fabric.root_cert.subject().clone())
2097 .subject(DistinguishedName::new(vec![
2098 DnAttribute::FabricId(fabric.fabric_id),
2099 DnAttribute::NodeId(0x2),
2100 ]))
2101 .validity(
2102 MatterTime::from_unix_secs(1_704_067_200),
2103 MatterTime::NO_EXPIRY,
2104 )
2105 .public_key(PublicKey::new(key_bytes).expect("valid sec1 prefix"))
2106 .extensions(
2107 Extensions::builder()
2108 .basic_constraints(Some(BasicConstraints::new(false, None)))
2109 .build(),
2110 )
2111 .build_unsigned()
2112 .expect("builder")
2113 .assemble([0u8; 64]);
2114
2115 sm.stage = Stage::SendNoc;
2116 sm.issued_noc = Some(synthetic_noc);
2117 sm.issued_noc_public_key = Some(key_bytes);
2118
2119 match sm.poll().expect("poll SendNoc") {
2120 Action::Invoke {
2121 cluster,
2122 command,
2123 expect,
2124 ..
2125 } => {
2126 assert_eq!(cluster, 0x003E);
2127 assert_eq!(command, 0x06);
2128 assert_eq!(expect, Expectation::NocResponse);
2129 }
2130 other => panic!("expected Invoke, got {other:?}"),
2131 }
2132
2133 let mut noc_response = Vec::new();
2135 {
2136 use matter_codec::{Tag, TlvWriter};
2137 let mut w = TlvWriter::new(&mut noc_response);
2138 w.start_structure(Tag::Anonymous).expect("infallible");
2139 w.put_uint(Tag::Context(0), 0).expect("infallible"); w.put_uint(Tag::Context(1), 1).expect("infallible"); w.end_container().expect("infallible");
2142 }
2143 sm.on_response(Expectation::NocResponse, &noc_response)
2144 .expect("NocResponse accepted");
2145 assert_eq!(sm.stage(), Stage::ReadNetworkCommissioningInfo);
2146 }
2147
2148 #[test]
2152 fn send_noc_failure_status_aborts_with_device_im_status() {
2153 use matter_cert::{
2154 BasicConstraints, DistinguishedName, DnAttribute, Extensions, MatterCertificate,
2155 PublicKey,
2156 };
2157
2158 let fabric = make_fabric_record();
2159 let setup = make_setup_payload();
2160 let paa = PaaTrustStore::with_example_device_roots();
2161 let cd = CdSigningRoots::with_example_device_roots();
2162 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2163 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2164 let mut sm = Commissioner::new(cfg).expect("valid config");
2165
2166 let mut key_bytes = [0u8; 65];
2167 key_bytes[0] = 0x04;
2168 let synthetic_noc = MatterCertificate::builder()
2169 .serial(vec![9])
2170 .issuer(fabric.root_cert.subject().clone())
2171 .subject(DistinguishedName::new(vec![
2172 DnAttribute::FabricId(fabric.fabric_id),
2173 DnAttribute::NodeId(0x2),
2174 ]))
2175 .validity(
2176 MatterTime::from_unix_secs(1_704_067_200),
2177 MatterTime::NO_EXPIRY,
2178 )
2179 .public_key(PublicKey::new(key_bytes).expect("valid sec1 prefix"))
2180 .extensions(
2181 Extensions::builder()
2182 .basic_constraints(Some(BasicConstraints::new(false, None)))
2183 .build(),
2184 )
2185 .build_unsigned()
2186 .expect("builder")
2187 .assemble([0u8; 64]);
2188
2189 sm.stage = Stage::SendNoc;
2190 sm.issued_noc = Some(synthetic_noc);
2191 sm.issued_noc_public_key = Some(key_bytes);
2192
2193 let _ = sm.poll().expect("poll SendNoc");
2194
2195 let mut bad_response = Vec::new();
2197 {
2198 use matter_codec::{Tag, TlvWriter};
2199 let mut w = TlvWriter::new(&mut bad_response);
2200 w.start_structure(Tag::Anonymous).expect("infallible");
2201 w.put_uint(Tag::Context(0), 9).expect("infallible");
2202 w.end_container().expect("infallible");
2203 }
2204 let err = sm
2205 .on_response(Expectation::NocResponse, &bad_response)
2206 .expect_err("non-zero NOC status should fail");
2207 assert!(matches!(
2208 err,
2209 CommissioningError::DeviceImStatus {
2210 stage: Stage::SendNoc,
2211 im_status: 9,
2212 }
2213 ));
2214 assert_eq!(sm.stage(), Stage::Failed);
2215 }
2216
2217 #[test]
2222 fn send_trusted_root_cert_emits_invoke_and_status_ack_advances() {
2223 let fabric = make_fabric_record();
2224 let setup = make_setup_payload();
2225 let paa = PaaTrustStore::with_example_device_roots();
2226 let cd = CdSigningRoots::with_example_device_roots();
2227 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2228 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2229 let mut sm = Commissioner::new(cfg).expect("valid config");
2230
2231 sm.stage = Stage::SendTrustedRootCert;
2232
2233 match sm.poll().expect("poll SendTrustedRootCert") {
2234 Action::Invoke {
2235 cluster,
2236 command,
2237 expect,
2238 payload,
2239 ..
2240 } => {
2241 assert_eq!(cluster, 0x003E);
2242 assert_eq!(command, 0x0B);
2243 assert_eq!(expect, Expectation::AddTrustedRootResponse);
2244 assert!(payload.len() > 16, "RCAC TLV too short: {}", payload.len());
2247 assert_eq!(payload[0], 0x15); }
2249 other => panic!("expected Invoke, got {other:?}"),
2250 }
2251
2252 sm.on_response(Expectation::AddTrustedRootResponse, &[0x00])
2254 .expect("status-ack accepted");
2255 assert_eq!(sm.stage(), Stage::SendNoc);
2256 }
2257
2258 #[test]
2261 fn find_operational_for_complete_emits_establish_case() {
2262 let fabric = make_fabric_record();
2263 let setup = make_setup_payload();
2264 let paa = PaaTrustStore::with_example_device_roots();
2265 let cd = crate::attestation::CdSigningRoots::with_example_device_roots();
2266 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2267 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2268 let mut sm = Commissioner::new(cfg).expect("valid config");
2269 sm.stage = Stage::FindOperationalForComplete;
2270 match sm.poll().expect("poll establish case") {
2271 Action::EstablishCase {
2272 fabric_id,
2273 peer_node_id,
2274 } => {
2275 assert_eq!(fabric_id, fabric.fabric_id);
2276 assert_eq!(peer_node_id, 0x2); }
2278 other => panic!("expected EstablishCase, got {other:?}"),
2279 }
2280 assert!(sm.awaiting_case_session);
2281 }
2282
2283 #[test]
2284 fn on_case_established_advances_to_send_complete() {
2285 let fabric = make_fabric_record();
2286 let setup = make_setup_payload();
2287 let paa = PaaTrustStore::with_example_device_roots();
2288 let cd = crate::attestation::CdSigningRoots::with_example_device_roots();
2289 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2290 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2291 let mut sm = Commissioner::new(cfg).expect("valid config");
2292 sm.stage = Stage::FindOperationalForComplete;
2293 let _ = sm.poll().expect("emit EstablishCase");
2294 sm.on_case_established().expect("case established");
2295 assert_eq!(sm.stage(), Stage::SendComplete);
2296 assert!(!sm.awaiting_case_session);
2297 }
2298
2299 #[test]
2300 fn on_case_established_without_pending_emits_out_of_order() {
2301 let fabric = make_fabric_record();
2302 let setup = make_setup_payload();
2303 let paa = PaaTrustStore::with_example_device_roots();
2304 let cd = crate::attestation::CdSigningRoots::with_example_device_roots();
2305 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2306 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2307 let mut sm = Commissioner::new(cfg).expect("valid config");
2308 let err = sm.on_case_established().expect_err("no pending establish");
2309 assert!(matches!(err, CommissioningError::OutOfOrderResponse(_)));
2310 }
2311
2312 #[test]
2313 fn send_complete_emits_invoke_over_case_session() {
2314 let fabric = make_fabric_record();
2315 let setup = make_setup_payload();
2316 let paa = PaaTrustStore::with_example_device_roots();
2317 let cd = crate::attestation::CdSigningRoots::with_example_device_roots();
2318 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2319 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2320 let mut sm = Commissioner::new(cfg).expect("valid config");
2321 sm.stage = Stage::SendComplete;
2322 match sm.poll().expect("poll send complete") {
2323 Action::Invoke {
2324 session,
2325 cluster,
2326 command,
2327 expect,
2328 payload,
2329 ..
2330 } => {
2331 assert_eq!(session, crate::state_machine::SessionContext::Case);
2332 assert_eq!(cluster, 0x0030);
2333 assert_eq!(command, 0x04); assert_eq!(expect, Expectation::CommissioningCompleteResponse);
2335 assert_eq!(payload, vec![0x15, 0x18]);
2337 }
2338 other => panic!("expected Invoke, got {other:?}"),
2339 }
2340 }
2341
2342 #[test]
2343 fn send_complete_success_advances_to_cleanup() {
2344 let fabric = make_fabric_record();
2345 let setup = make_setup_payload();
2346 let paa = PaaTrustStore::with_example_device_roots();
2347 let cd = crate::attestation::CdSigningRoots::with_example_device_roots();
2348 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2349 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2350 let mut sm = Commissioner::new(cfg).expect("valid config");
2351 sm.stage = Stage::SendComplete;
2352 let _ = sm.poll().expect("emit invoke");
2353 sm.on_response(
2354 Expectation::CommissioningCompleteResponse,
2355 &[0x15, 0x24, 0x00, 0x00, 0x18], )
2357 .expect("complete ok");
2358 assert_eq!(sm.stage(), Stage::Cleanup);
2359 }
2360
2361 #[test]
2362 fn cleanup_emits_done_with_noc_public_key() {
2363 let fabric = make_fabric_record();
2364 let setup = make_setup_payload();
2365 let paa = PaaTrustStore::with_example_device_roots();
2366 let cd = crate::attestation::CdSigningRoots::with_example_device_roots();
2367 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2368 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2369 let mut sm = Commissioner::new(cfg).expect("valid config");
2370 sm.stage = Stage::Cleanup;
2371 sm.issued_noc_public_key = Some([0xCA; 65]);
2372 match sm.poll().expect("poll cleanup") {
2373 Action::Done(cf) => {
2374 assert_eq!(cf.peer_node_id, 0x2);
2375 assert_eq!(cf.peer_root_public_key, [0xCA; 65]);
2376 assert_eq!(cf.terminated_at, Stage::Cleanup);
2377 assert_eq!(cf.fabric.fabric_id, fabric.fabric_id);
2378 }
2379 other => panic!("expected Done, got {other:?}"),
2380 }
2381 }
2382
2383 #[test]
2386 fn case_failed_response_aborts_with_case_establishment_failed() {
2387 let fabric = make_fabric_record();
2388 let setup = make_setup_payload();
2389 let paa = PaaTrustStore::with_example_device_roots();
2390 let cd = crate::attestation::CdSigningRoots::with_example_device_roots();
2391 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2392 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2393 let mut sm = Commissioner::new(cfg).expect("valid config");
2394
2395 sm.stage = Stage::FindOperationalForComplete;
2398 let _ = sm.poll().expect("emit EstablishCase");
2399 assert!(sm.awaiting_case_session);
2400
2401 let err = sm
2403 .on_response(Expectation::CaseFailed, &[])
2404 .expect_err("CaseFailed should error");
2405 assert!(matches!(err, CommissioningError::CaseEstablishmentFailed));
2406 assert_eq!(sm.stage(), Stage::Failed);
2407 assert!(!sm.awaiting_case_session);
2408
2409 match sm.poll().expect("emit abort") {
2411 Action::Abort {
2412 send_disarm_failsafe,
2413 reason,
2414 } => {
2415 assert!(send_disarm_failsafe);
2416 assert!(
2417 reason.contains("CASE"),
2418 "abort reason should mention CASE: {reason}"
2419 );
2420 }
2421 other => panic!("expected Abort, got {other:?}"),
2422 }
2423 }
2424
2425 #[test]
2426 fn case_failed_when_not_awaiting_returns_out_of_order() {
2427 let fabric = make_fabric_record();
2428 let setup = make_setup_payload();
2429 let paa = PaaTrustStore::with_example_device_roots();
2430 let cd = crate::attestation::CdSigningRoots::with_example_device_roots();
2431 let rng: Arc<dyn crate::noc::NocRng> = Arc::new(SystemNocRng);
2432 let cfg = base_config(&fabric, &setup, &paa, &cd, rng);
2433 let mut sm = Commissioner::new(cfg).expect("valid config");
2434 let err = sm
2435 .on_response(Expectation::CaseFailed, &[])
2436 .expect_err("CaseFailed without pending should error");
2437 assert!(matches!(err, CommissioningError::OutOfOrderResponse(_)));
2438 }
2439
2440 fn sample_valid_config() -> CommissionerConfig<'static> {
2444 use std::sync::OnceLock;
2445 static FABRIC: OnceLock<FabricRecord> = OnceLock::new();
2446 static SETUP: OnceLock<SetupPayload> = OnceLock::new();
2447 static PAA: OnceLock<PaaTrustStore> = OnceLock::new();
2448 static CD: OnceLock<crate::attestation::CdSigningRoots> = OnceLock::new();
2449
2450 let fabric = FABRIC.get_or_init(make_fabric_record);
2451 let setup = SETUP.get_or_init(make_setup_payload);
2452 let paa = PAA.get_or_init(PaaTrustStore::with_example_device_roots);
2453 let cd = CD.get_or_init(crate::attestation::CdSigningRoots::with_example_device_roots);
2454 let rng: Arc<dyn NocRng> = Arc::new(SystemNocRng);
2455
2456 CommissionerConfig {
2457 pase_attestation_challenge: [0u8; 16],
2458 fabric,
2459 setup_payload: setup,
2460 paa_trust_store: paa,
2461 cd_signing_roots: cd,
2462 commissioner_node_id: 0x1,
2463 assigned_node_id: 0x2,
2464 ipk_epoch_key: [0x42_u8; 16],
2465 case_admin_subject: 0x1,
2466 admin_vendor_id: 0xFFF1,
2467 now: MatterTime::from_unix_secs(1_704_067_200),
2468 rng,
2469 network: NetworkCredentials::AlreadyOnNetwork,
2470 }
2471 }
2472
2473 #[test]
2474 fn empty_ssid_is_rejected() {
2475 let mut config = sample_valid_config();
2476 config.network = NetworkCredentials::WiFi(WiFiCredentials {
2477 ssid: vec![],
2478 credentials: vec![],
2479 });
2480 let Err(err) = Commissioner::new(config) else {
2481 panic!("empty ssid should fail");
2482 };
2483 assert!(
2484 matches!(err, CommissioningError::InvalidConfig(m) if m.contains("ssid")),
2485 "got {err:?}",
2486 );
2487 }
2488
2489 #[test]
2490 fn oversize_ssid_is_rejected() {
2491 let mut config = sample_valid_config();
2492 config.network = NetworkCredentials::WiFi(WiFiCredentials {
2493 ssid: vec![b'a'; 33],
2494 credentials: vec![],
2495 });
2496 let Err(err) = Commissioner::new(config) else {
2497 panic!("33-byte ssid should fail");
2498 };
2499 assert!(
2500 matches!(err, CommissioningError::InvalidConfig(m) if m.contains("≤32")),
2501 "got {err:?}",
2502 );
2503 }
2504
2505 #[test]
2506 fn oversize_credentials_is_rejected() {
2507 let mut config = sample_valid_config();
2508 config.network = NetworkCredentials::WiFi(WiFiCredentials {
2509 ssid: b"matter".to_vec(),
2510 credentials: vec![0u8; 65],
2511 });
2512 let Err(err) = Commissioner::new(config) else {
2513 panic!("65-byte credentials should fail");
2514 };
2515 assert!(
2516 matches!(err, CommissioningError::InvalidConfig(m) if m.contains("≤64")),
2517 "got {err:?}",
2518 );
2519 }
2520
2521 #[test]
2522 fn wifi_credentials_none_is_accepted() {
2523 let mut config = sample_valid_config();
2524 config.network = NetworkCredentials::AlreadyOnNetwork;
2525 Commissioner::new(config).expect("AlreadyOnNetwork should pass validation");
2526 }
2527
2528 #[test]
2536 fn network_commissioning_cluster_absent_skips_provisioning_when_already_on_network() {
2537 let mut config = sample_valid_config();
2538 config.network = NetworkCredentials::AlreadyOnNetwork;
2539 let mut sm = Commissioner::new(config).expect("valid config");
2540 sm.stage = Stage::ReadNetworkCommissioningInfo;
2541 match sm.poll().expect("poll emits the read") {
2542 Action::ReadAttribute { expect, .. } => {
2543 assert_eq!(expect, Expectation::NetworkCommissioningInfo);
2544 }
2545 other => panic!("expected ReadAttribute, got {other:?}"),
2546 }
2547
2548 sm.on_response(Expectation::NetworkCommissioningInfo, &[])
2549 .expect("absent cluster is acceptable when already on network");
2550 assert_eq!(sm.stage(), Stage::EvictPreviousCaseSessions);
2551 }
2552
2553 #[test]
2558 fn network_commissioning_cluster_absent_rejects_wifi_provisioning() {
2559 use crate::state_machine::NetworkKind;
2560
2561 let mut config = sample_valid_config();
2562 config.network = NetworkCredentials::WiFi(WiFiCredentials {
2563 ssid: b"matter".to_vec(),
2564 credentials: b"hunter22".to_vec(),
2565 });
2566 let mut sm = Commissioner::new(config).expect("valid config");
2567 sm.stage = Stage::ReadNetworkCommissioningInfo;
2568 let _ = sm.poll().expect("poll emits the read");
2569
2570 let Err(err) = sm.on_response(Expectation::NetworkCommissioningInfo, &[]) else {
2571 panic!("absent cluster with Wi-Fi credentials must fail");
2572 };
2573 assert!(
2574 matches!(
2575 err,
2576 CommissioningError::NetworkFeatureUnsupported {
2577 needed: NetworkKind::WiFi,
2578 }
2579 ),
2580 "got {err:?}",
2581 );
2582 }
2583
2584 #[test]
2585 fn network_credentials_thread_variant_accepted() {
2586 let ds =
2589 crate::thread_dataset::ThreadDataset::new(vec![0x02, 0x08, 0, 0, 0, 0, 0, 0, 0, 0])
2590 .expect("minimal ext-pan-id dataset is valid");
2591 let mut config = sample_valid_config();
2592 config.network = NetworkCredentials::Thread(ds);
2593 let c = Commissioner::new(config).expect("Thread network should pass validation");
2594 assert!(matches!(c.network(), NetworkCredentials::Thread(_)));
2595 }
2596
2597 #[test]
2598 fn wifi_credentials_debug_redacts_passphrase() {
2599 let creds = WiFiCredentials {
2600 ssid: b"matter".to_vec(),
2601 credentials: b"hunter22".to_vec(),
2602 };
2603 let rendered = format!("{creds:?}");
2604 assert!(
2605 !rendered.contains("hunter22"),
2606 "Debug must not contain credentials bytes: {rendered}",
2607 );
2608 assert!(rendered.contains("redacted"), "got {rendered}");
2609 assert!(
2610 rendered.contains('8'),
2611 "credentials length should appear: {rendered}"
2612 );
2613 assert!(
2614 rendered.contains('6'),
2615 "ssid length should appear: {rendered}"
2616 );
2617 }
2618
2619 #[test]
2622 fn failsafe_expiry_derives_from_basic_commissioning_info() {
2623 let mut sm = Commissioner::new(sample_valid_config()).expect("valid config");
2624 let _initial = sm.poll().expect("initial poll");
2626 let response = vec![
2627 0x15, 0x25, 0x00, 0x78, 0x00, 0x18,
2629 ];
2630 sm.on_response(Expectation::CommissioningInfo, &response)
2631 .expect("commissioning info accepted");
2632 let action = sm.poll().expect("arm-failsafe poll");
2634 match action {
2635 Action::Invoke {
2636 payload,
2637 cluster,
2638 command,
2639 ..
2640 } => {
2641 assert_eq!(cluster, 0x0030);
2642 assert_eq!(command, 0x00);
2643 assert!(
2646 payload.windows(3).any(|w| w == [0x24, 0x00, 0x78]),
2647 "ArmFailSafe payload should carry expiry=120: {payload:02x?}",
2648 );
2649 }
2650 other => panic!("expected Invoke, got {other:?}"),
2651 }
2652 }
2653
2654 #[test]
2655 fn failsafe_expiry_capped_at_max_cumulative_failsafe_seconds() {
2656 let mut sm = Commissioner::new(sample_valid_config()).expect("valid config");
2657 let _initial = sm.poll().expect("initial poll");
2658 let response = vec![
2662 0x15, 0x25, 0x00, 0x78, 0x00, 0x25, 0x01, 0x5A, 0x00, 0x18,
2666 ];
2667 sm.on_response(Expectation::CommissioningInfo, &response)
2668 .expect("commissioning info accepted");
2669 let action = sm.poll().expect("arm-failsafe poll");
2670 match action {
2671 Action::Invoke { payload, .. } => {
2672 assert!(
2673 payload.windows(3).any(|w| w == [0x24, 0x00, 0x5A]),
2674 "ArmFailSafe expiry must be capped to 90: {payload:02x?}",
2675 );
2676 assert!(
2677 !payload.windows(3).any(|w| w == [0x24, 0x00, 0x78]),
2678 "the uncapped 120 must NOT be sent: {payload:02x?}",
2679 );
2680 }
2681 other => panic!("expected Invoke, got {other:?}"),
2682 }
2683 }
2684
2685 #[test]
2686 fn failsafe_expiry_falls_back_to_60_on_empty_basic_commissioning_info() {
2687 let mut sm = Commissioner::new(sample_valid_config()).expect("valid config");
2688 let _initial = sm.poll().expect("initial poll");
2689 sm.on_response(Expectation::CommissioningInfo, &[0x15, 0x18])
2693 .expect("empty struct accepted");
2694 let action = sm.poll().expect("arm-failsafe poll");
2695 if let Action::Invoke { payload, .. } = action {
2696 assert!(
2698 payload.windows(3).any(|w| w == [0x24, 0x00, 0x3C]),
2699 "ArmFailSafe payload should carry expiry=60 fallback: {payload:02x?}",
2700 );
2701 }
2702 }
2703
2704 #[test]
2707 fn breadcrumb_increases_across_commands() {
2708 fn extract_uint_at_tag(payload: &[u8], tag_num: u8) -> Option<u64> {
2709 use matter_codec::{Element, Tag, TlvReader, Value};
2710 let mut reader = TlvReader::new(payload);
2711 while let Ok(Some(elem)) = reader.next() {
2712 if let Element::Scalar {
2713 tag: Tag::Context(t),
2714 value: Value::Uint(v),
2715 } = elem
2716 {
2717 if t == tag_num {
2718 return Some(v);
2719 }
2720 }
2721 }
2722 None
2723 }
2724
2725 let mut sm = Commissioner::new(sample_valid_config()).expect("valid config");
2726 let mut breadcrumbs: Vec<u64> = Vec::new();
2727
2728 let _ = sm.poll().expect("read commissioning info");
2730 sm.on_response(Expectation::CommissioningInfo, &[0x15, 0x18])
2731 .expect("info accepted");
2732
2733 let action = sm.poll().expect("arm-failsafe");
2735 if let Action::Invoke { payload, .. } = action {
2736 if let Some(b) = extract_uint_at_tag(&payload, 1) {
2737 breadcrumbs.push(b);
2738 }
2739 }
2740 sm.on_response(
2741 Expectation::ArmFailsafeResponse,
2742 &[0x15, 0x24, 0x00, 0x00, 0x18],
2743 )
2744 .expect("arm-failsafe ok");
2745
2746 let action = sm.poll().expect("set-regulatory");
2748 if let Action::Invoke { payload, .. } = action {
2749 if let Some(b) = extract_uint_at_tag(&payload, 2) {
2750 breadcrumbs.push(b);
2751 }
2752 }
2753
2754 assert_eq!(
2755 breadcrumbs.len(),
2756 2,
2757 "should have extracted exactly two breadcrumbs, got {breadcrumbs:?}",
2758 );
2759 assert!(
2760 breadcrumbs[0] < breadcrumbs[1],
2761 "breadcrumbs should be strictly increasing: {breadcrumbs:?}",
2762 );
2763 }
2764}