blob: 0023909334ea27555fb3c8ef8c86f6e307b08eb3 [file]
/*
*
* 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 <lib/core/StringBuilderAdapters.h>
#include <pw_unit_test/framework.h>
#include <app/clusters/bindings/binding-table.h>
#include <lib/core/TLV.h>
#include <lib/support/DefaultStorageKeyAllocator.h>
#include <lib/support/TestPersistentStorageDelegate.h>
using namespace chip::app::Clusters;
namespace {
void VerifyTableSame(Binding::Table & table, const std::vector<Binding::TableEntry> & expected)
{
ASSERT_EQ(table.Size(), expected.size());
auto iter1 = table.begin();
auto iter2 = expected.begin();
while (iter2 != expected.end())
{
EXPECT_EQ(*iter1, *iter2);
++iter1;
++iter2;
}
EXPECT_EQ(iter1, table.end());
}
void VerifyRestored(chip::TestPersistentStorageDelegate & storage, const std::vector<Binding::TableEntry> & expected)
{
Binding::Table restoredTable;
restoredTable.SetPersistentStorage(&storage);
EXPECT_EQ(restoredTable.LoadFromStorage(), CHIP_NO_ERROR);
VerifyTableSame(restoredTable, expected);
}
TEST(TestBindingTable, TestEmptyBindingTable)
{
Binding::Table table;
chip::TestPersistentStorageDelegate testStorage;
table.SetPersistentStorage(&testStorage);
EXPECT_EQ(table.Size(), 0u);
EXPECT_EQ(table.begin(), table.end());
}
TEST(TestBindingTable, TestAdd)
{
Binding::Table table;
chip::TestPersistentStorageDelegate testStorage;
table.SetPersistentStorage(&testStorage);
Binding::TableEntry unusedEntry;
unusedEntry.type = Binding::MATTER_UNUSED_BINDING;
EXPECT_EQ(table.Add(unusedEntry), CHIP_ERROR_INVALID_ARGUMENT);
for (uint8_t i = 0; i < Binding::Table::kMaxBindingEntries; i++)
{
EXPECT_EQ(table.Add(Binding::TableEntry::ForNode(0, i, 0, 0, std::nullopt)), CHIP_NO_ERROR);
}
EXPECT_EQ(table.Add(Binding::TableEntry::ForNode(0, 0, 0, 0, std::nullopt)), CHIP_ERROR_NO_MEMORY);
EXPECT_EQ(table.Size(), Binding::Table::kMaxBindingEntries);
auto iter = table.begin();
for (uint8_t i = 0; i < Binding::Table::kMaxBindingEntries; i++)
{
EXPECT_NE(iter, table.end());
EXPECT_EQ(iter->nodeId, i);
EXPECT_EQ(iter.GetIndex(), i);
++iter;
}
EXPECT_EQ(iter, table.end());
}
TEST(TestBindingTable, TestRemoveThenAdd)
{
Binding::Table table;
chip::TestPersistentStorageDelegate testStorage;
table.SetPersistentStorage(&testStorage);
EXPECT_EQ(table.Add(Binding::TableEntry::ForNode(0, 0, 0, 0, std::nullopt)), CHIP_NO_ERROR);
auto iter = table.begin();
EXPECT_EQ(table.RemoveAt(iter), CHIP_NO_ERROR);
EXPECT_EQ(iter, table.end());
EXPECT_EQ(table.Size(), 0u);
EXPECT_EQ(table.begin(), table.end());
for (uint8_t i = 0; i < Binding::Table::kMaxBindingEntries; i++)
{
EXPECT_EQ(table.Add(Binding::TableEntry::ForNode(0, i, 0, 0, std::nullopt)), CHIP_NO_ERROR);
}
iter = table.begin();
++iter;
EXPECT_EQ(table.RemoveAt(iter), CHIP_NO_ERROR);
EXPECT_EQ(table.Size(), Binding::Table::kMaxBindingEntries - 1);
EXPECT_EQ(iter->nodeId, 2u);
EXPECT_EQ(iter.GetIndex(), 2u);
auto iterCheck = table.begin();
++iterCheck;
EXPECT_EQ(iter, iterCheck);
EXPECT_EQ(table.Add(Binding::TableEntry::ForNode(0, 1, 0, 0, std::nullopt)), CHIP_NO_ERROR);
EXPECT_EQ(table.Size(), Binding::Table::kMaxBindingEntries);
iter = table.begin();
for (uint8_t i = 0; i < Binding::Table::kMaxBindingEntries - 1; i++)
{
++iter;
}
EXPECT_EQ(iter->nodeId, 1u);
EXPECT_EQ(iter.GetIndex(), 1u);
++iter;
EXPECT_EQ(iter, table.end());
iter = table.begin();
EXPECT_EQ(table.RemoveAt(iter), CHIP_NO_ERROR);
EXPECT_EQ(table.Size(), Binding::Table::kMaxBindingEntries - 1);
EXPECT_EQ(iter, table.begin());
EXPECT_EQ(iter.GetIndex(), 2u);
EXPECT_EQ(iter->nodeId, 2u);
EXPECT_EQ(table.GetAt(0).type, Binding::MATTER_UNUSED_BINDING);
}
TEST(TestBindingTable, TestPersistentStorage)
{
chip::TestPersistentStorageDelegate testStorage;
Binding::Table table;
chip::Optional<chip::ClusterId> cluster = chip::MakeOptional<chip::ClusterId>(static_cast<chip::ClusterId>(UINT16_MAX + 6));
std::vector<Binding::TableEntry> expected = {
Binding::TableEntry::ForNode(0, 0, 0, 0, std::nullopt),
Binding::TableEntry::ForNode(1, 1, 0, 0, cluster.std_optional()),
Binding::TableEntry::ForGroup(2, 2, 0, std::nullopt),
Binding::TableEntry::ForGroup(3, 3, 0, cluster.std_optional()),
};
table.SetPersistentStorage(&testStorage);
EXPECT_EQ(table.Add(expected[0]), CHIP_NO_ERROR);
EXPECT_EQ(table.Add(expected[1]), CHIP_NO_ERROR);
EXPECT_EQ(table.Add(expected[2]), CHIP_NO_ERROR);
EXPECT_EQ(table.Add(expected[3]), CHIP_NO_ERROR);
VerifyRestored(testStorage, expected);
// Verify storage untouched if add fails
testStorage.AddPoisonKey(chip::DefaultStorageKeyAllocator::BindingTableEntry(4).KeyName());
EXPECT_NE(table.Add(Binding::TableEntry::ForNode(4, 4, 0, 0, std::nullopt)), CHIP_NO_ERROR);
VerifyRestored(testStorage, expected);
testStorage.ClearPoisonKeys();
// Verify storage untouched if removing head fails
testStorage.AddPoisonKey(chip::DefaultStorageKeyAllocator::BindingTable().KeyName());
auto iter = table.begin();
EXPECT_NE(table.RemoveAt(iter), CHIP_NO_ERROR);
VerifyTableSame(table, expected);
testStorage.ClearPoisonKeys();
VerifyRestored(testStorage, expected);
// Verify storage untouched if removing other nodes fails
testStorage.AddPoisonKey(chip::DefaultStorageKeyAllocator::BindingTableEntry(0).KeyName());
iter = table.begin();
++iter;
EXPECT_NE(table.RemoveAt(iter), CHIP_NO_ERROR);
VerifyTableSame(table, expected);
testStorage.ClearPoisonKeys();
VerifyRestored(testStorage, expected);
// Verify removing head
iter = table.begin();
EXPECT_EQ(table.RemoveAt(iter), CHIP_NO_ERROR);
VerifyTableSame(table, { expected[1], expected[2], expected[3] });
VerifyRestored(testStorage, { expected[1], expected[2], expected[3] });
// Verify removing other nodes
++iter;
EXPECT_EQ(table.RemoveAt(iter), CHIP_NO_ERROR);
VerifyTableSame(table, { expected[1], expected[3] });
VerifyRestored(testStorage, { expected[1], expected[3] });
}
TEST(TestBindingTable, TestLoadRejectsOutOfRangeIndex)
{
chip::TestPersistentStorageDelegate testStorage;
// Populate one valid entry so a well-formed serialized entry blob exists in storage.
{
Binding::Table table;
table.SetPersistentStorage(&testStorage);
EXPECT_EQ(table.Add(Binding::TableEntry::ForNode(0, 0, 0, 0, std::nullopt)), CHIP_NO_ERROR);
}
// One past the last valid slot; mBindingTable / mNextIndex are sized kMaxBindingEntries.
constexpr uint8_t kOutOfRangeIndex = static_cast<uint8_t>(Binding::Table::kMaxBindingEntries);
// Copy the serialized entry to the out-of-range index key so a full entry parses and the load reaches
// the mBindingTable[index] write.
{
uint8_t entryBuffer[128];
uint16_t entrySize = sizeof(entryBuffer);
EXPECT_EQ(
testStorage.SyncGetKeyValue(chip::DefaultStorageKeyAllocator::BindingTableEntry(0).KeyName(), entryBuffer, entrySize),
CHIP_NO_ERROR);
EXPECT_EQ(testStorage.SyncSetKeyValue(chip::DefaultStorageKeyAllocator::BindingTableEntry(kOutOfRangeIndex).KeyName(),
entryBuffer, entrySize),
CHIP_NO_ERROR);
}
// Point the stored list head at the out-of-range index. Layout mirrors Table::SaveListInfo(): an
// anonymous structure of { storage version (context tag 1) = 1, head (context tag 2) }.
{
uint8_t listInfoBuffer[32];
chip::TLV::TLVWriter writer;
writer.Init(listInfoBuffer);
chip::TLV::TLVType container;
EXPECT_EQ(writer.StartContainer(chip::TLV::AnonymousTag(), chip::TLV::kTLVType_Structure, container), CHIP_NO_ERROR);
EXPECT_EQ(writer.Put(chip::TLV::ContextTag(1), static_cast<uint32_t>(1)), CHIP_NO_ERROR);
EXPECT_EQ(writer.Put(chip::TLV::ContextTag(2), kOutOfRangeIndex), CHIP_NO_ERROR);
EXPECT_EQ(writer.EndContainer(container), CHIP_NO_ERROR);
EXPECT_EQ(testStorage.SyncSetKeyValue(chip::DefaultStorageKeyAllocator::BindingTable().KeyName(), listInfoBuffer,
static_cast<uint16_t>(writer.GetLengthWritten())),
CHIP_NO_ERROR);
}
// The load must fail cleanly instead of writing past the end of the table arrays.
Binding::Table table;
table.SetPersistentStorage(&testStorage);
EXPECT_NE(table.LoadFromStorage(), CHIP_NO_ERROR);
EXPECT_EQ(table.Size(), 0u);
}
} // namespace