Add `SpanWriter` class for zero-allocation stream writing
What changed, and why it matters
This commit adds a new helper class called SpanWriter that lets Bitcoin Core write serialized data into a fixed-size byte buffer without allocating new memory. It includes bounds checking so writing past the end throws an error, and it comes with unit tests. There is no indication this is a security fix or that it addresses any reported vulnerability.
No security action required. Review as normal code-quality/feature addition if desired.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch introduces SpanWriter in src/streams.h. It wraps a std::span
Changed components
src/streams.hsrc/test/streams_tests.cppInspect captured patch +54 / −0
diff --git a/src/streams.h b/src/streams.h
index 466084e9..12a2cac2 100644
--- a/src/streams.h
+++ b/src/streams.h
@@ -121,6 +121,38 @@ public:
}
};
+/** Minimal stream for writing to an existing span of bytes.
+ */
+class SpanWriter
+{
+private:
+ std::span<std::byte> m_dest;
+
+public:
+ explicit SpanWriter(std::span<std::byte> dest) : m_dest{dest} {}
+ template <typename... Args>
+ SpanWriter(std::span<std::byte> dest, Args&&... args) : SpanWriter{dest}
+ {
+ ::SerializeMany(*this, std::forward<Args>(args)...);
+ }
+
+ void write(std::span<const std::byte> src)
+ {
+ if (src.size() > m_dest.size()) {
+ throw std::ios_base::failure("SpanWriter::write(): exceeded buffer size");
+ }
+ memcpy(m_dest.data(), src.data(), src.size());
+ m_dest = m_dest.subspan(src.size());
+ }
+
+ template<typename T>
+ SpanWriter& operator<<(const T& obj)
+ {
+ ::Serialize(*this, obj);
+ return *this;
+ }
+};
+
/** Double ended buffer combining vector and stream-like interfaces.
*
* >> and << read and write unformatted data using the above serialization templates.
diff --git a/src/test/streams_tests.cpp b/src/test/streams_tests.cpp
index af75ee98..cb1ca6ee 100644
--- a/src/test/streams_tests.cpp
+++ b/src/test/streams_tests.cpp
@@ -207,6 +207,28 @@ BOOST_AUTO_TEST_CASE(streams_vector_writer)
vch.clear();
}
+BOOST_AUTO_TEST_CASE(streams_span_writer)
+{
+ unsigned char a(1);
+ unsigned char b(2);
+ unsigned char bytes[] = {3, 4, 5, 6};
+ std::array<std::byte, 8> arr{};
+
+ // Test operator<<
+ SpanWriter writer{arr};
+ writer << a << b;
+ BOOST_CHECK_EQUAL(HexStr(arr), "0102000000000000");
+
+ // Use variadic constructor and write to subspan.
+ SpanWriter{std::span{arr}.subspan(2), a, bytes, b};
+ BOOST_CHECK_EQUAL(HexStr(arr), "0102010304050602");
+
+ // Writing past the end throws
+ std::array<std::byte, 1> small{};
+ BOOST_CHECK_THROW(SpanWriter(std::span{small}, a, b), std::ios_base::failure);
+ BOOST_CHECK_THROW(SpanWriter(std::span{small}) << a << b, std::ios_base::failure);
+}
+
BOOST_AUTO_TEST_CASE(streams_vector_reader)
{
std::vector<unsigned char> vch = {1, 255, 3, 4, 5, 6};
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.