From 144597626491bd3b521a9325802b942ed94d3f0c Mon Sep 17 00:00:00 2001 From: lpbeliveau-silabs Date: Mon, 7 Nov 2022 16:30:11 -0500 Subject: [PATCH] Implementation of persistant storage of scene table along with test. --- scripts/examples/gn_efr32_example.sh | 11 +- src/app/chip_data_model.gni | 5 + .../clusters/scenes/ExtensionFieldsSets.cpp | 95 ++++ src/app/clusters/scenes/ExtensionFieldsSets.h | 100 ++++ src/app/clusters/scenes/SceneTable.h | 243 +++++++++ src/app/clusters/scenes/SceneTableImpl.cpp | 467 ++++++++++++++++++ src/app/clusters/scenes/SceneTableImpl.h | 109 ++++ src/lib/core/DataModelTypes.h | 1 + src/lib/core/SceneId.h | 66 +++ src/lib/support/DefaultStorageKeyAllocator.h | 13 +- src/lib/support/TestSceneTable.h | 140 ++++++ 11 files changed, 1248 insertions(+), 2 deletions(-) create mode 100644 src/app/clusters/scenes/ExtensionFieldsSets.cpp create mode 100644 src/app/clusters/scenes/ExtensionFieldsSets.h create mode 100644 src/app/clusters/scenes/SceneTable.h create mode 100644 src/app/clusters/scenes/SceneTableImpl.cpp create mode 100644 src/app/clusters/scenes/SceneTableImpl.h create mode 100644 src/lib/core/SceneId.h create mode 100644 src/lib/support/TestSceneTable.h diff --git a/scripts/examples/gn_efr32_example.sh b/scripts/examples/gn_efr32_example.sh index 45284c96587ef9..c7a0681764083a 100755 --- a/scripts/examples/gn_efr32_example.sh +++ b/scripts/examples/gn_efr32_example.sh @@ -144,6 +144,10 @@ else shift while [ $# -gt 0 ]; do case $1 in + --clean) + DIR_CLEAN=true + shift + ;; --wifi) if [ -z "$2" ]; then echo "--wifi requires rs9116 or SiWx917 or wf200" @@ -154,7 +158,7 @@ else elif [ "$2" = "SiWx917" ]; then optArgs+="use_SiWx917=true " elif [ "$2" = "wf200" ]; then - optArgs+="use_wf200=true " + optArgs+="use_wf200=true" else echo "Wifi usage: --wifi rs9116|SiWx917|wf200" exit 1 @@ -242,6 +246,11 @@ else BUILD_DIR=$OUTDIR/$SILABS_BOARD echo BUILD_DIR="$BUILD_DIR" + + if [ "$DIR_CLEAN" == true ]; then + rm -rf "$BUILD_DIR" + fi + if [ "$USE_WIFI" == true ]; then # wifi build # NCP mode EFR32 + wifi module diff --git a/src/app/chip_data_model.gni b/src/app/chip_data_model.gni index 1233b8458c101c..cfbd9c5911492c 100644 --- a/src/app/chip_data_model.gni +++ b/src/app/chip_data_model.gni @@ -157,6 +157,11 @@ template("chip_data_model") { "${_app_root}/clusters/identify-server/identify-server.h", "${_app_root}/clusters/level-control/level-control.h", "${_app_root}/clusters/on-off-server/on-off-server.h", + "${_app_root}/clusters/scenes/ExtensionFieldsSets.cpp", + "${_app_root}/clusters/scenes/ExtensionFieldsSets.h", + "${_app_root}/clusters/scenes/SceneTable.h", + "${_app_root}/clusters/scenes/SceneTableImpl.cpp", + "${_app_root}/clusters/scenes/SceneTableImpl.h", "${_app_root}/clusters/scenes/scenes-tokens.h", "${_app_root}/clusters/scenes/scenes.h", "${_app_root}/util/ClientMonitoringRegistrationTable.cpp", diff --git a/src/app/clusters/scenes/ExtensionFieldsSets.cpp b/src/app/clusters/scenes/ExtensionFieldsSets.cpp new file mode 100644 index 00000000000000..66d578940bd266 --- /dev/null +++ b/src/app/clusters/scenes/ExtensionFieldsSets.cpp @@ -0,0 +1,95 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://urldefense.com/v3/__http://www.apache.org/licenses/LICENSE-2.0__;!!N30Cs7Jr!UgbMbEQ59BIK-1Xslc7QXYm0lQBh92qA3ElecRe1CF_9YhXxbwPOZa6j4plru7B7kCJ7bKQgHxgQrket3-Dnk268sIdA7Qb8$ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ExtensionFieldsSets.h" + +namespace chip { +namespace scenes { + +ExtensionFieldsSets::ExtensionFieldsSets() +{ + // check if any of the clusters with scene attributes are enabled + if (this->kExentesionFieldsSetsSize == 1) + { + // a size of 1 byte indicates an empty struct, or a struct that only contains : + // the on-off cluster + // the Mode select cluster + // the door lock cluster +#ifdef ZCL_USING_ON_OFF_CLUSTER_SERVER + this->enabledFieldSets.onOff = false; + +#elif defined(ZCL_USING_MODE_SELECT_CLUSTER_SERVER) + this->enabledFieldSets.currentMode = 0; +#elif defined(ZCL_USING_DOOR_LOCK_CLUSTER_SERVER) + this->enabledFieldSets.lockState = 0; +#else + this->empty = true; +#endif + } + else if (this->kExentesionFieldsSetsSize > 1) + { + memset(&this->enabledFieldSets, 0, kExentesionFieldsSetsSize); + } +} + +CHIP_ERROR ExtensionFieldsSets::Serialize(TLV::TLVWriter & writer) const +{ + if (!this->empty) + { + TLV::TLVType container; + ReturnErrorOnFailure(writer.StartContainer(TLV::ContextTag(1), TLV::kTLVType_Structure, container)); + + ReturnErrorOnFailure( + writer.PutBytes(TagEnabledFielsSets(), (uint8_t *) &this->enabledFieldSets, kExentesionFieldsSetsSize)); + + return writer.EndContainer(container); + } + else + { + return CHIP_NO_ERROR; + } +} + +CHIP_ERROR ExtensionFieldsSets::Deserialize(TLV::TLVReader & reader) +{ + if (!this->empty) + { + TLV::TLVType container; + ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::ContextTag(1))); + ReturnErrorOnFailure(reader.EnterContainer(container)); + + ReturnErrorOnFailure(reader.Next(TagEnabledFielsSets())); + ReturnErrorOnFailure(reader.GetBytes((uint8_t *) &this->enabledFieldSets, kExentesionFieldsSetsSize)); + + return reader.ExitContainer(container); + } + else + { + return CHIP_NO_ERROR; + } +} +void ExtensionFieldsSets::clear() +{ + if (!this->empty) + { + memset(&this->enabledFieldSets, 0, sizeof(this->enabledFieldSets)); + } +} + +} // namespace scenes + +} // namespace chip \ No newline at end of file diff --git a/src/app/clusters/scenes/ExtensionFieldsSets.h b/src/app/clusters/scenes/ExtensionFieldsSets.h new file mode 100644 index 00000000000000..cae1d41499ce5a --- /dev/null +++ b/src/app/clusters/scenes/ExtensionFieldsSets.h @@ -0,0 +1,100 @@ +/** + * + * Copyright (c) 2023 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://urldefense.com/v3/__http://www.apache.org/licenses/LICENSE-2.0__;!!N30Cs7Jr!UgbMbEQ59BIK-1Xslc7QXYm0lQBh92qA3ElecRe1CF_9YhXxbwPOZa6j4plru7B7kCJ7bKQgHxgQrket3-Dnk268sIdA7Qb8$ + * + * 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 + +namespace chip { +namespace scenes { + +typedef struct fieldSets_s +{ +#ifdef ZCL_USING_ON_OFF_CLUSTER_SERVER + bool onOff; +#endif + +#ifdef ZCL_USING_LEVEL_CONTROL_CLUSTER_SERVER + uint8_t currentLevel; + uint16_t currentFrequency; +#endif + +#ifdef ZCL_USING_MODE_SELECT_CLUSTER_SERVER + uint8_t currentMode; +#endif + +#ifdef ZCL_USING_COLOR_CONTROL_CLUSTER_SERVER + uint8_t currentSaturation; + uint16_t currentX; + uint16_t currentY; + uint16_t colorTemperatureMireds; + uint16_t enhancedCurrentHue; + uint8_t enhancedColorMode; + uint8_t colorLoopActive; + uint8_t colorLoopDirection; + uint16_t colorLoopTime; +#endif + +#ifdef ZCL_USING_THERMOSTAT_CLUSTER_SERVER + uint16_t occupiedCoolingSetpoint; + uint16_t occupiedHeatingSetpoint; + uint8_t systemMode; +#endif + +#ifdef ZCL_USING_DOOR_LOCK_CLUSTER_SERVER + uint8_t lockState; +#endif + +#ifdef ZCL_USING_WINDOW_COVERING_CLUSTER_SERVER + uint8_t currentPositionLiftPercentage; + uint8_t currentPositionTiltPercentage; + uint8_t targetPositionLiftPercent100ths; + uint8_t targetPositionTiltPercent100ths; +#endif +} fieldSets_t; + +class ExtensionFieldsSets +{ +public: + static constexpr size_t kExentesionFieldsSetsSize = sizeof(fieldSets_t); + static constexpr TLV::Tag TagEnabledFielsSets() { return TLV::ContextTag(1); } + fieldSets_t enabledFieldSets; + bool empty = false; + + ExtensionFieldsSets(); + virtual ~ExtensionFieldsSets() = default; + + CHIP_ERROR Serialize(TLV::TLVWriter & writer) const; + CHIP_ERROR Deserialize(TLV::TLVReader & reader); + + void clear(); + + bool operator==(const ExtensionFieldsSets & other) + { + return (!memcmp(&this->enabledFieldSets, &other.enabledFieldSets, kExentesionFieldsSetsSize)); + } + + void operator=(const ExtensionFieldsSets & other) + { + memcpy(&this->enabledFieldSets, &other.enabledFieldSets, kExentesionFieldsSetsSize); + } +}; +} // namespace scenes +} // namespace chip diff --git a/src/app/clusters/scenes/SceneTable.h b/src/app/clusters/scenes/SceneTable.h new file mode 100644 index 00000000000000..82f89ca3d55152 --- /dev/null +++ b/src/app/clusters/scenes/SceneTable.h @@ -0,0 +1,243 @@ +/* + * + * 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 + * + * https://urldefense.com/v3/__http://www.apache.org/licenses/LICENSE-2.0__;!!N30Cs7Jr!UgbMbEQ59BIK-1Xslc7QXYm0lQBh92qA3ElecRe1CF_9YhXxbwPOZa6j4plru7B7kCJ7bKQgHxgQrket3-Dnk268sIdA7Qb8$ + * + * 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 + +namespace chip { +namespace scenes { + +class SceneTable +{ +public: + static constexpr size_t kIteratorsMax = CHIP_CONFIG_MAX_GROUP_CONCURRENT_ITERATORS; + static constexpr size_t kSceneNameMax = ZCL_SCENES_CLUSTER_MAXIMUM_NAME_LENGTH; + static constexpr uint8_t kMaxScenePerFabric = SCENE_MAX_PER_FABRIC; + /// @brief struct used to identify a scene in storage by 3 ids, endpoint, group and scene + struct SceneStorageId + { + static constexpr TLV::Tag TagFirstSceneEndpointID() { return TLV::ContextTag(1); } + static constexpr TLV::Tag TagFirstSceneGroupID() { return TLV::ContextTag(2); } + static constexpr TLV::Tag TagFirstSceneID() { return TLV::ContextTag(3); } + + // Identifies endpoint to which this scene applies to + EndpointId sceneEndpointId = kInvalidEndpointId; + // Identifies group within the scope of the given fabric + SceneGroupID sceneGroupId = kGlobalGroupSceneId; + SceneId sceneId = kUndefinedSceneId; + + SceneStorageId() = default; + SceneStorageId(EndpointId endpoint, SceneId id, SceneGroupID groupId = kGlobalGroupSceneId) : + sceneEndpointId(endpoint), sceneGroupId(groupId), sceneId(id) + {} + SceneStorageId(const SceneStorageId & storageId) : + sceneEndpointId(storageId.sceneEndpointId), sceneGroupId(storageId.sceneGroupId), sceneId(storageId.sceneId) + {} + CHIP_ERROR Serialize(TLV::TLVWriter & writer) const + { + TLV::TLVType container; + ReturnErrorOnFailure(writer.StartContainer(TLV::ContextTag(1), TLV::kTLVType_Structure, container)); + + ReturnErrorOnFailure(writer.Put(TagFirstSceneEndpointID(), static_cast(this->sceneEndpointId))); + ReturnErrorOnFailure(writer.Put(TagFirstSceneGroupID(), static_cast(this->sceneGroupId))); + ReturnErrorOnFailure(writer.Put(TagFirstSceneID(), static_cast(this->sceneId))); + + return writer.EndContainer(container); + } + CHIP_ERROR Deserialize(TLV::TLVReader & reader) + { + TLV::TLVType container; + ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::ContextTag(1))); + ReturnErrorOnFailure(reader.EnterContainer(container)); + + ReturnErrorOnFailure(reader.Next(TagFirstSceneEndpointID())); + ReturnErrorOnFailure(reader.Get(this->sceneEndpointId)); + ReturnErrorOnFailure(reader.Next(TagFirstSceneGroupID())); + ReturnErrorOnFailure(reader.Get(this->sceneGroupId)); + ReturnErrorOnFailure(reader.Next(TagFirstSceneID())); + ReturnErrorOnFailure(reader.Get(this->sceneId)); + + return reader.ExitContainer(container); + } + + void clear() + { + sceneEndpointId = kInvalidEndpointId; + sceneGroupId = kGlobalGroupSceneId; + sceneId = kUndefinedSceneId; + } + bool operator==(const SceneStorageId & other) + { + return (this->sceneEndpointId == other.sceneEndpointId && this->sceneGroupId == other.sceneGroupId && + this->sceneId == other.sceneId); + } + void operator=(const SceneStorageId & other) + { + this->sceneEndpointId = other.sceneEndpointId; + this->sceneGroupId = other.sceneGroupId; + this->sceneId = other.sceneId; + } + }; + + /// @brief struct used to store data held in a scene + struct SceneData + { + static constexpr TLV::Tag TagSceneName() { return TLV::ContextTag(1); } + static constexpr TLV::Tag TagSceneTransitionTime() { return TLV::ContextTag(2); } + static constexpr TLV::Tag TagSceneTransitionTime100() { return TLV::ContextTag(3); } + + char name[kSceneNameMax] = { 0 }; + SceneTransitionTime sceneTransitionTime = 0; + ExtensionFieldsSets extentsionFieldsSets; + TransitionTime100ms transitionTime100 = 0; + + SceneData() = default; + SceneData(const char * sceneName, SceneTransitionTime time = 0, TransitionTime100ms time100ms = 0) : + sceneTransitionTime(time), transitionTime100(time100ms) + { + SetName(sceneName); + } + SceneData(const char * sceneName, ExtensionFieldsSets fields, SceneTransitionTime time = 0, + TransitionTime100ms time100ms = 0) : + sceneTransitionTime(time), + transitionTime100(time100ms) + { + SetName(sceneName); + extentsionFieldsSets = fields; + } + SceneData(const SceneData & data) : sceneTransitionTime(data.sceneTransitionTime), transitionTime100(data.transitionTime100) + { + SetName(data.name); + extentsionFieldsSets = data.extentsionFieldsSets; + } + + CHIP_ERROR Serialize(TLV::TLVWriter & writer) const + { + TLV::TLVType container; + ReturnErrorOnFailure(writer.StartContainer(TLV::ContextTag(1), TLV::kTLVType_Structure, container)); + + size_t name_size = strnlen(this->name, kSceneNameMax); + ReturnErrorOnFailure(writer.PutString(TagSceneName(), this->name, static_cast(name_size))); + ReturnErrorOnFailure(writer.Put(TagSceneTransitionTime(), static_cast(this->sceneTransitionTime))); + ReturnErrorOnFailure(writer.Put(TagSceneTransitionTime100(), static_cast(this->transitionTime100))); + ReturnErrorOnFailure(this->extentsionFieldsSets.Serialize(writer)); + + return writer.EndContainer(container); + } + CHIP_ERROR Deserialize(TLV::TLVReader & reader) + { + TLV::TLVType container; + ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::ContextTag(1))); + ReturnErrorOnFailure(reader.EnterContainer(container)); + + ReturnErrorOnFailure(reader.Next(TagSceneName())); + ReturnErrorOnFailure(reader.GetString(this->name, sizeof(this->name))); + size_t name_size = strnlen(this->name, kSceneNameMax); // TODO : verify use of strnlen is ok + this->name[name_size] = 0; // Putting a null terminator + ReturnErrorOnFailure(reader.Next(TagSceneTransitionTime())); + ReturnErrorOnFailure(reader.Get(this->sceneTransitionTime)); + ReturnErrorOnFailure(reader.Next(TagSceneTransitionTime100())); + ReturnErrorOnFailure(reader.Get(this->transitionTime100)); + ReturnErrorOnFailure(this->extentsionFieldsSets.Deserialize(reader)); + + return reader.ExitContainer(container); + } + void SetName(const char * sceneName) + { + if (nullptr == sceneName) + { + name[0] = 0; + } + else + { + Platform::CopyString(name, sceneName); + } + } + + void clear() + { + this->SetName(nullptr); + sceneTransitionTime = 0; + transitionTime100 = 0; + extentsionFieldsSets.clear(); + } + + bool operator==(const SceneData & other) + { + return (!strncmp(this->name, other.name, kSceneNameMax) && (this->sceneTransitionTime == other.sceneTransitionTime) && + (this->transitionTime100 == other.transitionTime100) && + (this->extentsionFieldsSets == other.extentsionFieldsSets)); + } + + void operator=(const SceneData & other) + { + this->SetName(other.name); + this->extentsionFieldsSets = other.extentsionFieldsSets; + this->sceneTransitionTime = other.sceneTransitionTime; + this->transitionTime100 = other.transitionTime100; + } + }; + + /// @brief Struct combining both ID and data of a table entry + struct SceneTableEntry + { + + // ID + SceneStorageId storageId; + + // DATA + SceneData storageData; + + SceneTableEntry() = default; + SceneTableEntry(SceneStorageId id) : storageId(id) {} + SceneTableEntry(const SceneStorageId id, const SceneData data) : storageId(id), storageData(data) {} + + bool operator==(const SceneTableEntry & other) { return (this->storageId == other.storageId); } + + void operator=(const SceneTableEntry & other) + { + this->storageId = other.storageId; + this->storageData = other.storageData; + } + }; + + SceneTable(){}; + virtual ~SceneTable() = default; + + // Not copyable + SceneTable(const SceneTable &) = delete; + SceneTable & operator=(const SceneTable &) = delete; + + virtual CHIP_ERROR Init() = 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; + + // Iterators + using SceneEntryIterator = CommonIterator; + + virtual SceneEntryIterator * IterateSceneEntry(FabricIndex fabric_index) = 0; +}; + +} // namespace scenes +} // namespace chip diff --git a/src/app/clusters/scenes/SceneTableImpl.cpp b/src/app/clusters/scenes/SceneTableImpl.cpp new file mode 100644 index 00000000000000..306c6016aa8e9a --- /dev/null +++ b/src/app/clusters/scenes/SceneTableImpl.cpp @@ -0,0 +1,467 @@ +/* + * + * 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 + * + * https://urldefense.com/v3/__http://www.apache.org/licenses/LICENSE-2.0__;!!N30Cs7Jr!UgbMbEQ59BIK-1Xslc7QXYm0lQBh92qA3ElecRe1CF_9YhXxbwPOZa6j4plru7B7kCJ7bKQgHxgQrket3-Dnk268sIdA7Qb8$ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +namespace chip { +namespace scenes { + +using SceneTableEntry = SceneTableImpl::SceneTableEntry; +using SceneStorageId = SceneTableImpl::SceneStorageId; +using SceneData = SceneTableImpl::SceneData; + +struct FabricList : public CommonPersistentData::FabricList +{ + CHIP_ERROR UpdateKey(StorageKeyName & key) override + { + key = DefaultStorageKeyAllocator::SceneFabricList(); + return CHIP_NO_ERROR; + } +}; + +static constexpr size_t kPersistentBufferMax = 128; + +/** + * @brief Linked list of all scenes in a fabric, stored in persistent memory + * + * FabricSceneData is an access to a linked list of scenes + */ +struct FabricSceneData : public PersistentData +{ + // static constexpr TLV::Tag TagTableID() { return TLV::ContextTag(1); } + static constexpr TLV::Tag TagSceneCount() { return TLV::ContextTag(1); } + static constexpr TLV::Tag TagNext() { return TLV::ContextTag(2); } + + chip::FabricIndex fabric_index = kUndefinedFabricIndex; + SceneTableImpl::SceneStorageId first_scene; + uint16_t scene_count = 0; + FabricIndex next = kUndefinedFabricIndex; + + FabricSceneData() = default; + FabricSceneData(FabricIndex fabric) : fabric_index(fabric) {} + + CHIP_ERROR UpdateKey(StorageKeyName & key) override + { + VerifyOrReturnError(kUndefinedFabricIndex != fabric_index, CHIP_ERROR_INVALID_FABRIC_INDEX); + key = DefaultStorageKeyAllocator::FabricScenesKey(fabric_index); + return CHIP_NO_ERROR; + } + + void Clear() override + { + first_scene.clear(); + scene_count = 0; + next = kUndefinedFabricIndex; + } + + CHIP_ERROR Serialize(TLV::TLVWriter & writer) const override + { + TLV::TLVType container; + ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, container)); + + ReturnErrorOnFailure(first_scene.Serialize(writer)); + ReturnErrorOnFailure(writer.Put(TagSceneCount(), static_cast(scene_count))); + ReturnErrorOnFailure(writer.Put(TagNext(), static_cast(next))); + + return writer.EndContainer(container); + } + + CHIP_ERROR Deserialize(TLV::TLVReader & reader) override + { + ReturnErrorOnFailure(reader.Next(TLV::AnonymousTag())); + VerifyOrReturnError(TLV::kTLVType_Structure == reader.GetType(), CHIP_ERROR_INTERNAL); + + TLV::TLVType container; + ReturnErrorOnFailure(reader.EnterContainer(container)); + + ReturnErrorOnFailure(first_scene.Deserialize(reader)); + ReturnErrorOnFailure(reader.Next(TagSceneCount())); + ReturnErrorOnFailure(reader.Get(scene_count)); + ReturnErrorOnFailure(reader.Next(TagNext())); + ReturnErrorOnFailure(reader.Get(next)); + + return reader.ExitContainer(container); + } + + // Register the fabric in the fabrics' linked-list + CHIP_ERROR Register(PersistentStorageDelegate * storage) + { + FabricList fabric_list; + CHIP_ERROR err = fabric_list.Load(storage); + if (CHIP_ERROR_NOT_FOUND == err) + { + // New fabric list + fabric_list.first_entry = fabric_index; + fabric_list.entry_count = 1; + return fabric_list.Save(storage); + } + ReturnErrorOnFailure(err); + + // Existing fabric list, search for existing entry + FabricSceneData fabric(fabric_list.first_entry); + for (size_t i = 0; i < fabric_list.entry_count; i++) + { + err = fabric.Load(storage); + if (CHIP_NO_ERROR != err) + { + break; + } + if (fabric.fabric_index == this->fabric_index) + { + // Fabric already registered + return CHIP_NO_ERROR; + } + fabric.fabric_index = fabric.next; + } + // Add this fabric to the fabric list + this->next = fabric_list.first_entry; + fabric_list.first_entry = this->fabric_index; + fabric_list.entry_count++; + return fabric_list.Save(storage); + } + + // Remove the fabric from the fabrics' linked list + CHIP_ERROR Unregister(PersistentStorageDelegate * storage) const + { + FabricList fabric_list; + CHIP_ERROR err = fabric_list.Load(storage); + VerifyOrReturnError(CHIP_NO_ERROR == err || CHIP_ERROR_NOT_FOUND == err, err); + + // Existing fabric list, search for existing entry + FabricSceneData fabric(fabric_list.first_entry); + FabricSceneData prev; + + for (size_t i = 0; i < fabric_list.entry_count; i++) + { + err = fabric.Load(storage); + if (CHIP_NO_ERROR != err) + { + break; + } + if (fabric.fabric_index == this->fabric_index) + { + // Fabric found + if (i == 0) + { + // Remove first fabric + fabric_list.first_entry = this->next; + } + else + { + // Remove intermediate fabric + prev.next = this->next; + ReturnErrorOnFailure(prev.Save(storage)); + } + VerifyOrReturnError(fabric_list.entry_count > 0, CHIP_ERROR_INTERNAL); + fabric_list.entry_count--; + return fabric_list.Save(storage); + } + prev = fabric; + fabric.fabric_index = fabric.next; + } + // Fabric not in the list + return CHIP_ERROR_NOT_FOUND; + } + + // Check the fabric is registered in the fabrics' linked list + CHIP_ERROR Validate(PersistentStorageDelegate * storage) const + { + FabricList fabric_list; + ReturnErrorOnFailure(fabric_list.Load(storage)); + + // Existing fabric list, search for existing entry + FabricSceneData fabric(fabric_list.first_entry); + + for (size_t i = 0; i < fabric_list.entry_count; i++) + { + ReturnErrorOnFailure(fabric.Load(storage)); + if (fabric.fabric_index == this->fabric_index) + { + return CHIP_NO_ERROR; + } + fabric.fabric_index = fabric.next; + } + // Fabric not in the list + return CHIP_ERROR_NOT_FOUND; + } + + CHIP_ERROR Save(PersistentStorageDelegate * storage) override + { + ReturnErrorOnFailure(Register(storage)); + return PersistentData::Save(storage); + } + + CHIP_ERROR Delete(PersistentStorageDelegate * storage) override + { + ReturnErrorOnFailure(Unregister(storage)); + return PersistentData::Delete(storage); + } +}; + +struct SceneTableData : public SceneTableEntry, PersistentData +{ + FabricIndex fabric_index = kUndefinedFabricIndex; + uint8_t index = 0; + SceneStorageId next; + SceneStorageId prev; + bool first = true; + + SceneTableData() : SceneTableEntry() {} + SceneTableData(FabricIndex fabric) : fabric_index(fabric) {} + SceneTableData(FabricIndex fabric, SceneStorageId storageId) : SceneTableEntry(storageId), fabric_index(fabric) {} + SceneTableData(FabricIndex fabric, SceneStorageId storageId, SceneData data) : + SceneTableEntry(storageId, data), fabric_index(fabric) + {} + + CHIP_ERROR UpdateKey(StorageKeyName & key) override + { + VerifyOrReturnError(kUndefinedFabricIndex != fabric_index, CHIP_ERROR_INVALID_FABRIC_INDEX); + key = DefaultStorageKeyAllocator::FabricSceneKey(fabric_index, storageId.sceneEndpointId, storageId.sceneGroupId, + storageId.sceneId); + return CHIP_NO_ERROR; + } + + void Clear() override + { + storageData.clear(); + next.clear(); + } + + CHIP_ERROR Serialize(TLV::TLVWriter & writer) const override + { + TLV::TLVType container; + ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, container)); + + ReturnErrorOnFailure(storageId.Serialize(writer)); + ReturnErrorOnFailure(storageData.Serialize(writer)); + ReturnErrorOnFailure(next.Serialize(writer)); + + return writer.EndContainer(container); + } + + CHIP_ERROR Deserialize(TLV::TLVReader & reader) override + { + ReturnErrorOnFailure(reader.Next(TLV::AnonymousTag())); + VerifyOrReturnError(TLV::kTLVType_Structure == reader.GetType(), CHIP_ERROR_INTERNAL); + + TLV::TLVType container; + ReturnErrorOnFailure(reader.EnterContainer(container)); + + ReturnErrorOnFailure(storageId.Deserialize(reader)); + ReturnErrorOnFailure(storageData.Deserialize(reader)); + ReturnErrorOnFailure(next.Deserialize(reader)); + + return reader.ExitContainer(container); + } + + bool Find(PersistentStorageDelegate * storage, const FabricSceneData & fabric, SceneTableImpl::SceneStorageId target_scene) + { + fabric_index = fabric.fabric_index; + storageId = fabric.first_scene; + index = 0; + first = true; + + while (index < fabric.scene_count) + { + if (CHIP_NO_ERROR != Load(storage)) + { + break; + } + if (storageId == target_scene) + { + // Target index found + return true; + } + first = false; + prev = storageId; + storageId = next; + index++; + } + return false; + } +}; + +void SceneTableImpl::SetStorageDelegate(PersistentStorageDelegate * storage) +{ + VerifyOrDie(storage != nullptr); + mStorage = storage; +} + +CHIP_ERROR SceneTableImpl::Init() +{ + if (mStorage == nullptr) + { + return CHIP_ERROR_INCORRECT_STATE; + } + return CHIP_NO_ERROR; +} + +void SceneTableImpl::Finish() +{ + mSceneEntryIterators.ReleaseAll(); +} + +CHIP_ERROR SceneTableImpl::SetSceneTableEntry(FabricIndex fabric_index, const SceneTableEntry & entry) +{ + VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INTERNAL); + + FabricSceneData fabric(fabric_index); + SceneTableData scene; + + // Load fabric data (defaults to zero) + CHIP_ERROR err = fabric.Load(mStorage); + VerifyOrReturnError(CHIP_NO_ERROR == err || CHIP_ERROR_NOT_FOUND == err, err); + + if (scene.Find(mStorage, fabric, entry.storageId)) + { + // Existing scene + scene.storageData = entry.storageData; + return scene.Save(mStorage); + } + + scene.storageId = entry.storageId; + scene.storageData = entry.storageData; + + VerifyOrReturnError(fabric.scene_count < mMaxScenePerFabric, CHIP_ERROR_INVALID_LIST_LENGTH); + fabric.scene_count++; + + if (scene.first) + { + fabric.first_scene = scene.storageId; + } + else + { + // Update previous scene's next + SceneTableData prev(fabric_index, scene.prev); + ReturnErrorOnFailure(prev.Load(mStorage)); + prev.next = scene.storageId; + ReturnErrorOnFailure(prev.Save(mStorage)); + } + + ReturnErrorOnFailure(fabric.Save(mStorage)); + + return scene.Save(mStorage); +} +CHIP_ERROR SceneTableImpl::GetSceneTableEntry(FabricIndex fabric_index, SceneStorageId scene_id, SceneTableEntry & entry) +{ + FabricSceneData fabric(fabric_index); + SceneTableData scene; + + ReturnErrorOnFailure(fabric.Load(mStorage)); + VerifyOrReturnError(scene.Find(mStorage, fabric, scene_id), CHIP_ERROR_NOT_FOUND); + + entry.storageId = scene.storageId; + entry.storageData = scene.storageData; + return CHIP_NO_ERROR; +} +CHIP_ERROR SceneTableImpl::RemoveSceneTableEntry(FabricIndex fabric_index, SceneStorageId scene_id) +{ + FabricSceneData fabric(fabric_index); + SceneTableData scene; + + ReturnErrorOnFailure(fabric.Load(mStorage)); + VerifyOrReturnError(scene.Find(mStorage, fabric, scene_id), CHIP_ERROR_NOT_FOUND); + + ReturnErrorOnFailure(scene.Delete(mStorage)); + if (scene.first) + { + // Remove first scene + fabric.first_scene = scene.next; + } + else + { + // Update previous scene's next + SceneTableData prev(fabric_index, scene.prev); + ReturnErrorOnFailure(prev.Load(mStorage)); + prev.next = scene.next; + ReturnErrorOnFailure(prev.Save(mStorage)); + } + if (fabric.scene_count > 0) + { + fabric.scene_count--; + } + + // Update fabric info + ReturnErrorOnFailure(fabric.Save(mStorage)); + + return CHIP_NO_ERROR; +} + +SceneTableImpl::SceneEntryIterator * SceneTableImpl::IterateSceneEntry(FabricIndex fabric_index) +{ + VerifyOrReturnError(IsInitialized(), nullptr); + return mSceneEntryIterators.CreateObject(*this, fabric_index); +} + +SceneTableImpl::SceneEntryIteratorImpl::SceneEntryIteratorImpl(SceneTableImpl & provider, FabricIndex fabric_index) : + mProvider(provider), mFabric(fabric_index) +{ + FabricSceneData fabric(fabric_index); + if (CHIP_NO_ERROR == fabric.Load(provider.mStorage)) + { + + mNextSceneId = fabric.first_scene; + mTotalScene = fabric.scene_count; + mSceneCount = 0; + } +} + +size_t SceneTableImpl::SceneEntryIteratorImpl::Count() +{ + return mTotalScene; +} + +bool SceneTableImpl::SceneEntryIteratorImpl::Next(SceneTableEntry & output) +{ + VerifyOrReturnError(mSceneCount < mTotalScene, false); + + SceneTableData scene(mFabric, mNextSceneId); + VerifyOrReturnError(CHIP_NO_ERROR == scene.Load(mProvider.mStorage), false); + + mSceneCount++; + mNextSceneId = scene.next; + output.storageId = scene.storageId; + output.storageData = scene.storageData; + return true; +} + +void SceneTableImpl::SceneEntryIteratorImpl::Release() +{ + mProvider.mSceneEntryIterators.ReleaseObject(this); +} + +namespace { + +SceneTableImpl * gSceneTable = nullptr; + +} // namespace + +SceneTableImpl * GetSceneTable() +{ + return gSceneTable; +} + +void SetSceneTable(SceneTableImpl * provider) +{ + gSceneTable = provider; +} + +} // namespace scenes +} // namespace chip \ No newline at end of file diff --git a/src/app/clusters/scenes/SceneTableImpl.h b/src/app/clusters/scenes/SceneTableImpl.h new file mode 100644 index 00000000000000..760caa0e562a66 --- /dev/null +++ b/src/app/clusters/scenes/SceneTableImpl.h @@ -0,0 +1,109 @@ +/** + * + * 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 + * + * https://urldefense.com/v3/__http://www.apache.org/licenses/LICENSE-2.0__;!!N30Cs7Jr!UgbMbEQ59BIK-1Xslc7QXYm0lQBh92qA3ElecRe1CF_9YhXxbwPOZa6j4plru7B7kCJ7bKQgHxgQrket3-Dnk268sIdA7Qb8$ + * + * 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 + +namespace chip { +namespace scenes { + +/** + * @brief Implementation of a storage in permanent memory of the scene table. + * + * SceneTableImpl is an implementation that allows to store scenes in a permenent manner. + * It handles the storage of scenes by their ID, GroupID and EnpointID over multiple fabrics. + * It is meant to be used exclusively when the scene cluster is enable for at least one endpoint + * on the device. + */ +class SceneTableImpl : public SceneTable +{ +public: + SceneTableImpl() = default; + + ~SceneTableImpl() override {} + /** + * @brief Set the storage implementation used for non-volatile storage of configuration data. + * This method MUST be called before Init(). + * + * @param storage Pointer to storage instance to set. Cannot be nullptr, will assert. + */ + void SetStorageDelegate(PersistentStorageDelegate * storage); + + CHIP_ERROR Init(); + void Finish(); + + // + // Scene Data + // + + // By id + CHIP_ERROR SetSceneTableEntry(FabricIndex fabric_index, const SceneTableEntry & entry); + CHIP_ERROR GetSceneTableEntry(FabricIndex fabric_index, SceneTableImpl::SceneStorageId scene_id, SceneTableEntry & entry); + CHIP_ERROR RemoveSceneTableEntry(FabricIndex fabric_index, SceneTableImpl::SceneStorageId scene_id); + + // Iterators + SceneEntryIterator * IterateSceneEntry(FabricIndex fabric_index) override; + +protected: + class SceneEntryIteratorImpl : public SceneEntryIterator + { + public: + SceneEntryIteratorImpl(SceneTableImpl & provider, FabricIndex fabric_index); + size_t Count() override; + bool Next(SceneTableEntry & output) override; + void Release() override; + + protected: + SceneTableImpl & mProvider; + FabricIndex mFabric = kUndefinedFabricIndex; + SceneStorageId mNextSceneId; + size_t mSceneCount = 0; + size_t mTotalScene = 0; + }; + bool IsInitialized() { return (mStorage != nullptr); } + + chip::PersistentStorageDelegate * mStorage = nullptr; + ObjectPool mSceneEntryIterators; + + const uint8_t mMaxScenePerFabric = kMaxScenePerFabric; +}; // class SceneTableImpl + +/** + * Instance getter for the global SceneTable. + * + * Callers have to externally synchronize usage of this function. + * + * @return The global Scene Table + */ +SceneTableImpl * GetSceneTable(); + +/** + * Instance setter for the global Scene Table. + * + * Callers have to externally synchronize usage of this function. + * + * The `provider` can be set to nullptr if the owner is done with it fully. + * + * @param[in] provider pointer to the Scene Table global isntance to use + */ +void SetSceneTable(SceneTableImpl * provider); +} // namespace scenes +} // namespace chip diff --git a/src/lib/core/DataModelTypes.h b/src/lib/core/DataModelTypes.h index fb79584c5c6988..308b679057778e 100644 --- a/src/lib/core/DataModelTypes.h +++ b/src/lib/core/DataModelTypes.h @@ -23,6 +23,7 @@ #include #include #include +#include namespace chip { diff --git a/src/lib/core/SceneId.h b/src/lib/core/SceneId.h new file mode 100644 index 00000000000000..83645e3671069f --- /dev/null +++ b/src/lib/core/SceneId.h @@ -0,0 +1,66 @@ +/* + * + * 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 + +/** + * @brief Indicates the absence of a Scene table entry. + */ +#define SCENE_TABLE_NULL_INDEX 0xFF +/** + * @brief Value used when setting or getting the endpoint in a Scene table + * entry. It indicates that the entry is not in use. + */ +#define SCENE_TABLE_UNUSED_ENDPOINT_ID 0x00 +/** + * @brief Maximum length of Scene names, not including the length byte. + */ +#define ZCL_SCENES_CLUSTER_MAXIMUM_NAME_LENGTH 16 +/** + * @brief The group identifier for the global scene. + */ +#define ZCL_SCENES_GLOBAL_SCENE_GROUP_ID 0x0000 +/** + * @brief The scene identifier for the global scene. + */ +#define ZCL_SCENES_GLOBAL_SCENE_SCENE_ID 0x00 + +/** + * @brief The maximum number of scenes according to spec + */ +#define SCENE_MAX_NUMBER 16 + +/** + * @brief The maximum number of scenes allowed on a single fabric + */ +#define SCENE_MAX_PER_FABRIC SCENE_MAX_NUMBER / 2 + +namespace chip { + +typedef GroupId SceneGroupID; +typedef uint8_t SceneId; +typedef uint16_t SceneTransitionTime; +typedef uint8_t TransitionTime100ms; + +constexpr SceneGroupID kGlobalGroupSceneId = ZCL_SCENES_GLOBAL_SCENE_GROUP_ID; +constexpr SceneId kUndefinedSceneId = SCENE_TABLE_NULL_INDEX; +constexpr SceneId kUnusedEndpointId = SCENE_TABLE_UNUSED_ENDPOINT_ID; + +} // namespace chip \ No newline at end of file diff --git a/src/lib/support/DefaultStorageKeyAllocator.h b/src/lib/support/DefaultStorageKeyAllocator.h index c5a612db7fe8ea..a534632a5be064 100644 --- a/src/lib/support/DefaultStorageKeyAllocator.h +++ b/src/lib/support/DefaultStorageKeyAllocator.h @@ -33,7 +33,7 @@ namespace chip { class StorageKeyName { public: - StorageKeyName(const StorageKeyName & other) = default; + StorageKeyName(const StorageKeyName & other) = default; StorageKeyName & operator=(const StorageKeyName & other) = default; ~StorageKeyName() { memset(mKeyNameBuffer, 0, sizeof(mKeyNameBuffer)); } @@ -191,12 +191,23 @@ class DefaultStorageKeyAllocator // Event number counter. static StorageKeyName IMEventNumber() { return StorageKeyName::FromConst("g/im/ec"); } +<<<<<<< HEAD // Subscription resumption static StorageKeyName SubscriptionResumption(size_t index) { return StorageKeyName::Formatted("g/su/%x", static_cast(index)); } static StorageKeyName SubscriptionResumptionMaxCount() { return StorageKeyName::Formatted("g/sum"); } +======= + // Scene Storage + static StorageKeyName SceneFabricList() { return StorageKeyName::FromConst("g/sfl"); } + static StorageKeyName FabricScenesKey(chip::FabricIndex fabric) { return StorageKeyName::Formatted("f/%x/s", fabric); } + static StorageKeyName FabricSceneKey(chip::FabricIndex fabric, chip::EndpointId endpoint, chip::GroupId group, + chip::SceneId scene) + { + return StorageKeyName::Formatted("f/%x/s/e%xg%xs%x", fabric, endpoint, group, scene); + } +>>>>>>> 47da6c11b4 (Implementation of persistant storage of scene table along with test.) }; } // namespace chip diff --git a/src/lib/support/TestSceneTable.h b/src/lib/support/TestSceneTable.h new file mode 100644 index 00000000000000..0a38834dc35c28 --- /dev/null +++ b/src/lib/support/TestSceneTable.h @@ -0,0 +1,140 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://urldefense.com/v3/__http://www.apache.org/licenses/LICENSE-2.0__;!!N30Cs7Jr!UgbMbEQ59BIK-1Xslc7QXYm0lQBh92qA3ElecRe1CF_9YhXxbwPOZa6j4plru7B7kCJ7bKQgHxgQrket3-Dnk268sIdA7Qb8$ + * + * 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 + +namespace chip { + +namespace SceneTesting { + +using FabricIndex = chip::FabricIndex; +using SceneTableEntry = chip::scenes::SceneTableImpl::SceneTableEntry; +using SceneTableImpl = chip::scenes::SceneTableImpl; +using SceneStorageId = chip::scenes::SceneTableImpl::SceneStorageId; +using SceneData = chip::scenes::SceneTableImpl::SceneData; + +CHIP_ERROR scene_store_test(SceneTableImpl * provider, FabricIndex fabric_index, SceneTableEntry & entry) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + SceneTableEntry temp; + + LogErrorOnFailure(provider->SetSceneTableEntry(fabric_index, entry)); + LogErrorOnFailure(provider->GetSceneTableEntry(fabric_index, entry.storageId, temp)); + VerifyOrReturnError(temp.storageId == entry.storageId, CHIP_ERROR_WRITE_FAILED); + VerifyOrReturnError(temp.storageData == entry.storageData, CHIP_ERROR_WRITE_FAILED); + + return err; +} + +CHIP_ERROR scene_iterator_test(SceneTableImpl * provider, FabricIndex fabric_index, const SceneTableEntry & entry1, + const SceneTableEntry & entry2, const SceneTableEntry & entry3) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + SceneTableEntry temp; + + auto * iterator = provider->IterateSceneEntry(fabric_index); + if (iterator) + { + VerifyOrReturnError(iterator->Count() == 3, CHIP_ERROR_INVALID_ARGUMENT); + + VerifyOrReturnError(iterator->Next(temp), CHIP_ERROR_INVALID_ACCESS_TOKEN); + VerifyOrReturnError(temp.storageId == entry1.storageId, CHIP_ERROR_INVALID_ARGUMENT); + + VerifyOrReturnError(iterator->Next(temp), CHIP_ERROR_INVALID_ACCESS_TOKEN); + VerifyOrReturnError(temp.storageId == entry2.storageId, CHIP_ERROR_INVALID_ARGUMENT); + + VerifyOrReturnError(iterator->Next(temp), CHIP_ERROR_INVALID_ACCESS_TOKEN); + VerifyOrReturnError(temp.storageId == entry3.storageId, CHIP_ERROR_INVALID_ARGUMENT); + + // Iterator should return false here + VerifyOrReturnError(iterator->Next(temp) == false, CHIP_ERROR_INVALID_ACCESS_TOKEN); + + iterator->Release(); + } + + return err; +} + +CHIP_ERROR scene_remove_test(SceneTableImpl * provider, FabricIndex fabric_index, SceneTableEntry & entry1, + SceneTableEntry & entry2, SceneTableEntry & entry3) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + SceneTableEntry temp; + + LogErrorOnFailure(provider->RemoveSceneTableEntry(fabric_index, entry2.storageId)); + + auto * iterator = provider->IterateSceneEntry(fabric_index); + VerifyOrReturnError(iterator->Count() == 2, CHIP_ERROR_INVALID_ARGUMENT); + iterator->Next(temp); + VerifyOrReturnError(temp.storageId == entry1.storageId, CHIP_ERROR_INVALID_ARGUMENT); + iterator->Release(); + + LogErrorOnFailure(provider->RemoveSceneTableEntry(fabric_index, entry1.storageId)); + iterator = provider->IterateSceneEntry(fabric_index); + VerifyOrReturnError(iterator->Count() == 1, CHIP_ERROR_INVALID_ARGUMENT); + iterator->Next(temp); + VerifyOrReturnError(temp.storageId == entry3.storageId, CHIP_ERROR_INVALID_ARGUMENT); + + LogErrorOnFailure(provider->RemoveSceneTableEntry(fabric_index, entry3.storageId)); + iterator = provider->IterateSceneEntry(fabric_index); + VerifyOrReturnError(iterator->Count() == 0, CHIP_ERROR_INVALID_ARGUMENT); + + // Iterator should return false here + VerifyOrReturnError(iterator->Next(temp) == false, CHIP_ERROR_INVALID_ACCESS_TOKEN); + + return err; +} + +CHIP_ERROR TestSceneData(SceneTableImpl * provider, FabricIndex fabric_index) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + // Scene storage ID + static const SceneStorageId sceneId1(1, 0xAA, 0x101); + static const SceneStorageId sceneId2(1, 0xBB, 0x00); + static const SceneStorageId sceneId3(1, 0xCC, 0x102); + + // Scene data + static const SceneData sceneData1("Scene #1"); + static const SceneData sceneData2("Scene #2", 2, 5); + static const SceneData sceneData3("Scene #3", 25); + // Scenes + SceneTableEntry scene1(sceneId1, sceneData1); + SceneTableEntry scene2(sceneId2, sceneData2); + SceneTableEntry scene3(sceneId3, sceneData3); + + // Tests + err = scene_store_test(provider, fabric_index, scene1); + LogErrorOnFailure(err); + err = scene_store_test(provider, fabric_index, scene2); + LogErrorOnFailure(err); + err = scene_store_test(provider, fabric_index, scene3); + LogErrorOnFailure(err); + + err = scene_iterator_test(provider, fabric_index, scene1, scene2, scene3); + LogErrorOnFailure(err); + err = scene_remove_test(provider, fabric_index, scene1, scene2, scene3); + LogErrorOnFailure(err); + + return err; +} + +} // namespace SceneTesting + +} // namespace chip