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