Add sequential memcpy helper object

Tested: Unit tests
Google-Bug-Id: 322561358
Change-Id: I980f82637e5f8b84daa29eb2b3b9bd4c6ad75594
Signed-off-by: Aryk Ledet <arykledet@google.com>
diff --git a/include/cper_encoder.hpp b/include/cper_encoder.hpp
index d1b5ae5..20df915 100644
--- a/include/cper_encoder.hpp
+++ b/include/cper_encoder.hpp
@@ -3,9 +3,12 @@
 #include "cper.hpp"
 
 #include <chrono>
+#include <concepts>
 
 namespace uefi::cper
 {
+template <typename T>
+concept ByteLike = std::is_trivially_copyable_v<T> && sizeof(T) == 1;
 
 /**
  * @brief Helper function to create a UTC UEFI CPER timestamp from a
@@ -49,4 +52,47 @@
     memcpy(&ret, &timestamp, sizeof(ret));
     return ret;
 }
+
+template <ByteLike ByteType>
+class SeqMemcopy
+{
+  public:
+    // Prevent the class from outliving the baseDest pointer.
+    SeqMemcopy() = delete;
+    SeqMemcopy(const SeqMemcopy&) = delete;
+    SeqMemcopy& operator=(const SeqMemcopy&) = delete;
+
+    /**
+     * @brief Manages sequentially copying chunks of data to a contiguous memory
+     * block.
+     *
+     * @param[in] baseDest - The memory location to copy to.
+     */
+    SeqMemcopy(std::span<ByteType> baseDest) : baseDest_(baseDest), offset_(0)
+    {}
+
+    /**
+     * @brief Copies the src to the next section in the dest address.
+     *
+     * @param[in] src - The source address to copy from.
+     * @param[in] srcSize - The source size.
+     *
+     * @throws out_of_range If source size goes out of the destinations bounds.
+     */
+    void copy(const void* src, const uint64_t srcSize)
+    {
+        if ((offset_ + srcSize) > baseDest_.size_bytes())
+        {
+            throw std::out_of_range(
+                "Attempt to copy out of the destinations bounds");
+        }
+
+        memcpy(baseDest_.data() + offset_, src, srcSize);
+        offset_ += srcSize;
+    }
+
+  private:
+    std::span<ByteType> baseDest_;
+    uint64_t offset_;
+};
 } // namespace uefi::cper
diff --git a/test/cper_encoder_test.cpp b/test/cper_encoder_test.cpp
index 2903936..c1e756f 100644
--- a/test/cper_encoder_test.cpp
+++ b/test/cper_encoder_test.cpp
@@ -35,6 +35,28 @@
     }
 };
 
+TEST_F(CperEncoderTest, SeqMemcopy)
+{
+    std::string section1 = "Hello";
+    std::string section2 = " ";
+    std::string section3 = "World";
+
+    std::string dest(section1.size() + section2.size() + section3.size(), 0);
+
+    SeqMemcopy<char> seqMemcpy(dest);
+
+    EXPECT_NO_THROW(seqMemcpy.copy(section1.data(), section1.size()));
+    EXPECT_NO_THROW(seqMemcpy.copy(section2.data(), section2.size()));
+    EXPECT_NO_THROW(seqMemcpy.copy(section3.data(), section3.size()));
+
+    EXPECT_EQ(dest, section1 + section2 + section3);
+
+    std::string section4 = "This is an attempted buffer overflow!";
+
+    EXPECT_THROW(seqMemcpy.copy(section4.data(), section4.size()),
+                 std::out_of_range);
+}
+
 TEST_F(CperEncoderTest, CreateCperTimestamp)
 {
     const auto now = std::chrono::system_clock::now();