From 5b52f834638ef6f0b45c86fc8ee6beb616d4f5a9 Mon Sep 17 00:00:00 2001 From: lpbeliveau-silabs <112982107+lpbeliveau-silabs@users.noreply.github.com> Date: Wed, 12 Apr 2023 16:37:18 -0400 Subject: [PATCH] SceneTable functionnalities (#25849) * Added a Global scene count, GetAllSceneIdsInGroup and RemoveAllScenesInGroup along with their test. Fixed line endings from SceneTable.h Added automaticall removal of scene when it can't be loaded due to number of supported cluster being reduced by OTA Added tests for OTA with changes in max scenes number (global and per fabric) * fixed comparison on wrong variable causing un-initialized member * Added Isnit verification to all scene table operation requiring permanent storage access, added check on table capacity to prevent creation of table table with capacity above max * Update src/app/clusters/scenes/SceneTableImpl.cpp Co-authored-by: Boris Zbarsky * Update src/app/clusters/scenes/SceneTableImpl.cpp Co-authored-by: Boris Zbarsky * Update src/app/clusters/scenes/SceneTableImpl.cpp Co-authored-by: Boris Zbarsky * Update src/app/clusters/scenes/SceneTableImpl.h Co-authored-by: Boris Zbarsky * Removed dupplicate of Deserialize for Scene Fabric Data, added test for GetAllSceneIdsInGroup for the corner case of a list of size = 0 and for BUFFER_TOO_SMALL other situations, added comments for the size of the global count in storage * Restyled by clang-format * Update src/app/clusters/scenes/SceneTableImpl.cpp Co-authored-by: Boris Zbarsky * Update src/app/clusters/scenes/SceneTableImpl.cpp Co-authored-by: Boris Zbarsky * Update src/app/clusters/scenes/SceneTableImpl.cpp Co-authored-by: Boris Zbarsky * Update src/app/clusters/scenes/SceneTableImpl.cpp Co-authored-by: Boris Zbarsky * Stubed Deserialize and re-implemented DeserializeAdjust to avoid duplicating code * Apply suggestions from code review Co-authored-by: Boris Zbarsky * Changed return of Stubbed Deserialize to return error if used, Changed DeserializeAdjust->Deserialize and moved adjustment to GlobalSceneCount out of Deserialize into Load function to adjust the count in the case where a failure would happen after scenes are deleted * Update src/app/clusters/scenes/SceneTableImpl.cpp Co-authored-by: Boris Zbarsky * Fixed return of load in the event of a failiure to Deserialize when deleting scenes, explicited deleted_scenes_count value on failure to deserialize * Update src/app/clusters/scenes/SceneTableImpl.cpp Co-authored-by: Boris Zbarsky * Removed redundant returns in Load() method --------- Co-authored-by: Boris Zbarsky Co-authored-by: Restyled.io --- .../scenes/ExtensionFieldSetsImpl.cpp | 2 +- src/app/clusters/scenes/SceneTable.h | 611 +++++++++--------- src/app/clusters/scenes/SceneTableImpl.cpp | 380 +++++++++-- src/app/clusters/scenes/SceneTableImpl.h | 30 +- src/app/tests/TestSceneTable.cpp | 393 ++++++++++- src/lib/support/DefaultStorageKeyAllocator.h | 1 + src/lib/support/PersistentData.h | 2 +- 7 files changed, 1058 insertions(+), 361 deletions(-) diff --git a/src/app/clusters/scenes/ExtensionFieldSetsImpl.cpp b/src/app/clusters/scenes/ExtensionFieldSetsImpl.cpp index 03f2581a0e443f..489e6788be77f9 100644 --- a/src/app/clusters/scenes/ExtensionFieldSetsImpl.cpp +++ b/src/app/clusters/scenes/ExtensionFieldSetsImpl.cpp @@ -121,7 +121,7 @@ CHIP_ERROR ExtensionFieldSetsImpl::InsertFieldSet(const ExtensionFieldSet & fiel CHIP_ERROR ExtensionFieldSetsImpl::GetFieldSetAtPosition(ExtensionFieldSet & fieldSet, uint8_t position) const { - VerifyOrReturnError(position < mFieldSetsCount, CHIP_ERROR_BUFFER_TOO_SMALL); + VerifyOrReturnError(position < mFieldSetsCount, CHIP_ERROR_INVALID_ARGUMENT); fieldSet = mFieldSets[position]; diff --git a/src/app/clusters/scenes/SceneTable.h b/src/app/clusters/scenes/SceneTable.h index bdb1f261cd9067..4352158db1f217 100644 --- a/src/app/clusters/scenes/SceneTable.h +++ b/src/app/clusters/scenes/SceneTable.h @@ -1,301 +1,310 @@ -/* - * - * Copyright (c) 2022 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 -#include - -namespace chip { -namespace scenes { - -// Storage index for scenes in nvm -typedef uint8_t SceneIndex; - -typedef uint32_t TransitionTimeMs; -typedef uint32_t SceneTransitionTime; - -constexpr GroupId kGlobalGroupSceneId = 0x0000; -constexpr SceneIndex kUndefinedSceneIndex = 0xff; -constexpr SceneId kUndefinedSceneId = 0xff; -static constexpr uint8_t kMaxScenesPerFabric = CHIP_CONFIG_SCENES_MAX_PER_FABRIC; - -static constexpr size_t kIteratorsMax = CHIP_CONFIG_MAX_SCENES_CONCURRENT_ITERATORS; -static constexpr size_t kSceneNameMaxLength = CHIP_CONFIG_SCENES_CLUSTER_MAXIMUM_NAME_LENGTH; - -/// @brief SceneHandlers are meant as interface between various clusters and the Scene table. -/// When a scene command involving extension field sets is received, the Scene Table will go through -/// the list of handlers to either retrieve, populate or apply those extension field sets. -/// -/// Generally, for each specific pair there should be one and only one handler -/// registered with the scene table that claims to handle that pair. -/// -/// A SceneHandler can handle a single pair, or many such pairs. -/// -/// @note If more than one handler claims to handle a specific pair, only one of -/// those handlers will get called when executing actions related to extension field sets on the scene -/// table. It is not defined which handler will be selected. - -class SceneHandler : public IntrusiveListNodeBase<> -{ -public: - SceneHandler(){}; - virtual ~SceneHandler() = default; - - /// @brief Copies the list of supported clusters for an endpoint in a Span and resizes the span to fit the actual number of - /// supported clusters - /// @param endpoint target endpoint - /// @param clusterBuffer Buffer to hold the supported cluster IDs, cannot hold more than - /// CHIP_CONFIG_SCENES_MAX_CLUSTERS_PER_SCENE. The function shall use the reduce_size() method in the event it is supporting - /// less than CHIP_CONFIG_SCENES_MAX_CLUSTERS_PER_SCENE clusters. - virtual void GetSupportedClusters(EndpointId endpoint, Span & clusterBuffer) = 0; - - /// @brief Returns whether or not a cluster for scenes is supported on an endpoint - /// - /// @param endpoint Target Endpoint ID - /// @param cluster Target Cluster ID - /// @return true if supported, false if not supported - virtual bool SupportsCluster(EndpointId endpoint, ClusterId cluster) = 0; - - /// @brief Called when handling AddScene. Allows the handler to filter through the clusters in the command to serialize only - /// the supported ones. - /// - /// @param endpoint[in] Endpoint ID - /// @param extensionFieldSet[in] ExtensionFieldSets provided by the AddScene Command, pre initialized - /// @param serialisedBytes[out] Buffer to fill from the ExtensionFieldSet in command - /// @return CHIP_NO_ERROR if successful, CHIP_ERROR value otherwise - /// @note Only gets called after the scene-cluster has previously verified that the endpoint,cluster pair is supported by - - /// the handler. It is therefore the implementation's reponsibility to also implement the SupportsCluster method. - virtual CHIP_ERROR SerializeAdd(EndpointId endpoint, - const app::Clusters::Scenes::Structs::ExtensionFieldSet::DecodableType & extensionFieldSet, - MutableByteSpan & serialisedBytes) = 0; - - /// @brief Called when handling StoreScene, and only if the handler supports the given endpoint and cluster. - /// - /// The implementation must write the actual scene data to store to serializedBytes as described below. - /// - /// @param endpoint[in] Target Endpoint - /// @param cluster[in] Target Cluster - /// @param serializedBytes[out] Output buffer, data needs to be writen in there and size adjusted to the size of the data - /// written. - /// - /// @return CHIP_NO_ERROR if successful, CHIP_ERROR value otherwise - virtual CHIP_ERROR SerializeSave(EndpointId endpoint, ClusterId cluster, MutableByteSpan & serializedBytes) = 0; - - /// @brief Deserialize an ExtensionFieldSet into a cluster object (e.g. when handling ViewScene). - /// - /// @param endpoint[in] Endpoint ID - /// @param cluster[in] Cluster ID - /// @param serializedBytes[in] ExtensionFieldSet stored in NVM - /// - /// @param extensionFieldSet[out] ExtensionFieldSet in command format - /// @return CHIP_NO_ERROR if successful, CHIP_ERROR value otherwise - /// @note Only gets called for handlers for which SupportsCluster() is true for the given endpoint and cluster. - virtual CHIP_ERROR Deserialize(EndpointId endpoint, ClusterId cluster, const ByteSpan & serializedBytes, - - app::Clusters::Scenes::Structs::ExtensionFieldSet::Type & extensionFieldSet) = 0; - - /// @brief Restore a stored scene for the given cluster instance, over timeMs milliseconds (e.g. when handling RecallScene) - /// - /// @param endpoint[in] Endpoint ID - /// @param cluster[in] Cluster ID - /// @param serializedBytes[in] ExtensionFieldSet stored in NVM - /// - /// @param timeMs[in] Transition time in ms to apply the scene - /// @return CHIP_NO_ERROR if successful, CHIP_ERROR value otherwise - /// @note Only gets called for handlers for which SupportsCluster() is true for the given endpoint and cluster. - virtual CHIP_ERROR ApplyScene(EndpointId endpoint, ClusterId cluster, const ByteSpan & serializedBytes, - TransitionTimeMs timeMs) = 0; -}; - -template -class SceneTable -{ -public: - /// @brief struct used to identify a scene in storage by 3 ids, endpoint, group and scene - struct SceneStorageId - { - // Identifies endpoint to which this scene applies - EndpointId mEndpointId = kInvalidEndpointId; - // Identifies group within the scope of the given fabric - GroupId mGroupId = kGlobalGroupSceneId; - SceneId mSceneId = kUndefinedSceneId; - - SceneStorageId() = default; - SceneStorageId(EndpointId endpoint, SceneId id, GroupId groupId = kGlobalGroupSceneId) : - mEndpointId(endpoint), mGroupId(groupId), mSceneId(id) - {} - - void Clear() - { - mEndpointId = kInvalidEndpointId; - mGroupId = kGlobalGroupSceneId; - mSceneId = kUndefinedSceneId; - } - - bool IsValid() { return (mEndpointId != kInvalidEndpointId) && (mSceneId != kUndefinedSceneId); } - - bool operator==(const SceneStorageId & other) - { - return (mEndpointId == other.mEndpointId && mGroupId == other.mGroupId && mSceneId == other.mSceneId); - } - }; - - /// @brief struct used to store data held in a scene - /// Members: - /// mName: char buffer holding the name of the scene, only serialized when mNameLenght is greater than 0 - /// mNameLength: lentgh of the name if a name was provided at scene creation - /// mSceneTransitionTimeSeconds: Time in seconds it will take a cluster to change to the scene - /// mExtensionFieldSets: class holding the different field sets of each cluster values to store with the scene - /// mTransitionTime100ms: Transition time in tenths of a second, allows for more precise transition when combiened with - /// mSceneTransitionTimeSeconds in enhanced scene commands - struct SceneData - { - char mName[kSceneNameMaxLength] = { 0 }; - size_t mNameLength = 0; - SceneTransitionTime mSceneTransitionTimeMs = 0; - EFStype mExtensionFieldSets; - - SceneData(const CharSpan & sceneName = CharSpan(), SceneTransitionTime time = 0) : mSceneTransitionTimeMs(time) - { - SetName(sceneName); - } - SceneData(EFStype fields, const CharSpan & sceneName = CharSpan(), SceneTransitionTime time = 0) : - mSceneTransitionTimeMs(time) - { - SetName(sceneName); - - mExtensionFieldSets = fields; - } - SceneData(const SceneData & other) : mSceneTransitionTimeMs(other.mSceneTransitionTimeMs) - { - SetName(CharSpan(other.mName, other.mNameLength)); - - mExtensionFieldSets = other.mExtensionFieldSets; - } - ~SceneData(){}; - - void SetName(const CharSpan & sceneName) - { - if (nullptr == sceneName.data()) - { - mName[0] = 0; - mNameLength = 0; - } - else - { - size_t maxChars = std::min(sceneName.size(), kSceneNameMaxLength); - memcpy(mName, sceneName.data(), maxChars); - mNameLength = maxChars; - } - } - - void Clear() - { - SetName(CharSpan()); - - mSceneTransitionTimeMs = 0; - mExtensionFieldSets.Clear(); - } - - bool operator==(const SceneData & other) - { - return (mNameLength == other.mNameLength && !memcmp(mName, other.mName, mNameLength) && - (mSceneTransitionTimeMs == other.mSceneTransitionTimeMs) && (mExtensionFieldSets == other.mExtensionFieldSets)); - } - - void operator=(const SceneData & other) - { - SetName(CharSpan(other.mName, other.mNameLength)); - mExtensionFieldSets = other.mExtensionFieldSets; - mSceneTransitionTimeMs = other.mSceneTransitionTimeMs; - } - }; - - /// @brief Struct combining both ID and data of a table entry - struct SceneTableEntry - { - // ID - SceneStorageId mStorageId; - - // DATA - SceneData mStorageData; - - SceneTableEntry() = default; - SceneTableEntry(SceneStorageId id) : mStorageId(id) {} - SceneTableEntry(const SceneStorageId id, const SceneData data) : mStorageId(id), mStorageData(data) {} - - bool operator==(const SceneTableEntry & other) - { - return (mStorageId == other.mStorageId && mStorageData == other.mStorageData); - } - - void operator=(const SceneTableEntry & other) - { - mStorageId = other.mStorageId; - mStorageData = other.mStorageData; - } - }; - - SceneTable(){}; - - virtual ~SceneTable() = default; - - // Not copyable - SceneTable(const SceneTable &) = delete; - - SceneTable & operator=(const SceneTable &) = delete; - - virtual CHIP_ERROR Init(PersistentStorageDelegate * storage) = 0; - virtual void Finish() = 0; - - // Data - virtual CHIP_ERROR SetSceneTableEntry(FabricIndex fabric_index, const SceneTableEntry & entry) = 0; - virtual CHIP_ERROR GetSceneTableEntry(FabricIndex fabric_index, SceneStorageId scene_id, SceneTableEntry & entry) = 0; - virtual CHIP_ERROR RemoveSceneTableEntry(FabricIndex fabric_index, SceneStorageId scene_id) = 0; - virtual CHIP_ERROR RemoveSceneTableEntryAtPosition(FabricIndex fabric_index, SceneIndex scene_idx) = 0; - - // SceneHandlers - virtual void RegisterHandler(SceneHandler * handler) = 0; - virtual void UnregisterHandler(SceneHandler * handler) = 0; - virtual void UnregisterAllHandlers() = 0; - - // Extension field sets operation - virtual CHIP_ERROR SceneSaveEFS(SceneTableEntry & scene) = 0; - virtual CHIP_ERROR SceneApplyEFS(const SceneTableEntry & scene) = 0; - - // Fabrics - virtual CHIP_ERROR RemoveFabric(FabricIndex fabric_index) = 0; - - // Iterators - using SceneEntryIterator = CommonIterator; - - virtual SceneEntryIterator * IterateSceneEntries(FabricIndex fabric_index) = 0; - - // Handlers - virtual bool HandlerListEmpty() { return mHandlerList.Empty(); } - - IntrusiveList mHandlerList; -}; - -} // namespace scenes -} // namespace chip +/* + * + * Copyright (c) 2022 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 +#include + +namespace chip { +namespace scenes { + +// Storage index for scenes in nvm +typedef uint8_t SceneIndex; + +typedef uint32_t TransitionTimeMs; +typedef uint32_t SceneTransitionTime; + +constexpr GroupId kGlobalGroupSceneId = 0x0000; +constexpr SceneIndex kUndefinedSceneIndex = 0xff; +constexpr SceneId kUndefinedSceneId = 0xff; +static constexpr uint8_t kMaxScenesPerFabric = CHIP_CONFIG_SCENES_MAX_PER_FABRIC; +static constexpr uint8_t kMaxScenesGlobal = CHIP_CONFIG_SCENES_MAX_NUMBER; + +static constexpr size_t kIteratorsMax = CHIP_CONFIG_MAX_SCENES_CONCURRENT_ITERATORS; +static constexpr size_t kSceneNameMaxLength = CHIP_CONFIG_SCENES_CLUSTER_MAXIMUM_NAME_LENGTH; + +/// @brief SceneHandlers are meant as interface between various clusters and the Scene table. +/// When a scene command involving extension field sets is received, the Scene Table will go through +/// the list of handlers to either retrieve, populate or apply those extension field sets. +/// +/// Generally, for each specific pair there should be one and only one handler +/// registered with the scene table that claims to handle that pair. +/// +/// A SceneHandler can handle a single pair, or many such pairs. +/// +/// @note If more than one handler claims to handle a specific pair, only one of +/// those handlers will get called when executing actions related to extension field sets on the scene +/// table. It is not defined which handler will be selected. + +class SceneHandler : public IntrusiveListNodeBase<> +{ +public: + SceneHandler(){}; + virtual ~SceneHandler() = default; + + /// @brief Copies the list of supported clusters for an endpoint in a Span and resizes the span to fit the actual number of + /// supported clusters + /// @param endpoint target endpoint + /// @param clusterBuffer Buffer to hold the supported cluster IDs, cannot hold more than + /// CHIP_CONFIG_SCENES_MAX_CLUSTERS_PER_SCENE. The function shall use the reduce_size() method in the event it is supporting + /// less than CHIP_CONFIG_SCENES_MAX_CLUSTERS_PER_SCENE clusters. + virtual void GetSupportedClusters(EndpointId endpoint, Span & clusterBuffer) = 0; + + /// @brief Returns whether or not a cluster for scenes is supported on an endpoint + /// + /// @param endpoint Target Endpoint ID + /// @param cluster Target Cluster ID + /// @return true if supported, false if not supported + virtual bool SupportsCluster(EndpointId endpoint, ClusterId cluster) = 0; + + /// @brief Called when handling AddScene. Allows the handler to filter through the clusters in the command to serialize only + /// the supported ones. + /// + /// @param endpoint[in] Endpoint ID + /// @param extensionFieldSet[in] ExtensionFieldSets provided by the AddScene Command, pre initialized + /// @param serialisedBytes[out] Buffer to fill from the ExtensionFieldSet in command + /// @return CHIP_NO_ERROR if successful, CHIP_ERROR value otherwise + /// @note Only gets called after the scene-cluster has previously verified that the endpoint,cluster pair is supported by + + /// the handler. It is therefore the implementation's reponsibility to also implement the SupportsCluster method. + virtual CHIP_ERROR SerializeAdd(EndpointId endpoint, + const app::Clusters::Scenes::Structs::ExtensionFieldSet::DecodableType & extensionFieldSet, + MutableByteSpan & serialisedBytes) = 0; + + /// @brief Called when handling StoreScene, and only if the handler supports the given endpoint and cluster. + /// + /// The implementation must write the actual scene data to store to serializedBytes as described below. + /// + /// @param endpoint[in] Target Endpoint + /// @param cluster[in] Target Cluster + /// @param serializedBytes[out] Output buffer, data needs to be writen in there and size adjusted to the size of the data + /// written. + /// + /// @return CHIP_NO_ERROR if successful, CHIP_ERROR value otherwise + virtual CHIP_ERROR SerializeSave(EndpointId endpoint, ClusterId cluster, MutableByteSpan & serializedBytes) = 0; + + /// @brief Deserialize an ExtensionFieldSet into a cluster object (e.g. when handling ViewScene). + /// + /// @param endpoint[in] Endpoint ID + /// @param cluster[in] Cluster ID + /// @param serializedBytes[in] ExtensionFieldSet stored in NVM + /// + /// @param extensionFieldSet[out] ExtensionFieldSet in command format + /// @return CHIP_NO_ERROR if successful, CHIP_ERROR value otherwise + /// @note Only gets called for handlers for which SupportsCluster() is true for the given endpoint and cluster. + virtual CHIP_ERROR Deserialize(EndpointId endpoint, ClusterId cluster, const ByteSpan & serializedBytes, + + app::Clusters::Scenes::Structs::ExtensionFieldSet::Type & extensionFieldSet) = 0; + + /// @brief Restore a stored scene for the given cluster instance, over timeMs milliseconds (e.g. when handling RecallScene) + /// + /// @param endpoint[in] Endpoint ID + /// @param cluster[in] Cluster ID + /// @param serializedBytes[in] ExtensionFieldSet stored in NVM + /// + /// @param timeMs[in] Transition time in ms to apply the scene + /// @return CHIP_NO_ERROR if successful, CHIP_ERROR value otherwise + /// @note Only gets called for handlers for which SupportsCluster() is true for the given endpoint and cluster. + virtual CHIP_ERROR ApplyScene(EndpointId endpoint, ClusterId cluster, const ByteSpan & serializedBytes, + TransitionTimeMs timeMs) = 0; +}; + +template +class SceneTable +{ +public: + /// @brief struct used to identify a scene in storage by 3 ids, endpoint, group and scene + struct SceneStorageId + { + // Identifies endpoint to which this scene applies + EndpointId mEndpointId = kInvalidEndpointId; + // Identifies group within the scope of the given fabric + GroupId mGroupId = kGlobalGroupSceneId; + SceneId mSceneId = kUndefinedSceneId; + + SceneStorageId() = default; + SceneStorageId(EndpointId endpoint, SceneId id, GroupId groupId = kGlobalGroupSceneId) : + mEndpointId(endpoint), mGroupId(groupId), mSceneId(id) + {} + + void Clear() + { + mEndpointId = kInvalidEndpointId; + mGroupId = kGlobalGroupSceneId; + mSceneId = kUndefinedSceneId; + } + + bool IsValid() { return (mEndpointId != kInvalidEndpointId) && (mSceneId != kUndefinedSceneId); } + + bool operator==(const SceneStorageId & other) + { + return (mEndpointId == other.mEndpointId && mGroupId == other.mGroupId && mSceneId == other.mSceneId); + } + }; + + /// @brief struct used to store data held in a scene + /// Members: + /// mName: char buffer holding the name of the scene, only serialized when mNameLenght is greater than 0 + /// mNameLength: lentgh of the name if a name was provided at scene creation + /// mSceneTransitionTimeSeconds: Time in seconds it will take a cluster to change to the scene + /// mExtensionFieldSets: class holding the different field sets of each cluster values to store with the scene + /// mTransitionTime100ms: Transition time in tenths of a second, allows for more precise transition when combiened with + /// mSceneTransitionTimeSeconds in enhanced scene commands + struct SceneData + { + char mName[kSceneNameMaxLength] = { 0 }; + size_t mNameLength = 0; + SceneTransitionTime mSceneTransitionTimeMs = 0; + EFStype mExtensionFieldSets; + + SceneData(const CharSpan & sceneName = CharSpan(), SceneTransitionTime time = 0) : mSceneTransitionTimeMs(time) + { + SetName(sceneName); + } + SceneData(EFStype fields, const CharSpan & sceneName = CharSpan(), SceneTransitionTime time = 0) : + mSceneTransitionTimeMs(time) + { + SetName(sceneName); + + mExtensionFieldSets = fields; + } + SceneData(const SceneData & other) : mSceneTransitionTimeMs(other.mSceneTransitionTimeMs) + { + SetName(CharSpan(other.mName, other.mNameLength)); + + mExtensionFieldSets = other.mExtensionFieldSets; + } + ~SceneData(){}; + + void SetName(const CharSpan & sceneName) + { + if (nullptr == sceneName.data()) + { + mName[0] = 0; + mNameLength = 0; + } + else + { + size_t maxChars = std::min(sceneName.size(), kSceneNameMaxLength); + memcpy(mName, sceneName.data(), maxChars); + mNameLength = maxChars; + } + } + + void Clear() + { + SetName(CharSpan()); + + mSceneTransitionTimeMs = 0; + mExtensionFieldSets.Clear(); + } + + bool operator==(const SceneData & other) + { + return (mNameLength == other.mNameLength && !memcmp(mName, other.mName, mNameLength) && + (mSceneTransitionTimeMs == other.mSceneTransitionTimeMs) && (mExtensionFieldSets == other.mExtensionFieldSets)); + } + + void operator=(const SceneData & other) + { + SetName(CharSpan(other.mName, other.mNameLength)); + mExtensionFieldSets = other.mExtensionFieldSets; + mSceneTransitionTimeMs = other.mSceneTransitionTimeMs; + } + }; + + /// @brief Struct combining both ID and data of a table entry + struct SceneTableEntry + { + // ID + SceneStorageId mStorageId; + + // DATA + SceneData mStorageData; + + SceneTableEntry() = default; + SceneTableEntry(SceneStorageId id) : mStorageId(id) {} + SceneTableEntry(const SceneStorageId id, const SceneData data) : mStorageId(id), mStorageData(data) {} + + bool operator==(const SceneTableEntry & other) + { + return (mStorageId == other.mStorageId && mStorageData == other.mStorageData); + } + + void operator=(const SceneTableEntry & other) + { + mStorageId = other.mStorageId; + mStorageData = other.mStorageData; + } + }; + + SceneTable(){}; + + virtual ~SceneTable() = default; + + // Not copyable + SceneTable(const SceneTable &) = delete; + + SceneTable & operator=(const SceneTable &) = delete; + + virtual CHIP_ERROR Init(PersistentStorageDelegate * storage) = 0; + virtual void Finish() = 0; + + // Global scene count + virtual CHIP_ERROR GetGlobalSceneCount(uint8_t & scene_count) = 0; + + // Data + virtual CHIP_ERROR GetRemainingCapacity(FabricIndex fabric_index, uint8_t & capacity) = 0; + virtual CHIP_ERROR SetSceneTableEntry(FabricIndex fabric_index, const SceneTableEntry & entry) = 0; + virtual CHIP_ERROR GetSceneTableEntry(FabricIndex fabric_index, SceneStorageId scene_id, SceneTableEntry & entry) = 0; + virtual CHIP_ERROR RemoveSceneTableEntry(FabricIndex fabric_index, SceneStorageId scene_id) = 0; + virtual CHIP_ERROR RemoveSceneTableEntryAtPosition(FabricIndex fabric_index, SceneIndex scene_idx) = 0; + + // Groups + virtual CHIP_ERROR GetAllSceneIdsInGroup(FabricIndex fabric_index, GroupId group_id, Span & scene_list) = 0; + virtual CHIP_ERROR DeleteAllScenesInGroup(FabricIndex fabric_index, GroupId group_id) = 0; + + // SceneHandlers + virtual void RegisterHandler(SceneHandler * handler) = 0; + virtual void UnregisterHandler(SceneHandler * handler) = 0; + virtual void UnregisterAllHandlers() = 0; + + // Extension field sets operation + virtual CHIP_ERROR SceneSaveEFS(SceneTableEntry & scene) = 0; + virtual CHIP_ERROR SceneApplyEFS(const SceneTableEntry & scene) = 0; + + // Fabrics + virtual CHIP_ERROR RemoveFabric(FabricIndex fabric_index) = 0; + + // Iterators + using SceneEntryIterator = CommonIterator; + + virtual SceneEntryIterator * IterateSceneEntries(FabricIndex fabric_index) = 0; + + // Handlers + virtual bool HandlerListEmpty() { return mHandlerList.Empty(); } + + IntrusiveList mHandlerList; +}; + +} // namespace scenes +} // namespace chip diff --git a/src/app/clusters/scenes/SceneTableImpl.cpp b/src/app/clusters/scenes/SceneTableImpl.cpp index 19b9a2994151f3..a34cd9179ddcc7 100644 --- a/src/app/clusters/scenes/SceneTableImpl.cpp +++ b/src/app/clusters/scenes/SceneTableImpl.cpp @@ -33,7 +33,8 @@ namespace scenes { /// kTransitionTime: Tag for the transition time of the scene in miliseconds enum class TagScene : uint8_t { - kSceneCount = 1, + kGlobalSceneCount = 1, + kSceneCount, kStorageIDArray, kEndpointID, kGroupID, @@ -47,6 +48,57 @@ using SceneTableEntry = DefaultSceneTableImpl::SceneTableEntry; using SceneStorageId = DefaultSceneTableImpl::SceneStorageId; using SceneData = DefaultSceneTableImpl::SceneData; +// Currently takes 5 Bytes to serialize Container and value in a TLV: 1 byte start struct, 2 bytes control + tag for the value, 1 +// byte value, 1 byte end struct. 8 Bytes leaves space for potential increase in count_value size. +static constexpr size_t kPersistentBufferSceneCountBytes = 8; + +struct GlobalSceneCount : public PersistentData +{ + uint8_t count_value = 0; + + GlobalSceneCount(uint8_t count = 0) : count_value(count) {} + ~GlobalSceneCount() {} + + void Clear() override { count_value = 0; } + + CHIP_ERROR UpdateKey(StorageKeyName & key) override + { + key = DefaultStorageKeyAllocator::GlobalSceneCountKey(); + return CHIP_NO_ERROR; + } + + CHIP_ERROR Serialize(TLV::TLVWriter & writer) const override + { + TLV::TLVType container; + ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, container)); + ReturnErrorOnFailure(writer.Put(TLV::ContextTag(TagScene::kGlobalSceneCount), count_value)); + return writer.EndContainer(container); + } + + CHIP_ERROR Deserialize(TLV::TLVReader & reader) override + { + ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag())); + + TLV::TLVType container; + ReturnErrorOnFailure(reader.EnterContainer(container)); + ReturnErrorOnFailure(reader.Next(TLV::ContextTag(TagScene::kGlobalSceneCount))); + ReturnErrorOnFailure(reader.Get(count_value)); + return reader.ExitContainer(container); + } + + CHIP_ERROR Load(PersistentStorageDelegate * storage) override + { + CHIP_ERROR err = PersistentData::Load(storage); + VerifyOrReturnError(CHIP_NO_ERROR == err || CHIP_ERROR_NOT_FOUND == err, err); + if (CHIP_ERROR_NOT_FOUND == err) + { + count_value = 0; + } + + return CHIP_NO_ERROR; + } +}; + // Worst case tested: Add Scene Command with EFS using the default SerializeAdd Method. This yielded a serialized scene of 212bytes // when using the OnOff, Level Control and Color Control as well as the maximal name length of 16 bytes. Putting 256 gives some // slack in case different clusters are used. Value obtained by using writer.GetLengthWritten at the end of the SceneTableData @@ -151,12 +203,17 @@ static constexpr size_t kPersistentFabricBufferMax = 128; */ struct FabricSceneData : public PersistentData { - FabricIndex fabric_index = kUndefinedFabricIndex; - uint8_t scene_count = 0; + FabricIndex fabric_index; + uint8_t scene_count = 0; + uint8_t max_scenes_per_fabric; + uint8_t max_scenes_global; SceneStorageId scene_map[kMaxScenesPerFabric]; - FabricSceneData() = default; - FabricSceneData(FabricIndex fabric) : fabric_index(fabric) {} + FabricSceneData(FabricIndex fabric = kUndefinedFabricIndex, uint8_t maxScenesPerFabric = kMaxScenesPerFabric, + uint8_t maxScenesGlobal = kMaxScenesGlobal) : + fabric_index(fabric), + max_scenes_per_fabric(maxScenesPerFabric), max_scenes_global(maxScenesGlobal) + {} CHIP_ERROR UpdateKey(StorageKeyName & key) override { @@ -168,7 +225,7 @@ struct FabricSceneData : public PersistentData void Clear() override { scene_count = 0; - for (uint8_t i = 0; i < kMaxScenesPerFabric; i++) + for (uint8_t i = 0; i < max_scenes_per_fabric; i++) { scene_map[i].Clear(); } @@ -184,7 +241,7 @@ struct FabricSceneData : public PersistentData writer.StartContainer(TLV::ContextTag(TagScene::kStorageIDArray), TLV::kTLVType_Array, sceneMapContainer)); // Storing the scene map - for (uint8_t i = 0; i < kMaxScenesPerFabric; i++) + for (uint8_t i = 0; i < max_scenes_per_fabric; i++) { TLV::TLVType sceneIdContainer; ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, sceneIdContainer)); @@ -197,40 +254,71 @@ struct FabricSceneData : public PersistentData return writer.EndContainer(fabricSceneContainer); } - CHIP_ERROR Deserialize(TLV::TLVReader & reader) override + /// @brief This Deserialize method is implemented only to allow compilation. It is not used throughout the code. + /// @param reader TLV reader + /// @return CHIP_NO_ERROR + CHIP_ERROR Deserialize(TLV::TLVReader & reader) override { return CHIP_ERROR_INCORRECT_STATE; } + + /// @brief This Deserialize method checks that the recovered scenes from the deserialization fit in the current max and if + /// there are too many scenes in nvm, it deletes them. The method sets the deleted_scenes output parameter to true if scenes + /// were deleted so that the load function can know it needs to save the Fabric scene data to update the scene_count and the + /// scene map in stored memory. + /// @param reade [in] TLV reader, must be big enough to hold the scene map size + /// @param storage [in] Persistent Storage Delegate, required to delete scenes if the number of scenes in storage is greater + /// than the maximum allowed + /// @param deleted_scenes_count [out] uint8_t letting the caller (in this case the load method) know how many scenes were + /// deleted so it can adjust the fabric and global scene count accordingly. Even if Deserialize fails, this value will return + /// the number of scenes deleted before the failure happened. + /// @return CHIP_NO_ERROR on success, specific CHIP_ERROR otherwise + CHIP_ERROR Deserialize(TLV::TLVReader & reader, PersistentStorageDelegate * storage, uint8_t & deleted_scenes_count) { ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag())); - TLV::TLVType fabricSceneContainer; ReturnErrorOnFailure(reader.EnterContainer(fabricSceneContainer)); - ReturnErrorOnFailure(reader.Next(TLV::ContextTag(TagScene::kSceneCount))); ReturnErrorOnFailure(reader.Get(scene_count)); + scene_count = min(scene_count, max_scenes_per_fabric); ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Array, TLV::ContextTag(TagScene::kStorageIDArray))); TLV::TLVType sceneMapContainer; ReturnErrorOnFailure(reader.EnterContainer(sceneMapContainer)); uint8_t i = 0; CHIP_ERROR err; - while ((err = reader.Next(TLV::AnonymousTag())) == CHIP_NO_ERROR && i < kMaxScenesPerFabric) + deleted_scenes_count = 0; + + while ((err = reader.Next(TLV::AnonymousTag())) == CHIP_NO_ERROR) { TLV::TLVType sceneIdContainer; - ReturnErrorOnFailure(reader.EnterContainer(sceneIdContainer)); - ReturnErrorOnFailure(reader.Next(TLV::ContextTag(TagScene::kEndpointID))); - ReturnErrorOnFailure(reader.Get(scene_map[i].mEndpointId)); - ReturnErrorOnFailure(reader.Next(TLV::ContextTag(TagScene::kGroupID))); - ReturnErrorOnFailure(reader.Get(scene_map[i].mGroupId)); - ReturnErrorOnFailure(reader.Next(TLV::ContextTag(TagScene::kSceneID))); - ReturnErrorOnFailure(reader.Get(scene_map[i].mSceneId)); - ReturnErrorOnFailure(reader.ExitContainer(sceneIdContainer)); + if (i < max_scenes_per_fabric) + { + ReturnErrorOnFailure(reader.EnterContainer(sceneIdContainer)); + ReturnErrorOnFailure(reader.Next(TLV::ContextTag(TagScene::kEndpointID))); + ReturnErrorOnFailure(reader.Get(scene_map[i].mEndpointId)); + ReturnErrorOnFailure(reader.Next(TLV::ContextTag(TagScene::kGroupID))); + ReturnErrorOnFailure(reader.Get(scene_map[i].mGroupId)); + ReturnErrorOnFailure(reader.Next(TLV::ContextTag(TagScene::kSceneID))); + ReturnErrorOnFailure(reader.Get(scene_map[i].mSceneId)); + ReturnErrorOnFailure(reader.ExitContainer(sceneIdContainer)); + } + else + { + SceneTableData scene(fabric_index, i); + ReturnErrorOnFailure(reader.EnterContainer(sceneIdContainer)); + ReturnErrorOnFailure(reader.Next(TLV::ContextTag(TagScene::kEndpointID))); + ReturnErrorOnFailure(reader.Get(scene.mStorageId.mEndpointId)); + ReturnErrorOnFailure(reader.Next(TLV::ContextTag(TagScene::kGroupID))); + ReturnErrorOnFailure(reader.Get(scene.mStorageId.mGroupId)); + ReturnErrorOnFailure(reader.Next(TLV::ContextTag(TagScene::kSceneID))); + ReturnErrorOnFailure(reader.Get(scene.mStorageId.mSceneId)); + ReturnErrorOnFailure(reader.ExitContainer(sceneIdContainer)); + ReturnErrorOnFailure(scene.Delete(storage)); + deleted_scenes_count++; + } i++; } - // In the event of an OTA, is kMaxScenesPerFabric was reduced, err will be equal to CHIP_NO_ERROR. We close the TLV with - // only the acceptable number of scenes and the next save will take care of reducing the memory usage of the map. This - // allows the user to preserve their scenes in between OTA updates. - VerifyOrReturnError(err == CHIP_END_OF_TLV || err == CHIP_NO_ERROR, err); + VerifyOrReturnError(err == CHIP_END_OF_TLV, err); ReturnErrorOnFailure(reader.ExitContainer(sceneMapContainer)); return reader.ExitContainer(fabricSceneContainer); } @@ -246,7 +334,7 @@ struct FabricSceneData : public PersistentData SceneIndex firstFreeIdx = kUndefinedSceneIndex; // storage index if scene not found uint8_t index = 0; - while (index < kMaxScenesPerFabric) + while (index < max_scenes_per_fabric) { if (scene_map[index] == target_scene) { @@ -260,7 +348,7 @@ struct FabricSceneData : public PersistentData index++; } - if (firstFreeIdx < kMaxScenesPerFabric) + if (firstFreeIdx < max_scenes_per_fabric) { idx = firstFreeIdx; return CHIP_ERROR_NOT_FOUND; @@ -284,26 +372,48 @@ struct FabricSceneData : public PersistentData if (CHIP_ERROR_NOT_FOUND == err) // If not found, scene.index should be the first free index { + // Update the global scene count + GlobalSceneCount global_scene_count; + ReturnErrorOnFailure(global_scene_count.Load(storage)); + VerifyOrReturnError(global_scene_count.count_value < max_scenes_global, CHIP_ERROR_NO_MEMORY); + global_scene_count.count_value++; + ReturnErrorOnFailure(global_scene_count.Save(storage)); + scene_count++; scene_map[scene.index] = scene.mStorageId; - ReturnErrorOnFailure(this->Save(storage)); + + err = this->Save(storage); + if (CHIP_NO_ERROR != err) + { + global_scene_count.count_value--; + ReturnErrorOnFailure(global_scene_count.Save(storage)); + return err; + } err = scene.Save(storage); // on failure to save the scene, undoes the changes to Fabric Scene Data - if (err != CHIP_NO_ERROR) + if (CHIP_NO_ERROR != err) { + global_scene_count.count_value--; + ReturnErrorOnFailure(global_scene_count.Save(storage)); + scene_count--; scene_map[scene.index].Clear(); ReturnErrorOnFailure(this->Save(storage)); + return err; } - - return err; } - return CHIP_ERROR_INVALID_LIST_LENGTH; + return err; } + /// @brief Removes a scene from the non-volatile memory and clears its index in the scene map. Decreases the number of scenes in + /// the global scene count and in the scene fabric data if successful. As the scene map size is not compressed upon removal, + /// this only clears the entry correpsonding to the scene from the scene map. + /// @param storage Storage delegate to access the scene + /// @param scene_id Scene to remove + /// @return CHIP_NO_ERROR if successful, specific CHIP_ERROR otherwise CHIP_ERROR RemoveScene(PersistentStorageDelegate * storage, const SceneStorageId & scene_id) { CHIP_ERROR err = CHIP_NO_ERROR; @@ -315,22 +425,81 @@ struct FabricSceneData : public PersistentData // If Find doesn't return CHIP_NO_ERROR, the scene wasn't found, which doesn't return an error VerifyOrReturnValue(this->Find(scene_id, scene.index) == CHIP_NO_ERROR, CHIP_NO_ERROR); + // Update the global scene count + GlobalSceneCount global_scene_count; + ReturnErrorOnFailure(global_scene_count.Load(storage)); + global_scene_count.count_value--; + ReturnErrorOnFailure(global_scene_count.Save(storage)); + scene_count--; scene_map[scene.index].Clear(); - ReturnErrorOnFailure(this->Save(storage)); + err = this->Save(storage); + + // On failure to update the scene map, undo the global count modification + if (CHIP_NO_ERROR != err) + { + global_scene_count.count_value++; + ReturnErrorOnFailure(global_scene_count.Save(storage)); + return err; + } err = scene.Delete(storage); - // On failure to delete scene, undoes the change to the Fabric Scene Data - if (err != CHIP_NO_ERROR) + // On failure to delete scene, undo the change to the Fabric Scene Data and the global scene count + if (CHIP_NO_ERROR != err) { + global_scene_count.count_value++; + ReturnErrorOnFailure(global_scene_count.Save(storage)); + scene_count++; scene_map[scene.index] = scene.mStorageId; ReturnErrorOnFailure(this->Save(storage)); + return err; } } return err; } + + CHIP_ERROR Load(PersistentStorageDelegate * storage) override + { + VerifyOrReturnError(nullptr != storage, CHIP_ERROR_INVALID_ARGUMENT); + uint8_t deleted_scenes_count = 0; + + uint8_t buffer[kPersistentFabricBufferMax] = { 0 }; + StorageKeyName key = StorageKeyName::Uninitialized(); + + // Set data to defaults + Clear(); + + // Update storage key + ReturnErrorOnFailure(UpdateKey(key)); + + // Load the serialized data + uint16_t size = static_cast(sizeof(buffer)); + CHIP_ERROR err = storage->SyncGetKeyValue(key.KeyName(), buffer, size); + VerifyOrReturnError(CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND != err, CHIP_ERROR_NOT_FOUND); + ReturnErrorOnFailure(err); + + // Decode serialized data + TLV::TLVReader reader; + reader.Init(buffer, size); + + err = Deserialize(reader, storage, deleted_scenes_count); + + // If Deserialize sets the "deleted_scenes" variable, the table in flash memory held too many scenes (can happen + // if max_scenes_per_fabric was reduced during an OTA) and was adjusted during deserailizing . The fabric data must then + // be updated + if (deleted_scenes_count) + { + GlobalSceneCount global_count; + ReturnErrorOnFailure(global_count.Load(storage)); + global_count.count_value = static_cast(global_count.count_value - deleted_scenes_count); + ReturnErrorOnFailure(global_count.Save(storage)); + ReturnErrorOnFailure(this->Save(storage)); + } + + return err; + } }; CHIP_ERROR DefaultSceneTableImpl::Init(PersistentStorageDelegate * storage) @@ -340,6 +509,9 @@ CHIP_ERROR DefaultSceneTableImpl::Init(PersistentStorageDelegate * storage) return CHIP_ERROR_INCORRECT_STATE; } + // Verified the initialized parameter respect the maximum allowed values for scene capacity + VerifyOrReturnError(mMaxScenesPerFabric <= kMaxScenesPerFabric && mMaxScenesGlobal <= kMaxScenesGlobal, + CHIP_ERROR_INVALID_INTEGER_VALUE); mStorage = storage; return CHIP_NO_ERROR; } @@ -350,28 +522,91 @@ void DefaultSceneTableImpl::Finish() mSceneEntryIterators.ReleaseAll(); } -CHIP_ERROR DefaultSceneTableImpl::SetSceneTableEntry(FabricIndex fabric_index, const SceneTableEntry & entry) +CHIP_ERROR DefaultSceneTableImpl::GetGlobalSceneCount(uint8_t & scene_count) { VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INTERNAL); + GlobalSceneCount global_count; + + ReturnErrorOnFailure(global_count.Load(mStorage)); + scene_count = global_count.count_value; + + return CHIP_NO_ERROR; +} +CHIP_ERROR DefaultSceneTableImpl::SetGlobalSceneCount(const uint8_t & scene_count) +{ + VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INTERNAL); + + GlobalSceneCount global_count(scene_count); + return global_count.Save(mStorage); +} + +CHIP_ERROR DefaultSceneTableImpl::GetRemainingCapacity(FabricIndex fabric_index, uint8_t & capacity) +{ + VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INTERNAL); + + uint8_t global_scene_count = 0; + ReturnErrorOnFailure(GetGlobalSceneCount(global_scene_count)); + + // If the global scene count is higher than the maximal Global scene capacity, this returns a capacity of 0 until enough scenes + // have been deleted to bring the global number of scenes under the global maximum. + if (global_scene_count > mMaxScenesGlobal) + { + capacity = 0; + return CHIP_NO_ERROR; + } + uint8_t remaining_capacity_global = static_cast(mMaxScenesGlobal - global_scene_count); + uint8_t remaining_capacity_fabric = mMaxScenesPerFabric; + FabricSceneData fabric(fabric_index); // Load fabric data (defaults to zero) CHIP_ERROR err = fabric.Load(mStorage); VerifyOrReturnError(CHIP_NO_ERROR == err || CHIP_ERROR_NOT_FOUND == err, err); + if (err == CHIP_NO_ERROR) + { + remaining_capacity_fabric = static_cast(mMaxScenesPerFabric - fabric.scene_count); + } + + capacity = min(remaining_capacity_fabric, remaining_capacity_global); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DefaultSceneTableImpl::SetSceneTableEntry(FabricIndex fabric_index, const SceneTableEntry & entry) +{ + VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INTERNAL); + + FabricSceneData fabric(fabric_index, mMaxScenesPerFabric, mMaxScenesGlobal); + + // Load fabric data (defaults to zero) + CHIP_ERROR err = fabric.Load(mStorage); + VerifyOrReturnError(CHIP_NO_ERROR == err || CHIP_ERROR_NOT_FOUND == err, err); + return fabric.SaveScene(mStorage, entry); } CHIP_ERROR DefaultSceneTableImpl::GetSceneTableEntry(FabricIndex fabric_index, SceneStorageId scene_id, SceneTableEntry & entry) { - FabricSceneData fabric(fabric_index); + VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INTERNAL); + + FabricSceneData fabric(fabric_index, mMaxScenesPerFabric, mMaxScenesGlobal); SceneTableData scene(fabric_index); ReturnErrorOnFailure(fabric.Load(mStorage)); VerifyOrReturnError(fabric.Find(scene_id, scene.index) == CHIP_NO_ERROR, CHIP_ERROR_NOT_FOUND); - ReturnErrorOnFailure(scene.Load(mStorage)); + CHIP_ERROR err = scene.Load(mStorage); + + // If scene.Load returns "buffer too small", the scene in memory is too big to be retrieve (this could happen if the + // kMaxClustersPerScene was reduced by OTA) and therefore must be deleted as is is no longer considered accessible. + if (err == CHIP_ERROR_BUFFER_TOO_SMALL) + { + ReturnErrorOnFailure(this->RemoveSceneTableEntry(fabric_index, scene_id)); + } + ReturnErrorOnFailure(err); + entry.mStorageId = scene.mStorageId; entry.mStorageData = scene.mStorageData; @@ -380,7 +615,9 @@ CHIP_ERROR DefaultSceneTableImpl::GetSceneTableEntry(FabricIndex fabric_index, S CHIP_ERROR DefaultSceneTableImpl::RemoveSceneTableEntry(FabricIndex fabric_index, SceneStorageId scene_id) { - FabricSceneData fabric(fabric_index); + VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INTERNAL); + + FabricSceneData fabric(fabric_index, mMaxScenesPerFabric, mMaxScenesGlobal); ReturnErrorOnFailure(fabric.Load(mStorage)); @@ -394,8 +631,10 @@ CHIP_ERROR DefaultSceneTableImpl::RemoveSceneTableEntry(FabricIndex fabric_index /// @return CHIP_NO_ERROR if removal was successful, errors if failed to remove the scene or to update the fabric after removing it CHIP_ERROR DefaultSceneTableImpl::RemoveSceneTableEntryAtPosition(FabricIndex fabric_index, SceneIndex scene_idx) { + VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INTERNAL); + CHIP_ERROR err = CHIP_NO_ERROR; - FabricSceneData fabric(fabric_index); + FabricSceneData fabric(fabric_index, mMaxScenesPerFabric, mMaxScenesGlobal); SceneTableData scene(fabric_index, scene_idx); ReturnErrorOnFailure(fabric.Load(mStorage)); @@ -406,6 +645,55 @@ CHIP_ERROR DefaultSceneTableImpl::RemoveSceneTableEntryAtPosition(FabricIndex fa return fabric.RemoveScene(mStorage, scene.mStorageId); } +CHIP_ERROR DefaultSceneTableImpl::GetAllSceneIdsInGroup(FabricIndex fabric_index, GroupId group_id, Span & scene_list) +{ + FabricSceneData fabric(fabric_index, mMaxScenesPerFabric, mMaxScenesGlobal); + SceneTableData scene(fabric_index); + + auto * iterator = this->IterateSceneEntries(fabric_index); + VerifyOrReturnError(nullptr != iterator, CHIP_ERROR_INTERNAL); + SceneId * list = scene_list.data(); + uint8_t scene_count = 0; + + while (iterator->Next(scene)) + { + if (scene.mStorageId.mGroupId == group_id) + { + if (scene_count >= scene_list.size()) + { + iterator->Release(); + return CHIP_ERROR_BUFFER_TOO_SMALL; + } + list[scene_count] = scene.mStorageId.mSceneId; + scene_count++; + } + } + scene_list.reduce_size(scene_count); + iterator->Release(); + return CHIP_NO_ERROR; +} + +CHIP_ERROR DefaultSceneTableImpl::DeleteAllScenesInGroup(FabricIndex fabric_index, GroupId group_id) +{ + VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INTERNAL); + + FabricSceneData fabric(fabric_index, mMaxScenesPerFabric, mMaxScenesGlobal); + SceneTableData scene(fabric_index); + + ReturnErrorOnFailure(fabric.Load(mStorage)); + + for (uint8_t i = 0; i < mMaxScenesPerFabric; i++) + { + if (fabric.scene_map[i].mGroupId == group_id) + { + // Removing each scene from the nvm and clearing their entry in the scene map + ReturnErrorOnFailure(fabric.RemoveScene(mStorage, fabric.scene_map[i])); + } + } + + return CHIP_NO_ERROR; +} + /// @brief Register a handler in the handler linked list /// @param handler Cluster specific handler for extension field sets interaction void DefaultSceneTableImpl::RegisterHandler(SceneHandler * handler) @@ -502,12 +790,14 @@ CHIP_ERROR DefaultSceneTableImpl::SceneApplyEFS(const SceneTableEntry & scene) CHIP_ERROR DefaultSceneTableImpl::RemoveFabric(FabricIndex fabric_index) { + VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INTERNAL); + FabricSceneData fabric(fabric_index); SceneIndex idx = 0; CHIP_ERROR err = fabric.Load(mStorage); VerifyOrReturnError(CHIP_NO_ERROR == err || CHIP_ERROR_NOT_FOUND == err, err); - while (idx < kMaxScenesPerFabric) + while (idx < mMaxScenesPerFabric) { err = RemoveSceneTableEntryAtPosition(fabric_index, idx); VerifyOrReturnError(CHIP_NO_ERROR == err || CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND == err, err); @@ -528,13 +818,15 @@ uint8_t DefaultSceneTableImpl::GetClustersFromEndpoint(EndpointId endpoint, Clus DefaultSceneTableImpl::SceneEntryIterator * DefaultSceneTableImpl::IterateSceneEntries(FabricIndex fabric_index) { VerifyOrReturnError(IsInitialized(), nullptr); - return mSceneEntryIterators.CreateObject(*this, fabric_index); + return mSceneEntryIterators.CreateObject(*this, fabric_index, mMaxScenesPerFabric, mMaxScenesGlobal); } -DefaultSceneTableImpl::SceneEntryIteratorImpl::SceneEntryIteratorImpl(DefaultSceneTableImpl & provider, FabricIndex fabric_index) : - mProvider(provider), mFabric(fabric_index) +DefaultSceneTableImpl::SceneEntryIteratorImpl::SceneEntryIteratorImpl(DefaultSceneTableImpl & provider, FabricIndex fabric_index, + uint8_t maxScenesPerFabric, uint8_t maxScenesGlobal) : + mProvider(provider), + mFabric(fabric_index), mMaxScenesPerFabric(maxScenesPerFabric), mMaxScenesGlobal(maxScenesGlobal) { - FabricSceneData fabric(fabric_index); + FabricSceneData fabric(fabric_index, mMaxScenesPerFabric, mMaxScenesGlobal); ReturnOnFailure(fabric.Load(provider.mStorage)); mTotalScenes = fabric.scene_count; mSceneIndex = 0; @@ -553,7 +845,7 @@ bool DefaultSceneTableImpl::SceneEntryIteratorImpl::Next(SceneTableEntry & outpu VerifyOrReturnError(fabric.Load(mProvider.mStorage) == CHIP_NO_ERROR, false); // looks for next available scene - while (mSceneIndex < kMaxScenesPerFabric) + while (mSceneIndex < mMaxScenesPerFabric) { if (fabric.scene_map[mSceneIndex].IsValid()) { diff --git a/src/app/clusters/scenes/SceneTableImpl.h b/src/app/clusters/scenes/SceneTableImpl.h index c136d4f630c124..c0453b5e9da58d 100644 --- a/src/app/clusters/scenes/SceneTableImpl.h +++ b/src/app/clusters/scenes/SceneTableImpl.h @@ -178,19 +178,27 @@ class DefaultSceneHandlerImpl : public scenes::SceneHandler class DefaultSceneTableImpl : public SceneTable { public: - DefaultSceneTableImpl() = default; + DefaultSceneTableImpl() {} ~DefaultSceneTableImpl() override {} CHIP_ERROR Init(PersistentStorageDelegate * storage) override; void Finish() override; - // Scene access by Id + // Global scene count + CHIP_ERROR GetGlobalSceneCount(uint8_t & scene_count) override; + + // Data + CHIP_ERROR GetRemainingCapacity(FabricIndex fabric_index, uint8_t & capacity) override; CHIP_ERROR SetSceneTableEntry(FabricIndex fabric_index, const SceneTableEntry & entry) override; CHIP_ERROR GetSceneTableEntry(FabricIndex fabric_index, SceneStorageId scene_id, SceneTableEntry & entry) override; CHIP_ERROR RemoveSceneTableEntry(FabricIndex fabric_index, SceneStorageId scene_id) override; CHIP_ERROR RemoveSceneTableEntryAtPosition(FabricIndex fabric_index, SceneIndex scene_idx) override; + // Groups + CHIP_ERROR GetAllSceneIdsInGroup(FabricIndex fabric_index, GroupId group_id, Span & scene_list) override; + CHIP_ERROR DeleteAllScenesInGroup(FabricIndex fabric_index, GroupId group_id) override; + // SceneHandlers void RegisterHandler(SceneHandler * handler) override; void UnregisterHandler(SceneHandler * handler) override; @@ -208,13 +216,25 @@ class DefaultSceneTableImpl : public SceneTable SceneEntryIterator * IterateSceneEntries(FabricIndex fabric_index) override; protected: + // This constructor is meant for test purposes, it allows to change the defined max for scenes per fabric and global, which + // allows to simulate OTA where this value was changed + DefaultSceneTableImpl(uint8_t maxScenesPerFabric = scenes::kMaxScenesPerFabric, + uint8_t maxScenesGlobal = scenes::kMaxScenesGlobal) : + mMaxScenesPerFabric(maxScenesPerFabric), + mMaxScenesGlobal(maxScenesGlobal) + {} + + // Global scene count + CHIP_ERROR SetGlobalSceneCount(const uint8_t & scene_count); + // wrapper function around emberAfGetClustersFromEndpoint to allow override when testing virtual uint8_t GetClustersFromEndpoint(EndpointId endpoint, ClusterId * clusterList, uint8_t listLen); class SceneEntryIteratorImpl : public SceneEntryIterator { public: - SceneEntryIteratorImpl(DefaultSceneTableImpl & provider, FabricIndex fabric_index); + SceneEntryIteratorImpl(DefaultSceneTableImpl & provider, FabricIndex fabric_index, uint8_t maxScenesPerFabric, + uint8_t maxScenesGlobal); size_t Count() override; bool Next(SceneTableEntry & output) override; void Release() override; @@ -225,9 +245,13 @@ class DefaultSceneTableImpl : public SceneTable SceneIndex mNextSceneIdx; SceneIndex mSceneIndex = 0; uint8_t mTotalScenes = 0; + uint8_t mMaxScenesPerFabric; + uint8_t mMaxScenesGlobal; }; bool IsInitialized() { return (mStorage != nullptr); } + const uint8_t mMaxScenesPerFabric = kMaxScenesPerFabric; + const uint8_t mMaxScenesGlobal = kMaxScenesGlobal; chip::PersistentStorageDelegate * mStorage = nullptr; ObjectPool mSceneEntryIterators; }; // class DefaultSceneTableImpl diff --git a/src/app/tests/TestSceneTable.cpp b/src/app/tests/TestSceneTable.cpp index 45d0ca7c840171..c4e09b2b83e3e1 100644 --- a/src/app/tests/TestSceneTable.cpp +++ b/src/app/tests/TestSceneTable.cpp @@ -59,20 +59,33 @@ constexpr uint32_t kColorLoopActiveId = 0x4002; constexpr uint32_t kColorLoopDirectionId = 0x4003; constexpr uint32_t kColorLoopTimeId = 0x4004; +// Test Group ID +constexpr chip::GroupId kGroup1 = 0x101; +constexpr chip::GroupId kGroup2 = 0x102; +constexpr chip::GroupId kGroup3 = 0x103; +constexpr chip::GroupId kGroup4 = 0x00; + +// Test Scene ID +constexpr chip::SceneId kScene1 = 0xAA; +constexpr chip::SceneId kScene2 = 0x45; +constexpr chip::SceneId kScene3 = 0x77; +constexpr chip::SceneId kScene4 = 0xEE; + // Test fabrics, adding more requires to modify the "ResetSceneTable" function constexpr chip::FabricIndex kFabric1 = 1; constexpr chip::FabricIndex kFabric2 = 7; +constexpr chip::FabricIndex kFabric3 = 77; // Scene storage ID -static const SceneStorageId sceneId1(kTestEndpoint1, 0xAA, 0x101); -static const SceneStorageId sceneId2(kTestEndpoint1, 0xBB, 0x00); -static const SceneStorageId sceneId3(kTestEndpoint2, 0xCC, 0x102); -static const SceneStorageId sceneId4(kTestEndpoint2, 0xBE, 0x00); -static const SceneStorageId sceneId5(kTestEndpoint1, 0x45, 0x103); -static const SceneStorageId sceneId6(kTestEndpoint1, 0x65, 0x00); -static const SceneStorageId sceneId7(kTestEndpoint1, 0x77, 0x101); -static const SceneStorageId sceneId8(kTestEndpoint3, 0xEE, 0x101); -static const SceneStorageId sceneId9(kTestEndpoint2, 0xAB, 0x101); +static const SceneStorageId sceneId1(kTestEndpoint1, kScene1, kGroup1); +static const SceneStorageId sceneId2(kTestEndpoint1, kScene4, kGroup1); +static const SceneStorageId sceneId3(kTestEndpoint2, kScene2, kGroup1); +static const SceneStorageId sceneId4(kTestEndpoint2, kScene4, kGroup1); +static const SceneStorageId sceneId5(kTestEndpoint1, kScene3, kGroup2); +static const SceneStorageId sceneId6(kTestEndpoint1, kScene4, kGroup2); +static const SceneStorageId sceneId7(kTestEndpoint1, kScene1, kGroup3); +static const SceneStorageId sceneId8(kTestEndpoint3, kScene1, kGroup4); +static const SceneStorageId sceneId9(kTestEndpoint2, kScene1, kGroup4); CharSpan empty; @@ -367,6 +380,14 @@ class TestSceneHandler : public scenes::DefaultSceneHandlerImpl class TestSceneTableImpl : public SceneTableImpl { +public: + TestSceneTableImpl(uint8_t maxScenesPerFabric = scenes::kMaxScenesPerFabric, + uint8_t maxScenesGlobal = scenes::kMaxScenesGlobal) : + SceneTableImpl(maxScenesPerFabric, maxScenesGlobal) + {} + ~TestSceneTableImpl() override {} + +protected: uint8_t GetClustersFromEndpoint(EndpointId endpoint, ClusterId * clusterList, uint8_t listLen) override { if (listLen >= 3) @@ -391,6 +412,7 @@ void ResetSceneTable(SceneTable * sceneTable) { sceneTable->RemoveFabric(kFabric1); sceneTable->RemoveFabric(kFabric2); + sceneTable->RemoveFabric(kFabric3); } void TestHandlerRegistration(nlTestSuite * aSuite, void * aContext) @@ -724,6 +746,8 @@ void TestHandlerFunctions(nlTestSuite * aSuite, void * aContext) void TestStoreScenes(nlTestSuite * aSuite, void * aContext) { SceneTable * sceneTable = &sSceneTable; + SceneId sceneList[scenes::kMaxScenesPerFabric]; + NL_TEST_ASSERT(aSuite, sceneTable); // Reset test @@ -745,8 +769,22 @@ void TestStoreScenes(nlTestSuite * aSuite, void * aContext) NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SceneSaveEFS(scene8)); SceneTableEntry scene; + Span sceneListSpan = Span(sceneList); + Span emptyListSpan = Span(sceneList, 0); + Span smallListSpan = Span(sceneList, 1); + + // Test Get All scenes in Group in empty scene table + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetAllSceneIdsInGroup(kFabric1, kGroup1, emptyListSpan)); + NL_TEST_ASSERT(aSuite, 0 == emptyListSpan.size()); + // Set test NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric1, scene1)); + + // Test single scene in table with 0 size span + NL_TEST_ASSERT(aSuite, CHIP_ERROR_BUFFER_TOO_SMALL == sceneTable->GetAllSceneIdsInGroup(kFabric1, kGroup1, emptyListSpan)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetAllSceneIdsInGroup(kFabric1, kGroup1, smallListSpan)); + NL_TEST_ASSERT(aSuite, 1 == smallListSpan.size()); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric1, scene2)); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric1, scene3)); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric1, scene4)); @@ -756,10 +794,11 @@ void TestStoreScenes(nlTestSuite * aSuite, void * aContext) NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric1, scene8)); // Too many scenes for 1 fabric - NL_TEST_ASSERT(aSuite, CHIP_ERROR_INVALID_LIST_LENGTH == sceneTable->SetSceneTableEntry(kFabric1, scene9)); + NL_TEST_ASSERT(aSuite, CHIP_ERROR_NO_MEMORY == sceneTable->SetSceneTableEntry(kFabric1, scene9)); // Not Found NL_TEST_ASSERT(aSuite, CHIP_ERROR_NOT_FOUND == sceneTable->GetSceneTableEntry(kFabric1, sceneId9, scene)); + NL_TEST_ASSERT(aSuite, CHIP_ERROR_BUFFER_TOO_SMALL == sceneTable->GetAllSceneIdsInGroup(kFabric1, kGroup1, emptyListSpan)); // Get test NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric1, sceneId1, scene)); @@ -787,6 +826,37 @@ void TestStoreScenes(nlTestSuite * aSuite, void * aContext) NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric1, sceneId8, scene)); NL_TEST_ASSERT(aSuite, scene == scene8); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SceneApplyEFS(scene)); + + // Test error when list too small in a full table + // Test failure for 3 spaces in 4 scenes list + NL_TEST_ASSERT(aSuite, CHIP_ERROR_BUFFER_TOO_SMALL == sceneTable->GetAllSceneIdsInGroup(kFabric1, kGroup1, smallListSpan)); + // Test failure for no space in a 4 scenes list + NL_TEST_ASSERT(aSuite, CHIP_ERROR_BUFFER_TOO_SMALL == sceneTable->GetAllSceneIdsInGroup(kFabric1, kGroup1, emptyListSpan)); + // Test failure for no space in a 1 scene list + NL_TEST_ASSERT(aSuite, CHIP_ERROR_BUFFER_TOO_SMALL == sceneTable->GetAllSceneIdsInGroup(kFabric1, kGroup3, emptyListSpan)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetAllSceneIdsInGroup(kFabric1, kGroup3, smallListSpan)); + NL_TEST_ASSERT(aSuite, 1 == smallListSpan.size()); + + // Test successfully getting Ids from various groups + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetAllSceneIdsInGroup(kFabric1, kGroup1, sceneListSpan)); + NL_TEST_ASSERT(aSuite, 4 == sceneListSpan.size()); + NL_TEST_ASSERT(aSuite, kScene1 == sceneList[0]); + NL_TEST_ASSERT(aSuite, kScene4 == sceneList[1]); + NL_TEST_ASSERT(aSuite, kScene2 == sceneList[2]); + NL_TEST_ASSERT(aSuite, kScene4 == sceneList[3]); + + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetAllSceneIdsInGroup(kFabric1, kGroup2, sceneListSpan)); + NL_TEST_ASSERT(aSuite, 2 == sceneListSpan.size()); + NL_TEST_ASSERT(aSuite, kScene3 == sceneList[0]); + NL_TEST_ASSERT(aSuite, kScene4 == sceneList[1]); + + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetAllSceneIdsInGroup(kFabric1, kGroup3, sceneListSpan)); + NL_TEST_ASSERT(aSuite, 1 == sceneListSpan.size()); + NL_TEST_ASSERT(aSuite, kScene1 == sceneList[0]); + + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetAllSceneIdsInGroup(kFabric1, kGroup4, sceneListSpan)); + NL_TEST_ASSERT(aSuite, 1 == sceneListSpan.size()); + NL_TEST_ASSERT(aSuite, kScene1 == sceneList[0]); } void TestOverwriteScenes(nlTestSuite * aSuite, void * aContext) @@ -943,6 +1013,31 @@ void TestRemoveScenes(nlTestSuite * aSuite, void * aContext) iterator = sceneTable->IterateSceneEntries(kFabric1); NL_TEST_ASSERT(aSuite, iterator->Count() == 0); iterator->Release(); + + // Test Remove all scenes in Group + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric1, scene1)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric1, scene2)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric1, scene3)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric1, scene4)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric1, scene5)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric1, scene6)); + iterator = sceneTable->IterateSceneEntries(kFabric1); + NL_TEST_ASSERT(aSuite, iterator->Count() == 6); + iterator->Release(); + + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->DeleteAllScenesInGroup(kFabric1, kGroup1)); + iterator = sceneTable->IterateSceneEntries(kFabric1); + NL_TEST_ASSERT(aSuite, iterator->Count() == 2); + NL_TEST_ASSERT(aSuite, iterator->Next(scene)); + NL_TEST_ASSERT(aSuite, scene == scene5); + NL_TEST_ASSERT(aSuite, iterator->Next(scene)); + NL_TEST_ASSERT(aSuite, scene == scene6); + iterator->Release(); + + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->DeleteAllScenesInGroup(kFabric1, kGroup2)); + iterator = sceneTable->IterateSceneEntries(kFabric1); + NL_TEST_ASSERT(aSuite, iterator->Count() == 0); + iterator->Release(); } void TestFabricScenes(nlTestSuite * aSuite, void * aContext) @@ -954,29 +1049,93 @@ void TestFabricScenes(nlTestSuite * aSuite, void * aContext) ResetSceneTable(sceneTable); SceneTableEntry scene; + uint8_t fabric_capacity = 0; + + // Verify capacities are at max + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetRemainingCapacity(kFabric1, fabric_capacity)); + NL_TEST_ASSERT(aSuite, scenes::kMaxScenesPerFabric == fabric_capacity); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetRemainingCapacity(kFabric2, fabric_capacity)); + NL_TEST_ASSERT(aSuite, scenes::kMaxScenesPerFabric == fabric_capacity); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetRemainingCapacity(kFabric3, fabric_capacity)); + NL_TEST_ASSERT(aSuite, scenes::kMaxScenesPerFabric == fabric_capacity); // Fabric 1 inserts NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric1, scene1)); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric1, scene2)); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric1, scene3)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric1, scene4)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric1, scene5)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric1, scene6)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric1, scene7)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric1, scene8)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetRemainingCapacity(kFabric1, fabric_capacity)); + NL_TEST_ASSERT(aSuite, 0 == fabric_capacity); // Fabric 2 inserts + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetRemainingCapacity(kFabric2, fabric_capacity)); + NL_TEST_ASSERT(aSuite, scenes::kMaxScenesPerFabric == fabric_capacity); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric2, scene1)); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric2, scene2)); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric2, scene3)); - + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric2, scene4)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetRemainingCapacity(kFabric2, fabric_capacity)); + NL_TEST_ASSERT(aSuite, (scenes::kMaxScenesPerFabric - 4) == fabric_capacity); + + // Fabric 3 inserts, should only be 4 spaces left at this point since 12 got taken + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetRemainingCapacity(kFabric3, fabric_capacity)); + NL_TEST_ASSERT(aSuite, scenes::kMaxScenesPerFabric - 4 == fabric_capacity); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric3, scene1)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric3, scene2)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric3, scene3)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric3, scene4)); + + // Checks capacity is now 0 accross all fabrics + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetRemainingCapacity(kFabric1, fabric_capacity)); + NL_TEST_ASSERT(aSuite, 0 == fabric_capacity); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetRemainingCapacity(kFabric2, fabric_capacity)); + NL_TEST_ASSERT(aSuite, 0 == fabric_capacity); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetRemainingCapacity(kFabric3, fabric_capacity)); + NL_TEST_ASSERT(aSuite, 0 == fabric_capacity); + + // To many scenes accross fabrics (Max scenes accross fabrics == 16) + NL_TEST_ASSERT(aSuite, CHIP_ERROR_NO_MEMORY == sceneTable->SetSceneTableEntry(kFabric3, scene5)); + NL_TEST_ASSERT(aSuite, CHIP_ERROR_NO_MEMORY == sceneTable->SetSceneTableEntry(kFabric2, scene5)); + + // Verifying all inserted scenes are accessible NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric1, sceneId1, scene)); NL_TEST_ASSERT(aSuite, scene == scene1); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric1, sceneId2, scene)); NL_TEST_ASSERT(aSuite, scene == scene2); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric1, sceneId3, scene)); NL_TEST_ASSERT(aSuite, scene == scene3); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric1, sceneId4, scene)); + NL_TEST_ASSERT(aSuite, scene == scene4); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric1, sceneId5, scene)); + NL_TEST_ASSERT(aSuite, scene == scene5); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric1, sceneId6, scene)); + NL_TEST_ASSERT(aSuite, scene == scene6); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric1, sceneId7, scene)); + NL_TEST_ASSERT(aSuite, scene == scene7); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric1, sceneId8, scene)); + NL_TEST_ASSERT(aSuite, scene == scene8); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric2, sceneId1, scene)); NL_TEST_ASSERT(aSuite, scene == scene1); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric2, sceneId2, scene)); NL_TEST_ASSERT(aSuite, scene == scene2); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric2, sceneId3, scene)); NL_TEST_ASSERT(aSuite, scene == scene3); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric2, sceneId4, scene)); + NL_TEST_ASSERT(aSuite, scene == scene4); + + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric3, sceneId1, scene)); + NL_TEST_ASSERT(aSuite, scene == scene1); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric3, sceneId2, scene)); + NL_TEST_ASSERT(aSuite, scene == scene2); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric3, sceneId3, scene)); + NL_TEST_ASSERT(aSuite, scene == scene3); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric3, sceneId4, scene)); + NL_TEST_ASSERT(aSuite, scene == scene4); // Remove Fabric 1 NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->RemoveFabric(kFabric1)); @@ -985,6 +1144,11 @@ void TestFabricScenes(nlTestSuite * aSuite, void * aContext) NL_TEST_ASSERT(aSuite, CHIP_ERROR_NOT_FOUND == sceneTable->GetSceneTableEntry(kFabric1, sceneId1, scene)); NL_TEST_ASSERT(aSuite, CHIP_ERROR_NOT_FOUND == sceneTable->GetSceneTableEntry(kFabric1, sceneId2, scene)); NL_TEST_ASSERT(aSuite, CHIP_ERROR_NOT_FOUND == sceneTable->GetSceneTableEntry(kFabric1, sceneId3, scene)); + NL_TEST_ASSERT(aSuite, CHIP_ERROR_NOT_FOUND == sceneTable->GetSceneTableEntry(kFabric1, sceneId4, scene)); + NL_TEST_ASSERT(aSuite, CHIP_ERROR_NOT_FOUND == sceneTable->GetSceneTableEntry(kFabric1, sceneId5, scene)); + NL_TEST_ASSERT(aSuite, CHIP_ERROR_NOT_FOUND == sceneTable->GetSceneTableEntry(kFabric1, sceneId6, scene)); + NL_TEST_ASSERT(aSuite, CHIP_ERROR_NOT_FOUND == sceneTable->GetSceneTableEntry(kFabric1, sceneId7, scene)); + NL_TEST_ASSERT(aSuite, CHIP_ERROR_NOT_FOUND == sceneTable->GetSceneTableEntry(kFabric1, sceneId8, scene)); // Verify Fabric 2 still there NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric2, sceneId1, scene)); @@ -993,6 +1157,46 @@ void TestFabricScenes(nlTestSuite * aSuite, void * aContext) NL_TEST_ASSERT(aSuite, scene == scene2); NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric2, sceneId3, scene)); NL_TEST_ASSERT(aSuite, scene == scene3); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric2, sceneId4, scene)); + NL_TEST_ASSERT(aSuite, scene == scene4); + + // Verify capacity updated for all fabrics + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetRemainingCapacity(kFabric1, fabric_capacity)); + NL_TEST_ASSERT(aSuite, scenes::kMaxScenesPerFabric == fabric_capacity); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetRemainingCapacity(kFabric2, fabric_capacity)); + NL_TEST_ASSERT(aSuite, scenes::kMaxScenesPerFabric - 4 == fabric_capacity); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetRemainingCapacity(kFabric3, fabric_capacity)); + NL_TEST_ASSERT(aSuite, scenes::kMaxScenesPerFabric - 4 == fabric_capacity); + + // Verify we can now write more scenes in scene fabric 2 + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric2, scene5)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric2, scene6)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric2, scene7)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric2, scene8)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric2, sceneId5, scene)); + NL_TEST_ASSERT(aSuite, scene == scene5); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric2, sceneId6, scene)); + NL_TEST_ASSERT(aSuite, scene == scene6); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric2, sceneId7, scene)); + NL_TEST_ASSERT(aSuite, scene == scene7); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric2, sceneId8, scene)); + NL_TEST_ASSERT(aSuite, scene == scene8); + + // Verify capacity updated properly + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetRemainingCapacity(kFabric1, fabric_capacity)); + NL_TEST_ASSERT(aSuite, scenes::kMaxScenesPerFabric - 4 == fabric_capacity); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetRemainingCapacity(kFabric3, fabric_capacity)); + NL_TEST_ASSERT(aSuite, scenes::kMaxScenesPerFabric - 4 == fabric_capacity); + + // Verify Fabric 3 still there + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric3, sceneId1, scene)); + NL_TEST_ASSERT(aSuite, scene == scene1); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric3, sceneId2, scene)); + NL_TEST_ASSERT(aSuite, scene == scene2); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric3, sceneId3, scene)); + NL_TEST_ASSERT(aSuite, scene == scene3); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric3, sceneId4, scene)); + NL_TEST_ASSERT(aSuite, scene == scene4); // Remove Fabric 2 NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->RemoveFabric(kFabric2)); @@ -1001,6 +1205,172 @@ void TestFabricScenes(nlTestSuite * aSuite, void * aContext) NL_TEST_ASSERT(aSuite, CHIP_ERROR_NOT_FOUND == sceneTable->GetSceneTableEntry(kFabric2, sceneId1, scene)); NL_TEST_ASSERT(aSuite, CHIP_ERROR_NOT_FOUND == sceneTable->GetSceneTableEntry(kFabric2, sceneId2, scene)); NL_TEST_ASSERT(aSuite, CHIP_ERROR_NOT_FOUND == sceneTable->GetSceneTableEntry(kFabric2, sceneId3, scene)); + NL_TEST_ASSERT(aSuite, CHIP_ERROR_NOT_FOUND == sceneTable->GetSceneTableEntry(kFabric2, sceneId4, scene)); + NL_TEST_ASSERT(aSuite, CHIP_ERROR_NOT_FOUND == sceneTable->GetSceneTableEntry(kFabric2, sceneId5, scene)); + NL_TEST_ASSERT(aSuite, CHIP_ERROR_NOT_FOUND == sceneTable->GetSceneTableEntry(kFabric2, sceneId6, scene)); + NL_TEST_ASSERT(aSuite, CHIP_ERROR_NOT_FOUND == sceneTable->GetSceneTableEntry(kFabric2, sceneId7, scene)); + NL_TEST_ASSERT(aSuite, CHIP_ERROR_NOT_FOUND == sceneTable->GetSceneTableEntry(kFabric2, sceneId8, scene)); + + // Verify Fabric 3 still there + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric3, sceneId1, scene)); + NL_TEST_ASSERT(aSuite, scene == scene1); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric3, sceneId2, scene)); + NL_TEST_ASSERT(aSuite, scene == scene2); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric3, sceneId3, scene)); + NL_TEST_ASSERT(aSuite, scene == scene3); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetSceneTableEntry(kFabric3, sceneId4, scene)); + NL_TEST_ASSERT(aSuite, scene == scene4); + + // Remove Fabric 3 + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->RemoveFabric(kFabric3)); + // Verify Fabric 3 removed + NL_TEST_ASSERT(aSuite, CHIP_ERROR_NOT_FOUND == sceneTable->RemoveFabric(kFabric3)); + NL_TEST_ASSERT(aSuite, CHIP_ERROR_NOT_FOUND == sceneTable->GetSceneTableEntry(kFabric3, sceneId1, scene)); + NL_TEST_ASSERT(aSuite, CHIP_ERROR_NOT_FOUND == sceneTable->GetSceneTableEntry(kFabric3, sceneId2, scene)); + NL_TEST_ASSERT(aSuite, CHIP_ERROR_NOT_FOUND == sceneTable->GetSceneTableEntry(kFabric3, sceneId3, scene)); + NL_TEST_ASSERT(aSuite, CHIP_ERROR_NOT_FOUND == sceneTable->GetSceneTableEntry(kFabric3, sceneId4, scene)); + + // Verify capacity updated for all fabrics + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetRemainingCapacity(kFabric1, fabric_capacity)); + NL_TEST_ASSERT(aSuite, scenes::kMaxScenesPerFabric == fabric_capacity); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetRemainingCapacity(kFabric2, fabric_capacity)); + NL_TEST_ASSERT(aSuite, scenes::kMaxScenesPerFabric == fabric_capacity); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetRemainingCapacity(kFabric3, fabric_capacity)); + NL_TEST_ASSERT(aSuite, scenes::kMaxScenesPerFabric == fabric_capacity); +} + +void TestOTAChanges(nlTestSuite * aSuite, void * aContext) +{ + SceneTable * sceneTable = &sSceneTable; + NL_TEST_ASSERT(aSuite, sceneTable); + + // Reset test + ResetSceneTable(sceneTable); + + SceneTableEntry scene; + uint8_t fabric_capacity = 0; + + // Fill scene table + // Fill fabric 1 to capacity + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric1, scene1)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric1, scene2)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric1, scene3)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric1, scene4)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric1, scene5)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric1, scene6)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric1, scene7)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric1, scene8)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetRemainingCapacity(kFabric1, fabric_capacity)); + NL_TEST_ASSERT(aSuite, 0 == fabric_capacity); + auto * iterator = sceneTable->IterateSceneEntries(kFabric1); + NL_TEST_ASSERT(aSuite, scenes::kMaxScenesPerFabric == iterator->Count()); + iterator->Release(); + + // Fill fabric 2 to capacity + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric2, scene1)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric2, scene2)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric2, scene3)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric2, scene4)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric2, scene5)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric2, scene6)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric2, scene7)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->SetSceneTableEntry(kFabric2, scene8)); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetRemainingCapacity(kFabric1, fabric_capacity)); + NL_TEST_ASSERT(aSuite, 0 == fabric_capacity); + iterator = sceneTable->IterateSceneEntries(kFabric2); + NL_TEST_ASSERT(aSuite, scenes::kMaxScenesPerFabric == iterator->Count()); + iterator->Release(); + // SceneTable should be full at this point + uint8_t scene_count; + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetGlobalSceneCount(scene_count)); + // Global count should not have been modified + NL_TEST_ASSERT(aSuite, scenes::kMaxScenesGlobal == scene_count); + + // Test failure to init a SceneTable with sizes above the defined max scenes per fabric or globaly + TestSceneTableImpl SceneTableTooManyPerFabric(scenes::kMaxScenesPerFabric + 1, scenes::kMaxScenesGlobal); + NL_TEST_ASSERT(aSuite, CHIP_ERROR_INVALID_INTEGER_VALUE == SceneTableTooManyPerFabric.Init(&testStorage)); + SceneTableTooManyPerFabric.Finish(); + + TestSceneTableImpl SceneTableTooManyGlobal(scenes::kMaxScenesPerFabric, scenes::kMaxScenesGlobal + 1); + NL_TEST_ASSERT(aSuite, CHIP_ERROR_INVALID_INTEGER_VALUE == SceneTableTooManyGlobal.Init(&testStorage)); + SceneTableTooManyGlobal.Finish(); + + // Create a new table with a different limit of scenes per fabric + TestSceneTableImpl ReducedSceneTable(scenes::kMaxScenesPerFabric - 1, scenes::kMaxScenesGlobal - 2); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == ReducedSceneTable.Init(&testStorage)); + // Global count should not have been modified + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == ReducedSceneTable.GetGlobalSceneCount(scene_count)); + NL_TEST_ASSERT(aSuite, scenes::kMaxScenesGlobal == scene_count); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == ReducedSceneTable.GetRemainingCapacity(kFabric1, fabric_capacity)); + NL_TEST_ASSERT(aSuite, 0 == fabric_capacity); + + // Load a scene from fabric 1, this should adjust fabric 1 scene count in flash + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == ReducedSceneTable.GetSceneTableEntry(kFabric1, sceneId1, scene)); + NL_TEST_ASSERT(aSuite, scene == scene1); + + // The number count of scenes in Fabric 1 should have been adjusted here + iterator = ReducedSceneTable.IterateSceneEntries(kFabric1); + NL_TEST_ASSERT(aSuite, scenes::kMaxScenesPerFabric - 1 == iterator->Count()); + iterator->Release(); + // Capacity should still be 0 + NL_TEST_ASSERT(aSuite, 0 == fabric_capacity); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == ReducedSceneTable.GetGlobalSceneCount(scene_count)); + // Global count should have been reduced by 1 + NL_TEST_ASSERT(aSuite, scenes::kMaxScenesGlobal - 1 == scene_count); + + // Remove a Scene from the Fabric 1 + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == ReducedSceneTable.RemoveSceneTableEntry(kFabric1, scene.mStorageId)); + // Check count updated for fabric + iterator = ReducedSceneTable.IterateSceneEntries(kFabric1); + NL_TEST_ASSERT(aSuite, scenes::kMaxScenesPerFabric - 2 == iterator->Count()); + iterator->Release(); + // Check fabric still doesn't have capacity because fabric 2 still have a higher number of scene than allowed + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == ReducedSceneTable.GetRemainingCapacity(kFabric1, fabric_capacity)); + NL_TEST_ASSERT(aSuite, 0 == fabric_capacity); + // Remove another scene from fabric 1 + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == ReducedSceneTable.RemoveSceneTableEntry(kFabric1, scene2.mStorageId)); + // Check count updated for fabric + iterator = ReducedSceneTable.IterateSceneEntries(kFabric1); + NL_TEST_ASSERT(aSuite, scenes::kMaxScenesPerFabric - 3 == iterator->Count()); + iterator->Release(); + + // Global count should now have been adjusted + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == ReducedSceneTable.GetGlobalSceneCount(scene_count)); + NL_TEST_ASSERT(aSuite, scenes::kMaxScenesGlobal - 3 == scene_count); + // Confirm we now have capacity + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == ReducedSceneTable.GetRemainingCapacity(kFabric1, fabric_capacity)); + NL_TEST_ASSERT(aSuite, 1 == fabric_capacity); + + // Load a scene from fabric 2, this should adjust fabric 1 scene count in flash + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == ReducedSceneTable.GetSceneTableEntry(kFabric2, sceneId1, scene)); + NL_TEST_ASSERT(aSuite, scene == scene1); + + // The number count of scenes in Fabric 1 should have been adjusted here + iterator = ReducedSceneTable.IterateSceneEntries(kFabric2); + NL_TEST_ASSERT(aSuite, scenes::kMaxScenesPerFabric - 1 == iterator->Count()); + iterator->Release(); + // Global count should now have been adjusted + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == ReducedSceneTable.GetGlobalSceneCount(scene_count)); + NL_TEST_ASSERT(aSuite, scenes::kMaxScenesGlobal - 4 == scene_count); + // Confirm we now have capacity in the first fabric since we previously removed 2 scenes form there + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == ReducedSceneTable.GetRemainingCapacity(kFabric1, fabric_capacity)); + NL_TEST_ASSERT(aSuite, 2 == fabric_capacity); + // Fabric 2 should still be at capacity + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == ReducedSceneTable.GetRemainingCapacity(kFabric2, fabric_capacity)); + NL_TEST_ASSERT(aSuite, 0 == fabric_capacity); + + ReducedSceneTable.Finish(); + + // The Scene 8 should now have been deleted from the memory and thus not be accessible from both fabrics in the original scene + // table + NL_TEST_ASSERT(aSuite, CHIP_ERROR_NOT_FOUND == sceneTable->GetSceneTableEntry(kFabric1, sceneId8, scene)); + NL_TEST_ASSERT(aSuite, CHIP_ERROR_NOT_FOUND == sceneTable->GetSceneTableEntry(kFabric2, sceneId8, scene)); + // The Remaining capacity in the original scene table therefore have been modified as well + // Fabric 2 should still be at capacity + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetRemainingCapacity(kFabric1, fabric_capacity)); + NL_TEST_ASSERT(aSuite, 3 == fabric_capacity); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == sceneTable->GetRemainingCapacity(kFabric2, fabric_capacity)); + NL_TEST_ASSERT(aSuite, 1 == fabric_capacity); } } // namespace TestScenes @@ -1040,6 +1410,7 @@ int TestSceneTable() NL_TEST_DEF("TestIterateScenes", TestScenes::TestIterateScenes), NL_TEST_DEF("TestRemoveScenes", TestScenes::TestRemoveScenes), NL_TEST_DEF("TestFabricScenes", TestScenes::TestFabricScenes), + NL_TEST_DEF("TestOTAChanges", TestScenes::TestOTAChanges), NL_TEST_SENTINEL() }; diff --git a/src/lib/support/DefaultStorageKeyAllocator.h b/src/lib/support/DefaultStorageKeyAllocator.h index 6be4b307eda680..418220a3f3d520 100644 --- a/src/lib/support/DefaultStorageKeyAllocator.h +++ b/src/lib/support/DefaultStorageKeyAllocator.h @@ -198,6 +198,7 @@ class DefaultStorageKeyAllocator } static StorageKeyName SubscriptionResumptionMaxCount() { return StorageKeyName::Formatted("g/sum"); } + static StorageKeyName GlobalSceneCountKey() { return StorageKeyName::Formatted("g/scc"); } static StorageKeyName FabricSceneDataKey(chip::FabricIndex fabric) { return StorageKeyName::Formatted("f/%x/sc", fabric); } static StorageKeyName FabricSceneKey(chip::FabricIndex fabric, uint8_t id) { diff --git a/src/lib/support/PersistentData.h b/src/lib/support/PersistentData.h index f2961c7a5427f0..4ad74da92bf32a 100644 --- a/src/lib/support/PersistentData.h +++ b/src/lib/support/PersistentData.h @@ -52,7 +52,7 @@ struct PersistentData return storage->SyncSetKeyValue(key.KeyName(), buffer, static_cast(writer.GetLengthWritten())); } - CHIP_ERROR Load(PersistentStorageDelegate * storage) + virtual CHIP_ERROR Load(PersistentStorageDelegate * storage) { VerifyOrReturnError(nullptr != storage, CHIP_ERROR_INVALID_ARGUMENT);