blob: 1ceef98a7d30fc379dc0a588f073990a42169ef9 [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/AttributeValueEncoder.h>
#include <clusters/CommissioningProxy/Enums.h>
#include <clusters/CommissioningProxy/Structs.h>
#include <lib/core/CHIPConfig.h>
#include <lib/core/CHIPError.h>
#include <lib/core/Optional.h>
#include <lib/support/BitMask.h>
#include <lib/support/TimerDelegate.h>
#include <system/SystemClock.h>
#include <cstdint>
namespace chip {
namespace app {
namespace Clusters {
namespace CommissioningProxy {
/**
* @brief Callbacks from CommissioningProxyScanCache to its owning cluster.
*
* Defined here (not in CommissioningProxyCluster.h) so CommissioningProxyScanCache.cpp
* can call back without including the full cluster header, which lives in a
* separate GN target compiled in the consumer's context.
*/
class ScanCacheObserver
{
public:
virtual ~ScanCacheObserver() = default;
virtual void MarkCachedResultsDirty() = 0;
virtual uint16_t GetCacheTimeout() const = 0;
virtual uint8_t GetMaxCachedResults() const = 0;
};
/**
* @brief Transport-agnostic background-scan result cache.
*
* Backs the CachedResults / NumCachedResults attributes. Per spec, CachedResults is
* a single cluster-wide list keyed on discriminator/VendorID/ProductID/Transport, so
* every transport shares ONE cache here — one combined MaxCachedResults cap, one
* NumCachedResults/CachedResults view, and one TTL sweep timer.
*
* CacheTimeout and MaxCachedResults are read back from the owning cluster; whenever
* the set of entries changes the cache calls the cluster's MarkCachedResultsDirty()
* so change-reporting stays the cluster's responsibility (delegates no longer touch
* NumCachedResults/CachedResults directly).
*
* All entry points must run on the Matter thread with the stack lock held.
*/
class CommissioningProxyScanCache : public TimerContext
{
public:
using ScanResultEntry = Structs::ScanResultStruct::Type;
CommissioningProxyScanCache(ScanCacheObserver & cluster, TimerDelegate & timerDelegate) :
mCluster(cluster), mTimerDelegate(timerDelegate)
{}
/// Idempotent if Shutdown() has already run, and required if it has not: the sweep
/// timer would otherwise be left holding a pointer to this destroyed TimerContext.
~CommissioningProxyScanCache() override { Shutdown(); }
/**
* @brief Insert or refresh a discovered device. @p result.transport carries the
* single discovering transport bit. Refreshes the entry's TTL if already
* cached; otherwise inserts subject to the MaxCachedResults cap. Marks
* CachedResults dirty on any change and (re)arms the sweep timer.
*/
void Report(const ScanResultEntry & result);
/**
* @brief Drop cached results for a scan that has stopped. @p bands == 0 means the
* transport itself stopped, so every entry on it goes; otherwise only
* entries discovered on those bands go. Spec gives a ScanResultStruct with
* no WiFiBand the fallback value 2G4, which is applied when matching.
* Marks dirty if anything was removed.
*/
void ClearTransport(BitMask<CapabilitiesBitmap> transport, BitMask<WiFiBandBitmap> bands = {});
/// NumCachedResults: current combined entry count.
uint8_t Count() const;
/// Encode the CachedResults list attribute (NullNullable when empty).
CHIP_ERROR Encode(app::AttributeValueEncoder & encoder) const;
/// Cancel the sweep timer and drop all entries (cluster teardown).
void Shutdown();
void TimerFired() override { OnSweep(); }
private:
// A device is unique per discriminator/VendorID/ProductID/Transport (spec).
struct Key
{
uint8_t transport;
uint16_t discriminator;
uint16_t vid;
uint16_t pid;
bool operator==(const Key & o) const;
};
// ScanResultStruct constrains Address to 100 bytes and ExtendedData to 128, so both
// are held inline rather than heap-allocated per entry.
static constexpr size_t kMaxAddressBytes = 100;
static constexpr size_t kMaxExtendedDataBytes = 128;
// Self-owned copy of a ScanResultStruct so its ByteSpans survive in the cache.
// `inUse` false marks a free slot; the table is small (MaxCachedResults).
struct Entry
{
bool inUse = false;
Key key = {};
bool hasAddress = false;
uint8_t address[kMaxAddressBytes];
uint8_t addressLen = 0;
BitMask<CapabilitiesBitmap> transport{};
uint16_t discriminator = 0;
VendorId vendorID = static_cast<VendorId>(0);
uint16_t productID = 0;
bool hasExtendedData = false;
uint8_t extendedData[kMaxExtendedDataBytes];
uint8_t extendedDataLen = 0;
Optional<BitMask<WiFiBandBitmap>> wiFiBand;
System::Clock::Timestamp expiresAt{};
};
/// Sweep expiry: drop every entry whose TTL has passed, then re-arm while any
/// entry remains.
void OnSweep();
void ArmSweepIfNeeded();
Entry * FindEntry(const Key & key);
ScanCacheObserver & mCluster;
TimerDelegate & mTimerDelegate;
Entry mEntries[CHIP_CONFIG_COMMISSIONING_PROXY_MAX_CACHED_RESULTS];
bool mSweepArmed = false;
};
} // namespace CommissioningProxy
} // namespace Clusters
} // namespace app
} // namespace chip