1use core::fmt::Debug;
21
22use either::Either;
23
24use rand_core::RngCore;
25
26use crate::crypto::Crypto;
27use crate::dm::clusters::net_comm::NetworksAccess;
28use crate::dm::{Cluster, Dataver, InvokeContext, OperationContext, ReadContext, WriteContext};
29use crate::error::{Error, ErrorCode};
30use crate::fabric::FabricPersist;
31use crate::persist::{Persist, NETWORKS_KEY};
32use crate::sc::pase::MAX_COMM_WINDOW_TIMEOUT_SECS;
33use crate::tlv::{Nullable, Octets, OctetsBuilder, TLVBuilderParent};
34use crate::transport::session::SessionMode;
35use crate::utils::sync::DynBase;
36use crate::{except, with, MatterState};
37
38pub use crate::dm::clusters::decl::general_commissioning::*;
39
40impl CommissioningErrorEnum {
41 fn map(result: Result<(), Error>) -> Result<Self, Error> {
42 Self::map_result(result).map(Self::ok)
43 }
44
45 fn map_result<T>(result: Result<T, Error>) -> Result<Either<T, Self>, Error> {
46 match result {
47 Ok(value) => Ok(Either::Left(value)),
48 Err(err) => match err.code() {
49 ErrorCode::Busy | ErrorCode::NocInvalidFabricIndex => {
50 Ok(Either::Right(Self::BusyWithOtherAdmin))
51 }
52 ErrorCode::GennCommInvalidAuthentication => {
53 Ok(Either::Right(Self::InvalidAuthentication))
54 }
55 ErrorCode::FailSafeRequired => Ok(Either::Right(Self::NoFailSafe)),
56 _ => Err(err),
57 },
58 }
59 }
60
61 fn ok<T>(value: Either<T, Self>) -> Self {
62 match value {
63 Either::Left(_) => Self::OK,
64 Either::Right(code) => code,
65 }
66 }
67}
68
69pub trait CommPolicy: DynBase {
72 fn concurrent_connection_supported(&self) -> bool;
75
76 fn failsafe_expiry_len_secs(&self) -> u16;
78
79 fn failsafe_max_cml_secs(&self) -> u16;
81
82 fn location_cap(&self) -> RegulatoryLocationTypeEnum;
84}
85
86impl<T> CommPolicy for &T
87where
88 T: CommPolicy,
89{
90 fn concurrent_connection_supported(&self) -> bool {
91 (*self).concurrent_connection_supported()
92 }
93
94 fn failsafe_expiry_len_secs(&self) -> u16 {
95 (*self).failsafe_expiry_len_secs()
96 }
97
98 fn failsafe_max_cml_secs(&self) -> u16 {
99 (*self).failsafe_max_cml_secs()
100 }
101
102 fn location_cap(&self) -> RegulatoryLocationTypeEnum {
103 (*self).location_cap()
104 }
105}
106
107impl DynBase for bool {}
108
109impl CommPolicy for bool {
110 fn concurrent_connection_supported(&self) -> bool {
111 *self
112 }
113
114 fn failsafe_expiry_len_secs(&self) -> u16 {
115 120
116 }
117
118 fn failsafe_max_cml_secs(&self) -> u16 {
119 MAX_COMM_WINDOW_TIMEOUT_SECS
126 }
127
128 fn location_cap(&self) -> RegulatoryLocationTypeEnum {
129 RegulatoryLocationTypeEnum::IndoorOutdoor
130 }
131}
132
133pub struct GenCommHandler<'a> {
135 dataver: Dataver,
136 commissioning_policy: &'a dyn CommPolicy,
137}
138
139impl<'a> GenCommHandler<'a> {
140 pub const fn new(dataver: Dataver, commissioning_policy: &'a dyn CommPolicy) -> Self {
142 Self {
143 dataver,
144 commissioning_policy,
145 }
146 }
147
148 pub const fn adapt(self) -> HandlerAdaptor<Self> {
150 HandlerAdaptor(self)
151 }
152
153 pub(crate) fn with_armed_failsafe<F, T>(ctx: impl OperationContext, f: F) -> Result<T, Error>
158 where
159 F: FnOnce(&mut MatterState, &mut dyn FnMut()) -> Result<T, Error>,
160 {
161 Self::with_armed_failsafe_ex(ctx, f)
162 }
163
164 fn is_regulatory_config_supported(
168 policy: &dyn CommPolicy,
169 new_config: RegulatoryLocationTypeEnum,
170 ) -> bool {
171 match policy.location_cap() {
172 RegulatoryLocationTypeEnum::Indoor => {
173 matches!(new_config, RegulatoryLocationTypeEnum::Indoor)
174 }
175 RegulatoryLocationTypeEnum::Outdoor => {
176 matches!(new_config, RegulatoryLocationTypeEnum::Outdoor)
177 }
178 RegulatoryLocationTypeEnum::IndoorOutdoor => true,
179 }
180 }
181
182 pub(crate) fn with_armed_failsafe_ex<F, T, E>(ctx: impl OperationContext, f: F) -> Result<T, E>
187 where
188 F: FnOnce(&mut MatterState, &mut dyn FnMut()) -> Result<T, E>,
189 E: From<Error>,
190 {
191 let mut notify_mdns = || ctx.exchange().matter().transport().notify_mdns_changed();
192
193 ctx.exchange().with_state_ex(|state| {
194 let sess = ctx.exchange().id().session(&mut state.sessions);
195
196 state
197 .failsafe
198 .check_armed(sess.get_session_mode())
199 .map_err(|err| match err.code() {
200 ErrorCode::NocInvalidFabricIndex => {
201 Error::new(ErrorCode::GennCommInvalidAuthentication)
202 }
203 _ => err,
204 })?;
205
206 f(state, &mut notify_mdns)
207 })
208 }
209
210 fn recovery_identifier_value(ctx: &impl ReadContext) -> Result<u64, Error> {
213 if let Some(id) = ctx
216 .exchange()
217 .with_state(|state| Ok(state.basic_info_settings.recovery_identifier))?
218 {
219 return Ok(id);
220 }
221
222 let fresh = ctx.crypto().rand()?.next_u64();
224
225 let mut persist = Persist::new(ctx.kv());
226
227 let id = ctx.exchange().with_state(|state| {
228 let id = *state
231 .basic_info_settings
232 .recovery_identifier
233 .get_or_insert(fresh);
234
235 state.basic_info_settings.store_persist(&mut persist)?;
236
237 Ok(id)
238 })?;
239
240 persist.run()?;
241
242 Ok(id)
243 }
244}
245
246pub const CLUSTER_NETWORK_RECOVERY: Cluster<'static> = FULL_CLUSTER
258 .with_attrs(
259 with!(required; AttributeId::RecoveryIdentifier | AttributeId::NetworkRecoveryReason),
260 )
261 .with_cmds(except!(CommandId::SetTCAcknowledgements))
262 .with_features(Feature::NETWORK_RECOVERY.bits());
263
264impl ClusterHandler for GenCommHandler<'_> {
265 const CLUSTER: Cluster<'static> = FULL_CLUSTER
266 .with_attrs(with!(required))
267 .with_cmds(except!(CommandId::SetTCAcknowledgements));
268
269 fn dataver(&self) -> u32 {
270 self.dataver.get()
271 }
272
273 fn dataver_changed(&self) {
274 self.dataver.changed();
275 }
276
277 fn breadcrumb(&self, ctx: impl ReadContext) -> Result<u64, Error> {
278 ctx.exchange()
279 .with_state(|state| Ok(state.failsafe.breadcrumb()))
280 }
281
282 fn set_breadcrumb(&self, ctx: impl WriteContext, value: u64) -> Result<(), Error> {
283 ctx.exchange().with_state(|state| {
284 state.failsafe.set_breadcrumb(value);
285
286 Ok(())
287 })
288 }
289
290 fn basic_commissioning_info<P: TLVBuilderParent>(
291 &self,
292 _ctx: impl ReadContext,
293 builder: BasicCommissioningInfoBuilder<P>,
294 ) -> Result<P, Error> {
295 builder
296 .fail_safe_expiry_length_seconds(self.commissioning_policy.failsafe_expiry_len_secs())?
297 .max_cumulative_failsafe_seconds(self.commissioning_policy.failsafe_max_cml_secs())?
298 .end()
299 }
300
301 fn regulatory_config(
302 &self,
303 ctx: impl ReadContext,
304 ) -> Result<RegulatoryLocationTypeEnum, Error> {
305 ctx.exchange().with_state(|state| {
309 Ok(state
310 .basic_info_settings
311 .location_type
312 .unwrap_or(self.commissioning_policy.location_cap()))
313 })
314 }
315
316 fn location_capability(
317 &self,
318 _ctx: impl ReadContext,
319 ) -> Result<RegulatoryLocationTypeEnum, Error> {
320 Ok(self.commissioning_policy.location_cap())
321 }
322
323 fn supports_concurrent_connection(&self, _ctx: impl ReadContext) -> Result<bool, Error> {
324 Ok(self.commissioning_policy.concurrent_connection_supported())
325 }
326
327 fn recovery_identifier<P: TLVBuilderParent>(
328 &self,
329 ctx: impl ReadContext,
330 builder: OctetsBuilder<P>,
331 ) -> Result<P, Error> {
332 let id = Self::recovery_identifier_value(&ctx)?;
336
337 builder.set(Octets::new(&id.to_be_bytes()))
338 }
339
340 fn network_recovery_reason(
341 &self,
342 _ctx: impl ReadContext,
343 ) -> Result<Nullable<NetworkRecoveryReasonEnum>, Error> {
344 Ok(Nullable::none())
349 }
350
351 fn handle_arm_fail_safe<P: TLVBuilderParent>(
352 &self,
353 ctx: impl InvokeContext,
354 request: ArmFailSafeRequest<'_>,
355 response: ArmFailSafeResponseBuilder<P>,
356 ) -> Result<P, Error> {
357 let expiry_length_seconds = request.expiry_length_seconds()?;
358
359 info!(
360 "Got Arm Fail Safe Request, expiry {}s",
361 expiry_length_seconds
362 );
363
364 let mut removed_fabric = None;
372
373 let status = if expiry_length_seconds == 0 {
374 let notify_mdns = || ctx.exchange().matter().transport().notify_mdns_changed();
375 let notify_change = |endpt_id, clust_id| ctx.notify_cluster_changed(endpt_id, clust_id);
376
377 CommissioningErrorEnum::map(ctx.exchange().with_state(|state| {
378 let sess = ctx.exchange().id().session(&mut state.sessions);
379 let pase_sess_id =
380 matches!(sess.get_session_mode(), SessionMode::Pase { .. }).then(|| sess.id());
381
382 removed_fabric = state.failsafe.expire(
383 &mut state.fabrics,
384 &mut state.sessions,
385 pase_sess_id,
386 ctx.networks(),
387 ctx.kv(),
388 notify_mdns,
389 notify_change,
390 )?;
391
392 Ok(())
393 }))?
394 } else {
395 CommissioningErrorEnum::map(ctx.exchange().with_state(|state| {
396 let sess = ctx.exchange().id().session(&mut state.sessions);
397
398 state.failsafe.arm(
399 expiry_length_seconds,
400 request.breadcrumb()?,
401 sess.get_session_mode(),
402 &mut state.pase,
403 )
404 }))?
405 };
406
407 if let Some(fab_idx) = removed_fabric {
412 ctx.notify_fabric_removed(fab_idx);
413 }
414
415 ctx.notify_own_cluster_changed();
417
418 response.error_code(status)?.debug_text("")?.end()
419 }
420
421 fn handle_set_regulatory_config<P: TLVBuilderParent>(
422 &self,
423 ctx: impl InvokeContext,
424 request: SetRegulatoryConfigRequest<'_>,
425 response: SetRegulatoryConfigResponseBuilder<P>,
426 ) -> Result<P, Error> {
427 info!("Got Set Regulatory Config Request");
428
429 let country_code = request.country_code()?;
430 if country_code.len() != 2 {
431 return Err(ErrorCode::ConstraintError.into());
432 }
433
434 let location_type = request.new_regulatory_config();
448 let breadcrumb = request.breadcrumb()?;
449
450 let location_type = match location_type {
451 Ok(loc) if Self::is_regulatory_config_supported(self.commissioning_policy, loc) => loc,
452 _ => {
453 return response
454 .error_code(CommissioningErrorEnum::ValueOutsideRange)?
455 .debug_text("")?
456 .end();
457 }
458 };
459
460 let mut persist = Persist::new(ctx.kv());
461
462 let status = CommissioningErrorEnum::map(ctx.exchange().with_state(|state| {
463 state.basic_info_settings.set_location(country_code);
464 state.basic_info_settings.location_type = Some(location_type);
465
466 state.failsafe.set_breadcrumb(breadcrumb);
467
468 state.basic_info_settings.store_persist(&mut persist)?;
469
470 Ok(())
471 }))?;
472
473 persist.run()?;
474
475 ctx.notify_own_endpoint_changed();
478
479 response.error_code(status)?.debug_text("")?.end()
480 }
481
482 fn handle_commissioning_complete<P: TLVBuilderParent>(
483 &self,
484 ctx: impl InvokeContext,
485 response: CommissioningCompleteResponseBuilder<P>,
486 ) -> Result<P, Error> {
487 info!("Got Commissioning Complete Request");
488
489 let notify_change = |endpt_id, clust_id| ctx.notify_cluster_changed(endpt_id, clust_id);
490
491 let mut persist = FabricPersist::new(ctx.kv());
492
493 let status =
494 CommissioningErrorEnum::map(Self::with_armed_failsafe(&ctx, |state, notify_mdns| {
495 let sess = ctx.exchange().id().session(&mut state.sessions);
496 let pase_sess_id =
516 matches!(sess.get_session_mode(), SessionMode::Pase { .. }).then(|| sess.id());
517
518 let fabric = state
519 .failsafe
520 .disarm(sess.get_session_mode(), &mut state.fabrics)?;
521
522 state.pase.close_comm_window(notify_mdns, notify_change)?;
523 state.sessions.remove_pase(pase_sess_id);
524 ctx.exchange().matter().transport().notify_session_removed();
525
526 persist.store(fabric)?;
528 ctx.networks().access(|networks| {
529 networks.set_managed(true)?;
530
531 persist
532 .persist_mut()
533 .store(NETWORKS_KEY, |buf| networks.save(buf))
534 })?;
535
536 info!("Commissioning complete, fabric and network settings persisted");
537
538 Ok(())
539 }))?;
540
541 persist.run()?;
542
543 ctx.notify_own_endpoint_changed();
547
548 response.error_code(status)?.debug_text("")?.end()
549 }
550
551 fn handle_set_tc_acknowledgements<P: TLVBuilderParent>(
552 &self,
553 _ctx: impl InvokeContext,
554 _request: SetTCAcknowledgementsRequest<'_>,
555 _response: SetTCAcknowledgementsResponseBuilder<P>,
556 ) -> Result<P, Error> {
557 Err(ErrorCode::CommandNotFound.into())
558 }
559}
560
561impl Debug for GenCommHandler<'_> {
562 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
563 f.debug_struct("GenCommHandler")
564 .field("dataver", &self.dataver)
565 .finish()
566 }
567}
568
569#[cfg(feature = "defmt")]
570impl defmt::Format for GenCommHandler<'_> {
571 fn format(&self, fmt: defmt::Formatter) {
572 defmt::write!(fmt, "GenCommHandler {{ dataver: {} }}", self.dataver);
573 }
574}