From 45e971530d949b0e3e568686054bdec5f2186bc1 Mon Sep 17 00:00:00 2001 From: C Freeman Date: Thu, 3 Aug 2023 22:18:23 -0400 Subject: [PATCH] Power source: Implementation of dynamic endpoint list setter (#28110) * Implementation of dynamic endpoint list setter Test: tested by adding a call to set endpoint list in all clusters with chip-tool. Also see TestPowerSourceCluster.cpp * Restyled by whitespace * Restyled by gn * Remove the define Some platforms run these tests, but don't have that define defined. Instead, just change the function name to test only. * Address review comments * free is being called, man, where's the leak? * Restyled by clang-format * Address some review comments. * Fix leak. * Deal with zero-length arrays. * shutdown -> clear * Use EncodeList for list * test fix. * types need to match --------- Co-authored-by: Restyled.io --- .../power-source-server.cpp | 181 +++++++++- .../power-source-server/power-source-server.h | 66 ++++ src/app/tests/BUILD.gn | 14 + src/app/tests/TestPowerSourceCluster.cpp | 339 ++++++++++++++++++ 4 files changed, 583 insertions(+), 17 deletions(-) create mode 100644 src/app/clusters/power-source-server/power-source-server.h create mode 100644 src/app/tests/TestPowerSourceCluster.cpp diff --git a/src/app/clusters/power-source-server/power-source-server.cpp b/src/app/clusters/power-source-server/power-source-server.cpp index a5163df93b2895..b09e25d7ad75b5 100644 --- a/src/app/clusters/power-source-server/power-source-server.cpp +++ b/src/app/clusters/power-source-server/power-source-server.cpp @@ -19,32 +19,82 @@ * @brief Implementation for the Power Source Server Cluster ***************************************************************************/ +#include "power-source-server.h" + #include #include -#include -#include #include #include #include +#include using namespace chip; -using namespace chip::app; -using namespace chip::app::Clusters; -using namespace chip::app::Clusters::PowerSource::Attributes; +using namespace app; +using namespace app::Clusters; +using namespace app::Clusters::PowerSource::Attributes; namespace { -class PowerSourceAttrAccess : public AttributeAccessInterface +struct PowerSourceClusterInfo { -public: - // Register on all endpoints. - PowerSourceAttrAccess() : AttributeAccessInterface(Optional::Missing(), PowerSource::Id) {} - - CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; + PowerSourceClusterInfo() : mClusterEndpoint(kInvalidEndpointId) {} + explicit PowerSourceClusterInfo(EndpointId powerClusterEndpointId) : mClusterEndpoint(powerClusterEndpointId) {} + void Clear() + { + mBuf.Free(); + mEndpointList = Span(); + } + CHIP_ERROR SetEndpointList(Span endpointList) + { + Clear(); + if (endpointList.size() == 0) + { + mEndpointList = Span(); + return CHIP_NO_ERROR; + } + mBuf.Calloc(endpointList.size()); + if (mBuf.Get() == nullptr) + { + return CHIP_ERROR_NO_MEMORY; + } + memcpy(mBuf.Get(), endpointList.data(), endpointList.size() * sizeof(EndpointId)); + mEndpointList = Span(mBuf.Get(), endpointList.size()); + return CHIP_NO_ERROR; + } + EndpointId mClusterEndpoint = kInvalidEndpointId; + Platform::ScopedMemoryBuffer mBuf; + Span mEndpointList; }; +PowerSourceServer gPowerSourceServer; + PowerSourceAttrAccess gAttrAccess; +#ifdef ZCL_USING_POWER_SOURCE_CLUSTER_SERVER +#define POWER_SERVER_NUM_SUPPORTED_ENDPOINTS \ + (EMBER_AF_POWER_SOURCE_CLUSTER_SERVER_ENDPOINT_COUNT + CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT) +#else +#define POWER_SERVER_NUM_SUPPORTED_ENDPOINTS CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT +#endif +static constexpr size_t kNumSupportedEndpoints = POWER_SERVER_NUM_SUPPORTED_ENDPOINTS; + +#if POWER_SERVER_NUM_SUPPORTED_ENDPOINTS > 0 +PowerSourceClusterInfo sPowerSourceClusterInfo[kNumSupportedEndpoints] = {}; +#else +PowerSourceClusterInfo * sPowerSourceClusterInfo = nullptr; +#endif + +} // anonymous namespace + +void MatterPowerSourcePluginServerInitCallback() +{ + registerAttributeAccessOverride(&gAttrAccess); +} + +namespace chip { +namespace app { +namespace Clusters { + CHIP_ERROR PowerSourceAttrAccess::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) { CHIP_ERROR err = CHIP_NO_ERROR; @@ -55,10 +105,25 @@ CHIP_ERROR PowerSourceAttrAccess::Read(const ConcreteReadAttributePath & aPath, // TODO: Needs implementation. err = aEncoder.EncodeEmptyList(); break; - case EndpointList::Id: - // TODO: Needs implementation and a way to allow dynamic endpoints to register endpoints - err = aEncoder.EncodeEmptyList(); + case EndpointList::Id: { + PowerSourceServer & server = PowerSourceServer::Instance(); + const Span * span = server.GetEndpointList(aPath.mEndpointId); + if (span == nullptr) + { + err = aEncoder.EncodeEmptyList(); + } + else + { + err = aEncoder.EncodeList([span](const auto & encoder) -> CHIP_ERROR { + for (auto id : *span) + { + ReturnErrorOnFailure(encoder.Encode(id)); + } + return CHIP_NO_ERROR; + }); + } break; + } default: break; } @@ -66,9 +131,91 @@ CHIP_ERROR PowerSourceAttrAccess::Read(const ConcreteReadAttributePath & aPath, return err; } -} // anonymous namespace +PowerSourceAttrAccess & TestOnlyGetPowerSourceAttrAccess() +{ + return gAttrAccess; +} -void MatterPowerSourcePluginServerInitCallback() +PowerSourceServer & PowerSourceServer::Instance() { - registerAttributeAccessOverride(&gAttrAccess); + return gPowerSourceServer; +} + +// Caller does not need to retain the span past the call point as these are copied into an internal storage +CHIP_ERROR PowerSourceServer::SetEndpointList(EndpointId powerSourceClusterEndpoint, Span endpointList) +{ + // TODO: should check here that the power source cluster exists on the endpoint, but for now let's take the caller's word + // for it + + size_t idx = PowerSourceClusterEndpointIndex(powerSourceClusterEndpoint); + if (idx >= kNumSupportedEndpoints) + { + idx = NextEmptyIndex(); + } + if (idx >= kNumSupportedEndpoints) + { + return CHIP_ERROR_NO_MEMORY; + } + + sPowerSourceClusterInfo[idx].Clear(); + if (endpointList.size() == 0) + { + sPowerSourceClusterInfo[idx] = PowerSourceClusterInfo(); + } + else + { + sPowerSourceClusterInfo[idx] = PowerSourceClusterInfo(powerSourceClusterEndpoint); + sPowerSourceClusterInfo[idx].SetEndpointList(endpointList); + } + return CHIP_NO_ERROR; +} +const Span * PowerSourceServer::GetEndpointList(EndpointId powerSourceClusterEndpoint) const +{ + size_t idx = PowerSourceClusterEndpointIndex(powerSourceClusterEndpoint); + if (idx != std::numeric_limits::max()) + { + return &sPowerSourceClusterInfo[idx].mEndpointList; + } + return nullptr; +} + +void PowerSourceServer::Shutdown() +{ + for (size_t i = 0; i < kNumSupportedEndpoints; ++i) + { + sPowerSourceClusterInfo[i].Clear(); + } +} + +size_t PowerSourceServer::GetNumSupportedEndpointLists() const +{ + return kNumSupportedEndpoints; } + +size_t PowerSourceServer::PowerSourceClusterEndpointIndex(EndpointId endpointId) const +{ + for (size_t i = 0; i < kNumSupportedEndpoints; ++i) + { + if (sPowerSourceClusterInfo[i].mClusterEndpoint == endpointId) + { + return i; + } + } + return std::numeric_limits::max(); +} + +size_t PowerSourceServer::NextEmptyIndex() const +{ + for (size_t i = 0; i < kNumSupportedEndpoints; ++i) + { + if (sPowerSourceClusterInfo[i].mClusterEndpoint == kInvalidEndpointId) + { + return i; + } + } + return std::numeric_limits::max(); +} + +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/src/app/clusters/power-source-server/power-source-server.h b/src/app/clusters/power-source-server/power-source-server.h new file mode 100644 index 00000000000000..a02f54cd5375c1 --- /dev/null +++ b/src/app/clusters/power-source-server/power-source-server.h @@ -0,0 +1,66 @@ +/* + * + * Copyright (c) 2023 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. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace app { +namespace Clusters { + +class PowerSourceServer +{ +public: + static PowerSourceServer & Instance(); + + // Caller does not need to retain the span past the call point as these are copied into an internal storage + CHIP_ERROR SetEndpointList(EndpointId powerSourceClusterEndpoint, Span endpointList); + CHIP_ERROR ClearEndpointList(EndpointId powerSourceClusterEndpoint) + { + return SetEndpointList(powerSourceClusterEndpoint, Span()); + } + // returns nullptr if there's not endpoint list set for this power source cluster endpoint id. + const Span * GetEndpointList(EndpointId powerSourceClusterEndpoint) const; + void Shutdown(); + size_t GetNumSupportedEndpointLists() const; + +private: + // Both return std::numeric_limits::max() for not found + size_t PowerSourceClusterEndpointIndex(EndpointId endpointId) const; + size_t NextEmptyIndex() const; +}; + +class PowerSourceAttrAccess : public AttributeAccessInterface +{ +public: + // Register on all endpoints. + PowerSourceAttrAccess() : AttributeAccessInterface(Optional::Missing(), PowerSource::Id) {} + + CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; +}; + +PowerSourceAttrAccess & TestOnlyGetPowerSourceAttrAccess(); + +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn index 90b4796fef3c47..9078702fb983e9 100644 --- a/src/app/tests/BUILD.gn +++ b/src/app/tests/BUILD.gn @@ -78,6 +78,18 @@ source_set("time-sync-data-provider-test-srcs") { ] } +source_set("power-cluster-test-srcs") { + sources = [ + "${chip_root}/src/app/clusters/power-source-server/power-source-server.cpp", + ] + + public_deps = [ + "${chip_root}/src/app/common:cluster-objects", + "${chip_root}/src/app/util/mock:mock_ember", + "${chip_root}/src/lib/core", + ] +} + source_set("scenes-table-test-srcs") { sources = [ "${chip_root}/src/app/clusters/scenes-server/ExtensionFieldSets.h", @@ -133,6 +145,7 @@ chip_test_suite("tests") { "TestNumericAttributeTraits.cpp", "TestOperationalStateDelegate.cpp", "TestPendingNotificationMap.cpp", + "TestPowerSourceCluster.cpp", "TestReadInteraction.cpp", "TestReportingEngine.cpp", "TestSceneTable.cpp", @@ -183,6 +196,7 @@ chip_test_suite("tests") { ":binding-test-srcs", ":operational-state-test-srcs", ":ota-requestor-test-srcs", + ":power-cluster-test-srcs", ":scenes-table-test-srcs", ":time-sync-data-provider-test-srcs", "${chip_root}/src/app", diff --git a/src/app/tests/TestPowerSourceCluster.cpp b/src/app/tests/TestPowerSourceCluster.cpp new file mode 100644 index 00000000000000..e224571a45780d --- /dev/null +++ b/src/app/tests/TestPowerSourceCluster.cpp @@ -0,0 +1,339 @@ +/* + * + * Copyright (c) 2021 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace chip { +namespace app { + +class TestPowerSourceCluster +{ +public: + static void TestEndpointList(nlTestSuite * apSuite, void * apContext); +}; + +std::vector ReadEndpointsThroughAttributeReader(nlTestSuite * apSuite, EndpointId endpoint) +{ + Clusters::PowerSourceAttrAccess & attrAccess = Clusters::TestOnlyGetPowerSourceAttrAccess(); + CHIP_ERROR err = CHIP_NO_ERROR; + + // Write TLV through the attribute access interface into the buffer + + // Buffer setup + constexpr size_t buflen = 128; + uint8_t buf[buflen]; + memset(buf, 0, buflen); + + // Create the builders + TLV::TLVWriter tlvWriter; + tlvWriter.Init(buf); + + AttributeReportIBs::Builder builder; + builder.Init(&tlvWriter); + + ConcreteAttributePath path(endpoint, Clusters::PowerSource::Id, Clusters::PowerSource::Attributes::EndpointList::Id); + ConcreteReadAttributePath readPath(path); + chip::DataVersion dataVersion(0); + AttributeValueEncoder aEncoder(builder, 0, path, dataVersion); + + err = attrAccess.Read(readPath, aEncoder); + + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + // Read out from the buffer. This comes back as a nested struct + // AttributeReportIBs is a list of + // AttributeReportIB structs containing + // AttributeDataIB struct, which holds DataVersion (tag 0), AttributePathIB (tag 1) and Data (tag 2) + + TLV::TLVReader reader; + reader.Init(buf); + + TLV::TLVReader attrReportsReader; + TLV::TLVReader attrReportReader; + TLV::TLVReader attrDataReader; + + reader.Next(); + reader.OpenContainer(attrReportsReader); + + attrReportsReader.Next(); + attrReportsReader.OpenContainer(attrReportReader); + + attrReportReader.Next(); + attrReportReader.OpenContainer(attrDataReader); + + // We're now in the attribute data IB, skip to the desired tag, we want TagNum = 2 + attrDataReader.Next(); + for (int i = 0; i < 3 && !(IsContextTag(attrDataReader.GetTag()) && TagNumFromTag(attrDataReader.GetTag()) == 2); ++i) + { + attrDataReader.Next(); + } + NL_TEST_ASSERT(apSuite, IsContextTag(attrDataReader.GetTag())); + NL_TEST_ASSERT(apSuite, TagNumFromTag(attrDataReader.GetTag()) == 2); + + // OK, we should be in the right spot now, let's decode the list. + Clusters::PowerSource::Attributes::EndpointList::TypeInfo::DecodableType list; + err = list.Decode(attrDataReader); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + std::vector ret; + auto iter = list.begin(); + while (iter.Next()) + { + ret.push_back(iter.GetValue()); + } + return ret; +} + +void TestPowerSourceCluster::TestEndpointList(nlTestSuite * apSuite, void * apContext) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + Clusters::PowerSourceServer & powerSourceServer = Clusters::PowerSourceServer::Instance(); + + // test that when we read everything we get an empty list as nothing has been set up yet + for (EndpointId i = 0; i < 11; ++i) + { + std::vector vec = ReadEndpointsThroughAttributeReader(apSuite, i); + NL_TEST_ASSERT(apSuite, vec.size() == 0); + } + + if (powerSourceServer.GetNumSupportedEndpointLists() < 2 || + powerSourceServer.GetNumSupportedEndpointLists() > std::numeric_limits::max()) + { + // Test assumes at least two endpoints. This runs on linux, not worthwhile to run on platforms with fewer endpoints. + return; + } + + // ***************** + // Test setting, getting and reading through the attribute access interface + // ***************** + EndpointId list0[5] = { 1, 2, 3, 4, 5 }; + EndpointId list1[10] = { 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; + EndpointId listRest[1] = { 2 }; + + // we checked earlier that this fit + // This test just uses endpoints in order, so we want to set endpoints from + // 0 to numEndpoints - 1, and use this for overflow checking + EndpointId numEndpoints = static_cast(powerSourceServer.GetNumSupportedEndpointLists()); + + // Endpoint 0 - list of 5 + err = powerSourceServer.SetEndpointList(0, Span(list0)); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + const Span * readBack = powerSourceServer.GetEndpointList(0); + NL_TEST_EXIT_ON_FAILED_ASSERT(apSuite, readBack != nullptr); + NL_TEST_ASSERT(apSuite, readBack->size() == 5); + for (size_t i = 0; i < readBack->size(); ++i) + { + NL_TEST_ASSERT(apSuite, readBack->data()[i] == list0[i]); + } + + // Endpoint 1 - list of 10 + err = powerSourceServer.SetEndpointList(1, Span(list1)); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + readBack = powerSourceServer.GetEndpointList(1); + NL_TEST_EXIT_ON_FAILED_ASSERT(apSuite, readBack != nullptr); + NL_TEST_ASSERT(apSuite, readBack->size() == 10); + for (size_t i = 0; i < readBack->size(); ++i) + { + NL_TEST_ASSERT(apSuite, readBack->data()[i] == list1[i]); + } + + // Remaining endpoints - list of 1 + for (EndpointId ep = 2; ep < numEndpoints; ++ep) + { + err = powerSourceServer.SetEndpointList(ep, Span(listRest)); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + readBack = powerSourceServer.GetEndpointList(ep); + NL_TEST_EXIT_ON_FAILED_ASSERT(apSuite, readBack != nullptr); + NL_TEST_ASSERT(apSuite, readBack->size() == 1); + if (readBack->size() == 1) + { + NL_TEST_ASSERT(apSuite, readBack->data()[0] == listRest[0]); + } + } + + // ***************** + // Check for out of memory error when setting too many endpoints + // ***************** + // pick a random endpoint number for the power cluster - it doesn't matter, we don't have space anyway. + err = powerSourceServer.SetEndpointList(55, Span(listRest)); + NL_TEST_ASSERT(apSuite, err == CHIP_ERROR_NO_MEMORY); + + // ***************** + // Recheck getting and reading after OOM + // ***************** + // EP0 + readBack = powerSourceServer.GetEndpointList(0); + NL_TEST_EXIT_ON_FAILED_ASSERT(apSuite, readBack != nullptr); + NL_TEST_ASSERT(apSuite, readBack->size() == 5); + for (size_t i = 0; i < readBack->size(); ++i) + { + NL_TEST_ASSERT(apSuite, readBack->data()[i] == list0[i]); + } + + // EP1 + readBack = powerSourceServer.GetEndpointList(1); + NL_TEST_EXIT_ON_FAILED_ASSERT(apSuite, readBack != nullptr); + NL_TEST_ASSERT(apSuite, readBack->size() == 10); + for (size_t i = 0; i < readBack->size(); ++i) + { + NL_TEST_ASSERT(apSuite, readBack->data()[i] == list1[i]); + } + + // Remaining endpoints + for (EndpointId ep = 2; ep < numEndpoints; ++ep) + { + readBack = powerSourceServer.GetEndpointList(ep); + NL_TEST_EXIT_ON_FAILED_ASSERT(apSuite, readBack != nullptr); + NL_TEST_ASSERT(apSuite, readBack->size() == 1); + if (readBack->size() == 1) + { + NL_TEST_ASSERT(apSuite, readBack->data()[0] == listRest[0]); + } + } + + // ***************** + // Test overwriting + // ***************** + // Overwrite a list + err = powerSourceServer.SetEndpointList(1, Span(listRest)); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + readBack = powerSourceServer.GetEndpointList(1); + NL_TEST_ASSERT(apSuite, readBack->size() == 1); + if (readBack->size() == 1) + { + NL_TEST_ASSERT(apSuite, readBack->data()[0] == listRest[0]); + } + + // Ensure only the overwritten list was changed, using read interface + for (EndpointId ep = 0; ep < numEndpoints + 1; ++ep) + { + std::vector vec = ReadEndpointsThroughAttributeReader(apSuite, ep); + if (ep == 0) + { + NL_TEST_ASSERT(apSuite, vec.size() == 5); + for (size_t j = 0; j < vec.size(); ++j) + { + NL_TEST_ASSERT(apSuite, vec[j] == list0[j]); + } + } + else if (ep == numEndpoints) + { + NL_TEST_ASSERT(apSuite, vec.size() == 0); + } + else + { + NL_TEST_ASSERT(apSuite, vec.size() == 1); + if (vec.size() == 1) + { + NL_TEST_ASSERT(apSuite, vec[0] == listRest[0]); + } + } + } + + // ***************** + // Test removal + // ***************** + for (EndpointId ep = 0; ep < numEndpoints; ++ep) + { + err = powerSourceServer.SetEndpointList(ep, Span()); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + readBack = powerSourceServer.GetEndpointList(ep); + NL_TEST_ASSERT(apSuite, readBack == nullptr); + } + + // Check through the read interface + for (EndpointId ep = 0; ep < numEndpoints + 1; ++ep) + { + std::vector vec = ReadEndpointsThroughAttributeReader(apSuite, ep); + NL_TEST_ASSERT(apSuite, vec.size() == 0); + } +} + +} // namespace app +} // namespace chip + +namespace { + +/** + * Test Suite. It lists all the test functions. + */ + +// clang-format off +const nlTest sTests[] = +{ + NL_TEST_DEF("TestEndpointList", chip::app::TestPowerSourceCluster::TestEndpointList), + NL_TEST_SENTINEL() +}; +// clang-format on + +/** + * Set up the test suite. + */ +int TestPowerSourceClusterContext_Setup(void * inContext) +{ + CHIP_ERROR error = chip::Platform::MemoryInit(); + if (error != CHIP_NO_ERROR) + return FAILURE; + return SUCCESS; +} + +/** + * Tear down the test suite. + */ +int TestPowerSourceClusterContext_Teardown(void * inContext) +{ + chip::app::Clusters::PowerSourceServer::Instance().Shutdown(); + chip::Platform::MemoryShutdown(); + return SUCCESS; +} + +// clang-format off +nlTestSuite sSuite = +{ + "TestPowerSourceCluster", + &sTests[0], + TestPowerSourceClusterContext_Setup, + TestPowerSourceClusterContext_Teardown +}; +// clang-format on + +} // namespace + +int TestPowerSource() +{ + nlTestRunner(&sSuite, nullptr); + return nlTestRunnerStats(&sSuite); +} + +CHIP_REGISTER_TEST_SUITE(TestPowerSource)