| /* | |
| * | |
| * 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 |