blob: 448243c3fd4ef20ed849892cf9600a5bbfc4dc64 [file]
/*
*
* Copyright (c) 2026 Project CHIP Authors
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <app/clusters/commissioning-proxy-server/CommissioningProxyScanAggregator.h>
#include <app/clusters/commissioning-proxy-server/CommissioningProxyScanCache.h>
#include <app/clusters/commissioning-proxy-server/CommissioningProxySessionManager.h>
#include <app/clusters/commissioning-proxy-server/CommissioningProxyTransport.h>
#include <app/server-cluster/DefaultServerCluster.h>
#include <app/server-cluster/OptionalAttributeSet.h>
#include <clusters/CommissioningProxy/AttributeIds.h>
#include <clusters/CommissioningProxy/ClusterId.h>
#include <clusters/CommissioningProxy/Enums.h>
#include <clusters/CommissioningProxy/Events.h>
#include <clusters/CommissioningProxy/Structs.h>
#include <credentials/FabricTable.h>
#include <lib/core/CHIPConfig.h>
#include <lib/core/Optional.h>
#include <lib/support/TimerDelegate.h>
namespace chip {
namespace app {
namespace Clusters {
namespace CommissioningProxy {
class CommissioningProxyCluster : public DefaultServerCluster, public ScanCacheObserver, public FabricTable::Delegate
{
private:
using OptionalAttributesSet =
OptionalAttributeSet<CommissioningProxy::Attributes::MaxCachedResults::Id,
CommissioningProxy::Attributes::NumCachedResults::Id, CommissioningProxy::Attributes::CacheTimeout::Id,
CommissioningProxy::Attributes::CachedResults::Id, CommissioningProxy::Attributes::WiFiBand::Id>;
public:
enum State
{
kState_CPDisconnected = 0,
kState_CPConnected
};
// Static device capabilities. The app supplies these up front; transports are
// registered separately (RegisterTransport) since they may be constructed after
// the cluster. There is no application delegate: all transport actions go through
// the registered CommissioningProxyTransport drivers, and all reportable/derived
// attribute state is owned by the cluster and its subsystems.
// MaxSessions and MaxCachedResults are Fixed-quality attributes, so they are build
// -time device characteristics rather than per-instance config: they come from
// CHIP_CONFIG_COMMISSIONING_PROXY_MAX_SESSIONS / _MAX_CACHED_RESULTS, which also
// size the storage behind them.
struct Config
{
BitMask<CommissioningProxy::Feature> featureFlags;
BitMask<CommissioningProxy::WiFiBandBitmap> supportedWiFiBands;
Config(BitMask<CommissioningProxy::Feature> aFeatures,
BitMask<CommissioningProxy::WiFiBandBitmap> aSupportedWiFiBands = {}) :
featureFlags(aFeatures),
supportedWiFiBands(aSupportedWiFiBands)
{}
};
CommissioningProxyCluster() = delete;
// The endpoint id is supplied separately from Config: a device's cluster config is
// typically fixed up front, but the endpoint is only known when the device is
// registered and the clusters are created.
// @p timerDelegate supplies the response-timeout and scan-watchdog timers. Apps
// normally pass a chip::app::DefaultTimerDelegate (see CodegenIntegration.cpp);
// tests substitute their own to drive expiry without the event loop.
//
// @p fabricTable is watched so a removed fabric's sessions and background scans go
// with it. May be nullptr where no FabricTable exists (unit tests), in which case
// OnFabricRemoved must be driven directly.
CommissioningProxyCluster(EndpointId endpointId, const Config & config, TimerDelegate & timerDelegate,
FabricTable * fabricTable = nullptr) :
DefaultServerCluster({ endpointId, CommissioningProxy::Id }),
mFeatureFlags(config.featureFlags), mSupportedWiFiBands(config.supportedWiFiBands), mEnabledOptionalAttributes([&]() {
OptionalAttributesSet attrs;
attrs.Set<CommissioningProxy::Attributes::MaxCachedResults::Id>(
config.featureFlags.Has(CommissioningProxy::Feature::kBackgroundScan));
attrs.Set<CommissioningProxy::Attributes::NumCachedResults::Id>(
config.featureFlags.Has(CommissioningProxy::Feature::kBackgroundScan));
attrs.Set<CommissioningProxy::Attributes::CacheTimeout::Id>(
config.featureFlags.Has(CommissioningProxy::Feature::kBackgroundScan));
attrs.Set<CommissioningProxy::Attributes::CachedResults::Id>(
config.featureFlags.Has(CommissioningProxy::Feature::kBackgroundScan));
attrs.Set<CommissioningProxy::Attributes::WiFiBand::Id>(
config.featureFlags.Has(CommissioningProxy::Feature::kWiFiNetworkInterface));
return attrs;
}()),
mSessions(timerDelegate), mScanCache(*this, timerDelegate), mScanAggregator(timerDelegate), mFabricTable(fabricTable)
{
mMainCommissioningProxyState = kState_CPDisconnected;
}
~CommissioningProxyCluster() override
{
// Safety net: idempotent if the application already called Shutdown().
Shutdown(ClusterShutdownType::kClusterShutdown);
}
// Cancel subsystem timers, detach transports, and clear IM context. Must be
// called (or destructor invoked) before any stack-allocated CommandHandler
// that was passed to InvokeCommand() goes out of scope.
void Shutdown(ClusterShutdownType type) override
{
if (mFabricTable != nullptr)
{
mFabricTable->RemoveFabricDelegate(this);
}
mScanCache.Shutdown();
mScanAggregator.Shutdown();
mSessions.Shutdown();
for (size_t i = 0; i < mTransportCount; i++)
{
mTransports[i]->Shutdown();
mTransports[i]->SetHost(nullptr);
}
mTransportCount = 0;
DefaultServerCluster::Shutdown(type);
}
/**
* @brief Register a platform transport driver. The driver's host back-pointer
* is set to this cluster. Call before Startup; a driver for a given transport
* bit may be registered only once.
*/
void RegisterTransport(CommissioningProxyTransport & transport)
{
VerifyOrDie(mTransportCount < kMaxTransports);
VerifyOrDie(FindTransport(transport.GetTransportType()) == nullptr);
mTransports[mTransportCount++] = &transport;
transport.SetHost(this);
}
const OptionalAttributesSet & OptionalAttributes() const { return mEnabledOptionalAttributes; }
const BitFlags<CommissioningProxy::Feature> & Features() const { return mFeatureFlags; }
CHIP_ERROR Startup(ServerClusterContext & context) override;
/**
* @brief Tear down everything a removed fabric owned: any in-flight connect, its
* proxy sessions, and its background-scan requests on every transport.
*/
void OnFabricRemoved(const FabricTable & fabricTable, FabricIndex fabricIndex) override;
DataModel::ActionReturnStatus ReadAttribute(const DataModel::ReadAttributeRequest & request,
AttributeValueEncoder & encoder) override;
DataModel::ActionReturnStatus WriteAttribute(const DataModel::WriteAttributeRequest & request,
AttributeValueDecoder & decoder) override;
std::optional<DataModel::ActionReturnStatus> InvokeCommand(const DataModel::InvokeRequest & request,
TLV::TLVReader & input_arguments, CommandHandler * handler) override;
CHIP_ERROR Attributes(const ConcreteClusterPath & path, ReadOnlyBufferBuilder<DataModel::AttributeEntry> & builder) override;
CHIP_ERROR AcceptedCommands(const ConcreteClusterPath & path,
ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> & builder) override;
CHIP_ERROR GeneratedCommands(const ConcreteClusterPath & path, ReadOnlyBufferBuilder<CommandId> & builder) override;
CHIP_ERROR SetCPState(State state);
CommissioningProxyCluster::State GetCPState();
/**
* @brief Report the proxy disconnected once no session remains, anywhere.
*
* For a transport whose peer closed the connection: the ProxyDisconnectRequest path
* sets the state itself. A transport must not decide this from its own sessions --
* with more than one transport configured, the last session on one of them is not the
* last session on the proxy.
*/
void SetDisconnectedIfLastSession();
/**
* @brief Current ScanMaxTime / CacheTimeout attribute values.
*
* These writable attributes are stored and change-reported by the cluster.
* A transport performing a scan reads the live value through these getters
* (via its host back-pointer) rather than caching its own copy.
*/
uint8_t GetScanMaxTime() const { return mScanMaxTime; }
uint16_t GetCacheTimeout() const override { return mCacheTimeout; }
// Static device capabilities (from Config).
uint8_t GetMaxSessions() const { return mMaxSessions; }
uint8_t GetMaxCachedResults() const override { return mMaxCachedResults; }
BitMask<CommissioningProxy::WiFiBandBitmap> GetSupportedWiFiBands() const { return mSupportedWiFiBands; }
/// Update the supported Wi-Fi bands (e.g. once the radio's capabilities are
/// known at init). Also the Config default.
void SetSupportedWiFiBands(BitMask<CommissioningProxy::WiFiBandBitmap> bands) { mSupportedWiFiBands = bands; }
/// Number of proxy sessions counted against MaxSessions: established sessions
/// plus any in-flight connect on any registered transport.
uint8_t GetActiveSessionCount() const
{
uint8_t count = mSessions.ActiveCount();
for (size_t i = 0; i < mTransportCount; i++)
{
if (mTransports[i]->IsConnectPending())
{
count++;
}
}
return count;
}
/// The set of transports supported by this instance: those with a registered
/// driver. Mirrors the Transport attribute and drives command validation.
BitMask<CapabilitiesBitmap> GetSupportedTransports() const;
/// Find the registered driver for a single transport bit, or nullptr.
CommissioningProxyTransport * FindTransport(CapabilitiesBitmap bit) const;
/**
* @brief Accessors for the cluster's transport-agnostic subsystems. Platform
* transports call these (via their host back-pointer) to register/close
* sessions, route ProxyMessage replies, cache background-scan results, and
* contribute foreground-scan results.
*/
CommissioningProxySessionManager & Sessions() { return mSessions; }
CommissioningProxyScanCache & ScanCache() { return mScanCache; }
CommissioningProxyScanAggregator & ScanAggregator() { return mScanAggregator; }
/**
* @brief Notify subscribers that CachedResults and NumCachedResults have changed.
*
* Called by the scan cache whenever the background-scan result set changes.
*/
void MarkCachedResultsDirty() override
{
NotifyAttributeChanged(CommissioningProxy::Attributes::CachedResults::Id);
NotifyAttributeChanged(CommissioningProxy::Attributes::NumCachedResults::Id);
}
private:
std::optional<DataModel::ActionReturnStatus>
HandleProxyConnectRequest(const DataModel::InvokeRequest & request, TLV::TLVReader & input_arguments, CommandHandler * handler);
std::optional<DataModel::ActionReturnStatus> HandleProxyDisconnectRequest(const DataModel::InvokeRequest & request,
TLV::TLVReader & input_arguments,
CommandHandler * handler);
std::optional<DataModel::ActionReturnStatus> HandleProxyScanRequest(const DataModel::InvokeRequest & request,
TLV::TLVReader & input_arguments, CommandHandler * handler);
std::optional<DataModel::ActionReturnStatus> HandleProxyBackGroundScanStartRequest(const DataModel::InvokeRequest & request,
TLV::TLVReader & input_arguments,
CommandHandler * handler);
std::optional<DataModel::ActionReturnStatus> HandleProxyBackGroundScanStopRequest(const DataModel::InvokeRequest & request,
TLV::TLVReader & input_arguments,
CommandHandler * handler);
std::optional<DataModel::ActionReturnStatus>
HandleProxyMessageRequest(const DataModel::InvokeRequest & request, TLV::TLVReader & input_arguments, CommandHandler * handler);
/// Cancel any in-flight connect(s) owned by @p fabricIndex across all transports
/// (null-SessionID ProxyDisconnectRequest).
Protocols::InteractionModel::Status CancelPendingConnect(FabricIndex fabricIndex);
const BitFlags<CommissioningProxy::Feature> mFeatureFlags;
static constexpr uint8_t mMaxSessions = CHIP_CONFIG_COMMISSIONING_PROXY_MAX_SESSIONS;
static constexpr uint8_t mMaxCachedResults = CHIP_CONFIG_COMMISSIONING_PROXY_MAX_CACHED_RESULTS;
BitMask<CommissioningProxy::WiFiBandBitmap> mSupportedWiFiBands;
const OptionalAttributesSet mEnabledOptionalAttributes;
State mMainCommissioningProxyState;
// Writable attributes owned (stored and change-reported) by the cluster.
uint8_t mScanMaxTime = 10;
uint16_t mCacheTimeout = 120;
// Transport-agnostic subsystems owned by the cluster.
CommissioningProxySessionManager mSessions;
CommissioningProxyScanCache mScanCache;
CommissioningProxyScanAggregator mScanAggregator;
FabricTable * mFabricTable;
// Registered platform transport drivers (BLE, Wi-Fi PAF, ...), at most one per
// transport bit. Owned by the application, not the cluster.
static constexpr size_t kMaxTransports = 3;
CommissioningProxyTransport * mTransports[kMaxTransports] = {};
size_t mTransportCount = 0;
};
} // namespace CommissioningProxy
} // namespace Clusters
} // namespace app
} // namespace chip