blob: e46906d2edbbb3be845833bf2fb247cd90bf7b69 [file] [log] [blame]
/*
* Copyright (c) 2023 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 <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <lib/support/CHIPMem.h>
#include <lib/support/ScopedBuffer.h>
#include <lib/support/Span.h>
#include <lib/support/UnitTestRegistration.h>
#include <system/TLVPacketBufferBackingStore.h>
#include <nlunit-test.h>
using ::chip::Platform::ScopedMemoryBuffer;
using ::chip::System::PacketBuffer;
using ::chip::System::PacketBufferHandle;
using ::chip::System::PacketBufferTLVReader;
using ::chip::System::PacketBufferTLVWriter;
using namespace ::chip;
namespace {
void WriteUntilRemainingLessThan(nlTestSuite * inSuite, PacketBufferTLVWriter & writer, const uint32_t remainingSize)
{
uint32_t lengthRemaining = writer.GetRemainingFreeLength();
while (lengthRemaining >= remainingSize)
{
NL_TEST_ASSERT(inSuite, writer.Put(TLV::AnonymousTag(), static_cast<uint8_t>(7)) == CHIP_NO_ERROR);
lengthRemaining = writer.GetRemainingFreeLength();
}
}
class TLVPacketBufferBackingStoreTest
{
public:
static int TestSetup(void * inContext);
static int TestTeardown(void * inContext);
static void BasicEncodeDecode(nlTestSuite * inSuite, void * inContext);
static void MultiBufferEncode(nlTestSuite * inSuite, void * inContext);
static void NonChainedBufferCanReserve(nlTestSuite * inSuite, void * inContext);
static void TestWriterReserveUnreserveDoesNotOverflow(nlTestSuite * inSuite, void * inContext);
static void TestWriterReserve(nlTestSuite * inSuite, void * inContext);
};
int TLVPacketBufferBackingStoreTest::TestSetup(void * inContext)
{
chip::Platform::MemoryInit();
return SUCCESS;
}
int TLVPacketBufferBackingStoreTest::TestTeardown(void * inContext)
{
chip::Platform::MemoryShutdown();
return SUCCESS;
}
/**
* Test that we can do a basic encode to TLV followed by decode.
*/
void TLVPacketBufferBackingStoreTest::BasicEncodeDecode(nlTestSuite * inSuite, void * inContext)
{
auto buffer = PacketBufferHandle::New(PacketBuffer::kMaxSizeWithoutReserve, 0);
PacketBufferTLVWriter writer;
writer.Init(std::move(buffer));
TLV::TLVType outerContainerType;
CHIP_ERROR error = writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Array, outerContainerType);
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
error = writer.Put(TLV::AnonymousTag(), static_cast<uint8_t>(7));
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
error = writer.Put(TLV::AnonymousTag(), static_cast<uint8_t>(8));
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
error = writer.Put(TLV::AnonymousTag(), static_cast<uint8_t>(9));
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
error = writer.EndContainer(outerContainerType);
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
error = writer.Finalize(&buffer);
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
// Array start/end is 2 bytes. Each entry is also 2 bytes: control +
// value. So 8 bytes total.
NL_TEST_ASSERT(inSuite, !buffer->HasChainedBuffer());
NL_TEST_ASSERT(inSuite, buffer->TotalLength() == 8);
NL_TEST_ASSERT(inSuite, buffer->DataLength() == 8);
PacketBufferTLVReader reader;
reader.Init(std::move(buffer));
error = reader.Next(TLV::kTLVType_Array, TLV::AnonymousTag());
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
error = reader.EnterContainer(outerContainerType);
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
error = reader.Next(TLV::kTLVType_UnsignedInteger, TLV::AnonymousTag());
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
uint8_t value;
error = reader.Get(value);
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, value == 7);
error = reader.Next(TLV::kTLVType_UnsignedInteger, TLV::AnonymousTag());
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
error = reader.Get(value);
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, value == 8);
error = reader.Next(TLV::kTLVType_UnsignedInteger, TLV::AnonymousTag());
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
error = reader.Get(value);
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, value == 9);
error = reader.Next();
NL_TEST_ASSERT(inSuite, error == CHIP_END_OF_TLV);
error = reader.ExitContainer(outerContainerType);
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
error = reader.Next();
NL_TEST_ASSERT(inSuite, error == CHIP_END_OF_TLV);
}
/**
* Test that we can do an encode that's going to split across multiple buffers correctly.
*/
void TLVPacketBufferBackingStoreTest::MultiBufferEncode(nlTestSuite * inSuite, void * inContext)
{
// Start with a too-small buffer.
auto buffer = PacketBufferHandle::New(2, 0);
PacketBufferTLVWriter writer;
writer.Init(std::move(buffer), /* useChainedBuffers = */ true);
TLV::TLVType outerContainerType;
CHIP_ERROR error = writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Array, outerContainerType);
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
error = writer.Put(TLV::AnonymousTag(), static_cast<uint8_t>(7));
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
error = writer.Put(TLV::AnonymousTag(), static_cast<uint8_t>(8));
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
// Something to make sure we have 3 buffers.
uint8_t bytes[2000] = { 0 };
error = writer.Put(TLV::AnonymousTag(), ByteSpan(bytes));
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
error = writer.EndContainer(outerContainerType);
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
error = writer.Finalize(&buffer);
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
// Array start/end is 2 bytes. First two entries are 2 bytes each.
// Third entry is 1 control byte, 2 length bytes, 2000 bytes of data,
// for a total of 2009 bytes.
constexpr size_t totalSize = 2009;
NL_TEST_ASSERT(inSuite, buffer->HasChainedBuffer());
NL_TEST_ASSERT(inSuite, buffer->TotalLength() == totalSize);
NL_TEST_ASSERT(inSuite, buffer->DataLength() == 2);
auto nextBuffer = buffer->Next();
NL_TEST_ASSERT(inSuite, nextBuffer->HasChainedBuffer());
NL_TEST_ASSERT(inSuite, nextBuffer->TotalLength() == totalSize - 2);
NL_TEST_ASSERT(inSuite, nextBuffer->DataLength() == PacketBuffer::kMaxSizeWithoutReserve);
nextBuffer = nextBuffer->Next();
NL_TEST_ASSERT(inSuite, !nextBuffer->HasChainedBuffer());
NL_TEST_ASSERT(inSuite, nextBuffer->TotalLength() == nextBuffer->DataLength());
NL_TEST_ASSERT(inSuite, nextBuffer->DataLength() == totalSize - 2 - PacketBuffer::kMaxSizeWithoutReserve);
// PacketBufferTLVReader cannot handle non-contiguous buffers, and our
// buffers are too big to stick into a single packet buffer.
ScopedMemoryBuffer<uint8_t> buf;
NL_TEST_ASSERT(inSuite, buf.Calloc(totalSize));
size_t offset = 0;
while (!buffer.IsNull())
{
memcpy(buf.Get() + offset, buffer->Start(), buffer->DataLength());
offset += buffer->DataLength();
buffer.Advance();
NL_TEST_ASSERT(inSuite, offset < totalSize || (offset == totalSize && buffer.IsNull()));
}
TLV::TLVReader reader;
reader.Init(buf.Get(), totalSize);
error = reader.Next(TLV::kTLVType_Array, TLV::AnonymousTag());
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
error = reader.EnterContainer(outerContainerType);
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
error = reader.Next(TLV::kTLVType_UnsignedInteger, TLV::AnonymousTag());
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
uint8_t value;
error = reader.Get(value);
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, value == 7);
error = reader.Next(TLV::kTLVType_UnsignedInteger, TLV::AnonymousTag());
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
error = reader.Get(value);
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, value == 8);
error = reader.Next(TLV::kTLVType_ByteString, TLV::AnonymousTag());
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
ByteSpan byteValue;
error = reader.Get(byteValue);
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, byteValue.size() == sizeof(bytes));
error = reader.Next();
NL_TEST_ASSERT(inSuite, error == CHIP_END_OF_TLV);
error = reader.ExitContainer(outerContainerType);
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
error = reader.Next();
NL_TEST_ASSERT(inSuite, error == CHIP_END_OF_TLV);
}
void TLVPacketBufferBackingStoreTest::NonChainedBufferCanReserve(nlTestSuite * inSuite, void * inContext)
{
// Start with a too-small buffer.
uint32_t smallSize = 5;
uint32_t smallerSizeToReserver = smallSize - 1;
auto buffer = PacketBufferHandle::New(smallSize, /* aReservedSize = */ 0);
PacketBufferTLVWriter writer;
writer.Init(std::move(buffer), /* useChainedBuffers = */ false);
CHIP_ERROR error = writer.ReserveBuffer(smallerSizeToReserver);
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
}
// This test previously was created to show that there was an overflow bug, now this test mainly
// just checks that you cannot reserve this type of TLVBackingStorage buffer.
void TLVPacketBufferBackingStoreTest::TestWriterReserveUnreserveDoesNotOverflow(nlTestSuite * inSuite, void * inContext)
{
// Start with a too-small buffer.
uint32_t smallSize = 100;
uint32_t smallerSizeToReserver = smallSize - 1;
auto buffer = PacketBufferHandle::New(smallSize, 0);
PacketBufferTLVWriter writer;
writer.Init(std::move(buffer), /* useChainedBuffers = */ true);
CHIP_ERROR error = writer.ReserveBuffer(smallerSizeToReserver);
if (error == CHIP_NO_ERROR)
{
uint32_t lengthRemaining = writer.GetRemainingFreeLength();
NL_TEST_ASSERT(inSuite, lengthRemaining == 1);
// Lets try to overflow by getting next buffer in the chain,
// unreserving then writing until the end of the current buffer.
error = writer.Put(TLV::AnonymousTag(), static_cast<uint8_t>(7));
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
lengthRemaining = writer.GetRemainingFreeLength();
NL_TEST_ASSERT(inSuite, lengthRemaining > smallerSizeToReserver);
WriteUntilRemainingLessThan(inSuite, writer, 2);
lengthRemaining = writer.GetRemainingFreeLength();
NL_TEST_ASSERT(inSuite, lengthRemaining != 0);
NL_TEST_ASSERT(inSuite, lengthRemaining < smallerSizeToReserver);
error = writer.UnreserveBuffer(smallerSizeToReserver);
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
lengthRemaining = writer.GetRemainingFreeLength();
NL_TEST_ASSERT(inSuite, lengthRemaining > smallerSizeToReserver);
// This is where we get overflow.
WriteUntilRemainingLessThan(inSuite, writer, 2);
// If we get here then the overflow condition we were expecting did not happen. If that is the case,
// either we have fixed reservation for chained buffers, or expected failure didn't hit on this
// platform.
//
// If there is a fix please add reservation for chained buffers, please make sure you account for
// what happens if TLVWriter::WriteData fails to get a new buffer but we are not at max size, do
// you actually have space for what was supposed to be reserved.
NL_TEST_ASSERT(inSuite, false);
}
// We no longer allow non-contigous buffers to be reserved.
NL_TEST_ASSERT(inSuite, error == CHIP_ERROR_INCORRECT_STATE);
}
void TLVPacketBufferBackingStoreTest::TestWriterReserve(nlTestSuite * inSuite, void * inContext)
{
// Start with a too-small buffer.
uint32_t smallSize = 5;
uint32_t smallerSizeToReserver = smallSize - 1;
auto buffer = PacketBufferHandle::New(smallSize, 0);
PacketBufferTLVWriter writer;
writer.Init(std::move(buffer), /* useChainedBuffers = */ false);
CHIP_ERROR error = writer.ReserveBuffer(smallerSizeToReserver);
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
error = writer.Put(TLV::AnonymousTag(), static_cast<uint8_t>(7));
NL_TEST_ASSERT(inSuite, error == CHIP_ERROR_NO_MEMORY);
error = writer.UnreserveBuffer(smallerSizeToReserver);
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
error = writer.Put(TLV::AnonymousTag(), static_cast<uint8_t>(7));
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
}
/**
* Test Suite. It lists all the test functions.
*/
// clang-format off
const nlTest sTests[] =
{
NL_TEST_DEF("BasicEncodeDecode", TLVPacketBufferBackingStoreTest::BasicEncodeDecode),
NL_TEST_DEF("MultiBufferEncode", TLVPacketBufferBackingStoreTest::MultiBufferEncode),
NL_TEST_DEF("NonChainedBufferCanReserve", TLVPacketBufferBackingStoreTest::NonChainedBufferCanReserve),
NL_TEST_DEF("TestWriterReserveUnreserveDoesNotOverflow", TLVPacketBufferBackingStoreTest::TestWriterReserveUnreserveDoesNotOverflow),
NL_TEST_DEF("TestWriterReserve", TLVPacketBufferBackingStoreTest::TestWriterReserve),
NL_TEST_SENTINEL()
};
// clang-format on
} // anonymous namespace
int TestTLVPacketBufferBackingStore()
{
// clang-format off
nlTestSuite theSuite = {
.name ="chip-tlv-packet-buffer-backing-store",
.tests = &sTests[0],
.setup = TLVPacketBufferBackingStoreTest::TestSetup,
.tear_down = TLVPacketBufferBackingStoreTest::TestTeardown,
};
// clang-format on
// Run test suite.
nlTestRunner(&theSuite, nullptr);
return (nlTestRunnerStats(&theSuite));
}
CHIP_REGISTER_TEST_SUITE(TestTLVPacketBufferBackingStore)