diff --git a/examples/all-clusters-app/all-clusters-common/src/binding-handler.cpp b/examples/all-clusters-app/all-clusters-common/src/binding-handler.cpp index 0a845c4e1e3d1d..5891fd4498e8ab 100644 --- a/examples/all-clusters-app/all-clusters-common/src/binding-handler.cpp +++ b/examples/all-clusters-app/all-clusters-common/src/binding-handler.cpp @@ -105,15 +105,20 @@ static void BoundDeviceChangedHandler(const EmberBindingTableEntry & binding, ch static void InitBindingHandlerInternal(intptr_t arg) { - chip::BindingManager::GetInstance().SetAppServer(&chip::Server::GetInstance()); + auto & server = chip::Server::GetInstance(); + chip::BindingManager::GetInstance().Init( + { &server.GetFabricTable(), server.GetCASESessionManager(), &server.GetPersistentStorage() }); chip::BindingManager::GetInstance().RegisterBoundDeviceChangedHandler(BoundDeviceChangedHandler); -#if defined(ENABLE_CHIP_SHELL) - RegisterSwitchCommands(); -#endif } CHIP_ERROR InitBindingHandlers() { + // The initialization of binding manager will try establishing connection with unicast peers + // so it requires the Server instance to be correctly initialized. Post the init function to + // the event queue so that everything is ready when initialization is conducted. chip::DeviceLayer::PlatformMgr().ScheduleWork(InitBindingHandlerInternal); +#if defined(ENABLE_CHIP_SHELL) + RegisterSwitchCommands(); +#endif return CHIP_NO_ERROR; } diff --git a/examples/light-switch-app/efr32/src/binding-handler.cpp b/examples/light-switch-app/efr32/src/binding-handler.cpp index 909ad0ab9a2fee..a6d2036def94a5 100644 --- a/examples/light-switch-app/efr32/src/binding-handler.cpp +++ b/examples/light-switch-app/efr32/src/binding-handler.cpp @@ -136,11 +136,10 @@ static void RegisterSwitchCommands() void InitBindingHandlerInternal(intptr_t arg) { - chip::BindingManager::GetInstance().SetAppServer(&chip::Server::GetInstance()); + auto & server = chip::Server::GetInstance(); + chip::BindingManager::GetInstance().Init( + { &server.GetFabricTable(), server.GetCASESessionManager(), &server.GetPersistentStorage() }); chip::BindingManager::GetInstance().RegisterBoundDeviceChangedHandler(BoundDeviceChangedHandler); -#if defined(ENABLE_CHIP_SHELL) - RegisterSwitchCommands(); -#endif } } // namespace @@ -180,6 +179,12 @@ void SwitchOnOffOff(intptr_t context) CHIP_ERROR InitBindingHandler() { + // The initialization of binding manager will try establishing connection with unicast peers + // so it requires the Server instance to be correctly initialized. Post the init function to + // the event queue so that everything is ready when initialization is conducted. chip::DeviceLayer::PlatformMgr().ScheduleWork(InitBindingHandlerInternal); +#if defined(ENABLE_CHIP_SHELL) + RegisterSwitchCommands(); +#endif return CHIP_NO_ERROR; } diff --git a/src/app/chip_data_model.gni b/src/app/chip_data_model.gni index 26a678b6d8b716..a8cff820c5717a 100644 --- a/src/app/chip_data_model.gni +++ b/src/app/chip_data_model.gni @@ -141,7 +141,6 @@ template("chip_data_model") { } else if (cluster == "bindings") { sources += [ "${_app_root}/clusters/${cluster}/${cluster}.cpp", - "${_app_root}/clusters/${cluster}/${cluster}.h", "${_app_root}/clusters/${cluster}/BindingManager.cpp", "${_app_root}/clusters/${cluster}/BindingManager.h", "${_app_root}/clusters/${cluster}/PendingNotificationMap.cpp", diff --git a/src/app/clusters/bindings/BindingManager.cpp b/src/app/clusters/bindings/BindingManager.cpp index afb6ed1e4269b9..c0fa2246b4211e 100644 --- a/src/app/clusters/bindings/BindingManager.cpp +++ b/src/app/clusters/bindings/BindingManager.cpp @@ -16,7 +16,6 @@ */ #include -#include #include #include #include @@ -33,7 +32,7 @@ class BindingFabricTableDelegate : public chip::FabricTableDelegate { if (iter->fabricIndex == fabricIndex) { - iter = bindingTable.RemoveAt(iter); + bindingTable.RemoveAt(iter); } else { @@ -41,26 +40,6 @@ class BindingFabricTableDelegate : public chip::FabricTableDelegate } } chip::BindingManager::GetInstance().FabricRemoved(compressedFabricId, fabricIndex); - - EmberBindingTableEntry * table = static_cast( - chip::Platform::MemoryAlloc(bindingTable.Size() * sizeof(EmberBindingTableEntry))); - uint8_t idx = 0; - if (table == nullptr) - { - return; - } - for (const EmberBindingTableEntry & entry : chip::BindingTable::GetInstance()) - { - table[idx++] = entry; - } - CHIP_ERROR error = chip::Server::GetInstance().GetPersistentStorage().SyncSetKeyValue( - chip::kBindingStoargeKey, table, static_cast(bindingTable.Size() * sizeof(EmberBindingTableEntry))); - // Error recovery is not possible here, the fabricIndex will be filtered upon next reboot. - if (error != CHIP_NO_ERROR) - { - ChipLogError(AppServer, "Failed to save binding table after fabric removed %" CHIP_ERROR_FORMAT, error.Format()); - } - chip::Platform::MemoryFree(table); } // Intentionally left blank @@ -72,35 +51,13 @@ class BindingFabricTableDelegate : public chip::FabricTableDelegate BindingFabricTableDelegate gFabricTableDelegate; -void LoadBindingTableFromStorage(chip::Server * appServer) -{ - EmberBindingTableEntry * table = static_cast( - chip::Platform::MemoryAlloc(EMBER_BINDING_TABLE_SIZE * sizeof(EmberBindingTableEntry))); - uint16_t readSize = EMBER_BINDING_TABLE_SIZE * sizeof(EmberBindingTableEntry); - - if (appServer->GetPersistentStorage().SyncGetKeyValue(chip::kBindingStoargeKey, table, readSize) == CHIP_NO_ERROR && - readSize % sizeof(EmberBindingTableEntry) == 0) - { - size_t numEntries = readSize / sizeof(EmberBindingTableEntry); - for (size_t i = 0; i < numEntries; i++) - { - // In case the binding table storage failed when fabric removed - if (appServer->GetFabricTable().FindFabricWithIndex(table[i].fabricIndex) != nullptr) - { - chip::AddBindingEntry(table[i]); - } - } - } - chip::Platform::MemoryFree(table); -} - } // namespace namespace { -chip::PeerId PeerIdForNode(chip::FabricTable & fabricTable, chip::FabricIndex fabric, chip::NodeId node) +chip::PeerId PeerIdForNode(chip::FabricTable * fabricTable, chip::FabricIndex fabric, chip::NodeId node) { - chip::FabricInfo * fabricInfo = fabricTable.FindFabricWithIndex(fabric); + chip::FabricInfo * fabricInfo = fabricTable->FindFabricWithIndex(fabric); if (fabricInfo == nullptr) { return chip::PeerId(); @@ -125,21 +82,42 @@ CHIP_ERROR BindingManager::UnicastBindingRemoved(uint8_t bindingEntryId) return CHIP_NO_ERROR; } -void BindingManager::SetAppServer(Server * appServer) +CHIP_ERROR BindingManager::Init(const BindingManagerInitParams & params) { - mAppServer = appServer; - mAppServer->GetFabricTable().AddFabricDelegate(&gFabricTableDelegate); - LoadBindingTableFromStorage(appServer); + VerifyOrReturnError(params.mCASESessionManager != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(params.mFabricTable != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(params.mStorage != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + mInitParams = params; + params.mFabricTable->AddFabricDelegate(&gFabricTableDelegate); + BindingTable::GetInstance().SetPersistentStorage(params.mStorage); + CHIP_ERROR error = BindingTable::GetInstance().LoadFromStorage(); + if (error != CHIP_NO_ERROR) + { + // This can happen during first boot of the device. + ChipLogProgress(AppServer, "Cannot load binding table: %" CHIP_ERROR_FORMAT, error.Format()); + } + else + { + for (const EmberBindingTableEntry & entry : BindingTable::GetInstance()) + { + if (entry.type == EMBER_UNICAST_BINDING) + { + // The CASE connection can also fail if the unicast peer is offline. + // There is recovery mechanism to retry connection on-demand so ignore error. + (void) UnicastBindingCreated(entry.fabricIndex, entry.nodeId); + } + } + } + return CHIP_NO_ERROR; } CHIP_ERROR BindingManager::EstablishConnection(FabricIndex fabric, NodeId node) { - VerifyOrReturnError(mAppServer != nullptr, CHIP_ERROR_INCORRECT_STATE); - - PeerId peer = PeerIdForNode(mAppServer->GetFabricTable(), fabric, node); + VerifyOrReturnError(mInitParams.mCASESessionManager != nullptr, CHIP_ERROR_INCORRECT_STATE); + PeerId peer = PeerIdForNode(mInitParams.mFabricTable, fabric, node); VerifyOrReturnError(peer.GetNodeId() != kUndefinedNodeId, CHIP_ERROR_NOT_FOUND); CHIP_ERROR error = - mAppServer->GetCASESessionManager()->FindOrEstablishSession(peer, &mOnConnectedCallback, &mOnConnectionFailureCallback); + mInitParams.mCASESessionManager->FindOrEstablishSession(peer, &mOnConnectedCallback, &mOnConnectionFailureCallback); if (error == CHIP_ERROR_NO_MEMORY) { // Release the least recently used entry @@ -150,11 +128,11 @@ CHIP_ERROR BindingManager::EstablishConnection(FabricIndex fabric, NodeId node) if (mPendingNotificationMap.FindLRUConnectPeer(&fabricToRemove, &nodeToRemove) == CHIP_NO_ERROR) { mPendingNotificationMap.RemoveAllEntriesForNode(fabricToRemove, nodeToRemove); - PeerId lruPeer = PeerIdForNode(mAppServer->GetFabricTable(), fabricToRemove, nodeToRemove); - mAppServer->GetCASESessionManager()->ReleaseSession(lruPeer); + PeerId lruPeer = PeerIdForNode(mInitParams.mFabricTable, fabricToRemove, nodeToRemove); + mInitParams.mCASESessionManager->ReleaseSession(lruPeer); // Now retry - error = mAppServer->GetCASESessionManager()->FindOrEstablishSession(peer, &mOnConnectedCallback, - &mOnConnectionFailureCallback); + error = + mInitParams.mCASESessionManager->FindOrEstablishSession(peer, &mOnConnectedCallback, &mOnConnectionFailureCallback); } } return error; @@ -177,7 +155,7 @@ void BindingManager::HandleDeviceConnected(OperationalDeviceProxy * device) { EmberBindingTableEntry entry = BindingTable::GetInstance().GetAt(pendingNotification.mBindingEntryId); - PeerId peer = PeerIdForNode(mAppServer->GetFabricTable(), entry.fabricIndex, entry.nodeId); + PeerId peer = PeerIdForNode(mInitParams.mFabricTable, entry.fabricIndex, entry.nodeId); if (device->GetPeerId() == peer) { fabricToRemove = entry.fabricIndex; @@ -198,18 +176,18 @@ void BindingManager::HandleDeviceConnectionFailure(PeerId peerId, CHIP_ERROR err { // Simply release the entry, the connection will be re-established as needed. ChipLogError(AppServer, "Failed to establish connection to node 0x" ChipLogFormatX64, ChipLogValueX64(peerId.GetNodeId())); - mAppServer->GetCASESessionManager()->ReleaseSession(peerId); + mInitParams.mCASESessionManager->ReleaseSession(peerId); } void BindingManager::FabricRemoved(CompressedFabricId compressedFabricId, FabricIndex fabricIndex) { mPendingNotificationMap.RemoveAllEntriesForFabric(fabricIndex); - mAppServer->GetCASESessionManager()->ReleaseSessionForFabric(compressedFabricId); + mInitParams.mCASESessionManager->ReleaseSessionForFabric(compressedFabricId); } CHIP_ERROR BindingManager::NotifyBoundClusterChanged(EndpointId endpoint, ClusterId cluster, void * context) { - VerifyOrReturnError(mAppServer != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mInitParams.mFabricTable != nullptr, CHIP_ERROR_INCORRECT_STATE); for (auto iter = BindingTable::GetInstance().begin(); iter != BindingTable::GetInstance().end(); ++iter) { @@ -217,10 +195,10 @@ CHIP_ERROR BindingManager::NotifyBoundClusterChanged(EndpointId endpoint, Cluste { if (iter->type == EMBER_UNICAST_BINDING) { - FabricInfo * fabricInfo = mAppServer->GetFabricTable().FindFabricWithIndex(iter->fabricIndex); + FabricInfo * fabricInfo = mInitParams.mFabricTable->FindFabricWithIndex(iter->fabricIndex); VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_NOT_FOUND); PeerId peer = fabricInfo->GetPeerIdForNode(iter->nodeId); - OperationalDeviceProxy * peerDevice = mAppServer->GetCASESessionManager()->FindExistingSession(peer); + OperationalDeviceProxy * peerDevice = mInitParams.mCASESessionManager->FindExistingSession(peer); if (peerDevice != nullptr && peerDevice->IsConnected() && mBoundDeviceChangedHandler) { // We already have an active connection diff --git a/src/app/clusters/bindings/BindingManager.h b/src/app/clusters/bindings/BindingManager.h index 0fabe14ff2d5f0..88bedac4df357b 100644 --- a/src/app/clusters/bindings/BindingManager.h +++ b/src/app/clusters/bindings/BindingManager.h @@ -21,6 +21,8 @@ #include #include #include +#include +#include namespace chip { @@ -38,6 +40,13 @@ namespace chip { */ using BoundDeviceChangedHandler = void (*)(const EmberBindingTableEntry & binding, DeviceProxy * peer_device, void * context); +struct BindingManagerInitParams +{ + FabricTable * mFabricTable = nullptr; + CASESessionManager * mCASESessionManager = nullptr; + PersistentStorageDelegate * mStorage = nullptr; +}; + /** * * The BindingManager class manages the connections for unicast bindings and notifies the application @@ -61,7 +70,7 @@ class BindingManager void RegisterBoundDeviceChangedHandler(BoundDeviceChangedHandler handler) { mBoundDeviceChangedHandler = handler; } - void SetAppServer(Server * appServer); + CHIP_ERROR Init(const BindingManagerInitParams & params); /* * Notifies the BindingManager that a new unicast binding is created. @@ -108,7 +117,7 @@ class BindingManager PendingNotificationMap mPendingNotificationMap; BoundDeviceChangedHandler mBoundDeviceChangedHandler; - Server * mAppServer = nullptr; + BindingManagerInitParams mInitParams; Callback::Callback mOnConnectedCallback; Callback::Callback mOnConnectionFailureCallback; diff --git a/src/app/clusters/bindings/bindings.cpp b/src/app/clusters/bindings/bindings.cpp index 4ecc014e1d77bf..b3354744b0933c 100644 --- a/src/app/clusters/bindings/bindings.cpp +++ b/src/app/clusters/bindings/bindings.cpp @@ -23,14 +23,11 @@ #include #include #include -#include #include #include #include -#include #include #include -#include #include using namespace chip; @@ -64,20 +61,7 @@ bool IsValidBinding(const TargetStructType & entry) (!entry.endpoint.HasValue() && !entry.node.HasValue() && entry.group.HasValue()); } -EmberBindingTableEntry BindingTableEntryFromTargetStruct(const TargetStructType & entry, EndpointId localEndpoint) -{ - if (entry.group.HasValue()) - { - return EmberBindingTableEntry::ForGroup(entry.fabricIndex, entry.group.Value(), localEndpoint, entry.cluster); - } - else - { - return EmberBindingTableEntry::ForNode(entry.fabricIndex, entry.node.Value(), localEndpoint, entry.endpoint.Value(), - entry.cluster); - } -} - -CHIP_ERROR CheckValidBindingList(const DecodableBindingListType & bindingList, FabricIndex accessingFabricIndex, size_t * newSize) +CHIP_ERROR CheckValidBindingList(const DecodableBindingListType & bindingList, FabricIndex accessingFabricIndex) { size_t listSize = 0; auto iter = bindingList.begin(); @@ -97,11 +81,35 @@ CHIP_ERROR CheckValidBindingList(const DecodableBindingListType & bindingList, F oldListSize++; } } - *newSize = BindingTable::GetInstance().Size() - oldListSize + listSize; - ReturnErrorCodeIf(*newSize > EMBER_BINDING_TABLE_SIZE, CHIP_IM_GLOBAL_STATUS(ResourceExhausted)); + ReturnErrorCodeIf(BindingTable::GetInstance().Size() - oldListSize + listSize > EMBER_BINDING_TABLE_SIZE, + CHIP_IM_GLOBAL_STATUS(ResourceExhausted)); return CHIP_NO_ERROR; } +void AddBindingEntry(const TargetStructType & entry, EndpointId localEndpoint) +{ + EmberBindingTableEntry bindingEntry; + + if (entry.group.HasValue()) + { + bindingEntry = EmberBindingTableEntry::ForGroup(entry.fabricIndex, entry.group.Value(), localEndpoint, entry.cluster); + } + else + { + bindingEntry = EmberBindingTableEntry::ForNode(entry.fabricIndex, entry.node.Value(), localEndpoint, entry.endpoint.Value(), + entry.cluster); + CHIP_ERROR err = BindingManager::GetInstance().UnicastBindingCreated(entry.fabricIndex, entry.node.Value()); + if (err != CHIP_NO_ERROR) + { + // Unicast connection failure can happen if peer is offline. We'll retry connection on-demand. + ChipLogProgress( + Zcl, "Binding: Failed to create session for unicast binding to device " ChipLogFormatX64 ": %" CHIP_ERROR_FORMAT, + ChipLogValueX64(entry.node.Value()), err.Format()); + } + } + BindingTable::GetInstance().Add(bindingEntry); +} + CHIP_ERROR BindingTableAccess::Read(const ConcreteReadAttributePath & path, AttributeValueEncoder & encoder) { switch (path.mAttributeId) @@ -158,75 +166,15 @@ CHIP_ERROR BindingTableAccess::Write(const ConcreteDataAttributePath & path, Att return CHIP_NO_ERROR; } -CHIP_ERROR SaveWithDecodableBindingList(const DecodableBindingListType & bindingList, size_t newTableSize, - FabricIndex accessingFabricIndex, EndpointId localEndpoint) -{ - // The binding table can be at most 480 bytes, allocate on heap to reduce static stack size allocation - // which cannot be reused. - EmberBindingTableEntry * table = - static_cast(Platform::MemoryAlloc(newTableSize * sizeof(EmberBindingTableEntry))); - uint8_t idx = 0; - - if (table == nullptr) - { - return CHIP_ERROR_NO_MEMORY; - } - for (const EmberBindingTableEntry & entry : BindingTable::GetInstance()) - { - if (entry.fabricIndex != accessingFabricIndex) - { - table[idx++] = entry; - } - } - auto iter = bindingList.begin(); - while (iter.Next()) - { - table[idx++] = BindingTableEntryFromTargetStruct(iter.GetValue(), localEndpoint); - } - CHIP_ERROR error = Server::GetInstance().GetPersistentStorage().SyncSetKeyValue( - kBindingStoargeKey, table, static_cast(newTableSize * sizeof(EmberBindingTableEntry))); - - Platform::MemoryFree(table); - return error; -} - -CHIP_ERROR SaveWithNewEntry(const TargetStructType & newEntry, EndpointId localEndpoint) -{ - size_t newTableSize = BindingTable::GetInstance().Size() + 1; - EmberBindingTableEntry * table = - static_cast(Platform::MemoryAlloc(newTableSize * sizeof(EmberBindingTableEntry))); - uint8_t idx = 0; - - if (table == nullptr) - { - return CHIP_ERROR_NO_MEMORY; - } - for (const EmberBindingTableEntry & entry : BindingTable::GetInstance()) - { - table[idx++] = entry; - } - table[idx++] = BindingTableEntryFromTargetStruct(newEntry, localEndpoint); - - CHIP_ERROR error = Server::GetInstance().GetPersistentStorage().SyncSetKeyValue( - kBindingStoargeKey, table, static_cast(newTableSize * sizeof(EmberBindingTableEntry))); - - Platform::MemoryFree(table); - return error; -} - CHIP_ERROR BindingTableAccess::WriteBindingTable(const ConcreteDataAttributePath & path, AttributeValueDecoder & decoder) { FabricIndex accessingFabricIndex = decoder.AccessingFabricIndex(); if (!path.IsListOperation() || path.mListOp == ConcreteDataAttributePath::ListOperation::ReplaceAll) { DecodableBindingListType newBindingList; - size_t newTableSize; ReturnErrorOnFailure(decoder.Decode(newBindingList)); - ReturnErrorOnFailure(CheckValidBindingList(newBindingList, accessingFabricIndex, &newTableSize)); - - // Try saving to storage first, if saving fails we won't update the content. - ReturnErrorOnFailure(SaveWithDecodableBindingList(newBindingList, newTableSize, accessingFabricIndex, path.mEndpointId)); + ReturnErrorOnFailure(CheckValidBindingList(newBindingList, accessingFabricIndex)); // Clear all entries for the current accessing fabric auto bindingTableIter = BindingTable::GetInstance().begin(); @@ -238,7 +186,7 @@ CHIP_ERROR BindingTableAccess::WriteBindingTable(const ConcreteDataAttributePath { BindingManager::GetInstance().UnicastBindingRemoved(bindingTableIter.GetIndex()); } - bindingTableIter = BindingTable::GetInstance().RemoveAt(bindingTableIter); + ReturnErrorOnFailure(BindingTable::GetInstance().RemoveAt(bindingTableIter)); } else { @@ -250,7 +198,7 @@ CHIP_ERROR BindingTableAccess::WriteBindingTable(const ConcreteDataAttributePath auto iter = newBindingList.begin(); while (iter.Next()) { - AddBindingEntry(BindingTableEntryFromTargetStruct(iter.GetValue(), path.mEndpointId)); + AddBindingEntry(iter.GetValue(), path.mEndpointId); } return CHIP_NO_ERROR; } @@ -262,37 +210,13 @@ CHIP_ERROR BindingTableAccess::WriteBindingTable(const ConcreteDataAttributePath { return CHIP_IM_GLOBAL_STATUS(ConstraintError); } - if (BindingTable::GetInstance().Size() == EMBER_BINDING_TABLE_SIZE) - { - return CHIP_IM_GLOBAL_STATUS(ResourceExhausted); - } - ReturnErrorOnFailure(SaveWithNewEntry(target, path.mEndpointId)); - AddBindingEntry(BindingTableEntryFromTargetStruct(target, path.mEndpointId)); + AddBindingEntry(target, path.mEndpointId); return CHIP_NO_ERROR; } return CHIP_IM_GLOBAL_STATUS(UnsupportedWrite); } } // namespace -namespace chip { - -void AddBindingEntry(const EmberBindingTableEntry & entry) -{ - if (entry.type == EMBER_UNICAST_BINDING) - { - CHIP_ERROR err = BindingManager::GetInstance().UnicastBindingCreated(entry.fabricIndex, entry.nodeId); - if (err != CHIP_NO_ERROR) - { - ChipLogProgress( - Zcl, "Binding: Failed to create session for unicast binding to device " ChipLogFormatX64 ": %" CHIP_ERROR_FORMAT, - ChipLogValueX64(entry.nodeId), err.Format()); - } - } - BindingTable::GetInstance().Add(entry); -} - -} // namespace chip - void MatterBindingPluginServerInitCallback() { registerAttributeAccessOverride(&gAttrAccess); diff --git a/src/app/clusters/bindings/bindings.h b/src/app/clusters/bindings/bindings.h deleted file mode 100644 index 303814b987e114..00000000000000 --- a/src/app/clusters/bindings/bindings.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * - * 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. - */ - -#include - -namespace chip { - -constexpr char kBindingStoargeKey[] = "binding"; - -void AddBindingEntry(const EmberBindingTableEntry & entry); - -} // namespace chip diff --git a/src/app/tests/TestBindingTable.cpp b/src/app/tests/TestBindingTable.cpp index 5a72d4e1213a97..389d5820dc92d1 100644 --- a/src/app/tests/TestBindingTable.cpp +++ b/src/app/tests/TestBindingTable.cpp @@ -16,6 +16,8 @@ */ #include +#include +#include #include #include @@ -27,6 +29,8 @@ namespace { void TestEmptyBindingTable(nlTestSuite * aSuite, void * aContext) { BindingTable table; + chip::TestPersistentStorageDelegate testStorage; + table.SetPersistentStorage(&testStorage); NL_TEST_ASSERT(aSuite, table.Size() == 0); NL_TEST_ASSERT(aSuite, table.begin() == table.end()); } @@ -34,7 +38,8 @@ void TestEmptyBindingTable(nlTestSuite * aSuite, void * aContext) void TestAdd(nlTestSuite * aSuite, void * aContext) { BindingTable table; - + chip::TestPersistentStorageDelegate testStorage; + table.SetPersistentStorage(&testStorage); EmberBindingTableEntry unusedEntry; unusedEntry.type = EMBER_UNUSED_BINDING; NL_TEST_ASSERT(aSuite, table.Add(unusedEntry) == CHIP_ERROR_INVALID_ARGUMENT); @@ -59,9 +64,12 @@ void TestAdd(nlTestSuite * aSuite, void * aContext) void TestRemoveThenAdd(nlTestSuite * aSuite, void * aContext) { BindingTable table; + chip::TestPersistentStorageDelegate testStorage; + table.SetPersistentStorage(&testStorage); NL_TEST_ASSERT(aSuite, table.Add(EmberBindingTableEntry::ForNode(0, 0, 0, 0, NullOptional)) == CHIP_NO_ERROR); auto iter = table.begin(); - NL_TEST_ASSERT(aSuite, table.RemoveAt(iter) == table.end()); + NL_TEST_ASSERT(aSuite, table.RemoveAt(iter) == CHIP_NO_ERROR); + NL_TEST_ASSERT(aSuite, iter == table.end()); NL_TEST_ASSERT(aSuite, table.Size() == 0); NL_TEST_ASSERT(aSuite, table.begin() == table.end()); for (uint8_t i = 0; i < EMBER_BINDING_TABLE_SIZE; i++) @@ -70,7 +78,7 @@ void TestRemoveThenAdd(nlTestSuite * aSuite, void * aContext) } iter = table.begin(); ++iter; - iter = table.RemoveAt(iter); + NL_TEST_ASSERT(aSuite, table.RemoveAt(iter) == CHIP_NO_ERROR); NL_TEST_ASSERT(aSuite, table.Size() == EMBER_BINDING_TABLE_SIZE - 1); NL_TEST_ASSERT(aSuite, iter->nodeId == 2); NL_TEST_ASSERT(aSuite, iter.GetIndex() == 2); @@ -90,7 +98,7 @@ void TestRemoveThenAdd(nlTestSuite * aSuite, void * aContext) ++iter; NL_TEST_ASSERT(aSuite, iter == table.end()); iter = table.begin(); - iter = table.RemoveAt(iter); + NL_TEST_ASSERT(aSuite, table.RemoveAt(iter) == CHIP_NO_ERROR); NL_TEST_ASSERT(aSuite, table.Size() == EMBER_BINDING_TABLE_SIZE - 1); NL_TEST_ASSERT(aSuite, iter == table.begin()); NL_TEST_ASSERT(aSuite, iter.GetIndex() == 2); @@ -98,6 +106,85 @@ void TestRemoveThenAdd(nlTestSuite * aSuite, void * aContext) NL_TEST_ASSERT(aSuite, table.GetAt(0).type == EMBER_UNUSED_BINDING); } +void VerifyTableSame(nlTestSuite * aSuite, BindingTable & table, const std::vector & expected) +{ + NL_TEST_ASSERT(aSuite, table.Size() == expected.size()); + auto iter1 = table.begin(); + auto iter2 = expected.begin(); + while (iter2 != expected.end()) + { + NL_TEST_ASSERT(aSuite, iter1 != table.end()); + NL_TEST_ASSERT(aSuite, *iter1 == *iter2); + ++iter1; + ++iter2; + } + NL_TEST_ASSERT(aSuite, iter1 == table.end()); +} + +void VerifyRestored(nlTestSuite * aSuite, chip::TestPersistentStorageDelegate & storage, + const std::vector & expected) +{ + BindingTable restoredTable; + restoredTable.SetPersistentStorage(&storage); + NL_TEST_ASSERT(aSuite, restoredTable.LoadFromStorage() == CHIP_NO_ERROR); + VerifyTableSame(aSuite, restoredTable, expected); +} + +void TestPersistentStorage(nlTestSuite * aSuite, void * aContext) +{ + chip::TestPersistentStorageDelegate testStorage; + BindingTable table; + chip::DefaultStorageKeyAllocator key; + chip::Optional cluster = chip::MakeOptional(static_cast(6)); + std::vector expected = { + EmberBindingTableEntry::ForNode(0, 0, 0, 0, NullOptional), + EmberBindingTableEntry::ForNode(1, 1, 0, 0, cluster), + EmberBindingTableEntry::ForGroup(2, 2, 0, NullOptional), + EmberBindingTableEntry::ForGroup(3, 3, 0, cluster), + }; + table.SetPersistentStorage(&testStorage); + NL_TEST_ASSERT(aSuite, table.Add(expected[0]) == CHIP_NO_ERROR); + NL_TEST_ASSERT(aSuite, table.Add(expected[1]) == CHIP_NO_ERROR); + NL_TEST_ASSERT(aSuite, table.Add(expected[2]) == CHIP_NO_ERROR); + NL_TEST_ASSERT(aSuite, table.Add(expected[3]) == CHIP_NO_ERROR); + VerifyRestored(aSuite, testStorage, expected); + + // Verify storage untouched if add fails + testStorage.AddErrorKey(key.BindingTableEntry(4)); + NL_TEST_ASSERT(aSuite, table.Add(EmberBindingTableEntry::ForNode(4, 4, 0, 0, NullOptional)) != CHIP_NO_ERROR); + VerifyRestored(aSuite, testStorage, expected); + testStorage.ClearErrorKey(); + + // Verify storage untouched if removing head fails + testStorage.AddErrorKey(key.BindingTable()); + auto iter = table.begin(); + NL_TEST_ASSERT(aSuite, table.RemoveAt(iter) != CHIP_NO_ERROR); + VerifyTableSame(aSuite, table, expected); + testStorage.ClearErrorKey(); + VerifyRestored(aSuite, testStorage, expected); + + // Verify storage untouched if removing other nodes fails + testStorage.AddErrorKey(key.BindingTableEntry(0)); + iter = table.begin(); + ++iter; + NL_TEST_ASSERT(aSuite, table.RemoveAt(iter) != CHIP_NO_ERROR); + VerifyTableSame(aSuite, table, expected); + testStorage.ClearErrorKey(); + VerifyRestored(aSuite, testStorage, expected); + + // Verify removing head + iter = table.begin(); + NL_TEST_ASSERT(aSuite, table.RemoveAt(iter) == CHIP_NO_ERROR); + VerifyTableSame(aSuite, table, { expected[1], expected[2], expected[3] }); + VerifyRestored(aSuite, testStorage, { expected[1], expected[2], expected[3] }); + + // Verify removing other nodes + ++iter; + NL_TEST_ASSERT(aSuite, table.RemoveAt(iter) == CHIP_NO_ERROR); + VerifyTableSame(aSuite, table, { expected[1], expected[3] }); + VerifyRestored(aSuite, testStorage, { expected[1], expected[3] }); +} + } // namespace int TestBindingTable() @@ -106,6 +193,7 @@ int TestBindingTable() NL_TEST_DEF("TestEmptyBindingTable", TestEmptyBindingTable), NL_TEST_DEF("TestAdd", TestAdd), NL_TEST_DEF("TestRemoveThenAdd", TestRemoveThenAdd), + NL_TEST_DEF("TestPersistentStorage", TestPersistentStorage), NL_TEST_SENTINEL(), }; diff --git a/src/app/tests/TestPendingNotificationMap.cpp b/src/app/tests/TestPendingNotificationMap.cpp index 7a5b730502e622..a622b2da701ce8 100644 --- a/src/app/tests/TestPendingNotificationMap.cpp +++ b/src/app/tests/TestPendingNotificationMap.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -36,7 +37,7 @@ void ClearBindingTable(BindingTable & table) auto iter = table.begin(); while (iter != table.end()) { - iter = table.RemoveAt(iter); + table.RemoveAt(iter); } } @@ -140,6 +141,8 @@ int TestPeindingNotificationMap() nullptr, nullptr, }; + chip::TestPersistentStorageDelegate storage; + BindingTable::GetInstance().SetPersistentStorage(&storage); nlTestRunner(&theSuite, nullptr); return (nlTestRunnerStats(&theSuite)); } diff --git a/src/app/util/binding-table.cpp b/src/app/util/binding-table.cpp index 1b3bf4a2000142..932243a7226209 100644 --- a/src/app/util/binding-table.cpp +++ b/src/app/util/binding-table.cpp @@ -27,7 +27,7 @@ BindingTable BindingTable::sInstance; BindingTable::BindingTable() { - memset(mNextIndex, EMBER_BINDING_TABLE_SIZE, sizeof(mNextIndex)); + memset(mNextIndex, kNextNullIndex, sizeof(mNextIndex)); } CHIP_ERROR BindingTable::Add(const EmberBindingTableEntry & entry) @@ -42,18 +42,45 @@ CHIP_ERROR BindingTable::Add(const EmberBindingTableEntry & entry) return CHIP_ERROR_NO_MEMORY; } mBindingTable[newIndex] = entry; - if (mTail == EMBER_BINDING_TABLE_SIZE) + CHIP_ERROR error = SaveEntryToStorage(newIndex, kNextNullIndex); + if (error == CHIP_NO_ERROR) { - mTail = newIndex; - mHead = newIndex; + if (mTail == kNextNullIndex) + { + error = SaveListInfo(newIndex); + } + else + { + error = SaveEntryToStorage(mTail, newIndex); + } + if (error != CHIP_NO_ERROR) + { + mStorage->SyncDeleteKeyValue(mKeyAllocator.BindingTableEntry(newIndex)); + } + } + if (error != CHIP_NO_ERROR) + { + // Roll back + mBindingTable[newIndex].type = EMBER_UNUSED_BINDING; + return error; } else { - mNextIndex[mTail] = newIndex; - mTail = newIndex; + if (mTail == kNextNullIndex) + { + mTail = newIndex; + mHead = newIndex; + } + else + { + mNextIndex[mTail] = newIndex; + mNextIndex[newIndex] = kNextNullIndex; + mTail = newIndex; + } + + mSize++; + return CHIP_NO_ERROR; } - mSize++; - return CHIP_NO_ERROR; } const EmberBindingTableEntry & BindingTable::GetAt(uint8_t index) @@ -61,11 +88,154 @@ const EmberBindingTableEntry & BindingTable::GetAt(uint8_t index) return mBindingTable[index]; } -BindingTable::Iterator BindingTable::RemoveAt(Iterator iter) +CHIP_ERROR BindingTable::SaveEntryToStorage(uint8_t index, uint8_t nextIndex) +{ + EmberBindingTableEntry & entry = mBindingTable[index]; + uint8_t buffer[kEntryStorageSize] = { 0 }; + chip::TLV::TLVWriter writer; + writer.Init(buffer); + chip::TLV::TLVType container; + ReturnErrorOnFailure(writer.StartContainer(chip::TLV::AnonymousTag(), chip::TLV::TLVType::kTLVType_Structure, container)); + ReturnErrorOnFailure(writer.Put(chip::TLV::ContextTag(kTagFabricIndex), entry.fabricIndex)); + ReturnErrorOnFailure(writer.Put(chip::TLV::ContextTag(kTagLocalEndpoint), entry.local)); + if (entry.clusterId.HasValue()) + { + ReturnErrorOnFailure(writer.Put(chip::TLV::ContextTag(kTagCluster), entry.clusterId.Value())); + } + if (entry.type == EMBER_UNICAST_BINDING) + { + ReturnErrorOnFailure(writer.Put(chip::TLV::ContextTag(kTagRemoteEndpoint), entry.remote)); + ReturnErrorOnFailure(writer.Put(chip::TLV::ContextTag(kTagNodeId), entry.nodeId)); + } + else + { + ReturnErrorOnFailure(writer.Put(chip::TLV::ContextTag(kTagGroupId), entry.groupId)); + } + ReturnErrorOnFailure(writer.Put(chip::TLV::ContextTag(kTagNextEntry), nextIndex)); + ReturnErrorOnFailure(writer.EndContainer(container)); + ReturnErrorOnFailure(writer.Finalize()); + return mStorage->SyncSetKeyValue(mKeyAllocator.BindingTableEntry(index), buffer, + static_cast(writer.GetLengthWritten())); +} + +CHIP_ERROR BindingTable::SaveListInfo(uint8_t head) { - if (iter.mTable != this || iter.mIndex == EMBER_BINDING_TABLE_SIZE) + uint8_t buffer[kListInfoStorageSize] = { 0 }; + chip::TLV::TLVWriter writer; + writer.Init(buffer); + chip::TLV::TLVType container; + ReturnErrorOnFailure(writer.StartContainer(chip::TLV::AnonymousTag(), chip::TLV::TLVType::kTLVType_Structure, container)); + ReturnErrorOnFailure(writer.Put(chip::TLV::ContextTag(kTagStorageVersion), kStorageVersion)); + ReturnErrorOnFailure(writer.Put(chip::TLV::ContextTag(kTagHead), head)); + ReturnErrorOnFailure(writer.EndContainer(container)); + ReturnErrorOnFailure(writer.Finalize()); + return mStorage->SyncSetKeyValue(mKeyAllocator.BindingTable(), buffer, static_cast(writer.GetLengthWritten())); +} + +CHIP_ERROR BindingTable::LoadFromStorage() +{ + VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE); + uint8_t buffer[kListInfoStorageSize] = { 0 }; + uint16_t size = sizeof(buffer); + CHIP_ERROR error; + + ReturnErrorOnFailure(mStorage->SyncGetKeyValue(mKeyAllocator.BindingTable(), buffer, size)); + chip::TLV::TLVReader reader; + reader.Init(buffer, size); + + ReturnErrorOnFailure(reader.Next(chip::TLV::kTLVType_Structure, chip::TLV::AnonymousTag())); + + chip::TLV::TLVType container; + ReturnErrorOnFailure(reader.EnterContainer(container)); + + ReturnErrorOnFailure(reader.Next(chip::TLV::ContextTag(kTagStorageVersion))); + uint32_t version; + ReturnErrorOnFailure(reader.Get(version)); + VerifyOrReturnError(version == kStorageVersion, CHIP_ERROR_VERSION_MISMATCH); + ReturnErrorOnFailure(reader.Next(chip::TLV::ContextTag(kTagHead))); + uint8_t index; + ReturnErrorOnFailure(reader.Get(index)); + mHead = index; + while (index != kNextNullIndex) { - return iter; + uint8_t nextIndex; + error = LoadEntryFromStorage(index, nextIndex); + if (error != CHIP_NO_ERROR) + { + mHead = kNextNullIndex; + mTail = kNextNullIndex; + return error; + } + mTail = index; + index = nextIndex; + mSize++; + } + error = reader.ExitContainer(container); + if (error != CHIP_NO_ERROR) + { + mHead = kNextNullIndex; + mTail = kNextNullIndex; + } + return error; +} + +CHIP_ERROR BindingTable::LoadEntryFromStorage(uint8_t index, uint8_t & nextIndex) +{ + uint8_t buffer[kEntryStorageSize] = { 0 }; + uint16_t size = sizeof(buffer); + EmberBindingTableEntry entry; + + ReturnErrorOnFailure(mStorage->SyncGetKeyValue(mKeyAllocator.BindingTableEntry(index), buffer, size)); + chip::TLV::TLVReader reader; + reader.Init(buffer, size); + + ReturnErrorOnFailure(reader.Next(chip::TLV::kTLVType_Structure, chip::TLV::AnonymousTag())); + + chip::TLV::TLVType container; + ReturnErrorOnFailure(reader.EnterContainer(container)); + ReturnErrorOnFailure(reader.Next(chip::TLV::ContextTag(kTagFabricIndex))); + ReturnErrorOnFailure(reader.Get(entry.fabricIndex)); + ReturnErrorOnFailure(reader.Next(chip::TLV::ContextTag(kTagLocalEndpoint))); + ReturnErrorOnFailure(reader.Get(entry.local)); + ReturnErrorOnFailure(reader.Next()); + if (reader.GetTag() == chip::TLV::ContextTag(kTagCluster)) + { + uint16_t clusterId; + ReturnErrorOnFailure(reader.Get(clusterId)); + entry.clusterId.SetValue(clusterId); + ReturnErrorOnFailure(reader.Next()); + } + else + { + entry.clusterId = NullOptional; + } + if (reader.GetTag() == chip::TLV::ContextTag(kTagRemoteEndpoint)) + { + entry.type = EMBER_UNICAST_BINDING; + ReturnErrorOnFailure(reader.Get(entry.remote)); + ReturnErrorOnFailure(reader.Next(chip::TLV::ContextTag(kTagNodeId))); + ReturnErrorOnFailure(reader.Get(entry.nodeId)); + } + else + { + entry.type = EMBER_MULTICAST_BINDING; + ReturnErrorCodeIf(reader.GetTag() != chip::TLV::ContextTag(kTagGroupId), CHIP_ERROR_INVALID_TLV_TAG); + ReturnErrorOnFailure(reader.Get(entry.groupId)); + } + ReturnErrorOnFailure(reader.Next(chip::TLV::ContextTag(kTagNextEntry))); + ReturnErrorOnFailure(reader.Get(nextIndex)); + ReturnErrorOnFailure(reader.ExitContainer(container)); + mBindingTable[index] = entry; + mNextIndex[index] = nextIndex; + return CHIP_NO_ERROR; +} + +CHIP_ERROR BindingTable::RemoveAt(Iterator & iter) +{ + CHIP_ERROR error; + if (iter.mTable != this || iter.mIndex == kNextNullIndex) + { + return CHIP_ERROR_INVALID_ARGUMENT; } if (iter.mIndex == mTail) { @@ -74,24 +244,40 @@ BindingTable::Iterator BindingTable::RemoveAt(Iterator iter) uint8_t next = mNextIndex[iter.mIndex]; if (iter.mIndex != mHead) { - mNextIndex[iter.mPrevIndex] = next; + error = SaveEntryToStorage(iter.mPrevIndex, next); + if (error == CHIP_NO_ERROR) + { + mNextIndex[iter.mPrevIndex] = next; + } } else { - mHead = next; + error = SaveListInfo(next); + if (error == CHIP_NO_ERROR) + { + mHead = next; + } + } + if (error == CHIP_NO_ERROR) + { + // The remove is considered "submitted" once the change on prev node takes effect + if (mStorage->SyncDeleteKeyValue(mKeyAllocator.BindingTableEntry(iter.mIndex)) != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "Failed to remove binding table entry %u from storage", iter.mIndex); + } + mBindingTable[iter.mIndex].type = EMBER_UNUSED_BINDING; + mNextIndex[iter.mIndex] = kNextNullIndex; + mSize--; } - mBindingTable[iter.mIndex].type = EMBER_UNUSED_BINDING; - mNextIndex[iter.mIndex] = EMBER_BINDING_TABLE_SIZE; - mSize--; iter.mIndex = next; - return iter; + return error; } BindingTable::Iterator BindingTable::begin() { Iterator iter; iter.mTable = this; - iter.mPrevIndex = EMBER_BINDING_TABLE_SIZE; + iter.mPrevIndex = kNextNullIndex; iter.mIndex = mHead; return iter; } @@ -100,7 +286,7 @@ BindingTable::Iterator BindingTable::end() { Iterator iter; iter.mTable = this; - iter.mIndex = EMBER_BINDING_TABLE_SIZE; + iter.mIndex = kNextNullIndex; return iter; } @@ -118,7 +304,7 @@ uint8_t BindingTable::GetNextAvaiableIndex() BindingTable::Iterator BindingTable::Iterator::operator++() { - if (mIndex != EMBER_BINDING_TABLE_SIZE) + if (mIndex != kNextNullIndex) { mPrevIndex = mIndex; mIndex = mTable->mNextIndex[mIndex]; diff --git a/src/app/util/binding-table.h b/src/app/util/binding-table.h index 948711d51b86da..bdc41c1836f41a 100644 --- a/src/app/util/binding-table.h +++ b/src/app/util/binding-table.h @@ -22,6 +22,8 @@ #pragma once #include +#include +#include namespace chip { @@ -63,9 +65,8 @@ class BindingTable const EmberBindingTableEntry & GetAt(uint8_t index); - // The RemoveAt function shares the same sematics as the std::list::remove. - // It returns the next iterator after removal and the old iterator is no loger valid. - Iterator RemoveAt(Iterator iter); + // The iter will be moved to the next item in the table after calling RemoveAt. + CHIP_ERROR RemoveAt(Iterator & iter); // Returns the number of active entries in the binding table. // *NOTE* The function does not return the capacity of the binding table. @@ -75,19 +76,44 @@ class BindingTable Iterator end(); + void SetPersistentStorage(PersistentStorageDelegate * storage) { mStorage = storage; } + + CHIP_ERROR LoadFromStorage(); + static BindingTable & GetInstance() { return sInstance; } private: static BindingTable sInstance; + static constexpr uint8_t kEntryStorageSize = 64; + static constexpr uint8_t kListInfoStorageSize = 16; + static constexpr uint32_t kStorageVersion = 1; + static constexpr uint8_t kTagStorageVersion = 1; + static constexpr uint8_t kTagHead = 2; + static constexpr uint8_t kTagFabricIndex = 1; + static constexpr uint8_t kTagLocalEndpoint = 2; + static constexpr uint8_t kTagCluster = 3; + static constexpr uint8_t kTagRemoteEndpoint = 4; + static constexpr uint8_t kTagNodeId = 5; + static constexpr uint8_t kTagGroupId = 6; + static constexpr uint8_t kTagNextEntry = 7; + static constexpr uint8_t kNextNullIndex = 255; uint8_t GetNextAvaiableIndex(); + CHIP_ERROR SaveEntryToStorage(uint8_t index, uint8_t nextIndex); + CHIP_ERROR SaveListInfo(uint8_t head); + + CHIP_ERROR LoadEntryFromStorage(uint8_t index, uint8_t & nextIndex); + EmberBindingTableEntry mBindingTable[EMBER_BINDING_TABLE_SIZE]; uint8_t mNextIndex[EMBER_BINDING_TABLE_SIZE]; - uint8_t mHead = EMBER_BINDING_TABLE_SIZE; - uint8_t mTail = EMBER_BINDING_TABLE_SIZE; + uint8_t mHead = kNextNullIndex; + uint8_t mTail = kNextNullIndex; uint8_t mSize = 0; + + PersistentStorageDelegate * mStorage; + DefaultStorageKeyAllocator mKeyAllocator; }; } // namespace chip diff --git a/src/app/util/types_stub.h b/src/app/util/types_stub.h index ca381d3f0fbfd5..afd75dbb31d89d 100644 --- a/src/app/util/types_stub.h +++ b/src/app/util/types_stub.h @@ -565,12 +565,12 @@ struct EmberBindingTableEntry return false; } - if (type == EMBER_UNICAST_BINDING && nodeId != other.nodeId) + if (type == EMBER_UNICAST_BINDING && (nodeId != other.nodeId || remote != other.remote)) { return false; } - return fabricIndex == other.fabricIndex && local == other.local && clusterId == other.clusterId && remote == other.remote; + return fabricIndex == other.fabricIndex && local == other.local && clusterId == other.clusterId; } }; diff --git a/src/lib/support/DefaultStorageKeyAllocator.h b/src/lib/support/DefaultStorageKeyAllocator.h index 2297b69dc0e55b..e89d7dd087de7e 100644 --- a/src/lib/support/DefaultStorageKeyAllocator.h +++ b/src/lib/support/DefaultStorageKeyAllocator.h @@ -69,6 +69,10 @@ class DefaultStorageKeyAllocator return Format("a/%" PRIx16 "/%" PRIx32 "/%" PRIx32, aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId); } + // Binding Table + const char * BindingTable() { return Format("bt"); } + const char * BindingTableEntry(uint8_t index) { return Format("bt/%x", index); } + private: static const size_t kKeyLengthMax = 32; diff --git a/src/lib/support/TestPersistentStorageDelegate.h b/src/lib/support/TestPersistentStorageDelegate.h index bd05826f9e9048..a490892b7be989 100644 --- a/src/lib/support/TestPersistentStorageDelegate.h +++ b/src/lib/support/TestPersistentStorageDelegate.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -36,6 +37,10 @@ class TestPersistentStorageDelegate : public PersistentStorageDelegate, public F CHIP_ERROR SyncGetKeyValue(const char * key, void * buffer, uint16_t & size) override { + if (mErrorKeys.find(std::string(key)) != mErrorKeys.end()) + { + return CHIP_ERROR_PERSISTED_STORAGE_FAILED; + } bool contains = mStorage.find(key) != mStorage.end(); VerifyOrReturnError(contains, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND); @@ -50,6 +55,10 @@ class TestPersistentStorageDelegate : public PersistentStorageDelegate, public F CHIP_ERROR SyncSetKeyValue(const char * key, const void * value, uint16_t size) override { + if (mErrorKeys.find(std::string(key)) != mErrorKeys.end()) + { + return CHIP_ERROR_PERSISTED_STORAGE_FAILED; + } const uint8_t * bytes = static_cast(value); mStorage[key] = std::vector(bytes, bytes + size); return CHIP_NO_ERROR; @@ -57,6 +66,10 @@ class TestPersistentStorageDelegate : public PersistentStorageDelegate, public F CHIP_ERROR SyncDeleteKeyValue(const char * key) override { + if (mErrorKeys.find(std::string(key)) != mErrorKeys.end()) + { + return CHIP_ERROR_PERSISTED_STORAGE_FAILED; + } mStorage.erase(key); return CHIP_NO_ERROR; } @@ -73,8 +86,13 @@ class TestPersistentStorageDelegate : public PersistentStorageDelegate, public F CHIP_ERROR SyncDelete(FabricIndex fabricIndex, const char * key) override { return SyncDeleteKeyValue(key); }; + void AddErrorKey(const std::string & key) { mErrorKeys.insert(key); } + + void ClearErrorKey() { mErrorKeys.clear(); } + protected: std::map> mStorage; + std::set mErrorKeys; }; } // namespace chip