1use rand_core::RngCore;
21
22use crate::crypto::Crypto;
23use crate::dm::{Cluster, Dataver, InvokeContext, ReadContext};
24use crate::error::{Error, ErrorCode};
25use crate::sc::pase::spake2p::SPAKE2P_VERIFIER_SALT_ZEROED;
26use crate::sc::pase::{CommWindowOpener, CommWindowType};
27use crate::tlv::Nullable;
28use crate::MatterState;
29
30pub use crate::dm::clusters::decl::administrator_commissioning::*;
31use crate::transport::exchange::ExchangeId;
32use crate::transport::session::SessionMode;
33
34const MIN_PBKDF_ITERATIONS: u32 = 1000;
36const MAX_PBKDF_ITERATIONS: u32 = 100_000;
37
38const MIN_PAKE_SALT_LEN: usize = 16;
40const MAX_PAKE_SALT_LEN: usize = 32;
41
42const PAKE_VERIFIER_LEN: usize = 97;
45
46fn cluster_status_err(ctx: &impl InvokeContext, status: StatusCode) -> Error {
50 ctx.cmd().set_cluster_status(status as u8);
51 Error::new(ErrorCode::Failure)
52}
53
54#[derive(Debug, Clone)]
56#[cfg_attr(feature = "defmt", derive(defmt::Format))]
57pub struct AdminCommHandler {
58 dataver: Dataver,
59}
60
61impl AdminCommHandler {
62 pub const fn new(dataver: Dataver) -> Self {
64 Self { dataver }
65 }
66
67 pub const fn adapt(self) -> HandlerAdaptor<Self> {
69 HandlerAdaptor(self)
70 }
71
72 fn current_window_opener(state: &mut MatterState, id: &ExchangeId) -> Option<CommWindowOpener> {
77 let session = id.session(&mut state.sessions);
78
79 match session.get_session_mode() {
80 SessionMode::Case { fab_idx, .. } => Some(CommWindowOpener {
81 fab_idx: *fab_idx,
82 vendor_id: unwrap!(state.fabrics.get(*fab_idx)).vendor_id(),
83 }),
84 _ => None,
85 }
86 }
87}
88
89impl ClusterHandler for AdminCommHandler {
90 const CLUSTER: Cluster<'static> = FULL_CLUSTER.with_features(Feature::BASIC.bits());
91
92 fn dataver(&self) -> u32 {
93 self.dataver.get()
94 }
95
96 fn dataver_changed(&self) {
97 self.dataver.changed();
98 }
99
100 fn window_status(&self, ctx: impl ReadContext) -> Result<CommissioningWindowStatusEnum, Error> {
101 let notify_mdns = || ctx.exchange().matter().transport().notify_mdns_changed();
102 let notify_change = |_, _| ctx.notify_own_cluster_changed();
103
104 ctx.exchange().with_state(|state| {
105 state
106 .pase
107 .check_comm_window_timeout(notify_mdns, notify_change)?;
108
109 let comm_window = state.pase.comm_window();
110
111 let window_type = comm_window.map(|comm_window| comm_window.comm_window_type());
112
113 Ok(match window_type {
114 Some(CommWindowType::Basic) => CommissioningWindowStatusEnum::BasicWindowOpen,
115 Some(CommWindowType::Enhanced) => CommissioningWindowStatusEnum::EnhancedWindowOpen,
116 None => CommissioningWindowStatusEnum::WindowNotOpen,
117 })
118 })
119 }
120
121 fn admin_fabric_index(&self, ctx: impl ReadContext) -> Result<Nullable<u8>, Error> {
122 let notify_mdns = || ctx.exchange().matter().transport().notify_mdns_changed();
123 let notify_change = |_, _| ctx.notify_own_cluster_changed();
124
125 ctx.exchange().with_state(|state| {
126 state
127 .pase
128 .check_comm_window_timeout(notify_mdns, notify_change)?;
129
130 let comm_window = state.pase.comm_window();
131
132 if let Some(opener) = comm_window.and_then(|comm_window| comm_window.opener()) {
133 if state.fabrics.get(opener.fab_idx).is_some() {
134 return Ok(Nullable::some(opener.fab_idx.get()));
137 }
138 }
139
140 Ok(Nullable::none())
141 })
142 }
143
144 fn admin_vendor_id(&self, ctx: impl ReadContext) -> Result<Nullable<u16>, Error> {
145 let notify_mdns = || ctx.exchange().matter().transport().notify_mdns_changed();
146 let notify_change = |_, _| ctx.notify_own_cluster_changed();
147
148 ctx.exchange().with_state(|state| {
149 state
150 .pase
151 .check_comm_window_timeout(notify_mdns, notify_change)?;
152
153 let comm_window = state.pase.comm_window();
154
155 Ok(Nullable::new(
156 comm_window
157 .and_then(|comm_window| comm_window.opener())
158 .map(|opener| opener.vendor_id),
159 ))
160 })
161 }
162
163 fn handle_open_commissioning_window(
164 &self,
165 ctx: impl InvokeContext,
166 request: OpenCommissioningWindowRequest<'_>,
167 ) -> Result<(), Error> {
168 let notify_mdns = || ctx.exchange().matter().transport().notify_mdns_changed();
169 let notify_change = |_, _| ctx.notify_own_cluster_changed();
170
171 let iterations = request.iterations()?;
175 if !(MIN_PBKDF_ITERATIONS..=MAX_PBKDF_ITERATIONS).contains(&iterations) {
176 return Err(cluster_status_err(&ctx, StatusCode::PAKEParameterError));
177 }
178
179 let salt = request.salt()?;
180 if !(MIN_PAKE_SALT_LEN..=MAX_PAKE_SALT_LEN).contains(&salt.0.len()) {
181 return Err(cluster_status_err(&ctx, StatusCode::PAKEParameterError));
182 }
183
184 let verifier = request.pake_passcode_verifier()?;
185 if verifier.0.len() != PAKE_VERIFIER_LEN {
186 return Err(cluster_status_err(&ctx, StatusCode::PAKEParameterError));
187 }
188
189 ctx.exchange().with_state(|state| {
190 state
191 .pase
192 .check_comm_window_timeout(notify_mdns, notify_change)?;
193
194 let opener = Self::current_window_opener(state, &ctx.exchange().id());
195
196 let mdns_id = ctx.crypto().rand()?.next_u64();
197
198 state
199 .pase
200 .open_comm_window(
201 mdns_id,
202 verifier.0.try_into()?,
203 salt.0,
204 iterations,
205 request.discriminator()?,
206 request.commissioning_timeout()?,
207 opener,
208 notify_mdns,
209 notify_change,
210 )
211 .map_err(|err| map_open_window_err(&ctx, err))
212 })
213 }
214
215 fn handle_open_basic_commissioning_window(
216 &self,
217 ctx: impl InvokeContext,
218 request: OpenBasicCommissioningWindowRequest<'_>,
219 ) -> Result<(), Error> {
220 let notify_mdns = || ctx.exchange().matter().transport().notify_mdns_changed();
221 let notify_change = |_, _| ctx.notify_own_cluster_changed();
222
223 ctx.exchange().with_state(|state| {
224 state
225 .pase
226 .check_comm_window_timeout(notify_mdns, notify_change)?;
227
228 let opener = Self::current_window_opener(state, &ctx.exchange().id());
229 let dev_comm = ctx.exchange().matter().dev_comm();
230
231 let crypto = ctx.crypto();
232 let mut rand = crypto.rand()?;
233
234 let mdns_id = rand.next_u64();
235
236 let mut salt = SPAKE2P_VERIFIER_SALT_ZEROED;
237 rand.fill_bytes(salt.access_mut());
238
239 state
240 .pase
241 .open_basic_comm_window(
242 mdns_id,
243 salt.access(),
244 dev_comm.password.reference(),
245 dev_comm.discriminator,
246 request.commissioning_timeout()?,
247 opener,
248 notify_mdns,
249 notify_change,
250 )
251 .map_err(|err| map_open_window_err(&ctx, err))
252 })
253 }
254
255 fn handle_revoke_commissioning(&self, ctx: impl InvokeContext) -> Result<(), Error> {
256 let notify_mdns = || ctx.exchange().matter().transport().notify_mdns_changed();
257 let notify_change = |_, _| ctx.notify_own_cluster_changed();
258
259 let removed_fabric = ctx.exchange().with_state(|state| {
279 let sess = ctx.exchange().id().session(&mut state.sessions);
285 let expire_sess_id = matches!(
286 sess.get_session_mode(),
287 crate::transport::session::SessionMode::Pase { .. }
288 )
289 .then(|| sess.id());
290
291 let removed_fabric = state.failsafe.expire(
292 &mut state.fabrics,
293 &mut state.sessions,
294 expire_sess_id,
295 ctx.networks(),
296 ctx.kv(),
297 notify_mdns,
298 notify_change,
299 )?;
300
301 ctx.exchange().matter().transport().notify_session_removed();
302
303 Ok::<_, Error>(removed_fabric)
304 })?;
305
306 if let Some(fab_idx) = removed_fabric {
311 ctx.notify_fabric_removed(fab_idx);
312 }
313
314 ctx.exchange().with_state(|state| {
315 state
316 .pase
317 .close_comm_window(notify_mdns, notify_change)
318 .map_err(|err| map_revoke_err(&ctx, err))
319 })?;
320
321 Ok(())
322 }
323}
324
325fn map_open_window_err(ctx: &impl InvokeContext, err: Error) -> Error {
331 if err.code() == ErrorCode::Busy {
332 cluster_status_err(ctx, StatusCode::Busy)
333 } else {
334 err
335 }
336}
337
338fn map_revoke_err(ctx: &impl InvokeContext, err: Error) -> Error {
342 if matches!(err.code(), ErrorCode::Invalid) {
343 cluster_status_err(ctx, StatusCode::WindowNotOpen)
344 } else {
345 err
346 }
347}