blob: 7ca3c4ba18aace08017942eda242146ee3ea17a9 [file] [log] [blame]
/**
*
* Copyright (c) 2020 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.
*/
/**
* @file Basic implementation of a binding table.
*/
#include <app/util/binding-table.h>
#include <app/util/config.h>
namespace chip {
BindingTable BindingTable::sInstance;
BindingTable::BindingTable()
{
memset(mNextIndex, kNextNullIndex, sizeof(mNextIndex));
}
CHIP_ERROR BindingTable::Add(const EmberBindingTableEntry & entry)
{
if (entry.type == MATTER_UNUSED_BINDING)
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
uint8_t newIndex = GetNextAvaiableIndex();
if (newIndex >= MATTER_BINDING_TABLE_SIZE)
{
return CHIP_ERROR_NO_MEMORY;
}
mBindingTable[newIndex] = entry;
CHIP_ERROR error = SaveEntryToStorage(newIndex, kNextNullIndex);
if (error == CHIP_NO_ERROR)
{
if (mTail == kNextNullIndex)
{
error = SaveListInfo(newIndex);
}
else
{
error = SaveEntryToStorage(mTail, newIndex);
}
if (error != CHIP_NO_ERROR)
{
mStorage->SyncDeleteKeyValue(DefaultStorageKeyAllocator::BindingTableEntry(newIndex).KeyName());
}
}
if (error != CHIP_NO_ERROR)
{
// Roll back
mBindingTable[newIndex].type = MATTER_UNUSED_BINDING;
return error;
}
if (mTail == kNextNullIndex)
{
mTail = newIndex;
mHead = newIndex;
}
else
{
mNextIndex[mTail] = newIndex;
mNextIndex[newIndex] = kNextNullIndex;
mTail = newIndex;
}
mSize++;
return CHIP_NO_ERROR;
}
const EmberBindingTableEntry & BindingTable::GetAt(uint8_t index)
{
return mBindingTable[index];
}
CHIP_ERROR BindingTable::SaveEntryToStorage(uint8_t index, uint8_t nextIndex)
{
EmberBindingTableEntry & entry = mBindingTable[index];
uint8_t buffer[kEntryStorageSize] = { 0 };
TLV::TLVWriter writer;
writer.Init(buffer);
TLV::TLVType container;
ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::TLVType::kTLVType_Structure, container));
ReturnErrorOnFailure(writer.Put(TLV::ContextTag(kTagFabricIndex), entry.fabricIndex));
ReturnErrorOnFailure(writer.Put(TLV::ContextTag(kTagLocalEndpoint), entry.local));
if (entry.clusterId.has_value())
{
ReturnErrorOnFailure(writer.Put(TLV::ContextTag(kTagCluster), *entry.clusterId));
}
if (entry.type == MATTER_UNICAST_BINDING)
{
ReturnErrorOnFailure(writer.Put(TLV::ContextTag(kTagRemoteEndpoint), entry.remote));
ReturnErrorOnFailure(writer.Put(TLV::ContextTag(kTagNodeId), entry.nodeId));
}
else
{
ReturnErrorOnFailure(writer.Put(TLV::ContextTag(kTagGroupId), entry.groupId));
}
ReturnErrorOnFailure(writer.Put(TLV::ContextTag(kTagNextEntry), nextIndex));
ReturnErrorOnFailure(writer.EndContainer(container));
ReturnErrorOnFailure(writer.Finalize());
return mStorage->SyncSetKeyValue(DefaultStorageKeyAllocator::BindingTableEntry(index).KeyName(), buffer,
static_cast<uint16_t>(writer.GetLengthWritten()));
}
CHIP_ERROR BindingTable::SaveListInfo(uint8_t head)
{
uint8_t buffer[kListInfoStorageSize] = { 0 };
TLV::TLVWriter writer;
writer.Init(buffer);
TLV::TLVType container;
ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::TLVType::kTLVType_Structure, container));
ReturnErrorOnFailure(writer.Put(TLV::ContextTag(kTagStorageVersion), kStorageVersion));
ReturnErrorOnFailure(writer.Put(TLV::ContextTag(kTagHead), head));
ReturnErrorOnFailure(writer.EndContainer(container));
ReturnErrorOnFailure(writer.Finalize());
return mStorage->SyncSetKeyValue(DefaultStorageKeyAllocator::BindingTable().KeyName(), buffer,
static_cast<uint16_t>(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(DefaultStorageKeyAllocator::BindingTable().KeyName(), buffer, size));
TLV::TLVReader reader;
reader.Init(buffer, size);
ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag()));
TLV::TLVType container;
ReturnErrorOnFailure(reader.EnterContainer(container));
ReturnErrorOnFailure(reader.Next(TLV::ContextTag(kTagStorageVersion)));
uint32_t version;
ReturnErrorOnFailure(reader.Get(version));
VerifyOrReturnError(version == kStorageVersion, CHIP_ERROR_VERSION_MISMATCH);
ReturnErrorOnFailure(reader.Next(TLV::ContextTag(kTagHead)));
uint8_t index;
ReturnErrorOnFailure(reader.Get(index));
mHead = index;
while (index != kNextNullIndex)
{
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(DefaultStorageKeyAllocator::BindingTableEntry(index).KeyName(), buffer, size));
TLV::TLVReader reader;
reader.Init(buffer, size);
ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag()));
TLV::TLVType container;
ReturnErrorOnFailure(reader.EnterContainer(container));
ReturnErrorOnFailure(reader.Next(TLV::ContextTag(kTagFabricIndex)));
ReturnErrorOnFailure(reader.Get(entry.fabricIndex));
ReturnErrorOnFailure(reader.Next(TLV::ContextTag(kTagLocalEndpoint)));
ReturnErrorOnFailure(reader.Get(entry.local));
ReturnErrorOnFailure(reader.Next());
if (reader.GetTag() == TLV::ContextTag(kTagCluster))
{
ClusterId clusterId;
ReturnErrorOnFailure(reader.Get(clusterId));
entry.clusterId.emplace(clusterId);
ReturnErrorOnFailure(reader.Next());
}
else
{
entry.clusterId = std::nullopt;
}
if (reader.GetTag() == TLV::ContextTag(kTagRemoteEndpoint))
{
entry.type = MATTER_UNICAST_BINDING;
ReturnErrorOnFailure(reader.Get(entry.remote));
ReturnErrorOnFailure(reader.Next(TLV::ContextTag(kTagNodeId)));
ReturnErrorOnFailure(reader.Get(entry.nodeId));
}
else
{
entry.type = MATTER_MULTICAST_BINDING;
VerifyOrReturnError(reader.GetTag() == TLV::ContextTag(kTagGroupId), CHIP_ERROR_INVALID_TLV_TAG);
ReturnErrorOnFailure(reader.Get(entry.groupId));
}
ReturnErrorOnFailure(reader.Next(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)
{
mTail = iter.mPrevIndex;
}
uint8_t next = mNextIndex[iter.mIndex];
if (iter.mIndex != mHead)
{
error = SaveEntryToStorage(iter.mPrevIndex, next);
if (error == CHIP_NO_ERROR)
{
mNextIndex[iter.mPrevIndex] = next;
}
}
else
{
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(DefaultStorageKeyAllocator::BindingTableEntry(iter.mIndex).KeyName()) != CHIP_NO_ERROR)
{
ChipLogError(AppServer, "Failed to remove binding table entry %u from storage", iter.mIndex);
}
mBindingTable[iter.mIndex].type = MATTER_UNUSED_BINDING;
mNextIndex[iter.mIndex] = kNextNullIndex;
mSize--;
}
iter.mIndex = next;
return error;
}
BindingTable::Iterator BindingTable::begin()
{
Iterator iter;
iter.mTable = this;
iter.mPrevIndex = kNextNullIndex;
iter.mIndex = mHead;
return iter;
}
BindingTable::Iterator BindingTable::end()
{
Iterator iter;
iter.mTable = this;
iter.mIndex = kNextNullIndex;
return iter;
}
uint8_t BindingTable::GetNextAvaiableIndex()
{
for (uint8_t i = 0; i < MATTER_BINDING_TABLE_SIZE; i++)
{
if (mBindingTable[i].type == MATTER_UNUSED_BINDING)
{
return i;
}
}
return MATTER_BINDING_TABLE_SIZE;
}
BindingTable::Iterator BindingTable::Iterator::operator++()
{
if (mIndex != kNextNullIndex)
{
mPrevIndex = mIndex;
mIndex = mTable->mNextIndex[mIndex];
}
return *this;
}
} // namespace chip