Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2025 Project CHIP Authors
4 : *
5 : * Licensed under the Apache License, Version 2.0 (the "License");
6 : * you may not use this file except in compliance with the License.
7 : * You may obtain a copy of the License at
8 : *
9 : * http://www.apache.org/licenses/LICENSE-2.0
10 : *
11 : * Unless required by applicable law or agreed to in writing, software
12 : * distributed under the License is distributed on an "AS IS" BASIS,
13 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 : * See the License for the specific language governing permissions and
15 : * limitations under the License.
16 : */
17 :
18 : #include <app/server/JointFabricDatastore.h>
19 :
20 : #include <algorithm>
21 : #include <cstring>
22 : #include <unordered_set>
23 :
24 : namespace chip {
25 : namespace app {
26 :
27 : namespace {
28 : /**
29 : * Validate that an input is the correct length to be an Epoch Key. Null keys are considered valid.
30 : */
31 9 : bool EpochKeyFitsStorage(const DataModel::Nullable<ByteSpan> & key)
32 : {
33 : using EpochKeyStorage = Crypto::SensitiveDataBuffer<Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES>;
34 9 : return key.IsNull() || key.Value().size() <= EpochKeyStorage::Capacity();
35 : }
36 : } // namespace
37 :
38 3 : CHIP_ERROR JointFabricDatastore::CopyGroupKeySetWithOwnedSpans(
39 : const Clusters::JointFabricDatastore::Structs::DatastoreGroupKeySetStruct::Type & source,
40 : Clusters::JointFabricDatastore::Structs::DatastoreGroupKeySetStruct::Type & destination)
41 : {
42 : // Validate before mutating any storage so a non-conformant input leaves existing entries untouched.
43 3 : VerifyOrReturnError(EpochKeyFitsStorage(source.epochKey0) && EpochKeyFitsStorage(source.epochKey1) &&
44 : EpochKeyFitsStorage(source.epochKey2),
45 : CHIP_IM_GLOBAL_STATUS(ConstraintError));
46 :
47 3 : auto & storage = mGroupKeySetStorage[source.groupKeySetID];
48 :
49 3 : destination.groupKeySetID = source.groupKeySetID;
50 3 : destination.groupKeySecurityPolicy = source.groupKeySecurityPolicy;
51 :
52 3 : CopyByteSpanWithOwnedStorage(source.epochKey0, storage.epochKey0, destination.epochKey0);
53 3 : CopyByteSpanWithOwnedStorage(source.epochKey1, storage.epochKey1, destination.epochKey1);
54 3 : CopyByteSpanWithOwnedStorage(source.epochKey2, storage.epochKey2, destination.epochKey2);
55 :
56 3 : CopyNullableValue(source.epochStartTime0, destination.epochStartTime0);
57 3 : CopyNullableValue(source.epochStartTime1, destination.epochStartTime1);
58 3 : CopyNullableValue(source.epochStartTime2, destination.epochStartTime2);
59 :
60 3 : return CHIP_NO_ERROR;
61 : }
62 :
63 0 : void JointFabricDatastore::RemoveGroupKeySetStorage(uint16_t groupKeySetId)
64 : {
65 : // The epoch-key buffers self-zeroize in their destructors when the entry is erased.
66 0 : mGroupKeySetStorage.erase(groupKeySetId);
67 0 : }
68 :
69 5 : void JointFabricDatastore::SetGroupInformationFriendlyNameWithOwnedStorage(
70 : GroupId groupId, const CharSpan & friendlyName,
71 : Clusters::JointFabricDatastore::Structs::DatastoreGroupInformationEntryStruct::Type & destination)
72 : {
73 5 : auto & storage = mGroupInformationStorage[groupId];
74 5 : storage.friendlyName.assign(friendlyName.data(), friendlyName.data() + friendlyName.size());
75 5 : destination.friendlyName = CharSpan(storage.friendlyName.data(), storage.friendlyName.size());
76 5 : }
77 :
78 0 : void JointFabricDatastore::RemoveGroupInformationStorage(GroupId groupId)
79 : {
80 0 : mGroupInformationStorage.erase(groupId);
81 0 : }
82 :
83 2 : CHIP_ERROR JointFabricDatastore::SetAdminEntryWithOwnedStorage(
84 : NodeId nodeId, const CharSpan & friendlyName, const ByteSpan & icac,
85 : Clusters::JointFabricDatastore::Structs::DatastoreAdministratorInformationEntryStruct::Type & destination)
86 : {
87 2 : auto & storage = mAdminEntryStorage[nodeId];
88 :
89 2 : storage.friendlyName.assign(friendlyName.data(), friendlyName.data() + friendlyName.size());
90 2 : destination.friendlyName = CharSpan(storage.friendlyName.data(), storage.friendlyName.size());
91 :
92 2 : ReturnErrorOnFailure(storage.icac.SetLength(icac.size()));
93 2 : memcpy(storage.icac.Bytes(), icac.data(), icac.size());
94 2 : destination.icac = storage.icac.Span();
95 :
96 2 : return CHIP_NO_ERROR;
97 : }
98 :
99 0 : void JointFabricDatastore::RemoveAdminEntryStorage(NodeId nodeId)
100 : {
101 : // The ICAC buffer self-zeroizes in its destructor when the entry is erased.
102 0 : mAdminEntryStorage.erase(nodeId);
103 0 : }
104 :
105 8 : void JointFabricDatastore::SetEndpointFriendlyNameWithOwnedStorage(
106 : NodeId nodeId, EndpointId endpointId, const CharSpan & friendlyName,
107 : Clusters::JointFabricDatastore::Structs::DatastoreEndpointEntryStruct::Type & destination)
108 : {
109 8 : auto & storage = mEndpointFriendlyNameStorage[{ nodeId, endpointId }];
110 8 : storage.assign(friendlyName.data(), friendlyName.data() + friendlyName.size());
111 8 : destination.friendlyName = CharSpan(storage.data(), storage.size());
112 8 : }
113 :
114 0 : void JointFabricDatastore::RemoveEndpointFriendlyNameStorage(NodeId nodeId, EndpointId endpointId)
115 : {
116 0 : mEndpointFriendlyNameStorage.erase({ nodeId, endpointId });
117 0 : }
118 :
119 9 : void JointFabricDatastore::CopyByteSpanWithOwnedStorage(const DataModel::Nullable<ByteSpan> & source, EpochKeyStorage & storage,
120 : DataModel::Nullable<ByteSpan> & destination)
121 : {
122 : // Over-length epoch keys are rejected by CopyGroupKeySetWithOwnedSpans before reaching here, so the
123 : // SetLength below is expected to succeed; the failure branch remains as a defensive fallback only.
124 14 : if (!source.IsNull() && storage.SetLength(source.Value().size()) == CHIP_NO_ERROR)
125 : {
126 5 : memcpy(storage.Bytes(), source.Value().data(), source.Value().size());
127 5 : destination = storage.Span();
128 : }
129 : else
130 : {
131 4 : storage.Clear();
132 4 : destination.SetNull();
133 : }
134 9 : }
135 :
136 3 : void JointFabricDatastore::AddListener(Listener & listener)
137 : {
138 3 : if (mListeners == nullptr)
139 : {
140 3 : mListeners = &listener;
141 3 : listener.mNext = nullptr;
142 3 : return;
143 : }
144 :
145 0 : for (Listener * l = mListeners; /**/; l = l->mNext)
146 : {
147 0 : if (l == &listener)
148 : {
149 0 : return;
150 : }
151 :
152 0 : if (l->mNext == nullptr)
153 : {
154 0 : l->mNext = &listener;
155 0 : listener.mNext = nullptr;
156 0 : return;
157 : }
158 : }
159 : }
160 :
161 1 : void JointFabricDatastore::RemoveListener(Listener & listener)
162 : {
163 1 : if (mListeners == &listener)
164 : {
165 1 : mListeners = listener.mNext;
166 1 : listener.mNext = nullptr;
167 1 : return;
168 : }
169 :
170 0 : for (Listener * l = mListeners; l != nullptr; l = l->mNext)
171 : {
172 0 : if (l->mNext == &listener)
173 : {
174 0 : l->mNext = listener.mNext;
175 0 : listener.mNext = nullptr;
176 0 : return;
177 : }
178 : }
179 : }
180 :
181 24 : CHIP_ERROR JointFabricDatastore::AddPendingNode(NodeId nodeId, const CharSpan & friendlyName)
182 : {
183 24 : VerifyOrReturnError(mNodeInformationEntries.size() < kMaxNodes, CHIP_ERROR_NO_MEMORY);
184 : // check that nodeId does not already exist
185 102 : VerifyOrReturnError(
186 : std::none_of(mNodeInformationEntries.begin(), mNodeInformationEntries.end(),
187 : [nodeId](const GenericDatastoreNodeInformationEntry & entry) { return entry.nodeID == nodeId; }),
188 : CHIP_IM_GLOBAL_STATUS(ConstraintError));
189 :
190 24 : mNodeInformationEntries.push_back(GenericDatastoreNodeInformationEntry(
191 24 : nodeId, Clusters::JointFabricDatastore::DatastoreStateEnum::kPending, MakeOptional(friendlyName)));
192 :
193 26 : for (Listener * listener = mListeners; listener != nullptr; listener = listener->mNext)
194 : {
195 2 : listener->MarkNodeListChanged();
196 : }
197 :
198 24 : return CHIP_NO_ERROR;
199 : }
200 :
201 1 : CHIP_ERROR JointFabricDatastore::UpdateNode(NodeId nodeId, const CharSpan & friendlyName)
202 : {
203 1 : for (auto & entry : mNodeInformationEntries)
204 : {
205 1 : if (entry.nodeID == nodeId)
206 : {
207 1 : entry.Set(MakeOptional(friendlyName));
208 :
209 2 : for (Listener * listener = mListeners; listener != nullptr; listener = listener->mNext)
210 : {
211 1 : listener->MarkNodeListChanged();
212 : }
213 :
214 1 : return CHIP_NO_ERROR;
215 : }
216 : }
217 :
218 0 : return CHIP_IM_GLOBAL_STATUS(ConstraintError);
219 : }
220 :
221 0 : CHIP_ERROR JointFabricDatastore::RemoveNode(NodeId nodeId)
222 : {
223 0 : for (auto it = mNodeInformationEntries.begin(); it != mNodeInformationEntries.end(); ++it)
224 : {
225 0 : if (it->nodeID == nodeId)
226 : {
227 0 : mNodeInformationEntries.erase(it);
228 :
229 0 : for (Listener * listener = mListeners; listener != nullptr; listener = listener->mNext)
230 : {
231 0 : listener->MarkNodeListChanged();
232 : }
233 :
234 0 : return CHIP_NO_ERROR;
235 : }
236 : }
237 :
238 0 : return CHIP_IM_GLOBAL_STATUS(ConstraintError);
239 : }
240 :
241 2 : CHIP_ERROR JointFabricDatastore::RefreshNode(NodeId nodeId)
242 : {
243 2 : VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
244 2 : VerifyOrReturnError(mRefreshingNodeId == kUndefinedNodeId, CHIP_ERROR_INCORRECT_STATE);
245 2 : VerifyOrReturnError(mRefreshState == kIdle, CHIP_ERROR_INCORRECT_STATE);
246 :
247 2 : mRefreshingNodeId = nodeId;
248 :
249 2 : ReturnErrorOnFailure(ContinueRefresh());
250 :
251 1 : return CHIP_NO_ERROR;
252 : }
253 :
254 12 : CHIP_ERROR JointFabricDatastore::ContinueRefresh()
255 : {
256 :
257 12 : switch (mRefreshState)
258 : {
259 2 : case kIdle: {
260 : // 1. Confirm that a Node Information Entry exists for the given NodeID, and if not, return NOT_FOUND.
261 : // 2. Set the Node Information Entry's state to Pending.
262 2 : ReturnErrorOnFailure(SetNode(mRefreshingNodeId, Clusters::JointFabricDatastore::DatastoreStateEnum::kPending));
263 :
264 : // Request endpoints from the device's Descriptor cluster and transition to the
265 : // kRefreshingEndpoints state. The delegate call is asynchronous and will invoke
266 : // the provided callback when complete; the callback stores the received list
267 : // and calls ContinueRefresh() to advance the state machine.
268 :
269 4 : ReturnErrorOnFailure(mDelegate->FetchEndpointList(
270 : mRefreshingNodeId,
271 : [this](CHIP_ERROR err,
272 : const std::vector<Clusters::JointFabricDatastore::Structs::DatastoreEndpointEntryStruct::Type> & endpoints) {
273 : if (err == CHIP_NO_ERROR)
274 : {
275 : // Store the fetched endpoints for processing in the next state.
276 : mRefreshingEndpointsList = endpoints;
277 :
278 : // Advance the state machine to process the endpoints.
279 : mRefreshState = kRefreshingEndpoints;
280 : }
281 : else
282 : {
283 : // Leave node as pending but tear down the refresh state.
284 : mRefreshingNodeId = kUndefinedNodeId;
285 : mRefreshState = kIdle;
286 : return;
287 : }
288 :
289 : // Continue the state machine (will enter kRefreshingEndpoints branch
290 : // when successful and process mRefreshingEndpointsList).
291 : if (ContinueRefresh() != CHIP_NO_ERROR)
292 : {
293 : // Ignore errors in continuation from within the callback.
294 : }
295 : }));
296 : }
297 1 : break;
298 1 : case kRefreshingEndpoints: {
299 : // 3. cycle through mRefreshingEndpointsList and add them to the endpoint entries
300 1 : for (const auto & endpoint : mRefreshingEndpointsList)
301 : {
302 0 : auto it = std::find_if(
303 : mEndpointEntries.begin(), mEndpointEntries.end(),
304 0 : [this, &endpoint](const Clusters::JointFabricDatastore::Structs::DatastoreEndpointEntryStruct::Type & entry) {
305 0 : return entry.nodeID == mRefreshingNodeId && entry.endpointID == endpoint.endpointID;
306 : });
307 0 : if (it == mEndpointEntries.end())
308 : {
309 0 : Clusters::JointFabricDatastore::Structs::DatastoreEndpointEntryStruct::Type newEntry;
310 0 : newEntry.endpointID = endpoint.endpointID;
311 0 : newEntry.nodeID = mRefreshingNodeId;
312 0 : mEndpointEntries.push_back(newEntry);
313 : }
314 : }
315 :
316 : // TODO: sync friendly name between datastore entry and basic cluster
317 :
318 : // Remove EndpointEntries that are not in the mRefreshingEndpointsList
319 2 : mEndpointEntries.erase(
320 1 : std::remove_if(mEndpointEntries.begin(), mEndpointEntries.end(),
321 0 : [&](const auto & entry) {
322 0 : if (entry.nodeID != mRefreshingNodeId)
323 : {
324 0 : return false;
325 : }
326 : const bool shouldRemove =
327 0 : std::none_of(mRefreshingEndpointsList.begin(), mRefreshingEndpointsList.end(),
328 0 : [&](const auto & endpoint) { return entry.endpointID == endpoint.endpointID; });
329 0 : if (shouldRemove)
330 : {
331 0 : RemoveEndpointFriendlyNameStorage(entry.nodeID, entry.endpointID);
332 : }
333 0 : return shouldRemove;
334 : }),
335 1 : mEndpointEntries.end());
336 :
337 1 : if (std::none_of(mRefreshingEndpointsList.begin(), mRefreshingEndpointsList.end(),
338 0 : [](const auto & endpoint) { return endpoint.endpointID == kRootEndpointId; }))
339 : {
340 1 : Clusters::JointFabricDatastore::Structs::DatastoreEndpointEntryStruct::Type rootEndpoint;
341 1 : rootEndpoint.nodeID = mRefreshingNodeId;
342 1 : rootEndpoint.endpointID = kRootEndpointId;
343 1 : mRefreshingEndpointsList.push_back(rootEndpoint);
344 : }
345 :
346 : // Start fetching groups from the first endpoint
347 1 : mRefreshingEndpointIndex = 0;
348 1 : mRefreshState = kRefreshingGroups;
349 :
350 : // Fall through to kRefreshingGroups to start fetching
351 1 : return ContinueRefresh();
352 : }
353 : break;
354 :
355 2 : case kRefreshingGroups: {
356 : // Check if we still have endpoints to process for group fetching
357 2 : if (mRefreshingEndpointIndex < mRefreshingEndpointsList.size())
358 : {
359 : // Fetch group list for the current endpoint
360 1 : EndpointId currentEndpointId = mRefreshingEndpointsList[mRefreshingEndpointIndex].endpointID;
361 :
362 4 : ReturnErrorOnFailure(mDelegate->FetchEndpointGroupList(
363 : mRefreshingNodeId, currentEndpointId,
364 : [this, currentEndpointId](
365 : CHIP_ERROR err,
366 : const std::vector<Clusters::JointFabricDatastore::Structs::DatastoreGroupInformationEntryStruct::Type> &
367 : endpointGroups) {
368 : if (err == CHIP_NO_ERROR)
369 : {
370 : // Convert endpointGroups to mEndpointGroupIDEntries for this specific endpoint
371 : for (const auto & endpointGroup : endpointGroups)
372 : {
373 : auto it = std::find_if(
374 : mEndpointGroupIDEntries.begin(), mEndpointGroupIDEntries.end(),
375 : [this, currentEndpointId, &endpointGroup](
376 : const Clusters::JointFabricDatastore::Structs::DatastoreEndpointGroupIDEntryStruct::Type &
377 : entry) {
378 : return entry.nodeID == mRefreshingNodeId && entry.endpointID == currentEndpointId &&
379 : entry.groupID == endpointGroup.groupID;
380 : });
381 :
382 : if (it == mEndpointGroupIDEntries.end())
383 : {
384 : Clusters::JointFabricDatastore::Structs::DatastoreEndpointGroupIDEntryStruct::Type newEntry;
385 : newEntry.nodeID = mRefreshingNodeId;
386 : newEntry.endpointID = currentEndpointId;
387 : newEntry.groupID = static_cast<GroupId>(endpointGroup.groupID);
388 : newEntry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
389 : mEndpointGroupIDEntries.push_back(newEntry);
390 : }
391 : }
392 :
393 : // Remove entries not in endpointGroups for this specific endpoint
394 : mEndpointGroupIDEntries.erase(
395 : std::remove_if(mEndpointGroupIDEntries.begin(), mEndpointGroupIDEntries.end(),
396 : [&, currentEndpointId](const auto & entry) {
397 : if (entry.nodeID != mRefreshingNodeId || entry.endpointID != currentEndpointId)
398 : {
399 : return false;
400 : }
401 : if (entry.statusEntry.state !=
402 : Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted &&
403 : entry.statusEntry.state !=
404 : Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending)
405 : {
406 : return false;
407 : }
408 : return std::none_of(endpointGroups.begin(), endpointGroups.end(),
409 : [&](const auto & eg) { return entry.groupID == eg.groupID; });
410 : }),
411 : mEndpointGroupIDEntries.end());
412 : }
413 :
414 : // Move to the next endpoint
415 : mRefreshingEndpointIndex++;
416 :
417 : // Continue to process next endpoint or move to syncing phase
418 : if (ContinueRefresh() != CHIP_NO_ERROR)
419 : {
420 : // Ignore errors in continuation from within the callback.
421 : }
422 : }));
423 :
424 : // Return here - the callback will call ContinueRefresh() again
425 1 : return CHIP_NO_ERROR;
426 : }
427 :
428 : // All endpoints processed; now sync any pending/delete-pending entries
429 1 : for (auto it = mEndpointGroupIDEntries.begin(); it != mEndpointGroupIDEntries.end();)
430 : {
431 0 : if (it->nodeID == mRefreshingNodeId)
432 : {
433 0 : if (it->statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kPending)
434 : {
435 0 : auto entryToSync = *it;
436 0 : const NodeId nodeId = entryToSync.nodeID;
437 0 : const EndpointId epId = entryToSync.endpointID;
438 0 : const GroupId groupId = entryToSync.groupID;
439 0 : ReturnErrorOnFailure(mDelegate->SyncNode(mRefreshingNodeId, entryToSync, [this, nodeId, epId, groupId]() {
440 : detail::MarkEntryCommittedIfFound(mEndpointGroupIDEntries, [&](const auto & e) {
441 : return e.nodeID == nodeId && e.endpointID == epId && e.groupID == groupId;
442 : });
443 : }));
444 : }
445 0 : else if (it->statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending)
446 : {
447 0 : Clusters::JointFabricDatastore::Structs::DatastoreEndpointGroupIDEntryStruct::Type endpointGroupIdNullEntry{
448 : 0
449 : };
450 :
451 0 : auto entryToErase = *it;
452 0 : ReturnErrorOnFailure(mDelegate->SyncNode(mRefreshingNodeId, endpointGroupIdNullEntry, [this, entryToErase]() {
453 : mEndpointGroupIDEntries.erase(std::remove_if(mEndpointGroupIDEntries.begin(), mEndpointGroupIDEntries.end(),
454 : [&](const auto & entry) {
455 : return entry.nodeID == entryToErase.nodeID &&
456 : entry.endpointID == entryToErase.endpointID &&
457 : entry.groupID == entryToErase.groupID;
458 : }),
459 : mEndpointGroupIDEntries.end());
460 : }));
461 : }
462 0 : else if (it->statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitFailed)
463 : {
464 0 : CHIP_ERROR failureCode(it->statusEntry.failureCode);
465 :
466 0 : if (failureCode == CHIP_IM_GLOBAL_STATUS(ConstraintError) ||
467 0 : failureCode == CHIP_IM_GLOBAL_STATUS(ResourceExhausted))
468 : {
469 0 : ++it;
470 0 : continue;
471 : }
472 :
473 : // Retry or handle failure - for now skip
474 0 : ++it;
475 0 : continue;
476 0 : }
477 : }
478 :
479 0 : ++it;
480 : }
481 :
482 : // Start fetching groups from the first endpoint
483 1 : mRefreshingEndpointIndex = 0;
484 1 : mRefreshState = kRefreshingBindings;
485 :
486 : // Fall through to kRefreshingGroups to start fetching
487 1 : return ContinueRefresh();
488 : }
489 : break;
490 :
491 2 : case kRefreshingBindings: {
492 : // Check if we still have endpoints to process for group fetching
493 2 : if (mRefreshingEndpointIndex < mRefreshingEndpointsList.size())
494 : {
495 : // Fetch group list for the current endpoint
496 1 : EndpointId currentEndpointId = mRefreshingEndpointsList[mRefreshingEndpointIndex].endpointID;
497 :
498 4 : ReturnErrorOnFailure(mDelegate->FetchEndpointBindingList(
499 : mRefreshingNodeId, currentEndpointId,
500 : [this](CHIP_ERROR err,
501 : const std::vector<Clusters::JointFabricDatastore::Structs::DatastoreEndpointBindingEntryStruct::Type> &
502 : endpointBindings) {
503 : if (err == CHIP_NO_ERROR)
504 : {
505 : // Convert endpointBindings to mEndpointBindingEntries
506 : for (const auto & endpointBinding : endpointBindings)
507 : {
508 : auto it = std::find_if(
509 : mEndpointBindingEntries.begin(), mEndpointBindingEntries.end(),
510 : [this, &endpointBinding](
511 : const Clusters::JointFabricDatastore::Structs::DatastoreEndpointBindingEntryStruct::Type &
512 : entry) {
513 : return entry.nodeID == mRefreshingNodeId && entry.endpointID == endpointBinding.endpointID &&
514 : BindingMatches(entry.binding, endpointBinding.binding);
515 : });
516 :
517 : if (it == mEndpointBindingEntries.end())
518 : {
519 : Clusters::JointFabricDatastore::Structs::DatastoreEndpointBindingEntryStruct::Type newEntry;
520 : newEntry.nodeID = mRefreshingNodeId;
521 : newEntry.endpointID = endpointBinding.endpointID;
522 : newEntry.binding = endpointBinding.binding;
523 : if (GenerateAndAssignAUniqueListID(newEntry.listID) != CHIP_NO_ERROR)
524 : {
525 : // Unable to generate a unique List ID; skip this entry.
526 : continue;
527 : }
528 : newEntry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
529 : mEndpointBindingEntries.push_back(newEntry);
530 : }
531 : }
532 :
533 : // Remove entries not in endpointBindings, but only if they are Committed or DeletePending
534 : mEndpointBindingEntries.erase(
535 : std::remove_if(
536 : mEndpointBindingEntries.begin(), mEndpointBindingEntries.end(),
537 : [&](const auto & entry) {
538 : if (entry.nodeID != mRefreshingNodeId)
539 : {
540 : return false;
541 : }
542 : if (entry.statusEntry.state != Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted &&
543 : entry.statusEntry.state !=
544 : Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending)
545 : {
546 : return false;
547 : }
548 : return std::none_of(endpointBindings.begin(), endpointBindings.end(), [&](const auto & eb) {
549 : return entry.endpointID == eb.endpointID && BindingMatches(entry.binding, eb.binding);
550 : });
551 : }),
552 : mEndpointBindingEntries.end());
553 : }
554 :
555 : // Move to the next endpoint
556 : mRefreshingEndpointIndex++;
557 :
558 : // Continue the state machine to let the kRefreshingBindings branch process mEndpointBindingList.
559 : if (ContinueRefresh() != CHIP_NO_ERROR)
560 : {
561 : // Ignore errors in continuation from within the callback.
562 : }
563 : }));
564 :
565 : // Return here - the callback will call ContinueRefresh() again
566 1 : return CHIP_NO_ERROR;
567 : }
568 :
569 1 : for (auto it = mEndpointBindingEntries.begin(); it != mEndpointBindingEntries.end();)
570 : {
571 0 : if (it->nodeID == mRefreshingNodeId)
572 : {
573 0 : if (it->statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kPending ||
574 0 : it->statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted)
575 : {
576 0 : mRefreshingBindingEntries.push_back(*it);
577 : }
578 0 : else if (it->statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitFailed)
579 : {
580 0 : CHIP_ERROR failureCode(it->statusEntry.failureCode);
581 :
582 0 : if (failureCode == CHIP_IM_GLOBAL_STATUS(ConstraintError) ||
583 0 : failureCode == CHIP_IM_GLOBAL_STATUS(ResourceExhausted))
584 : {
585 : // remove entry from the list
586 0 : it = mEndpointBindingEntries.erase(it);
587 0 : continue;
588 : }
589 :
590 0 : mRefreshingBindingEntries.push_back(*it);
591 : }
592 : }
593 :
594 0 : ++it;
595 : }
596 :
597 3 : ReturnErrorOnFailure(mDelegate->SyncNode(mRefreshingNodeId, mRefreshingBindingEntries, [this]() {
598 : for (auto & entry : mEndpointBindingEntries)
599 : {
600 : if (entry.nodeID == mRefreshingNodeId &&
601 : (entry.statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kPending ||
602 : entry.statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitFailed))
603 : {
604 : for (const auto & bindingEntry : mRefreshingBindingEntries)
605 : {
606 : if (entry.endpointID == bindingEntry.endpointID && BindingMatches(entry.binding, bindingEntry.binding))
607 : {
608 : entry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
609 : break;
610 : }
611 : }
612 : }
613 : }
614 :
615 : // Remove all DeletePending entries for mRefreshingNodeId
616 : mEndpointBindingEntries.erase(std::remove_if(mEndpointBindingEntries.begin(), mEndpointBindingEntries.end(),
617 : [this](const auto & entry) {
618 : return entry.nodeID == mRefreshingNodeId &&
619 : entry.statusEntry.state ==
620 : Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending;
621 : }),
622 : mEndpointBindingEntries.end());
623 :
624 : // After syncing bindings, move to fetching group key sets
625 : mRefreshState = kFetchingGroupKeySetList;
626 : if (ContinueRefresh() != CHIP_NO_ERROR)
627 : {
628 : // Ignore errors in continuation from within the callback.
629 : }
630 : }));
631 : }
632 1 : break;
633 1 : case kFetchingGroupKeySetList: {
634 4 : ReturnErrorOnFailure(
635 : mDelegate->FetchGroupKeySetList(mRefreshingNodeId, [this](CHIP_ERROR err, const std::vector<uint16_t> & groupKeySets) {
636 : if (err == CHIP_NO_ERROR)
637 : {
638 : // Store the fetched group key sets for processing in the next state.
639 : mRefreshingGroupKeySetIDs = groupKeySets;
640 :
641 : // Advance the state machine to process the group key sets.
642 : mRefreshState = kFetchingGroupKeySets;
643 : mRefreshingGroupKeySetIndex = 0;
644 : }
645 : else
646 : {
647 : // Leave node as pending but tear down the refresh state.
648 : mRefreshingNodeId = kUndefinedNodeId;
649 : mRefreshState = kIdle;
650 : return;
651 : }
652 :
653 : // Continue the state machine to let the kFetchingGroupKeySets branch process mRefreshingGroupKeySetIDs.
654 : if (ContinueRefresh() != CHIP_NO_ERROR)
655 : {
656 : // Ignore errors in continuation from within the callback.
657 : }
658 : }));
659 : }
660 1 : break;
661 2 : case kFetchingGroupKeySets: {
662 : // Request each Group Key Set from the device and transition to kRefreshingGroupKeySets once all indices are read.
663 2 : if (mRefreshingGroupKeySetIndex < mRefreshingGroupKeySetIDs.size())
664 : {
665 1 : const uint16_t groupKeySetID = mRefreshingGroupKeySetIDs[mRefreshingGroupKeySetIndex];
666 :
667 2 : return mDelegate->FetchGroupKeySet(
668 : mRefreshingNodeId, groupKeySetID,
669 1 : [this](CHIP_ERROR err,
670 7 : const Clusters::JointFabricDatastore::Structs::DatastoreGroupKeySetStruct::Type & groupKeySet) {
671 2 : if (err == CHIP_NO_ERROR)
672 : {
673 1 : auto it = std::find_if(
674 : mGroupKeySetList.begin(), mGroupKeySetList.end(),
675 0 : [&groupKeySet](
676 0 : const Clusters::JointFabricDatastore::Structs::DatastoreGroupKeySetStruct::Type & entry) {
677 0 : return entry.groupKeySetID == groupKeySet.groupKeySetID;
678 : });
679 :
680 1 : if (it == mGroupKeySetList.end())
681 : {
682 1 : Clusters::JointFabricDatastore::Structs::DatastoreGroupKeySetStruct::Type copiedKeySet;
683 1 : LogErrorOnFailure(CopyGroupKeySetWithOwnedSpans(groupKeySet, copiedKeySet));
684 1 : mGroupKeySetList.push_back(copiedKeySet);
685 : }
686 : else
687 : {
688 : // Update existing entry
689 0 : LogErrorOnFailure(CopyGroupKeySetWithOwnedSpans(groupKeySet, *it));
690 : }
691 :
692 1 : ++mRefreshingGroupKeySetIndex;
693 : }
694 : else
695 : {
696 : // Leave node as pending but tear down the refresh state.
697 0 : mRefreshingNodeId = kUndefinedNodeId;
698 0 : mRefreshState = kIdle;
699 0 : return;
700 : }
701 :
702 : // Continue fetching key sets until complete, then process mGroupKeySetList in kRefreshingGroupKeySets.
703 2 : if (ContinueRefresh() != CHIP_NO_ERROR)
704 : {
705 : // Ignore errors in continuation from within the callback.
706 : }
707 1 : });
708 : }
709 :
710 : // No group key sets to fetch; advance the state machine to process group key sets (which will be empty) and sync to
711 : // nodes.
712 1 : mRefreshState = kRefreshingGroupKeySets;
713 1 : return ContinueRefresh();
714 : }
715 : break;
716 1 : case kRefreshingGroupKeySets: {
717 : // 4. Ensure per-node key-set entries for each GroupKeySet are synced to devices.
718 2 : for (auto gksIt = mGroupKeySetList.begin(); gksIt != mGroupKeySetList.end(); ++gksIt)
719 : {
720 1 : const uint16_t groupKeySetId = gksIt->groupKeySetID;
721 :
722 1 : for (auto nkIt = mNodeKeySetEntries.begin(); nkIt != mNodeKeySetEntries.end();)
723 : {
724 0 : if (nkIt->groupKeySetID != groupKeySetId)
725 : {
726 0 : ++nkIt;
727 0 : continue;
728 : }
729 :
730 : // nkIt references the current groupKeySetId
731 0 : if (nkIt->statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kPending)
732 : {
733 : // Make a copy of the group key set to send to the node.
734 0 : const NodeId entryNodeId = nkIt->nodeID;
735 0 : auto groupKeySet = *gksIt;
736 0 : ReturnErrorOnFailure(mDelegate->SyncNode(nkIt->nodeID, groupKeySet, [this, entryNodeId, groupKeySetId]() {
737 : detail::MarkEntryCommittedIfFound(mNodeKeySetEntries, [&](const auto & e) {
738 : return e.nodeID == entryNodeId && e.groupKeySetID == groupKeySetId;
739 : });
740 : }));
741 0 : ++nkIt;
742 : }
743 0 : else if (nkIt->statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending)
744 : {
745 : // zero-initialized struct to indicate deletion for the SyncNode call
746 0 : Clusters::JointFabricDatastore::Structs::DatastoreNodeKeySetEntryStruct::Type nullEntry{ 0 };
747 :
748 0 : auto nodeIdToErase = nkIt->nodeID;
749 0 : auto groupKeySetIdToErase = nkIt->groupKeySetID;
750 0 : ReturnErrorOnFailure(
751 : mDelegate->SyncNode(nkIt->nodeID, nullEntry, [this, nodeIdToErase, groupKeySetIdToErase]() {
752 : mNodeKeySetEntries.erase(std::remove_if(mNodeKeySetEntries.begin(), mNodeKeySetEntries.end(),
753 : [&](const auto & entry) {
754 : return entry.nodeID == nodeIdToErase &&
755 : entry.groupKeySetID == groupKeySetIdToErase;
756 : }),
757 : mNodeKeySetEntries.end());
758 : }));
759 : }
760 0 : else if (nkIt->statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitFailed)
761 : {
762 0 : CHIP_ERROR failureCode(nkIt->statusEntry.failureCode);
763 :
764 0 : if (failureCode == CHIP_IM_GLOBAL_STATUS(ConstraintError) ||
765 0 : failureCode == CHIP_IM_GLOBAL_STATUS(ResourceExhausted))
766 : {
767 : // remove entry from the list
768 0 : nkIt = mNodeKeySetEntries.erase(nkIt);
769 : }
770 : else
771 : {
772 : // Retry the failed commit by attempting to SyncNode again.
773 0 : const NodeId entryNodeId = nkIt->nodeID;
774 0 : auto groupKeySet = *gksIt;
775 0 : ReturnErrorOnFailure(mDelegate->SyncNode(nkIt->nodeID, groupKeySet, [this, entryNodeId, groupKeySetId]() {
776 : detail::MarkEntryCommittedIfFound(mNodeKeySetEntries, [&](const auto & e) {
777 : return e.nodeID == entryNodeId && e.groupKeySetID == groupKeySetId;
778 : });
779 : }));
780 0 : ++nkIt;
781 : }
782 : }
783 : else
784 : {
785 0 : ++nkIt;
786 : }
787 : }
788 : }
789 :
790 : // Request ACL List from the device and transition to kRefreshingACLs.
791 4 : ReturnErrorOnFailure(mDelegate->FetchACLList(
792 : mRefreshingNodeId,
793 : [this](CHIP_ERROR err,
794 : const std::vector<Clusters::JointFabricDatastore::Structs::DatastoreACLEntryStruct::Type> & acls) {
795 : if (err == CHIP_NO_ERROR)
796 : {
797 : // Convert acls to mACLEntries
798 : for (const auto & acl : acls)
799 : {
800 : auto it = std::find_if(mACLEntries.begin(), mACLEntries.end(),
801 : [this, &acl](const datastore::ACLEntryStruct & entry) {
802 : return entry.nodeID == mRefreshingNodeId && entry.listID == acl.listID;
803 : });
804 :
805 : if (it == mACLEntries.end())
806 : {
807 : datastore::ACLEntryStruct newEntry;
808 : newEntry.nodeID = mRefreshingNodeId;
809 : newEntry.listID = acl.listID;
810 : newEntry.ACLEntry.authMode = acl.ACLEntry.authMode;
811 : newEntry.ACLEntry.privilege = acl.ACLEntry.privilege;
812 :
813 : if (!acl.ACLEntry.subjects.IsNull())
814 : {
815 : for (size_t subjectsIndex = 0; subjectsIndex < acl.ACLEntry.subjects.Value().size();
816 : ++subjectsIndex)
817 : {
818 : newEntry.ACLEntry.subjects.push_back(acl.ACLEntry.subjects.Value()[subjectsIndex]);
819 : }
820 : }
821 :
822 : if (!acl.ACLEntry.targets.IsNull())
823 : {
824 : for (size_t targetsIndex = 0; targetsIndex < acl.ACLEntry.targets.Value().size(); ++targetsIndex)
825 : {
826 : newEntry.ACLEntry.targets.push_back(acl.ACLEntry.targets.Value()[targetsIndex]);
827 : }
828 : }
829 :
830 : newEntry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
831 : mACLEntries.push_back(newEntry);
832 : }
833 : }
834 :
835 : // Remove entries not in acls, but only if they are Committed or DeletePending
836 : mACLEntries.erase(std::remove_if(mACLEntries.begin(), mACLEntries.end(),
837 : [&](const auto & entry) {
838 : if (entry.nodeID != mRefreshingNodeId)
839 : {
840 : return false;
841 : }
842 : if (entry.statusEntry.state !=
843 : Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted &&
844 : entry.statusEntry.state !=
845 : Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending)
846 : {
847 : return false;
848 : }
849 : return std::none_of(acls.begin(), acls.end(), [&](const auto & acl) {
850 : return entry.listID == acl.listID;
851 : });
852 : }),
853 : mACLEntries.end());
854 :
855 : // Advance the state machine to process the ACLs.
856 : mRefreshState = kRefreshingACLs;
857 : }
858 : else
859 : {
860 : // Leave node as pending but tear down the refresh state.
861 : mRefreshingNodeId = kUndefinedNodeId;
862 : mRefreshState = kIdle;
863 : return;
864 : }
865 :
866 : // Continue the state machine to let the kRefreshingACLs branch process mACLList.
867 : if (ContinueRefresh() != CHIP_NO_ERROR)
868 : {
869 : // Ignore errors in continuation from within the callback.
870 : }
871 : }));
872 : }
873 1 : break;
874 1 : case kRefreshingACLs: {
875 : // 5.
876 1 : for (auto it = mACLEntries.begin(); it != mACLEntries.end();)
877 : {
878 0 : if (it->nodeID == mRefreshingNodeId)
879 : {
880 0 : if (it->statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kPending ||
881 0 : it->statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted)
882 : {
883 : {
884 : // Prepare an encoded ACL entry to send to the node.
885 0 : Clusters::JointFabricDatastore::Structs::DatastoreACLEntryStruct::Type entryToSync;
886 0 : entryToSync.nodeID = it->nodeID;
887 0 : entryToSync.listID = it->listID;
888 0 : entryToSync.ACLEntry.authMode = it->ACLEntry.authMode;
889 0 : entryToSync.ACLEntry.privilege = it->ACLEntry.privilege;
890 0 : entryToSync.ACLEntry.subjects =
891 0 : DataModel::List<const uint64_t>(it->ACLEntry.subjects.data(), it->ACLEntry.subjects.size());
892 0 : entryToSync.ACLEntry.targets = DataModel::List<
893 : const Clusters::JointFabricDatastore::Structs::DatastoreAccessControlTargetStruct::Type>(
894 0 : it->ACLEntry.targets.data(), it->ACLEntry.targets.size());
895 0 : entryToSync.statusEntry = it->statusEntry;
896 :
897 0 : mRefreshingACLEntries.push_back(entryToSync);
898 : }
899 : }
900 0 : else if (it->statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitFailed)
901 : {
902 0 : CHIP_ERROR failureCode(it->statusEntry.failureCode);
903 :
904 0 : if (failureCode == CHIP_IM_GLOBAL_STATUS(ConstraintError) ||
905 0 : failureCode == CHIP_IM_GLOBAL_STATUS(ResourceExhausted))
906 : {
907 : // remove entry from the list
908 0 : it = mACLEntries.erase(it);
909 0 : continue;
910 : }
911 :
912 : // Prepare an encoded ACL entry to retry the failed commit.
913 0 : Clusters::JointFabricDatastore::Structs::DatastoreACLEntryStruct::Type entryToSync;
914 0 : entryToSync.nodeID = it->nodeID;
915 0 : entryToSync.listID = it->listID;
916 0 : entryToSync.ACLEntry.authMode = it->ACLEntry.authMode;
917 0 : entryToSync.ACLEntry.privilege = it->ACLEntry.privilege;
918 0 : entryToSync.ACLEntry.subjects =
919 0 : DataModel::List<const uint64_t>(it->ACLEntry.subjects.data(), it->ACLEntry.subjects.size());
920 0 : entryToSync.ACLEntry.targets =
921 0 : DataModel::List<const Clusters::JointFabricDatastore::Structs::DatastoreAccessControlTargetStruct::Type>(
922 0 : it->ACLEntry.targets.data(), it->ACLEntry.targets.size());
923 0 : entryToSync.statusEntry = it->statusEntry;
924 :
925 0 : mRefreshingACLEntries.push_back(entryToSync);
926 : }
927 : }
928 :
929 0 : ++it;
930 : }
931 :
932 2 : ReturnErrorOnFailure(mDelegate->SyncNode(mRefreshingNodeId, mRefreshingACLEntries, [this]() {
933 : for (auto & entry : mACLEntries)
934 : {
935 : if (entry.nodeID == mRefreshingNodeId &&
936 : (entry.statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kPending ||
937 : entry.statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitFailed))
938 : {
939 : for (const auto & aclEntry : mRefreshingACLEntries)
940 : {
941 : if (entry.listID == aclEntry.listID)
942 : {
943 : entry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
944 : break;
945 : }
946 : }
947 : }
948 : }
949 :
950 : // Remove all DeletePending entries for mRefreshingNodeId
951 : mACLEntries.erase(std::remove_if(mACLEntries.begin(), mACLEntries.end(),
952 : [this](const auto & entry) {
953 : return entry.nodeID == mRefreshingNodeId &&
954 : entry.statusEntry.state ==
955 : Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending;
956 : }),
957 : mACLEntries.end());
958 : }));
959 :
960 : // 6.
961 1 : ReturnErrorOnFailure(SetNode(mRefreshingNodeId, Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted));
962 :
963 1 : ChipLogDetail(AppServer, "Finished refreshing node (ID: 0x" ChipLogFormatX64 "). Node is now marked as Committed.",
964 : ChipLogValueX64(mRefreshingNodeId));
965 :
966 1 : for (Listener * listener = mListeners; listener != nullptr; listener = listener->mNext)
967 : {
968 0 : listener->MarkNodeListChanged();
969 : }
970 :
971 1 : mRefreshingNodeId = kUndefinedNodeId;
972 1 : mRefreshState = kIdle;
973 : }
974 1 : break;
975 : }
976 :
977 5 : return CHIP_NO_ERROR;
978 : }
979 :
980 3 : CHIP_ERROR JointFabricDatastore::SetNode(NodeId nodeId, Clusters::JointFabricDatastore::DatastoreStateEnum state)
981 : {
982 3 : size_t index = 0;
983 3 : ReturnErrorOnFailure(IsNodeIDInDatastore(nodeId, index));
984 2 : mNodeInformationEntries[index].commissioningStatusEntry.state = state;
985 2 : return CHIP_NO_ERROR;
986 : }
987 :
988 3 : CHIP_ERROR JointFabricDatastore::IsNodeIDInDatastore(NodeId nodeId, size_t & index)
989 : {
990 3 : for (auto & entry : mNodeInformationEntries)
991 : {
992 2 : if (entry.nodeID == nodeId)
993 : {
994 2 : index = static_cast<size_t>(&entry - &mNodeInformationEntries[0]);
995 2 : return CHIP_NO_ERROR;
996 : }
997 : }
998 :
999 1 : return CHIP_ERROR_NOT_FOUND;
1000 : }
1001 :
1002 : CHIP_ERROR
1003 2 : JointFabricDatastore::AddGroupKeySetEntry(
1004 : const Clusters::JointFabricDatastore::Structs::DatastoreGroupKeySetStruct::Type & groupKeySet)
1005 : {
1006 2 : VerifyOrReturnError(IsGroupKeySetEntryPresent(groupKeySet.groupKeySetID) == false, CHIP_IM_GLOBAL_STATUS(ConstraintError));
1007 2 : VerifyOrReturnError(mGroupKeySetList.size() < kMaxGroupKeySet, CHIP_ERROR_NO_MEMORY);
1008 :
1009 2 : Clusters::JointFabricDatastore::Structs::DatastoreGroupKeySetStruct::Type copiedKeySet;
1010 2 : ReturnErrorOnFailure(CopyGroupKeySetWithOwnedSpans(groupKeySet, copiedKeySet));
1011 :
1012 2 : mGroupKeySetList.push_back(copiedKeySet);
1013 :
1014 2 : return CHIP_NO_ERROR;
1015 : }
1016 :
1017 2 : bool JointFabricDatastore::IsGroupKeySetEntryPresent(uint16_t groupKeySetId)
1018 : {
1019 2 : for (auto & entry : mGroupKeySetList)
1020 : {
1021 0 : if (entry.groupKeySetID == groupKeySetId)
1022 : {
1023 0 : return true;
1024 : }
1025 : }
1026 :
1027 2 : return false;
1028 : }
1029 :
1030 0 : CHIP_ERROR JointFabricDatastore::RemoveGroupKeySetEntry(uint16_t groupKeySetId)
1031 : {
1032 0 : VerifyOrReturnValue(groupKeySetId != 0, CHIP_IM_GLOBAL_STATUS(ConstraintError));
1033 :
1034 0 : for (auto it = mGroupKeySetList.begin(); it != mGroupKeySetList.end(); ++it)
1035 : {
1036 0 : if (it->groupKeySetID == groupKeySetId)
1037 : {
1038 0 : RemoveGroupKeySetStorage(groupKeySetId);
1039 0 : mGroupKeySetList.erase(it);
1040 0 : return CHIP_NO_ERROR;
1041 : }
1042 : }
1043 :
1044 0 : return CHIP_IM_GLOBAL_STATUS(NotFound);
1045 : }
1046 :
1047 : CHIP_ERROR
1048 0 : JointFabricDatastore::UpdateGroupKeySetEntry(
1049 : Clusters::JointFabricDatastore::Structs::DatastoreGroupKeySetStruct::Type & groupKeySet)
1050 : {
1051 0 : for (auto & entry : mGroupKeySetList)
1052 : {
1053 0 : if (entry.groupKeySetID == groupKeySet.groupKeySetID)
1054 : {
1055 0 : LogErrorOnFailure(UpdateNodeKeySetList(groupKeySet));
1056 :
1057 0 : VerifyOrReturnValue(groupKeySet.groupKeySecurityPolicy <
1058 : Clusters::JointFabricDatastore::DatastoreGroupKeySecurityPolicyEnum::kUnknownEnumValue,
1059 : CHIP_IM_GLOBAL_STATUS(ConstraintError));
1060 :
1061 0 : ReturnErrorOnFailure(CopyGroupKeySetWithOwnedSpans(groupKeySet, entry));
1062 :
1063 0 : return CHIP_NO_ERROR;
1064 : }
1065 : }
1066 :
1067 0 : return CHIP_ERROR_NOT_FOUND;
1068 : }
1069 :
1070 : CHIP_ERROR
1071 2 : JointFabricDatastore::AddAdmin(
1072 : const Clusters::JointFabricDatastore::Structs::DatastoreAdministratorInformationEntryStruct::Type & adminId)
1073 : {
1074 2 : VerifyOrReturnError(IsAdminEntryPresent(adminId.nodeID) == false, CHIP_IM_GLOBAL_STATUS(ConstraintError));
1075 2 : VerifyOrReturnError(mAdminEntries.size() < kMaxAdminNodes, CHIP_ERROR_NO_MEMORY);
1076 :
1077 2 : Clusters::JointFabricDatastore::Structs::DatastoreAdministratorInformationEntryStruct::Type entryToStore;
1078 2 : entryToStore.nodeID = adminId.nodeID;
1079 2 : entryToStore.vendorID = adminId.vendorID;
1080 :
1081 2 : ReturnErrorOnFailure(SetAdminEntryWithOwnedStorage(adminId.nodeID, adminId.friendlyName, adminId.icac, entryToStore));
1082 :
1083 2 : mAdminEntries.push_back(entryToStore);
1084 :
1085 2 : return CHIP_NO_ERROR;
1086 : }
1087 :
1088 2 : bool JointFabricDatastore::IsAdminEntryPresent(NodeId nodeId)
1089 : {
1090 2 : for (auto & entry : mAdminEntries)
1091 : {
1092 0 : if (entry.nodeID == nodeId)
1093 : {
1094 0 : return true;
1095 : }
1096 : }
1097 :
1098 2 : return false;
1099 : }
1100 :
1101 1 : CHIP_ERROR JointFabricDatastore::UpdateAdmin(NodeId nodeId, Optional<CharSpan> friendlyName, Optional<ByteSpan> icac)
1102 : {
1103 1 : for (auto & entry : mAdminEntries)
1104 : {
1105 1 : if (entry.nodeID == nodeId)
1106 : {
1107 1 : auto & storage = mAdminEntryStorage[nodeId];
1108 1 : if (friendlyName.HasValue())
1109 : {
1110 1 : const auto & name = friendlyName.Value();
1111 1 : storage.friendlyName.assign(name.data(), name.data() + name.size());
1112 1 : entry.friendlyName = CharSpan(storage.friendlyName.data(), storage.friendlyName.size());
1113 : }
1114 1 : if (icac.HasValue())
1115 : {
1116 1 : const auto & icacVal = icac.Value();
1117 1 : ReturnErrorOnFailure(storage.icac.SetLength(icacVal.size()));
1118 1 : memcpy(storage.icac.Bytes(), icacVal.data(), icacVal.size());
1119 1 : entry.icac = storage.icac.Span();
1120 : }
1121 1 : return CHIP_NO_ERROR;
1122 : }
1123 : }
1124 :
1125 0 : return CHIP_ERROR_NOT_FOUND;
1126 : }
1127 :
1128 0 : CHIP_ERROR JointFabricDatastore::RemoveAdmin(NodeId nodeId)
1129 : {
1130 0 : for (auto it = mAdminEntries.begin(); it != mAdminEntries.end(); ++it)
1131 : {
1132 0 : if (it->nodeID == nodeId)
1133 : {
1134 0 : mAdminEntries.erase(it);
1135 0 : RemoveAdminEntryStorage(nodeId);
1136 0 : return CHIP_NO_ERROR;
1137 : }
1138 : }
1139 :
1140 0 : return CHIP_ERROR_NOT_FOUND;
1141 : }
1142 :
1143 : CHIP_ERROR
1144 0 : JointFabricDatastore::UpdateNodeKeySetList(Clusters::JointFabricDatastore::Structs::DatastoreGroupKeySetStruct::Type & groupKeySet)
1145 : {
1146 0 : bool entryUpdated = false;
1147 :
1148 0 : for (size_t i = 0; i < mNodeKeySetEntries.size(); ++i)
1149 : {
1150 0 : auto & entry = mNodeKeySetEntries[i];
1151 0 : if (entry.groupKeySetID == groupKeySet.groupKeySetID)
1152 : {
1153 0 : if (groupKeySet.groupKeySecurityPolicy <
1154 : Clusters::JointFabricDatastore::DatastoreGroupKeySecurityPolicyEnum::kUnknownEnumValue)
1155 : {
1156 :
1157 0 : const NodeId entryNodeId = entry.nodeID;
1158 0 : const uint16_t entryGroupKeySetID = groupKeySet.groupKeySetID;
1159 0 : LogErrorOnFailure(mDelegate->SyncNode(entry.nodeID, groupKeySet, [this, entryNodeId, entryGroupKeySetID]() {
1160 : detail::MarkEntryCommittedIfFound(mNodeKeySetEntries, [&](const auto & e) {
1161 : return e.nodeID == entryNodeId && e.groupKeySetID == entryGroupKeySetID;
1162 : });
1163 : }));
1164 :
1165 0 : if (entryUpdated == false)
1166 : {
1167 0 : entryUpdated = true;
1168 : }
1169 : }
1170 : else
1171 : {
1172 0 : entry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitFailed;
1173 0 : return CHIP_IM_GLOBAL_STATUS(ConstraintError);
1174 : }
1175 : }
1176 : }
1177 :
1178 0 : return entryUpdated ? CHIP_NO_ERROR : CHIP_ERROR_NOT_FOUND;
1179 : }
1180 :
1181 0 : CHIP_ERROR JointFabricDatastore::RemoveKeySet(uint16_t groupKeySetId)
1182 : {
1183 0 : for (auto it = mNodeKeySetEntries.begin(); it != mNodeKeySetEntries.end(); ++it)
1184 : {
1185 0 : if (it->groupKeySetID == groupKeySetId)
1186 : {
1187 0 : if (it->statusEntry.state != Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending)
1188 : {
1189 0 : return CHIP_IM_GLOBAL_STATUS(ConstraintError); // Cannot remove a key set that is not pending
1190 : }
1191 :
1192 0 : ReturnErrorOnFailure(RemoveGroupKeySetEntry(groupKeySetId));
1193 :
1194 0 : return CHIP_NO_ERROR;
1195 : }
1196 : }
1197 :
1198 0 : return CHIP_IM_GLOBAL_STATUS(NotFound);
1199 : }
1200 :
1201 5 : CHIP_ERROR JointFabricDatastore::AddGroup(const Clusters::JointFabricDatastore::Commands::AddGroup::DecodableType & commandData)
1202 : {
1203 5 : size_t index = 0;
1204 : // Check if the group ID already exists in the datastore
1205 10 : VerifyOrReturnError(IsGroupIDInDatastore(commandData.groupID, index) == CHIP_ERROR_NOT_FOUND,
1206 : CHIP_IM_GLOBAL_STATUS(ConstraintError));
1207 :
1208 5 : if (commandData.groupCAT.ValueOr(0) == kAdminCATIdentifier || commandData.groupCAT.ValueOr(0) == kAnchorCATIdentifier)
1209 : {
1210 : // If the group is an AdminCAT or AnchorCAT, we cannot add it
1211 0 : return CHIP_IM_GLOBAL_STATUS(ConstraintError);
1212 : }
1213 :
1214 5 : Clusters::JointFabricDatastore::Structs::DatastoreGroupInformationEntryStruct::Type groupEntry;
1215 5 : groupEntry.groupID = commandData.groupID;
1216 5 : groupEntry.groupKeySetID = commandData.groupKeySetID;
1217 5 : groupEntry.groupCAT = commandData.groupCAT;
1218 5 : groupEntry.groupCATVersion = commandData.groupCATVersion;
1219 5 : groupEntry.groupPermission = commandData.groupPermission;
1220 5 : SetGroupInformationFriendlyNameWithOwnedStorage(commandData.groupID, commandData.friendlyName, groupEntry);
1221 :
1222 : // Add the group entry to the datastore
1223 5 : mGroupInformationEntries.push_back(groupEntry);
1224 :
1225 5 : return CHIP_NO_ERROR;
1226 : }
1227 :
1228 : CHIP_ERROR
1229 0 : JointFabricDatastore::ForceAddGroup(const Clusters::JointFabricDatastore::Commands::AddGroup::DecodableType & commandData)
1230 : {
1231 0 : size_t index = 0;
1232 : // Check if the group ID already exists in the datastore
1233 0 : VerifyOrReturnError(IsGroupIDInDatastore(commandData.groupID, index) == CHIP_ERROR_NOT_FOUND,
1234 : CHIP_IM_GLOBAL_STATUS(ConstraintError));
1235 :
1236 0 : Clusters::JointFabricDatastore::Structs::DatastoreGroupInformationEntryStruct::Type groupEntry;
1237 0 : groupEntry.groupID = commandData.groupID;
1238 0 : groupEntry.groupKeySetID = commandData.groupKeySetID;
1239 0 : groupEntry.groupCAT = commandData.groupCAT;
1240 0 : groupEntry.groupCATVersion = commandData.groupCATVersion;
1241 0 : groupEntry.groupPermission = commandData.groupPermission;
1242 0 : SetGroupInformationFriendlyNameWithOwnedStorage(commandData.groupID, commandData.friendlyName, groupEntry);
1243 :
1244 : // Add the group entry to the datastore
1245 0 : mGroupInformationEntries.push_back(groupEntry);
1246 :
1247 0 : return CHIP_NO_ERROR;
1248 : }
1249 :
1250 : CHIP_ERROR
1251 3 : JointFabricDatastore::UpdateGroup(const Clusters::JointFabricDatastore::Commands::UpdateGroup::DecodableType & commandData)
1252 : {
1253 3 : VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
1254 :
1255 3 : size_t index = 0;
1256 : // Check if the group ID exists in the datastore
1257 6 : VerifyOrReturnError(IsGroupIDInDatastore(commandData.groupID, index) == CHIP_NO_ERROR, CHIP_IM_GLOBAL_STATUS(ConstraintError));
1258 :
1259 6 : if (mGroupInformationEntries[index].groupCAT.ValueOr(0) == kAdminCATIdentifier ||
1260 6 : mGroupInformationEntries[index].groupCAT.ValueOr(0) == kAnchorCATIdentifier)
1261 : {
1262 : // If the group is an AdminCAT or AnchorCAT, we cannot update it
1263 0 : return CHIP_IM_GLOBAL_STATUS(ConstraintError);
1264 : }
1265 :
1266 : // Update the group entry with the new data
1267 3 : if (commandData.friendlyName.IsNull() == false)
1268 : {
1269 0 : if (mGroupInformationEntries[index].friendlyName.data_equal(commandData.friendlyName.Value()) == false)
1270 : {
1271 : // Friendly name changed. For every endpoint that references this group, mark the endpoint's
1272 : // GroupIDList entry as pending and attempt to push the change to the node. If the push
1273 : // fails, leave the entry as pending so a subsequent Refresh can apply it.
1274 0 : const GroupId updatedGroupId = commandData.groupID;
1275 0 : for (size_t i = 0; i < mEndpointGroupIDEntries.size(); ++i)
1276 : {
1277 0 : auto & epGroupEntry = mEndpointGroupIDEntries[i];
1278 0 : if (epGroupEntry.groupID == updatedGroupId)
1279 : {
1280 0 : epGroupEntry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kPending;
1281 :
1282 : // Make a copy to send to the node. Do not fail the entire UpdateGroup if SyncNode
1283 : // returns an error; leave the entry pending for a later refresh per spec.
1284 0 : auto entryToSync = epGroupEntry;
1285 :
1286 0 : const NodeId entryNodeId = epGroupEntry.nodeID;
1287 0 : const EndpointId entryEndpointId = epGroupEntry.endpointID;
1288 0 : CHIP_ERROR syncErr = mDelegate->SyncNode(
1289 0 : epGroupEntry.nodeID, entryToSync, [this, entryNodeId, entryEndpointId, updatedGroupId]() {
1290 0 : detail::MarkEntryCommittedIfFound(mEndpointGroupIDEntries, [&](const auto & e) {
1291 0 : return e.nodeID == entryNodeId && e.endpointID == entryEndpointId && e.groupID == updatedGroupId;
1292 : });
1293 0 : });
1294 :
1295 0 : if (syncErr != CHIP_NO_ERROR)
1296 : {
1297 0 : ChipLogError(DataManagement,
1298 : "Failed to sync node for group friendly name update, leaving as pending: %" CHIP_ERROR_FORMAT,
1299 : syncErr.Format());
1300 : }
1301 : }
1302 : }
1303 :
1304 : // Update the friendly name in the datastore
1305 0 : SetGroupInformationFriendlyNameWithOwnedStorage(static_cast<GroupId>(mGroupInformationEntries[index].groupID),
1306 0 : commandData.friendlyName.Value(), mGroupInformationEntries[index]);
1307 : }
1308 : }
1309 3 : if (commandData.groupKeySetID.IsNull() == false)
1310 : {
1311 1 : if (mGroupInformationEntries[index].groupKeySetID.IsNull() ||
1312 0 : mGroupInformationEntries[index].groupKeySetID.Value() != commandData.groupKeySetID.Value())
1313 : {
1314 : // If the groupKeySetID is being updated, we need to ensure that the new key set exists
1315 1 : ReturnErrorOnFailure(AddNodeKeySetEntry(commandData.groupID, commandData.groupKeySetID.Value()));
1316 1 : if (!mGroupInformationEntries[index].groupKeySetID.IsNull())
1317 : {
1318 0 : LogErrorOnFailure(RemoveNodeKeySetEntry(
1319 : commandData.groupID, mGroupInformationEntries[index].groupKeySetID.Value())); // Remove the old key set
1320 : }
1321 : }
1322 1 : mGroupInformationEntries[index].groupKeySetID = commandData.groupKeySetID;
1323 : }
1324 :
1325 3 : bool anyGroupCATFieldUpdated = false;
1326 :
1327 3 : if (commandData.groupCAT.IsNull() == false)
1328 : {
1329 1 : if (mGroupInformationEntries[index].groupCAT.IsNull() ||
1330 0 : mGroupInformationEntries[index].groupCAT.Value() != commandData.groupCAT.Value())
1331 : {
1332 1 : anyGroupCATFieldUpdated = true;
1333 : }
1334 : // Update the groupCAT
1335 1 : mGroupInformationEntries[index].groupCAT = commandData.groupCAT;
1336 : }
1337 3 : if (commandData.groupCATVersion.IsNull() == false)
1338 : {
1339 1 : if (mGroupInformationEntries[index].groupCATVersion.IsNull() ||
1340 0 : mGroupInformationEntries[index].groupCATVersion.Value() != commandData.groupCATVersion.Value())
1341 : {
1342 1 : anyGroupCATFieldUpdated = true;
1343 : }
1344 1 : mGroupInformationEntries[index].groupCATVersion = commandData.groupCATVersion;
1345 : }
1346 3 : if (commandData.groupPermission.IsNull() == false &&
1347 0 : commandData.groupPermission.Value() !=
1348 : Clusters::JointFabricDatastore::DatastoreAccessControlEntryPrivilegeEnum::kUnknownEnumValue)
1349 : {
1350 0 : if (mGroupInformationEntries[index].groupPermission != commandData.groupPermission.Value())
1351 : {
1352 0 : anyGroupCATFieldUpdated = true;
1353 : }
1354 : // If the groupPermission is not set to kUnknownEnumValue, update it
1355 0 : mGroupInformationEntries[index].groupPermission = commandData.groupPermission.Value();
1356 : }
1357 :
1358 3 : if (anyGroupCATFieldUpdated)
1359 : {
1360 2 : const GroupId updatedGroupId = commandData.groupID;
1361 :
1362 2 : for (size_t i = 0; i < mACLEntries.size(); ++i)
1363 : {
1364 0 : auto & acl = mACLEntries[i];
1365 :
1366 : // Determine if this ACL entry references the updated group
1367 0 : bool referencesGroup = false;
1368 0 : for (const auto & subject : acl.ACLEntry.subjects)
1369 : {
1370 : // If the target has a group field and it matches the updated group, mark for update.
1371 : // Use IsNull() to match other usages in this file.
1372 0 : if (subject == static_cast<uint64_t>(updatedGroupId))
1373 : {
1374 0 : referencesGroup = true;
1375 0 : break;
1376 : }
1377 : }
1378 :
1379 0 : if (!referencesGroup)
1380 : {
1381 0 : continue;
1382 : }
1383 :
1384 : // Update the ACL entry in the datastore to reflect the new group permission and mark Pending.
1385 0 : acl.ACLEntry.privilege = mGroupInformationEntries[index].groupPermission;
1386 0 : acl.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kPending;
1387 :
1388 : // Prepare an encoded entry to send to the node.
1389 0 : Clusters::JointFabricDatastore::Structs::DatastoreACLEntryStruct::Type entryToEncode;
1390 0 : entryToEncode.nodeID = acl.nodeID;
1391 0 : entryToEncode.listID = acl.listID;
1392 0 : entryToEncode.ACLEntry.authMode = acl.ACLEntry.authMode;
1393 0 : entryToEncode.ACLEntry.privilege = acl.ACLEntry.privilege;
1394 0 : entryToEncode.ACLEntry.subjects =
1395 0 : DataModel::List<const uint64_t>(acl.ACLEntry.subjects.data(), acl.ACLEntry.subjects.size());
1396 0 : entryToEncode.ACLEntry.targets =
1397 0 : DataModel::List<const Clusters::JointFabricDatastore::Structs::DatastoreAccessControlTargetStruct::Type>(
1398 0 : acl.ACLEntry.targets.data(), acl.ACLEntry.targets.size());
1399 0 : entryToEncode.statusEntry = acl.statusEntry;
1400 :
1401 : // Attempt to update the ACL on the node. On success, mark the ACL entry as Committed.
1402 : // Re-resolve by stable key (nodeID + listID) inside the completion; capturing the loop
1403 : // index would mark the wrong/invalid slot if an interleaved Invoke mutated the vector.
1404 0 : const NodeId entryNodeId = acl.nodeID;
1405 0 : const uint16_t entryListId = acl.listID;
1406 0 : ReturnErrorOnFailure(mDelegate->SyncNode(acl.nodeID, entryToEncode, [this, entryNodeId, entryListId]() {
1407 : detail::MarkEntryCommittedIfFound(
1408 : mACLEntries, [&](const auto & e) { return e.nodeID == entryNodeId && e.listID == entryListId; });
1409 : }));
1410 : }
1411 : }
1412 :
1413 3 : return CHIP_NO_ERROR;
1414 : }
1415 :
1416 : CHIP_ERROR
1417 0 : JointFabricDatastore::RemoveGroup(const Clusters::JointFabricDatastore::Commands::RemoveGroup::DecodableType & commandData)
1418 : {
1419 0 : size_t index = 0;
1420 : // Check if the group ID exists in the datastore
1421 0 : VerifyOrReturnError(IsGroupIDInDatastore(commandData.groupID, index) == CHIP_NO_ERROR, CHIP_IM_GLOBAL_STATUS(ConstraintError));
1422 :
1423 : // Remove the group entry from the datastore
1424 0 : auto it = mGroupInformationEntries.begin();
1425 0 : std::advance(it, index);
1426 :
1427 0 : if (it->groupCAT.ValueOr(0) == kAdminCATIdentifier || it->groupCAT.ValueOr(0) == kAnchorCATIdentifier)
1428 : {
1429 : // If the group is an AdminCAT or AnchorCAT, we cannot remove it
1430 0 : return CHIP_IM_GLOBAL_STATUS(ConstraintError);
1431 : }
1432 :
1433 0 : const GroupId removedGroupId = static_cast<GroupId>(it->groupID);
1434 0 : mGroupInformationEntries.erase(it);
1435 0 : RemoveGroupInformationStorage(removedGroupId);
1436 :
1437 0 : return CHIP_NO_ERROR;
1438 : }
1439 :
1440 10 : CHIP_ERROR JointFabricDatastore::IsGroupIDInDatastore(chip::GroupId groupId, size_t & index)
1441 : {
1442 10 : for (auto & entry : mGroupInformationEntries)
1443 : {
1444 5 : if (entry.groupID == groupId)
1445 : {
1446 5 : index = static_cast<size_t>(&entry - &mGroupInformationEntries[0]);
1447 5 : return CHIP_NO_ERROR;
1448 : }
1449 : }
1450 :
1451 5 : return CHIP_ERROR_NOT_FOUND;
1452 : }
1453 :
1454 18 : CHIP_ERROR JointFabricDatastore::IsNodeIdInNodeInformationEntries(NodeId nodeId, size_t & index)
1455 : {
1456 96 : for (auto & entry : mNodeInformationEntries)
1457 : {
1458 96 : if (entry.nodeID == nodeId)
1459 : {
1460 18 : index = static_cast<size_t>(&entry - &mNodeInformationEntries[0]);
1461 18 : return CHIP_NO_ERROR;
1462 : }
1463 : }
1464 :
1465 0 : return CHIP_IM_GLOBAL_STATUS(ConstraintError);
1466 : }
1467 :
1468 1 : CHIP_ERROR JointFabricDatastore::UpdateEndpointForNode(NodeId nodeId, chip::EndpointId endpointId, CharSpan friendlyName)
1469 : {
1470 1 : for (auto & entry : mEndpointEntries)
1471 : {
1472 1 : if (entry.nodeID == nodeId && entry.endpointID == endpointId)
1473 : {
1474 1 : SetEndpointFriendlyNameWithOwnedStorage(nodeId, endpointId, friendlyName, entry);
1475 1 : return CHIP_NO_ERROR;
1476 : }
1477 : }
1478 :
1479 0 : return CHIP_IM_GLOBAL_STATUS(ConstraintError);
1480 : }
1481 :
1482 33 : CHIP_ERROR JointFabricDatastore::IsNodeIdAndEndpointInEndpointInformationEntries(NodeId nodeId, EndpointId endpointId,
1483 : size_t & index)
1484 : {
1485 33 : for (auto & entry : mEndpointEntries)
1486 : {
1487 33 : if (entry.nodeID == nodeId && entry.endpointID == endpointId)
1488 : {
1489 33 : index = static_cast<size_t>(&entry - &mEndpointEntries[0]);
1490 33 : return CHIP_NO_ERROR;
1491 : }
1492 : }
1493 :
1494 0 : return CHIP_IM_GLOBAL_STATUS(ConstraintError);
1495 : }
1496 :
1497 1 : CHIP_ERROR JointFabricDatastore::AddGroupIDToEndpointForNode(NodeId nodeId, chip::EndpointId endpointId, chip::GroupId groupId)
1498 : {
1499 1 : VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
1500 :
1501 1 : size_t index = 0;
1502 1 : ReturnErrorOnFailure(IsNodeIdAndEndpointInEndpointInformationEntries(nodeId, endpointId, index));
1503 :
1504 2 : VerifyOrReturnError(IsGroupIDInDatastore(groupId, index) == CHIP_NO_ERROR, CHIP_IM_GLOBAL_STATUS(ConstraintError));
1505 :
1506 1 : if (mGroupInformationEntries[index].groupKeySetID.IsNull() == false)
1507 : {
1508 1 : uint16_t groupKeySetID = mGroupInformationEntries[index].groupKeySetID.Value();
1509 :
1510 : // make sure mNodeKeySetEntries contains an entry for this keyset and node, else add one and update device
1511 1 : bool nodeKeySetExists = false;
1512 1 : for (auto & entry : mNodeKeySetEntries)
1513 : {
1514 0 : if (entry.nodeID == nodeId && entry.groupKeySetID == groupKeySetID)
1515 : {
1516 0 : nodeKeySetExists = true;
1517 0 : break; // Found the group key set, no need to add it again
1518 : }
1519 : }
1520 :
1521 1 : if (!nodeKeySetExists)
1522 : {
1523 : // Create a new group key set entry if it doesn't exist
1524 1 : Clusters::JointFabricDatastore::Structs::DatastoreNodeKeySetEntryStruct::Type newNodeKeySet;
1525 1 : newNodeKeySet.nodeID = nodeId;
1526 1 : newNodeKeySet.groupKeySetID = groupKeySetID;
1527 1 : newNodeKeySet.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kPending;
1528 :
1529 1 : mNodeKeySetEntries.push_back(newNodeKeySet);
1530 :
1531 3 : ReturnErrorOnFailure(mDelegate->SyncNode(nodeId, newNodeKeySet, [this, nodeId, groupKeySetID]() {
1532 : detail::MarkEntryCommittedIfFound(mNodeKeySetEntries, [&](const auto & entry) {
1533 : return entry.nodeID == nodeId && entry.groupKeySetID == groupKeySetID;
1534 : });
1535 : }));
1536 : }
1537 : }
1538 :
1539 : // Check if the group ID already exists for the endpoint
1540 1 : for (auto & entry : mEndpointGroupIDEntries)
1541 : {
1542 0 : if (entry.nodeID == nodeId && entry.endpointID == endpointId && entry.groupID == groupId)
1543 : {
1544 0 : return CHIP_NO_ERROR;
1545 : }
1546 : }
1547 :
1548 1 : VerifyOrReturnError(mEndpointGroupIDEntries.size() < kMaxGroups, CHIP_ERROR_NO_MEMORY);
1549 :
1550 : // Create a new endpoint group ID entry
1551 1 : Clusters::JointFabricDatastore::Structs::DatastoreEndpointGroupIDEntryStruct::Type newGroupEntry;
1552 1 : newGroupEntry.nodeID = nodeId;
1553 1 : newGroupEntry.endpointID = endpointId;
1554 1 : newGroupEntry.groupID = groupId;
1555 1 : newGroupEntry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kPending;
1556 :
1557 : // Add the new ACL entry to the datastore
1558 1 : mEndpointGroupIDEntries.push_back(newGroupEntry);
1559 :
1560 2 : return mDelegate->SyncNode(nodeId, newGroupEntry, [this, nodeId, endpointId, groupId]() {
1561 1 : detail::MarkEntryCommittedIfFound(mEndpointGroupIDEntries, [&](const auto & entry) {
1562 1 : return entry.nodeID == nodeId && entry.endpointID == endpointId && entry.groupID == groupId;
1563 : });
1564 1 : });
1565 : }
1566 :
1567 1 : CHIP_ERROR JointFabricDatastore::RemoveGroupIDFromEndpointForNode(NodeId nodeId, chip::EndpointId endpointId, chip::GroupId groupId)
1568 : {
1569 1 : VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
1570 :
1571 1 : size_t index = 0;
1572 1 : ReturnErrorOnFailure(IsNodeIdAndEndpointInEndpointInformationEntries(nodeId, endpointId, index));
1573 :
1574 1 : for (auto it = mEndpointGroupIDEntries.begin(); it != mEndpointGroupIDEntries.end(); ++it)
1575 : {
1576 1 : if (it->nodeID == nodeId && it->endpointID == endpointId && it->groupID == groupId)
1577 : {
1578 1 : it->statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending;
1579 1 : const auto erasedNodeId = it->nodeID;
1580 1 : const auto erasedEndpointId = it->endpointID;
1581 1 : const auto erasedGroupId = it->groupID;
1582 2 : ReturnErrorOnFailure(mDelegate->SyncNode(nodeId, *it, [this, erasedNodeId, erasedEndpointId, erasedGroupId]() {
1583 : for (auto eraseIt = mEndpointGroupIDEntries.begin(); eraseIt != mEndpointGroupIDEntries.end(); ++eraseIt)
1584 : {
1585 : if (eraseIt->nodeID == erasedNodeId && eraseIt->endpointID == erasedEndpointId &&
1586 : eraseIt->groupID == erasedGroupId)
1587 : {
1588 : mEndpointGroupIDEntries.erase(eraseIt);
1589 : break;
1590 : }
1591 : }
1592 : }));
1593 :
1594 2 : if (IsGroupIDInDatastore(groupId, index) == CHIP_NO_ERROR)
1595 : {
1596 1 : for (auto it2 = mNodeKeySetEntries.begin(); it2 != mNodeKeySetEntries.end(); ++it2)
1597 : {
1598 2 : if (it2->nodeID == nodeId && mGroupInformationEntries[index].groupKeySetID.IsNull() == false &&
1599 1 : it2->groupKeySetID == mGroupInformationEntries[index].groupKeySetID.Value())
1600 : {
1601 1 : it2->statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending;
1602 1 : const auto erasedKeySetNodeId = it2->nodeID;
1603 1 : const auto erasedKeySetGroupId = it2->groupKeySetID;
1604 2 : ReturnErrorOnFailure(mDelegate->SyncNode(nodeId, *it2, [this, erasedKeySetNodeId, erasedKeySetGroupId]() {
1605 : for (auto eraseIt = mNodeKeySetEntries.begin(); eraseIt != mNodeKeySetEntries.end(); ++eraseIt)
1606 : {
1607 : if (eraseIt->nodeID == erasedKeySetNodeId && eraseIt->groupKeySetID == erasedKeySetGroupId)
1608 : {
1609 : mNodeKeySetEntries.erase(eraseIt);
1610 : break;
1611 : }
1612 : }
1613 : }));
1614 :
1615 1 : break;
1616 : }
1617 : }
1618 : }
1619 :
1620 1 : return CHIP_NO_ERROR;
1621 : }
1622 : }
1623 :
1624 0 : return CHIP_IM_GLOBAL_STATUS(NotFound);
1625 : }
1626 :
1627 : // look-up the highest listId used so far, from Endpoint Binding Entries and ACL Entries
1628 44 : CHIP_ERROR JointFabricDatastore::GenerateAndAssignAUniqueListID(uint16_t & listId)
1629 : {
1630 44 : uint16_t highestListID = 0;
1631 201 : for (auto & entry : mEndpointBindingEntries)
1632 : {
1633 157 : if (entry.listID >= highestListID)
1634 : {
1635 157 : highestListID = entry.listID + 1;
1636 : }
1637 : }
1638 122 : for (auto & entry : mACLEntries)
1639 : {
1640 78 : if (entry.listID >= highestListID)
1641 : {
1642 78 : highestListID = entry.listID + 1;
1643 : }
1644 : }
1645 :
1646 44 : listId = highestListID;
1647 :
1648 44 : return CHIP_NO_ERROR;
1649 : }
1650 :
1651 156 : bool JointFabricDatastore::BindingMatches(
1652 : const Clusters::JointFabricDatastore::Structs::DatastoreBindingTargetStruct::Type & binding1,
1653 : const Clusters::JointFabricDatastore::Structs::DatastoreBindingTargetStruct::Type & binding2)
1654 : {
1655 156 : if (binding1.node.HasValue() && binding2.node.HasValue())
1656 : {
1657 156 : if (binding1.node.Value() != binding2.node.Value())
1658 : {
1659 156 : return false;
1660 : }
1661 : }
1662 0 : else if (binding1.node.HasValue() || binding2.node.HasValue())
1663 : {
1664 0 : return false;
1665 : }
1666 :
1667 0 : if (binding1.group.HasValue() && binding2.group.HasValue())
1668 : {
1669 0 : if (binding1.group.Value() != binding2.group.Value())
1670 : {
1671 0 : return false;
1672 : }
1673 : }
1674 0 : else if (binding1.group.HasValue() || binding2.group.HasValue())
1675 : {
1676 0 : return false;
1677 : }
1678 :
1679 0 : if (binding1.endpoint.HasValue() && binding2.endpoint.HasValue())
1680 : {
1681 0 : if (binding1.endpoint.Value() != binding2.endpoint.Value())
1682 : {
1683 0 : return false;
1684 : }
1685 : }
1686 0 : else if (binding1.endpoint.HasValue() || binding2.endpoint.HasValue())
1687 : {
1688 0 : return false;
1689 : }
1690 :
1691 0 : if (binding1.cluster.HasValue() && binding2.cluster.HasValue())
1692 : {
1693 0 : if (binding1.cluster.Value() != binding2.cluster.Value())
1694 : {
1695 0 : return false;
1696 : }
1697 : }
1698 0 : else if (binding1.cluster.HasValue() || binding2.cluster.HasValue())
1699 : {
1700 0 : return false;
1701 : }
1702 :
1703 0 : return true;
1704 : }
1705 :
1706 : CHIP_ERROR
1707 29 : JointFabricDatastore::AddBindingToEndpointForNode(
1708 : NodeId nodeId, chip::EndpointId endpointId,
1709 : const Clusters::JointFabricDatastore::Structs::DatastoreBindingTargetStruct::Type & binding)
1710 : {
1711 29 : VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
1712 :
1713 29 : size_t index = 0;
1714 29 : ReturnErrorOnFailure(IsNodeIdAndEndpointInEndpointInformationEntries(nodeId, endpointId, index));
1715 :
1716 : // Check if the group ID already exists for the endpoint
1717 185 : for (auto & entry : mEndpointBindingEntries)
1718 : {
1719 156 : if (entry.nodeID == nodeId && entry.endpointID == endpointId)
1720 : {
1721 156 : if (BindingMatches(entry.binding, binding))
1722 : {
1723 0 : return CHIP_NO_ERROR;
1724 : }
1725 : }
1726 : }
1727 :
1728 29 : VerifyOrReturnError(mEndpointBindingEntries.size() < kMaxGroups, CHIP_ERROR_NO_MEMORY);
1729 :
1730 : // Create a new binding entry
1731 29 : Clusters::JointFabricDatastore::Structs::DatastoreEndpointBindingEntryStruct::Type newBindingEntry;
1732 29 : newBindingEntry.nodeID = nodeId;
1733 29 : newBindingEntry.endpointID = endpointId;
1734 29 : newBindingEntry.binding = binding;
1735 29 : ReturnErrorOnFailure(GenerateAndAssignAUniqueListID(newBindingEntry.listID));
1736 29 : newBindingEntry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kPending;
1737 :
1738 : // Add the new binding entry to the datastore
1739 29 : mEndpointBindingEntries.push_back(newBindingEntry);
1740 :
1741 29 : const uint16_t listID = newBindingEntry.listID;
1742 58 : return mDelegate->SyncNode(nodeId, newBindingEntry, [this, nodeId, endpointId, listID]() {
1743 29 : detail::MarkEntryCommittedIfFound(mEndpointBindingEntries, [&](const auto & entry) {
1744 185 : return entry.nodeID == nodeId && entry.endpointID == endpointId && entry.listID == listID;
1745 : });
1746 29 : });
1747 : }
1748 :
1749 : CHIP_ERROR
1750 2 : JointFabricDatastore::RemoveBindingFromEndpointForNode(uint16_t listId, NodeId nodeId, chip::EndpointId endpointId)
1751 : {
1752 2 : VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
1753 :
1754 2 : size_t index = 0;
1755 2 : ReturnErrorOnFailure(IsNodeIdAndEndpointInEndpointInformationEntries(nodeId, endpointId, index));
1756 :
1757 2 : for (auto it = mEndpointBindingEntries.begin(); it != mEndpointBindingEntries.end(); ++it)
1758 : {
1759 2 : if (it->nodeID == nodeId && it->listID == listId && it->endpointID == endpointId)
1760 : {
1761 2 : it->statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending;
1762 : // Re-resolve by stable key inside the async completion instead of capturing the raw
1763 : // iterator (which dangles if an interleaved Add*/Remove* reallocates the vector).
1764 4 : return mDelegate->SyncNode(nodeId, *it, [this, listId, nodeId, endpointId]() {
1765 2 : mEndpointBindingEntries.erase(std::remove_if(mEndpointBindingEntries.begin(), mEndpointBindingEntries.end(),
1766 14 : [&](const auto & entry) {
1767 16 : return entry.nodeID == nodeId && entry.listID == listId &&
1768 16 : entry.endpointID == endpointId;
1769 : }),
1770 2 : mEndpointBindingEntries.end());
1771 2 : });
1772 : }
1773 : }
1774 :
1775 0 : return CHIP_ERROR_NOT_FOUND;
1776 : }
1777 :
1778 0 : bool JointFabricDatastore::ACLTargetMatches(
1779 : const Clusters::JointFabricDatastore::Structs::DatastoreAccessControlTargetStruct::Type & target1,
1780 : const Clusters::JointFabricDatastore::Structs::DatastoreAccessControlTargetStruct::Type & target2)
1781 : {
1782 0 : if (!target1.cluster.IsNull() && !target2.cluster.IsNull())
1783 : {
1784 0 : if (target1.cluster.Value() != target2.cluster.Value())
1785 : {
1786 0 : return false;
1787 : }
1788 : }
1789 0 : else if (!target1.cluster.IsNull() || !target2.cluster.IsNull())
1790 : {
1791 0 : return false;
1792 : }
1793 :
1794 0 : if (!target1.endpoint.IsNull() && !target2.endpoint.IsNull())
1795 : {
1796 0 : if (target1.endpoint.Value() != target2.endpoint.Value())
1797 : {
1798 0 : return false;
1799 : }
1800 : }
1801 0 : else if (!target1.endpoint.IsNull() || !target2.endpoint.IsNull())
1802 : {
1803 0 : return false;
1804 : }
1805 :
1806 0 : if (!target1.deviceType.IsNull() && !target2.deviceType.IsNull())
1807 : {
1808 0 : if (target1.deviceType.Value() != target2.deviceType.Value())
1809 : {
1810 0 : return false;
1811 : }
1812 : }
1813 0 : else if (!target1.deviceType.IsNull() || !target2.deviceType.IsNull())
1814 : {
1815 0 : return false;
1816 : }
1817 :
1818 0 : return true;
1819 : }
1820 :
1821 1 : bool JointFabricDatastore::ACLMatches(
1822 : const datastore::AccessControlEntryStruct & acl1,
1823 : const Clusters::JointFabricDatastore::Structs::DatastoreAccessControlEntryStruct::DecodableType & acl2)
1824 : {
1825 1 : if (acl1.privilege != acl2.privilege)
1826 : {
1827 0 : return false;
1828 : }
1829 :
1830 1 : if (acl1.authMode != acl2.authMode)
1831 : {
1832 0 : return false;
1833 : }
1834 :
1835 1 : if (acl2.subjects.IsNull())
1836 : {
1837 1 : if (!acl1.subjects.empty())
1838 : {
1839 0 : return false;
1840 : }
1841 : }
1842 : else
1843 : {
1844 0 : auto it1 = acl1.subjects.begin();
1845 0 : auto it2 = acl2.subjects.Value().begin();
1846 :
1847 0 : while (it1 != acl1.subjects.end() && it2.Next())
1848 : {
1849 0 : if (*it1 != it2.GetValue())
1850 : {
1851 0 : return false;
1852 : }
1853 0 : ++it1;
1854 : }
1855 :
1856 0 : if (it1 != acl1.subjects.end() || it2.Next())
1857 : {
1858 0 : return false;
1859 : }
1860 : }
1861 :
1862 1 : if (acl2.targets.IsNull())
1863 : {
1864 1 : if (!acl1.targets.empty())
1865 : {
1866 0 : return false;
1867 : }
1868 : }
1869 : else
1870 : {
1871 0 : auto it1 = acl1.targets.begin();
1872 0 : auto it2 = acl2.targets.Value().begin();
1873 :
1874 0 : while (it1 != acl1.targets.end() && it2.Next())
1875 : {
1876 0 : if (ACLTargetMatches(*it1, it2.GetValue()) == false)
1877 : {
1878 0 : return false;
1879 : }
1880 0 : ++it1;
1881 : }
1882 :
1883 0 : if (it1 != acl1.targets.end() || it2.Next())
1884 : {
1885 0 : return false;
1886 : }
1887 : }
1888 :
1889 1 : return true;
1890 : }
1891 :
1892 : CHIP_ERROR
1893 16 : JointFabricDatastore::AddACLToNode(
1894 : NodeId nodeId, const Clusters::JointFabricDatastore::Structs::DatastoreAccessControlEntryStruct::DecodableType & aclEntry)
1895 : {
1896 16 : VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
1897 :
1898 16 : size_t index = 0;
1899 16 : ReturnErrorOnFailure(IsNodeIdInNodeInformationEntries(nodeId, index));
1900 :
1901 : // Check if the ACL entry already exists for the node
1902 94 : for (auto & entry : mACLEntries)
1903 : {
1904 79 : if (entry.nodeID == nodeId)
1905 : {
1906 1 : if (ACLMatches(entry.ACLEntry, aclEntry))
1907 : {
1908 1 : return CHIP_NO_ERROR;
1909 : }
1910 : }
1911 : }
1912 15 : VerifyOrReturnError(mACLEntries.size() < kMaxACLs, CHIP_ERROR_NO_MEMORY);
1913 : // Create a new ACL entry
1914 15 : datastore::ACLEntryStruct newACLEntry;
1915 15 : newACLEntry.nodeID = nodeId;
1916 15 : newACLEntry.ACLEntry.privilege = aclEntry.privilege;
1917 15 : newACLEntry.ACLEntry.authMode = aclEntry.authMode;
1918 :
1919 15 : newACLEntry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kPending;
1920 :
1921 15 : if (!aclEntry.subjects.IsNull())
1922 : {
1923 0 : auto iter = aclEntry.subjects.Value().begin();
1924 0 : while (iter.Next())
1925 : {
1926 0 : newACLEntry.ACLEntry.subjects.push_back(iter.GetValue());
1927 : }
1928 0 : ReturnErrorOnFailure(iter.GetStatus());
1929 : }
1930 :
1931 15 : if (!aclEntry.targets.IsNull())
1932 : {
1933 0 : auto iter = aclEntry.targets.Value().begin();
1934 0 : while (iter.Next())
1935 : {
1936 0 : newACLEntry.ACLEntry.targets.push_back(iter.GetValue());
1937 : }
1938 0 : ReturnErrorOnFailure(iter.GetStatus());
1939 : }
1940 :
1941 15 : ReturnErrorOnFailure(GenerateAndAssignAUniqueListID(newACLEntry.listID));
1942 :
1943 : // Add the new ACL entry to the datastore
1944 15 : mACLEntries.push_back(newACLEntry);
1945 15 : const auto & storedEntry = mACLEntries.back();
1946 :
1947 15 : Clusters::JointFabricDatastore::Structs::DatastoreACLEntryStruct::Type entryToEncode;
1948 15 : entryToEncode.nodeID = storedEntry.nodeID;
1949 15 : entryToEncode.listID = storedEntry.listID;
1950 15 : entryToEncode.ACLEntry.authMode = storedEntry.ACLEntry.authMode;
1951 15 : entryToEncode.ACLEntry.privilege = storedEntry.ACLEntry.privilege;
1952 15 : entryToEncode.ACLEntry.subjects =
1953 15 : DataModel::List<const uint64_t>(storedEntry.ACLEntry.subjects.data(), storedEntry.ACLEntry.subjects.size());
1954 15 : entryToEncode.ACLEntry.targets =
1955 15 : DataModel::List<const Clusters::JointFabricDatastore::Structs::DatastoreAccessControlTargetStruct::Type>(
1956 : storedEntry.ACLEntry.targets.data(), storedEntry.ACLEntry.targets.size());
1957 15 : entryToEncode.statusEntry = storedEntry.statusEntry;
1958 :
1959 15 : const auto committedNodeId = storedEntry.nodeID;
1960 15 : const auto committedListId = storedEntry.listID;
1961 :
1962 30 : return mDelegate->SyncNode(nodeId, entryToEncode, [this, committedNodeId, committedListId]() {
1963 93 : for (auto & entry : mACLEntries)
1964 : {
1965 93 : if (entry.nodeID == committedNodeId && entry.listID == committedListId)
1966 : {
1967 15 : entry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
1968 15 : break;
1969 : }
1970 : }
1971 15 : });
1972 15 : }
1973 :
1974 2 : CHIP_ERROR JointFabricDatastore::RemoveACLFromNode(uint16_t listId, NodeId nodeId)
1975 : {
1976 2 : VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
1977 :
1978 2 : size_t index = 0;
1979 2 : ReturnErrorOnFailure(IsNodeIdInNodeInformationEntries(nodeId, index));
1980 :
1981 2 : for (auto it = mACLEntries.begin(); it != mACLEntries.end(); ++it)
1982 : {
1983 2 : if (it->nodeID == nodeId && it->listID == listId)
1984 : {
1985 2 : it->statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending;
1986 :
1987 : // initialize struct to indicate nodeid/listid and status set to DeletePending for the SyncNode call to delete the ACL
1988 : // entry on the node
1989 2 : Clusters::JointFabricDatastore::Structs::DatastoreACLEntryStruct::Type entryToDelete{ 0 };
1990 2 : entryToDelete.nodeID = it->nodeID;
1991 2 : entryToDelete.listID = it->listID;
1992 2 : entryToDelete.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending;
1993 : // Re-resolve by stable key inside the async completion instead of capturing the raw
1994 : // iterator (which dangles if an interleaved Add*/Remove* reallocates the vector).
1995 4 : return mDelegate->SyncNode(nodeId, entryToDelete, [this, listId, nodeId]() {
1996 4 : mACLEntries.erase(
1997 2 : std::remove_if(mACLEntries.begin(), mACLEntries.end(),
1998 14 : [&](const auto & entry) { return entry.nodeID == nodeId && entry.listID == listId; }),
1999 2 : mACLEntries.end());
2000 2 : });
2001 : }
2002 : }
2003 :
2004 0 : return CHIP_ERROR_NOT_FOUND;
2005 : }
2006 :
2007 1 : CHIP_ERROR JointFabricDatastore::AddNodeKeySetEntry(GroupId groupId, uint16_t groupKeySetId)
2008 : {
2009 1 : VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
2010 :
2011 : // Find all nodes that are members of this group
2012 1 : std::unordered_set<NodeId> nodesInGroup;
2013 1 : for (const auto & entry : mEndpointGroupIDEntries)
2014 : {
2015 0 : if (entry.groupID == groupId)
2016 : {
2017 0 : nodesInGroup.insert(entry.nodeID);
2018 : }
2019 : }
2020 :
2021 1 : if (!nodesInGroup.empty())
2022 : {
2023 0 : for (const auto nodeId : nodesInGroup)
2024 : {
2025 : // Skip if a matching NodeKeySet entry already exists for this node
2026 0 : bool exists = false;
2027 0 : for (const auto & nkse : mNodeKeySetEntries)
2028 : {
2029 0 : if (nkse.nodeID == nodeId && nkse.groupKeySetID == groupKeySetId)
2030 : {
2031 0 : exists = true;
2032 0 : break;
2033 : }
2034 : }
2035 0 : if (exists)
2036 : {
2037 0 : continue;
2038 : }
2039 :
2040 0 : Clusters::JointFabricDatastore::Structs::DatastoreNodeKeySetEntryStruct::Type newEntry;
2041 0 : newEntry.nodeID = nodeId;
2042 0 : newEntry.groupKeySetID = groupKeySetId;
2043 0 : newEntry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kPending;
2044 :
2045 0 : mNodeKeySetEntries.push_back(newEntry);
2046 :
2047 : // Sync to the node and mark committed on success. Re-resolve by stable key inside the
2048 : // completion; capturing the index would mark the wrong/invalid slot if an interleaved
2049 : // Invoke mutated the vector before the async completion fires.
2050 0 : ReturnErrorOnFailure(mDelegate->SyncNode(nodeId, newEntry, [this, nodeId, groupKeySetId]() {
2051 : detail::MarkEntryCommittedIfFound(
2052 : mNodeKeySetEntries, [&](const auto & e) { return e.nodeID == nodeId && e.groupKeySetID == groupKeySetId; });
2053 : }));
2054 : }
2055 : }
2056 :
2057 1 : return CHIP_NO_ERROR;
2058 1 : }
2059 :
2060 0 : CHIP_ERROR JointFabricDatastore::RemoveNodeKeySetEntry(GroupId groupId, uint16_t groupKeySetId)
2061 : {
2062 : // NOTE: this method assumes its ok to remove the keyset from each node (its not in use by any group)
2063 :
2064 : // Find all nodes that are members of this group
2065 0 : std::unordered_set<NodeId> nodesInGroup;
2066 0 : for (const auto & entry : mEndpointGroupIDEntries)
2067 : {
2068 0 : if (entry.groupID == groupId)
2069 : {
2070 0 : nodesInGroup.insert(entry.nodeID);
2071 : }
2072 : }
2073 :
2074 0 : for (auto it = mNodeKeySetEntries.begin(); it != mNodeKeySetEntries.end(); ++it)
2075 : {
2076 0 : for (const auto & nodeId : nodesInGroup)
2077 : {
2078 0 : if (it->nodeID == nodeId && it->groupKeySetID == groupKeySetId)
2079 : {
2080 : // zero-initialized struct to indicate deletion for the SyncNode call
2081 0 : Clusters::JointFabricDatastore::Structs::DatastoreNodeKeySetEntryStruct::Type nullEntry{ 0 };
2082 :
2083 0 : auto nodeIdToErase = it->nodeID;
2084 0 : auto groupKeySetIdToErase = it->groupKeySetID;
2085 0 : ReturnErrorOnFailure(mDelegate->SyncNode(nodeId, nullEntry, [this, nodeIdToErase, groupKeySetIdToErase]() {
2086 : mNodeKeySetEntries.erase(std::remove_if(mNodeKeySetEntries.begin(), mNodeKeySetEntries.end(),
2087 : [&](const auto & entry) {
2088 : return entry.nodeID == nodeIdToErase &&
2089 : entry.groupKeySetID == groupKeySetIdToErase;
2090 : }),
2091 : mNodeKeySetEntries.end());
2092 : }));
2093 :
2094 0 : return CHIP_NO_ERROR;
2095 : }
2096 : }
2097 : }
2098 :
2099 0 : return CHIP_ERROR_NOT_FOUND;
2100 0 : }
2101 :
2102 0 : CHIP_ERROR JointFabricDatastore::TestAddNodeKeySetEntry(GroupId groupId, uint16_t groupKeySetId, NodeId nodeId)
2103 : {
2104 0 : VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
2105 :
2106 0 : Clusters::JointFabricDatastore::Structs::DatastoreNodeKeySetEntryStruct::Type newEntry;
2107 0 : newEntry.nodeID = nodeId;
2108 0 : newEntry.groupKeySetID = groupKeySetId;
2109 0 : newEntry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kPending;
2110 :
2111 0 : mNodeKeySetEntries.push_back(newEntry);
2112 :
2113 : // Sync to the node and mark committed on success. Re-resolve by stable key inside the completion
2114 : // rather than capturing the index, which an interleaved Invoke could invalidate.
2115 0 : return mDelegate->SyncNode(nodeId, newEntry, [this, nodeId, groupKeySetId]() {
2116 0 : detail::MarkEntryCommittedIfFound(mNodeKeySetEntries,
2117 0 : [&](const auto & e) { return e.nodeID == nodeId && e.groupKeySetID == groupKeySetId; });
2118 0 : });
2119 : }
2120 :
2121 7 : CHIP_ERROR JointFabricDatastore::TestAddEndpointEntry(EndpointId endpointId, NodeId nodeId, CharSpan friendlyName)
2122 : {
2123 7 : Clusters::JointFabricDatastore::Structs::DatastoreEndpointEntryStruct::Type newEntry;
2124 7 : newEntry.nodeID = nodeId;
2125 7 : newEntry.endpointID = endpointId;
2126 7 : SetEndpointFriendlyNameWithOwnedStorage(nodeId, endpointId, friendlyName, newEntry);
2127 :
2128 7 : mEndpointEntries.push_back(newEntry);
2129 :
2130 7 : return CHIP_NO_ERROR;
2131 : }
2132 :
2133 0 : CHIP_ERROR JointFabricDatastore::ForceAddNodeKeySetEntry(uint16_t groupKeySetId, NodeId nodeId)
2134 : {
2135 0 : Clusters::JointFabricDatastore::Structs::DatastoreNodeKeySetEntryStruct::Type newEntry;
2136 0 : newEntry.nodeID = nodeId;
2137 0 : newEntry.groupKeySetID = groupKeySetId;
2138 0 : newEntry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
2139 :
2140 0 : mNodeKeySetEntries.push_back(newEntry);
2141 0 : return CHIP_NO_ERROR;
2142 : }
2143 :
2144 : } // namespace app
2145 : } // namespace chip
|