Skip to content

Commit 287747a

Browse files
dcjclaudepre-commit-ci[bot]
authored
[PowerTopology] Add ElectricalCircuit (CIRC) feature and ElectricalCircuitNodes attribute (project-chip#73402)
* [PowerTopology] Add ElectricalCircuit (CIRC) feature + ElectricalCircuitNodes Add the Matter 1.7 ElectricalCircuit (CIRC) feature to the code-driven power-topology-server, exposing the ElectricalCircuitNodes attribute (0x0002): a fabric-scoped, writable, non-volatile List<CircuitNodeStruct>. - Read is fabric-filtered automatically (fabric-scoped item type). - Write is fabric-scoped: the accessing fabric is authoritative, ReplaceAll replaces only that fabric's slice with full staging/validation before any mutation, AppendItem appends one node. - Non-volatile persistence via an explicit TLV blob (retaining fabricIndex, which the generated fabric-scoped write codec omits), restored on Startup. - Fabric removal purges the removed fabric's nodes via FabricTable::Delegate; the FabricTable is an optional nullable field on the cluster Config. Enable CIRC on the EVSE example app and thread the FabricTable through PtConfig so fabric-removal cleanup is active. The added FabricTable parameters are defaulted, so the water-heater app (which shares ElectricalSensorManager) is unaffected. Extend TC_PWRTL_2_1 to assert ElectricalCircuitNodes present iff CIRC and to read, write (round-trip), and verify Non-volatile persistence of the attribute. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * [PowerTopology] Scope TC_PWRTL_2_1 CIRC change to attribute presence Keep only the AttributeList presence-iff-CIRC assertion for ElectricalCircuitNodes in TC_PWRTL_2_1 (the attributes test). Functional read/write coverage of the attribute belongs to the dedicated TC_PWRTL_2_2, which this PR's CIRC-enabled DUT unblocks; avoid duplicating it here. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * [PowerTopology] Heap-allocate ElectricalCircuitNodes write staging buffer WriteElectricalCircuitNodes staged the incoming list in a stack array of kMaxCircuitNodes StoredCircuitNode entries (~8 KB), which can overflow the constrained Matter thread stack on embedded platforms. Allocate the staging buffer on the heap via ScopedMemoryBuffer, sized to the incoming count. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * [PowerTopology] Heap-allocate ElectricalCircuitNodes node storage The ElectricalCircuitNodes storage was an inline StoredCircuitNode array of kMaxCircuitNodes entries (~8 KB: char label[128] x 50). As a member this made every stack-allocated PowerTopologyCluster instance ~8 KB, exceeding the -Wstack-usage=8192 limit on 32-bit embedded builds (Zephyr/ESP32) in the unit tests, and inflating the cluster's footprint on constrained platforms generally. Store the nodes in a ScopedMemoryBuffer allocated on Startup, only when the CIRC feature is enabled. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * [PowerTopology] Move ElectricalCircuitNodes storage behind an interface Review feedback: the cluster should not hard-code how ElectricalCircuitNodes is stored, and holding the entries in a heap buffer rules out platforms with stricter memory requirements. Following thread-network-directory-server, which solves the same problem for a writable persisted list: - CircuitNodeStorage is an abstract interface: Capacity(), Count(), CountForFabric(), GetNodeAtIndex(), ReplaceNodesForFabric() for the ReplaceAll write, AppendNode() for AppendItem, and RemoveNodesForFabric() for fabric purge. - DefaultCircuitNodeStorage implements it against the cluster's attribute storage, carrying over the previous TLV format unchanged, so existing behaviour (durable nodes, same location) is preserved with no application work. - The cluster holds only a CircuitNodeStorage pointer and allocates nothing for this attribute. StoredCircuitNode, the ScopedMemoryBuffer member, and SaveCircuitNodes()/LoadCircuitNodes() are gone. A platform without a heap now supplies a fixed-array implementation and reports its real limit through Capacity(). The unit tests do exactly that, so the allocation-free path is exercised rather than merely possible. CircuitNodeStorage::Init() is a no-op by default and exists so an implementation that persists through the cluster's attribute storage can obtain it at cluster Startup, which is the earliest point it exists. Implementations with their own persistence ignore it. PowerTopology::Instance gains an overload taking application-provided storage; the existing constructor keeps using DefaultCircuitNodeStorage, so applications need no change. Load() stops at Capacity() rather than erroring, so reducing a platform's capacity cannot prevent startup on previously persisted data. Testing: unit tests pass (22 cases, up from 21). The added DefaultCircuitNodeStoragePersistsAcrossInstances covers the TLV round trip that previously ran through the cluster, since the cluster-level tests now use the non-persisting fixed-array storage. all-clusters-app builds and links. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * [PowerTopology] Shrink the fixed-array test storage to fit the Zephyr stack budget The FixedCircuitNodeStorage test double held Node[50], roughly 8 KB, and the tests place it on the stack. That tripped -Werror=stack-usage=8192 on the Zephyr native_posix unit-test build (worst case 12112 bytes), breaking the nRF Connect SDK and ESP32_QEMU checks. Make the capacity a template parameter defaulting to 8 (about 1.3 KB), which brings the worst case to roughly 5.4 KB. No test stores more than four entries, and the resource-exhaustion test writes far past any capacity so it still exercises the limit. Fittingly, the double was failing the very constraint it exists to demonstrate. Testing: unit tests pass (22 cases). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * [PowerTopology] Require the fabric table for CIRC; bound the ReplaceAll staging write Two review findings on project-chip#73403, which carries this file until project-chip#73402 merges. 1. The ElectricalCircuit feature needs the fabric table, not "uses it if present". ElectricalCircuitNodes is fabric-scoped, so a removed fabric's entries must be purged, and that only happens through the OnFabricRemoved callback registered via AddFabricDelegate. Startup() previously skipped the registration silently when Config::fabricTable was null, and PowerTopology Instance defaults that parameter to nullptr, so an application could enable CIRC, compile cleanly, and leak a removed fabric's nodes with no diagnostic. Startup() now fails with CHIP_ERROR_INCORRECT_STATE and a log line, matching the Config::circuitNodeStorage check three lines above it. Returning an error is preferred over VerifyOrDie: Startup() already reports this class of misconfiguration through its return value, and a crash would be a harsher contract than the surrounding code uses. Shutdown() keeps its null check, since it can run on a cluster that never started or does not have CIRC. The unit tests construct CIRC clusters, so they now supply a fabric table via FabricTestFixture, as ElectricalCircuitNodesPurgedOnFabricRemovalTest already did. Added ElectricalCircuitWithoutFabricTableTest to pin the new contract. 2. WriteElectricalCircuitNodes staged the decoded ReplaceAll list into a buffer sized from list.ComputeSize() and indexed it with a counter driven by the iterator, without checking the two agree. Added a bounds check. Beyond decoder inconsistency this also covers newCount == 0, where the staging buffer is never allocated at all yet staging[0] would be written if the iterator yielded an element. Testing: TestPowerTopologyCluster 23/23 pass (was 22, plus the new one) and TestPowerTopologyClusterBackwardsCompatibility 1/1 passes, built natively with chip_build_tests=true. Confirmed the new guard is actually exercised rather than merely compiled: its error log appears exactly once across the suite, in the new test. clang-format clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * [PowerTopology] Discard a partially decoded ElectricalCircuitNodes value Load() appends entries as it decodes them, so a failure part way through a stored blob left the decoded prefix in place. Init() logs that error and returns CHIP_NO_ERROR, since a bad stored value must not brick startup, so the cluster went on to serve an arbitrary fraction of a corrupt list as the attribute value. Reset the count in that path so the list reads as empty instead. Also document the persisted blob's worst case size against the per-value limits some platforms impose, and move the anonymous namespace close below the final test so every fixture-derived test sits inside it. * TC-PWRTL-2.1: align the script step list with the test plan The script and the plan had drifted: the script combined the AttributeList read and its composition check into one step, wrote to only one of the two read-only attributes, and split the reboot and the persistence read across two steps. Step n in the script and step n in the plan stopped describing the same thing from step 5 onwards. Both are now 11 steps and match one for one. The reboot step also moves to request_device_reboot() with an app-ready-pattern directive, matching TC-PWRTL-2.2, instead of the is_pics_sdk_ci_only and wait_for_user_input dispatch, so it runs in CI rather than being skipped. * [TC-PWRTL-2.1] Make the reserved-bits check effective Step 4 is specified to validate the CIRC bit and reserved bits 5..31, but the reserved-bits half was inert. PowerTopology.Feature is an IntFlag whose members cover exactly bits 0..4, so `~KNOWN_BITS_MASK` complements only within the class mask and evaluates to 0. `reserved_bits` was therefore unconditionally 0 and a DUT setting any reserved bit passed. Casting both operands to int restores the full-width complement. * Drop the step-numbering remark from the TC-PWRTL-2.1 docstring It describes the file rather than the test, and goes stale the moment the plan renumbers. * Drop the unsupported-write step from TC-PWRTL-2.1 Per James Harrow: unsupported-write checks come out, since the IDM and low-level SDK tests already cover write access. Step 11 becomes step 10; the Status import goes with the last use. * Align TC-PWRTL-2.1 with its test plan Per James Harrow: align the script to the plan, and pre-existing lines are not exempt. The merged TC-PWRTL-2.1 plan has three steps; the script had eleven. It is now the plan's three, each carrying the plan's expected outcome as an expectation. Implementing the plan literally also adds an assertion the script never had: the plan requires both endpoint lists to hold no more than 20 entries, and nothing checked that. Removed: FeatureMap O.a conformance, the CIRC and reserved-bit checks, and the AttributeList composition checks, all covered by TC_IDM_10_2 per cecille, which is why the plan rework (chip-test-plans#6166) and its script alignment (project-chip#73732) were closed on 2026-08-28. Also removed the Non-Volatile persistence check, which the plan does not describe. * Drop the plan URL from the TC-PWRTL-2.1 docstring Only 16 of 501 scripts on master embed one, and a hardcoded branch-and-anchor link goes stale on any renumber. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 18745b7 commit 287747a

14 files changed

Lines changed: 1419 additions & 156 deletions

examples/energy-management/electrical-sensor/include/ElectricalSensorManager.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ class ElectricalSensorManager
5959
struct PtConfig
6060
{
6161
BitMask<PowerTopology::Feature> features;
62+
// Optional: pass the fabric table when the CIRC feature is enabled so the Power Topology
63+
// cluster can purge a removed fabric's ElectricalCircuitNodes entries. Null otherwise.
64+
FabricTable * fabricTable = nullptr;
6265
};
6366

6467
CHIP_ERROR Init(EndpointId endpointId, const EpmConfig & epmConfig, const EemConfig & eemConfig, const PtConfig & ptConfig);

examples/energy-management/electrical-sensor/include/PowerTopologyDelegateImpl.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <app/clusters/power-topology-server/power-topology-server.h>
2121

2222
#include <app/util/af-types.h>
23+
#include <credentials/FabricTable.h>
2324
#include <lib/core/CHIPError.h>
2425

2526
namespace chip {
@@ -39,8 +40,9 @@ class PowerTopologyDelegate : public Delegate
3940
class PowerTopologyInstance : public Instance
4041
{
4142
public:
42-
PowerTopologyInstance(EndpointId aEndpointId, PowerTopologyDelegate & aDelegate, BitMask<Feature> aFeature) :
43-
PowerTopology::Instance(aEndpointId, aDelegate, aFeature)
43+
PowerTopologyInstance(EndpointId aEndpointId, PowerTopologyDelegate & aDelegate, BitMask<Feature> aFeature,
44+
FabricTable * aFabricTable = nullptr) :
45+
PowerTopology::Instance(aEndpointId, aDelegate, aFeature, aFabricTable)
4446
{
4547
mDelegate = &aDelegate;
4648
}
@@ -70,7 +72,8 @@ class PowerTopologyInstance : public Instance
7072
* @return CHIP_NO_ERROR if the PowerTopology cluster is initialized successfully, otherwise an error code
7173
*/
7274
CHIP_ERROR PowerTopologyInit(chip::EndpointId endpointId, std::unique_ptr<PowerTopologyDelegate> & aDelegate,
73-
std::unique_ptr<PowerTopologyInstance> & aInstance, BitMask<Feature> aFeature);
75+
std::unique_ptr<PowerTopologyInstance> & aInstance, BitMask<Feature> aFeature,
76+
FabricTable * aFabricTable = nullptr);
7477

7578
CHIP_ERROR PowerTopologyShutdown(std::unique_ptr<PowerTopologyInstance> & aInstance,
7679
std::unique_ptr<PowerTopologyDelegate> & aDelegate);

examples/energy-management/electrical-sensor/src/ElectricalSensorManager.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ CHIP_ERROR ElectricalSensorManager::Init(EndpointId endpointId, const EpmConfig
4949
SuccessOrShutdown(CodegenDataModelProvider::Instance().Registry().Register(mEEMCluster->Registration()));
5050

5151
// --- Initialize Power Topology ---
52-
SuccessOrShutdown(PowerTopology::PowerTopologyInit(endpointId, mPTDelegate, mPTInstance, ptConfig.features));
52+
SuccessOrShutdown(
53+
PowerTopology::PowerTopologyInit(endpointId, mPTDelegate, mPTInstance, ptConfig.features, ptConfig.fabricTable));
5354

5455
return CHIP_NO_ERROR;
5556
}

examples/energy-management/electrical-sensor/src/PowerTopologyDelegateImpl.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ void PowerTopologyInstance::Shutdown()
5151
* Then call the Instance->Init() to register the attribute and command handlers
5252
*/
5353
CHIP_ERROR PowerTopologyInit(chip::EndpointId endpointId, std::unique_ptr<PowerTopologyDelegate> & aDelegate,
54-
std::unique_ptr<PowerTopologyInstance> & aInstance, BitMask<Feature> aFeature)
54+
std::unique_ptr<PowerTopologyInstance> & aInstance, BitMask<Feature> aFeature,
55+
FabricTable * aFabricTable)
5556
{
5657
CHIP_ERROR err;
5758

@@ -68,7 +69,7 @@ CHIP_ERROR PowerTopologyInit(chip::EndpointId endpointId, std::unique_ptr<PowerT
6869
return CHIP_ERROR_NO_MEMORY;
6970
}
7071

71-
aInstance = std::make_unique<PowerTopologyInstance>(EndpointId(endpointId), *aDelegate, aFeature);
72+
aInstance = std::make_unique<PowerTopologyInstance>(EndpointId(endpointId), *aDelegate, aFeature, aFabricTable);
7273

7374
if (!aInstance)
7475
{

examples/evse-app/evse-common/src/EnergyEvseMain.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,9 @@ CHIP_ERROR EnergyManagementCommonClustersInit(chip::EndpointId endpointId)
252252
};
253253

254254
ElectricalSensorManager::PtConfig ptConfig{
255-
.features = BitMask<PowerTopology::Feature, uint32_t>(PowerTopology::Feature::kNodeTopology),
255+
.features = BitMask<PowerTopology::Feature, uint32_t>(PowerTopology::Feature::kNodeTopology,
256+
PowerTopology::Feature::kElectricalCircuit),
257+
.fabricTable = &Server::GetInstance().GetFabricTable(),
256258
};
257259

258260
ReturnErrorOnFailure(gESManager->Init(endpointId, epmConfig, eemConfig, ptConfig));

src/app/clusters/power-topology-server/BUILD.gn

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ import("//build_overrides/chip.gni")
1616

1717
source_set("power-topology-server") {
1818
sources = [
19+
"DefaultPowerTopologyCircuitNodeStorage.cpp",
20+
"DefaultPowerTopologyCircuitNodeStorage.h",
21+
"PowerTopologyCircuitNodeStorage.h",
1922
"PowerTopologyCluster.cpp",
2023
"PowerTopologyCluster.h",
2124
"PowerTopologyDelegate.h",

src/app/clusters/power-topology-server/CodegenIntegration.h

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
*/
1818
#pragma once
1919

20+
#include <app/clusters/power-topology-server/DefaultPowerTopologyCircuitNodeStorage.h>
2021
#include <app/clusters/power-topology-server/PowerTopologyCluster.h>
2122
#include <app/clusters/power-topology-server/PowerTopologyDelegate.h>
2223

@@ -31,19 +32,42 @@ namespace PowerTopology {
3132
class Instance
3233
{
3334
public:
34-
Instance(EndpointId aEndpointId, Delegate & aDelegate, BitMask<Feature> aFeature) :
35+
/// Uses DefaultCircuitNodeStorage for the ElectricalCircuitNodes attribute, so an application
36+
/// enabling the ElectricalCircuit feature gets persistence with no extra work. An application
37+
/// that cannot allocate, or that wants its own persistence, should use the overload below.
38+
Instance(EndpointId aEndpointId, Delegate & aDelegate, BitMask<Feature> aFeature, FabricTable * aFabricTable = nullptr) :
3539
mCluster(PowerTopologyCluster::Config{
36-
.endpointId = aEndpointId,
37-
.delegate = aDelegate,
38-
.features = aFeature,
40+
.endpointId = aEndpointId,
41+
.delegate = aDelegate,
42+
.features = aFeature,
43+
.fabricTable = aFabricTable,
44+
.circuitNodeStorage = &mDefaultCircuitNodeStorage,
3945
})
4046
{}
47+
48+
/// Uses application-provided storage for ElectricalCircuitNodes. `aCircuitNodeStorage` must
49+
/// outlive this Instance.
50+
Instance(EndpointId aEndpointId, Delegate & aDelegate, BitMask<Feature> aFeature, CircuitNodeStorage & aCircuitNodeStorage,
51+
FabricTable * aFabricTable = nullptr) :
52+
mCluster(PowerTopologyCluster::Config{
53+
.endpointId = aEndpointId,
54+
.delegate = aDelegate,
55+
.features = aFeature,
56+
.fabricTable = aFabricTable,
57+
.circuitNodeStorage = &aCircuitNodeStorage,
58+
})
59+
{}
60+
4161
~Instance() { Shutdown(); }
4262

4363
CHIP_ERROR Init();
4464
void Shutdown();
4565

4666
private:
67+
// Only used by the first constructor; harmless (and unallocated) otherwise, since
68+
// DefaultCircuitNodeStorage allocates nothing until Init() and the cluster only calls Init()
69+
// when the ElectricalCircuit feature is enabled.
70+
DefaultCircuitNodeStorage mDefaultCircuitNodeStorage;
4771
RegisteredServerCluster<PowerTopologyCluster> mCluster;
4872
};
4973

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
/*
2+
*
3+
* Copyright (c) 2026 Project CHIP Authors
4+
* All rights reserved.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
#include <app/clusters/power-topology-server/DefaultPowerTopologyCircuitNodeStorage.h>
20+
21+
#include <clusters/PowerTopology/AttributeIds.h>
22+
#include <clusters/PowerTopology/ClusterId.h>
23+
#include <lib/core/TLV.h>
24+
#include <lib/support/CodeUtils.h>
25+
#include <lib/support/logging/CHIPLogging.h>
26+
27+
#include <cstring>
28+
29+
namespace chip {
30+
namespace app {
31+
namespace Clusters {
32+
namespace PowerTopology {
33+
34+
namespace {
35+
36+
// TLV tags for the persisted ElectricalCircuitNodes blob. This is a private storage format, not the
37+
// wire encoding: the CircuitNodeStruct wire codec is fabric-aware and omits the fabric index on
38+
// write, so persistence uses an explicit format that retains every field including fabricIndex.
39+
constexpr TLV::Tag kTagFabricIndex = TLV::ContextTag(0);
40+
constexpr TLV::Tag kTagNode = TLV::ContextTag(1);
41+
constexpr TLV::Tag kTagEndpoint = TLV::ContextTag(2);
42+
constexpr TLV::Tag kTagLabel = TLV::ContextTag(3);
43+
44+
// Worst case ~ kMaxCircuitNodes * (node + endpoint + label + fabricIndex + TLV overhead).
45+
constexpr size_t kCircuitNodesBlobSize = CircuitNodeStorage::kMaxCircuitNodes * (CircuitNodeStorage::kMaxNodeLabelLength + 32) + 8;
46+
47+
ConcreteAttributePath CircuitNodesPath(EndpointId endpointId)
48+
{
49+
return ConcreteAttributePath(endpointId, PowerTopology::Id, Attributes::ElectricalCircuitNodes::Id);
50+
}
51+
52+
} // namespace
53+
54+
CHIP_ERROR DefaultCircuitNodeStorage::Init(AttributePersistenceProvider & attributeStorage, EndpointId endpointId)
55+
{
56+
VerifyOrReturnError(mNodes.Alloc(kMaxCircuitNodes), CHIP_ERROR_NO_MEMORY);
57+
58+
mAttributeStorage = &attributeStorage;
59+
mEndpointId = endpointId;
60+
mCount = 0;
61+
62+
// A missing value is normal on first boot and must not fail startup.
63+
CHIP_ERROR err = Load();
64+
if (err != CHIP_NO_ERROR && err != CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
65+
{
66+
ChipLogError(Zcl, "PowerTopology: failed to load ElectricalCircuitNodes: %" CHIP_ERROR_FORMAT, err.Format());
67+
// Load() appends as it decodes, so a failure part way through leaves a
68+
// truncated prefix of a corrupt blob behind. Startup still proceeds, but
69+
// report an empty list rather than an arbitrary fraction of the stored one.
70+
mCount = 0;
71+
}
72+
return CHIP_NO_ERROR;
73+
}
74+
75+
size_t DefaultCircuitNodeStorage::CountForFabric(FabricIndex fabricIndex) const
76+
{
77+
size_t count = 0;
78+
for (size_t i = 0; i < mCount; i++)
79+
{
80+
if (mNodes[i].fabricIndex == fabricIndex)
81+
{
82+
count++;
83+
}
84+
}
85+
return count;
86+
}
87+
88+
CHIP_ERROR DefaultCircuitNodeStorage::GetNodeAtIndex(size_t index, Node & outNode) const
89+
{
90+
VerifyOrReturnError(index < mCount, CHIP_ERROR_INVALID_ARGUMENT);
91+
outNode = mNodes[index];
92+
return CHIP_NO_ERROR;
93+
}
94+
95+
size_t DefaultCircuitNodeStorage::EraseFabric(FabricIndex fabricIndex)
96+
{
97+
size_t kept = 0;
98+
for (size_t i = 0; i < mCount; i++)
99+
{
100+
if (mNodes[i].fabricIndex != fabricIndex)
101+
{
102+
if (kept != i)
103+
{
104+
mNodes[kept] = mNodes[i];
105+
}
106+
kept++;
107+
}
108+
}
109+
const size_t removed = mCount - kept;
110+
mCount = kept;
111+
return removed;
112+
}
113+
114+
CHIP_ERROR DefaultCircuitNodeStorage::ReplaceNodesForFabric(FabricIndex fabricIndex, const Node * nodes, size_t count)
115+
{
116+
VerifyOrReturnError(mAttributeStorage != nullptr, CHIP_ERROR_INCORRECT_STATE);
117+
VerifyOrReturnError(count == 0 || nodes != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
118+
119+
// Check capacity before mutating anything: on failure the stored contents must be unchanged.
120+
const size_t otherFabricCount = mCount - CountForFabric(fabricIndex);
121+
VerifyOrReturnError(otherFabricCount + count <= Capacity(), CHIP_ERROR_NO_MEMORY);
122+
123+
EraseFabric(fabricIndex);
124+
for (size_t i = 0; i < count; i++)
125+
{
126+
mNodes[mCount++] = nodes[i];
127+
}
128+
return Save();
129+
}
130+
131+
CHIP_ERROR DefaultCircuitNodeStorage::AppendNode(const Node & node)
132+
{
133+
VerifyOrReturnError(mAttributeStorage != nullptr, CHIP_ERROR_INCORRECT_STATE);
134+
VerifyOrReturnError(mCount < Capacity(), CHIP_ERROR_NO_MEMORY);
135+
136+
mNodes[mCount++] = node;
137+
return Save();
138+
}
139+
140+
CHIP_ERROR DefaultCircuitNodeStorage::RemoveNodesForFabric(FabricIndex fabricIndex)
141+
{
142+
VerifyOrReturnError(mAttributeStorage != nullptr, CHIP_ERROR_INCORRECT_STATE);
143+
144+
// Removing a fabric with no entries succeeds without a persist.
145+
VerifyOrReturnValue(EraseFabric(fabricIndex) > 0, CHIP_NO_ERROR);
146+
return Save();
147+
}
148+
149+
CHIP_ERROR DefaultCircuitNodeStorage::Save() const
150+
{
151+
VerifyOrReturnError(mAttributeStorage != nullptr, CHIP_ERROR_INCORRECT_STATE);
152+
153+
Platform::ScopedMemoryBuffer<uint8_t> buffer;
154+
VerifyOrReturnError(buffer.Calloc(kCircuitNodesBlobSize), CHIP_ERROR_NO_MEMORY);
155+
156+
TLV::TLVWriter writer;
157+
writer.Init(buffer.Get(), kCircuitNodesBlobSize);
158+
TLV::TLVType arrayContainer;
159+
ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Array, arrayContainer));
160+
for (size_t i = 0; i < mCount; i++)
161+
{
162+
const Node & node = mNodes[i];
163+
TLV::TLVType nodeContainer;
164+
ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, nodeContainer));
165+
ReturnErrorOnFailure(writer.Put(kTagFabricIndex, node.fabricIndex));
166+
ReturnErrorOnFailure(writer.Put(kTagNode, node.node));
167+
if (node.endpoint.HasValue())
168+
{
169+
ReturnErrorOnFailure(writer.Put(kTagEndpoint, node.endpoint.Value()));
170+
}
171+
if (node.hasLabel)
172+
{
173+
ReturnErrorOnFailure(writer.PutString(kTagLabel, CharSpan(node.label, node.labelLength)));
174+
}
175+
ReturnErrorOnFailure(writer.EndContainer(nodeContainer));
176+
}
177+
ReturnErrorOnFailure(writer.EndContainer(arrayContainer));
178+
ReturnErrorOnFailure(writer.Finalize());
179+
180+
return mAttributeStorage->WriteValue(CircuitNodesPath(mEndpointId), ByteSpan(buffer.Get(), writer.GetLengthWritten()));
181+
}
182+
183+
CHIP_ERROR DefaultCircuitNodeStorage::Load()
184+
{
185+
VerifyOrReturnError(mAttributeStorage != nullptr, CHIP_ERROR_INCORRECT_STATE);
186+
187+
Platform::ScopedMemoryBuffer<uint8_t> buffer;
188+
VerifyOrReturnError(buffer.Calloc(kCircuitNodesBlobSize), CHIP_ERROR_NO_MEMORY);
189+
190+
MutableByteSpan span(buffer.Get(), kCircuitNodesBlobSize);
191+
ReturnErrorOnFailure(mAttributeStorage->ReadValue(CircuitNodesPath(mEndpointId), span));
192+
193+
TLV::TLVReader reader;
194+
reader.Init(span);
195+
ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Array, TLV::AnonymousTag()));
196+
TLV::TLVType arrayContainer;
197+
ReturnErrorOnFailure(reader.EnterContainer(arrayContainer));
198+
199+
mCount = 0;
200+
CHIP_ERROR err;
201+
while ((err = reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag())) == CHIP_NO_ERROR)
202+
{
203+
// Silently stop at capacity rather than fail: a shrunk Capacity() must not brick startup.
204+
VerifyOrReturnError(mCount < Capacity(), CHIP_NO_ERROR);
205+
206+
TLV::TLVType nodeContainer;
207+
ReturnErrorOnFailure(reader.EnterContainer(nodeContainer));
208+
209+
Node node;
210+
CHIP_ERROR fieldErr;
211+
while ((fieldErr = reader.Next()) == CHIP_NO_ERROR)
212+
{
213+
if (reader.GetTag() == kTagFabricIndex)
214+
{
215+
ReturnErrorOnFailure(reader.Get(node.fabricIndex));
216+
}
217+
else if (reader.GetTag() == kTagNode)
218+
{
219+
ReturnErrorOnFailure(reader.Get(node.node));
220+
}
221+
else if (reader.GetTag() == kTagEndpoint)
222+
{
223+
EndpointId endpoint;
224+
ReturnErrorOnFailure(reader.Get(endpoint));
225+
node.endpoint.SetValue(endpoint);
226+
}
227+
else if (reader.GetTag() == kTagLabel)
228+
{
229+
CharSpan label;
230+
ReturnErrorOnFailure(reader.Get(label));
231+
VerifyOrReturnError(label.size() <= kMaxNodeLabelLength, CHIP_ERROR_INVALID_TLV_ELEMENT);
232+
memcpy(node.label, label.data(), label.size());
233+
node.labelLength = label.size();
234+
node.hasLabel = true;
235+
}
236+
}
237+
VerifyOrReturnError(fieldErr == CHIP_END_OF_TLV, fieldErr);
238+
ReturnErrorOnFailure(reader.ExitContainer(nodeContainer));
239+
240+
mNodes[mCount++] = node;
241+
}
242+
VerifyOrReturnError(err == CHIP_END_OF_TLV, err);
243+
return reader.ExitContainer(arrayContainer);
244+
}
245+
246+
} // namespace PowerTopology
247+
} // namespace Clusters
248+
} // namespace app
249+
} // namespace chip

0 commit comments

Comments
 (0)