matter_controller/error.rs
1//! Error type for `matter-controller`.
2
3use crate::store::StoreError;
4
5/// Errors surfaced by the controller's persistence and identity layer.
6///
7/// `#[non_exhaustive]` so later sub-phases can add networked variants
8/// (e.g. `SessionLost`, `DeviceUnreachable`) without a breaking change.
9#[derive(Debug, thiserror::Error)]
10#[non_exhaustive]
11pub enum Error {
12 /// The backing [`ControllerStore`](crate::store::ControllerStore) failed.
13 #[error("store error: {0}")]
14 Store(#[from] StoreError),
15
16 /// TLV encode/decode of the snapshot blob failed.
17 #[error("TLV codec error: {0}")]
18 Codec(#[from] matter_codec::Error),
19
20 /// A certificate failed to parse or serialize.
21 #[error("certificate error: {0}")]
22 Cert(#[from] matter_cert::Error),
23
24 /// NOC/RCAC issuance failed.
25 #[error("NOC issuance error: {0}")]
26 Noc(#[from] matter_commissioning::NocError),
27
28 /// A signing key could not be generated or reconstructed.
29 #[error("signer error: {0}")]
30 Signer(String),
31
32 /// The persisted snapshot was structurally invalid or an unknown version.
33 #[error("malformed snapshot: {0}")]
34 Snapshot(String),
35
36 /// CASE session establishment failed, or a driver operation errored.
37 #[error("driver error: {0}")]
38 Driver(#[from] matter_commissioning::driver::DriverError),
39
40 /// A transport / session-manager (framing, MRP) operation failed.
41 #[error("transport error: {0}")]
42 Transport(#[from] matter_transport::Error),
43
44 /// No fabric exists, or the requested node/fabric is not addressable.
45 #[error("not commissioned: {0}")]
46 NotCommissioned(String),
47
48 /// The owning controller task has stopped (channel closed).
49 #[error("controller task is no longer running")]
50 ControllerStopped,
51
52 /// An Interaction-Model request/response failed to build or parse.
53 #[error("interaction model error: {0}")]
54 InteractionModel(#[from] matter_interaction::ImError),
55
56 /// An operational-path failure with a human-readable detail — a key
57 /// derivation (operational IPK / compressed fabric id), a transport/session
58 /// send or decode, a request timeout, or a subscription liveness timeout.
59 #[error("operational error: {0}")]
60 Operational(String),
61
62 /// Attestation trust material could not be loaded.
63 #[error("attestation trust error: {0}")]
64 Trust(String),
65
66 /// The setup code (QR / manual) could not be parsed.
67 #[error("invalid setup code: {0}")]
68 SetupCode(String),
69
70 /// No attestation trust configured; commissioning cannot verify the device.
71 #[error(
72 "no attestation trust configured — commissioning cannot verify the device's \
73 attestation. Build the controller with MatterController::builder(store)\
74 .attestation_trust(AttestationTrust::from_dirs(paa_dir, cd_dir)).build(), \
75 not MatterController::open(store)"
76 )]
77 NoTrust,
78
79 /// An `AdministratorCommissioning` command returned a non-success IM status
80 /// (e.g. 0x02 Busy, 0x03 `PAKEParameterError`, 0x04 `WindowNotOpen` reported as
81 /// a cluster status). The raw IM status byte is preserved.
82 #[error("commissioning window command rejected (IM status {0:#04x})")]
83 CommissioningWindowRejected(u8),
84
85 /// Refused to remove the controller's own fabric (would sever the CASE
86 /// session and orphan persisted device state). No `force` override exists.
87 #[error("refusing to remove our own fabric (would orphan the device)")]
88 WouldRemoveSelf,
89
90 /// An `OperationalCredentials` command returned a non-success
91 /// `NodeOperationalCertStatusEnum` (e.g. 7 `InvalidFabricIndex`). Raw code preserved.
92 #[error("operational-credentials command rejected (status {0})")]
93 OperationalCredentialsRejected(u8),
94
95 /// Refused an ACL write that would strip our own administrative access
96 /// (no Administer/CASE entry covering our commissioner node id). Prevents
97 /// orphaning the device. Checked before any bytes are sent.
98 #[error("refusing ACL write: it would remove our own administrative access")]
99 AclWouldLockOut,
100
101 /// A `Groups` / `GroupKeyManagement` command returned a non-success status
102 /// (e.g. `ResourceExhausted` from `MaxGroupsPerFabric`). Raw status preserved.
103 #[error("group command rejected (status {0})")]
104 GroupCommandRejected(u8),
105
106 /// A group send (`invoke_group`) named a `key_set_id` that has not been
107 /// provisioned on the controller's fabric (no matching
108 /// [`GroupKeySetConfig`](crate::GroupKeySetConfig) in `group_keys`). Call
109 /// [`MatterController::create_group`](crate::MatterController::create_group)
110 /// first to mint and persist the key set.
111 #[error("group key set {0} is not provisioned on this fabric")]
112 GroupNotProvisioned(u16),
113}
114
115impl Error {
116 /// If this error is the device rejecting the supplied network-credential
117 /// *type* — e.g. Thread credentials handed to a Wi-Fi-only device
118 /// (`NetworkCommissioning::FeatureMap` lacks the needed bit) — returns
119 /// which network type the credentials required. Use this to route to a
120 /// different credential type instead of substring-matching the rendered
121 /// message.
122 ///
123 /// Returns `None` for every other error.
124 #[must_use]
125 pub fn network_feature_unsupported(&self) -> Option<matter_commissioning::NetworkKind> {
126 match self {
127 Error::Driver(matter_commissioning::driver::DriverError::Commissioning(
128 matter_commissioning::CommissioningError::NetworkFeatureUnsupported { needed },
129 )) => Some(*needed),
130 _ => None,
131 }
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 #[test]
138 fn no_trust_error_names_the_fix() {
139 let msg = crate::error::Error::NoTrust.to_string();
140 assert!(
141 msg.contains("attestation_trust"),
142 "NoTrust must name the builder fix: {msg}"
143 );
144 assert!(
145 msg.contains("from_dirs"),
146 "NoTrust must name from_dirs: {msg}"
147 );
148 }
149
150 #[test]
151 fn network_feature_unsupported_is_typed_through_the_chain() {
152 use matter_commissioning::{driver::DriverError, CommissioningError, NetworkKind};
153
154 // The nested chain a commission failure actually produces.
155 let e = crate::error::Error::Driver(DriverError::Commissioning(
156 CommissioningError::NetworkFeatureUnsupported {
157 needed: NetworkKind::Thread,
158 },
159 ));
160 assert_eq!(e.network_feature_unsupported(), Some(NetworkKind::Thread));
161
162 // The substring WeaveHome matched still renders through the chain
163 // (belt for the matter-commissioning pin's braces).
164 assert!(e
165 .to_string()
166 .contains("does not support Thread network type"));
167
168 // Unrelated errors: None.
169 assert_eq!(
170 crate::error::Error::ControllerStopped.network_feature_unsupported(),
171 None
172 );
173 let other = crate::error::Error::Driver(DriverError::Commissioning(
174 CommissioningError::CaseEstablishmentFailed,
175 ));
176 assert_eq!(other.network_feature_unsupported(), None);
177 }
178}