blob: 44a21834e9e5698ba15d1e80d68779f4024b6f75 [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/CommandHandler.h>
#include <app/ConcreteCommandPath.h>
#include <clusters/CommissioningProxy/Structs.h>
#include <lib/core/CHIPConfig.h>
#include <lib/core/CHIPError.h>
#include <lib/support/Span.h>
#include <lib/support/TimerDelegate.h>
#include <system/SystemClock.h>
#include <cstdint>
namespace chip {
namespace app {
namespace Clusters {
namespace CommissioningProxy {
/**
* @brief Aggregates a multi-transport foreground ProxyScanRequest into one response.
*
* A ProxyScanRequest MAY select more than one transport. The cluster starts each
* requested transport's scan in parallel, hands this aggregator the command handle
* and the number of started sub-scans, and each transport reports its results via
* Contribute() when its sub-scan finishes. Once every started sub-scan has reported
* (or a watchdog fires), the aggregator emits the single combined ProxyScanResponse.
*
* Only one aggregation may be in flight at a time (matches a single outstanding
* foreground scan). Entry points run on the Matter thread with the stack lock held.
*/
class CommissioningProxyScanAggregator : public TimerContext
{
public:
using ScanResultEntry = Structs::ScanResultStruct::Type;
CommissioningProxyScanAggregator() = delete;
explicit CommissioningProxyScanAggregator(TimerDelegate & timerDelegate) : mTimerDelegate(timerDelegate) {}
~CommissioningProxyScanAggregator() override = default;
/**
* @brief Start a fresh aggregation. Takes ownership of the command handle for
* the combined response and arms a watchdog bounded by @p scanMaxTime.
* The caller must have checked InProgress() == false first.
*
* The number of contributors is not known up front: the caller starts each
* requested transport's scan and calls AddPendingContributor() for every one
* that starts successfully, then MaybeEmitIfComplete() (or Abort() if none started).
*
* Returns an error (leaving InProgress() false) if the watchdog could not be
* armed; the caller must reject the command rather than scan unbounded.
*/
CHIP_ERROR Begin(app::CommandHandler * commandObj, const app::ConcreteCommandPath & path, System::Clock::Seconds16 scanMaxTime);
/// Register one successfully-started sub-scan (increments the expected count).
void AddPendingContributor();
/// Number of sub-scans registered via AddPendingContributor().
uint8_t PendingContributors() const { return mExpected; }
/**
* @brief Report one transport's sub-scan results (empty span = found nothing).
* Entries are deep-copied, so the caller's backing storage need not
* outlive the call. Emits the combined ProxyScanResponse once every
* registered contributor has reported.
*/
void Contribute(Span<const ScanResultEntry> results);
/// Marks the expected count final and emits the combined response if every
/// registered contributor has already reported. Call once after starting all
/// sub-scans, to cover transports that contribute synchronously from within
/// Scan() (real transports report later, via Contribute()).
void MaybeEmitIfComplete();
/// True while an aggregation is in flight.
bool InProgress() const { return mInProgress; }
/// Abandon the in-flight aggregation without sending a response (no sub-scan
/// could be started, or cluster teardown).
void Abort();
/// Abandon any in-flight aggregation (cluster teardown).
void Shutdown() { Abort(); }
/// Watchdog expiry: a sub-scan never reported, so emit whatever did arrive rather
/// than leave the aggregation (and every later ProxyScanRequest) blocked.
void TimerFired() override;
private:
void EmitCombinedResponse();
// Spec: ProxyScanResult is constrained to "max MaxCachedResults", so the response
// is bounded by the same build-time limit as the cache, with Address/ExtendedData
// held inline at their spec maxima.
static constexpr size_t kMaxResults = CHIP_CONFIG_COMMISSIONING_PROXY_MAX_CACHED_RESULTS;
static constexpr size_t kMaxAddressBytes = 100;
static constexpr size_t kMaxExtendedDataBytes = 128;
struct ResultStore
{
uint8_t address[kMaxAddressBytes];
uint8_t addressLen = 0;
uint8_t extendedData[kMaxExtendedDataBytes];
uint8_t extendedDataLen = 0;
};
TimerDelegate & mTimerDelegate;
app::CommandHandler::Handle mHandle;
app::ConcreteCommandPath mPath;
bool mInProgress = false;
// Set by MaybeEmitIfComplete() once the caller has started every requested sub-scan.
// Until then mExpected is still growing, so a contributor reporting synchronously
// from within Scan() must not be allowed to satisfy it and emit early.
bool mAllContributorsRegistered = false;
uint8_t mExpected = 0;
uint8_t mReported = 0;
System::Clock::Seconds16 mScanMaxTime{ 0 };
ResultStore mStore[kMaxResults]; // keeps ByteSpan backing alive until emit
ScanResultEntry mResults[kMaxResults];
uint8_t mResultCount = 0;
};
} // namespace CommissioningProxy
} // namespace Clusters
} // namespace app
} // namespace chip