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);