diff --git a/doc/qbk/03_core/5_buffers.qbk b/doc/qbk/03_core/5_buffers.qbk
index 7043980b..c1c19eb4 100644
--- a/doc/qbk/03_core/5_buffers.qbk
+++ b/doc/qbk/03_core/5_buffers.qbk
@@ -96,6 +96,16 @@ transferred.
This function converts a buffer sequence to a `std::string`. It can
be used for diagnostic purposes and tests.
]]
+[[
+ [link beast.ref.boost__beast__buffer_ref `buffer_ref`]
+ [link beast.ref.boost__beast__ref `ref`]
+
+][
+ This function converts a beast buffer, that is to be passed by reference,
+ into a buffer reference, that can be passed by value into asio functions.
+
+ It implements the __DynamicBuffer__v2__ concept.
+]]
]
The __DynamicBuffer__ concept introduced in __Asio__ models a buffer
diff --git a/doc/qbk/main.qbk b/doc/qbk/main.qbk
index 32cf6d9b..84a5203b 100644
--- a/doc/qbk/main.qbk
+++ b/doc/qbk/main.qbk
@@ -87,6 +87,8 @@
[def __SyncReadStream__ [@boost:/doc/html/boost_asio/reference/SyncReadStream.html ['SyncReadStream]]]
[def __SyncWriteStream__ [@boost:/doc/html/boost_asio/reference/SyncWriteStream.html ['SyncWriteStream]]]
[def __WriteHandler__ [@boost:/doc/html/boost_asio/reference/WriteHandler.html ['WriteHandler]]]
+[def __DynamicBuffer__v1__ [@boost:/doc/html/boost_asio/reference/DynamicBuffer_v1.html ['DynamicBuffer_v1']]]
+[def __DynamicBuffer__v2__ [@boost:/doc/html/boost_asio/reference/DynamicBuffer_v2.html ['DynamicBuffer_v2']]]
[/ Beast Named Requirements ]
diff --git a/doc/qbk/quickref.xml b/doc/qbk/quickref.xml
index 51aaf28c..807cbb50 100644
--- a/doc/qbk/quickref.xml
+++ b/doc/qbk/quickref.xml
@@ -125,6 +125,7 @@
basic_flat_buffer
basic_multi_buffer
+ buffer_ref
buffered_read_stream
buffers_adaptor
buffers_cat_view
@@ -152,6 +153,7 @@
ostream
read_size
read_size_or_throw
+ ref
write
diff --git a/include/boost/beast/core/buffer_ref.hpp b/include/boost/beast/core/buffer_ref.hpp
new file mode 100644
index 00000000..3b15769b
--- /dev/null
+++ b/include/boost/beast/core/buffer_ref.hpp
@@ -0,0 +1,177 @@
+//
+// Copyright (c) 2022 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+#ifndef BOOST_BEAST_BUFFER_REF_HPP
+#define BOOST_BEAST_BUFFER_REF_HPP
+
+#include
+
+namespace boost {
+namespace beast {
+
+#if !defined(BOOST_ASIO_NO_DYNAMIC_BUFFER_V1)
+
+/** The buffer ref provides a wrapper around beast buffers
+ * to make them usable with asio dynamic_buffer v1.
+ *
+ * v2 is current not supported, so that
+ * `BOOST_ASIO_NO_DYNAMIC_BUFFER_V1` mustn't be defined.
+ *
+ * @par Example
+ *
+ * @code
+ *
+ * asio::tcp::socket sock;
+ * beast::flat_buffer fb;
+ * asio::read_until(sock, ref(fb) '\n');
+ *
+ * @endcode
+ *
+ * @tparam Buffer The underlying buffer
+ */
+template
+struct buffer_ref
+{
+ /// The ConstBufferSequence used to represent the readable bytes.
+ using const_buffers_type = typename Buffer::const_buffers_type;
+
+ /// The MutableBufferSequence used to represent the writable bytes.
+ using mutable_buffers_type = typename Buffer::mutable_buffers_type;
+
+ /// Returns the number of readable bytes.
+ std::size_t
+ size() const noexcept
+ {
+ return buffer_.size();
+ }
+
+ /// Return the maximum number of bytes, both readable and writable, that can ever be held.
+ std::size_t
+ max_size() const noexcept
+ {
+ return buffer_.max_size();
+ }
+
+ /// Return the maximum number of bytes, both readable and writable, that can be held without requiring an allocation.
+ std::size_t
+ capacity() const noexcept
+ {
+ return buffer_.capacity();
+ }
+
+ /// Returns a constant buffer sequence representing the readable bytes
+ const_buffers_type
+ data() const noexcept
+ {
+ return buffer_.data();
+ }
+
+ /// Get a list of buffers that represents the output
+ /// sequence, with the given size.
+ /**
+ * Ensures that the output sequence can accommodate @c n bytes, resizing the
+ * vector object as necessary.
+ *
+ * @returns An object of type @c mutable_buffers_type that satisfies
+ * MutableBufferSequence requirements, representing vector memory at the
+ * start of the output sequence of size @c n.
+ *
+ * @throws std::length_error If size() + n > max_size().
+ *
+ * @note The returned object is invalidated by any @c dynamic_vector_buffer
+ * or @c vector member function that modifies the input sequence or output
+ * sequence.
+ */
+ mutable_buffers_type prepare(std::size_t n)
+ {
+ return buffer_.prepare(n);
+ }
+
+ /// Move bytes from the output sequence to the input
+ /// sequence.
+ /**
+ * @param n The number of bytes to append from the start of the output
+ * sequence to the end of the input sequence. The remainder of the output
+ * sequence is discarded.
+ *
+ * Requires a preceding call prepare(x) where x >= n, and
+ * no intervening operations that modify the input or output sequence.
+ *
+ * @note If @c n is greater than the size of the output sequence, the entire
+ * output sequence is moved to the input sequence and no error is issued.
+ */
+ void commit(std::size_t n)
+ {
+ return buffer_.commit(n);
+ }
+
+ /// Remove `n` bytes from the readable byte sequence.
+ /**
+ * @b DynamicBuffer_v1: Removes @c n characters from the beginning of the
+ * input sequence. @note If @c n is greater than the size of the input
+ * sequence, the entire input sequence is consumed and no error is issued.
+ */
+ void consume(std::size_t n)
+ {
+ return buffer_.consume(n);
+ }
+
+ /// The type of the underlying buffer.
+ using buffer_type = Buffer;
+
+ /// Create a buffer reference around @c buffer.
+ buffer_ref(Buffer & buffer) : buffer_(buffer) {}
+
+ /// Copy the reference.
+ buffer_ref(const buffer_ref& buffer) = default;
+
+private:
+ Buffer &buffer_;
+};
+
+
+template
+class basic_flat_buffer;
+template
+class flat_static_buffer;
+template
+class basic_multi_buffer;
+template
+class static_buffer;
+
+/// Create a buffer_ref for basic_flat_buffer.
+template
+inline buffer_ref> ref(basic_flat_buffer & buf)
+{
+ return buffer_ref>(buf);
+}
+
+/// Create a buffer_ref for flat_static_buffer.
+template
+inline buffer_ref> ref(flat_static_buffer & buf)
+{
+ return buffer_ref>(buf);
+}
+
+/// Create a buffer_ref for basic_multi_buffer.
+template
+inline buffer_ref> ref(basic_multi_buffer & buf)
+{
+ return buffer_ref>(buf);
+}
+
+/// Create a buffer_ref for static_buffer.
+template
+inline buffer_ref> ref(static_buffer & buf)
+{
+ return buffer_ref>(buf);
+}
+
+#endif // !defined(BOOST_ASIO_NO_DYNAMIC_BUFFER_V1)
+
+}
+}
+
+#endif //BOOST_BEAST_BUFFER_REF_HPP
diff --git a/test/beast/core/CMakeLists.txt b/test/beast/core/CMakeLists.txt
index f4d36459..e631a823 100644
--- a/test/beast/core/CMakeLists.txt
+++ b/test/beast/core/CMakeLists.txt
@@ -31,6 +31,7 @@ add_executable (tests-beast-core
async_base.cpp
basic_stream.cpp
bind_handler.cpp
+ buffer_ref.cpp
buffer_traits.cpp
buffered_read_stream.cpp
buffers_adaptor.cpp
diff --git a/test/beast/core/Jamfile b/test/beast/core/Jamfile
index 16ac22d1..80fbe3b0 100644
--- a/test/beast/core/Jamfile
+++ b/test/beast/core/Jamfile
@@ -22,6 +22,7 @@ local SOURCES =
async_base.cpp
basic_stream.cpp
bind_handler.cpp
+ buffer_ref.cpp
buffer_traits.cpp
buffered_read_stream.cpp
buffers_adaptor.cpp
diff --git a/test/beast/core/buffer_ref.cpp b/test/beast/core/buffer_ref.cpp
new file mode 100644
index 00000000..fa6f176e
--- /dev/null
+++ b/test/beast/core/buffer_ref.cpp
@@ -0,0 +1,87 @@
+// Copyright (c) 2022 Klemens D. Morgenstern
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "test_buffer.hpp"
+
+namespace boost {
+namespace beast {
+
+template struct buffer_ref;
+template struct buffer_ref>;
+template struct buffer_ref;
+template struct buffer_ref>;
+
+class buffer_ref_test : public beast::unit_test::suite
+{
+
+ template
+ void testBuffer()
+ {
+ net::io_context ioc;
+ net::readable_pipe rp{ioc};
+ net::writable_pipe wp{ioc};
+ net::connect_pipe(rp, wp);
+
+ const char msg[] = "Hello, world!\n";
+
+ net::async_write(wp, net::buffer(msg), asio::detached);
+
+ Buffer buf;
+
+ net::async_read_until(rp, ref(buf), '\n', asio::detached);
+ ioc.run();
+ // writable, not commited yet!
+ std::string cmp;
+ cmp.resize(sizeof(msg) -1);
+ const auto n = net::buffer_copy(net::buffer(cmp), buf.data());
+ BEAST_EXPECT(n >= std::strlen(msg));
+ cmp.resize(13);
+ BEAST_EXPECT(cmp == "Hello, world!");
+
+
+ buf = Buffer();
+ test_dynamic_buffer_ref(ref(buf));
+ }
+
+ void
+ run() override
+ {
+ testBuffer();
+ testBuffer>();
+ testBuffer();
+ testBuffer>();
+
+ {
+ std::string buf;
+ test_dynamic_buffer_ref(asio::dynamic_buffer(buf));
+ }
+
+ {
+ std::vector buf;
+ test_dynamic_buffer_ref(asio::dynamic_buffer(buf));
+ }
+
+ }
+
+};
+
+BEAST_DEFINE_TESTSUITE(beast,core, buffer_ref);
+
+}
+}
+
diff --git a/test/beast/core/test_buffer.hpp b/test/beast/core/test_buffer.hpp
index 3b72dccf..b96f0b59 100644
--- a/test/beast/core/test_buffer.hpp
+++ b/test/beast/core/test_buffer.hpp
@@ -593,6 +593,184 @@ test_dynamic_buffer(
is_mutable_dynamic_buffer{});
}
+
+/** Test an instance of a dynamic buffer or mutable dynamic buffer.
+*/
+template
+void
+test_dynamic_buffer_ref(DynamicBuffer_v0 b0)
+{
+ BOOST_STATIC_ASSERT(
+ net::is_dynamic_buffer_v1::value);
+
+ BOOST_STATIC_ASSERT(
+ net::is_const_buffer_sequence::value);
+
+ BOOST_STATIC_ASSERT(
+ net::is_mutable_buffer_sequence::value);
+
+ BEAST_EXPECT(b0.size() == 0);
+ BEAST_EXPECT(buffer_bytes(b0.data()) == 0);
+
+ // members
+ {
+ string_view src = "Hello, world!";
+
+ DynamicBuffer_v0 b1(b0);
+ auto const mb = b1.prepare(src.size());
+ b1.commit(net::buffer_copy(mb,
+ net::const_buffer(src.data(), src.size())));
+
+ // copy constructor
+ {
+ DynamicBuffer_v0 b2(b1);
+ BEAST_EXPECT(b2.size() == b1.size());
+ BEAST_EXPECT(
+ buffers_to_string(b1.data()) ==
+ buffers_to_string(b2.data()));
+
+ // https://github.com/boostorg/beast/issues/1621
+ b2.consume(1);
+ BEAST_EXPECT(b0.size() == b2.size());
+ BEAST_EXPECT(
+ buffers_to_string(b2.data()) ==
+ buffers_to_string(b0.data()));
+ }
+
+ // move constructor
+ {
+ DynamicBuffer_v0 b2(b1);
+ DynamicBuffer_v0 b3(std::move(b2));
+ BEAST_EXPECT(b3.size() == b1.size());
+ BEAST_EXPECT(
+ buffers_to_string(b3.data()) ==
+ buffers_to_string(b1.data()));
+ }
+ }
+
+ // n == 0
+ {
+
+ DynamicBuffer_v0 b(b0);
+
+ const std::size_t len = b0.size();
+ BEAST_EXPECT(b.size() == len);
+ BEAST_EXPECT(buffer_bytes(b.prepare(0)) == 0);
+ b.commit(0);
+ BEAST_EXPECT(b.size() == len);
+ b.commit(1);
+ BEAST_EXPECT(b.size() == len);
+ b.commit(b.max_size() + 1);
+ BEAST_EXPECT(b.size() == len);
+ b.consume(0);
+ BEAST_EXPECT(b.size() == len);
+ b.consume(1);
+ BEAST_EXPECT(b.size() == len - 1);
+ b.consume(len - 1);
+ BEAST_EXPECTS(b.size() == 0, typeid(b).name());
+ }
+
+ // max_size
+ {
+ DynamicBuffer_v0 b(b0);
+ if(b.max_size() + 1 > b.max_size())
+ {
+ try
+ {
+ b.prepare(b.max_size() + 1);
+ BEAST_FAIL();
+ }
+ catch(std::length_error const&)
+ {
+ BEAST_PASS();
+ }
+ catch(...)
+ {
+ BEAST_FAIL();
+ }
+ }
+ else
+ BEAST_EXPECT(b.max_size() == std::numeric_limits::max());
+ }
+
+ // setup source buffer
+ char buf[13];
+ unsigned char k0 = 0;
+ string_view src(buf, sizeof(buf));
+ if(src.size() > b0.max_size())
+ src = {src.data(), b0.max_size()};
+ BEAST_EXPECT(b0.max_size() >= src.size());
+ BEAST_EXPECT(b0.size() == 0);
+ BEAST_EXPECT(buffer_bytes(b0.data()) == 0);
+ auto const make_new_src =
+ [&buf, &k0, &src]
+ {
+ auto k = k0++;
+ for(std::size_t i = 0; i < src.size(); ++i)
+ buf[i] = k++;
+ };
+
+ // readable / writable buffer sequence tests
+ {
+ make_new_src();
+ DynamicBuffer_v0 b(b0);
+ auto const& bc(b);
+ auto const mb = b.prepare(src.size());
+ BEAST_EXPECT(buffer_bytes(mb) == src.size());
+ beast::test_buffer_sequence(mb);
+ b.commit(net::buffer_copy(mb,
+ net::const_buffer(src.data(), src.size())));
+ BEAST_EXPECT(
+ buffer_bytes(bc.data()) == src.size());
+ beast::test_buffer_sequence(bc.data());
+ }
+
+ // h = in size
+ // i = prepare size
+ // j = commit size
+ // k = consume size
+ for(std::size_t h = 1; h <= src.size(); ++h)
+ {
+ string_view in(src.data(), h);
+ for(std::size_t i = 1; i <= in.size(); ++i) {
+ for(std::size_t j = 1; j <= i + 1; ++j) {
+ for(std::size_t k = 1; k <= in.size(); ++k) {
+ {
+ make_new_src();
+
+ const std::size_t len = b0.size();
+ DynamicBuffer_v0 b(b0);
+
+ auto const& bc(b);
+ net::const_buffer cb(in.data(), in.size());
+ while(cb.size() > 0)
+ {
+ auto const mb = b.prepare(
+ std::min(i,
+ b.max_size() - b.size()));
+ auto const n = net::buffer_copy(mb,
+ net::const_buffer(cb.data(),
+ std::min(j, cb.size())));
+ b.commit(n);
+ cb += n;
+ }
+ BEAST_EXPECT(b.size() == (in.size() + len));
+ BEAST_EXPECT(buffer_bytes(bc.data()) == in.size() + len);
+ BEAST_EXPECT(beast::buffers_to_string(bc.data()).substr(len) == in);
+ while(b.size() > 0)
+ b.consume(k);
+ BEAST_EXPECT(buffer_bytes(bc.data()) == 0);
+ }
+ } } }
+ }
+
+ // MutableDynamicBuffer_v0 refinement
+ detail::test_mutable_dynamic_buffer(b0,
+ is_mutable_dynamic_buffer{});
+}
+
} // beast
} // boost