diff --git a/source/common/common/utility.cc b/source/common/common/utility.cc index 0c72f30c816e3..2ed40659314c0 100644 --- a/source/common/common/utility.cc +++ b/source/common/common/utility.cc @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include #include @@ -221,6 +223,19 @@ std::string DateFormatter::now(TimeSource& time_source) { return fromTime(time_source.systemTime()); } +MutableMemoryStreamBuffer::MutableMemoryStreamBuffer(char* base, size_t size) { + this->setp(base, base + size); +} + +OutputBufferStream::OutputBufferStream(char* data, size_t size) + : MutableMemoryStreamBuffer{data, size}, std::ostream{static_cast(this)} {} + +int OutputBufferStream::bytesWritten() const { return pptr() - pbase(); } + +absl::string_view OutputBufferStream::contents() const { + return absl::string_view(pbase(), bytesWritten()); +} + ConstMemoryStreamBuffer::ConstMemoryStreamBuffer(const char* data, size_t size) { // std::streambuf won't modify `data`, but the interface still requires a char* for convenience, // so we need to const_cast. diff --git a/source/common/common/utility.h b/source/common/common/utility.h index f91d74832b60e..e4eba5b371177 100644 --- a/source/common/common/utility.h +++ b/source/common/common/utility.h @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -110,6 +111,32 @@ class RealTimeSource : public TimeSource { MonotonicTime monotonicTime() override { return std::chrono::steady_clock::now(); } }; +/** + * Class used for creating non-memory allocating std::ostream. + */ +class MutableMemoryStreamBuffer : public std::streambuf { +public: + MutableMemoryStreamBuffer(char* base, size_t size); +}; + +/** + * std::ostream class that serializes writes into the provided buffer. + */ +class OutputBufferStream : private MutableMemoryStreamBuffer, public std::ostream { +public: + OutputBufferStream(char* data, size_t size); + + /** + * @return the number of bytes written prior to the "put" pointer into the buffer. + */ + int bytesWritten() const; + + /** + * @return a string view of the written bytes. + */ + absl::string_view contents() const; +}; + /** * Class used for creating non-copying std::istream's. See InputConstMemoryStream below. */ diff --git a/test/common/common/BUILD b/test/common/common/BUILD index c002da716b0a6..726f2caa28dc4 100644 --- a/test/common/common/BUILD +++ b/test/common/common/BUILD @@ -210,6 +210,7 @@ envoy_cc_test( ], deps = [ "//source/common/common:utility_lib", + "//test/common/stats:stat_test_utility_lib", "//test/test_common:simulated_time_system_lib", "//test/test_common:test_time_lib", "//test/test_common:utility_lib", diff --git a/test/common/common/utility_test.cc b/test/common/common/utility_test.cc index cda2a65f807ae..6f4f8a2a628b3 100644 --- a/test/common/common/utility_test.cc +++ b/test/common/common/utility_test.cc @@ -1,3 +1,4 @@ +#include #include #include #include @@ -8,6 +9,7 @@ #include "common/common/utility.h" +#include "test/common/stats/stat_test_utility.h" #include "test/test_common/simulated_time_system.h" #include "test/test_common/test_time.h" #include "test/test_common/utility.h" @@ -116,6 +118,60 @@ TEST(DateUtil, NowToMilliseconds) { EXPECT_EQ(12345067, DateUtil::nowToMilliseconds(test_time)); } +TEST(OutputBufferStream, FailsOnWriteToEmptyBuffer) { + constexpr char data = 'x'; + OutputBufferStream ostream{nullptr, 0}; + ASSERT_TRUE(ostream.good()); + + ostream << data; + + EXPECT_TRUE(ostream.bad()); +} + +TEST(OutputBufferStream, CanWriteToBuffer) { + constexpr char data[] = "123"; + std::array buffer; + + OutputBufferStream ostream{buffer.data(), buffer.size()}; + ASSERT_EQ(ostream.bytesWritten(), 0); + + ostream << data; + + EXPECT_EQ(ostream.contents(), data); + EXPECT_EQ(ostream.bytesWritten(), 3); +} + +TEST(OutputBufferStream, CannotOverwriteBuffer) { + constexpr char data[] = "123"; + std::array buffer; + + OutputBufferStream ostream{buffer.data(), buffer.size()}; + ASSERT_EQ(ostream.bytesWritten(), 0); + + // Initial write should stop before overflowing. + ostream << data << std::endl; + EXPECT_EQ(ostream.contents(), "12"); + EXPECT_EQ(ostream.bytesWritten(), 2); + + // Try a subsequent write, which shouldn't change anything since + // the buffer is full. + ostream << data << std::endl; + EXPECT_EQ(ostream.contents(), "12"); + EXPECT_EQ(ostream.bytesWritten(), 2); +} + +TEST(OutputBufferStream, DoesNotAllocateMemoryEvenIfWeTryToOverflowBuffer) { + constexpr char data[] = "123"; + std::array buffer; + Stats::TestUtil::MemoryTest memory_test; + + OutputBufferStream ostream{buffer.data(), buffer.size()}; + ostream << data << std::endl; + + EXPECT_EQ(memory_test.consumedBytes(), 0); + EXPECT_EQ(ostream.contents(), "12"); +} + TEST(InputConstMemoryStream, All) { { InputConstMemoryStream istream{nullptr, 0};