1use core::fmt::Debug;
21
22use either::Either;
23
24use crate::dm::clusters::net_comm::NetworksAccess;
25use crate::dm::{Cluster, Dataver, InvokeContext, OperationContext, ReadContext, WriteContext};
26use crate::error::{Error, ErrorCode};
27use crate::fabric::FabricPersist;
28use crate::persist::{Persist, BASIC_INFO_KEY, NETWORKS_KEY};
29use crate::sc::pase::MAX_COMM_WINDOW_TIMEOUT_SECS;
30use crate::tlv::TLVBuilderParent;
31use crate::transport::session::SessionMode;
32use crate::utils::sync::DynBase;
33use crate::{except, with, MatterState};
34
35pub use crate::dm::clusters::decl::general_commissioning::*;
36
37impl CommissioningErrorEnum {
38 fn map(result: Result<(), Error>) -> Result<Self, Error> {
39 Self::map_result(result).map(Self::ok)
40 }
41
42 fn map_result<T>(result: Result<T, Error>) -> Result<Either<T, Self>, Error> {
43 match result {
44 Ok(value) => Ok(Either::Left(value)),
45 Err(err) => match err.code() {
46 ErrorCode::Busy | ErrorCode::NocInvalidFabricIndex => {
47 Ok(Either::Right(Self::BusyWithOtherAdmin))
48 }
49 ErrorCode::GennCommInvalidAuthentication => {
50 Ok(Either::Right(Self::InvalidAuthentication))
51 }
52 ErrorCode::FailSafeRequired => Ok(Either::Right(Self::NoFailSafe)),
53 _ => Err(err),
54 },
55 }
56 }
57
58 fn ok<T>(value: Either<T, Self>) -> Self {
59 match value {
60 Either::Left(_) => Self::OK,
61 Either::Right(code) => code,
62 }
63 }
64}
65
66pub trait CommPolicy: DynBase {
69 fn concurrent_connection_supported(&self) -> bool;
72
73 fn failsafe_expiry_len_secs(&self) -> u16;
75
76 fn failsafe_max_cml_secs(&self) -> u16;
78
79 fn regulatory_config(&self) -> RegulatoryLocationTypeEnum;
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 regulatory_config(&self) -> RegulatoryLocationTypeEnum {
103 (*self).regulatory_config()
104 }
105
106 fn location_cap(&self) -> RegulatoryLocationTypeEnum {
107 (*self).location_cap()
108 }
109}
110
111impl DynBase for bool {}
112
113impl CommPolicy for bool {
114 fn concurrent_connection_supported(&self) -> bool {
115 *self
116 }
117
118 fn failsafe_expiry_len_secs(&self) -> u16 {
119 120
120 }
121
122 fn failsafe_max_cml_secs(&self) -> u16 {
123 MAX_COMM_WINDOW_TIMEOUT_SECS
130 }
131
132 fn regulatory_config(&self) -> RegulatoryLocationTypeEnum {
133 RegulatoryLocationTypeEnum::IndoorOutdoor
134 }
135
136 fn location_cap(&self) -> RegulatoryLocationTypeEnum {
137 RegulatoryLocationTypeEnum::IndoorOutdoor
138 }
139}
140
141pub struct GenCommHandler<'a> {
143 dataver: Dataver,
144 commissioning_policy: &'a dyn CommPolicy,
145}
146
147impl<'a> GenCommHandler<'a> {
148 pub const fn new(dataver: Dataver, commissioning_policy: &'a dyn CommPolicy) -> Self {
150 Self {
151 dataver,
152 commissioning_policy,
153 }
154 }
155
156 pub const fn adapt(self) -> HandlerAdaptor<Self> {
158 HandlerAdaptor(self)
159 }
160
161 pub(crate) fn with_armed_failsafe<F, T>(ctx: impl OperationContext, f: F) -> Result<T, Error>
166 where
167 F: FnOnce(&mut MatterState, &mut dyn FnMut()) -> Result<T, Error>,
168 {
169 Self::with_armed_failsafe_ex(ctx, f)
170 }
171
172 fn is_regulatory_config_supported(
176 policy: &dyn CommPolicy,
177 new_config: RegulatoryLocationTypeEnum,
178 ) -> bool {
179 match policy.location_cap() {
180 RegulatoryLocationTypeEnum::Indoor => {
181 matches!(new_config, RegulatoryLocationTypeEnum::Indoor)
182 }
183 RegulatoryLocationTypeEnum::Outdoor => {
184 matches!(new_config, RegulatoryLocationTypeEnum::Outdoor)
185 }
186 RegulatoryLocationTypeEnum::IndoorOutdoor => true,
187 }
188 }
189
190 pub(crate) fn with_armed_failsafe_ex<F, T, E>(ctx: impl OperationContext, f: F) -> Result<T, E>
195 where
196 F: FnOnce(&mut MatterState, &mut dyn FnMut()) -> Result<T, E>,
197 E: From<Error>,
198 {
199 let mut notify_mdns = || ctx.exchange().matter().transport().notify_mdns_changed();
200
201 ctx.exchange().with_state_ex(|state| {
202 let sess = ctx.exchange().id().session(&mut state.sessions);
203
204 state
205 .failsafe
206 .check_armed(sess.get_session_mode())
207 .map_err(|err| match err.code() {
208 ErrorCode::NocInvalidFabricIndex => {
209 Error::new(ErrorCode::GennCommInvalidAuthentication)
210 }
211 _ => err,
212 })?;
213
214 f(state, &mut notify_mdns)
215 })
216 }
217}
218
219impl ClusterHandler for GenCommHandler<'_> {
220 const CLUSTER: Cluster<'static> = FULL_CLUSTER
221 .with_attrs(with!(required))
222 .with_cmds(except!(CommandId::SetTCAcknowledgements));
223
224 fn dataver(&self) -> u32 {
225 self.dataver.get()
226 }
227
228 fn dataver_changed(&self) {
229 self.dataver.changed();
230 }
231
232 fn breadcrumb(&self, ctx: impl ReadContext) -> Result<u64, Error> {
233 ctx.exchange()
234 .with_state(|state| Ok(state.failsafe.breadcrumb()))
235 }
236
237 fn set_breadcrumb(&self, ctx: impl WriteContext, value: u64) -> Result<(), Error> {
238 ctx.exchange().with_state(|state| {
239 state.failsafe.set_breadcrumb(value);
240
241 Ok(())
242 })
243 }
244
245 fn basic_commissioning_info<P: TLVBuilderParent>(
246 &self,
247 _ctx: impl ReadContext,
248 builder: BasicCommissioningInfoBuilder<P>,
249 ) -> Result<P, Error> {
250 builder
251 .fail_safe_expiry_length_seconds(self.commissioning_policy.failsafe_expiry_len_secs())?
252 .max_cumulative_failsafe_seconds(self.commissioning_policy.failsafe_max_cml_secs())?
253 .end()
254 }
255
256 fn regulatory_config(
257 &self,
258 ctx: impl ReadContext,
259 ) -> Result<RegulatoryLocationTypeEnum, Error> {
260 ctx.exchange()
261 .with_state(|state| Ok(state.basic_info_settings.location_type))
262 }
263
264 fn location_capability(
265 &self,
266 _ctx: impl ReadContext,
267 ) -> Result<RegulatoryLocationTypeEnum, Error> {
268 Ok(self.commissioning_policy.location_cap())
269 }
270
271 fn supports_concurrent_connection(&self, _ctx: impl ReadContext) -> Result<bool, Error> {
272 Ok(self.commissioning_policy.concurrent_connection_supported())
273 }
274
275 fn handle_arm_fail_safe<P: TLVBuilderParent>(
276 &self,
277 ctx: impl InvokeContext,
278 request: ArmFailSafeRequest<'_>,
279 response: ArmFailSafeResponseBuilder<P>,
280 ) -> Result<P, Error> {
281 let expiry_length_seconds = request.expiry_length_seconds()?;
282
283 info!(
284 "Got Arm Fail Safe Request, expiry {}s",
285 expiry_length_seconds
286 );
287
288 let status = if expiry_length_seconds == 0 {
296 let notify_mdns = || ctx.exchange().matter().transport().notify_mdns_changed();
297 let notify_change = |endpt_id, clust_id| ctx.notify_cluster_changed(endpt_id, clust_id);
298
299 CommissioningErrorEnum::map(ctx.exchange().with_state(|state| {
300 let sess = ctx.exchange().id().session(&mut state.sessions);
301 let pase_sess_id =
302 matches!(sess.get_session_mode(), SessionMode::Pase { .. }).then(|| sess.id());
303
304 state.failsafe.expire(
305 &mut state.fabrics,
306 &mut state.sessions,
307 pase_sess_id,
308 ctx.networks(),
309 ctx.kv(),
310 notify_mdns,
311 notify_change,
312 )?;
313
314 Ok(())
315 }))?
316 } else {
317 CommissioningErrorEnum::map(ctx.exchange().with_state(|state| {
318 let sess = ctx.exchange().id().session(&mut state.sessions);
319
320 state.failsafe.arm(
321 expiry_length_seconds,
322 request.breadcrumb()?,
323 sess.get_session_mode(),
324 &mut state.pase,
325 )
326 }))?
327 };
328
329 ctx.notify_own_cluster_changed();
331
332 response.error_code(status)?.debug_text("")?.end()
333 }
334
335 fn handle_set_regulatory_config<P: TLVBuilderParent>(
336 &self,
337 ctx: impl InvokeContext,
338 request: SetRegulatoryConfigRequest<'_>,
339 response: SetRegulatoryConfigResponseBuilder<P>,
340 ) -> Result<P, Error> {
341 info!("Got Set Regulatory Config Request");
342
343 let country_code = request.country_code()?;
344 if country_code.len() != 2 {
345 return Err(ErrorCode::ConstraintError.into());
346 }
347
348 let location_type = request.new_regulatory_config();
362 let breadcrumb = request.breadcrumb()?;
363
364 let location_type = match location_type {
365 Ok(loc) if Self::is_regulatory_config_supported(self.commissioning_policy, loc) => loc,
366 _ => {
367 return response
368 .error_code(CommissioningErrorEnum::ValueOutsideRange)?
369 .debug_text("")?
370 .end();
371 }
372 };
373
374 let mut persist = Persist::new(ctx.kv());
375
376 let status = CommissioningErrorEnum::map(ctx.exchange().with_state(|state| {
377 state.basic_info_settings.set_location(country_code);
378 state.basic_info_settings.location_type = location_type;
379
380 state.failsafe.set_breadcrumb(breadcrumb);
381
382 persist.store_tlv(BASIC_INFO_KEY, &state.basic_info_settings)?;
383
384 Ok(())
385 }))?;
386
387 persist.run()?;
388
389 ctx.notify_own_endpoint_changed();
392
393 response.error_code(status)?.debug_text("")?.end()
394 }
395
396 fn handle_commissioning_complete<P: TLVBuilderParent>(
397 &self,
398 ctx: impl InvokeContext,
399 response: CommissioningCompleteResponseBuilder<P>,
400 ) -> Result<P, Error> {
401 info!("Got Commissioning Complete Request");
402
403 let notify_change = |endpt_id, clust_id| ctx.notify_cluster_changed(endpt_id, clust_id);
404
405 let mut persist = FabricPersist::new(ctx.kv());
406
407 let status =
408 CommissioningErrorEnum::map(Self::with_armed_failsafe(&ctx, |state, notify_mdns| {
409 let sess = ctx.exchange().id().session(&mut state.sessions);
410 let pase_sess_id =
430 matches!(sess.get_session_mode(), SessionMode::Pase { .. }).then(|| sess.id());
431
432 let fabric = state
433 .failsafe
434 .disarm(sess.get_session_mode(), &mut state.fabrics)?;
435
436 state.pase.close_comm_window(notify_mdns, notify_change)?;
437 state.sessions.remove_pase(pase_sess_id);
438 ctx.exchange().matter().transport().notify_session_removed();
439
440 persist.store(fabric)?;
442 ctx.networks().access(|networks| {
443 networks.set_commissioned(true)?;
444
445 persist
446 .persist_mut()
447 .store(NETWORKS_KEY, |buf| networks.save(buf))
448 })?;
449
450 info!("Commissioning complete, fabric and network settings persisted");
451
452 Ok(())
453 }))?;
454
455 persist.run()?;
456
457 ctx.notify_own_endpoint_changed();
461
462 response.error_code(status)?.debug_text("")?.end()
463 }
464
465 fn handle_set_tc_acknowledgements<P: TLVBuilderParent>(
466 &self,
467 _ctx: impl InvokeContext,
468 _request: SetTCAcknowledgementsRequest<'_>,
469 _response: SetTCAcknowledgementsResponseBuilder<P>,
470 ) -> Result<P, Error> {
471 Err(ErrorCode::CommandNotFound.into())
472 }
473}
474
475impl Debug for GenCommHandler<'_> {
476 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
477 f.debug_struct("GenCommHandler")
478 .field("dataver", &self.dataver)
479 .finish()
480 }
481}
482
483#[cfg(feature = "defmt")]
484impl defmt::Format for GenCommHandler<'_> {
485 fn format(&self, fmt: defmt::Formatter) {
486 defmt::write!(fmt, "GenCommHandler {{ dataver: {} }}", self.dataver);
487 }
488}