From 1958e476f6b0ea36cadc278eeb387f47fe8f5db7 Mon Sep 17 00:00:00 2001
From: Jean-Francois Penven <67962328+jepenven-silabs@users.noreply.github.com>
Date: Thu, 17 Mar 2022 14:13:56 -0400
Subject: [PATCH] [Group] Configure Chip-tool's GroupDataProvider (#16232)

* Configure Chip-tool's GroupDataProvider
---
 .github/.wordlist.txt                         |   8 +
 examples/chip-tool/README.md                  |  39 ++
 .../chip-tool/commands/common/CHIPCommand.cpp |   2 +-
 examples/chip-tool/commands/group/Commands.h  | 372 ++++++++++++++++++
 examples/chip-tool/main.cpp                   |   2 +
 src/lib/support/TestGroupData.h               |   8 +
 6 files changed, 430 insertions(+), 1 deletion(-)
 create mode 100644 examples/chip-tool/commands/group/Commands.h

diff --git a/.github/.wordlist.txt b/.github/.wordlist.txt
index 3245626bebaa79..4b3359cc8684a4 100644
--- a/.github/.wordlist.txt
+++ b/.github/.wordlist.txt
@@ -300,6 +300,7 @@ cxx
 CY
 CYW
 DAC
+dadbdcdddedf
 DAP
 DAPLINK
 DataFrame
@@ -358,6 +359,7 @@ DevKitC
 DevKitM
 devtype
 df
+dfe
 dfu
 DgDxsfHx
 dhclient
@@ -542,6 +544,7 @@ Gradle
 gradlew
 GroupId
 GroupKeyManagement
+groupsettings
 gtk
 GUA
 Gv
@@ -646,6 +649,7 @@ kBusy
 kCase
 Kconfig
 KeypadInput
+keyset
 kGroup
 kInvalidCommandId
 KitProg
@@ -1184,6 +1188,7 @@ TestEmptyString
 TestGenExample
 TestGroupDemoConfig
 TestMultiRead
+TestName
 TESTPASSWD
 TestPICS
 TESTSSID
@@ -1326,6 +1331,7 @@ WS
 WSL
 WSTK
 xa
+xAAAA
 xab
 xaver
 xb
@@ -1358,6 +1364,8 @@ xfffff
 xFFFFFFFD
 xffffffffe
 xfffffffff
+xffffffffffff
+xffffffffffffXXXX
 xtensa
 xwayland
 XXXX
diff --git a/examples/chip-tool/README.md b/examples/chip-tool/README.md
index e90072970abc15..c44e830e299595 100644
--- a/examples/chip-tool/README.md
+++ b/examples/chip-tool/README.md
@@ -120,6 +120,45 @@ The endpoint id must be between 1 and 240.
 
 The client will send a single command packet and then exit.
 
+## Configuring the client for Group Commands
+
+Prior to sending a Group command, both the end device and the Client (Chip-tool)
+must be configured appropriately.
+
+To configure the client please use the groupsettings option
+
+    $ chip-tool groupsettings
+
+A group with a valid encryption key needs to be set. The groupid and the
+encryption key must match the one configured on the end device.
+
+To add a group
+
+    $ chip-tool groupsettings add-group TestName 0x1010
+
+To add a keyset
+
+    $ chip-tool groupsettings add-keyset 0xAAAA 0 0x000000000021dfe0 hex:d0d1d2d3d4d5d6d7d8d9dadbdcdddedf
+
+Take note that the epoch key must be in hex form with the 'hex:' prefix
+
+Finally to bind the keyset to the group
+
+    $ chip-tool groupsettings bind-keyset 0x1010 0xAAAA
+
+## Using the Client to Send Group (Multicast) Matter Commands
+
+To use the Client to send Matter commands, run the built executable and pass it
+the target cluster name, the target command name, the Group Id in Node Id form
+(0xffffffffffffXXXX) and an unused endpoint Id. Take note that Only commands and
+attributes write can be send with Group Id.
+
+E.G. sending to group Id 0x0025
+
+    $ chip-tool onoff on 0xffffffffffff0025 1
+
+The client will send a single multicast command packet and then exit.
+
 ### How to get the list of supported clusters
 
 To get the list of supported clusters, run the built executable without any
diff --git a/examples/chip-tool/commands/common/CHIPCommand.cpp b/examples/chip-tool/commands/common/CHIPCommand.cpp
index bb34584bf2ec72..856dbbeee6ed16 100644
--- a/examples/chip-tool/commands/common/CHIPCommand.cpp
+++ b/examples/chip-tool/commands/common/CHIPCommand.cpp
@@ -61,7 +61,7 @@ CHIP_ERROR CHIPCommand::Run()
     ReturnLogErrorOnFailure(InitializeCommissioner(kIdentityGamma, kIdentityGammaFabricId, trustStore));
 
     // Initialize Group Data
-    ReturnLogErrorOnFailure(chip::GroupTesting::InitProvider());
+    ReturnLogErrorOnFailure(chip::GroupTesting::InitProvider(mDefaultStorage));
     for (auto it = mCommissioners.begin(); it != mCommissioners.end(); it++)
     {
         chip::FabricInfo * fabric = it->second->GetFabricInfo();
diff --git a/examples/chip-tool/commands/group/Commands.h b/examples/chip-tool/commands/group/Commands.h
new file mode 100644
index 00000000000000..7f5b838566c145
--- /dev/null
+++ b/examples/chip-tool/commands/group/Commands.h
@@ -0,0 +1,372 @@
+/*
+ *   Copyright (c) 2022 Project CHIP Authors
+ *   All rights reserved.
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+
+#pragma once
+
+#include "../common/CHIPCommand.h"
+#include "../common/Command.h"
+
+#include <lib/support/Span.h>
+
+class ShowControllerGroups : public CHIPCommand
+{
+public:
+    ShowControllerGroups(CredentialIssuerCommands * credsIssuerConfig) : CHIPCommand("show-groups", credsIssuerConfig) {}
+    chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(5); }
+
+    bool FindKeySetId(chip::FabricIndex fabricIndex, chip::GroupId groupId, chip::KeysetId & keysetId)
+    {
+        chip::Credentials::GroupDataProvider * groupDataProvider = chip::Credentials::GetGroupDataProvider();
+        auto iter                                                = groupDataProvider->IterateGroupKeys(fabricIndex);
+        chip::Credentials::GroupDataProvider::GroupKey groupKey;
+        while (iter->Next(groupKey))
+        {
+            if (groupKey.group_id == groupId)
+            {
+                keysetId = groupKey.keyset_id;
+                iter->Release();
+                return true;
+            }
+        }
+        iter->Release();
+        return false;
+    }
+
+    CHIP_ERROR RunCommand() override
+    {
+        fprintf(stderr, "\n");
+        fprintf(stderr, "  +-------------------------------------------------------------------------------------+\n");
+        fprintf(stderr, "  | Available Groups :                                                                  |\n");
+        fprintf(stderr, "  +-------------------------------------------------------------------------------------+\n");
+        fprintf(stderr, "  | Group Id   |  KeySet Id     |   Group Name                                          |\n");
+        chip::FabricIndex fabricIndex;
+        CurrentCommissioner().GetFabricIndex(&fabricIndex);
+        chip::Credentials::GroupDataProvider * groupDataProvider = chip::Credentials::GetGroupDataProvider();
+        auto it                                                  = groupDataProvider->IterateGroupInfo(fabricIndex);
+        chip::Credentials::GroupDataProvider::GroupInfo group;
+        if (it)
+        {
+            while (it->Next(group))
+            {
+                chip::KeysetId keysetId;
+                if (FindKeySetId(fabricIndex, group.group_id, keysetId))
+                {
+                    fprintf(stderr, "  | 0x%-12x  0x%-13x  %-50s |\n", group.group_id, keysetId, group.name);
+                }
+                else
+                {
+                    fprintf(stderr, "  | 0x%-12x  %-15s  %-50s |\n", group.group_id, "None", group.name);
+                }
+            }
+            it->Release();
+        }
+        fprintf(stderr, "  +-------------------------------------------------------------------------------------+\n");
+        SetCommandExitStatus(CHIP_NO_ERROR);
+        return CHIP_NO_ERROR;
+    }
+};
+
+class AddGroup : public CHIPCommand
+{
+public:
+    AddGroup(CredentialIssuerCommands * credsIssuerConfig) : CHIPCommand("add-group", credsIssuerConfig)
+    {
+        AddArgument("groupName", &groupName);
+        AddArgument("groupId", chip::kUndefinedGroupId, UINT16_MAX, &groupId);
+    }
+    chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(20); }
+
+    CHIP_ERROR RunCommand() override
+    {
+        if (strlen(groupName) > CHIP_CONFIG_MAX_GROUP_NAME_LENGTH || groupId == chip::kUndefinedGroupId)
+        {
+            return CHIP_ERROR_INVALID_ARGUMENT;
+        }
+
+        chip::FabricIndex fabricIndex;
+        CurrentCommissioner().GetFabricIndex(&fabricIndex);
+        chip::Credentials::GroupDataProvider * groupDataProvider = chip::Credentials::GetGroupDataProvider();
+        chip::Credentials::GroupDataProvider::GroupInfo group;
+
+        group.SetName(groupName);
+        group.group_id = groupId;
+        ReturnErrorOnFailure(groupDataProvider->SetGroupInfo(fabricIndex, group));
+
+        SetCommandExitStatus(CHIP_NO_ERROR);
+        return CHIP_NO_ERROR;
+    }
+
+private:
+    char * groupName;
+    chip::GroupId groupId;
+};
+
+class RemoveGroup : public CHIPCommand
+{
+public:
+    RemoveGroup(CredentialIssuerCommands * credsIssuerConfig) : CHIPCommand("remove-group", credsIssuerConfig)
+    {
+        AddArgument("groupId", chip::kUndefinedGroupId, UINT16_MAX, &groupId);
+    }
+    chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(5); }
+
+    CHIP_ERROR RunCommand() override
+    {
+        if (groupId == chip::kUndefinedGroupId)
+        {
+            ChipLogError(chipTool, "Invalid group Id : 0x%x", groupId);
+            return CHIP_ERROR_INVALID_ARGUMENT;
+        }
+
+        chip::FabricIndex fabricIndex;
+        CurrentCommissioner().GetFabricIndex(&fabricIndex);
+        chip::Credentials::GroupDataProvider * groupDataProvider = chip::Credentials::GetGroupDataProvider();
+        ReturnErrorOnFailure(groupDataProvider->RemoveGroupInfo(fabricIndex, groupId));
+        SetCommandExitStatus(CHIP_NO_ERROR);
+        return CHIP_NO_ERROR;
+    }
+
+private:
+    chip::GroupId groupId;
+};
+
+class ShowKeySets : public CHIPCommand
+{
+public:
+    ShowKeySets(CredentialIssuerCommands * credsIssuerConfig) : CHIPCommand("show-keysets", credsIssuerConfig) {}
+    chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(5); }
+
+    CHIP_ERROR RunCommand() override
+    {
+        chip::FabricIndex fabricIndex;
+        CurrentCommissioner().GetFabricIndex(&fabricIndex);
+        chip::Credentials::GroupDataProvider * groupDataProvider = chip::Credentials::GetGroupDataProvider();
+        chip::Credentials::GroupDataProvider::KeySet keySet;
+
+        fprintf(stderr, "\n");
+        fprintf(stderr, "  +-------------------------------------------------------------------------------------+\n");
+        fprintf(stderr, "  | Available KeySets :                                                                 |\n");
+        fprintf(stderr, "  +-------------------------------------------------------------------------------------+\n");
+        fprintf(stderr, "  | KeySet Id   |   Key Policy                                                          |\n");
+
+        auto it = groupDataProvider->IterateKeySets(fabricIndex);
+        if (it)
+        {
+            while (it->Next(keySet))
+            {
+                fprintf(stderr, "  | 0x%-12x  %-66s  |\n", keySet.keyset_id,
+                        (keySet.policy == chip::Credentials::GroupDataProvider::SecurityPolicy::kCacheAndSync) ? "Cache and Sync"
+                                                                                                               : "Trust First");
+            }
+            it->Release();
+        }
+        fprintf(stderr, "  +-------------------------------------------------------------------------------------+\n");
+        SetCommandExitStatus(CHIP_NO_ERROR);
+        return CHIP_NO_ERROR;
+    }
+};
+
+class BindKeySet : public CHIPCommand
+{
+public:
+    BindKeySet(CredentialIssuerCommands * credsIssuerConfig) : CHIPCommand("bind-keyset", credsIssuerConfig)
+    {
+        AddArgument("groupId", chip::kUndefinedGroupId, UINT16_MAX, &groupId);
+        AddArgument("keysetId", 0, UINT16_MAX, &keysetId);
+    }
+    chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(5); }
+
+    CHIP_ERROR RunCommand() override
+    {
+        size_t current_count = 0;
+        chip::FabricIndex fabricIndex;
+        CurrentCommissioner().GetFabricIndex(&fabricIndex);
+        chip::Credentials::GroupDataProvider * groupDataProvider = chip::Credentials::GetGroupDataProvider();
+
+        auto iter     = groupDataProvider->IterateGroupKeys(fabricIndex);
+        current_count = iter->Count();
+        iter->Release();
+
+        ReturnErrorOnFailure(groupDataProvider->SetGroupKeyAt(fabricIndex, current_count,
+                                                              chip::Credentials::GroupDataProvider::GroupKey(groupId, keysetId)));
+
+        SetCommandExitStatus(CHIP_NO_ERROR);
+        return CHIP_NO_ERROR;
+    }
+
+private:
+    chip::GroupId groupId;
+    chip::KeysetId keysetId;
+};
+
+class UnbindKeySet : public CHIPCommand
+{
+public:
+    UnbindKeySet(CredentialIssuerCommands * credsIssuerConfig) : CHIPCommand("unbind-keyset", credsIssuerConfig)
+    {
+        AddArgument("groupId", chip::kUndefinedGroupId, UINT16_MAX, &groupId);
+        AddArgument("keysetId", 0, UINT16_MAX, &keysetId);
+    }
+    chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(5); }
+
+    CHIP_ERROR RunCommand() override
+    {
+        size_t index = 0;
+        chip::FabricIndex fabricIndex;
+        CurrentCommissioner().GetFabricIndex(&fabricIndex);
+        chip::Credentials::GroupDataProvider * groupDataProvider = chip::Credentials::GetGroupDataProvider();
+        auto iter                                                = groupDataProvider->IterateGroupKeys(fabricIndex);
+        size_t maxCount                                          = iter->Count();
+        chip::Credentials::GroupDataProvider::GroupKey groupKey;
+        while (iter->Next(groupKey))
+        {
+            if (groupKey.group_id == groupId && groupKey.keyset_id == keysetId)
+            {
+                break;
+            }
+            index++;
+        }
+        iter->Release();
+        if (index >= maxCount)
+        {
+            return CHIP_ERROR_INTERNAL;
+        }
+
+        ReturnErrorOnFailure(groupDataProvider->RemoveGroupKeyAt(fabricIndex, index));
+
+        SetCommandExitStatus(CHIP_NO_ERROR);
+        return CHIP_NO_ERROR;
+    }
+
+private:
+    chip::GroupId groupId;
+    chip::KeysetId keysetId;
+};
+
+class AddKeySet : public CHIPCommand
+{
+public:
+    AddKeySet(CredentialIssuerCommands * credsIssuerConfig) : CHIPCommand("add-keysets", credsIssuerConfig)
+    {
+        AddArgument("keysetId", 0, UINT16_MAX, &keysetId);
+        AddArgument("keyPolicy", 0, UINT16_MAX, &keyPolicy);
+        AddArgument("validityTime", 0, UINT64_MAX, &validityTime);
+        AddArgument("EpochKey", &epochKey);
+    }
+    chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(5); }
+
+    CHIP_ERROR RunCommand() override
+    {
+        chip::FabricIndex fabricIndex;
+        CurrentCommissioner().GetFabricIndex(&fabricIndex);
+        chip::Credentials::GroupDataProvider * groupDataProvider = chip::Credentials::GetGroupDataProvider();
+        uint8_t compressed_fabric_id[sizeof(uint64_t)];
+        chip::MutableByteSpan compressed_fabric_id_span(compressed_fabric_id);
+        ReturnLogErrorOnFailure(CurrentCommissioner().GetFabricInfo()->GetCompressedId(compressed_fabric_id_span));
+
+        if ((keyPolicy != chip::Credentials::GroupDataProvider::SecurityPolicy::kCacheAndSync &&
+             keyPolicy != chip::Credentials::GroupDataProvider::SecurityPolicy::kTrustFirst) ||
+            (epochKey.size()) != chip::Credentials::GroupDataProvider::EpochKey::kLengthBytes)
+        {
+            return CHIP_ERROR_INVALID_ARGUMENT;
+        }
+
+        chip::Credentials::GroupDataProvider::KeySet keySet(keysetId, keyPolicy, 1);
+        chip::Credentials::GroupDataProvider::EpochKey epoch_key;
+        epoch_key.start_time = validityTime;
+        memcpy(epoch_key.key, epochKey.data(), chip::Credentials::GroupDataProvider::EpochKey::kLengthBytes);
+
+        memcpy(keySet.epoch_keys, &epoch_key, sizeof(chip::Credentials::GroupDataProvider::EpochKey));
+        ReturnErrorOnFailure(groupDataProvider->SetKeySet(fabricIndex, compressed_fabric_id_span, keySet));
+
+        SetCommandExitStatus(CHIP_NO_ERROR);
+        return CHIP_NO_ERROR;
+    }
+
+private:
+    chip::KeysetId keysetId;
+    chip::Credentials::GroupDataProvider::SecurityPolicy keyPolicy;
+    uint64_t validityTime;
+    chip::ByteSpan epochKey;
+};
+
+class RemoveKeySet : public CHIPCommand
+{
+public:
+    RemoveKeySet(CredentialIssuerCommands * credsIssuerConfig) : CHIPCommand("remove-keyset", credsIssuerConfig)
+    {
+        AddArgument("keysetId", 0, UINT16_MAX, &keysetId);
+    }
+    chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(5); }
+
+    CHIP_ERROR RunCommand() override
+    {
+        CHIP_ERROR err = CHIP_NO_ERROR;
+        chip::FabricIndex fabricIndex;
+        CurrentCommissioner().GetFabricIndex(&fabricIndex);
+        chip::Credentials::GroupDataProvider * groupDataProvider = chip::Credentials::GetGroupDataProvider();
+
+        // Unbind all group
+        size_t index = 0;
+        auto iter    = groupDataProvider->IterateGroupKeys(fabricIndex);
+        chip::Credentials::GroupDataProvider::GroupKey groupKey;
+        while (iter->Next(groupKey))
+        {
+            if (groupKey.keyset_id == keysetId)
+            {
+                err = groupDataProvider->RemoveGroupKeyAt(fabricIndex, index);
+                if (err != CHIP_NO_ERROR)
+                {
+                    break;
+                }
+            }
+            index++;
+        }
+        iter->Release();
+
+        if (err == CHIP_NO_ERROR)
+        {
+            return err;
+        }
+        ReturnErrorOnFailure(groupDataProvider->RemoveKeySet(fabricIndex, keysetId));
+
+        SetCommandExitStatus(CHIP_NO_ERROR);
+        return CHIP_NO_ERROR;
+    }
+
+private:
+    chip::KeysetId keysetId;
+};
+
+void registerCommandsGroup(Commands & commands, CredentialIssuerCommands * credsIssuerConfig)
+{
+    const char * clusterName = "GroupSettings";
+
+    commands_list clusterCommands = {
+        make_unique<ShowControllerGroups>(credsIssuerConfig),
+        make_unique<AddGroup>(credsIssuerConfig),
+        make_unique<RemoveGroup>(credsIssuerConfig),
+        make_unique<ShowKeySets>(credsIssuerConfig),
+        make_unique<BindKeySet>(credsIssuerConfig),
+        make_unique<UnbindKeySet>(credsIssuerConfig),
+        make_unique<AddKeySet>(credsIssuerConfig),
+        make_unique<RemoveKeySet>(credsIssuerConfig),
+    };
+
+    commands.Register(clusterName, clusterCommands);
+}
diff --git a/examples/chip-tool/main.cpp b/examples/chip-tool/main.cpp
index 88d7adff4e800b..10e6b7440fb09c 100644
--- a/examples/chip-tool/main.cpp
+++ b/examples/chip-tool/main.cpp
@@ -20,6 +20,7 @@
 #include "commands/example/ExampleCredentialIssuerCommands.h"
 
 #include "commands/discover/Commands.h"
+#include "commands/group/Commands.h"
 #include "commands/pairing/Commands.h"
 #include "commands/payload/Commands.h"
 
@@ -37,6 +38,7 @@ int main(int argc, char * argv[])
     registerCommandsPayload(commands);
     registerCommandsPairing(commands, &credIssuerCommands);
     registerCommandsTests(commands, &credIssuerCommands);
+    registerCommandsGroup(commands, &credIssuerCommands);
     registerClusters(commands, &credIssuerCommands);
 
     return commands.Run(argc, argv);
diff --git a/src/lib/support/TestGroupData.h b/src/lib/support/TestGroupData.h
index bc80bac9ee84a8..dc611b4697609b 100644
--- a/src/lib/support/TestGroupData.h
+++ b/src/lib/support/TestGroupData.h
@@ -48,6 +48,14 @@ CHIP_ERROR InitProvider()
     return CHIP_NO_ERROR;
 }
 
+CHIP_ERROR InitProvider(chip::PersistentStorageDelegate & storageDelegate)
+{
+    sGroupsProvider.SetStorageDelegate(&storageDelegate);
+    ReturnErrorOnFailure(sGroupsProvider.Init());
+    chip::Credentials::SetGroupDataProvider(&sGroupsProvider);
+    return CHIP_NO_ERROR;
+}
+
 CHIP_ERROR InitData(chip::FabricIndex fabric_index, const ByteSpan & compressed_fabric_id)
 {
     // Groups