| /* |
| * Copyright (c) 2025 Project CHIP Authors |
| * |
| * 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. |
| */ |
| #include <lib/support/tests/ExtraPwTestMacros.h> |
| #include <pw_unit_test/framework.h> |
| |
| #include <access/AccessControl.h> |
| #include <app/MessageDef/CommandDataIB.h> |
| #include <app/clusters/groupcast/GroupcastCluster.h> |
| #include <app/data-model-provider/MetadataTypes.h> |
| #include <app/server-cluster/DefaultServerCluster.h> |
| #include <app/server-cluster/testing/AttributeTesting.h> |
| #include <app/server-cluster/testing/ClusterTester.h> |
| #include <app/server-cluster/testing/MockCommandHandler.h> |
| #include <app/server-cluster/testing/TestServerClusterContext.h> |
| #include <app/server-cluster/testing/ValidateGlobalAttributes.h> |
| #include <app/util/mock/Constants.h> |
| #include <app/util/mock/Functions.h> |
| #include <clusters/Groupcast/Enums.h> |
| #include <clusters/Groupcast/Metadata.h> |
| #include <credentials/GroupDataProviderImpl.h> |
| #include <lib/core/CHIPError.h> |
| #include <lib/core/DataModelTypes.h> |
| #include <lib/support/BitFlags.h> |
| #include <lib/support/CodeUtils.h> |
| #include <lib/support/ReadOnlyBuffer.h> |
| #include <lib/support/TimerDelegateMock.h> |
| #include <platform/NetworkCommissioning.h> |
| |
| #include <array> |
| #include <credentials/GroupDataProviderImpl.h> |
| #include <crypto/DefaultSessionKeystore.h> |
| #include <lib/support/TestPersistentStorageDelegate.h> |
| #include <set> |
| |
| namespace { |
| |
| using namespace chip; |
| using namespace chip::app; |
| using namespace chip::Access; |
| using namespace chip::Testing; |
| using namespace chip::Credentials; |
| using namespace chip::app::Clusters::Groupcast; |
| using namespace chip::System::Clock::Literals; |
| using namespace chip::Protocols::InteractionModel; |
| using chip::Testing::IsAcceptedCommandsListEqualTo; |
| using chip::Testing::IsAttributesListEqualTo; |
| |
| using chip::app::DataModel::AcceptedCommandEntry; |
| using chip::app::DataModel::AttributeEntry; |
| static constexpr size_t kMaxMembershipEndpoints = app::Clusters::GroupcastCluster::kMaxMembershipEndpoints; |
| |
| static constexpr FabricIndex kTestFabricIndex = Testing::kTestFabricIndex; |
| static constexpr NodeId kNodeIdForAdmin = 1ULL; |
| static constexpr NodeId kNodeIdForManage = 2ULL; |
| |
| static constexpr SubjectDescriptor kAdminSubjectDescriptor{ .fabricIndex = kTestFabricIndex, |
| .authMode = AuthMode::kCase, |
| .subject = kNodeIdForAdmin }; |
| |
| static constexpr SubjectDescriptor kManageSubjectDescriptor{ .fabricIndex = kTestFabricIndex, |
| .authMode = AuthMode::kCase, |
| .subject = kNodeIdForManage }; |
| |
| template <typename DecodableListType> |
| CHIP_ERROR CountListElements(DecodableListType & list, size_t & count) |
| { |
| count = 0; |
| auto it = list.begin(); |
| while (it.Next()) |
| { |
| ++count; |
| } |
| // Return the iterator status to the caller for assertion |
| return it.GetStatus(); |
| } |
| |
| class CustomDataModel : public EmptyProvider |
| { |
| public: |
| // Override of the EmptyProvider to mock a large Endpoint list in the data model that will be used in the following tests. |
| CHIP_ERROR Endpoints(ReadOnlyBufferBuilder<DataModel::EndpointEntry> & builder) override |
| { |
| static constexpr size_t kEndpointCount = 300; |
| static const std::array<DataModel::EndpointEntry, kEndpointCount> kEndpoints = []() { |
| std::array<DataModel::EndpointEntry, kEndpointCount> endpoints; |
| |
| for (size_t i = 0; i < kEndpointCount; i++) |
| { |
| endpoints[i] = DataModel::EndpointEntry{ |
| .id = static_cast<EndpointId>(i + 1), |
| .parentId = kInvalidEndpointId, |
| .compositionPattern = DataModel::EndpointCompositionPattern::kTree, |
| }; |
| } |
| |
| return endpoints; |
| }(); |
| |
| return builder.ReferenceExisting(Span<const DataModel::EndpointEntry>(kEndpoints.data(), kEndpoints.size())); |
| } |
| }; |
| |
| // initialize memory as ReadOnlyBufferBuilder may allocate |
| struct TestGroupcastCluster : public ::testing::Test |
| { |
| static void SetUpTestSuite() { ASSERT_EQ(Platform::MemoryInit(), CHIP_NO_ERROR); } |
| |
| static void TearDownTestSuite() { Platform::MemoryShutdown(); } |
| |
| class TestDeviceTypeResolver : public Access::AccessControl::DeviceTypeResolver |
| { |
| public: |
| bool IsDeviceTypeOnEndpoint(chip::DeviceTypeId deviceType, chip::EndpointId endpoint) override |
| { |
| (void) deviceType; |
| (void) endpoint; |
| return false; |
| } |
| }; |
| |
| class GroupcastTestingAccessControlDelegate : public Access::AccessControl::Delegate |
| { |
| CHIP_ERROR Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath, |
| Privilege requestPrivilege) override |
| { |
| if (subjectDescriptor.authMode != AuthMode::kCase) |
| { |
| return CHIP_ERROR_ACCESS_DENIED; |
| } |
| |
| switch (subjectDescriptor.subject) |
| { |
| case kNodeIdForAdmin: |
| // Admin always has access |
| return CHIP_NO_ERROR; |
| case kNodeIdForManage: { |
| // Manage can access everything but Administer-level things |
| CHIP_ERROR err = (requestPrivilege == Access::Privilege::kAdminister) ? CHIP_ERROR_ACCESS_DENIED : CHIP_NO_ERROR; |
| return err; |
| } |
| default: |
| return CHIP_ERROR_ACCESS_DENIED; |
| } |
| } |
| }; |
| |
| void SetUp() override |
| { |
| mProvider.SetStorageDelegate(&mTestContext.StorageDelegate()); |
| mProvider.SetSessionKeystore(&mKeystore); |
| mProvider.SetGroupcastEnabled(true); |
| ASSERT_EQ(mProvider.Init(), CHIP_NO_ERROR); |
| ASSERT_EQ(mAccessDelegate.Init(), CHIP_NO_ERROR); |
| ASSERT_EQ(mAccessControl.Init(&mAccessDelegate, mDeviceTypeResolver), CHIP_NO_ERROR); |
| |
| // Replace the DataModel Provider in the ServerClusterContext provided to the cluster implementation |
| // with our Mock DataModel Provider so we can test endpoints validations on JoinGroup command. |
| ServerClusterContext context = mTestContext.Get(); |
| clusterContext = std::make_unique<ServerClusterContext>(ServerClusterContext{ |
| .provider = customDataModel, |
| .storage = context.storage, |
| .attributeStorage = context.attributeStorage, |
| .interactionContext = context.interactionContext, |
| }); |
| |
| ASSERT_EQ(mSender.Startup(*clusterContext), CHIP_NO_ERROR); |
| ASSERT_EQ(mListener.Startup(*clusterContext), CHIP_NO_ERROR); |
| |
| CHIP_ERROR err = mFabricHelper.SetUpTestFabric(mFabricIndex); |
| ASSERT_EQ(err, CHIP_NO_ERROR); |
| ASSERT_EQ(mFabricIndex, kTestFabricIndex); |
| Credentials::SetGroupDataProvider(&mProvider); |
| } |
| |
| void TearDown() override |
| { |
| mAccessDelegate.Finish(); |
| mAccessControl.Finish(); |
| mSender.Shutdown(app::ClusterShutdownType::kClusterShutdown); |
| mListener.Shutdown(app::ClusterShutdownType::kClusterShutdown); |
| clusterContext.reset(); |
| Credentials::SetGroupDataProvider(nullptr); |
| CHIP_ERROR err = mFabricHelper.TearDownTestFabric(mFabricIndex); |
| ASSERT_EQ(err, CHIP_NO_ERROR); |
| mProvider.Finish(); |
| } |
| |
| void AssertStatus(std::optional<app::DataModel::ActionReturnStatus> & status, |
| const Protocols::InteractionModel::Status expected) |
| { |
| ASSERT_TRUE(status.has_value()); |
| EXPECT_EQ(status->GetStatusCode().GetStatus(), expected); |
| } |
| |
| FabricIndex mFabricIndex = kTestFabricIndex; |
| TestServerClusterContext mTestContext; |
| TestPersistentStorageDelegate mStorageDelegate; |
| GroupcastTestingAccessControlDelegate mAccessDelegate; |
| TestDeviceTypeResolver mDeviceTypeResolver; |
| Access::AccessControl mAccessControl; |
| Credentials::GroupDataProviderImpl mProvider; |
| TimerDelegateMock mMockTimerDelegate; |
| Crypto::DefaultSessionKeystore mKeystore; |
| CustomDataModel customDataModel; |
| std::unique_ptr<ServerClusterContext> clusterContext; |
| FabricTestFixture mFabricHelper{ &mTestContext.StorageDelegate() }; |
| ScopedAttributeChangeListenerRegistration mScopedListenerRegistration; // RAII registration |
| |
| chip::Groupcast::Testing mTesting; |
| app::Clusters::GroupcastCluster mSender{ { mFabricHelper.GetFabricTable(), mProvider, mMockTimerDelegate, mAccessControl, |
| mTesting }, |
| BitFlags<Feature>{ Feature::kSender } }; |
| app::Clusters::GroupcastCluster mListener{ { mFabricHelper.GetFabricTable(), mProvider, mMockTimerDelegate, mAccessControl, |
| mTesting }, |
| BitFlags<Feature>{ Feature::kListener, Feature::kPerGroup } }; |
| |
| TestGroupcastCluster() : mScopedListenerRegistration(customDataModel, mTestContext) {} |
| }; |
| |
| TEST_F(TestGroupcastCluster, TestAttributes) |
| { |
| // Attributes |
| { |
| ASSERT_TRUE(IsAttributesListEqualTo(mSender, |
| { |
| Attributes::Membership::kMetadataEntry, |
| Attributes::MaxMembershipCount::kMetadataEntry, |
| Attributes::MaxMcastAddrCount::kMetadataEntry, |
| Attributes::UsedMcastAddrCount::kMetadataEntry, |
| Attributes::FabricUnderTest::kMetadataEntry, |
| })); |
| } |
| |
| // Read attributes for expected values |
| { |
| chip::Testing::ClusterTester tester(mSender); |
| uint16_t revision{}; |
| ASSERT_EQ(tester.ReadAttribute(Attributes::ClusterRevision::Id, revision), CHIP_NO_ERROR); |
| ASSERT_EQ(revision, app::Clusters::Groupcast::kRevision); |
| |
| // Validate Constructor sets features correctly and is readable from attribute |
| uint32_t features{}; |
| ASSERT_EQ(tester.ReadAttribute(Attributes::FeatureMap::Id, features), CHIP_NO_ERROR); |
| ASSERT_EQ(features, to_underlying(Feature::kSender)); |
| } |
| } |
| |
| void ValidateMembership(const Attributes::Membership::TypeInfo::DecodableType & memberships, |
| const Clusters::Groupcast::Structs::MembershipStruct::Type * expectedMemberships, |
| size_t expectedMembershipsCount) |
| { |
| size_t membershipCount = 0; |
| ASSERT_EQ(CountListElements(memberships, membershipCount), CHIP_NO_ERROR); |
| ASSERT_EQ(membershipCount, expectedMembershipsCount); |
| |
| size_t index = 0; |
| auto iterMembership = memberships.begin(); |
| while (iterMembership.Next() && (index < expectedMembershipsCount)) |
| { |
| auto membership = iterMembership.GetValue(); |
| ASSERT_EQ(membership.groupID, expectedMemberships[index].groupID); |
| ASSERT_EQ(membership.keySetID, expectedMemberships[index].keySetID); |
| ASSERT_EQ(membership.mcastAddrPolicy, expectedMemberships[index].mcastAddrPolicy); |
| ASSERT_EQ(membership.hasAuxiliaryACL.HasValue(), expectedMemberships[index].hasAuxiliaryACL.HasValue()); |
| if (expectedMemberships[index].hasAuxiliaryACL.HasValue()) |
| { |
| ASSERT_EQ(membership.hasAuxiliaryACL, expectedMemberships[index].hasAuxiliaryACL); |
| } |
| |
| ASSERT_EQ(membership.endpoints.HasValue(), expectedMemberships[index].endpoints.HasValue()); |
| if (membership.endpoints.HasValue()) |
| { |
| size_t endpoint_count = 0; |
| ASSERT_EQ(membership.endpoints.Value().ComputeSize(&endpoint_count), CHIP_NO_ERROR); |
| ASSERT_EQ(endpoint_count, expectedMemberships[index].endpoints.Value().size()); |
| // Build set from expected endpoints |
| std::set<EndpointId> expectedEndpoints; |
| for (size_t i = 0; i < expectedMemberships[index].endpoints.Value().size(); i++) |
| { |
| expectedEndpoints.insert(expectedMemberships[index].endpoints.Value()[i]); |
| } |
| // Check each actual endpoint is in the expected set |
| auto iterEndpoints = membership.endpoints.Value().begin(); |
| while (iterEndpoints.Next()) |
| { |
| auto endpoint = iterEndpoints.GetValue(); |
| ASSERT_NE(expectedEndpoints.find(endpoint), expectedEndpoints.end()); |
| } |
| } |
| index++; |
| } |
| ASSERT_EQ(index, membershipCount); |
| } |
| |
| TEST_F(TestGroupcastCluster, TestAcceptedCommands) |
| { |
| ASSERT_TRUE(IsAcceptedCommandsListEqualTo(mListener, |
| { |
| Commands::JoinGroup::kMetadataEntry, |
| Commands::LeaveGroup::kMetadataEntry, |
| Commands::UpdateGroupKey::kMetadataEntry, |
| Commands::ConfigureAuxiliaryACL::kMetadataEntry, |
| Commands::GroupcastTesting::kMetadataEntry, |
| })); |
| } |
| |
| TEST_F(TestGroupcastCluster, TestJoinGroupFailsOnBadAargs) |
| { |
| GroupId kGroup1 = 0xab01; |
| KeysetId kKeyset1 = 0xabcd; |
| const uint8_t key[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; |
| const EndpointId kEndpoints[1] = { 1 }; |
| |
| chip::Testing::ClusterTester tester(mListener); |
| tester.SetFabricIndex(kTestFabricIndex); |
| tester.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| // Admin privilege, and valid arguments (including key) : JoinGroup should work. |
| { |
| Commands::JoinGroup::Type joinGroupCmd; |
| joinGroupCmd.groupID = kGroup1; |
| joinGroupCmd.keySetID = kKeyset1; |
| joinGroupCmd.key = MakeOptional(ByteSpan(key)); |
| joinGroupCmd.useAuxiliaryACL = NullOptional; |
| joinGroupCmd.mcastAddrPolicy = MakeOptional(app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr); |
| joinGroupCmd.endpoints = DataModel::List<const EndpointId>(kEndpoints, 1); |
| |
| auto result = tester.Invoke(Commands::JoinGroup::Id, joinGroupCmd); |
| ASSERT_TRUE(result.GetStatusCode().has_value()); |
| EXPECT_EQ(result.GetStatusCode()->GetStatus(), Status::Success); |
| } |
| |
| // Now group1 is OK and keys OK |
| |
| // Constraint on groupId >= 1 must be respected. |
| { |
| Commands::JoinGroup::Type joinGroupCmd; |
| joinGroupCmd.groupID = 0; // Disallowed |
| joinGroupCmd.keySetID = kKeyset1; |
| joinGroupCmd.key = MakeOptional(ByteSpan(key)); |
| joinGroupCmd.useAuxiliaryACL = NullOptional; |
| joinGroupCmd.mcastAddrPolicy = MakeOptional(app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr); |
| joinGroupCmd.endpoints = DataModel::List<const EndpointId>(kEndpoints, 1); |
| |
| auto result = tester.Invoke(Commands::JoinGroup::Id, joinGroupCmd); |
| ASSERT_TRUE(result.GetStatusCode().has_value()); |
| EXPECT_EQ(result.GetStatusCode()->GetStatus(), Status::ConstraintError); |
| } |
| |
| // Constraint on keySetId >= 1 must be respected. |
| { |
| Commands::JoinGroup::Type joinGroupCmd; |
| joinGroupCmd.groupID = kGroup1; |
| joinGroupCmd.keySetID = 0; // Disallowed |
| joinGroupCmd.key = MakeOptional(ByteSpan(key)); |
| joinGroupCmd.useAuxiliaryACL = NullOptional; |
| joinGroupCmd.mcastAddrPolicy = MakeOptional(app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr); |
| joinGroupCmd.endpoints = DataModel::List<const EndpointId>(kEndpoints, 1); |
| |
| auto result = tester.Invoke(Commands::JoinGroup::Id, joinGroupCmd); |
| ASSERT_TRUE(result.GetStatusCode().has_value()); |
| EXPECT_EQ(result.GetStatusCode()->GetStatus(), Status::ConstraintError); |
| } |
| |
| // Manage access: can't touch keys or useAuxiliary ACL |
| tester.SetSubjectDescriptor(kManageSubjectDescriptor); |
| |
| // Enabling useAuxiliaryACL should fail unless subject has administer privileges. |
| { |
| Commands::JoinGroup::Type joinGroupCmd; |
| joinGroupCmd.groupID = kGroup1; |
| joinGroupCmd.keySetID = kKeyset1; |
| joinGroupCmd.key = NullOptional; // <-- Not touching key |
| joinGroupCmd.useAuxiliaryACL = NullOptional; // <-- Not touching useAuxACL |
| joinGroupCmd.mcastAddrPolicy = MakeOptional(app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr); |
| joinGroupCmd.endpoints = DataModel::List<const EndpointId>(kEndpoints, 1); |
| |
| // Manage should work here |
| auto result = tester.Invoke(Commands::JoinGroup::Id, joinGroupCmd); |
| ASSERT_TRUE(result.GetStatusCode().has_value()); |
| EXPECT_EQ(result.GetStatusCode()->GetStatus(), Status::Success); |
| |
| // Touching useAuxiliaryACL, even if false should fail if present --> cannot update the field. |
| joinGroupCmd.useAuxiliaryACL = MakeOptional(false); |
| result = tester.Invoke(Commands::JoinGroup::Id, joinGroupCmd); |
| ASSERT_TRUE(result.GetStatusCode().has_value()); |
| EXPECT_EQ(result.GetStatusCode()->GetStatus(), Status::UnsupportedAccess); |
| |
| joinGroupCmd.useAuxiliaryACL = MakeOptional(true); |
| result = tester.Invoke(Commands::JoinGroup::Id, joinGroupCmd); |
| ASSERT_TRUE(result.GetStatusCode().has_value()); |
| EXPECT_EQ(result.GetStatusCode()->GetStatus(), Status::UnsupportedAccess); |
| |
| // Starts working after no longer touching AuxACL. |
| joinGroupCmd.key = NullOptional; // <-- Not touching key |
| joinGroupCmd.useAuxiliaryACL = NullOptional; // <-- Not touching useAuxACL |
| result = tester.Invoke(Commands::JoinGroup::Id, joinGroupCmd); |
| ASSERT_TRUE(result.GetStatusCode().has_value()); |
| EXPECT_EQ(result.GetStatusCode()->GetStatus(), Status::Success); |
| |
| // If touching key, but not useAuxiliaryACL, still not legal. |
| |
| joinGroupCmd.key = MakeOptional(ByteSpan(key)); // <-- Touching key |
| joinGroupCmd.useAuxiliaryACL = NullOptional; // <-- Not touching useAuxACL |
| result = tester.Invoke(Commands::JoinGroup::Id, joinGroupCmd); |
| ASSERT_TRUE(result.GetStatusCode().has_value()); |
| EXPECT_EQ(result.GetStatusCode()->GetStatus(), Status::UnsupportedAccess); |
| } |
| } |
| |
| TEST_F(TestGroupcastCluster, TestUpdateGroupKeyFailsOnBadAargs) |
| { |
| GroupId kGroup1 = 0xab01; |
| KeysetId kKeyset1 = 0xabcd; |
| KeysetId kKeyset2 = 0xa123; |
| const uint8_t key1[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; |
| const uint8_t key2[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; |
| const EndpointId kEndpoints[1] = { 1 }; |
| |
| chip::Testing::ClusterTester tester(mListener); |
| tester.SetFabricIndex(kTestFabricIndex); |
| tester.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| Commands::JoinGroup::Type joinGroupCmd; |
| joinGroupCmd.groupID = kGroup1; |
| joinGroupCmd.keySetID = kKeyset1; |
| joinGroupCmd.key = MakeOptional(ByteSpan(key1)); |
| joinGroupCmd.useAuxiliaryACL = MakeOptional(false); |
| joinGroupCmd.mcastAddrPolicy = MakeOptional(app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr); |
| joinGroupCmd.endpoints = DataModel::List<const EndpointId>(kEndpoints, 1); |
| |
| // Join must work |
| auto result = tester.Invoke(Commands::JoinGroup::Id, joinGroupCmd); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| |
| Commands::UpdateGroupKey::Type updateGroupKeyCmd; |
| updateGroupKeyCmd.groupID = kGroup1; |
| updateGroupKeyCmd.keySetID = kKeyset1; |
| |
| // UpdateGroupKey from Admin access should succeed (no key update). |
| result = tester.Invoke(Commands::UpdateGroupKey::Id, updateGroupKeyCmd); |
| ASSERT_TRUE(result.GetStatusCode().has_value()); |
| EXPECT_EQ(result.GetStatusCode()->GetStatus(), Status::Success); |
| |
| // If changing a key, should fail with Manage, succeed with Admin |
| tester.SetSubjectDescriptor(kManageSubjectDescriptor); |
| |
| updateGroupKeyCmd.groupID = kGroup1; |
| updateGroupKeyCmd.keySetID = kKeyset2; |
| updateGroupKeyCmd.key = MakeOptional(ByteSpan(key2)); |
| |
| result = tester.Invoke(Commands::UpdateGroupKey::Id, updateGroupKeyCmd); |
| ASSERT_TRUE(result.GetStatusCode().has_value()); |
| EXPECT_EQ(result.GetStatusCode()->GetStatus(), Status::UnsupportedAccess); |
| |
| tester.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| result = tester.Invoke(Commands::UpdateGroupKey::Id, updateGroupKeyCmd); |
| ASSERT_TRUE(result.GetStatusCode().has_value()); |
| EXPECT_EQ(result.GetStatusCode()->GetStatus(), Status::Success); |
| |
| // Constraint on groupId >= 1 must be respected. |
| updateGroupKeyCmd.groupID = 0; |
| updateGroupKeyCmd.keySetID = kKeyset1; |
| updateGroupKeyCmd.key = NullOptional; |
| |
| result = tester.Invoke(Commands::UpdateGroupKey::Id, updateGroupKeyCmd); |
| ASSERT_TRUE(result.GetStatusCode().has_value()); |
| EXPECT_EQ(result.GetStatusCode()->GetStatus(), Status::ConstraintError); |
| |
| // Constraint on keySetId >= 1 must be respected. |
| updateGroupKeyCmd.groupID = kGroup1; |
| updateGroupKeyCmd.keySetID = 0; |
| updateGroupKeyCmd.key = NullOptional; |
| |
| result = tester.Invoke(Commands::UpdateGroupKey::Id, updateGroupKeyCmd); |
| ASSERT_TRUE(result.GetStatusCode().has_value()); |
| EXPECT_EQ(result.GetStatusCode()->GetStatus(), Status::ConstraintError); |
| } |
| |
| TEST_F(TestGroupcastCluster, TestReadMembership) |
| { |
| static constexpr uint16_t kMaxEndpoints = app::Clusters::GroupcastCluster::kMaxCommandEndpoints; |
| static constexpr uint16_t kIntervals = 15; |
| static constexpr uint16_t kTotalEndpoints = kMaxEndpoints * kIntervals; |
| const uint8_t key[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; |
| const EndpointId kEndpoints[kIntervals][kMaxEndpoints] = { |
| { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 }, |
| { 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 }, |
| { 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60 }, |
| { 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80 }, |
| { 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100 }, |
| { 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120 }, |
| { 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140 }, |
| { 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160 }, |
| { 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180 }, |
| { 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200 }, |
| { 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220 }, |
| { 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240 }, |
| { 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260 }, |
| { 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280 }, |
| { 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300 } |
| }; |
| GroupId kGroup1 = 0xab01; |
| GroupId kGroup2 = 0xcd02; |
| GroupId kGroup3 = 0xef03; |
| KeysetId kKeyset1 = 0xabcd; |
| KeysetId kKeyset2 = 0xcafe; |
| |
| chip::Testing::ClusterTester tester(mListener); |
| tester.SetFabricIndex(kTestFabricIndex); |
| tester.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| // Join groups |
| { |
| Commands::JoinGroup::Type data; |
| data.groupID = kGroup1; |
| data.keySetID = kKeyset1; |
| data.key = MakeOptional(ByteSpan(key)); |
| data.useAuxiliaryACL = MakeOptional(true); |
| data.mcastAddrPolicy = MakeOptional(app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr); |
| data.endpoints = DataModel::List<const EndpointId>(kEndpoints[0], kMaxEndpoints); |
| |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| data.key.ClearValue(); |
| for (int i = 1; i < kIntervals; i++) |
| { |
| data.endpoints = DataModel::List<const EndpointId>(kEndpoints[i], kMaxEndpoints); |
| result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| } |
| // Join group 2 |
| data.groupID = kGroup2; |
| data.useAuxiliaryACL = MakeOptional(false); |
| data.mcastAddrPolicy = MakeOptional(app::Clusters::Groupcast::MulticastAddrPolicyEnum::kPerGroup); |
| for (int i = 0; i < 2; i++) |
| { |
| data.endpoints = DataModel::List<const EndpointId>(kEndpoints[i], kMaxEndpoints); |
| result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| } |
| |
| // Join group 3 |
| data.groupID = kGroup3; |
| data.keySetID = kKeyset2; |
| data.key = MakeOptional(ByteSpan(key)); |
| data.useAuxiliaryACL = MakeOptional(false); |
| data.mcastAddrPolicy = MakeOptional(app::Clusters::Groupcast::MulticastAddrPolicyEnum::kPerGroup); |
| data.endpoints = DataModel::List<const EndpointId>(kEndpoints[4], 8); |
| result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| } |
| |
| // Remove keyset used by Group 2 |
| EXPECT_EQ(CHIP_NO_ERROR, mProvider.RemoveKeySet(kTestFabricIndex, kKeyset2)); |
| |
| // Read Membership |
| { |
| app::Clusters::Groupcast::Attributes::Membership::TypeInfo::DecodableType memberships; |
| ASSERT_EQ(tester.ReadAttribute(Attributes::Membership::Id, memberships), CHIP_NO_ERROR); |
| |
| // Build expected endpoint arrays |
| // Group1 chunk 1: first kMaxMembershipEndpoints endpoints (intervals 0-12, plus 15 from interval 13 = 255 endpoints) |
| EndpointId group1_chunk1[kMaxMembershipEndpoints]; |
| for (size_t i = 0; i < kMaxMembershipEndpoints; i++) |
| { |
| group1_chunk1[i] = static_cast<EndpointId>(i + 1); |
| } |
| |
| // Group1 chunk 2: remaining 45 endpoints (last 5 from interval 13, plus all 20 from interval 14) |
| EndpointId group1_chunk2[kTotalEndpoints - kMaxMembershipEndpoints]; |
| for (size_t i = 0; i < MATTER_ARRAY_SIZE(group1_chunk2); i++) |
| { |
| group1_chunk2[i] = static_cast<EndpointId>(kMaxMembershipEndpoints + i + 1); |
| } |
| |
| // Group2: 40 endpoints from intervals 1-2 |
| EndpointId group2_endpoints[2 * static_cast<size_t>(kMaxEndpoints)]; |
| for (size_t i = 0; i < 2 * static_cast<size_t>(kMaxEndpoints); i++) |
| { |
| group2_endpoints[i] = static_cast<EndpointId>(i + 1); |
| } |
| |
| // Group3: 8 endpoints from interval 4 |
| EndpointId group3_endpoints[8]; |
| for (size_t i = 0; i < 8; i++) |
| { |
| group3_endpoints[i] = static_cast<EndpointId>(4 * static_cast<size_t>(kMaxEndpoints) + i + 1); |
| } |
| |
| Clusters::Groupcast::Structs::MembershipStruct::Type expectedMembership[] = { |
| { |
| .groupID = kGroup1, |
| .endpoints = MakeOptional(DataModel::List<const EndpointId>(group1_chunk1, kMaxMembershipEndpoints)), |
| .keySetID = kKeyset1, |
| .hasAuxiliaryACL = MakeOptional(true), |
| .mcastAddrPolicy = app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr, |
| }, |
| { |
| .groupID = kGroup1, |
| .endpoints = MakeOptional(DataModel::List<const EndpointId>(group1_chunk2, MATTER_ARRAY_SIZE(group1_chunk2))), |
| .keySetID = kKeyset1, |
| .hasAuxiliaryACL = MakeOptional(true), |
| .mcastAddrPolicy = app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr, |
| }, |
| { |
| .groupID = kGroup2, |
| .endpoints = MakeOptional(DataModel::List<const EndpointId>(group2_endpoints, MATTER_ARRAY_SIZE(group2_endpoints))), |
| .keySetID = kKeyset1, |
| .hasAuxiliaryACL = MakeOptional(false), |
| .mcastAddrPolicy = app::Clusters::Groupcast::MulticastAddrPolicyEnum::kPerGroup, |
| }, |
| { |
| .groupID = kGroup3, |
| .endpoints = MakeOptional(DataModel::List<const EndpointId>(group3_endpoints, MATTER_ARRAY_SIZE(group3_endpoints))), |
| .keySetID = kInvalidKeysetId, |
| .hasAuxiliaryACL = MakeOptional(false), |
| .mcastAddrPolicy = app::Clusters::Groupcast::MulticastAddrPolicyEnum::kPerGroup, |
| } |
| }; |
| |
| ValidateMembership(memberships, expectedMembership, MATTER_ARRAY_SIZE(expectedMembership)); |
| } |
| } |
| |
| TEST_F(TestGroupcastCluster, TestReadUsedMcastAddrCount) |
| { |
| const uint8_t key[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; |
| const EndpointId kEndpoints[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; |
| GroupId kGroup1 = 0xab01; |
| GroupId kGroup2 = 0xcd02; |
| GroupId kGroup3 = 0xef03; |
| GroupId kGroup4 = 0xff04; |
| KeysetId kKeyset = 0xabcd; |
| |
| constexpr System::Clock::Milliseconds32 kChangeTemporisation = System::Clock::Milliseconds32(251); |
| chip::Testing::ClusterTester tester(mListener); |
| tester.SetFabricIndex(kTestFabricIndex); |
| tester.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| app::Clusters::Groupcast::Attributes::UsedMcastAddrCount::TypeInfo::DecodableType multicastAddrCount; |
| app::ConcreteAttributePath membershipAttributePath(kRootEndpointId, app::Clusters::Groupcast::Id, |
| app::Clusters::Groupcast::Attributes::Membership::Id); |
| app::ConcreteAttributePath usedMcastAddrCountAttributePath(kRootEndpointId, app::Clusters::Groupcast::Id, |
| app::Clusters::Groupcast::Attributes::UsedMcastAddrCount::Id); |
| ASSERT_FALSE(mTestContext.ChangeListener().IsDirty(membershipAttributePath)); |
| ASSERT_FALSE(mTestContext.ChangeListener().IsDirty(usedMcastAddrCountAttributePath)); |
| // Read UsedMcastAddrCount |
| ASSERT_EQ(tester.ReadAttribute(app::Clusters::Groupcast::Attributes::UsedMcastAddrCount::Id, multicastAddrCount), |
| CHIP_NO_ERROR); |
| ASSERT_EQ(multicastAddrCount, 0u); |
| |
| // Join groups |
| { |
| // Group 1 (IanaAddr) |
| Commands::JoinGroup::Type data; |
| data.groupID = kGroup1; |
| data.keySetID = kKeyset; |
| data.key = MakeOptional(ByteSpan(key)); |
| data.useAuxiliaryACL = MakeOptional(true); |
| data.mcastAddrPolicy = MakeOptional(app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr); |
| data.endpoints = chip::app::DataModel::List<const EndpointId>(kEndpoints); |
| |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| mMockTimerDelegate.AdvanceClock(System::Clock::Milliseconds64(kChangeTemporisation)); |
| ASSERT_TRUE(mTestContext.ChangeListener().IsDirty(membershipAttributePath)); |
| ASSERT_TRUE(mTestContext.ChangeListener().IsDirty(usedMcastAddrCountAttributePath)); |
| // Read UsedMcastAddrCount |
| ASSERT_EQ(tester.ReadAttribute(app::Clusters::Groupcast::Attributes::UsedMcastAddrCount::Id, multicastAddrCount), |
| CHIP_NO_ERROR); |
| ASSERT_EQ(multicastAddrCount, 1u); |
| mTestContext.ChangeListener().DirtyList().clear(); |
| |
| // Group 2 (PerGroup) |
| data.groupID = kGroup2; |
| data.key.ClearValue(); |
| data.mcastAddrPolicy = MakeOptional(app::Clusters::Groupcast::MulticastAddrPolicyEnum::kPerGroup); |
| result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| mMockTimerDelegate.AdvanceClock(System::Clock::Milliseconds64(kChangeTemporisation)); |
| ASSERT_TRUE(mTestContext.ChangeListener().IsDirty(membershipAttributePath)); |
| ASSERT_TRUE(mTestContext.ChangeListener().IsDirty(usedMcastAddrCountAttributePath)); |
| // Read UsedMcastAddrCount |
| ASSERT_EQ(tester.ReadAttribute(app::Clusters::Groupcast::Attributes::UsedMcastAddrCount::Id, multicastAddrCount), |
| CHIP_NO_ERROR); |
| ASSERT_EQ(multicastAddrCount, 2u); |
| mTestContext.ChangeListener().DirtyList().clear(); |
| |
| // Group 3 (PerGroup) |
| data.groupID = kGroup3; |
| result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| mMockTimerDelegate.AdvanceClock(System::Clock::Milliseconds64(kChangeTemporisation)); |
| ASSERT_TRUE(mTestContext.ChangeListener().IsDirty(membershipAttributePath)); |
| ASSERT_TRUE(mTestContext.ChangeListener().IsDirty(usedMcastAddrCountAttributePath)); |
| // Read UsedMcastAddrCount |
| ASSERT_EQ(tester.ReadAttribute(app::Clusters::Groupcast::Attributes::UsedMcastAddrCount::Id, multicastAddrCount), |
| CHIP_NO_ERROR); |
| ASSERT_EQ(multicastAddrCount, 3u); |
| mTestContext.ChangeListener().DirtyList().clear(); |
| |
| // Group 4 (IanaAddr) |
| data.groupID = kGroup4; |
| data.mcastAddrPolicy = MakeOptional(app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr); |
| result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| mMockTimerDelegate.AdvanceClock(System::Clock::Milliseconds64(kChangeTemporisation)); |
| ASSERT_TRUE(mTestContext.ChangeListener().IsDirty(membershipAttributePath)); |
| ASSERT_FALSE(mTestContext.ChangeListener().IsDirty(usedMcastAddrCountAttributePath)); |
| // Read UsedMcastAddrCount |
| ASSERT_EQ(tester.ReadAttribute(app::Clusters::Groupcast::Attributes::UsedMcastAddrCount::Id, multicastAddrCount), |
| CHIP_NO_ERROR); |
| ASSERT_EQ(multicastAddrCount, 3u); |
| mTestContext.ChangeListener().DirtyList().clear(); |
| } |
| |
| // Leave groups |
| { |
| // Group 2 (PerGroup) |
| Commands::LeaveGroup::Type data; |
| data.groupID = kGroup2; |
| auto result = tester.Invoke(Commands::LeaveGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| mMockTimerDelegate.AdvanceClock(System::Clock::Milliseconds64(kChangeTemporisation)); |
| ASSERT_TRUE(mTestContext.ChangeListener().IsDirty(membershipAttributePath)); |
| ASSERT_TRUE(mTestContext.ChangeListener().IsDirty(usedMcastAddrCountAttributePath)); |
| // Read UsedMcastAddrCount |
| ASSERT_EQ(tester.ReadAttribute(app::Clusters::Groupcast::Attributes::UsedMcastAddrCount::Id, multicastAddrCount), |
| CHIP_NO_ERROR); |
| ASSERT_EQ(multicastAddrCount, 2u); |
| mTestContext.ChangeListener().DirtyList().clear(); |
| |
| // Group 1 (IanaAddr) |
| data.groupID = kGroup1; |
| result = tester.Invoke(Commands::LeaveGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| mMockTimerDelegate.AdvanceClock(System::Clock::Milliseconds64(kChangeTemporisation)); |
| ASSERT_TRUE(mTestContext.ChangeListener().IsDirty(membershipAttributePath)); |
| ASSERT_FALSE(mTestContext.ChangeListener().IsDirty(usedMcastAddrCountAttributePath)); |
| // Read UsedMcastAddrCount |
| ASSERT_EQ(tester.ReadAttribute(app::Clusters::Groupcast::Attributes::UsedMcastAddrCount::Id, multicastAddrCount), |
| CHIP_NO_ERROR); |
| ASSERT_EQ(multicastAddrCount, 2u); |
| mTestContext.ChangeListener().DirtyList().clear(); |
| |
| // Group 3 (PerGroup) |
| data.groupID = kGroup3; |
| result = tester.Invoke(Commands::LeaveGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| mMockTimerDelegate.AdvanceClock(System::Clock::Milliseconds64(kChangeTemporisation)); |
| ASSERT_TRUE(mTestContext.ChangeListener().IsDirty(membershipAttributePath)); |
| ASSERT_TRUE(mTestContext.ChangeListener().IsDirty(usedMcastAddrCountAttributePath)); |
| // Read UsedMcastAddrCount |
| ASSERT_EQ(tester.ReadAttribute(app::Clusters::Groupcast::Attributes::UsedMcastAddrCount::Id, multicastAddrCount), |
| CHIP_NO_ERROR); |
| ASSERT_EQ(multicastAddrCount, 1u); |
| mTestContext.ChangeListener().DirtyList().clear(); |
| |
| // Group 4 (IanaAddr) |
| data.groupID = kGroup4; |
| result = tester.Invoke(Commands::LeaveGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| mMockTimerDelegate.AdvanceClock(System::Clock::Milliseconds64(kChangeTemporisation)); |
| ASSERT_TRUE(mTestContext.ChangeListener().IsDirty(membershipAttributePath)); |
| ASSERT_TRUE(mTestContext.ChangeListener().IsDirty(usedMcastAddrCountAttributePath)); |
| // Read UsedMcastAddrCount |
| ASSERT_EQ(tester.ReadAttribute(app::Clusters::Groupcast::Attributes::UsedMcastAddrCount::Id, multicastAddrCount), |
| CHIP_NO_ERROR); |
| ASSERT_EQ(multicastAddrCount, 0u); |
| mTestContext.ChangeListener().DirtyList().clear(); |
| } |
| } |
| |
| TEST_F(TestGroupcastCluster, TestMaxMcastAddrCount) |
| { |
| const uint8_t key[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; |
| const EndpointId kEndpoints1[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; |
| const EndpointId kEndpoints2[] = { 101, 102 }; |
| KeysetId kKeyset = 0xabcd; |
| |
| chip::Testing::ClusterTester tester(mListener); |
| tester.SetFabricIndex(kTestFabricIndex); |
| tester.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| // Read MaxMcastAddrCount |
| app::Clusters::Groupcast::Attributes::MaxMcastAddrCount::TypeInfo::DecodableType maxMcastAddrCount{}; |
| ASSERT_EQ(tester.ReadAttribute(app::Clusters::Groupcast::Attributes::MaxMcastAddrCount::Id, maxMcastAddrCount), CHIP_NO_ERROR); |
| ASSERT_GT(maxMcastAddrCount, 0u); |
| |
| // Group |
| Commands::JoinGroup::Type data; |
| data.groupID = 1; |
| data.keySetID = kKeyset; |
| data.key = MakeOptional(ByteSpan(key)); |
| data.useAuxiliaryACL = MakeOptional(true); |
| data.endpoints = DataModel::List<const EndpointId>(kEndpoints1, MATTER_ARRAY_SIZE(kEndpoints1)); |
| |
| // Join MaxMcastAddrCount IANA address groups |
| { |
| for (GroupId i = 0; i < maxMcastAddrCount; i++) |
| { |
| data.groupID = i + 1; |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| data.key.ClearValue(); |
| } |
| } |
| // Join MaxMcastAddrCount+1 |
| { |
| data.groupID = maxMcastAddrCount + 1; |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| } |
| // Leave all groups |
| { |
| data.groupID = 0; |
| auto result = tester.Invoke(Commands::LeaveGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| } |
| // Join MaxMcastAddrCount PerGroup address groups |
| { |
| data.mcastAddrPolicy = MakeOptional(app::Clusters::Groupcast::MulticastAddrPolicyEnum::kPerGroup); |
| |
| for (GroupId i = 0; i < maxMcastAddrCount; i++) |
| { |
| data.groupID = i + 1; |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| } |
| } |
| // Join MaxMcastAddrCount+1 |
| { |
| data.groupID = maxMcastAddrCount + 1; |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ResourceExhausted)); |
| } |
| // Leave all groups |
| { |
| data.groupID = 0; |
| auto result = tester.Invoke(Commands::LeaveGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| } |
| // Join 1 IANA address plus (MaxMcastAddrCount - 1) PerGroup groups |
| { |
| data.mcastAddrPolicy = MakeOptional(app::Clusters::Groupcast::MulticastAddrPolicyEnum::kPerGroup); |
| |
| for (GroupId i = 0; i < maxMcastAddrCount; i++) |
| { |
| data.groupID = i + 1; |
| data.mcastAddrPolicy = MakeOptional((0 == i) ? app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr |
| : app::Clusters::Groupcast::MulticastAddrPolicyEnum::kPerGroup); |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| } |
| } |
| // Join MaxMcastAddrCount+1 |
| { |
| data.groupID = maxMcastAddrCount + 1; |
| data.mcastAddrPolicy = MakeOptional(app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr); |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| } |
| // Join MaxMcastAddrCount+2 |
| { |
| data.groupID = maxMcastAddrCount + 2; |
| data.mcastAddrPolicy = MakeOptional(app::Clusters::Groupcast::MulticastAddrPolicyEnum::kPerGroup); |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ResourceExhausted)); |
| } |
| // Join MaxMcastAddrCount+1 (existing group) |
| { |
| data.groupID = 2; |
| data.mcastAddrPolicy = MakeOptional(app::Clusters::Groupcast::MulticastAddrPolicyEnum::kPerGroup); |
| data.endpoints = DataModel::List<const EndpointId>(kEndpoints2, MATTER_ARRAY_SIZE(kEndpoints2)); |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| } |
| } |
| |
| TEST_F(TestGroupcastCluster, TestJoinGroupCommand) |
| { |
| const uint8_t key[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; |
| const EndpointId kEndpoints[] = { 1 }; |
| const KeysetId kKeyset = 0xabcd; |
| |
| Commands::JoinGroup::Type data; |
| data.groupID = 1; |
| data.keySetID = kKeyset; |
| data.key = MakeOptional(ByteSpan(key)); |
| data.useAuxiliaryACL = MakeOptional(true); |
| data.endpoints = DataModel::List<const EndpointId>(kEndpoints, MATTER_ARRAY_SIZE(kEndpoints)); |
| |
| // Neither Listener, nor Sender |
| { |
| app::Clusters::GroupcastCluster cluster( |
| { mFabricHelper.GetFabricTable(), mProvider, mMockTimerDelegate, mAccessControl, mTesting }); |
| chip::Testing::ClusterTester tester(cluster); |
| tester.SetFabricIndex(kTestFabricIndex); |
| tester.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ConstraintError)); |
| } |
| |
| // Listener basic flow |
| { |
| chip::Testing::ClusterTester tester(mListener); |
| tester.SetFabricIndex(kTestFabricIndex); |
| tester.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| // Join group: New keyset and key |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| |
| // Per spec 11.27.7.1.4 (JoinGroup Command/Key Field), a keyset created via JoinGroup with a Key has exactly one epoch |
| // key (num_keys_used == 1) with EpochStartTime0 = 1. |
| { |
| Credentials::GroupDataProvider::KeySet storedKeyset; |
| EXPECT_EQ(mProvider.GetKeySet(kTestFabricIndex, kKeyset, storedKeyset), CHIP_NO_ERROR); |
| EXPECT_EQ(storedKeyset.keyset_id, kKeyset); |
| EXPECT_EQ(storedKeyset.policy, Credentials::GroupDataProvider::SecurityPolicy::kTrustFirst); |
| EXPECT_EQ(storedKeyset.num_keys_used, 1u); |
| EXPECT_EQ(storedKeyset.epoch_keys[0].start_time, 1u); |
| } |
| |
| // Join group: Existing keyset and key (invalid) |
| data.groupID = 2; |
| result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::AlreadyExists)); |
| |
| // Join group: Existing keyset but no key |
| data.groupID = 2; |
| data.key.ClearValue(); |
| result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| |
| // Join group: Existing keyset but no key |
| data.groupID = 2; |
| data.key.ClearValue(); |
| result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| |
| // Join group with root endpoint: Invalid Endpoint |
| const EndpointId kRootEndpoint[] = { kRootEndpointId }; |
| data.groupID = 3; |
| data.endpoints = DataModel::List<const EndpointId>(kRootEndpoint, MATTER_ARRAY_SIZE(kRootEndpoint)); |
| result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::UnsupportedEndpoint)); |
| |
| // Join group with an invalid endpoint in the data model |
| const EndpointId kInvalidEndpoint[] = { 301 }; |
| data.groupID = 3; |
| data.endpoints = DataModel::List<const EndpointId>(kInvalidEndpoint, MATTER_ARRAY_SIZE(kInvalidEndpoint)); |
| result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::UnsupportedEndpoint)); |
| } |
| |
| // Sender |
| { |
| chip::Testing::ClusterTester tester(mSender); |
| tester.SetFabricIndex(kTestFabricIndex); |
| tester.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| data.endpoints = DataModel::List<const EndpointId>(); |
| |
| // Join group: UseAuxiliaryACL can't be set |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ConstraintError)); |
| |
| // Join group: UseAuxiliaryACL unset |
| data.useAuxiliaryACL.ClearValue(); |
| result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| |
| // Join group: ReplaceEndpoints |
| data.replaceEndpoints = MakeOptional(true); |
| result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ConstraintError)); |
| |
| data.replaceEndpoints = MakeOptional(false); |
| result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ConstraintError)); |
| |
| data.replaceEndpoints.ClearValue(); |
| result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| |
| // Join group: McastAddrPolicy kPerGroup without kPerGroup feature set |
| data.groupID = 2; |
| data.mcastAddrPolicy = MakeOptional(app::Clusters::Groupcast::MulticastAddrPolicyEnum::kPerGroup); |
| result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ConstraintError)); |
| |
| // Join group: McastAddrPolicy kIanaAddr without kPerGroup feature |
| data.mcastAddrPolicy = MakeOptional(app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr); |
| data.key.ClearValue(); |
| result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| |
| // Join group: McastAddrPolicy absent without kPerGroup feature |
| data.groupID = 3; |
| data.mcastAddrPolicy.ClearValue(); |
| result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| |
| // Join group: Non-empty endpoints |
| data.groupID = 3; |
| data.endpoints = DataModel::List<const EndpointId>(kEndpoints, MATTER_ARRAY_SIZE(kEndpoints)); |
| result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ConstraintError)); |
| } |
| } |
| |
| TEST_F(TestGroupcastCluster, TestReplaceEndpointsSucceedsProperly) |
| { |
| const uint8_t key[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; |
| GroupId kGroup1 = 0xab01; |
| KeysetId kKeyset1 = 0xabcd; |
| |
| chip::Testing::ClusterTester tester(mListener); |
| tester.SetFabricIndex(kTestFabricIndex); |
| tester.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| EndpointId initialEndpoints[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; |
| Commands::JoinGroup::Type data; |
| data.groupID = kGroup1; |
| data.keySetID = kKeyset1; |
| data.key = MakeOptional(ByteSpan(key)); |
| data.useAuxiliaryACL = MakeOptional(true); |
| data.endpoints = DataModel::List<const EndpointId>(initialEndpoints, MATTER_ARRAY_SIZE(initialEndpoints)); |
| |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| |
| // Read Membership |
| app::Clusters::Groupcast::Attributes::Membership::TypeInfo::DecodableType memberships; |
| ASSERT_EQ(tester.ReadAttribute(Attributes::Membership::Id, memberships), CHIP_NO_ERROR); |
| |
| Clusters::Groupcast::Structs::MembershipStruct::Type expectedMembership0[] = { { |
| .groupID = kGroup1, |
| .endpoints = MakeOptional(DataModel::List<const EndpointId>(initialEndpoints, MATTER_ARRAY_SIZE(initialEndpoints))), |
| .keySetID = kKeyset1, |
| .hasAuxiliaryACL = MakeOptional(true), |
| .mcastAddrPolicy = app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr, |
| } }; |
| ValidateMembership(memberships, expectedMembership0, MATTER_ARRAY_SIZE(expectedMembership0)); |
| |
| // Replace with 8 members |
| EndpointId replacementEndpoints[8] = { 11, 12, 13, 14, 15, 16, 17, 18 }; |
| data.key.ClearValue(); |
| data.replaceEndpoints = MakeOptional(true); |
| data.endpoints = DataModel::List<const EndpointId>(replacementEndpoints, MATTER_ARRAY_SIZE(replacementEndpoints)); |
| |
| result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| |
| ASSERT_EQ(tester.ReadAttribute(Attributes::Membership::Id, memberships), CHIP_NO_ERROR); |
| |
| Clusters::Groupcast::Structs::MembershipStruct::Type expectedMembership1[] = { { |
| .groupID = kGroup1, |
| .endpoints = MakeOptional(DataModel::List<const EndpointId>(replacementEndpoints, MATTER_ARRAY_SIZE(replacementEndpoints))), |
| .keySetID = kKeyset1, |
| .hasAuxiliaryACL = MakeOptional(true), |
| .mcastAddrPolicy = app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr, |
| } }; |
| ValidateMembership(memberships, expectedMembership1, MATTER_ARRAY_SIZE(expectedMembership1)); |
| |
| // Add 40 endpoints (requires 2 JoinGroup commands) |
| EndpointId fortyEndpoints1[20]; |
| EndpointId fortyEndpoints2[20]; |
| for (uint16_t i = 0; i < 20; i++) |
| { |
| fortyEndpoints1[i] = static_cast<EndpointId>(100 + i); |
| fortyEndpoints2[i] = static_cast<EndpointId>(120 + i); |
| } |
| |
| data.replaceEndpoints = MakeOptional(true); |
| data.endpoints = DataModel::List<const EndpointId>(fortyEndpoints1, MATTER_ARRAY_SIZE(fortyEndpoints1)); |
| result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| |
| data.replaceEndpoints = MakeOptional(false); |
| data.endpoints = DataModel::List<const EndpointId>(fortyEndpoints2, MATTER_ARRAY_SIZE(fortyEndpoints2)); |
| result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| |
| EndpointId expectedForty[40]; |
| for (uint16_t i = 0; i < 40; i++) |
| { |
| expectedForty[i] = static_cast<EndpointId>(100 + i); |
| } |
| |
| ASSERT_EQ(tester.ReadAttribute(Attributes::Membership::Id, memberships), CHIP_NO_ERROR); |
| Clusters::Groupcast::Structs::MembershipStruct::Type expectedMembership2[] = { { |
| .groupID = kGroup1, |
| .endpoints = MakeOptional(DataModel::List<const EndpointId>(expectedForty, MATTER_ARRAY_SIZE(expectedForty))), |
| .keySetID = kKeyset1, |
| .hasAuxiliaryACL = MakeOptional(true), |
| .mcastAddrPolicy = app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr, |
| } }; |
| ValidateMembership(memberships, expectedMembership2, MATTER_ARRAY_SIZE(expectedMembership2)); |
| |
| // JoinGroup with replace endpoints that adds only 1 endpoint |
| EndpointId oneEndpoint[1] = { 200 }; |
| data.replaceEndpoints = MakeOptional(true); |
| data.endpoints = DataModel::List<const EndpointId>(oneEndpoint, MATTER_ARRAY_SIZE(oneEndpoint)); |
| result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| |
| ASSERT_EQ(tester.ReadAttribute(Attributes::Membership::Id, memberships), CHIP_NO_ERROR); |
| Clusters::Groupcast::Structs::MembershipStruct::Type expectedMembership3[] = { { |
| .groupID = kGroup1, |
| .endpoints = MakeOptional(DataModel::List<const EndpointId>(oneEndpoint, MATTER_ARRAY_SIZE(oneEndpoint))), |
| .keySetID = kKeyset1, |
| .hasAuxiliaryACL = MakeOptional(true), |
| .mcastAddrPolicy = app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr, |
| } }; |
| ValidateMembership(memberships, expectedMembership3, MATTER_ARRAY_SIZE(expectedMembership3)); |
| |
| // Add 40 more endpoints |
| data.replaceEndpoints = MakeOptional(false); |
| data.endpoints = DataModel::List<const EndpointId>(fortyEndpoints1, MATTER_ARRAY_SIZE(fortyEndpoints1)); |
| result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| |
| data.endpoints = DataModel::List<const EndpointId>(fortyEndpoints2, MATTER_ARRAY_SIZE(fortyEndpoints2)); |
| result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| |
| EndpointId expectedFortyOne[41]; |
| expectedFortyOne[0] = 200; |
| for (uint16_t i = 0; i < 40; i++) |
| { |
| expectedFortyOne[i + 1] = static_cast<EndpointId>(100 + i); |
| } |
| |
| ASSERT_EQ(tester.ReadAttribute(Attributes::Membership::Id, memberships), CHIP_NO_ERROR); |
| Clusters::Groupcast::Structs::MembershipStruct::Type expectedMembership4[] = { { |
| .groupID = kGroup1, |
| .endpoints = MakeOptional(DataModel::List<const EndpointId>(expectedFortyOne, MATTER_ARRAY_SIZE(expectedFortyOne))), |
| .keySetID = kKeyset1, |
| .hasAuxiliaryACL = MakeOptional(true), |
| .mcastAddrPolicy = app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr, |
| } }; |
| ValidateMembership(memberships, expectedMembership4, MATTER_ARRAY_SIZE(expectedMembership4)); |
| } |
| |
| TEST_F(TestGroupcastCluster, TestLeaveGroup) |
| { |
| static constexpr uint16_t kMaxEndpoints = app::Clusters::GroupcastCluster::kMaxCommandEndpoints; |
| static constexpr uint16_t kIntervals = 5; |
| static constexpr uint16_t kTotalEndpoints = kMaxEndpoints * kIntervals; |
| const uint8_t key[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; |
| const EndpointId kEndpoints[kIntervals][kMaxEndpoints] = { |
| { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 }, |
| { 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 }, |
| { 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60 }, |
| { 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80 }, |
| { 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100 } |
| }; |
| const EndpointId kLeaveEndpoints1[] = { 1, 23, 45, 56, 67, 78, 89, 100 }; |
| const EndpointId kLeaveEndpoints2[] = { 3, 6, 29, 42, 48, 66, 76, 91 }; |
| |
| static const std::set<EndpointId> kRemoveSet1(std::begin(kLeaveEndpoints1), std::end(kLeaveEndpoints1)); |
| static const std::set<EndpointId> kRemoveSet2(std::begin(kLeaveEndpoints2), std::end(kLeaveEndpoints2)); |
| |
| GroupId kGroup1 = 0xab01; |
| GroupId kGroup2 = 0xcd02; |
| GroupId kGroup3 = 0xef03; |
| KeysetId kKeyset = 0xabcd; |
| |
| chip::Testing::ClusterTester tester(mListener); |
| tester.SetFabricIndex(kTestFabricIndex); |
| tester.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| EndpointId all_endpoints[kTotalEndpoints]; |
| for (size_t i = 0; i < kTotalEndpoints; i++) |
| { |
| all_endpoints[i] = static_cast<EndpointId>(i + 1); |
| } |
| |
| // Join groups |
| { |
| // Group 1 |
| Commands::JoinGroup::Type data; |
| data.groupID = kGroup1; |
| data.keySetID = kKeyset; |
| data.key = MakeOptional(ByteSpan(key)); |
| data.useAuxiliaryACL = MakeOptional(true); |
| data.endpoints = DataModel::List<const EndpointId>(kEndpoints[0], kMaxEndpoints); |
| |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| data.key.ClearValue(); |
| for (int i = 1; i < kIntervals; i++) |
| { |
| data.endpoints = DataModel::List<const EndpointId>(kEndpoints[i], kMaxEndpoints); |
| result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| } |
| // Group 2 |
| data.groupID = kGroup2; |
| data.useAuxiliaryACL = MakeOptional(false); |
| for (int i = 0; i < kIntervals; i++) |
| { |
| data.endpoints = DataModel::List<const EndpointId>(kEndpoints[i], kMaxEndpoints); |
| result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| } |
| } |
| |
| // Read Membership |
| { |
| app::Clusters::Groupcast::Attributes::Membership::TypeInfo::DecodableType memberships; |
| ASSERT_EQ(tester.ReadAttribute(Attributes::Membership::Id, memberships), CHIP_NO_ERROR); |
| |
| // Build expected endpoint arrays - both groups have all 100 endpoints |
| Clusters::Groupcast::Structs::MembershipStruct::Type expectedMembership[] = { |
| { |
| .groupID = kGroup1, |
| .endpoints = MakeOptional(DataModel::List<const EndpointId>(all_endpoints, kTotalEndpoints)), |
| .keySetID = kKeyset, |
| .hasAuxiliaryACL = MakeOptional(true), |
| .mcastAddrPolicy = app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr, |
| }, |
| { |
| .groupID = kGroup2, |
| .endpoints = MakeOptional(DataModel::List<const EndpointId>(all_endpoints, kTotalEndpoints)), |
| .keySetID = kKeyset, |
| .hasAuxiliaryACL = MakeOptional(false), |
| .mcastAddrPolicy = app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr, |
| } |
| }; |
| |
| ValidateMembership(memberships, expectedMembership, MATTER_ARRAY_SIZE(expectedMembership)); |
| } |
| |
| // LeaveGroup |
| { |
| Commands::LeaveGroup::Type data; |
| |
| // Update existing key (invalid) |
| data.groupID = kGroup1; |
| data.endpoints = MakeOptional(DataModel::List<const EndpointId>(kLeaveEndpoints1, MATTER_ARRAY_SIZE(kLeaveEndpoints1))); |
| auto result = tester.Invoke(Commands::LeaveGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| } |
| |
| // Read Membership |
| { |
| app::Clusters::Groupcast::Attributes::Membership::TypeInfo::DecodableType memberships; |
| ASSERT_EQ(tester.ReadAttribute(Attributes::Membership::Id, memberships), CHIP_NO_ERROR); |
| |
| // Build expected endpoint arrays |
| // Group1: all endpoints except kLeaveEndpoints1 (100 - 8 = 92 endpoints) |
| EndpointId group1_endpoints[kTotalEndpoints - MATTER_ARRAY_SIZE(kLeaveEndpoints1)]; |
| for (size_t i = 0, j = 0; (i < kTotalEndpoints) && (j < MATTER_ARRAY_SIZE(group1_endpoints)); i++) |
| { |
| EndpointId ep = static_cast<EndpointId>(i + 1); |
| if (kRemoveSet1.find(ep) == kRemoveSet1.end()) |
| { |
| group1_endpoints[j++] = ep; |
| } |
| } |
| |
| // Group2: all endpoints (100 endpoints) |
| |
| Clusters::Groupcast::Structs::MembershipStruct::Type expectedMembership[] = { |
| { |
| .groupID = kGroup1, |
| .endpoints = MakeOptional(DataModel::List<const EndpointId>(group1_endpoints, MATTER_ARRAY_SIZE(group1_endpoints))), |
| .keySetID = kKeyset, |
| .hasAuxiliaryACL = MakeOptional(true), |
| .mcastAddrPolicy = app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr, |
| }, |
| { |
| .groupID = kGroup2, |
| .endpoints = MakeOptional(DataModel::List<const EndpointId>(all_endpoints, kTotalEndpoints)), |
| .keySetID = kKeyset, |
| .hasAuxiliaryACL = MakeOptional(false), |
| .mcastAddrPolicy = app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr, |
| } |
| }; |
| |
| ValidateMembership(memberships, expectedMembership, MATTER_ARRAY_SIZE(expectedMembership)); |
| } |
| |
| // LeaveGroup a List of endpoints from all groups |
| { |
| Commands::LeaveGroup::Type data; |
| |
| data.groupID = 0; |
| data.endpoints = MakeOptional(DataModel::List<const EndpointId>(kLeaveEndpoints2, MATTER_ARRAY_SIZE(kLeaveEndpoints2))); |
| auto result = tester.Invoke(Commands::LeaveGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| } |
| |
| // Read Membership |
| { |
| app::Clusters::Groupcast::Attributes::Membership::TypeInfo::DecodableType memberships; |
| ASSERT_EQ(tester.ReadAttribute(Attributes::Membership::Id, memberships), CHIP_NO_ERROR); |
| |
| // Build expected endpoint arrays |
| // Group1: all endpoints except kLeaveEndpoints1 and kLeaveEndpoints2 (100 - 8 - 8 = 84 endpoints) |
| EndpointId group1_endpoints[kTotalEndpoints - MATTER_ARRAY_SIZE(kLeaveEndpoints1) - MATTER_ARRAY_SIZE(kLeaveEndpoints2)]; |
| for (size_t i = 0, j = 0; (i < kTotalEndpoints) && (j < MATTER_ARRAY_SIZE(group1_endpoints)); i++) |
| { |
| EndpointId ep = static_cast<EndpointId>(i + 1); |
| if (kRemoveSet1.find(ep) == kRemoveSet1.end() && kRemoveSet2.find(ep) == kRemoveSet2.end()) |
| { |
| group1_endpoints[j++] = ep; |
| } |
| } |
| |
| // Group2: all endpoints except kLeaveEndpoints2 (100 - 8 = 92 endpoints) |
| EndpointId group2_endpoints[kTotalEndpoints - MATTER_ARRAY_SIZE(kLeaveEndpoints2)]; |
| for (size_t i = 0, j = 0; (i < kTotalEndpoints) && (j < MATTER_ARRAY_SIZE(group2_endpoints)); i++) |
| { |
| EndpointId ep = static_cast<EndpointId>(i + 1); |
| if (kRemoveSet2.find(ep) == kRemoveSet2.end()) |
| { |
| group2_endpoints[j++] = ep; |
| } |
| } |
| |
| Clusters::Groupcast::Structs::MembershipStruct::Type expectedMembership[] = { |
| { |
| .groupID = kGroup1, |
| .endpoints = MakeOptional(DataModel::List<const EndpointId>(group1_endpoints, MATTER_ARRAY_SIZE(group1_endpoints))), |
| .keySetID = kKeyset, |
| .hasAuxiliaryACL = MakeOptional(true), |
| .mcastAddrPolicy = app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr, |
| }, |
| { |
| .groupID = kGroup2, |
| .endpoints = MakeOptional(DataModel::List<const EndpointId>(group2_endpoints, MATTER_ARRAY_SIZE(group2_endpoints))), |
| .keySetID = kKeyset, |
| .hasAuxiliaryACL = MakeOptional(false), |
| .mcastAddrPolicy = app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr, |
| } |
| }; |
| |
| ValidateMembership(memberships, expectedMembership, MATTER_ARRAY_SIZE(expectedMembership)); |
| } |
| |
| // LeaveGroup all groups completely. |
| { |
| Commands::LeaveGroup::Type data; |
| data.groupID = 0; |
| auto result = tester.Invoke(Commands::LeaveGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| } |
| |
| // Read Membership |
| { |
| app::Clusters::Groupcast::Attributes::Membership::TypeInfo::DecodableType memberships; |
| ASSERT_EQ(tester.ReadAttribute(Attributes::Membership::Id, memberships), CHIP_NO_ERROR); |
| |
| // After leaving all groups, membership should be empty |
| Clusters::Groupcast::Structs::MembershipStruct::Type expectedMembership[0] = {}; |
| ValidateMembership(memberships, expectedMembership, 0); |
| } |
| |
| // JoinGroup for GroupID 1 and then GroupID 2 with the same endpoint list. |
| { |
| // JoinGroup for GroupID 1 |
| Commands::JoinGroup::Type data; |
| data.groupID = kGroup1; |
| data.endpoints = DataModel::List<const EndpointId>(kEndpoints[0], kMaxEndpoints); |
| data.keySetID = kKeyset; |
| data.useAuxiliaryACL = MakeOptional(true); |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| // JoinGroup for GroupID 2 |
| data.groupID = kGroup2; |
| result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| |
| // Read Membership |
| app::Clusters::Groupcast::Attributes::Membership::TypeInfo::DecodableType memberships; |
| ASSERT_EQ(tester.ReadAttribute(Attributes::Membership::Id, memberships), CHIP_NO_ERROR); |
| |
| Clusters::Groupcast::Structs::MembershipStruct::Type expectedMembership[] = { |
| { |
| .groupID = kGroup1, |
| .endpoints = MakeOptional(DataModel::List<const EndpointId>(kEndpoints[0], kMaxEndpoints)), |
| .keySetID = kKeyset, |
| .hasAuxiliaryACL = MakeOptional(true), |
| .mcastAddrPolicy = Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr, |
| }, |
| { |
| .groupID = kGroup2, |
| .endpoints = MakeOptional(DataModel::List<const EndpointId>(kEndpoints[0], kMaxEndpoints)), |
| .keySetID = kKeyset, |
| .hasAuxiliaryACL = MakeOptional(true), |
| .mcastAddrPolicy = Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr, |
| } |
| }; |
| |
| ValidateMembership(memberships, expectedMembership, MATTER_ARRAY_SIZE(expectedMembership)); |
| } |
| |
| // LeaveGroup for GroupID 2 without providing any endpoints |
| { |
| Commands::LeaveGroup::Type data; |
| data.groupID = kGroup2; |
| data.endpoints.ClearValue(); |
| auto result = tester.Invoke(Commands::LeaveGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| |
| // Read Membership |
| Attributes::Membership::TypeInfo::DecodableType memberships; |
| ASSERT_EQ(tester.ReadAttribute(Attributes::Membership::Id, memberships), CHIP_NO_ERROR); |
| |
| Clusters::Groupcast::Structs::MembershipStruct::Type expectedMembership[] = { { |
| .groupID = kGroup1, |
| .endpoints = MakeOptional(DataModel::List<const EndpointId>(kEndpoints[0], kMaxEndpoints)), |
| .keySetID = kKeyset, |
| .hasAuxiliaryACL = MakeOptional(true), |
| .mcastAddrPolicy = Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr, |
| } }; |
| |
| ValidateMembership(memberships, expectedMembership, MATTER_ARRAY_SIZE(expectedMembership)); |
| } |
| |
| // Create a Listener and Sender capable group with 1 endpoint. |
| // Remove the endpoint from the group. Verify that the group still exists for Sender. |
| app::Clusters::GroupcastCluster ListenerAndSender{ { mFabricHelper.GetFabricTable(), mProvider, mMockTimerDelegate, |
| mAccessControl, mTesting }, |
| BitFlags<Feature>{ Feature::kListener, Feature::kSender } }; |
| ASSERT_EQ(ListenerAndSender.Startup(*clusterContext), CHIP_NO_ERROR); |
| chip::Testing::ClusterTester listenerAndSenderTester(ListenerAndSender); |
| listenerAndSenderTester.SetFabricIndex(kTestFabricIndex); |
| listenerAndSenderTester.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| { |
| // JoinGroup for GroupID 3 |
| Commands::JoinGroup::Type data; |
| data.groupID = kGroup3; |
| data.endpoints = DataModel::List<const EndpointId>(kEndpoints[0], 1); |
| data.keySetID = kKeyset; |
| data.useAuxiliaryACL = MakeOptional(true); |
| auto result = listenerAndSenderTester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| |
| // Read Membership |
| app::Clusters::Groupcast::Attributes::Membership::TypeInfo::DecodableType memberships; |
| ASSERT_EQ(listenerAndSenderTester.ReadAttribute(Attributes::Membership::Id, memberships), CHIP_NO_ERROR); |
| |
| Clusters::Groupcast::Structs::MembershipStruct::Type expectedMembership[] = { |
| { |
| .groupID = kGroup1, |
| .endpoints = MakeOptional(DataModel::List<const EndpointId>(kEndpoints[0], kMaxEndpoints)), |
| .keySetID = kKeyset, |
| .hasAuxiliaryACL = MakeOptional(true), |
| .mcastAddrPolicy = Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr, |
| }, |
| { |
| .groupID = kGroup3, |
| .endpoints = MakeOptional(DataModel::List<const EndpointId>(kEndpoints[0], 1)), |
| .keySetID = kKeyset, |
| .hasAuxiliaryACL = MakeOptional(true), |
| .mcastAddrPolicy = Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr, |
| } |
| }; |
| |
| ValidateMembership(memberships, expectedMembership, MATTER_ARRAY_SIZE(expectedMembership)); |
| } |
| |
| { |
| // LeaveGroup for GroupID 3 |
| Commands::LeaveGroup::Type data; |
| data.groupID = kGroup3; |
| data.endpoints = MakeOptional(DataModel::List<const EndpointId>(kEndpoints[0], 1)); |
| auto result = listenerAndSenderTester.Invoke(Commands::LeaveGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| |
| app::Clusters::Groupcast::Attributes::Membership::TypeInfo::DecodableType memberships; |
| ASSERT_EQ(listenerAndSenderTester.ReadAttribute(Attributes::Membership::Id, memberships), CHIP_NO_ERROR); |
| |
| Clusters::Groupcast::Structs::MembershipStruct::Type expectedMembership[] = { |
| { |
| .groupID = kGroup1, |
| .endpoints = MakeOptional(DataModel::List<const EndpointId>(kEndpoints[0], kMaxEndpoints)), |
| .keySetID = kKeyset, |
| .hasAuxiliaryACL = MakeOptional(true), |
| .mcastAddrPolicy = Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr, |
| }, |
| { |
| .groupID = kGroup3, |
| .endpoints = MakeOptional( |
| DataModel::List<const chip::EndpointId>()), // Listener is supported, so an empty endpoints list is expected. |
| .keySetID = kKeyset, |
| .hasAuxiliaryACL = MakeOptional(true), |
| .mcastAddrPolicy = Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr, |
| } |
| }; |
| |
| ValidateMembership(memberships, expectedMembership, MATTER_ARRAY_SIZE(expectedMembership)); |
| } |
| ListenerAndSender.Shutdown(app::ClusterShutdownType::kClusterShutdown); |
| } |
| |
| TEST_F(TestGroupcastCluster, TestUpdateGroupKey) |
| { |
| const uint8_t key1[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; |
| const uint8_t key2[] = { 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf }; |
| const EndpointId kEndpoints[] = { 1 }; |
| GroupId kGroup1 = 0xab01; |
| GroupId kGroup2 = 0xcd02; |
| const KeysetId kKeyset1 = 0xabcd; |
| const KeysetId kKeyset2 = 0xcafe; |
| const KeysetId kKeyset3 = 0xface; |
| |
| chip::Testing::ClusterTester tester(mListener); |
| tester.SetFabricIndex(kTestFabricIndex); |
| tester.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| // Join groups |
| { |
| Commands::JoinGroup::Type data; |
| data.groupID = kGroup1; |
| data.keySetID = kKeyset1; |
| data.key = MakeOptional(ByteSpan(key1)); |
| data.useAuxiliaryACL = MakeOptional(true); |
| data.endpoints = DataModel::List<const EndpointId>(kEndpoints, MATTER_ARRAY_SIZE(kEndpoints)); |
| |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| |
| data.groupID = kGroup2; |
| data.keySetID = kKeyset2; |
| data.key = MakeOptional(ByteSpan(key2)); |
| result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| } |
| |
| // Read Membership |
| { |
| app::Clusters::Groupcast::Attributes::Membership::TypeInfo::DecodableType memberships; |
| ASSERT_EQ(tester.ReadAttribute(Attributes::Membership::Id, memberships), CHIP_NO_ERROR); |
| Clusters::Groupcast::Structs::MembershipStruct::Type expectedMembership[] = { |
| { |
| .groupID = kGroup1, |
| .endpoints = MakeOptional(DataModel::List<const EndpointId>(kEndpoints, MATTER_ARRAY_SIZE(kEndpoints))), |
| .keySetID = kKeyset1, |
| .hasAuxiliaryACL = MakeOptional(true), |
| .mcastAddrPolicy = app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr, |
| }, |
| { |
| .groupID = kGroup2, |
| .endpoints = MakeOptional(DataModel::List<const EndpointId>(kEndpoints, MATTER_ARRAY_SIZE(kEndpoints))), |
| .keySetID = kKeyset2, |
| .hasAuxiliaryACL = MakeOptional(true), |
| .mcastAddrPolicy = app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr, |
| } |
| }; |
| |
| ValidateMembership(memberships, expectedMembership, MATTER_ARRAY_SIZE(expectedMembership)); |
| } |
| |
| // Update |
| { |
| Commands::UpdateGroupKey::Type data; |
| |
| // Update existing key (invalid) |
| data.groupID = kGroup2; |
| data.keySetID = kKeyset1; |
| data.key = MakeOptional(ByteSpan(key1)); |
| auto result = tester.Invoke(Commands::UpdateGroupKey::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::AlreadyExists)); |
| |
| // Update to non-existing keyset (invalid) |
| data.groupID = kGroup2; |
| data.keySetID = kKeyset3; |
| data.key.ClearValue(); |
| result = tester.Invoke(Commands::UpdateGroupKey::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::NotFound)); |
| |
| // Update without key (always valid) |
| data.groupID = kGroup2; |
| data.keySetID = kKeyset1; |
| data.key.ClearValue(); |
| result = tester.Invoke(Commands::UpdateGroupKey::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| |
| // Create a new key (valid, if i <= mProvider.GetMaxGroupKeysPerFabric()) |
| // 2 keysets already in use |
| data.key = MakeOptional(ByteSpan(key1)); |
| for (uint16_t i = 3; i <= mProvider.GetMaxGroupKeysPerFabric() + 1; ++i) |
| { |
| data.keySetID = kKeyset2 + i; |
| result = tester.Invoke(Commands::UpdateGroupKey::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), |
| i > mProvider.GetMaxGroupKeysPerFabric() ? ClusterStatusCode(Status::ResourceExhausted) |
| : ClusterStatusCode(Status::Success)); |
| } |
| } |
| |
| // Read Membership |
| { |
| app::Clusters::Groupcast::Attributes::Membership::TypeInfo::DecodableType memberships; |
| ASSERT_EQ(tester.ReadAttribute(Attributes::Membership::Id, memberships), CHIP_NO_ERROR); |
| |
| Clusters::Groupcast::Structs::MembershipStruct::Type expectedMembership[] = { |
| { |
| .groupID = kGroup1, |
| .endpoints = MakeOptional(DataModel::List<const EndpointId>(kEndpoints, MATTER_ARRAY_SIZE(kEndpoints))), |
| .keySetID = kKeyset1, |
| .hasAuxiliaryACL = MakeOptional(true), |
| .mcastAddrPolicy = app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr, |
| }, |
| { |
| .groupID = kGroup2, |
| .endpoints = MakeOptional(DataModel::List<const EndpointId>(kEndpoints, MATTER_ARRAY_SIZE(kEndpoints))), |
| .keySetID = static_cast<KeysetId>(kKeyset2 + mProvider.GetMaxGroupKeysPerFabric()), |
| .hasAuxiliaryACL = MakeOptional(true), |
| .mcastAddrPolicy = app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr, |
| } |
| }; |
| |
| ValidateMembership(memberships, expectedMembership, MATTER_ARRAY_SIZE(expectedMembership)); |
| } |
| } |
| |
| TEST_F(TestGroupcastCluster, TestConfigureAuxiliaryACL) |
| { |
| const uint8_t key[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; |
| const EndpointId kEndpoints[] = { 1 }; |
| const GroupId kGroupId = 0xcafe; |
| const KeysetId kKeyset = 0xabcd; |
| |
| chip::Testing::ClusterTester tester(mListener); |
| tester.SetFabricIndex(kTestFabricIndex); |
| tester.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| // Join group |
| { |
| Commands::JoinGroup::Type data; |
| data.groupID = kGroupId; |
| data.keySetID = kKeyset; |
| data.key = MakeOptional(ByteSpan(key)); |
| data.useAuxiliaryACL = MakeOptional(false); |
| data.endpoints = DataModel::List<const EndpointId>(kEndpoints, MATTER_ARRAY_SIZE(kEndpoints)); |
| |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| } |
| |
| // Read Membership |
| { |
| app::Clusters::Groupcast::Attributes::Membership::TypeInfo::DecodableType memberships; |
| ASSERT_EQ(tester.ReadAttribute(Attributes::Membership::Id, memberships), CHIP_NO_ERROR); |
| |
| Clusters::Groupcast::Structs::MembershipStruct::Type expectedMembership[] = { { |
| .groupID = kGroupId, |
| .endpoints = MakeOptional(DataModel::List<const EndpointId>(kEndpoints, MATTER_ARRAY_SIZE(kEndpoints))), |
| .keySetID = kKeyset, |
| .hasAuxiliaryACL = MakeOptional(false), |
| .mcastAddrPolicy = app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr, |
| } }; |
| |
| ValidateMembership(memberships, expectedMembership, MATTER_ARRAY_SIZE(expectedMembership)); |
| } |
| |
| // Update Sender (false to true), invalid |
| { |
| chip::Testing::ClusterTester sender_tester(mSender); |
| sender_tester.SetFabricIndex(kTestFabricIndex); |
| sender_tester.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| Commands::ConfigureAuxiliaryACL::Type data; |
| data.groupID = kGroupId; |
| data.useAuxiliaryACL = true; |
| |
| auto result = sender_tester.Invoke(Commands::ConfigureAuxiliaryACL::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ConstraintError)); |
| } |
| |
| // Update (false to true) |
| { |
| Commands::ConfigureAuxiliaryACL::Type data; |
| data.groupID = kGroupId; |
| data.useAuxiliaryACL = true; |
| |
| auto result = tester.Invoke(Commands::ConfigureAuxiliaryACL::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| } |
| |
| // Read Membership |
| { |
| app::Clusters::Groupcast::Attributes::Membership::TypeInfo::DecodableType memberships; |
| ASSERT_EQ(tester.ReadAttribute(Attributes::Membership::Id, memberships), CHIP_NO_ERROR); |
| |
| Clusters::Groupcast::Structs::MembershipStruct::Type expectedMembership[] = { { |
| .groupID = kGroupId, |
| .endpoints = MakeOptional(DataModel::List<const EndpointId>(kEndpoints, MATTER_ARRAY_SIZE(kEndpoints))), |
| .keySetID = kKeyset, |
| .hasAuxiliaryACL = MakeOptional(true), |
| .mcastAddrPolicy = app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr, |
| } }; |
| |
| ValidateMembership(memberships, expectedMembership, MATTER_ARRAY_SIZE(expectedMembership)); |
| } |
| |
| // Try to update for groupID 0 --> constraint error |
| { |
| tester.SetFabricIndex(kTestFabricIndex); |
| tester.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| Commands::ConfigureAuxiliaryACL::Type data; |
| data.groupID = 0; |
| data.useAuxiliaryACL = false; |
| |
| auto result = tester.Invoke(Commands::ConfigureAuxiliaryACL::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ConstraintError)); |
| } |
| |
| // Update (true to false) |
| { |
| Commands::ConfigureAuxiliaryACL::Type data; |
| data.groupID = kGroupId; |
| data.useAuxiliaryACL = false; |
| |
| auto result = tester.Invoke(Commands::ConfigureAuxiliaryACL::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| } |
| |
| // Read Membership |
| { |
| app::Clusters::Groupcast::Attributes::Membership::TypeInfo::DecodableType memberships; |
| ASSERT_EQ(tester.ReadAttribute(Attributes::Membership::Id, memberships), CHIP_NO_ERROR); |
| |
| Clusters::Groupcast::Structs::MembershipStruct::Type expectedMembership[] = { { |
| .groupID = kGroupId, |
| .endpoints = MakeOptional(DataModel::List<const EndpointId>(kEndpoints, MATTER_ARRAY_SIZE(kEndpoints))), |
| .keySetID = kKeyset, |
| .hasAuxiliaryACL = MakeOptional(false), |
| .mcastAddrPolicy = app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr, |
| } }; |
| |
| ValidateMembership(memberships, expectedMembership, MATTER_ARRAY_SIZE(expectedMembership)); |
| } |
| } |
| |
| TEST_F(TestGroupcastCluster, TestGroupcastTestingCommand) |
| { |
| ClusterTester tester(mListener); |
| tester.SetFabricIndex(kTestFabricIndex); |
| tester.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| // Default should be "no fabric under test" |
| FabricIndex fabricUnderTest = kUndefinedFabricIndex; |
| ASSERT_EQ(tester.ReadAttribute(Attributes::FabricUnderTest::Id, fabricUnderTest), CHIP_NO_ERROR); |
| EXPECT_EQ(fabricUnderTest, kUndefinedFabricIndex); |
| |
| // Test invalid duration |
| { |
| Commands::GroupcastTesting::Type data; |
| data.testOperation = GroupcastTestingEnum::kEnableListenerTesting; |
| // Too small |
| data.durationSeconds = MakeOptional(static_cast<uint16_t>(9)); |
| |
| auto result = tester.Invoke(Commands::GroupcastTesting::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ConstraintError)); |
| |
| // Command failed; should not have changed. |
| ASSERT_EQ(tester.ReadAttribute(Attributes::FabricUnderTest::Id, fabricUnderTest), CHIP_NO_ERROR); |
| EXPECT_EQ(fabricUnderTest, kUndefinedFabricIndex); |
| |
| // Too large |
| data.durationSeconds = MakeOptional(static_cast<uint16_t>(1201)); |
| result = tester.Invoke(Commands::GroupcastTesting::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ConstraintError)); |
| |
| // Command failed; should not have changed from enabled state. |
| ASSERT_EQ(tester.ReadAttribute(Attributes::FabricUnderTest::Id, fabricUnderTest), CHIP_NO_ERROR); |
| EXPECT_EQ(fabricUnderTest, kUndefinedFabricIndex); |
| } |
| |
| // Enable testing (no duration) |
| { |
| Commands::GroupcastTesting::Type data; |
| data.testOperation = GroupcastTestingEnum::kEnableListenerTesting; |
| |
| auto result = tester.Invoke(Commands::GroupcastTesting::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| |
| ASSERT_EQ(tester.ReadAttribute(Attributes::FabricUnderTest::Id, fabricUnderTest), CHIP_NO_ERROR); |
| EXPECT_EQ(fabricUnderTest, kTestFabricIndex); |
| } |
| |
| // Disable testing should clear the fabric under test |
| { |
| Commands::GroupcastTesting::Type data; |
| data.testOperation = GroupcastTestingEnum::kDisableTesting; |
| |
| auto result = tester.Invoke(Commands::GroupcastTesting::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| |
| ASSERT_EQ(tester.ReadAttribute(Attributes::FabricUnderTest::Id, fabricUnderTest), CHIP_NO_ERROR); |
| EXPECT_EQ(fabricUnderTest, kUndefinedFabricIndex); |
| } |
| |
| // Enable Listener Testing with duration |
| { |
| const uint16_t durationSeconds = 10; |
| |
| Commands::GroupcastTesting::Type data; |
| data.testOperation = GroupcastTestingEnum::kEnableListenerTesting; |
| data.durationSeconds = MakeOptional(durationSeconds); |
| |
| auto result = tester.Invoke(Commands::GroupcastTesting::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| |
| ASSERT_EQ(tester.ReadAttribute(Attributes::FabricUnderTest::Id, fabricUnderTest), CHIP_NO_ERROR); |
| EXPECT_EQ(fabricUnderTest, kTestFabricIndex); |
| |
| // Testing should still be active |
| mMockTimerDelegate.AdvanceClock(System::Clock::Seconds16(durationSeconds - 1)); |
| ASSERT_EQ(tester.ReadAttribute(Attributes::FabricUnderTest::Id, fabricUnderTest), CHIP_NO_ERROR); |
| EXPECT_EQ(fabricUnderTest, kTestFabricIndex); |
| |
| // Testing should end after the duration |
| mMockTimerDelegate.AdvanceClock(System::Clock::Seconds16(durationSeconds + 1)); |
| ASSERT_EQ(tester.ReadAttribute(Attributes::FabricUnderTest::Id, fabricUnderTest), CHIP_NO_ERROR); |
| EXPECT_EQ(fabricUnderTest, kUndefinedFabricIndex); |
| } |
| |
| // Enable Sender Testing with duration |
| } |
| |
| TEST_F(TestGroupcastCluster, TestAuxiliaryAccessUpdatedEvent) |
| { |
| const uint8_t key[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; |
| const EndpointId kEndpoints[] = { 1, 2 }; |
| const GroupId kGroupId = 0x1234; |
| const GroupId kGroupIdNoAux = 0x5678; |
| const KeysetId kKeyset = 0xabcd; |
| |
| ClusterTester tester(mListener); |
| tester.SetFabricIndex(kTestFabricIndex); |
| tester.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| auto & logOnlyEvents = mTestContext.EventsGenerator(); |
| |
| // 1. Test JoinGroup with useAuxiliaryACL = true -> Should generate event |
| { |
| Commands::JoinGroup::Type data; |
| data.groupID = kGroupId; |
| data.keySetID = kKeyset; |
| data.key = MakeOptional(ByteSpan(key)); |
| data.useAuxiliaryACL = MakeOptional(true); |
| data.endpoints = DataModel::List<const EndpointId>(kEndpoints, MATTER_ARRAY_SIZE(kEndpoints)); |
| |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| AssertStatus(result.status, Protocols::InteractionModel::Status::Success); |
| |
| auto event = logOnlyEvents.GetNextEvent(); |
| ASSERT_TRUE(event.has_value()); |
| EXPECT_EQ(event.value().eventOptions.mPath.mClusterId, Clusters::AccessControl::Id); |
| EXPECT_EQ(event.value().eventOptions.mPath.mEventId, Clusters::AccessControl::Events::AuxiliaryAccessUpdated::Id); |
| |
| Clusters::AccessControl::Events::AuxiliaryAccessUpdated::DecodableType decodedEvent; |
| ASSERT_EQ(event.value().GetEventData(decodedEvent), CHIP_NO_ERROR); |
| EXPECT_EQ(decodedEvent.fabricIndex, kTestFabricIndex); |
| } |
| |
| // 2. Test ConfigureAuxiliaryACL with no change -> Should NOT generate event |
| { |
| Commands::ConfigureAuxiliaryACL::Type data; |
| data.groupID = kGroupId; |
| data.useAuxiliaryACL = true; |
| |
| auto result = tester.Invoke(Commands::ConfigureAuxiliaryACL::Id, data); |
| AssertStatus(result.status, Protocols::InteractionModel::Status::Success); |
| |
| auto event = logOnlyEvents.GetNextEvent(); |
| EXPECT_FALSE(event.has_value()); |
| } |
| |
| // 3. Test ConfigureAuxiliaryACL with change (true to false) -> Should generate event |
| { |
| Commands::ConfigureAuxiliaryACL::Type data; |
| data.groupID = kGroupId; |
| data.useAuxiliaryACL = false; |
| |
| auto result = tester.Invoke(Commands::ConfigureAuxiliaryACL::Id, data); |
| AssertStatus(result.status, Protocols::InteractionModel::Status::Success); |
| |
| auto event = logOnlyEvents.GetNextEvent(); |
| ASSERT_TRUE(event.has_value()); |
| EXPECT_EQ(event.value().eventOptions.mPath.mEventId, Clusters::AccessControl::Events::AuxiliaryAccessUpdated::Id); |
| } |
| |
| // 4. Test LeaveGroup with endpoints (partial remove) -> Should NOT generate event (as hasAuxiliaryACL is false) |
| { |
| Commands::LeaveGroup::Type data; |
| data.groupID = kGroupId; |
| data.endpoints = MakeOptional(DataModel::List<const EndpointId>(kEndpoints, MATTER_ARRAY_SIZE(kEndpoints))); |
| |
| auto result = tester.Invoke(Commands::LeaveGroup::Id, data); |
| AssertStatus(result.status, Protocols::InteractionModel::Status::Success); |
| |
| auto event = logOnlyEvents.GetNextEvent(); |
| EXPECT_FALSE(event.has_value()); |
| } |
| |
| // Re-join with auxiliary ACL for LeaveGroup test |
| { |
| Commands::JoinGroup::Type data; |
| data.groupID = kGroupId; |
| data.keySetID = kKeyset; |
| data.useAuxiliaryACL = MakeOptional(true); |
| data.endpoints = DataModel::List<const EndpointId>(kEndpoints, MATTER_ARRAY_SIZE(kEndpoints)); |
| |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| AssertStatus(result.status, Protocols::InteractionModel::Status::Success); |
| |
| auto event = logOnlyEvents.GetNextEvent(); // Clear the event from JoinGroup |
| ASSERT_TRUE(event.has_value()); |
| } |
| |
| // 5. Test LeaveGroup (whole group) -> Should generate event (as hasAuxiliaryACL is true) |
| { |
| Commands::LeaveGroup::Type data; |
| data.groupID = kGroupId; |
| |
| auto result = tester.Invoke(Commands::LeaveGroup::Id, data); |
| AssertStatus(result.status, Protocols::InteractionModel::Status::Success); |
| |
| auto event = logOnlyEvents.GetNextEvent(); |
| ASSERT_TRUE(event.has_value()); |
| EXPECT_EQ(event.value().eventOptions.mPath.mEventId, Clusters::AccessControl::Events::AuxiliaryAccessUpdated::Id); |
| } |
| |
| // 6. Test JoinGroup for a group WITHOUT auxiliary ACL -> Should NOT generate event |
| { |
| Commands::JoinGroup::Type data; |
| data.groupID = kGroupIdNoAux; |
| data.keySetID = kKeyset; |
| data.useAuxiliaryACL = MakeOptional(false); |
| data.endpoints = DataModel::List<const EndpointId>(kEndpoints, MATTER_ARRAY_SIZE(kEndpoints)); |
| |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| AssertStatus(result.status, Protocols::InteractionModel::Status::Success); |
| |
| auto event = logOnlyEvents.GetNextEvent(); |
| EXPECT_FALSE(event.has_value()); |
| } |
| |
| // 7. Test Add Endpoint to group WITHOUT auxiliary ACL -> Should NOT generate event |
| { |
| const EndpointId kEndpoints2[] = { 3 }; |
| Commands::JoinGroup::Type data; |
| data.groupID = kGroupIdNoAux; |
| data.keySetID = kKeyset; |
| data.useAuxiliaryACL = MakeOptional(false); |
| data.endpoints = DataModel::List<const EndpointId>(kEndpoints2, MATTER_ARRAY_SIZE(kEndpoints2)); |
| |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| AssertStatus(result.status, Protocols::InteractionModel::Status::Success); |
| |
| auto event = logOnlyEvents.GetNextEvent(); |
| EXPECT_FALSE(event.has_value()); |
| } |
| |
| // 8. Test LeaveGroup (partial) for group WITHOUT auxiliary ACL -> Should NOT generate event |
| { |
| const EndpointId kEndpoints2[] = { 2 }; |
| Commands::LeaveGroup::Type data; |
| data.groupID = kGroupIdNoAux; |
| data.endpoints = MakeOptional(DataModel::List<const EndpointId>(kEndpoints2, MATTER_ARRAY_SIZE(kEndpoints2))); |
| |
| auto result = tester.Invoke(Commands::LeaveGroup::Id, data); |
| AssertStatus(result.status, Protocols::InteractionModel::Status::Success); |
| |
| auto event = logOnlyEvents.GetNextEvent(); |
| EXPECT_FALSE(event.has_value()); |
| } |
| |
| // 9. Test JoinGroup with different multicast policy (no updates resulting in ACL change) -> Should NOT generate event |
| { |
| // First, ensure group exists with Aux ACL, endpoint and IANA policy |
| Commands::JoinGroup::Type data; |
| data.groupID = kGroupId; |
| data.keySetID = kKeyset; |
| data.useAuxiliaryACL = MakeOptional(true); |
| data.mcastAddrPolicy = MakeOptional(app::Clusters::Groupcast::MulticastAddrPolicyEnum::kIanaAddr); |
| data.endpoints = DataModel::List<const EndpointId>(kEndpoints, MATTER_ARRAY_SIZE(kEndpoints)); |
| |
| auto setupResult = tester.Invoke(Commands::JoinGroup::Id, data); |
| AssertStatus(setupResult.status, Protocols::InteractionModel::Status::Success); |
| (void) logOnlyEvents.GetNextEvent(); // Clear event from setup |
| |
| // Now call JoinGroup again with same data but different multicast policy |
| data.mcastAddrPolicy = MakeOptional(app::Clusters::Groupcast::MulticastAddrPolicyEnum::kPerGroup); |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| AssertStatus(result.status, Protocols::InteractionModel::Status::Success); |
| |
| auto event = logOnlyEvents.GetNextEvent(); |
| EXPECT_FALSE(event.has_value()); |
| } |
| } |
| |
| TEST_F(TestGroupcastCluster, TestMaxMembershipPerFabric) |
| { |
| const uint8_t key[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; |
| const EndpointId kEndpoints[] = { 1 }; |
| const KeysetId kKeyset = 0xabcd; |
| |
| chip::Testing::ClusterTester tester(mListener); |
| tester.SetFabricIndex(kTestFabricIndex); |
| tester.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| Commands::JoinGroup::Type data; |
| data.keySetID = kKeyset; |
| data.useAuxiliaryACL = MakeOptional(true); |
| data.endpoints = DataModel::List<const EndpointId>(kEndpoints, MATTER_ARRAY_SIZE(kEndpoints)); |
| |
| constexpr uint16_t kMaxPerFabric = GroupDataProviderImpl::kMaxMembershipPerFabric; |
| |
| for (GroupId i = 1; i <= kMaxPerFabric; i++) |
| { |
| data.groupID = i; |
| data.key = (i == 1) ? MakeOptional(ByteSpan(key)) : Optional<ByteSpan>(); |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)) << "JoinGroup for group " << i << " should succeed"; |
| } |
| |
| // Per-fabric overflow |
| data.groupID = kMaxPerFabric + 1; |
| data.key.ClearValue(); |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ResourceExhausted)); |
| } |
| |
| TEST_F(TestGroupcastCluster, TestTotalMaxMembership) |
| { |
| const uint8_t key[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; |
| const EndpointId kEndpoints[] = { 1 }; |
| const KeysetId kKeyset = 0xabcd; |
| |
| constexpr uint16_t kMaxCount = GroupDataProviderImpl::kMaxMembershipCount; |
| constexpr uint16_t kMaxPerFab = GroupDataProviderImpl::kMaxMembershipPerFabric; |
| constexpr uint16_t kFab1Groups = kMaxPerFab - 1; |
| constexpr uint16_t kRemaining = kMaxCount - kFab1Groups; |
| constexpr uint16_t kFab2Groups = kRemaining / 2; |
| constexpr uint16_t kFab3Groups = kRemaining - kFab2Groups; |
| |
| // Set up two additional fabrics |
| FabricIndex fabricIndex2 = 2; |
| FabricIndex fabricIndex3 = 3; |
| ASSERT_EQ(mFabricHelper.AddAdditionalTestFabric(fabricIndex2), CHIP_NO_ERROR); |
| ASSERT_EQ(mFabricHelper.AddAdditionalTestFabric(fabricIndex3), CHIP_NO_ERROR); |
| |
| chip::Testing::ClusterTester tester(mListener); |
| SubjectDescriptor adminSubjectDescriptor = kAdminSubjectDescriptor; |
| |
| Commands::JoinGroup::Type data; |
| data.keySetID = kKeyset; |
| data.useAuxiliaryACL = MakeOptional(true); |
| data.endpoints = DataModel::List<const EndpointId>(kEndpoints, MATTER_ARRAY_SIZE(kEndpoints)); |
| |
| // Fabric 1: kFab1Groups (one below per-fabric limit) |
| tester.SetFabricIndex(kTestFabricIndex); |
| adminSubjectDescriptor.fabricIndex = kTestFabricIndex; |
| tester.SetSubjectDescriptor(adminSubjectDescriptor); |
| |
| for (GroupId i = 1; i <= kFab1Groups; i++) |
| { |
| data.groupID = i; |
| data.key = (i == 1) ? MakeOptional(ByteSpan(key)) : Optional<ByteSpan>(); |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| ASSERT_TRUE(result.IsSuccess()); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| } |
| |
| // Fabric 2: kFab2Groups |
| tester.SetFabricIndex(fabricIndex2); |
| adminSubjectDescriptor.fabricIndex = fabricIndex2; |
| tester.SetSubjectDescriptor(adminSubjectDescriptor); |
| |
| for (GroupId i = 1; i <= kFab2Groups; i++) |
| { |
| data.groupID = i; |
| data.key = (i == 1) ? MakeOptional(ByteSpan(key)) : Optional<ByteSpan>(); |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| ASSERT_TRUE(result.IsSuccess()); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| } |
| |
| // Fabric 3: kFab3Groups |
| tester.SetFabricIndex(fabricIndex3); |
| adminSubjectDescriptor.fabricIndex = fabricIndex3; |
| tester.SetSubjectDescriptor(adminSubjectDescriptor); |
| |
| for (GroupId i = 1; i <= kFab3Groups; i++) |
| { |
| data.groupID = i; |
| data.key = (i == 1) ? MakeOptional(ByteSpan(key)) : Optional<ByteSpan>(); |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| ASSERT_TRUE(result.IsSuccess()); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| } |
| |
| // Node-wide total is now kMaxCount. |
| // Fabric 1 has kFab1Groups (< kMaxPerFab) so the per-fabric check passes, |
| // but the node-wide check should return ResourceExhausted. |
| tester.SetFabricIndex(kTestFabricIndex); |
| adminSubjectDescriptor.fabricIndex = kTestFabricIndex; |
| tester.SetSubjectDescriptor(adminSubjectDescriptor); |
| |
| data.groupID = kFab1Groups + 1; |
| data.key.ClearValue(); |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ResourceExhausted)); |
| } |
| |
| // A LeaveGroup command whose Endpoints list exceeds the maximum constraint ("1 to 20") is rejected |
| // with CONSTRAINT_ERROR, without writing past the fixed-size response buffer. |
| TEST_F(TestGroupcastCluster, TestLeaveGroupEndpointsExceedingMaxReturnsConstraintError) |
| { |
| static constexpr uint16_t kMaxCommandEndpoints = app::Clusters::GroupcastCluster::kMaxCommandEndpoints; |
| // exceeds kMaxMembershipEndpoints (response buffer sizing) and kMaxCommandEndpoints |
| static constexpr uint16_t kEndpointsInGroup = kMaxMembershipEndpoints + 5; |
| const GroupId kGroup = 0xab01; |
| const KeysetId kKeyset = 0xabcd; |
| const uint8_t key[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; |
| |
| chip::Testing::ClusterTester tester(mListener); |
| tester.SetFabricIndex(kTestFabricIndex); |
| tester.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| // Join the group with kEndpointsInGroup distinct endpoints, kMaxCommandEndpoints at a time. |
| { |
| Commands::JoinGroup::Type data; |
| data.groupID = kGroup; |
| data.keySetID = kKeyset; |
| data.key = MakeOptional(ByteSpan(key)); |
| |
| EndpointId nextEndpoint = 1; |
| for (uint16_t added = 0; added < kEndpointsInGroup;) |
| { |
| EndpointId chunk[kMaxCommandEndpoints]; |
| uint16_t count = 0; |
| for (; count < kMaxCommandEndpoints && added < kEndpointsInGroup; count++, added++) |
| { |
| chunk[count] = nextEndpoint++; |
| } |
| data.endpoints = DataModel::List<const EndpointId>(chunk, count); |
| auto result = tester.Invoke(Commands::JoinGroup::Id, data); |
| ASSERT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::Success)); |
| data.key.ClearValue(); |
| } |
| } |
| |
| // Leaving the group while naming more than the maximum number of endpoints is rejected with |
| // CONSTRAINT_ERROR; no response payload is produced. |
| { |
| EndpointId leaveEndpoints[kEndpointsInGroup]; |
| for (uint16_t i = 0; i < kEndpointsInGroup; i++) |
| { |
| leaveEndpoints[i] = static_cast<EndpointId>(i + 1); |
| } |
| |
| Commands::LeaveGroup::Type data; |
| data.groupID = kGroup; |
| data.endpoints = MakeOptional(DataModel::List<const EndpointId>(leaveEndpoints, kEndpointsInGroup)); |
| |
| auto result = tester.Invoke(Commands::LeaveGroup::Id, data); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ConstraintError)); |
| EXPECT_FALSE(result.response.has_value()); |
| } |
| } |
| |
| } // namespace |