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