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