Implement a Vector-based TLVBackingStore (#32508)
* Implement a Vector-based TLVBackingStore for converting from stream-based input to TLV format
* :Address comments
* Fix for esp32 qemu build
diff --git a/scripts/tools/check_includes_config.py b/scripts/tools/check_includes_config.py
index 26689e4..0d9a7df 100644
--- a/scripts/tools/check_includes_config.py
+++ b/scripts/tools/check_includes_config.py
@@ -167,6 +167,8 @@
'src/app/PendingResponseTrackerImpl.h': {'unordered_set'},
# Not intended for embedded clients
+ 'src/lib/core/TLVVectorWriter.cpp': {'vector'},
+ 'src/lib/core/TLVVectorWriter.h': {'vector'},
'src/lib/support/jsontlv/JsonToTlv.cpp': {'sstream', 'string', 'vector'},
'src/lib/support/jsontlv/JsonToTlv.h': {'string'},
'src/lib/support/jsontlv/TlvToJson.h': {'string'},
diff --git a/src/lib/core/BUILD.gn b/src/lib/core/BUILD.gn
index fdb74c7..eaecf85 100644
--- a/src/lib/core/BUILD.gn
+++ b/src/lib/core/BUILD.gn
@@ -187,3 +187,17 @@
"${chip_root}/src/system",
]
}
+
+static_library("vectortlv") {
+ output_name = "libVectorTlv"
+ output_dir = "${root_out_dir}/lib"
+
+ sources = [
+ "TLVVectorWriter.cpp",
+ "TLVVectorWriter.h",
+ ]
+
+ cflags = [ "-Wconversion" ]
+
+ public_deps = [ ":core" ]
+}
diff --git a/src/lib/core/TLVVectorWriter.cpp b/src/lib/core/TLVVectorWriter.cpp
new file mode 100644
index 0000000..e75ed0d
--- /dev/null
+++ b/src/lib/core/TLVVectorWriter.cpp
@@ -0,0 +1,93 @@
+/*
+ *
+ * Copyright (c) 2024 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/TLVVectorWriter.h>
+
+#include <cstdint>
+#include <vector>
+
+#include <lib/core/CHIPError.h>
+#include <lib/core/TLVCommon.h>
+
+namespace chip {
+namespace TLV {
+
+namespace {
+
+constexpr uint32_t kIpv6MtuSize = 1280;
+
+} // namespace
+
+TlvVectorWriter::TlvVectorWriter(std::vector<uint8_t> & buffer) : mVectorBuffer(buffer)
+{
+ Init(mVectorBuffer);
+}
+
+TlvVectorWriter::~TlvVectorWriter() = default;
+
+TlvVectorWriter::TlvVectorBuffer::TlvVectorBuffer(std::vector<uint8_t> & buffer) : mFinalBuffer(buffer) {}
+
+TlvVectorWriter::TlvVectorBuffer::~TlvVectorBuffer() {}
+
+CHIP_ERROR TlvVectorWriter::TlvVectorBuffer::OnInit(TLVWriter & /*writer*/, uint8_t *& bufStart, uint32_t & bufLen)
+{
+ VerifyOrReturnError(mFinalBuffer.empty(), CHIP_ERROR_INCORRECT_STATE);
+
+ ResizeWriteBuffer(bufStart, bufLen);
+
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR TlvVectorWriter::TlvVectorBuffer::GetNewBuffer(TLVWriter & /*writer*/, uint8_t *& bufStart, uint32_t & bufLen)
+{
+ VerifyOrReturnError(!mFinalBuffer.empty(), CHIP_ERROR_INCORRECT_STATE);
+ VerifyOrReturnError(mWritingBuffer.data() == bufStart, CHIP_ERROR_INCORRECT_STATE);
+
+ ResizeWriteBuffer(bufStart, bufLen);
+
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR TlvVectorWriter::TlvVectorBuffer::FinalizeBuffer(TLVWriter & /*writer*/, uint8_t * bufStart, uint32_t bufLen)
+{
+ VerifyOrReturnError(mWritingBuffer.data() == bufStart, CHIP_ERROR_INCORRECT_STATE);
+ VerifyOrReturnError(bufLen <= mWritingBuffer.size(), CHIP_ERROR_BUFFER_TOO_SMALL);
+
+ mWritingBuffer.resize(bufLen);
+
+ mFinalBuffer.insert(mFinalBuffer.end(), mWritingBuffer.begin(), mWritingBuffer.end());
+ mWritingBuffer.resize(0);
+
+ mFinalBuffer.shrink_to_fit();
+
+ return CHIP_NO_ERROR;
+}
+
+void TlvVectorWriter::TlvVectorBuffer::ResizeWriteBuffer(uint8_t *& bufStart, uint32_t & bufLen)
+{
+ VerifyOrReturn(mWritingBuffer.empty());
+
+ mWritingBuffer.resize(kIpv6MtuSize);
+ bufStart = mWritingBuffer.data();
+
+ auto size = mWritingBuffer.size();
+ VerifyOrReturn(size <= std::numeric_limits<uint32_t>::max());
+ bufLen = static_cast<uint32_t>(size);
+}
+
+} // namespace TLV
+} // namespace chip
diff --git a/src/lib/core/TLVVectorWriter.h b/src/lib/core/TLVVectorWriter.h
new file mode 100644
index 0000000..b1baae3
--- /dev/null
+++ b/src/lib/core/TLVVectorWriter.h
@@ -0,0 +1,83 @@
+/*
+ *
+ * Copyright (c) 2024 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 <cstdint>
+#include <vector>
+
+#include <lib/core/CHIPError.h>
+#include <lib/core/TLVBackingStore.h>
+#include <lib/core/TLVCommon.h>
+
+namespace chip {
+namespace TLV {
+
+// Implementation of TLVWriter that writes to a std::vector, automatically
+// resizing the vector as needed when writing.
+// Users of TlvVectorWriter may call any public API of TLVWriter, except for the
+// Init functions.
+// This class is not thread-safe, it must be constructed, used, and destroyed on
+// a single thread.
+class TlvVectorWriter : public TLVWriter
+{
+public:
+ // All data will be written to and read from the provided buffer, which must
+ // outlive this object.
+ TlvVectorWriter(std::vector<uint8_t> & buffer);
+ TlvVectorWriter(const TlvVectorWriter &) = delete;
+ TlvVectorWriter & operator=(const TlvVectorWriter &) = delete;
+ ~TlvVectorWriter();
+
+private:
+ class TlvVectorBuffer : public TLVBackingStore
+ {
+ public:
+ TlvVectorBuffer(std::vector<uint8_t> & buffer);
+ TlvVectorBuffer(const TlvVectorBuffer &) = delete;
+ TlvVectorBuffer & operator=(const TlvVectorBuffer &) = delete;
+ ~TlvVectorBuffer() override;
+
+ // TLVBackingStore implementation:
+ CHIP_ERROR OnInit(TLVReader & reader, const uint8_t *& bufStart, uint32_t & bufLen) override
+ {
+ return CHIP_ERROR_NOT_IMPLEMENTED;
+ }
+ CHIP_ERROR GetNextBuffer(TLVReader & reader, const uint8_t *& bufStart, uint32_t & bufLen) override
+ {
+ return CHIP_ERROR_NOT_IMPLEMENTED;
+ }
+ CHIP_ERROR OnInit(TLVWriter & writer, uint8_t *& bufStart, uint32_t & bufLen) override;
+ CHIP_ERROR GetNewBuffer(TLVWriter & writer, uint8_t *& bufStart, uint32_t & bufLen) override;
+ CHIP_ERROR FinalizeBuffer(TLVWriter & writer, uint8_t * bufStart, uint32_t bufLen) override;
+
+ private:
+ void ResizeWriteBuffer(uint8_t *& bufStart, uint32_t & bufLen);
+
+ // mWritingBuffer is the mutable buffer exposed via the TLVBackingStore
+ // interface. When FinalizeBuffer is called the contents of mWritingBuffer
+ // are appended to mFinalBuffer and mWritingBuffer is cleared. This allows
+ // for reading all written data from a single, contiguous buffer
+ // (mFinalBuffer).
+ std::vector<uint8_t> mWritingBuffer;
+ std::vector<uint8_t> & mFinalBuffer;
+ };
+
+ TlvVectorBuffer mVectorBuffer;
+};
+
+} // namespace TLV
+} // namespace chip
diff --git a/src/lib/core/tests/BUILD.gn b/src/lib/core/tests/BUILD.gn
index e7fbba2..1e3373d 100644
--- a/src/lib/core/tests/BUILD.gn
+++ b/src/lib/core/tests/BUILD.gn
@@ -30,12 +30,14 @@
"TestOptional.cpp",
"TestReferenceCounted.cpp",
"TestTLV.cpp",
+ "TestTLVVectorWriter.cpp",
]
cflags = [ "-Wconversion" ]
public_deps = [
"${chip_root}/src/lib/core",
+ "${chip_root}/src/lib/core:vectortlv",
"${chip_root}/src/lib/support:test_utils",
"${chip_root}/src/lib/support:testing_nlunit",
"${chip_root}/src/platform",
diff --git a/src/lib/core/tests/TestTLVVectorWriter.cpp b/src/lib/core/tests/TestTLVVectorWriter.cpp
new file mode 100644
index 0000000..c68211b
--- /dev/null
+++ b/src/lib/core/tests/TestTLVVectorWriter.cpp
@@ -0,0 +1,180 @@
+/*
+ *
+ * Copyright (c) 2024 Project CHIP Authors
+ * All rights reserved.
+ *
+ * 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/TLVVectorWriter.h>
+
+#include <cstddef>
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include <lib/core/CHIPError.h>
+#include <lib/core/ErrorStr.h>
+#include <lib/core/TLVCommon.h>
+#include <lib/core/TLVTags.h>
+#include <lib/support/Span.h>
+#include <lib/support/UnitTestContext.h>
+#include <lib/support/UnitTestExtendedAssertions.h>
+#include <lib/support/UnitTestRegistration.h>
+#include <lib/support/UnitTestUtils.h>
+
+using namespace chip;
+using namespace chip::TLV;
+
+/**
+ * context
+ */
+
+struct TestTLVContext
+{
+ nlTestSuite * mSuite = nullptr;
+ int mEvictionCount = 0;
+ uint32_t mEvictedBytes = 0;
+
+ TestTLVContext(nlTestSuite * suite) : mSuite(suite) {}
+};
+
+void InitAndFinalizeWithNoData(nlTestSuite * inSuite, void * inContext)
+{
+ std::vector<uint8_t> buffer;
+ TlvVectorWriter writer(buffer);
+
+ // Init and finalize but write not data.
+ NL_TEST_ASSERT(inSuite, writer.Finalize() == CHIP_NO_ERROR);
+ NL_TEST_ASSERT(inSuite, buffer.empty());
+}
+
+void SingleSmallDataFitsInOriginalBuffer(nlTestSuite * inSuite, void * inContext)
+{
+ std::vector<uint8_t> buffer;
+ TlvVectorWriter writer(buffer);
+ TLVReader reader;
+
+ NL_TEST_ASSERT(inSuite, writer.Put(AnonymousTag(), true) == CHIP_NO_ERROR);
+ NL_TEST_ASSERT(inSuite, writer.Finalize() == CHIP_NO_ERROR);
+
+ reader.Init(buffer.data(), buffer.size());
+ NL_TEST_ASSERT(inSuite, reader.Next() == CHIP_NO_ERROR);
+ NL_TEST_ASSERT(inSuite, reader.GetTag() == AnonymousTag());
+
+ bool value = false;
+ NL_TEST_ASSERT(inSuite, reader.Get(value) == CHIP_NO_ERROR);
+ NL_TEST_ASSERT(inSuite, value == true);
+}
+
+void SingleLargeDataRequiresNewBufferAllocation(nlTestSuite * inSuite, void * inContext)
+{
+ std::vector<uint8_t> buffer;
+ TlvVectorWriter writer(buffer);
+ TLVReader reader;
+ static constexpr size_t kStringSize = 10000;
+
+ const std::string bytes(kStringSize, 'a');
+ CHIP_ERROR error = writer.PutString(AnonymousTag(), bytes.data());
+ NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
+ NL_TEST_ASSERT(inSuite, writer.Finalize() == CHIP_NO_ERROR);
+
+ reader.Init(buffer.data(), buffer.size());
+ NL_TEST_ASSERT(inSuite, reader.Next() == CHIP_NO_ERROR);
+ NL_TEST_ASSERT(inSuite, reader.GetTag() == AnonymousTag());
+
+ CharSpan span;
+ error = reader.Get(span);
+ NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
+ NL_TEST_ASSERT(inSuite, std::string(span.data(), span.size()) == bytes);
+}
+
+void ManySmallDataRequiresNewBufferAllocation(nlTestSuite * inSuite, void * inContext)
+{
+ std::vector<uint8_t> buffer;
+ TlvVectorWriter writer(buffer);
+ TLVReader reader;
+
+ for (int i = 0; i < 10000; i++)
+ {
+ NL_TEST_ASSERT(inSuite, writer.Put(AnonymousTag(), true) == CHIP_NO_ERROR);
+ }
+ NL_TEST_ASSERT(inSuite, writer.Finalize() == CHIP_NO_ERROR);
+
+ reader.Init(buffer.data(), buffer.size());
+ for (int i = 0; i < 10000; i++)
+ {
+ NL_TEST_ASSERT(inSuite, reader.Next() == CHIP_NO_ERROR);
+ NL_TEST_ASSERT(inSuite, reader.GetTag() == AnonymousTag());
+
+ bool value = false;
+ CHIP_ERROR error = reader.Get(value);
+
+ NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
+ NL_TEST_ASSERT(inSuite, value == true);
+ }
+ NL_TEST_ASSERT(inSuite, reader.Next() == CHIP_END_OF_TLV);
+}
+
+// Test Suite
+
+/**
+ * Test Suite that lists all the test functions.
+ */
+// clang-format off
+static const nlTest sTests[] =
+{
+ NL_TEST_DEF("Verify behavior on init and finalize without data manipulation", InitAndFinalizeWithNoData),
+ NL_TEST_DEF("Ensure correct write/read operations within Inet buffer constraints", SingleSmallDataFitsInOriginalBuffer),
+ NL_TEST_DEF("Handle cases where a single large data input exceeds buffer capacity", SingleLargeDataRequiresNewBufferAllocation),
+ NL_TEST_DEF("Validate output formatting for multiple small data inputs requiring additional buffer space", ManySmallDataRequiresNewBufferAllocation),
+ NL_TEST_SENTINEL()
+};
+// clang-format on
+
+/**
+ * Set up the test suite.
+ */
+int TestTLVVectorWriter_Setup(void * inContext)
+{
+ CHIP_ERROR error = chip::Platform::MemoryInit();
+ if (error != CHIP_NO_ERROR)
+ return FAILURE;
+ return SUCCESS;
+}
+
+/**
+ * Tear down the test suite.
+ */
+int TestTLVVectorWriter_Teardown(void * inContext)
+{
+ chip::Platform::MemoryShutdown();
+ return SUCCESS;
+}
+
+int TestTLVVectorWriter()
+{
+ // clang-format off
+ nlTestSuite theSuite =
+ {
+ "chip-tlv",
+ &sTests[0],
+ TestTLVVectorWriter_Setup,
+ TestTLVVectorWriter_Teardown
+ };
+ // clang-format on
+
+ return chip::ExecuteTestsWithContext<TestTLVContext>(&theSuite, &theSuite);
+}
+
+CHIP_REGISTER_TEST_SUITE(TestTLVVectorWriter)
diff --git a/src/test_driver/esp32/cmake/esp32_unit_tests.cmake b/src/test_driver/esp32/cmake/esp32_unit_tests.cmake
index 023ee7a..05eda6d 100644
--- a/src/test_driver/esp32/cmake/esp32_unit_tests.cmake
+++ b/src/test_driver/esp32/cmake/esp32_unit_tests.cmake
@@ -36,6 +36,7 @@
idf::main
-Wl,--whole-archive ${UNIT_TEST_LIBRARY} -Wl,--no-whole-archive
${UNIT_TEST_EXTRA_LIBRARIES}
+ -lVectorTlv
-lSupportTesting
-lTestUtils
nlunit-test