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