/* * Copyright (c) 2023 Project CHIP Authors * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @file * This file defines a basic implementation of SubscriptionResumptionStorage that * persists subscriptions in a flat list in TLV. */ #include #include #include #include #include namespace chip { namespace app { constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kPeerNodeIdTag; constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kFabricIndexTag; constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kSubscriptionIdTag; constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kMinIntervalTag; constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kMaxIntervalTag; constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kFabricFilteredTag; constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kAttributePathsListTag; constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kEventPathsListTag; constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kAttributePathTag; constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kEventPathTag; constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kEndpointIdTag; constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kClusterIdTag; constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kAttributeIdTag; constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kEventIdTag; constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kEventPathTypeTag; constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kResumptionRetriesTag; SimpleSubscriptionResumptionStorage::SimpleSubscriptionInfoIterator::SimpleSubscriptionInfoIterator( SimpleSubscriptionResumptionStorage & storage) : mStorage(storage) { mNextIndex = 0; } size_t SimpleSubscriptionResumptionStorage::SimpleSubscriptionInfoIterator::Count() { return static_cast(mStorage.Count()); } bool SimpleSubscriptionResumptionStorage::SimpleSubscriptionInfoIterator::Next(SubscriptionInfo & output) { for (; mNextIndex < CHIP_IM_MAX_NUM_SUBSCRIPTIONS; mNextIndex++) { CHIP_ERROR err = mStorage.Load(mNextIndex, output); if (err == CHIP_NO_ERROR) { // increment index for the next call mNextIndex++; return true; } if (err != CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) { ChipLogError(DataManagement, "Failed to load subscription at index %u error %" CHIP_ERROR_FORMAT, static_cast(mNextIndex), err.Format()); mStorage.Delete(mNextIndex); } } return false; } void SimpleSubscriptionResumptionStorage::SimpleSubscriptionInfoIterator::Release() { mStorage.mSubscriptionInfoIterators.ReleaseObject(this); } CHIP_ERROR SimpleSubscriptionResumptionStorage::Init(PersistentStorageDelegate * storage) { VerifyOrReturnError(storage != nullptr, CHIP_ERROR_INVALID_ARGUMENT); mStorage = storage; uint16_t countMax; uint16_t len = sizeof(countMax); CHIP_ERROR err = mStorage->SyncGetKeyValue(DefaultStorageKeyAllocator::SubscriptionResumptionMaxCount().KeyName(), &countMax, len); // If there's a previous countMax and it's larger than CHIP_IM_MAX_NUM_SUBSCRIPTIONS, // clean up subscriptions beyond the limit if ((err == CHIP_NO_ERROR) && (countMax != CHIP_IM_MAX_NUM_SUBSCRIPTIONS)) { for (uint16_t subscriptionIndex = CHIP_IM_MAX_NUM_SUBSCRIPTIONS; subscriptionIndex < countMax; subscriptionIndex++) { Delete(subscriptionIndex); } } // Always save the current CHIP_IM_MAX_NUM_SUBSCRIPTIONS uint16_t countMaxToSave = CHIP_IM_MAX_NUM_SUBSCRIPTIONS; ReturnErrorOnFailure(mStorage->SyncSetKeyValue(DefaultStorageKeyAllocator::SubscriptionResumptionMaxCount().KeyName(), &countMaxToSave, sizeof(uint16_t))); return CHIP_NO_ERROR; } SubscriptionResumptionStorage::SubscriptionInfoIterator * SimpleSubscriptionResumptionStorage::IterateSubscriptions() { return mSubscriptionInfoIterators.CreateObject(*this); } uint16_t SimpleSubscriptionResumptionStorage::Count() { uint16_t subscriptionCount = 0; for (uint16_t subscriptionIndex = 0; subscriptionIndex < CHIP_IM_MAX_NUM_SUBSCRIPTIONS; subscriptionIndex++) { if (mStorage->SyncDoesKeyExist(DefaultStorageKeyAllocator::SubscriptionResumption(subscriptionIndex).KeyName())) { subscriptionCount++; } } return subscriptionCount; } CHIP_ERROR SimpleSubscriptionResumptionStorage::Delete(uint16_t subscriptionIndex) { return mStorage->SyncDeleteKeyValue(DefaultStorageKeyAllocator::SubscriptionResumption(subscriptionIndex).KeyName()); } CHIP_ERROR SimpleSubscriptionResumptionStorage::Load(uint16_t subscriptionIndex, SubscriptionInfo & subscriptionInfo) { Platform::ScopedMemoryBuffer backingBuffer; backingBuffer.Calloc(MaxSubscriptionSize()); VerifyOrReturnError(backingBuffer.Get() != nullptr, CHIP_ERROR_NO_MEMORY); uint16_t len = static_cast(MaxSubscriptionSize()); ReturnErrorOnFailure(mStorage->SyncGetKeyValue(DefaultStorageKeyAllocator::SubscriptionResumption(subscriptionIndex).KeyName(), backingBuffer.Get(), len)); TLV::ScopedBufferTLVReader reader(std::move(backingBuffer), len); ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag())); TLV::TLVType subscriptionContainerType; ReturnErrorOnFailure(reader.EnterContainer(subscriptionContainerType)); // Node ID ReturnErrorOnFailure(reader.Next(kPeerNodeIdTag)); ReturnErrorOnFailure(reader.Get(subscriptionInfo.mNodeId)); // Fabric index ReturnErrorOnFailure(reader.Next(kFabricIndexTag)); ReturnErrorOnFailure(reader.Get(subscriptionInfo.mFabricIndex)); // Subscription ID ReturnErrorOnFailure(reader.Next(kSubscriptionIdTag)); ReturnErrorOnFailure(reader.Get(subscriptionInfo.mSubscriptionId)); // Min interval ReturnErrorOnFailure(reader.Next(kMinIntervalTag)); ReturnErrorOnFailure(reader.Get(subscriptionInfo.mMinInterval)); // Max interval ReturnErrorOnFailure(reader.Next(kMaxIntervalTag)); ReturnErrorOnFailure(reader.Get(subscriptionInfo.mMaxInterval)); // Fabric filtered boolean ReturnErrorOnFailure(reader.Next(kFabricFilteredTag)); ReturnErrorOnFailure(reader.Get(subscriptionInfo.mFabricFiltered)); // Attribute Paths ReturnErrorOnFailure(reader.Next(TLV::kTLVType_List, kAttributePathsListTag)); TLV::TLVType attributesListType; ReturnErrorOnFailure(reader.EnterContainer(attributesListType)); size_t pathCount = 0; ReturnErrorOnFailure(reader.CountRemainingInContainer(&pathCount)); // If a stack struct is being reused to iterate, free the previous paths ScopedMemoryBuffer subscriptionInfo.mAttributePaths.Free(); if (pathCount) { subscriptionInfo.mAttributePaths.Calloc(pathCount); VerifyOrReturnError(subscriptionInfo.mAttributePaths.Get() != nullptr, CHIP_ERROR_NO_MEMORY); for (size_t pathIndex = 0; pathIndex < pathCount; pathIndex++) { ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, kAttributePathTag)); TLV::TLVType attributeContainerType; ReturnErrorOnFailure(reader.EnterContainer(attributeContainerType)); ReturnErrorOnFailure(reader.Next(kEndpointIdTag)); ReturnErrorOnFailure(reader.Get(subscriptionInfo.mAttributePaths[pathIndex].mEndpointId)); ReturnErrorOnFailure(reader.Next(kClusterIdTag)); ReturnErrorOnFailure(reader.Get(subscriptionInfo.mAttributePaths[pathIndex].mClusterId)); ReturnErrorOnFailure(reader.Next(kAttributeIdTag)); ReturnErrorOnFailure(reader.Get(subscriptionInfo.mAttributePaths[pathIndex].mAttributeId)); ReturnErrorOnFailure(reader.ExitContainer(attributeContainerType)); } } ReturnErrorOnFailure(reader.ExitContainer(attributesListType)); // Event Paths ReturnErrorOnFailure(reader.Next(TLV::kTLVType_List, kEventPathsListTag)); TLV::TLVType eventsListType; ReturnErrorOnFailure(reader.EnterContainer(eventsListType)); ReturnErrorOnFailure(reader.CountRemainingInContainer(&pathCount)); // If a stack struct is being reused to iterate, free the previous paths ScopedMemoryBuffer subscriptionInfo.mEventPaths.Free(); if (pathCount) { subscriptionInfo.mEventPaths.Calloc(pathCount); VerifyOrReturnError(subscriptionInfo.mEventPaths.Get() != nullptr, CHIP_ERROR_NO_MEMORY); for (size_t pathIndex = 0; pathIndex < pathCount; pathIndex++) { ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, kEventPathTag)); TLV::TLVType eventContainerType; ReturnErrorOnFailure(reader.EnterContainer(eventContainerType)); EventPathType eventPathType; ReturnErrorOnFailure(reader.Next(kEventPathTypeTag)); ReturnErrorOnFailure(reader.Get(eventPathType)); subscriptionInfo.mEventPaths[pathIndex].mIsUrgentEvent = (eventPathType == EventPathType::kUrgent); ReturnErrorOnFailure(reader.Next(kEndpointIdTag)); ReturnErrorOnFailure(reader.Get(subscriptionInfo.mEventPaths[pathIndex].mEndpointId)); ReturnErrorOnFailure(reader.Next(kClusterIdTag)); ReturnErrorOnFailure(reader.Get(subscriptionInfo.mEventPaths[pathIndex].mClusterId)); ReturnErrorOnFailure(reader.Next(kEventIdTag)); ReturnErrorOnFailure(reader.Get(subscriptionInfo.mEventPaths[pathIndex].mEventId)); ReturnErrorOnFailure(reader.ExitContainer(eventContainerType)); } } ReturnErrorOnFailure(reader.ExitContainer(eventsListType)); #if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION // If the reader cannot get resumption retries, set it to 0 for subscriptionInfo if (reader.Next(kResumptionRetriesTag) == CHIP_NO_ERROR) { ReturnErrorOnFailure(reader.Get(subscriptionInfo.mResumptionRetries)); } else { subscriptionInfo.mResumptionRetries = 0; } #endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION ReturnErrorOnFailure(reader.ExitContainer(subscriptionContainerType)); return CHIP_NO_ERROR; } CHIP_ERROR SimpleSubscriptionResumptionStorage::Save(TLV::TLVWriter & writer, SubscriptionInfo & subscriptionInfo) { TLV::TLVType subscriptionContainerType; ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, subscriptionContainerType)); ReturnErrorOnFailure(writer.Put(kPeerNodeIdTag, subscriptionInfo.mNodeId)); ReturnErrorOnFailure(writer.Put(kFabricIndexTag, subscriptionInfo.mFabricIndex)); ReturnErrorOnFailure(writer.Put(kSubscriptionIdTag, subscriptionInfo.mSubscriptionId)); ReturnErrorOnFailure(writer.Put(kMinIntervalTag, subscriptionInfo.mMinInterval)); ReturnErrorOnFailure(writer.Put(kMaxIntervalTag, subscriptionInfo.mMaxInterval)); ReturnErrorOnFailure(writer.Put(kFabricFilteredTag, subscriptionInfo.mFabricFiltered)); // Attribute paths TLV::TLVType attributesListType; ReturnErrorOnFailure(writer.StartContainer(kAttributePathsListTag, TLV::kTLVType_List, attributesListType)); for (size_t pathIndex = 0; pathIndex < subscriptionInfo.mAttributePaths.AllocatedSize(); pathIndex++) { TLV::TLVType attributeContainerType = TLV::kTLVType_Structure; ReturnErrorOnFailure(writer.StartContainer(kAttributePathTag, TLV::kTLVType_Structure, attributeContainerType)); ReturnErrorOnFailure(writer.Put(kEndpointIdTag, subscriptionInfo.mAttributePaths[pathIndex].mEndpointId)); ReturnErrorOnFailure(writer.Put(kClusterIdTag, subscriptionInfo.mAttributePaths[pathIndex].mClusterId)); ReturnErrorOnFailure(writer.Put(kAttributeIdTag, subscriptionInfo.mAttributePaths[pathIndex].mAttributeId)); ReturnErrorOnFailure(writer.EndContainer(attributeContainerType)); } ReturnErrorOnFailure(writer.EndContainer(attributesListType)); // Event paths TLV::TLVType eventsListType; ReturnErrorOnFailure(writer.StartContainer(kEventPathsListTag, TLV::kTLVType_List, eventsListType)); for (size_t pathIndex = 0; pathIndex < subscriptionInfo.mEventPaths.AllocatedSize(); pathIndex++) { TLV::TLVType eventContainerType = TLV::kTLVType_Structure; ReturnErrorOnFailure(writer.StartContainer(kEventPathTag, TLV::kTLVType_Structure, eventContainerType)); if (subscriptionInfo.mEventPaths[pathIndex].mIsUrgentEvent) { ReturnErrorOnFailure(writer.Put(kEventPathTypeTag, EventPathType::kUrgent)); } else { ReturnErrorOnFailure(writer.Put(kEventPathTypeTag, EventPathType::kNonUrgent)); } ReturnErrorOnFailure(writer.Put(kEndpointIdTag, subscriptionInfo.mEventPaths[pathIndex].mEndpointId)); ReturnErrorOnFailure(writer.Put(kClusterIdTag, subscriptionInfo.mEventPaths[pathIndex].mClusterId)); ReturnErrorOnFailure(writer.Put(kEventIdTag, subscriptionInfo.mEventPaths[pathIndex].mEventId)); ReturnErrorOnFailure(writer.EndContainer(eventContainerType)); } ReturnErrorOnFailure(writer.EndContainer(eventsListType)); #if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION ReturnErrorOnFailure(writer.Put(kResumptionRetriesTag, subscriptionInfo.mResumptionRetries)); #endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION ReturnErrorOnFailure(writer.EndContainer(subscriptionContainerType)); return CHIP_NO_ERROR; } CHIP_ERROR SimpleSubscriptionResumptionStorage::Save(SubscriptionInfo & subscriptionInfo) { // Find empty index or duplicate if exists uint16_t subscriptionIndex; uint16_t firstEmptySubscriptionIndex = CHIP_IM_MAX_NUM_SUBSCRIPTIONS; // initialize to out of bounds as "not set" for (subscriptionIndex = 0; subscriptionIndex < CHIP_IM_MAX_NUM_SUBSCRIPTIONS; subscriptionIndex++) { SubscriptionInfo currentSubscriptionInfo; CHIP_ERROR err = Load(subscriptionIndex, currentSubscriptionInfo); // if empty and firstEmptySubscriptionIndex isn't set yet, then mark empty spot if ((firstEmptySubscriptionIndex == CHIP_IM_MAX_NUM_SUBSCRIPTIONS) && (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)) { firstEmptySubscriptionIndex = subscriptionIndex; } // delete duplicate if (err == CHIP_NO_ERROR) { if ((subscriptionInfo.mNodeId == currentSubscriptionInfo.mNodeId) && (subscriptionInfo.mFabricIndex == currentSubscriptionInfo.mFabricIndex) && (subscriptionInfo.mSubscriptionId == currentSubscriptionInfo.mSubscriptionId)) { Delete(subscriptionIndex); // if duplicate is the first empty spot, then also set it if (firstEmptySubscriptionIndex == CHIP_IM_MAX_NUM_SUBSCRIPTIONS) { firstEmptySubscriptionIndex = subscriptionIndex; } } } } // Fail if no empty space if (firstEmptySubscriptionIndex == CHIP_IM_MAX_NUM_SUBSCRIPTIONS) { return CHIP_ERROR_NO_MEMORY; } // Now construct subscription state and save Platform::ScopedMemoryBuffer backingBuffer; backingBuffer.Calloc(MaxSubscriptionSize()); VerifyOrReturnError(backingBuffer.Get() != nullptr, CHIP_ERROR_NO_MEMORY); TLV::ScopedBufferTLVWriter writer(std::move(backingBuffer), MaxSubscriptionSize()); ReturnErrorOnFailure(Save(writer, subscriptionInfo)); const auto len = writer.GetLengthWritten(); VerifyOrReturnError(CanCastTo(len), CHIP_ERROR_BUFFER_TOO_SMALL); writer.Finalize(backingBuffer); ReturnErrorOnFailure( mStorage->SyncSetKeyValue(DefaultStorageKeyAllocator::SubscriptionResumption(firstEmptySubscriptionIndex).KeyName(), backingBuffer.Get(), static_cast(len))); return CHIP_NO_ERROR; } CHIP_ERROR SimpleSubscriptionResumptionStorage::Delete(NodeId nodeId, FabricIndex fabricIndex, SubscriptionId subscriptionId) { bool subscriptionFound = false; CHIP_ERROR lastDeleteErr = CHIP_NO_ERROR; uint16_t remainingSubscriptionsCount = 0; for (uint16_t subscriptionIndex = 0; subscriptionIndex < CHIP_IM_MAX_NUM_SUBSCRIPTIONS; subscriptionIndex++) { SubscriptionInfo subscriptionInfo; CHIP_ERROR err = Load(subscriptionIndex, subscriptionInfo); // delete match if (err == CHIP_NO_ERROR) { if ((nodeId == subscriptionInfo.mNodeId) && (fabricIndex == subscriptionInfo.mFabricIndex) && (subscriptionId == subscriptionInfo.mSubscriptionId)) { subscriptionFound = true; CHIP_ERROR deleteErr = Delete(subscriptionIndex); if (deleteErr != CHIP_NO_ERROR) { lastDeleteErr = deleteErr; } } else { remainingSubscriptionsCount++; } } } // if there are no persisted subscriptions, the MaxCount can also be deleted if (remainingSubscriptionsCount == 0) { DeleteMaxCount(); } if (lastDeleteErr != CHIP_NO_ERROR) { return lastDeleteErr; } return subscriptionFound ? CHIP_NO_ERROR : CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND; } CHIP_ERROR SimpleSubscriptionResumptionStorage::DeleteMaxCount() { return mStorage->SyncDeleteKeyValue(DefaultStorageKeyAllocator::SubscriptionResumptionMaxCount().KeyName()); } CHIP_ERROR SimpleSubscriptionResumptionStorage::DeleteAll(FabricIndex fabricIndex) { CHIP_ERROR deleteErr = CHIP_NO_ERROR; uint16_t count = 0; for (uint16_t subscriptionIndex = 0; subscriptionIndex < CHIP_IM_MAX_NUM_SUBSCRIPTIONS; subscriptionIndex++) { SubscriptionInfo subscriptionInfo; CHIP_ERROR err = Load(subscriptionIndex, subscriptionInfo); if (err == CHIP_NO_ERROR) { if (fabricIndex == subscriptionInfo.mFabricIndex) { err = Delete(subscriptionIndex); if ((err != CHIP_NO_ERROR) && (err != CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)) { deleteErr = err; } } else { count++; } } } // if there are no persisted subscriptions, the MaxCount can also be deleted if (count == 0) { CHIP_ERROR err = DeleteMaxCount(); if ((err != CHIP_NO_ERROR) && (err != CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)) { deleteErr = err; } } return deleteErr; } } // namespace app } // namespace chip