/* * Copyright (c) 2024 Project CHIP Authors * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "lib/support/CHIPMem.h" #include #include #include namespace chip { namespace app { namespace { using namespace Clusters; using namespace Clusters::EcosystemInformation; const EndpointId kValidEndpointId = 1; const Structs::DeviceTypeStruct::Type kValidDeviceType = { .deviceType = 0, .revision = 1 }; constexpr Access::SubjectDescriptor kSubjectDescriptor = Testing::kAdminSubjectDescriptor; const FabricIndex kValidFabricIndex = kSubjectDescriptor.fabricIndex; struct RequiredEcosystemDeviceParams { EndpointId originalEndpointId = kValidEndpointId; Structs::DeviceTypeStruct::Type deviceType = kValidDeviceType; FabricIndex fabicIndex = kValidFabricIndex; }; const RequiredEcosystemDeviceParams kDefaultRequiredDeviceParams; const EndpointId kAnotherValidEndpointId = 2; static_assert(kValidEndpointId != kAnotherValidEndpointId); const char * kValidLocationName = "AValidLocationName"; const ClusterId kEcosystemInfoClusterId = EcosystemInformation::Id; const AttributeId kDeviceDirectoryAttributeId = EcosystemInformation::Attributes::DeviceDirectory::Id; const AttributeId kLocationDirectoryAttributeId = EcosystemInformation::Attributes::LocationDirectory::Id; class MockMatterContext : public MatterContext { public: virtual void MarkDirty(EndpointId endpointId, AttributeId attributeId) override { ConcreteAttributePath path(endpointId, kEcosystemInfoClusterId, attributeId); mDirtyMarkedList.push_back(path); } std::vector & GetDirtyList() { return mDirtyMarkedList; } private: std::vector mDirtyMarkedList; }; } // namespace class TestEcosystemInformationCluster : public ::testing::Test { public: TestEcosystemInformationCluster() : mClusterServer(TestOnlyParameter(), mMockMatterContext) {} static void SetUpTestSuite() { ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); } static void TearDownTestSuite() { chip::Platform::MemoryShutdown(); } Clusters::EcosystemInformation::EcosystemInformationServer & EcoInfoCluster() { return mClusterServer; } std::unique_ptr CreateSimplestValidDeviceStruct(const RequiredEcosystemDeviceParams & requiredParams = kDefaultRequiredDeviceParams) { std::unique_ptr deviceInfo = EcosystemDeviceStruct::Builder() .SetOriginalEndpoint(requiredParams.originalEndpointId) .AddDeviceType(requiredParams.deviceType) .SetFabricIndex(requiredParams.fabicIndex) .Build(); VerifyOrDie(deviceInfo); return deviceInfo; } std::unique_ptr CreateValidLocationStruct(const char * requiredLocationName = kValidLocationName) { std::string locationName(requiredLocationName); std::unique_ptr locationInfo = EcosystemLocationStruct::Builder().SetLocationName(locationName).Build(); VerifyOrDie(locationInfo); return locationInfo; } MockMatterContext & GetMockMatterContext() { return mMockMatterContext; } private: MockMatterContext mMockMatterContext; Clusters::EcosystemInformation::EcosystemInformationServer mClusterServer; }; TEST_F(TestEcosystemInformationCluster, UnsupportedClusterWhenReadingDeviceDirectoryOnNewClusterServer) { ConcreteAttributePath path(kValidEndpointId, kEcosystemInfoClusterId, kDeviceDirectoryAttributeId); Testing::ReadOperation testRequest(path); std::unique_ptr encoder = testRequest.StartEncoding(); ASSERT_EQ(EcoInfoCluster().ReadAttribute(path, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedCluster)); } TEST_F(TestEcosystemInformationCluster, UnsupportedClusterWhenReadingLocationDirectoryOnNewClusterServer) { ConcreteAttributePath path(kValidEndpointId, kEcosystemInfoClusterId, kLocationDirectoryAttributeId); Testing::ReadOperation testRequest(path); std::unique_ptr encoder = testRequest.StartEncoding(); ASSERT_EQ(EcoInfoCluster().ReadAttribute(path, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedCluster)); } TEST_F(TestEcosystemInformationCluster, EmptyReadAfterAddEcosystemInformationClusterToEndpoint) { ConcreteAttributePath deviceDirectoryPath(kValidEndpointId, kEcosystemInfoClusterId, kDeviceDirectoryAttributeId); ConcreteAttributePath locationDirectoryPath(kValidEndpointId, kEcosystemInfoClusterId, kLocationDirectoryAttributeId); ASSERT_EQ(EcoInfoCluster().AddEcosystemInformationClusterToEndpoint(kValidEndpointId), CHIP_NO_ERROR); Testing::ReadOperation testDeviceDirectoryRequest(deviceDirectoryPath); std::unique_ptr deviceDirectoryEncoder = testDeviceDirectoryRequest.StartEncoding(); ASSERT_EQ(EcoInfoCluster().ReadAttribute(deviceDirectoryPath, *deviceDirectoryEncoder), CHIP_NO_ERROR); ASSERT_EQ(testDeviceDirectoryRequest.FinishEncoding(), CHIP_NO_ERROR); std::vector deviceDirectoryAttributeData; ASSERT_EQ(testDeviceDirectoryRequest.GetEncodedIBs().Decode(deviceDirectoryAttributeData), CHIP_NO_ERROR); ASSERT_EQ(deviceDirectoryAttributeData.size(), 1u); Testing::DecodedAttributeData & deviceDirectoryEncodedData = deviceDirectoryAttributeData[0]; ASSERT_EQ(deviceDirectoryEncodedData.attributePath, testDeviceDirectoryRequest.GetRequest().path); EcosystemInformation::Attributes::DeviceDirectory::TypeInfo::DecodableType decodableDeviceDirectory; ASSERT_EQ(decodableDeviceDirectory.Decode(deviceDirectoryEncodedData.dataReader), CHIP_NO_ERROR); size_t deviceDirectorySize = 0; ASSERT_EQ(decodableDeviceDirectory.ComputeSize(&deviceDirectorySize), CHIP_NO_ERROR); ASSERT_EQ(deviceDirectorySize, 0u); Testing::ReadOperation testLocationDirectoryRequest(locationDirectoryPath); std::unique_ptr locationDirectoryEncoder = testLocationDirectoryRequest.StartEncoding(); ASSERT_EQ(EcoInfoCluster().ReadAttribute(locationDirectoryPath, *locationDirectoryEncoder), CHIP_NO_ERROR); ASSERT_EQ(testLocationDirectoryRequest.FinishEncoding(), CHIP_NO_ERROR); std::vector locationDirectoryAttributeData; ASSERT_EQ(testLocationDirectoryRequest.GetEncodedIBs().Decode(locationDirectoryAttributeData), CHIP_NO_ERROR); ASSERT_EQ(locationDirectoryAttributeData.size(), 1u); Testing::DecodedAttributeData & locationDirectoryEncodedData = locationDirectoryAttributeData[0]; ASSERT_EQ(locationDirectoryEncodedData.attributePath, testLocationDirectoryRequest.GetRequest().path); EcosystemInformation::Attributes::LocationDirectory::TypeInfo::DecodableType decodableLocationDirectory; ASSERT_EQ(decodableLocationDirectory.Decode(locationDirectoryEncodedData.dataReader), CHIP_NO_ERROR); size_t locationDirectorySize = 0; ASSERT_EQ(decodableLocationDirectory.ComputeSize(&locationDirectorySize), CHIP_NO_ERROR); ASSERT_EQ(locationDirectorySize, 0u); } TEST_F(TestEcosystemInformationCluster, BuildingEcosystemDeviceStruct) { EcosystemDeviceStruct::Builder deviceInfoBuilder; std::unique_ptr deviceInfo = deviceInfoBuilder.Build(); ASSERT_FALSE(deviceInfo); deviceInfoBuilder.SetOriginalEndpoint(1); deviceInfo = deviceInfoBuilder.Build(); ASSERT_FALSE(deviceInfo); auto deviceType = Structs::DeviceTypeStruct::Type(); deviceType.revision = 1; deviceInfoBuilder.AddDeviceType(deviceType); deviceInfo = deviceInfoBuilder.Build(); ASSERT_FALSE(deviceInfo); deviceInfoBuilder.SetFabricIndex(1); deviceInfo = deviceInfoBuilder.Build(); ASSERT_TRUE(deviceInfo); // Building a second device info with previously successfully built deviceInfoBuilder // is expected to fail. std::unique_ptr secondDeviceInfo = deviceInfoBuilder.Build(); ASSERT_FALSE(secondDeviceInfo); } TEST_F(TestEcosystemInformationCluster, BuildingInvalidEcosystemDeviceStruct) { auto deviceType = Structs::DeviceTypeStruct::Type(); deviceType.revision = 1; const FabricIndex kFabricIndexTooLow = 0; const FabricIndex kFabricIndexTooHigh = kMaxValidFabricIndex + 1; EcosystemDeviceStruct::Builder deviceInfoBuilder; deviceInfoBuilder.SetOriginalEndpoint(1); deviceInfoBuilder.AddDeviceType(deviceType); deviceInfoBuilder.SetFabricIndex(kFabricIndexTooLow); std::unique_ptr deviceInfo = deviceInfoBuilder.Build(); ASSERT_FALSE(deviceInfo); deviceInfoBuilder.SetFabricIndex(kFabricIndexTooHigh); deviceInfo = deviceInfoBuilder.Build(); ASSERT_FALSE(deviceInfo); deviceInfoBuilder.SetFabricIndex(1); // At this point deviceInfoBuilder would be able to be built successfully. std::string nameThatsTooLong(65, 'x'); uint64_t nameEpochValueUs = 0; // This values doesn't matter. deviceInfoBuilder.SetDeviceName(std::move(nameThatsTooLong), nameEpochValueUs); deviceInfo = deviceInfoBuilder.Build(); ASSERT_FALSE(deviceInfo); // Ending unit test by building something that should work just to make sure // Builder isn't silently failing on building for some other reason. std::string nameThatsMaxLength(64, 'x'); deviceInfoBuilder.SetDeviceName(std::move(nameThatsMaxLength), nameEpochValueUs); deviceInfo = deviceInfoBuilder.Build(); ASSERT_TRUE(deviceInfo); } TEST_F(TestEcosystemInformationCluster, AddDeviceInfoInvalidArguments) { ASSERT_EQ(EcoInfoCluster().AddDeviceInfo(kValidEndpointId, nullptr), CHIP_ERROR_INVALID_ARGUMENT); std::unique_ptr deviceInfo = CreateSimplestValidDeviceStruct(); ASSERT_TRUE(deviceInfo); ASSERT_EQ(EcoInfoCluster().AddDeviceInfo(kRootEndpointId, std::move(deviceInfo)), CHIP_ERROR_INVALID_ARGUMENT); deviceInfo = CreateSimplestValidDeviceStruct(); ASSERT_TRUE(deviceInfo); ASSERT_EQ(EcoInfoCluster().AddDeviceInfo(kInvalidEndpointId, std::move(deviceInfo)), CHIP_ERROR_INVALID_ARGUMENT); } TEST_F(TestEcosystemInformationCluster, AddDeviceInfo) { std::unique_ptr deviceInfo = CreateSimplestValidDeviceStruct(); // originalEndpoint and path endpoint do not need to be the same, for that reason we use a different value for // path endpoint static_assert(kAnotherValidEndpointId != kValidEndpointId); ASSERT_EQ(EcoInfoCluster().AddDeviceInfo(kAnotherValidEndpointId, std::move(deviceInfo)), CHIP_NO_ERROR); ConcreteAttributePath deviceDirectoryPath(kAnotherValidEndpointId, kEcosystemInfoClusterId, kDeviceDirectoryAttributeId); Testing::ReadOperation testDeviceDirectoryRequest(deviceDirectoryPath); testDeviceDirectoryRequest.SetSubjectDescriptor(kSubjectDescriptor); std::unique_ptr deviceDirectoryEncoder = testDeviceDirectoryRequest.StartEncoding(); ASSERT_EQ(EcoInfoCluster().ReadAttribute(deviceDirectoryPath, *deviceDirectoryEncoder), CHIP_NO_ERROR); ASSERT_EQ(testDeviceDirectoryRequest.FinishEncoding(), CHIP_NO_ERROR); std::vector attributeData; ASSERT_EQ(testDeviceDirectoryRequest.GetEncodedIBs().Decode(attributeData), CHIP_NO_ERROR); ASSERT_EQ(attributeData.size(), 1u); Testing::DecodedAttributeData & encodedData = attributeData[0]; ASSERT_EQ(encodedData.attributePath, testDeviceDirectoryRequest.GetRequest().path); EcosystemInformation::Attributes::DeviceDirectory::TypeInfo::DecodableType decodableDeviceDirectory; ASSERT_EQ(decodableDeviceDirectory.Decode(encodedData.dataReader), CHIP_NO_ERROR); size_t size = 0; ASSERT_EQ(decodableDeviceDirectory.ComputeSize(&size), CHIP_NO_ERROR); ASSERT_EQ(size, 1u); auto iterator = decodableDeviceDirectory.begin(); ASSERT_TRUE(iterator.Next()); auto deviceDirectoryEntry = iterator.GetValue(); ASSERT_FALSE(deviceDirectoryEntry.deviceName.HasValue()); ASSERT_FALSE(deviceDirectoryEntry.deviceNameLastEdit.HasValue()); ASSERT_EQ(deviceDirectoryEntry.bridgedEndpoint, kInvalidEndpointId); ASSERT_EQ(deviceDirectoryEntry.originalEndpoint, kValidEndpointId); size_t deviceTypeListSize = 0; ASSERT_EQ(deviceDirectoryEntry.deviceTypes.ComputeSize(&deviceTypeListSize), CHIP_NO_ERROR); ASSERT_EQ(deviceTypeListSize, 1u); auto deviceTypeIterator = deviceDirectoryEntry.deviceTypes.begin(); ASSERT_TRUE(deviceTypeIterator.Next()); auto deviceTypeEntry = deviceTypeIterator.GetValue(); ASSERT_EQ(deviceTypeEntry.deviceType, 0u); ASSERT_EQ(deviceTypeEntry.revision, 1); ASSERT_FALSE(deviceTypeIterator.Next()); size_t uniqueLocationIdListSize = 0; ASSERT_EQ(deviceDirectoryEntry.uniqueLocationIDs.ComputeSize(&uniqueLocationIdListSize), CHIP_NO_ERROR); ASSERT_EQ(uniqueLocationIdListSize, 0u); ASSERT_EQ(deviceDirectoryEntry.uniqueLocationIDsLastEdit, 0u); ASSERT_EQ(deviceDirectoryEntry.fabricIndex, kSubjectDescriptor.fabricIndex); ASSERT_FALSE(iterator.Next()); } TEST_F(TestEcosystemInformationCluster, AddDeviceInfoResultInMarkDirty) { std::unique_ptr deviceInfo = CreateSimplestValidDeviceStruct(); ASSERT_EQ(EcoInfoCluster().AddDeviceInfo(kValidEndpointId, std::move(deviceInfo)), CHIP_NO_ERROR); auto markedDirtyList = GetMockMatterContext().GetDirtyList(); ASSERT_EQ(markedDirtyList.size(), 1u); ConcreteAttributePath path = markedDirtyList[0]; ASSERT_EQ(path.mEndpointId, kValidEndpointId); ASSERT_EQ(path.mClusterId, kEcosystemInfoClusterId); ASSERT_EQ(path.mAttributeId, kDeviceDirectoryAttributeId); } TEST_F(TestEcosystemInformationCluster, BuildingEcosystemLocationStruct) { EcosystemLocationStruct::Builder locationInfoBuilder; std::string validLocationName = "validName"; locationInfoBuilder.SetLocationName(validLocationName); std::unique_ptr locationInfo = locationInfoBuilder.Build(); ASSERT_TRUE(locationInfo); // Building a second device info with previously successfully built deviceInfoBuilder // is expected to fail. locationInfo = locationInfoBuilder.Build(); ASSERT_FALSE(locationInfo); } TEST_F(TestEcosystemInformationCluster, BuildingInvalidEcosystemLocationStruct) { EcosystemLocationStruct::Builder locationInfoBuilder; std::string nameThatsTooLong(129, 'x'); locationInfoBuilder.SetLocationName(nameThatsTooLong); std::unique_ptr locationInfo = locationInfoBuilder.Build(); ASSERT_FALSE(locationInfo); // Ending unit test by building something that should work just to make sure // Builder isn't silently failing on building for some other reason. std::string nameThatsMaxLength(128, 'x'); locationInfoBuilder.SetLocationName(nameThatsMaxLength); locationInfo = locationInfoBuilder.Build(); ASSERT_TRUE(locationInfo); } TEST_F(TestEcosystemInformationCluster, AddLocationInfoInvalidArguments) { const FabricIndex kFabricIndexTooLow = 0; const FabricIndex kFabricIndexTooHigh = kMaxValidFabricIndex + 1; const std::string kEmptyLocationIdStr; const std::string kValidLocationIdStr = "SomeLocationString"; const std::string kInvalidLocationIdTooLongStr(65, 'x'); std::unique_ptr locationInfo = CreateValidLocationStruct(); ASSERT_TRUE(locationInfo); ASSERT_EQ(EcoInfoCluster().AddLocationInfo(kInvalidEndpointId, kValidLocationIdStr, kValidFabricIndex, std::move(locationInfo)), CHIP_ERROR_INVALID_ARGUMENT); locationInfo = CreateValidLocationStruct(); ASSERT_TRUE(locationInfo); ASSERT_EQ(EcoInfoCluster().AddLocationInfo(kRootEndpointId, kValidLocationIdStr, kValidFabricIndex, std::move(locationInfo)), CHIP_ERROR_INVALID_ARGUMENT); locationInfo = CreateValidLocationStruct(); ASSERT_TRUE(locationInfo); ASSERT_EQ(EcoInfoCluster().AddLocationInfo(kValidEndpointId, kEmptyLocationIdStr, kValidFabricIndex, std::move(locationInfo)), CHIP_ERROR_INVALID_ARGUMENT); locationInfo = CreateValidLocationStruct(); ASSERT_TRUE(locationInfo); ASSERT_EQ(EcoInfoCluster().AddLocationInfo(kValidEndpointId, kInvalidLocationIdTooLongStr, kValidFabricIndex, std::move(locationInfo)), CHIP_ERROR_INVALID_ARGUMENT); locationInfo = CreateValidLocationStruct(); ASSERT_TRUE(locationInfo); ASSERT_EQ(EcoInfoCluster().AddLocationInfo(kValidEndpointId, kValidLocationIdStr, kFabricIndexTooLow, std::move(locationInfo)), CHIP_ERROR_INVALID_ARGUMENT); locationInfo = CreateValidLocationStruct(); ASSERT_TRUE(locationInfo); ASSERT_EQ(EcoInfoCluster().AddLocationInfo(kValidEndpointId, kValidLocationIdStr, kFabricIndexTooHigh, std::move(locationInfo)), CHIP_ERROR_INVALID_ARGUMENT); // Sanity check that we can successfully add something after all the previously failed attempts locationInfo = CreateValidLocationStruct(); ASSERT_TRUE(locationInfo); ASSERT_EQ(EcoInfoCluster().AddLocationInfo(kValidEndpointId, kValidLocationIdStr, kValidFabricIndex, std::move(locationInfo)), CHIP_NO_ERROR); // Adding a second identical entry is expected to fail locationInfo = CreateValidLocationStruct(); ASSERT_TRUE(locationInfo); ASSERT_EQ(EcoInfoCluster().AddLocationInfo(kValidEndpointId, kValidLocationIdStr, kValidFabricIndex, std::move(locationInfo)), CHIP_ERROR_INVALID_ARGUMENT); } TEST_F(TestEcosystemInformationCluster, AddLocationInfo) { std::unique_ptr locationInfo = CreateValidLocationStruct(); const char * kValidLocationIdStr = "SomeLocationIdString"; ASSERT_EQ(EcoInfoCluster().AddLocationInfo(kValidEndpointId, kValidLocationIdStr, Testing::kAdminSubjectDescriptor.fabricIndex, std::move(locationInfo)), CHIP_NO_ERROR); ConcreteAttributePath locationDirectoryPath(kValidEndpointId, kEcosystemInfoClusterId, kLocationDirectoryAttributeId); Testing::ReadOperation testLocationDirectoryRequest(locationDirectoryPath); testLocationDirectoryRequest.SetSubjectDescriptor(Testing::kAdminSubjectDescriptor); std::unique_ptr locationDirectoryEncoder = testLocationDirectoryRequest.StartEncoding(); ASSERT_EQ(EcoInfoCluster().ReadAttribute(locationDirectoryPath, *locationDirectoryEncoder), CHIP_NO_ERROR); ASSERT_EQ(testLocationDirectoryRequest.FinishEncoding(), CHIP_NO_ERROR); std::vector locationDirectoryAttributeData; ASSERT_EQ(testLocationDirectoryRequest.GetEncodedIBs().Decode(locationDirectoryAttributeData), CHIP_NO_ERROR); ASSERT_EQ(locationDirectoryAttributeData.size(), 1u); Testing::DecodedAttributeData & locationDirectoryEncodedData = locationDirectoryAttributeData[0]; ASSERT_EQ(locationDirectoryEncodedData.attributePath, testLocationDirectoryRequest.GetRequest().path); EcosystemInformation::Attributes::LocationDirectory::TypeInfo::DecodableType decodableLocationDirectory; ASSERT_EQ(decodableLocationDirectory.Decode(locationDirectoryEncodedData.dataReader), CHIP_NO_ERROR); size_t locationDirectorySize = 0; ASSERT_EQ(decodableLocationDirectory.ComputeSize(&locationDirectorySize), CHIP_NO_ERROR); ASSERT_EQ(locationDirectorySize, 1u); auto iterator = decodableLocationDirectory.begin(); ASSERT_TRUE(iterator.Next()); auto locationDirectoryEntry = iterator.GetValue(); ASSERT_TRUE(locationDirectoryEntry.uniqueLocationID.data_equal(CharSpan::fromCharString(kValidLocationIdStr))); ASSERT_TRUE(locationDirectoryEntry.locationDescriptor.locationName.data_equal(CharSpan::fromCharString(kValidLocationName))); ASSERT_TRUE(locationDirectoryEntry.locationDescriptor.floorNumber.IsNull()); ASSERT_TRUE(locationDirectoryEntry.locationDescriptor.areaType.IsNull()); ASSERT_EQ(locationDirectoryEntry.locationDescriptorLastEdit, 0u); ASSERT_EQ(locationDirectoryEntry.fabricIndex, Testing::kAdminSubjectDescriptor.fabricIndex); ASSERT_FALSE(iterator.Next()); } TEST_F(TestEcosystemInformationCluster, AddLocationInfoResultInMarkDirty) { std::unique_ptr locationInfo = CreateValidLocationStruct(); const char * kValidLocationIdStr = "SomeLocationIdString"; ASSERT_EQ(EcoInfoCluster().AddLocationInfo(kValidEndpointId, kValidLocationIdStr, Testing::kAdminSubjectDescriptor.fabricIndex, std::move(locationInfo)), CHIP_NO_ERROR); auto markedDirtyList = GetMockMatterContext().GetDirtyList(); ASSERT_EQ(markedDirtyList.size(), 1u); ConcreteAttributePath path = markedDirtyList[0]; ASSERT_EQ(path.mEndpointId, kValidEndpointId); ASSERT_EQ(path.mClusterId, kEcosystemInfoClusterId); ASSERT_EQ(path.mAttributeId, kLocationDirectoryAttributeId); } } // namespace app } // namespace chip