use core::fmt::Debug;
use either::Either;
use rand_core::RngCore;
use crate::crypto::Crypto;
use crate::dm::clusters::net_comm::NetworksAccess;
use crate::dm::{Cluster, Dataver, InvokeContext, OperationContext, ReadContext, WriteContext};
use crate::error::{Error, ErrorCode};
use crate::fabric::FabricPersist;
use crate::persist::{Persist, NETWORKS_KEY};
use crate::sc::pase::MAX_COMM_WINDOW_TIMEOUT_SECS;
use crate::tlv::{Nullable, Octets, OctetsBuilder, TLVBuilderParent};
use crate::transport::session::SessionMode;
use crate::utils::sync::DynBase;
use crate::{except, with, MatterState};
pub use crate::dm::clusters::decl::general_commissioning::*;
impl CommissioningErrorEnum {
fn map(result: Result<(), Error>) -> Result<Self, Error> {
Self::map_result(result).map(Self::ok)
}
fn map_result<T>(result: Result<T, Error>) -> Result<Either<T, Self>, Error> {
match result {
Ok(value) => Ok(Either::Left(value)),
Err(err) => match err.code() {
ErrorCode::Busy | ErrorCode::NocInvalidFabricIndex => {
Ok(Either::Right(Self::BusyWithOtherAdmin))
}
ErrorCode::GennCommInvalidAuthentication => {
Ok(Either::Right(Self::InvalidAuthentication))
}
ErrorCode::FailSafeRequired => Ok(Either::Right(Self::NoFailSafe)),
_ => Err(err),
},
}
}
fn ok<T>(value: Either<T, Self>) -> Self {
match value {
Either::Left(_) => Self::OK,
Either::Right(code) => code,
}
}
}
pub trait CommPolicy: DynBase {
fn concurrent_connection_supported(&self) -> bool;
fn failsafe_expiry_len_secs(&self) -> u16;
fn failsafe_max_cml_secs(&self) -> u16;
fn location_cap(&self) -> RegulatoryLocationTypeEnum;
}
impl<T> CommPolicy for &T
where
T: CommPolicy,
{
fn concurrent_connection_supported(&self) -> bool {
(*self).concurrent_connection_supported()
}
fn failsafe_expiry_len_secs(&self) -> u16 {
(*self).failsafe_expiry_len_secs()
}
fn failsafe_max_cml_secs(&self) -> u16 {
(*self).failsafe_max_cml_secs()
}
fn location_cap(&self) -> RegulatoryLocationTypeEnum {
(*self).location_cap()
}
}
impl DynBase for bool {}
impl CommPolicy for bool {
fn concurrent_connection_supported(&self) -> bool {
*self
}
fn failsafe_expiry_len_secs(&self) -> u16 {
120
}
fn failsafe_max_cml_secs(&self) -> u16 {
MAX_COMM_WINDOW_TIMEOUT_SECS
}
fn location_cap(&self) -> RegulatoryLocationTypeEnum {
RegulatoryLocationTypeEnum::IndoorOutdoor
}
}
pub struct GenCommHandler<'a> {
dataver: Dataver,
commissioning_policy: &'a dyn CommPolicy,
}
impl<'a> GenCommHandler<'a> {
pub const fn new(dataver: Dataver, commissioning_policy: &'a dyn CommPolicy) -> Self {
Self {
dataver,
commissioning_policy,
}
}
pub const fn adapt(self) -> HandlerAdaptor<Self> {
HandlerAdaptor(self)
}
pub(crate) fn with_armed_failsafe<F, T>(ctx: impl OperationContext, f: F) -> Result<T, Error>
where
F: FnOnce(&mut MatterState, &mut dyn FnMut()) -> Result<T, Error>,
{
Self::with_armed_failsafe_ex(ctx, f)
}
fn is_regulatory_config_supported(
policy: &dyn CommPolicy,
new_config: RegulatoryLocationTypeEnum,
) -> bool {
match policy.location_cap() {
RegulatoryLocationTypeEnum::Indoor => {
matches!(new_config, RegulatoryLocationTypeEnum::Indoor)
}
RegulatoryLocationTypeEnum::Outdoor => {
matches!(new_config, RegulatoryLocationTypeEnum::Outdoor)
}
RegulatoryLocationTypeEnum::IndoorOutdoor => true,
}
}
pub(crate) fn with_armed_failsafe_ex<F, T, E>(ctx: impl OperationContext, f: F) -> Result<T, E>
where
F: FnOnce(&mut MatterState, &mut dyn FnMut()) -> Result<T, E>,
E: From<Error>,
{
let mut notify_mdns = || ctx.exchange().matter().transport().notify_mdns_changed();
ctx.exchange().with_state_ex(|state| {
let sess = ctx.exchange().id().session(&mut state.sessions);
state
.failsafe
.check_armed(sess.get_session_mode())
.map_err(|err| match err.code() {
ErrorCode::NocInvalidFabricIndex => {
Error::new(ErrorCode::GennCommInvalidAuthentication)
}
_ => err,
})?;
f(state, &mut notify_mdns)
})
}
fn recovery_identifier_value(ctx: &impl ReadContext) -> Result<u64, Error> {
if let Some(id) = ctx
.exchange()
.with_state(|state| Ok(state.basic_info_settings.recovery_identifier))?
{
return Ok(id);
}
let fresh = ctx.crypto().rand()?.next_u64();
let mut persist = Persist::new(ctx.kv());
let id = ctx.exchange().with_state(|state| {
let id = *state
.basic_info_settings
.recovery_identifier
.get_or_insert(fresh);
state.basic_info_settings.store_persist(&mut persist)?;
Ok(id)
})?;
persist.run()?;
Ok(id)
}
}
pub const CLUSTER_NETWORK_RECOVERY: Cluster<'static> = FULL_CLUSTER
.with_attrs(
with!(required; AttributeId::RecoveryIdentifier | AttributeId::NetworkRecoveryReason),
)
.with_cmds(except!(CommandId::SetTCAcknowledgements))
.with_features(Feature::NETWORK_RECOVERY.bits());
impl ClusterHandler for GenCommHandler<'_> {
const CLUSTER: Cluster<'static> = FULL_CLUSTER
.with_attrs(with!(required))
.with_cmds(except!(CommandId::SetTCAcknowledgements));
fn dataver(&self) -> u32 {
self.dataver.get()
}
fn dataver_changed(&self) {
self.dataver.changed();
}
fn breadcrumb(&self, ctx: impl ReadContext) -> Result<u64, Error> {
ctx.exchange()
.with_state(|state| Ok(state.failsafe.breadcrumb()))
}
fn set_breadcrumb(&self, ctx: impl WriteContext, value: u64) -> Result<(), Error> {
ctx.exchange().with_state(|state| {
state.failsafe.set_breadcrumb(value);
Ok(())
})
}
fn basic_commissioning_info<P: TLVBuilderParent>(
&self,
_ctx: impl ReadContext,
builder: BasicCommissioningInfoBuilder<P>,
) -> Result<P, Error> {
builder
.fail_safe_expiry_length_seconds(self.commissioning_policy.failsafe_expiry_len_secs())?
.max_cumulative_failsafe_seconds(self.commissioning_policy.failsafe_max_cml_secs())?
.end()
}
fn regulatory_config(
&self,
ctx: impl ReadContext,
) -> Result<RegulatoryLocationTypeEnum, Error> {
ctx.exchange().with_state(|state| {
Ok(state
.basic_info_settings
.location_type
.unwrap_or(self.commissioning_policy.location_cap()))
})
}
fn location_capability(
&self,
_ctx: impl ReadContext,
) -> Result<RegulatoryLocationTypeEnum, Error> {
Ok(self.commissioning_policy.location_cap())
}
fn supports_concurrent_connection(&self, _ctx: impl ReadContext) -> Result<bool, Error> {
Ok(self.commissioning_policy.concurrent_connection_supported())
}
fn recovery_identifier<P: TLVBuilderParent>(
&self,
ctx: impl ReadContext,
builder: OctetsBuilder<P>,
) -> Result<P, Error> {
let id = Self::recovery_identifier_value(&ctx)?;
builder.set(Octets::new(&id.to_be_bytes()))
}
fn network_recovery_reason(
&self,
_ctx: impl ReadContext,
) -> Result<Nullable<NetworkRecoveryReasonEnum>, Error> {
Ok(Nullable::none())
}
fn handle_arm_fail_safe<P: TLVBuilderParent>(
&self,
ctx: impl InvokeContext,
request: ArmFailSafeRequest<'_>,
response: ArmFailSafeResponseBuilder<P>,
) -> Result<P, Error> {
let expiry_length_seconds = request.expiry_length_seconds()?;
info!(
"Got Arm Fail Safe Request, expiry {}s",
expiry_length_seconds
);
let mut removed_fabric = None;
let status = if expiry_length_seconds == 0 {
let notify_mdns = || ctx.exchange().matter().transport().notify_mdns_changed();
let notify_change = |endpt_id, clust_id| ctx.notify_cluster_changed(endpt_id, clust_id);
CommissioningErrorEnum::map(ctx.exchange().with_state(|state| {
let sess = ctx.exchange().id().session(&mut state.sessions);
let pase_sess_id =
matches!(sess.get_session_mode(), SessionMode::Pase { .. }).then(|| sess.id());
removed_fabric = state.failsafe.expire(
&mut state.fabrics,
&mut state.sessions,
pase_sess_id,
ctx.networks(),
ctx.kv(),
notify_mdns,
notify_change,
)?;
Ok(())
}))?
} else {
CommissioningErrorEnum::map(ctx.exchange().with_state(|state| {
let sess = ctx.exchange().id().session(&mut state.sessions);
state.failsafe.arm(
expiry_length_seconds,
request.breadcrumb()?,
sess.get_session_mode(),
&mut state.pase,
)
}))?
};
if let Some(fab_idx) = removed_fabric {
ctx.notify_fabric_removed(fab_idx);
}
ctx.notify_own_cluster_changed();
response.error_code(status)?.debug_text("")?.end()
}
fn handle_set_regulatory_config<P: TLVBuilderParent>(
&self,
ctx: impl InvokeContext,
request: SetRegulatoryConfigRequest<'_>,
response: SetRegulatoryConfigResponseBuilder<P>,
) -> Result<P, Error> {
info!("Got Set Regulatory Config Request");
let country_code = request.country_code()?;
if country_code.len() != 2 {
return Err(ErrorCode::ConstraintError.into());
}
let location_type = request.new_regulatory_config();
let breadcrumb = request.breadcrumb()?;
let location_type = match location_type {
Ok(loc) if Self::is_regulatory_config_supported(self.commissioning_policy, loc) => loc,
_ => {
return response
.error_code(CommissioningErrorEnum::ValueOutsideRange)?
.debug_text("")?
.end();
}
};
let mut persist = Persist::new(ctx.kv());
let status = CommissioningErrorEnum::map(ctx.exchange().with_state(|state| {
state.basic_info_settings.set_location(country_code);
state.basic_info_settings.location_type = Some(location_type);
state.failsafe.set_breadcrumb(breadcrumb);
state.basic_info_settings.store_persist(&mut persist)?;
Ok(())
}))?;
persist.run()?;
ctx.notify_own_endpoint_changed();
response.error_code(status)?.debug_text("")?.end()
}
fn handle_commissioning_complete<P: TLVBuilderParent>(
&self,
ctx: impl InvokeContext,
response: CommissioningCompleteResponseBuilder<P>,
) -> Result<P, Error> {
info!("Got Commissioning Complete Request");
let notify_change = |endpt_id, clust_id| ctx.notify_cluster_changed(endpt_id, clust_id);
let mut persist = FabricPersist::new(ctx.kv());
let status =
CommissioningErrorEnum::map(Self::with_armed_failsafe(&ctx, |state, notify_mdns| {
let sess = ctx.exchange().id().session(&mut state.sessions);
let pase_sess_id =
matches!(sess.get_session_mode(), SessionMode::Pase { .. }).then(|| sess.id());
let fabric = state
.failsafe
.disarm(sess.get_session_mode(), &mut state.fabrics)?;
state.pase.close_comm_window(notify_mdns, notify_change)?;
state.sessions.remove_pase(pase_sess_id);
ctx.exchange().matter().transport().notify_session_removed();
persist.store(fabric)?;
ctx.networks().access(|networks| {
networks.set_managed(true)?;
persist
.persist_mut()
.store(NETWORKS_KEY, |buf| networks.save(buf))
})?;
info!("Commissioning complete, fabric and network settings persisted");
Ok(())
}))?;
persist.run()?;
ctx.notify_own_endpoint_changed();
response.error_code(status)?.debug_text("")?.end()
}
fn handle_set_tc_acknowledgements<P: TLVBuilderParent>(
&self,
_ctx: impl InvokeContext,
_request: SetTCAcknowledgementsRequest<'_>,
_response: SetTCAcknowledgementsResponseBuilder<P>,
) -> Result<P, Error> {
Err(ErrorCode::CommandNotFound.into())
}
}
impl Debug for GenCommHandler<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("GenCommHandler")
.field("dataver", &self.dataver)
.finish()
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for GenCommHandler<'_> {
fn format(&self, fmt: defmt::Formatter) {
defmt::write!(fmt, "GenCommHandler {{ dataver: {} }}", self.dataver);
}
}