blob: 51b2fa994be06850485e4507531f593458a15bcd [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/MetadataTypes.h>
#include <app/storage/FabricTableImpl.h>
#include <app/util/endpoint-config-api.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/DefaultStorageKeyAllocator.h>
#include <lib/support/ReadOnlyBuffer.h>
#include <lib/support/TypeTraits.h>
#include <cstdlib>
namespace chip {
namespace app {
namespace Storage {
using EntryIndex = Data::EntryIndex;
/// @brief Tags Used to serialize entries so they can be stored in flash memory.
/// kEndpointEntryCount: Number of entries in an endpoint
/// kEntryCount: Number of entries in a Fabric
/// kStorageIdArray: Array of StorageId struct
enum class TagEntry : uint8_t
{
kEndpointEntryCount = 1,
kEntryCount,
kStorageIdArray,
kFabricTableFirstSpecializationReservedTag,
kFabricTableLastSpecializationReservedTag = 127,
// Add new entries here; kFabricTableFirstSpecializationReservedTag through
// kFabricTableLastSpecializationReservedTag are reserved for specializations
};
// Currently takes 5 Bytes to serialize Container and value in a TLV: 1 byte start struct, 2 bytes control + tag for the value, 1
// byte value, 1 byte end struct. 8 Bytes leaves space for potential increase in count_value size.
static constexpr size_t kPersistentBufferEntryCountBytes = 8;
struct BaseEntryCount : public PersistableData<kPersistentBufferEntryCountBytes>
{
uint8_t count_value = 0;
BaseEntryCount(uint8_t count = 0) : count_value(count) {}
void Clear() override { count_value = 0; }
CHIP_ERROR Serialize(TLV::TLVWriter & writer) const override
{
TLV::TLVType container;
ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, container));
ReturnErrorOnFailure(writer.Put(TLV::ContextTag(TagEntry::kEndpointEntryCount), count_value));
return writer.EndContainer(container);
}
CHIP_ERROR Deserialize(TLV::TLVReader & reader) override
{
ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag()));
TLV::TLVType container;
ReturnErrorOnFailure(reader.EnterContainer(container));
ReturnErrorOnFailure(reader.Next(TLV::ContextTag(TagEntry::kEndpointEntryCount)));
ReturnErrorOnFailure(reader.Get(count_value));
return reader.ExitContainer(container);
}
CHIP_ERROR Load(PersistentStorageDelegate * storage) // NOLINT(bugprone-derived-method-shadowing-base-method)
{
CHIP_ERROR err = PersistableData::Load(storage);
return err.NoErrorIf(CHIP_ERROR_NOT_FOUND); // NOT_FOUND is OK; DataAccessor::Load already called Clear()
}
};
template <class StorageId, class StorageData>
struct EndpointEntryCount : public BaseEntryCount
{
using Serializer = DefaultSerializer<StorageId, StorageData>;
EndpointId endpoint_id = kInvalidEndpointId;
EndpointEntryCount(EndpointId endpoint, uint8_t count = 0) : BaseEntryCount(count), endpoint_id(endpoint) {}
~EndpointEntryCount() {}
CHIP_ERROR UpdateKey(StorageKeyName & key) const override
{
VerifyOrReturnError(kInvalidEndpointId != endpoint_id, CHIP_ERROR_INVALID_ARGUMENT);
key = Serializer::EndpointEntryCountKey(endpoint_id);
return CHIP_NO_ERROR;
}
};
// Prevent mutations from happening in TableEntryData::Serialize
// If we just used a raw reference for TableEntryData::mEntry, C++ allows us
// to mutate mEntry.mStorageId & mEntry.mStorageData in TableEntryData::Serialize
// without having to do a const_cast; as an example, if we were to accidentally introduce
// the following code in TableEntryData::Serialize (a const method):
//
// this->mEntry->mStorageData = StorageData();
//
// If TableEntryData::mEntry is a reference, it allows this with no compilation error;
// But with ConstCorrectRef, we get a compile-time error that TableEntryData::mEntry->mStorageData
// cannot be modified because it is a const value
template <typename T>
class ConstCorrectRef
{
T & mRef;
public:
inline ConstCorrectRef(T & ref) : mRef(ref) {}
inline const T * operator->() const { return &mRef; }
inline T * operator->() { return &mRef; }
inline const T & operator*() const { return mRef; }
inline T & operator*() { return mRef; }
};
template <class StorageId, class StorageData>
struct TableEntryData : DataAccessor
{
using Serializer = DefaultSerializer<StorageId, StorageData>;
EndpointId endpoint_id = kInvalidEndpointId;
FabricIndex fabric_index = kUndefinedFabricIndex;
EntryIndex index = 0;
bool first = true;
ConstCorrectRef<StorageId> storage_id;
ConstCorrectRef<StorageData> storage_data;
TableEntryData(EndpointId endpoint, FabricIndex fabric, StorageId & id, StorageData & data, EntryIndex idx = 0) :
endpoint_id(endpoint), fabric_index(fabric), index(idx), storage_id(id), storage_data(data)
{}
CHIP_ERROR UpdateKey(StorageKeyName & key) const override
{
VerifyOrReturnError(kUndefinedFabricIndex != fabric_index, CHIP_ERROR_INVALID_FABRIC_INDEX);
VerifyOrReturnError(kInvalidEndpointId != endpoint_id, CHIP_ERROR_INVALID_ARGUMENT);
key = Serializer::FabricEntryKey(fabric_index, endpoint_id, index);
return CHIP_NO_ERROR;
}
void Clear() override { Serializer::Clear(*storage_data); }
CHIP_ERROR Serialize(TLV::TLVWriter & writer) const override
{
TLV::TLVType container;
ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, container));
ReturnErrorOnFailure(Serializer::SerializeId(writer, *storage_id));
ReturnErrorOnFailure(Serializer::SerializeData(writer, *storage_data));
return writer.EndContainer(container);
}
CHIP_ERROR Deserialize(TLV::TLVReader & reader) override
{
ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag()));
TLV::TLVType container;
ReturnErrorOnFailure(reader.EnterContainer(container));
ReturnErrorOnFailure(Serializer::DeserializeId(reader, *storage_id));
ReturnErrorOnFailure(Serializer::DeserializeData(reader, *storage_data));
return reader.ExitContainer(container);
}
};
/**
* @brief Class that holds a map to all entries in a fabric for a specific endpoint
*
* FabricEntryData is an access to a linked list of entries
*/
template <class StorageId, class StorageData, size_t kEntryMaxBytes, size_t kFabricMaxBytes, uint16_t kMaxPerFabric>
struct FabricEntryData : public PersistableData<kFabricMaxBytes>
{
using Serializer = DefaultSerializer<StorageId, StorageData>;
using TypedTableEntryData = TableEntryData<StorageId, StorageData>;
using Buffer = PersistenceBuffer<kEntryMaxBytes>;
using TypedEndpointEntryCount = EndpointEntryCount<StorageId, StorageData>;
EndpointId endpoint_id;
FabricIndex fabric_index;
uint8_t entry_count = 0;
uint16_t max_per_fabric;
uint16_t max_per_endpoint;
StorageId entry_map[kMaxPerFabric];
FabricEntryData(EndpointId endpoint = kInvalidEndpointId, FabricIndex fabric = kUndefinedFabricIndex,
uint16_t maxPerFabric = kMaxPerFabric, uint16_t maxPerEndpoint = Serializer::kMaxPerEndpoint()) :
endpoint_id(endpoint),
fabric_index(fabric), max_per_fabric(maxPerFabric), max_per_endpoint(maxPerEndpoint)
{}
CHIP_ERROR UpdateKey(StorageKeyName & key) const override
{
VerifyOrReturnError(kUndefinedFabricIndex != fabric_index, CHIP_ERROR_INVALID_FABRIC_INDEX);
VerifyOrReturnError(kInvalidEndpointId != endpoint_id, CHIP_ERROR_INVALID_ARGUMENT);
key = Serializer::FabricEntryDataKey(fabric_index, endpoint_id);
return CHIP_NO_ERROR;
}
void Clear() override
{
entry_count = 0;
for (uint16_t i = 0; i < max_per_fabric; i++)
{
entry_map[i].Clear();
}
}
CHIP_ERROR Serialize(TLV::TLVWriter & writer) const override
{
TLV::TLVType fabricEntryContainer;
ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, fabricEntryContainer));
ReturnErrorOnFailure(writer.Put(TLV::ContextTag(TagEntry::kEntryCount), entry_count));
// Storing the entry map
TLV::TLVType entryMapContainer;
ReturnErrorOnFailure(
writer.StartContainer(TLV::ContextTag(TagEntry::kStorageIdArray), TLV::kTLVType_Array, entryMapContainer));
for (uint16_t i = 0; i < max_per_fabric; i++)
{
TLV::TLVType entryIdContainer;
ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, entryIdContainer));
ReturnErrorOnFailure(Serializer::SerializeId(writer, entry_map[i]));
ReturnErrorOnFailure(writer.EndContainer(entryIdContainer));
}
ReturnErrorOnFailure(writer.EndContainer(entryMapContainer));
return writer.EndContainer(fabricEntryContainer);
}
/// @brief This Deserialize method is implemented only to allow compilation. It is not used throughout the code.
/// @param reader TLV reader
/// @return CHIP_NO_ERROR
CHIP_ERROR Deserialize(TLV::TLVReader & reader) override { return CHIP_ERROR_INCORRECT_STATE; }
/// @brief This Deserialize method checks that the recovered entries from the deserialization fit in the current max and if
/// there are too many entries in nvm, it deletes them. The method sets the deleted_entries output parameter to true if entries
/// were deleted so that the load function can know it needs to save the Fabric entry data to update the entry_count and the
/// entry map in stored memory.
/// @param reade [in] TLV reader, must be big enough to hold the entry size
/// @param storage [in] Persistent Storage Delegate, required to delete entries if the number of entries in storage is greater
/// than the maximum allowed
/// @param deleted_entries_count [out] uint8_t letting the caller (in this case the load method) know how many entries were
/// deleted so it can adjust the fabric and global entry count accordingly. Even if Deserialize fails, this value will return
/// the number of entries deleted before the failure happened.
/// @return CHIP_NO_ERROR on success, specific CHIP_ERROR otherwise
CHIP_ERROR Deserialize(TLV::TLVReader & reader, PersistentStorageDelegate & storage, uint8_t & deleted_entries_count)
{
ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag()));
TLV::TLVType fabricEntryContainer;
ReturnErrorOnFailure(reader.EnterContainer(fabricEntryContainer));
ReturnErrorOnFailure(reader.Next(TLV::ContextTag(TagEntry::kEntryCount)));
ReturnErrorOnFailure(reader.Get(entry_count));
entry_count = std::min(entry_count, static_cast<uint8_t>(max_per_fabric));
ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Array, TLV::ContextTag(TagEntry::kStorageIdArray)));
TLV::TLVType entryMapContainer;
ReturnErrorOnFailure(reader.EnterContainer(entryMapContainer));
uint16_t i = 0;
CHIP_ERROR err;
deleted_entries_count = 0;
while ((err = reader.Next(TLV::AnonymousTag())) == CHIP_NO_ERROR)
{
TLV::TLVType entryIdContainer;
if (i < max_per_fabric)
{
ReturnErrorOnFailure(reader.EnterContainer(entryIdContainer));
ReturnErrorOnFailure(Serializer::DeserializeId(reader, entry_map[i]));
ReturnErrorOnFailure(reader.ExitContainer(entryIdContainer));
}
else
{
StorageId unused;
ReturnErrorOnFailure(reader.EnterContainer(entryIdContainer));
ReturnErrorOnFailure(Serializer::DeserializeId(reader, unused));
ReturnErrorOnFailure(reader.ExitContainer(entryIdContainer));
ReturnErrorOnFailure(DeleteValue(storage, i));
deleted_entries_count++;
}
i++;
}
VerifyOrReturnError(err == CHIP_END_OF_TLV, err);
ReturnErrorOnFailure(reader.ExitContainer(entryMapContainer));
return reader.ExitContainer(fabricEntryContainer);
}
/// @brief Finds the id of the entry with the specified index
/// @return CHIP_NO_ERROR if managed to find the target entry, CHIP_ERROR_NOT_FOUND if not found
CHIP_ERROR FindByIndex(PersistentStorageDelegate & storage, EntryIndex index, StorageId & entry_id)
{
VerifyOrReturnError(entry_map[index].IsValid(), CHIP_ERROR_NOT_FOUND);
VerifyOrReturnError(kUndefinedFabricIndex != fabric_index, CHIP_ERROR_INVALID_FABRIC_INDEX);
VerifyOrReturnError(kInvalidEndpointId != endpoint_id, CHIP_ERROR_INVALID_ARGUMENT);
if (!storage.SyncDoesKeyExist(Serializer::FabricEntryKey(fabric_index, endpoint_id, index).KeyName()))
{
return CHIP_ERROR_NOT_FOUND;
}
entry_id = entry_map[index];
return CHIP_NO_ERROR;
}
/// @brief Finds the index where the current entry should be inserted by going through the endpoint's table and checking
/// whether the entry is already there. If the target is not in the table, sets idx to the first empty space
/// @param target_entry StorageId of entry to find
/// @param idx Index where target or space is found
/// @return CHIP_NO_ERROR if managed to find the target entry, CHIP_ERROR_NOT_FOUND if not found and space left
/// CHIP_ERROR_NO_MEMORY if target was not found and table is full
CHIP_ERROR Find(const StorageId & target_entry, EntryIndex & idx)
{
EntryIndex firstFreeIdx = Data::kUndefinedEntryIndex; // storage index if entry not found
uint16_t index = 0;
while (index < max_per_fabric)
{
if (entry_map[index] == target_entry)
{
idx = index;
return CHIP_NO_ERROR; // return entry at current index if entry found
}
if (!entry_map[index].IsValid() && firstFreeIdx == Data::kUndefinedEntryIndex)
{
firstFreeIdx = index;
}
index++;
}
if (firstFreeIdx < max_per_fabric)
{
idx = firstFreeIdx;
return CHIP_ERROR_NOT_FOUND;
}
return CHIP_ERROR_NO_MEMORY;
}
CHIP_ERROR SaveEntry(PersistentStorageDelegate & storage, const StorageId & id, const StorageData & data, Buffer & buffer)
{
CHIP_ERROR err = CHIP_NO_ERROR;
// Look for empty storage space
EntryIndex index;
err = this->Find(id, index);
// C++ doesn't have const constructors; variable is declared const
const TypedTableEntryData entry(endpoint_id, fabric_index, const_cast<StorageId &>(id), const_cast<StorageData &>(data),
index);
if (CHIP_NO_ERROR == err)
{
return entry.Save(&storage, buffer.BufferSpan());
}
if (CHIP_ERROR_NOT_FOUND == err) // If not found, entry.index should be the first free index
{
// Update the global entry count
TypedEndpointEntryCount endpoint_count(endpoint_id);
ReturnErrorOnFailure(endpoint_count.Load(&storage));
VerifyOrReturnError(endpoint_count.count_value < max_per_endpoint, CHIP_ERROR_NO_MEMORY);
endpoint_count.count_value++;
ReturnErrorOnFailure(endpoint_count.Save(&storage));
entry_count++;
entry_map[entry.index] = id;
err = this->Save(&storage);
if (CHIP_NO_ERROR != err)
{
endpoint_count.count_value--;
ReturnErrorOnFailure(endpoint_count.Save(&storage));
return err;
}
err = entry.Save(&storage, buffer.BufferSpan());
// on failure to save the entry, undoes the changes to Fabric Entry Data
if (CHIP_NO_ERROR != err)
{
endpoint_count.count_value--;
ReturnErrorOnFailure(endpoint_count.Save(&storage));
entry_count--;
entry_map[entry.index].Clear();
ReturnErrorOnFailure(this->Save(&storage));
return err;
}
}
return err;
}
/// @brief Removes an entry from the non-volatile memory and clears its index in the entry map. Decreases the number of entries
/// in the global entry count and in the entry fabric data if successful. As the entry map size is not compressed upon removal,
/// this only clears the entry corresponding to the entry from the entry map.
/// @param storage Storage delegate to access the entry
/// @param entry_id Entry to remove
/// @return CHIP_NO_ERROR if successful, specific CHIP_ERROR otherwise
CHIP_ERROR RemoveEntry(PersistentStorageDelegate & storage, const StorageId & entry_id)
{
CHIP_ERROR err = CHIP_NO_ERROR;
EntryIndex entryIndex;
// Empty Entry Fabric Data returns CHIP_NO_ERROR on remove
if (entry_count > 0)
{
// If Find doesn't return CHIP_NO_ERROR, the entry wasn't found, which doesn't return an error
VerifyOrReturnValue(this->Find(entry_id, entryIndex) == CHIP_NO_ERROR, CHIP_NO_ERROR);
// Update the global entry count
TypedEndpointEntryCount endpoint_entry_count(endpoint_id);
ReturnErrorOnFailure(endpoint_entry_count.Load(&storage));
endpoint_entry_count.count_value--;
ReturnErrorOnFailure(endpoint_entry_count.Save(&storage));
entry_count--;
entry_map[entryIndex].Clear();
err = this->Save(&storage);
// On failure to update the entry map, undo the global count modification
if (CHIP_NO_ERROR != err)
{
endpoint_entry_count.count_value++;
ReturnErrorOnFailure(endpoint_entry_count.Save(&storage));
return err;
}
err = DeleteValue(storage, entryIndex);
// On failure to delete entry, undo the change to the Fabric Entry Data and the global entry count
if (CHIP_NO_ERROR != err)
{
endpoint_entry_count.count_value++;
ReturnErrorOnFailure(endpoint_entry_count.Save(&storage));
entry_count++;
entry_map[entryIndex] = entry_id;
ReturnErrorOnFailure(this->Save(&storage));
return err;
}
}
return err;
}
CHIP_ERROR Load(PersistentStorageDelegate * storage) // NOLINT(bugprone-derived-method-shadowing-base-method)
{
VerifyOrReturnError(nullptr != storage, CHIP_ERROR_INVALID_ARGUMENT);
uint8_t deleted_entries_count = 0;
uint8_t buffer[kFabricMaxBytes] = { 0 };
StorageKeyName key = StorageKeyName::Uninitialized();
// Set data to defaults
Clear();
// Update storage key
ReturnErrorOnFailure(UpdateKey(key));
// Load the serialized data
uint16_t size = static_cast<uint16_t>(sizeof(buffer));
CHIP_ERROR err = storage->SyncGetKeyValue(key.KeyName(), buffer, size);
VerifyOrReturnError(CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND != err, CHIP_ERROR_NOT_FOUND);
ReturnErrorOnFailure(err);
// Decode serialized data
TLV::TLVReader reader;
reader.Init(buffer, size);
err = Deserialize(reader, *storage, deleted_entries_count);
// If Deserialize sets the "deleted_entries" variable, the table in flash memory held too many entries (can happen
// if max_per_fabric was reduced during an OTA) and was adjusted during deserializing . The fabric data must then
// be updated
if (deleted_entries_count)
{
TypedEndpointEntryCount global_count(endpoint_id);
ReturnErrorOnFailure(global_count.Load(storage));
global_count.count_value = static_cast<uint8_t>(global_count.count_value - deleted_entries_count);
ReturnErrorOnFailure(global_count.Save(storage));
ReturnErrorOnFailure(this->Save(storage));
}
return err;
}
private:
CHIP_ERROR DeleteValue(PersistentStorageDelegate & storage, EntryIndex index)
{
StorageKeyName key = Serializer::FabricEntryKey(fabric_index, endpoint_id, index);
return storage.SyncDeleteKeyValue(key.KeyName());
}
};
template <class StorageId, class StorageData>
CHIP_ERROR FabricTableImpl<StorageId, StorageData>::Init(PersistentStorageDelegate & storage)
{
// Verify the initialized parameter respects the maximum allowed values for entry capacity
VerifyOrReturnError(mMaxPerFabric <= Serializer::kMaxPerFabric() && mMaxPerEndpoint <= Serializer::kMaxPerEndpoint(),
CHIP_ERROR_INVALID_INTEGER_VALUE);
this->mStorage = &storage;
return CHIP_NO_ERROR;
}
template <class StorageId, class StorageData>
void FabricTableImpl<StorageId, StorageData>::Finish()
{}
template <class StorageId, class StorageData>
CHIP_ERROR FabricTableImpl<StorageId, StorageData>::GetFabricEntryCount(FabricIndex fabric_index, uint8_t & entry_count)
{
using TypedFabricEntryData = FabricEntryData<StorageId, StorageData, Serializer::kEntryMaxBytes(),
Serializer::kFabricMaxBytes(), Serializer::kMaxPerFabric()>;
VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INTERNAL);
TypedFabricEntryData fabric(mEndpointId, fabric_index);
CHIP_ERROR err = fabric.Load(mStorage);
VerifyOrReturnError(CHIP_NO_ERROR == err || CHIP_ERROR_NOT_FOUND == err, err);
entry_count = (CHIP_ERROR_NOT_FOUND == err) ? 0 : fabric.entry_count;
return CHIP_NO_ERROR;
}
template <class StorageId, class StorageData>
CHIP_ERROR FabricTableImpl<StorageId, StorageData>::GetEndpointEntryCount(uint8_t & entry_count)
{
using TypedEndpointEntryCount = EndpointEntryCount<StorageId, StorageData>;
VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INTERNAL);
TypedEndpointEntryCount endpoint_entry_count(mEndpointId);
ReturnErrorOnFailure(endpoint_entry_count.Load(mStorage));
entry_count = endpoint_entry_count.count_value;
return CHIP_NO_ERROR;
}
template <class StorageId, class StorageData>
CHIP_ERROR FabricTableImpl<StorageId, StorageData>::SetEndpointEntryCount(const uint8_t & entry_count)
{
using TypedEndpointEntryCount = EndpointEntryCount<StorageId, StorageData>;
VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INTERNAL);
TypedEndpointEntryCount endpoint_entry_count(mEndpointId, entry_count);
return endpoint_entry_count.Save(mStorage);
}
template <class StorageId, class StorageData>
CHIP_ERROR FabricTableImpl<StorageId, StorageData>::GetRemainingCapacity(FabricIndex fabric_index, uint8_t & capacity)
{
using TypedFabricEntryData = FabricEntryData<StorageId, StorageData, Serializer::kEntryMaxBytes(),
Serializer::kFabricMaxBytes(), Serializer::kMaxPerFabric()>;
VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INTERNAL);
uint8_t endpoint_entry_count = 0;
ReturnErrorOnFailure(GetEndpointEntryCount(endpoint_entry_count));
// If the global entry count is higher than the maximal Global entry capacity, this returns a capacity of 0 until enough entries
// have been deleted to bring the global number of entries under the global maximum.
if (endpoint_entry_count > mMaxPerEndpoint)
{
capacity = 0;
return CHIP_NO_ERROR;
}
uint8_t remaining_capacity_global = static_cast<uint8_t>(mMaxPerEndpoint - endpoint_entry_count);
uint8_t remaining_capacity_fabric = static_cast<uint8_t>(mMaxPerFabric);
TypedFabricEntryData fabric(mEndpointId, fabric_index);
// Load fabric data (defaults to zero)TypedFabricEntryData
CHIP_ERROR err = fabric.Load(mStorage);
VerifyOrReturnError(CHIP_NO_ERROR == err || CHIP_ERROR_NOT_FOUND == err, err);
if (err == CHIP_NO_ERROR)
{
remaining_capacity_fabric = static_cast<uint8_t>(mMaxPerFabric - fabric.entry_count);
}
capacity = std::min(remaining_capacity_fabric, remaining_capacity_global);
return CHIP_NO_ERROR;
}
template <class StorageId, class StorageData>
template <size_t kEntryMaxBytes>
CHIP_ERROR FabricTableImpl<StorageId, StorageData>::SetTableEntry(FabricIndex fabric_index, const StorageId & id,
const StorageData & data,
PersistenceBuffer<kEntryMaxBytes> & writeBuffer)
{
using TypedFabricEntryData = FabricEntryData<StorageId, StorageData, Serializer::kEntryMaxBytes(),
Serializer::kFabricMaxBytes(), Serializer::kMaxPerFabric()>;
VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INTERNAL);
TypedFabricEntryData fabric(mEndpointId, fabric_index, mMaxPerFabric, mMaxPerEndpoint);
// Load fabric data (defaults to zero)
CHIP_ERROR err = fabric.Load(mStorage);
VerifyOrReturnError(CHIP_NO_ERROR == err || CHIP_ERROR_NOT_FOUND == err, err);
err = fabric.SaveEntry(*mStorage, id, data, writeBuffer);
return err;
}
template <class StorageId, class StorageData>
template <size_t kEntryMaxBytes>
CHIP_ERROR FabricTableImpl<StorageId, StorageData>::GetTableEntry(FabricIndex fabric_index, StorageId & entry_id,
StorageData & data, PersistenceBuffer<kEntryMaxBytes> & buffer)
{
using TypedFabricEntryData = FabricEntryData<StorageId, StorageData, Serializer::kEntryMaxBytes(),
Serializer::kFabricMaxBytes(), Serializer::kMaxPerFabric()>;
VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INTERNAL);
TypedFabricEntryData fabric(mEndpointId, fabric_index, mMaxPerFabric, mMaxPerEndpoint);
TableEntryData<StorageId, StorageData> table_entry(mEndpointId, fabric_index, entry_id, data);
ReturnErrorOnFailure(fabric.Load(mStorage));
VerifyOrReturnError(fabric.Find(entry_id, table_entry.index) == CHIP_NO_ERROR, CHIP_ERROR_NOT_FOUND);
CHIP_ERROR err = table_entry.Load(mStorage, buffer.BufferSpan());
// If entry.Load returns "buffer too small", the entry in memory is too big to be retrieved (this could happen if the
// kEntryMaxBytes was reduced by OTA) and therefore must be deleted as is is no longer considered accessible.
if (err == CHIP_ERROR_BUFFER_TOO_SMALL)
{
ReturnErrorOnFailure(this->RemoveTableEntry(fabric_index, entry_id));
}
ReturnErrorOnFailure(err);
return CHIP_NO_ERROR;
}
template <class StorageId, class StorageData>
CHIP_ERROR FabricTableImpl<StorageId, StorageData>::FindTableEntry(FabricIndex fabric_index, const StorageId & entry_id,
EntryIndex & idx)
{
using TypedFabricEntryData = FabricEntryData<StorageId, StorageData, Serializer::kEntryMaxBytes(),
Serializer::kFabricMaxBytes(), Serializer::kMaxPerFabric()>;
VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INTERNAL);
TypedFabricEntryData fabric(mEndpointId, fabric_index, mMaxPerFabric, mMaxPerEndpoint);
ReturnErrorOnFailure(fabric.Load(mStorage));
VerifyOrReturnError(fabric.Find(entry_id, idx) == CHIP_NO_ERROR, CHIP_ERROR_NOT_FOUND);
return CHIP_NO_ERROR;
}
template <class StorageId, class StorageData>
CHIP_ERROR FabricTableImpl<StorageId, StorageData>::RemoveTableEntry(FabricIndex fabric_index, const StorageId & entry_id)
{
using TypedFabricEntryData = FabricEntryData<StorageId, StorageData, Serializer::kEntryMaxBytes(),
Serializer::kFabricMaxBytes(), Serializer::kMaxPerFabric()>;
VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INTERNAL);
TypedFabricEntryData fabric(mEndpointId, fabric_index, mMaxPerFabric, mMaxPerEndpoint);
ReturnErrorOnFailure(fabric.Load(mStorage));
return fabric.RemoveEntry(*mStorage, entry_id);
}
/// @brief This function is meant to provide a way to empty the entry table without knowing any specific entry Id. Outside of this
/// specific use case, RemoveTableEntry should be used.
/// @param fabric_index Fabric in which the entry belongs
/// @param entry_idx Position in the Table
/// @return CHIP_NO_ERROR if removal was successful, errors if failed to remove the entry or to update the fabric after removing it
template <class StorageId, class StorageData>
CHIP_ERROR FabricTableImpl<StorageId, StorageData>::RemoveTableEntryAtPosition(EndpointId endpoint, FabricIndex fabric_index,
EntryIndex entry_idx)
{
using TypedFabricEntryData = FabricEntryData<StorageId, StorageData, Serializer::kEntryMaxBytes(),
Serializer::kFabricMaxBytes(), Serializer::kMaxPerFabric()>;
VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INTERNAL);
TypedFabricEntryData fabric(endpoint, fabric_index, mMaxPerFabric, mMaxPerEndpoint);
ReturnErrorOnFailure(fabric.Load(mStorage));
StorageId entryId;
CHIP_ERROR err = fabric.FindByIndex(*mStorage, entry_idx, entryId);
VerifyOrReturnValue(CHIP_ERROR_NOT_FOUND != err, CHIP_NO_ERROR);
ReturnErrorOnFailure(err);
return fabric.RemoveEntry(*mStorage, entryId);
}
template <class StorageId, class StorageData>
CHIP_ERROR FabricTableImpl<StorageId, StorageData>::RemoveFabric(DataModel::ProviderMetadataTree & provider,
FabricIndex fabric_index)
{
using TypedFabricEntryData = FabricEntryData<StorageId, StorageData, Serializer::kEntryMaxBytes(),
Serializer::kFabricMaxBytes(), Serializer::kMaxPerFabric()>;
VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INTERNAL);
ReadOnlyBufferBuilder<DataModel::EndpointEntry> endpointsBuilder;
ReturnErrorOnFailure(provider.Endpoints(endpointsBuilder));
for (const auto & ep : endpointsBuilder.TakeBuffer())
{
EndpointId endpoint = ep.id;
TypedFabricEntryData fabric(endpoint, fabric_index);
EntryIndex idx = 0;
CHIP_ERROR err = fabric.Load(mStorage);
VerifyOrReturnError(CHIP_NO_ERROR == err || CHIP_ERROR_NOT_FOUND == err, err);
if (CHIP_ERROR_NOT_FOUND == err)
{
continue;
}
while (idx < mMaxPerFabric)
{
err = RemoveTableEntryAtPosition(endpoint, fabric_index, idx);
VerifyOrReturnError(CHIP_NO_ERROR == err || CHIP_ERROR_NOT_FOUND == err, err);
idx++;
}
// Remove fabric entries on endpoint
ReturnErrorOnFailure(fabric.Delete(mStorage));
}
return CHIP_NO_ERROR;
}
template <class StorageId, class StorageData>
CHIP_ERROR FabricTableImpl<StorageId, StorageData>::RemoveEndpoint()
{
using TypedFabricEntryData = FabricEntryData<StorageId, StorageData, Serializer::kEntryMaxBytes(),
Serializer::kFabricMaxBytes(), Serializer::kMaxPerFabric()>;
VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INTERNAL);
for (FabricIndex fabric_index = kMinValidFabricIndex; fabric_index < kMaxValidFabricIndex; fabric_index++)
{
TypedFabricEntryData fabric(mEndpointId, fabric_index);
CHIP_ERROR err = fabric.Load(mStorage);
VerifyOrReturnError(CHIP_NO_ERROR == err || CHIP_ERROR_NOT_FOUND == err, err);
if (CHIP_ERROR_NOT_FOUND == err)
{
continue;
}
EntryIndex idx = 0;
while (idx < mMaxPerFabric)
{
err = RemoveTableEntryAtPosition(mEndpointId, fabric_index, idx);
VerifyOrReturnError(CHIP_NO_ERROR == err || CHIP_ERROR_NOT_FOUND == err, err);
idx++;
};
// Remove fabric entries on endpoint
ReturnErrorOnFailure(fabric.Delete(mStorage));
}
return CHIP_NO_ERROR;
}
template <class StorageId, class StorageData>
void FabricTableImpl<StorageId, StorageData>::SetEndpoint(EndpointId endpoint)
{
mEndpointId = endpoint;
}
template <class StorageId, class StorageData>
void FabricTableImpl<StorageId, StorageData>::SetTableSize(uint16_t endpointTableSize, uint16_t maxPerFabric)
{
// Verify the endpoint passed size respects the limits of the device configuration
VerifyOrDie(Serializer::kMaxPerFabric() > 0);
VerifyOrDie(Serializer::kMaxPerEndpoint() > 0);
mMaxPerEndpoint = std::min(Serializer::kMaxPerEndpoint(), endpointTableSize);
mMaxPerFabric = std::min(endpointTableSize, std::min(Serializer::kMaxPerFabric(), maxPerFabric));
}
template <class StorageId, class StorageData>
template <size_t kEntryMaxBytes, class UnaryFunc>
CHIP_ERROR FabricTableImpl<StorageId, StorageData>::IterateEntries(FabricIndex fabric, PersistenceBuffer<kEntryMaxBytes> & buffer,
UnaryFunc iterateFn)
{
VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INTERNAL);
EntryIteratorImpl<kEntryMaxBytes> iterator(*this, fabric, mEndpointId, mMaxPerFabric, mMaxPerEndpoint, buffer);
return iterateFn(iterator);
}
template <class StorageId, class StorageData>
template <size_t kEntryMaxBytes>
FabricTableImpl<StorageId, StorageData>::EntryIteratorImpl<kEntryMaxBytes>::EntryIteratorImpl(
FabricTableImpl & provider, FabricIndex fabricIdx, EndpointId endpoint, uint16_t maxPerFabric, uint16_t maxPerEndpoint,
PersistenceBuffer<kEntryMaxBytes> & buffer) :
mProvider(provider),
mBuffer(buffer), mFabric(fabricIdx), mEndpoint(endpoint), mMaxPerFabric(maxPerFabric), mMaxPerEndpoint(maxPerEndpoint)
{
using TypedFabricEntryData = FabricEntryData<StorageId, StorageData, Serializer::kEntryMaxBytes(),
Serializer::kFabricMaxBytes(), Serializer::kMaxPerFabric()>;
TypedFabricEntryData fabric(mEndpoint, fabricIdx, mMaxPerFabric, mMaxPerEndpoint);
ReturnOnFailure(fabric.Load(provider.mStorage));
mTotalEntries = fabric.entry_count;
mEntryIndex = 0;
}
template <class StorageId, class StorageData>
template <size_t kEntryMaxBytes>
size_t FabricTableImpl<StorageId, StorageData>::EntryIteratorImpl<kEntryMaxBytes>::Count()
{
return mTotalEntries;
}
template <class StorageId, class StorageData>
template <size_t kEntryMaxBytes>
bool FabricTableImpl<StorageId, StorageData>::EntryIteratorImpl<kEntryMaxBytes>::Next(TableEntry & output)
{
using TypedFabricEntryData = FabricEntryData<StorageId, StorageData, Serializer::kEntryMaxBytes(),
Serializer::kFabricMaxBytes(), Serializer::kMaxPerFabric()>;
TypedFabricEntryData fabric(mEndpoint, mFabric);
VerifyOrReturnError(fabric.Load(mProvider.mStorage) == CHIP_NO_ERROR, false);
// looks for next available entry
while (mEntryIndex < mMaxPerFabric)
{
if (fabric.entry_map[mEntryIndex].IsValid())
{
TableEntryData<StorageId, StorageData> entry(mEndpoint, mFabric, output.mStorageId, output.mStorageData, mEntryIndex);
VerifyOrReturnError(entry.Load(mProvider.mStorage, mBuffer.BufferSpan()) == CHIP_NO_ERROR, false);
mEntryIndex++;
return true;
}
mEntryIndex++;
}
return false;
}
template <class StorageId, class StorageData>
template <size_t kEntryMaxBytes>
void FabricTableImpl<StorageId, StorageData>::EntryIteratorImpl<kEntryMaxBytes>::Release()
{}
} // namespace Storage
} // namespace app
} // namespace chip