Coverage Report

Created: 2026-08-14 07:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/connectedhomeip/src/app/clusters/group-key-mgmt-server/GroupKeyManagementCluster.cpp
Line
Count
Source
1
/**
2
 *
3
 *    Copyright (c) 2020-2026 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-common/zap-generated/cluster-objects.h>
19
#include <app/clusters/group-key-mgmt-server/GroupKeyManagementCluster.h>
20
#include <app/server-cluster/AttributeListBuilder.h>
21
#include <app/server-cluster/DefaultServerCluster.h>
22
#include <clusters/GroupKeyManagement/ClusterId.h>
23
#include <clusters/GroupKeyManagement/Metadata.h>
24
#include <lib/support/AutoRelease.h>
25
26
using namespace chip;
27
using namespace chip::app;
28
using namespace chip::Credentials;
29
using namespace chip::app::Clusters;
30
using namespace chip::app::Clusters::GroupKeyManagement;
31
using namespace chip::app::Clusters::GroupKeyManagement::Attributes;
32
using namespace chip::DeviceLayer;
33
using chip::Protocols::InteractionModel::Status;
34
35
namespace {
36
37
[[maybe_unused]] constexpr uint32_t kGroupKeyClusterRevisionBeforeGroupcast = 2;
38
39
struct GroupTableCodec
40
{
41
    static constexpr TLV::Tag TagFabric()
42
0
    {
43
0
        return TLV::ContextTag(GroupKeyManagement::Structs::GroupInfoMapStruct::Fields::kFabricIndex);
44
0
    }
45
    static constexpr TLV::Tag TagGroup()
46
0
    {
47
0
        return TLV::ContextTag(GroupKeyManagement::Structs::GroupInfoMapStruct::Fields::kGroupId);
48
0
    }
49
    static constexpr TLV::Tag TagEndpoints()
50
0
    {
51
0
        return TLV::ContextTag(GroupKeyManagement::Structs::GroupInfoMapStruct::Fields::kEndpoints);
52
0
    }
53
    static constexpr TLV::Tag TagGroupName()
54
0
    {
55
0
        return TLV::ContextTag(GroupKeyManagement::Structs::GroupInfoMapStruct::Fields::kGroupName);
56
0
    }
57
58
    GroupDataProvider * mProvider = nullptr;
59
    chip::FabricIndex mFabric;
60
    GroupDataProvider::GroupInfo mInfo;
61
62
    GroupTableCodec(GroupDataProvider * provider, chip::FabricIndex fabric_index, GroupDataProvider::GroupInfo & info) :
63
0
        mProvider(provider), mFabric(fabric_index), mInfo(info)
64
0
    {}
65
66
    static constexpr bool kIsFabricScoped = true;
67
68
0
    auto GetFabricIndex() const { return mFabric; }
69
70
    CHIP_ERROR EncodeForRead(TLV::TLVWriter & writer, TLV::Tag tag, FabricIndex accessingFabricIndex) const
71
0
    {
72
0
        TLV::TLVType outer;
73
0
        ReturnErrorOnFailure(writer.StartContainer(tag, TLV::kTLVType_Structure, outer));
74
75
        // FabricIndex
76
0
        ReturnErrorOnFailure(DataModel::Encode(writer, TagFabric(), mFabric));
77
        // GroupId
78
0
        ReturnErrorOnFailure(DataModel::Encode(writer, TagGroup(), mInfo.group_id));
79
        // Endpoints
80
0
        TLV::TLVType inner;
81
0
        ReturnErrorOnFailure(writer.StartContainer(TagEndpoints(), TLV::kTLVType_Array, inner));
82
0
        GroupDataProvider::GroupEndpoint mapping;
83
0
        AutoRelease iter(mProvider->IterateEndpoints(mFabric, std::make_optional(mInfo.group_id)));
84
0
        if (!iter.IsNull())
85
0
        {
86
0
            while (iter->Next(mapping))
87
0
            {
88
0
                ReturnErrorOnFailure(writer.Put(TLV::AnonymousTag(), static_cast<uint16_t>(mapping.endpoint_id)));
89
0
            }
90
0
        }
91
0
        ReturnErrorOnFailure(writer.EndContainer(inner));
92
        // GroupName
93
0
        uint32_t name_size = static_cast<uint32_t>(strnlen(mInfo.name, GroupDataProvider::GroupInfo::kGroupNameMax));
94
0
        ReturnErrorOnFailure(writer.PutString(TagGroupName(), mInfo.name, name_size));
95
96
0
        return writer.EndContainer(outer);
97
0
    }
98
};
99
100
/*
101
 * This struct is used to build the response when the KeySetReadAllIndicies command
102
 * is invoked. It follows the format expected by AddResponse() by using a struct that
103
 * can be encoded with DataModel::Encode like the one in
104
 * GroupKeyManagement::Commands::KeySetReadAllIndicesResponse::Type. This struct however
105
 * specifies a different Encode() function that loops through all the elements pointed
106
 * to by the iterator and encodes each of them.
107
 */
108
struct KeySetReadAllIndicesResponse
109
{
110
0
    static constexpr CommandId GetCommandId() { return GroupKeyManagement::Commands::KeySetReadAllIndicesResponse::Id; }
111
0
    static constexpr ClusterId GetClusterId() { return GroupKeyManagement::Id; }
112
113
    GroupDataProvider::KeySetIterator * mIterator = nullptr;
114
115
0
    KeySetReadAllIndicesResponse(GroupDataProvider::KeySetIterator * iter) : mIterator(iter) {}
116
117
    CHIP_ERROR Encode(TLV::TLVWriter & writer, TLV::Tag tag) const
118
0
    {
119
0
        TLV::TLVType outer;
120
0
        ReturnErrorOnFailure(writer.StartContainer(tag, TLV::kTLVType_Structure, outer));
121
122
0
        TLV::TLVType array;
123
0
        ReturnErrorOnFailure(writer.StartContainer(
124
0
            TLV::ContextTag(GroupKeyManagement::Commands::KeySetReadAllIndicesResponse::Fields::kGroupKeySetIDs),
125
0
            TLV::kTLVType_Array, array));
126
127
0
        GroupDataProvider::KeySet keyset;
128
0
        while (mIterator && mIterator->Next(keyset))
129
0
        {
130
0
            ReturnErrorOnFailure(app::DataModel::Encode(writer, TLV::AnonymousTag(), keyset.keyset_id));
131
0
        }
132
133
0
        ReturnErrorOnFailure(writer.EndContainer(array));
134
0
        ReturnErrorOnFailure(writer.EndContainer(outer));
135
0
        return CHIP_NO_ERROR;
136
0
    }
137
};
138
CHIP_ERROR ReadGroupKeyMap(FabricTable & fabricTable, GroupDataProvider & provider, AttributeValueEncoder & aEncoder)
139
0
{
140
0
    return aEncoder.EncodeList([&fabricTable, &provider](const auto & encoder) -> CHIP_ERROR {
141
0
        for (auto & fabric : fabricTable)
142
0
        {
143
0
            auto fabric_index = fabric.GetFabricIndex();
144
0
            AutoRelease iter(provider.IterateGroupKeys(fabric_index));
145
0
            VerifyOrReturnError(!iter.IsNull(), CHIP_ERROR_NO_MEMORY);
146
147
0
            GroupDataProvider::GroupKey mapping;
148
0
            while (iter->Next(mapping))
149
0
            {
150
0
                GroupKeyManagement::Structs::GroupKeyMapStruct::Type key = {
151
0
                    .groupId       = mapping.group_id,
152
0
                    .groupKeySetID = mapping.keyset_id,
153
0
                    .fabricIndex   = fabric_index,
154
0
                };
155
0
                ReturnErrorOnFailure(encoder.Encode(key));
156
0
            }
157
0
        }
158
0
        return CHIP_NO_ERROR;
159
0
    });
160
0
}
161
162
CHIP_ERROR WriteGroupKeyMap(GroupDataProvider & provider, const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder)
163
0
{
164
0
    auto fabric_index = aDecoder.AccessingFabricIndex();
165
166
0
    if (!aPath.IsListItemOperation())
167
0
    {
168
0
        Attributes::GroupKeyMap::TypeInfo::DecodableType list;
169
0
        size_t new_count;
170
171
0
        ReturnErrorOnFailure(aDecoder.Decode(list));
172
0
        ReturnErrorOnFailure(list.ComputeSize(&new_count));
173
174
        // Remove existing keys, ignore errors
175
0
        TEMPORARY_RETURN_IGNORED provider.RemoveGroupKeys(fabric_index);
176
177
        // Add the new keys
178
0
        auto iter = list.begin();
179
0
        size_t i  = 0;
180
0
        while (iter.Next())
181
0
        {
182
0
            const auto & value = iter.GetValue();
183
0
            VerifyOrReturnError(fabric_index == value.fabricIndex, CHIP_ERROR_INVALID_FABRIC_INDEX);
184
            // Cannot map to IPK, see `GroupKeyMapStruct` in Group Key Management cluster spec
185
0
            VerifyOrReturnError(value.groupKeySetID != 0, CHIP_IM_GLOBAL_STATUS(ConstraintError));
186
187
0
            ReturnErrorOnFailure(
188
0
                provider.SetGroupKeyAt(value.fabricIndex, i++, GroupDataProvider::GroupKey(value.groupId, value.groupKeySetID)));
189
0
        }
190
0
        ReturnErrorOnFailure(iter.GetStatus());
191
0
    }
192
0
    else if (aPath.mListOp == ConcreteDataAttributePath::ListOperation::AppendItem)
193
0
    {
194
0
        Structs::GroupKeyMapStruct::DecodableType value;
195
0
        size_t current_count = 0;
196
0
        ReturnErrorOnFailure(aDecoder.Decode(value));
197
0
        VerifyOrReturnError(fabric_index == value.fabricIndex, CHIP_ERROR_INVALID_FABRIC_INDEX);
198
        // Cannot map to IPK, see `GroupKeyMapStruct` in Group Key Management cluster spec
199
0
        VerifyOrReturnError(value.groupKeySetID != 0, CHIP_IM_GLOBAL_STATUS(ConstraintError));
200
201
0
        {
202
0
            AutoRelease iter(provider.IterateGroupKeys(fabric_index));
203
0
            VerifyOrReturnError(!iter.IsNull(), CHIP_ERROR_NO_MEMORY);
204
205
0
            current_count = iter->Count();
206
0
        }
207
208
0
        ReturnErrorOnFailure(provider.SetGroupKeyAt(value.fabricIndex, current_count,
209
0
                                                    GroupDataProvider::GroupKey(value.groupId, value.groupKeySetID)));
210
0
    }
211
0
    else
212
0
    {
213
0
        return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE;
214
0
    }
215
216
0
    return CHIP_NO_ERROR;
217
0
}
218
219
CHIP_ERROR ReadGroupTable(FabricTable & fabricTable, GroupDataProvider & provider, AttributeValueEncoder & aEncoder)
220
0
{
221
0
    return aEncoder.EncodeList([&fabricTable, &provider](const auto & encoder) -> CHIP_ERROR {
222
0
        for (auto & fabric : fabricTable)
223
0
        {
224
0
            auto fabric_index = fabric.GetFabricIndex();
225
0
            AutoRelease iter(provider.IterateGroupInfo(fabric_index));
226
0
            VerifyOrReturnError(!iter.IsNull(), CHIP_ERROR_NO_MEMORY);
227
228
0
            GroupDataProvider::GroupInfo info;
229
0
            while (iter->Next(info))
230
0
            {
231
0
                ReturnErrorOnFailure(encoder.Encode(GroupTableCodec(&provider, fabric_index, info)));
232
0
            }
233
0
        }
234
235
0
        return CHIP_NO_ERROR;
236
0
    });
237
0
}
238
239
CHIP_ERROR ReadMaxGroupsPerFabric(GroupDataProvider & provider, AttributeValueEncoder & aEncoder)
240
0
{
241
0
    return aEncoder.Encode(provider.GetMaxGroupsPerFabric());
242
0
}
243
244
CHIP_ERROR ReadMaxGroupKeysPerFabric(GroupDataProvider & provider, AttributeValueEncoder & aEncoder)
245
0
{
246
0
    return aEncoder.Encode(provider.GetMaxGroupKeysPerFabric());
247
0
}
248
249
const FabricInfo * GetFabricInfoOrNull(CommandHandler * handler, FabricTable & fabricTable)
250
0
{
251
0
    return fabricTable.FindFabricWithIndex(handler->GetAccessingFabricIndex());
252
0
}
253
254
Status ValidateKeySetWriteArguments(const Commands::KeySetWrite::DecodableType & commandData)
255
0
{
256
    // SPEC: If the EpochKey0 field is null or its associated EpochStartTime0 field is null, then this command SHALL fail with an
257
    // INVALID_COMMAND status code responded to the client.
258
0
    if (commandData.groupKeySet.epochKey0.IsNull() || commandData.groupKeySet.epochStartTime0.IsNull())
259
0
    {
260
0
        return Status::InvalidCommand;
261
0
    }
262
263
    // SPEC: If the EpochStartTime0 is set to 0, then this command SHALL fail with an INVALID_COMMAND status code responded to the
264
    // client.
265
0
    if (0 == commandData.groupKeySet.epochStartTime0.Value())
266
0
    {
267
0
        return Status::InvalidCommand;
268
0
    }
269
270
    // By now we at least have epochKey0.
271
0
    static_assert(GroupDataProvider::EpochKey::kLengthBytes == 16,
272
0
                  "Expect EpochKey internal data structure to have a length of 16 bytes.");
273
274
    // SPEC: If the EpochKey0 field's length is not exactly 16 bytes, then this command SHALL fail with a CONSTRAINT_ERROR status
275
    // code responded to the client.
276
0
    if (commandData.groupKeySet.epochKey0.Value().size() != GroupDataProvider::EpochKey::kLengthBytes)
277
0
    {
278
0
        return Status::ConstraintError;
279
0
    }
280
281
    // Already known to be false by now
282
0
    bool epoch_key0_is_null    = false;
283
0
    uint64_t epoch_start_time0 = commandData.groupKeySet.epochStartTime0.Value();
284
285
0
    bool epoch_key1_is_null        = commandData.groupKeySet.epochKey1.IsNull();
286
0
    bool epoch_start_time1_is_null = commandData.groupKeySet.epochStartTime1.IsNull();
287
288
0
    uint64_t epoch_start_time1 = 0; // Will be overridden when known to be present.
289
290
    // SPEC: If exactly one of the EpochKey1 or EpochStartTime1 is null, rather than both being null, or neither being null, then
291
    // this command SHALL fail with an INVALID_COMMAND status code responded to the client.
292
0
    if (epoch_key1_is_null != epoch_start_time1_is_null)
293
0
    {
294
0
        return Status::InvalidCommand;
295
0
    }
296
297
0
    if (!epoch_key1_is_null)
298
0
    {
299
        // SPEC: If the EpochKey1 field is not null, then the EpochKey0 field SHALL NOT be null. Otherwise this command SHALL fail
300
        // with an INVALID_COMMAND status code responded to the client.
301
0
        if (epoch_key0_is_null)
302
0
        {
303
0
            return Status::InvalidCommand;
304
0
        }
305
306
        // SPEC: If the EpochKey1 field is not null, and the field's length is not exactly 16 bytes, then this command SHALL fail
307
        // with a CONSTRAINT_ERROR status code responded to the client.
308
0
        if (commandData.groupKeySet.epochKey1.Value().size() != GroupDataProvider::EpochKey::kLengthBytes)
309
0
        {
310
0
            return Status::ConstraintError;
311
0
        }
312
313
        // By now, if EpochKey1 was present, we know EpochStartTime1 was also present.
314
0
        epoch_start_time1 = commandData.groupKeySet.epochStartTime1.Value();
315
316
        // SPEC: If the EpochKey1 field is not null, its associated EpochStartTime1 field SHALL NOT be null and SHALL contain a
317
        // later epoch start time than the epoch start time found in the EpochStartTime0 field. Otherwise this command SHALL fail
318
        // with an INVALID_COMMAND status code responded to the client.
319
0
        bool epoch1_later_than_epoch0 = epoch_start_time1 > epoch_start_time0;
320
0
        if (!epoch1_later_than_epoch0)
321
0
        {
322
0
            return Status::InvalidCommand;
323
0
        }
324
0
    }
325
326
0
    bool epoch_key2_is_null        = commandData.groupKeySet.epochKey2.IsNull();
327
0
    bool epoch_start_time2_is_null = commandData.groupKeySet.epochStartTime2.IsNull();
328
329
    // SPEC: If exactly one of the EpochKey2 or EpochStartTime2 is null, rather than both being null, or neither being null, then
330
    // this command SHALL fail with an INVALID_COMMAND status code responded to the client.
331
0
    if (epoch_key2_is_null != epoch_start_time2_is_null)
332
0
    {
333
0
        return Status::InvalidCommand;
334
0
    }
335
336
0
    if (!epoch_key2_is_null)
337
0
    {
338
        // SPEC: If the EpochKey2 field is not null, then the EpochKey1 and EpochKey0 fields SHALL NOT be null. Otherwise this
339
        // command SHALL fail with an INVALID_COMMAND status code responded to the client.
340
0
        if (epoch_key0_is_null || epoch_key1_is_null)
341
0
        {
342
0
            return Status::InvalidCommand;
343
0
        }
344
345
        // SPEC: If the EpochKey2 field is not null, and the field's length is not exactly 16 bytes, then this command SHALL fail
346
        // with a CONSTRAINT_ERROR status code responded to the client.
347
0
        if (commandData.groupKeySet.epochKey2.Value().size() != GroupDataProvider::EpochKey::kLengthBytes)
348
0
        {
349
0
            return Status::ConstraintError;
350
0
        }
351
352
        // By now, if EpochKey2 was present, we know EpochStartTime2 was also present.
353
0
        uint64_t epoch_start_time2 = commandData.groupKeySet.epochStartTime2.Value();
354
355
        // SPEC: If the EpochKey2 field is not null, its associated EpochStartTime2 field SHALL NOT be null and SHALL contain a
356
        // later epoch start time than the epoch start time found in the EpochStartTime1 field. Otherwise this command SHALL fail
357
        // with an INVALID_COMMAND status code responded to the client.
358
0
        bool epoch2_later_than_epoch1 = epoch_start_time2 > epoch_start_time1;
359
0
        if (!epoch2_later_than_epoch1)
360
0
        {
361
0
            return Status::InvalidCommand;
362
0
        }
363
0
    }
364
365
0
    return Status::Success;
366
0
}
367
368
std::optional<DataModel::ActionReturnStatus> HandleKeySetWrite(CommandHandler * commandObj, const ConcreteCommandPath & commandPath,
369
                                                               const Commands::KeySetWrite::DecodableType & commandData,
370
                                                               Credentials::GroupDataProvider * provider, const FabricInfo * fabric)
371
0
{
372
    // Pre-validate all complex data dependency assumptions about the epoch keys
373
0
    Status status = ValidateKeySetWriteArguments(commandData);
374
0
    if (status != Status::Success)
375
0
    {
376
0
        commandObj->AddStatus(commandPath, status, "Failure to validate KeySet data dependencies.");
377
0
        return std::nullopt;
378
0
    }
379
380
0
    if (commandData.groupKeySet.groupKeySecurityPolicy == GroupKeySecurityPolicyEnum::kUnknownEnumValue)
381
0
    {
382
        // If a client indicates an enumeration value to the server, that is not
383
        // supported by the server, because it is ... a new value unrecognized
384
        // by a legacy server, then the server SHALL generate a general
385
        // constraint error
386
0
        commandObj->AddStatus(commandPath, Status::ConstraintError, "Received unknown GroupKeySecurityPolicyEnum value");
387
0
        return std::nullopt;
388
0
    }
389
390
0
    if (!GroupKeyManagementCluster::IsMCSPSupported() &&
391
0
        commandData.groupKeySet.groupKeySecurityPolicy == GroupKeySecurityPolicyEnum::kCacheAndSync)
392
0
    {
393
        // When CacheAndSync is not supported in the FeatureMap of this cluster,
394
        // any action attempting to set CacheAndSync in the
395
        // GroupKeySecurityPolicy field SHALL fail with an INVALID_COMMAND
396
        // error.
397
0
        commandObj->AddStatus(commandPath, Status::InvalidCommand,
398
0
                              "Received a CacheAndSync GroupKeySecurityPolicyEnum when MCSP not supported");
399
0
        return std::nullopt;
400
0
    }
401
402
    // All flight checks completed: by now we know that non-null keys are all valid and correct size.
403
0
    bool epoch_key1_present = !commandData.groupKeySet.epochKey1.IsNull();
404
0
    bool epoch_key2_present = !commandData.groupKeySet.epochKey2.IsNull();
405
406
0
    GroupDataProvider::KeySet keyset(commandData.groupKeySet.groupKeySetID, commandData.groupKeySet.groupKeySecurityPolicy, 0);
407
408
    // Epoch Key 0 always present
409
0
    keyset.epoch_keys[0].start_time = commandData.groupKeySet.epochStartTime0.Value();
410
0
    memcpy(keyset.epoch_keys[0].key, commandData.groupKeySet.epochKey0.Value().data(), GroupDataProvider::EpochKey::kLengthBytes);
411
0
    keyset.num_keys_used++;
412
413
    // Epoch Key 1
414
0
    if (epoch_key1_present)
415
0
    {
416
0
        keyset.epoch_keys[1].start_time = commandData.groupKeySet.epochStartTime1.Value();
417
0
        memcpy(keyset.epoch_keys[1].key, commandData.groupKeySet.epochKey1.Value().data(),
418
0
               GroupDataProvider::EpochKey::kLengthBytes);
419
0
        keyset.num_keys_used++;
420
0
    }
421
422
    // Epoch Key 2
423
0
    if (epoch_key2_present)
424
0
    {
425
0
        keyset.epoch_keys[2].start_time = commandData.groupKeySet.epochStartTime2.Value();
426
0
        memcpy(keyset.epoch_keys[2].key, commandData.groupKeySet.epochKey2.Value().data(),
427
0
               GroupDataProvider::EpochKey::kLengthBytes);
428
0
        keyset.num_keys_used++;
429
0
    }
430
431
0
    uint8_t compressed_fabric_id_buffer[sizeof(uint64_t)];
432
0
    MutableByteSpan compressed_fabric_id(compressed_fabric_id_buffer);
433
0
    CHIP_ERROR err = fabric->GetCompressedFabricIdBytes(compressed_fabric_id);
434
0
    if (CHIP_NO_ERROR != err)
435
0
    {
436
0
        return Status::Failure;
437
0
    }
438
439
    // Set KeySet
440
0
    err = provider->SetKeySet(fabric->GetFabricIndex(), compressed_fabric_id, keyset);
441
0
    if (CHIP_ERROR_INVALID_LIST_LENGTH == err)
442
0
    {
443
0
        commandObj->AddStatus(commandPath, Status::ResourceExhausted, "Not enough space left to add a new KeySet");
444
0
        return std::nullopt;
445
0
    }
446
447
0
    if (CHIP_NO_ERROR == err)
448
0
    {
449
0
        ChipLogDetail(Zcl, "GroupKeyManagementCluster: KeySetWrite OK");
450
0
    }
451
0
    else
452
0
    {
453
0
        ChipLogDetail(Zcl, "GroupKeyManagementCluster: KeySetWrite: %" CHIP_ERROR_FORMAT, err.Format());
454
0
    }
455
456
    // Send response
457
0
    return StatusIB(err).mStatus;
458
0
}
459
460
std::optional<DataModel::ActionReturnStatus> HandleKeySetRead(CommandHandler * commandObj, const ConcreteCommandPath & commandPath,
461
                                                              const Commands::KeySetRead::DecodableType & commandData,
462
                                                              Credentials::GroupDataProvider * provider, const FabricInfo * fabric)
463
0
{
464
0
    FabricIndex fabricIndex = fabric->GetFabricIndex();
465
0
    GroupDataProvider::KeySet keyset;
466
0
    if (CHIP_NO_ERROR != provider->GetKeySet(fabricIndex, commandData.groupKeySetID, keyset))
467
0
    {
468
        // KeySet ID not found
469
0
        commandObj->AddStatus(commandPath, Status::NotFound, "Keyset ID not found in KeySetRead");
470
0
        return std::nullopt;
471
0
    }
472
473
    // In KeySetReadResponse, EpochKey0, EpochKey1 and EpochKey2 key contents shall be null
474
0
    GroupKeyManagement::Commands::KeySetReadResponse::Type response;
475
0
    response.groupKeySet.groupKeySetID          = keyset.keyset_id;
476
0
    response.groupKeySet.groupKeySecurityPolicy = keyset.policy;
477
478
    // Keyset 0
479
0
    if (keyset.num_keys_used > 0)
480
0
    {
481
0
        response.groupKeySet.epochStartTime0.SetNonNull(keyset.epoch_keys[0].start_time);
482
0
    }
483
0
    else
484
0
    {
485
0
        response.groupKeySet.epochStartTime0.SetNull();
486
0
    }
487
0
    response.groupKeySet.epochKey0.SetNull();
488
489
    // Keyset 1
490
0
    if (keyset.num_keys_used > 1)
491
0
    {
492
0
        response.groupKeySet.epochStartTime1.SetNonNull(keyset.epoch_keys[1].start_time);
493
0
    }
494
0
    else
495
0
    {
496
0
        response.groupKeySet.epochStartTime1.SetNull();
497
0
    }
498
0
    response.groupKeySet.epochKey1.SetNull();
499
500
    // Keyset 2
501
0
    if (keyset.num_keys_used > 2)
502
0
    {
503
0
        response.groupKeySet.epochStartTime2.SetNonNull(keyset.epoch_keys[2].start_time);
504
0
    }
505
0
    else
506
0
    {
507
0
        response.groupKeySet.epochStartTime2.SetNull();
508
0
    }
509
0
    response.groupKeySet.epochKey2.SetNull();
510
511
0
    commandObj->AddResponse(commandPath, response);
512
0
    return std::nullopt;
513
0
}
514
515
std::optional<DataModel::ActionReturnStatus> HandleKeySetRemove(CommandHandler * commandObj,
516
                                                                const ConcreteCommandPath & commandPath,
517
                                                                const Commands::KeySetRemove::DecodableType & commandData,
518
                                                                Credentials::GroupDataProvider * provider,
519
                                                                const FabricInfo * fabric)
520
521
0
{
522
0
    if (commandData.groupKeySetID == GroupDataProvider::kIdentityProtectionKeySetId)
523
0
    {
524
        // SPEC: This command SHALL fail with an INVALID_COMMAND status code back to the initiator if the GroupKeySetID being
525
        // removed is 0, which is the Key Set associated with the Identity Protection Key (IPK).
526
0
        commandObj->AddStatus(commandPath, Status::InvalidCommand, "Attempted to KeySetRemove the identity protection key!");
527
0
        return std::nullopt;
528
0
    }
529
530
    // Remove keyset
531
0
    FabricIndex fabricIndex = fabric->GetFabricIndex();
532
0
    CHIP_ERROR err          = provider->RemoveKeySet(fabricIndex, commandData.groupKeySetID);
533
534
0
    if (CHIP_NO_ERROR == err)
535
0
    {
536
0
        return err;
537
0
    }
538
539
0
    Status status = (CHIP_ERROR_NOT_FOUND == err || CHIP_ERROR_KEY_NOT_FOUND == err) ? Status::NotFound : Status::Failure;
540
541
    // Send status response.
542
0
    commandObj->AddStatus(commandPath, status, "KeySetRemove failed");
543
0
    return std::nullopt;
544
0
}
545
546
std::optional<DataModel::ActionReturnStatus>
547
HandleKeySetReadAllIndices(CommandHandler * commandObj, const ConcreteCommandPath & commandPath,
548
                           const Commands::KeySetReadAllIndices::DecodableType & commandData,
549
                           Credentials::GroupDataProvider * provider, const FabricInfo * fabric)
550
0
{
551
0
    FabricIndex fabricIndex = fabric->GetFabricIndex();
552
0
    AutoRelease keysIt(provider->IterateKeySets(fabricIndex));
553
0
    if (keysIt.IsNull())
554
0
    {
555
0
        commandObj->AddStatus(commandPath, Status::Failure, "Failed iteration of key set indices!");
556
0
        return std::nullopt;
557
0
    }
558
559
0
    commandObj->AddResponse(commandPath, KeySetReadAllIndicesResponse(&*keysIt));
560
0
    return std::nullopt;
561
0
}
562
} // namespace
563
564
namespace chip {
565
namespace app {
566
namespace Clusters {
567
568
std::optional<DataModel::ActionReturnStatus> GroupKeyManagementCluster::InvokeCommand(const DataModel::InvokeRequest & request,
569
                                                                                      chip::TLV::TLVReader & input_arguments,
570
                                                                                      CommandHandler * handler)
571
0
{
572
0
    const FabricInfo * fabric = GetFabricInfoOrNull(handler, mContext.fabricTable);
573
574
0
    if (fabric == nullptr)
575
0
    {
576
0
        ChipLogError(Zcl, "GroupKeyManagement: Failed to find fabric for index %u", handler->GetAccessingFabricIndex());
577
0
        return CHIP_ERROR_INTERNAL;
578
0
    }
579
580
0
    const FabricIndex fabric_index = fabric->GetFabricIndex();
581
582
0
    GroupDataProvider * provider = &mContext.groupDataProvider;
583
584
0
    switch (request.path.mCommandId)
585
0
    {
586
0
    case GroupKeyManagement::Commands::KeySetWrite::Id: {
587
0
        GroupKeyManagement::Commands::KeySetWrite::DecodableType request_data;
588
0
        ReturnErrorOnFailure(request_data.Decode(input_arguments, fabric_index));
589
0
        return HandleKeySetWrite(handler, request.path, request_data, provider, fabric);
590
0
    }
591
0
    case GroupKeyManagement::Commands::KeySetRead::Id: {
592
0
        GroupKeyManagement::Commands::KeySetRead::DecodableType request_data;
593
0
        ReturnErrorOnFailure(request_data.Decode(input_arguments, fabric_index));
594
0
        return HandleKeySetRead(handler, request.path, request_data, provider, fabric);
595
0
    }
596
0
    case GroupKeyManagement::Commands::KeySetRemove::Id: {
597
0
        GroupKeyManagement::Commands::KeySetRemove::DecodableType request_data;
598
0
        ReturnErrorOnFailure(request_data.Decode(input_arguments, fabric_index));
599
0
        return HandleKeySetRemove(handler, request.path, request_data, provider, fabric);
600
0
    }
601
0
    case GroupKeyManagement::Commands::KeySetReadAllIndices::Id: {
602
0
        GroupKeyManagement::Commands::KeySetReadAllIndices::DecodableType request_data;
603
0
        ReturnErrorOnFailure(request_data.Decode(input_arguments, fabric_index));
604
0
        return HandleKeySetReadAllIndices(handler, request.path, request_data, provider, fabric);
605
0
    }
606
0
    default:
607
0
        return Protocols::InteractionModel::Status::UnsupportedCommand;
608
0
    }
609
0
}
610
611
DataModel::ActionReturnStatus GroupKeyManagementCluster::ReadAttribute(const DataModel::ReadAttributeRequest & request,
612
                                                                       AttributeValueEncoder & encoder)
613
0
{
614
0
    switch (request.path.mAttributeId)
615
0
    {
616
0
    case GroupKeyManagement::Attributes::ClusterRevision::Id:
617
0
        return encoder.Encode(kRevision);
618
0
    case Attributes::FeatureMap::Id: {
619
0
        BitFlags<GroupKeyManagement::Feature> features;
620
0
        if (mContext.groupDataProvider.IsGroupcastEnabled())
621
0
        {
622
0
            features.Set(Clusters::GroupKeyManagement::Feature::kGroupcast);
623
0
        }
624
0
        if (IsMCSPSupported())
625
0
        {
626
0
            features.Set(Clusters::GroupKeyManagement::Feature::kCacheAndSync);
627
0
        }
628
0
        return encoder.Encode(features);
629
0
    }
630
0
    case GroupKeyManagement::Attributes::GroupKeyMap::Id:
631
0
        return ReadGroupKeyMap(mContext.fabricTable, mContext.groupDataProvider, encoder);
632
0
    case GroupKeyManagement::Attributes::GroupTable::Id:
633
0
        return ReadGroupTable(mContext.fabricTable, mContext.groupDataProvider, encoder);
634
0
    case GroupKeyManagement::Attributes::MaxGroupsPerFabric::Id:
635
0
        return ReadMaxGroupsPerFabric(mContext.groupDataProvider, encoder);
636
0
    case GroupKeyManagement::Attributes::MaxGroupKeysPerFabric::Id:
637
0
        return ReadMaxGroupKeysPerFabric(mContext.groupDataProvider, encoder);
638
0
    default:
639
0
        return Protocols::InteractionModel::Status::UnsupportedCommand;
640
0
    }
641
0
}
642
643
DataModel::ActionReturnStatus GroupKeyManagementCluster::WriteAttribute(const DataModel::WriteAttributeRequest & request,
644
                                                                        AttributeValueDecoder & decoder)
645
0
{
646
0
    switch (request.path.mAttributeId)
647
0
    {
648
0
    case GroupKeyMap::Id: {
649
0
        return NotifyAttributeChangedIfSuccess(request.path.mAttributeId,
650
0
                                               WriteGroupKeyMap(mContext.groupDataProvider, request.path, decoder),
651
0
                                               DataModel::AttributeChangeType::kQuiet);
652
0
    }
653
0
    default:
654
0
        return Protocols::InteractionModel::Status::UnsupportedWrite;
655
0
    }
656
0
}
657
658
CHIP_ERROR GroupKeyManagementCluster::Attributes(const ConcreteClusterPath & path,
659
                                                 ReadOnlyBufferBuilder<DataModel::AttributeEntry> & builder)
660
0
{
661
    // TODO(#72714): remove this override once the AttributeQualityFlags::kChangesOmitted quality is honored by the generator.
662
0
    static constexpr DataModel::AttributeEntry kMandatoryMetadataWithChangesOmitted[] = {
663
0
        DataModel::AttributeEntry(GroupKeyMap::Id,
664
0
                                  BitFlags<DataModel::AttributeQualityFlags>(DataModel::AttributeQualityFlags::kListAttribute,
665
0
                                                                             DataModel::AttributeQualityFlags::kChangesOmitted),
666
0
                                  Access::Privilege::kView, Access::Privilege::kManage),
667
0
        GroupTable::kMetadataEntry,
668
0
        MaxGroupsPerFabric::kMetadataEntry,
669
0
        MaxGroupKeysPerFabric::kMetadataEntry,
670
0
    };
671
672
0
    AttributeListBuilder listBuilder(builder);
673
0
    return listBuilder.Append(Span(kMandatoryMetadataWithChangesOmitted), {});
674
0
}
675
676
CHIP_ERROR GroupKeyManagementCluster::AcceptedCommands(const ConcreteClusterPath & path,
677
                                                       ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> & builder)
678
0
{
679
0
    static constexpr DataModel::AcceptedCommandEntry kAcceptedCommands[] = {
680
0
        Commands::KeySetWrite::kMetadataEntry,
681
0
        Commands::KeySetRead::kMetadataEntry,
682
0
        Commands::KeySetRemove::kMetadataEntry,
683
0
        Commands::KeySetReadAllIndices::kMetadataEntry,
684
0
    };
685
0
    return builder.ReferenceExisting(kAcceptedCommands);
686
0
}
687
688
CHIP_ERROR GroupKeyManagementCluster::GeneratedCommands(const ConcreteClusterPath & path,
689
                                                        ReadOnlyBufferBuilder<CommandId> & builder)
690
0
{
691
0
    static constexpr CommandId kGeneratedCommands[] = {
692
0
        Commands::KeySetReadAllIndicesResponse::Id,
693
0
        Commands::KeySetReadResponse::Id,
694
0
    };
695
0
    return builder.ReferenceExisting(kGeneratedCommands);
696
0
}
697
698
} // namespace Clusters
699
} // namespace app
700
} // namespace chip