blob: cee8c50bea345c14144729b0da0a4f2e0181da7d [file] [log] [blame]
/**
*
* Copyright (c) 2025 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 <app/data-model-provider/ProviderMetadataTree.h>
#include <app/storage/TableEntry.h>
#include <lib/support/CommonIterator.h>
#include <lib/support/PersistentData.h>
#include <lib/support/TypeTraits.h>
namespace chip {
namespace app {
namespace Storage {
/**
* @brief Container which exposes various compile-time constants for specializations
* of FabricTableImpl.
*/
template <class StorageId, class StorageData>
class DefaultSerializer
{
public:
// gcc bug prevents us from using a static variable; see:
// https://stackoverflow.com/questions/50638053/constexpr-static-data-member-without-initializer
// The number of bytes used by an entry (StorageData) + its metadata when persisting to storage
static constexpr size_t kEntryMaxBytes();
// The number of bytes used by an ID (StorageId)
static constexpr size_t kIdMaxBytes() { return TLV::EstimateStructOverhead(sizeof(StorageId)); }
// The number of bytes used by FabricEntryData, which is dependent on the size of StorageId
static constexpr size_t kFabricMaxBytes()
{
return TLV::EstimateStructOverhead(sizeof(uint8_t), // entry_count
kMaxPerFabric() * kIdMaxBytes());
}
// The max number of entries per fabric; this value directly affects memory usage
static constexpr uint16_t kMaxPerFabric();
// The max number of entries for the endpoint (programmatic limit)
static constexpr uint16_t kMaxPerEndpoint();
DefaultSerializer() {}
~DefaultSerializer(){};
static CHIP_ERROR SerializeId(TLV::TLVWriter & writer, const StorageId & id);
static CHIP_ERROR DeserializeId(TLV::TLVReader & reader, StorageId & id);
static CHIP_ERROR SerializeData(TLV::TLVWriter & writer, const StorageData & data);
static CHIP_ERROR DeserializeData(TLV::TLVReader & reader, StorageData & data);
static StorageKeyName EndpointEntryCountKey(EndpointId endpoint);
// The key for persisting the data for a fabric
static StorageKeyName FabricEntryDataKey(FabricIndex fabric, EndpointId endpoint);
// The key for persisting the data for an entry in a fabric; FabricEntryDataKey should be a root prefix
// of this key, such that removing a fabric removes all its entries
static StorageKeyName FabricEntryKey(FabricIndex fabric, EndpointId endpoint, uint16_t idx);
// Clears the data to default values
static void Clear(StorageData & data) { data.Clear(); }
}; // class DefaultSerializer
/**
* @brief Implementation of a storage accessor in nonvolatile storage of a templatized table that stores by fabric index.
* This class does not actually hold the entries, but rather acts as a wrapper/accessor around the storage layer,
* reading entries from the storage pointed to by calling SetEndpoint.
*
* FabricTableImpl is an implementation that allows to store arbitrary entities using PersistentStorageDelegate.
* It handles the storage of entities by their StorageId and EnpointId over multiple fabrics.
*/
template <class StorageId, class StorageData>
class FabricTableImpl
{
using TableEntry = Data::TableEntryRef<StorageId, StorageData>;
public:
using EntryIterator = CommonIterator<TableEntry>;
using EntryIndex = Data::EntryIndex;
using Serializer = DefaultSerializer<StorageId, StorageData>;
virtual ~FabricTableImpl() { Finish(); };
CHIP_ERROR Init(PersistentStorageDelegate & storage);
void Finish();
// Entry count
/**
* @brief Get the total number of stored entries for the entire endpoint
* @param entry_count[out] the count of entries
* @return CHIP_ERROR, CHIP_NO_ERROR if successful or if the Fabric was not found, specific CHIP_ERROR otherwise
*/
CHIP_ERROR GetEndpointEntryCount(uint8_t & entry_count);
/**
* @brief Get the total number of stored entries for the specified fabric on the currently selected endpoint.
* @param fabric_index the fabric to get the count for
* @param entry_count[out] the count of entries
* @return CHIP_ERROR, CHIP_NO_ERROR if successful or if the Fabric was not found, specific CHIP_ERROR otherwise
*/
CHIP_ERROR GetFabricEntryCount(FabricIndex fabric_index, uint8_t & entry_count);
// Data
CHIP_ERROR GetRemainingCapacity(FabricIndex fabric_index, uint8_t & capacity);
/**
* @brief Writes the entry to persistent storage.
* @param fabric_index the fabric to write the entry to
* @param entry_id the unique entry identifier
* @param data the source data
* @param writeBuffer the buffer that will be used to write the data before being persisted; PersistentStorageDelegate does not
* offer a way to stream bytes to be written
*/
template <size_t kEntryMaxBytes>
CHIP_ERROR SetTableEntry(FabricIndex fabric_index, const StorageId & entry_id, const StorageData & data,
PersistenceBuffer<kEntryMaxBytes> & writeBuffer);
/**
* @brief Loads the entry from persistent storage.
* @param fabric_index the fabric to load the entry from
* @param entry_id the unique entry identifier
* @param data the target for the loaded data
* @param buffer the buffer that will be used to load from persistence; some data types in the data argument, such as
* DecodableList, point directly into the buffer, and as such for those types of structures the lifetime of the buffer needs to
* be equal to or greater than data
*/
template <size_t kEntryMaxBytes>
CHIP_ERROR GetTableEntry(FabricIndex fabric_index, StorageId & entry_id, StorageData & data,
PersistenceBuffer<kEntryMaxBytes> & buffer);
CHIP_ERROR FindTableEntry(FabricIndex fabric_index, const StorageId & entry_id, EntryIndex & idx);
CHIP_ERROR RemoveTableEntry(FabricIndex fabric_index, const StorageId & entry_id);
CHIP_ERROR RemoveTableEntryAtPosition(EndpointId endpoint, FabricIndex fabric_index, EntryIndex entry_idx);
// Fabrics
CHIP_ERROR RemoveFabric(DataModel::ProviderMetadataTree & provider, FabricIndex fabric_index);
CHIP_ERROR RemoveEndpoint();
/**
* @brief Selects the endpoint that the table will point to & entries will be read from.
* @param endpoint the endpoint which entries will be stored to or read from.
*/
void SetEndpoint(EndpointId endpoint);
void SetTableSize(uint16_t endpointEntryTableSize, uint16_t maxPerFabric);
bool IsInitialized() { return (mStorage != nullptr); }
/**
* @brief Iterates through all entries in fabric, calling iterateFn with the allocated iterator.
* @tparam kEntryMaxBytes size of the buffer for loading entries, should match DefaultSerializer::kEntryMaxBytes
* @tparam UnaryFunc a function of type std::function<CHIP_ERROR(EntryIterator & iterator)>; template arg for GCC inlining
* efficiency
* @param fabric the fabric to iterate entries for
* @param store the in-memory buffer that an entry will be read into
* @param iterateFn a function that will be called with the iterator; if this function returns an error result, iteration stops
* and IterateEntries returns that same error result.
*/
template <size_t kEntryMaxBytes, class UnaryFunc>
CHIP_ERROR IterateEntries(FabricIndex fabric, PersistenceBuffer<kEntryMaxBytes> & buffer, UnaryFunc iterateFn);
protected:
// This constructor is meant for test purposes, it allows to change the defined max for entries per fabric and global, which
// allows to simulate OTA where this value was changed
FabricTableImpl(uint16_t maxEntriesPerFabric, uint16_t maxEntriesPerEndpoint) :
mMaxPerFabric(maxEntriesPerFabric), mMaxPerEndpoint(maxEntriesPerEndpoint)
{}
// Endpoint entry count
CHIP_ERROR SetEndpointEntryCount(const uint8_t & entry_count);
/**
* @brief Implementation of an iterator over the elements in the FabricTableImpl.
*
* If you would like to expose iterators in your subclass of FabricTableImpl, you can:
* A) Use this class in an ObjectPool<EntryIteratorImpl> field to allow callers to obtain an iterator, with AutoRelease to free
* resources B) Use IterateEntries to allocate on stack
*/
template <size_t kEntryMaxBytes>
class EntryIteratorImpl : public EntryIterator
{
public:
EntryIteratorImpl(FabricTableImpl & provider, FabricIndex fabricIdx, EndpointId endpoint, uint16_t maxEntriesPerFabric,
uint16_t maxEntriesPerEndpoint, PersistenceBuffer<kEntryMaxBytes> & buffer);
size_t Count() override;
bool Next(TableEntry & output) override;
void Release() override;
protected:
FabricTableImpl & mProvider;
PersistenceBuffer<kEntryMaxBytes> & mBuffer;
FabricIndex mFabric = kUndefinedFabricIndex;
EndpointId mEndpoint = kInvalidEndpointId;
EntryIndex mNextEntryIdx;
EntryIndex mEntryIndex = 0;
uint8_t mTotalEntries = 0;
uint16_t mMaxPerFabric;
uint16_t mMaxPerEndpoint;
};
uint16_t mMaxPerFabric;
uint16_t mMaxPerEndpoint;
EndpointId mEndpointId = kInvalidEndpointId;
PersistentStorageDelegate * mStorage = nullptr;
}; // class FabricTableImpl
} // namespace Storage
} // namespace app
} // namespace chip