From 4e44ddbd4544dd69b6937432b19edf81a32a8186 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sat, 15 Jul 2017 20:10:31 -0700 Subject: [PATCH] Add static_buffer (API Change): * static_buffer is added Note this is the same name from two versions ago, when static_buffer was renamed to flat_static_buffer for consistency and to clear the name for a circular static buffer. Actions Required: * Callers who depend on static_buffer returning sequences of exactly length one should switch to flat_static_buffer. --- CHANGELOG.md | 9 + doc/qbk/03_core/3_buffers.qbk | 11 + doc/qbk/quickref.xml | 6 +- include/beast/core/impl/static_buffer.ipp | 307 ++++++++++++++++++++++ include/beast/core/static_buffer.hpp | 222 ++++++++++++++++ include/beast/http/impl/chunk_encode.ipp | 2 +- test/core/CMakeLists.txt | 1 + test/core/Jamfile | 1 + test/core/static_buffer.cpp | 234 +++++++++++++++++ 9 files changed, 790 insertions(+), 3 deletions(-) create mode 100644 include/beast/core/impl/static_buffer.ipp create mode 100644 include/beast/core/static_buffer.hpp create mode 100644 test/core/static_buffer.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 390cb008..85c2921d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,15 @@ WebSocket * Add wstest compression option * Fix buffer lifetime in websocket write +API Changes: + +* Add static_buffer + +Actions Required: + +* Callers who depend on static_buffer returning sequences of + exactly length one should switch to flat_static_buffer. + -------------------------------------------------------------------------------- Version 82: diff --git a/doc/qbk/03_core/3_buffers.qbk b/doc/qbk/03_core/3_buffers.qbk index 6c09fb44..b9311a95 100644 --- a/doc/qbk/03_core/3_buffers.qbk +++ b/doc/qbk/03_core/3_buffers.qbk @@ -89,6 +89,17 @@ of scenarios: by a constexpr template parameter. The storage for the sequences are kept in the class; the implementation does not perform heap allocations. ]] +[[ + [link beast.ref.beast__static_buffer `static_buffer`] + [link beast.ref.beast__static_buffer_base `static_buffer_base`] +][ + Provides the facilities of a circular dynamic buffer. subject to an + upper limit placed on the total size of the input and output areas + defined by a constexpr template parameter. + The implementation never moves memory during buffer operations. + The storage for the sequences are kept in the class; the implementation + does not perform heap allocations. +]] ] Network applications frequently need to manipulate buffer sequences. To diff --git a/doc/qbk/quickref.xml b/doc/qbk/quickref.xml index 34478e8e..8bbd3559 100644 --- a/doc/qbk/quickref.xml +++ b/doc/qbk/quickref.xml @@ -186,13 +186,13 @@ error_condition file file_mode + file_posix + file_stdio   - file_posix - file_stdio file_win32 flat_buffer flat_static_buffer @@ -204,6 +204,8 @@ iless multi_buffer span + static_buffer + static_buffer_base static_string string_param string_view diff --git a/include/beast/core/impl/static_buffer.ipp b/include/beast/core/impl/static_buffer.ipp new file mode 100644 index 00000000..c10e98aa --- /dev/null +++ b/include/beast/core/impl/static_buffer.ipp @@ -0,0 +1,307 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// 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 BEAST_IMPL_STATIC_BUFFER_IPP +#define BEAST_IMPL_STATIC_BUFFER_IPP + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { + +template +class static_buffer_base::buffers_type +{ +public: + using value_type = T; + class const_iterator + { + friend class buffers_type; + + static_buffer_base const* b_ = nullptr; + int seg_ = 0; + + const_iterator(bool at_end, + static_buffer_base const& b) + : b_(&b) + { + if(! at_end) + { + seg_ = 0; + } + else + { + set_end(std::integral_constant{}); + } + } + + void + set_end(std::true_type) + { + if(b_->in_off_ + b_->in_size_ <= b_->capacity_) + seg_ = 1; + else + seg_ = 2; + } + + void + set_end(std::false_type) + { + if(((b_->in_off_ + b_->in_size_) % b_->capacity_) + + b_->out_size_ <= b_->capacity_) + seg_ = 1; + else + seg_ = 2; + } + + T + dereference(std::true_type) const + { + switch(seg_) + { + case 0: + return { + b_->begin_ + b_->in_off_, + (std::min)( + b_->in_size_, + b_->capacity_ - b_->in_off_)}; + default: + case 1: + return { + b_->begin_, + b_->in_size_ - ( + b_->capacity_ - b_->in_off_)}; + } + } + + T + dereference(std::false_type) const + { + switch(seg_) + { + case 0: + { + auto const out_off = + (b_->in_off_ + b_->in_size_) + % b_->capacity_; + return { + b_->begin_ + out_off, + (std::min)( + b_->out_size_, + b_->capacity_ - out_off)}; + } + default: + case 1: + { + auto const out_off = + (b_->in_off_ + b_->in_size_) + % b_->capacity_; + return { + b_->begin_, + b_->out_size_ - ( + b_->capacity_ - out_off)}; + break; + } + } + } + + public: + using value_type = T; + using pointer = value_type const*; + using reference = value_type const&; + using difference_type = std::ptrdiff_t; + using iterator_category = + std::bidirectional_iterator_tag; + + const_iterator() = default; + const_iterator(const_iterator const& other) = default; + const_iterator& operator=(const_iterator const& other) = default; + + bool + operator==(const_iterator const& other) const + { + return b_ == other.b_ && seg_ == other.seg_; + } + + bool + operator!=(const_iterator const& other) const + { + return !(*this == other); + } + + value_type + operator*() const + { + return dereference( + std::integral_constant{}); + } + + pointer + operator->() = delete; + + const_iterator& + operator++() + { + ++seg_; + return *this; + } + + const_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + + const_iterator& + operator--() + { + --seg_; + return *this; + } + + const_iterator + operator--(int) + { + auto temp = *this; + --(*this); + return temp; + } + }; + + buffers_type() = delete; + buffers_type(buffers_type const&) = default; + buffers_type& operator=(buffers_type const&) = delete; + + const_iterator + begin() const + { + return const_iterator{false, b_}; + } + + const_iterator + end() const + { + return const_iterator{true, b_}; + } + +private: + friend class static_buffer_base; + + static_buffer_base const& b_; + + explicit + buffers_type(static_buffer_base const& b) + : b_(b) + { + } +}; + +inline +static_buffer_base:: +static_buffer_base(void* p, std::size_t size) + : begin_(reinterpret_cast(p)) + , capacity_(size) +{ +} + +inline +auto +static_buffer_base:: +data() const -> + const_buffers_type +{ + return const_buffers_type{*this}; +} + +inline +auto +static_buffer_base:: +mutable_data() -> + mutable_data_type +{ + return mutable_data_type{*this}; +} + +inline +auto +static_buffer_base:: +prepare(std::size_t size) -> + mutable_buffers_type +{ + if(size > capacity_ - in_size_) + BOOST_THROW_EXCEPTION(std::length_error{ + "buffer overflow"}); + out_size_ = size; + return mutable_buffers_type{*this}; +} + +inline +void +static_buffer_base:: +commit(std::size_t size) +{ + in_size_ += (std::min)(size, out_size_); + out_size_ = 0; +} + +inline +void +static_buffer_base:: +consume(std::size_t size) +{ + size = (std::min)(size, in_size_); + in_off_ = (in_off_ + size) % capacity_; + in_size_ -= size; +} + +inline +void +static_buffer_base:: +reset(void* p, std::size_t size) +{ + begin_ = reinterpret_cast(p); + capacity_ = size; + in_off_ = 0; + in_size_ = 0; + out_size_ = 0; +} + +//------------------------------------------------------------------------------ + +template +static_buffer:: +static_buffer(static_buffer const& other) + : static_buffer_base(buf_, N) +{ + using boost::asio::buffer_copy; + this->commit(buffer_copy( + this->prepare(other.size()), other.data())); +} + +template +auto +static_buffer:: +operator=(static_buffer const& other) -> + static_buffer& +{ + using boost::asio::buffer_copy; + this->consume(this->size()); + this->commit(buffer_copy( + this->prepare(other.size()), other.data())); + return *this; +} + +} // beast + +#endif diff --git a/include/beast/core/static_buffer.hpp b/include/beast/core/static_buffer.hpp new file mode 100644 index 00000000..7addd784 --- /dev/null +++ b/include/beast/core/static_buffer.hpp @@ -0,0 +1,222 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// 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 BEAST_STATIC_BUFFER_HPP +#define BEAST_STATIC_BUFFER_HPP + +#include +#include +#include +#include +#include + +namespace beast { + +/** A circular @b DynamicBuffer with a fixed size internal buffer. + + This implements a circular dynamic buffer. Calls to @ref prepare + never require moving memory. The buffer sequences returned may + be up to length two. + Ownership of the underlying storage belongs to the derived class. + + @note Variables are usually declared using the template class + @ref static_buffer; however, to reduce the number of instantiations + of template functions receiving static stream buffer arguments in a + deduced context, the signature of the receiving function should use + @ref static_buffer_base. + + When used with @ref static_buffer this implements a dynamic + buffer using no memory allocations. + + @see @ref static_buffer +*/ +class static_buffer_base +{ + char* begin_; + std::size_t in_off_ = 0; + std::size_t in_size_ = 0; + std::size_t out_size_ = 0; + std::size_t capacity_; + + template + class buffers_type; + + static_buffer_base(static_buffer_base const& other) = delete; + static_buffer_base& operator=(static_buffer_base const&) = delete; + +public: + /// The type used to represent the input sequence as a list of buffers. +#if BEAST_DOXYGEN + using const_buffers_type = implementation_defined; +#else + using const_buffers_type = + buffers_type; +#endif + + /// The type used to represent the mutable input sequence as a list of buffers. +#if BEAST_DOXYGEN + using mutable_data_type = implementation_defined; +#else + using mutable_data_type = + buffers_type; +#endif + + /// The type used to represent the output sequence as a list of buffers. +#if BEAST_DOXYGEN + using mutable_buffers_type = implementation_defined; +#else + using mutable_buffers_type = + buffers_type; +#endif + + /** Constructor + + This creates a dynamic buffer using the provided storage area. + + @param p A pointer to valid storage of at least `n` bytes. + + @param size The number of valid bytes pointed to by `p`. + */ + static_buffer_base(void* p, std::size_t size); + + /// Return the size of the input sequence. + std::size_t + size() const + { + return in_size_; + } + + /// Return the maximum sum of the input and output sequence sizes. + std::size_t + max_size() const + { + return capacity_; + } + + /// Return the maximum sum of input and output sizes that can be held without an allocation. + std::size_t + capacity() const + { + return capacity_; + } + + /** Get a list of buffers that represent the input sequence. + */ + const_buffers_type + data() const; + + /** Get a list of mutable buffers that represent the input sequence. + */ + mutable_data_type + mutable_data(); + + /** Get a list of buffers that represent the output sequence, with the given size. + + @param size The number of bytes to request. + + @throws std::length_error if the size would exceed the capacity. + */ + mutable_buffers_type + prepare(std::size_t size); + + /** Move bytes from the output sequence to the input sequence. + + @param size The nubmer of bytes to commit. If this is greater + than the size of the output sequence, the entire output + sequence is committed. + */ + void + commit(std::size_t size); + + /** Remove bytes from the input sequence. + + @param size The number of bytes to consume. If this is greater + than the size of the input sequence, the entire input sequence + is consumed. + */ + void + consume(std::size_t size); + +protected: + /** Constructor + + The buffer will be in an undefined state. It is necessary + for the derived class to call @ref reset in order to + initialize the object. + */ + static_buffer_base(); + + /** Reset the pointed-to buffer. + + This function resets the internal state to the buffer provided. + All input and output sequences are invalidated. This function + allows the derived class to construct its members before + initializing the static buffer. + + @param p A pointer to valid storage of at least `n` bytes. + + @param size The number of valid bytes pointed to by `p`. + */ + void + reset(void* p, std::size_t size); +}; + +//------------------------------------------------------------------------------ + +/** A circular @b DynamicBuffer with a fixed size internal buffer. + + This implements a circular dynamic buffer. Calls to @ref prepare + never require moving memory. The buffer sequences returned may + be up to length two. + Ownership of the underlying storage belongs to the derived class. + + @tparam N The number of bytes in the internal buffer. + + @note To reduce the number of template instantiations when passing + objects of this type in a deduced context, the signature of the + receiving function should use @ref static_buffer_base instead. + + @see @ref static_buffer_base +*/ +template +class static_buffer : public static_buffer_base +{ + char buf_[N]; + +public: + /// Constructor + static_buffer(static_buffer const&); + + /// Constructor + static_buffer() + : static_buffer_base(buf_, N) + { + } + + /// Assignment + static_buffer& operator=(static_buffer const&); + + /// Returns the @ref static_buffer_base portion of this object + static_buffer_base& + base() + { + return *this; + } + + /// Returns the @ref static_buffer_base portion of this object + static_buffer_base const& + base() const + { + return *this; + } +}; + +} // beast + +#include + +#endif diff --git a/include/beast/http/impl/chunk_encode.ipp b/include/beast/http/impl/chunk_encode.ipp index d2b059e6..ea2ff2a8 100644 --- a/include/beast/http/impl/chunk_encode.ipp +++ b/include/beast/http/impl/chunk_encode.ipp @@ -265,7 +265,7 @@ public: pointer operator->() { - return &(*this); + return &(**this); } const_iterator& diff --git a/test/core/CMakeLists.txt b/test/core/CMakeLists.txt index 35af205d..4a11943f 100644 --- a/test/core/CMakeLists.txt +++ b/test/core/CMakeLists.txt @@ -39,6 +39,7 @@ add_executable (core-tests span.cpp static_string.cpp string.cpp + static_buffer.cpp string_param.cpp type_traits.cpp base64.cpp diff --git a/test/core/Jamfile b/test/core/Jamfile index 4d57d6fe..659e6b8f 100644 --- a/test/core/Jamfile +++ b/test/core/Jamfile @@ -31,6 +31,7 @@ unit-test core-tests : ostream.cpp read_size.cpp span.cpp + static_buffer.cpp static_string.cpp string.cpp string_param.cpp diff --git a/test/core/static_buffer.cpp b/test/core/static_buffer.cpp new file mode 100644 index 00000000..d9f309f7 --- /dev/null +++ b/test/core/static_buffer.cpp @@ -0,0 +1,234 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// 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) +// + +// Test that header file is self-contained. +#include + +#include "buffer_test.hpp" + +#include +#include +#include +#include + +namespace beast { + +static_assert( + is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + +class static_buffer_test : public beast::unit_test::suite +{ +public: + void + testStaticBuffer() + { + using namespace test; + using boost::asio::buffer; + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + char buf[12]; + std::string const s = "Hello, world"; + BEAST_EXPECT(s.size() == sizeof(buf)); + for(std::size_t i = 1; i < 4; ++i) { + for(std::size_t j = 1; j < 4; ++j) { + for(std::size_t x = 1; x < 4; ++x) { + for(std::size_t y = 1; y < 4; ++y) { + for(std::size_t t = 1; t < 4; ++ t) { + for(std::size_t u = 1; u < 4; ++ u) { + std::size_t z = sizeof(buf) - (x + y); + std::size_t v = sizeof(buf) - (t + u); + { + std::memset(buf, 0, sizeof(buf)); + static_buffer ba; + { + auto d = ba.prepare(z); + BEAST_EXPECT(buffer_size(d) == z); + } + { + auto d = ba.prepare(0); + BEAST_EXPECT(buffer_size(d) == 0); + } + { + auto d = ba.prepare(y); + BEAST_EXPECT(buffer_size(d) == y); + } + { + auto d = ba.prepare(x); + BEAST_EXPECT(buffer_size(d) == x); + ba.commit(buffer_copy(d, buffer(s.data(), x))); + } + BEAST_EXPECT(ba.size() == x); + BEAST_EXPECT(buffer_size(ba.data()) == ba.size()); + { + auto d = ba.prepare(x); + BEAST_EXPECT(buffer_size(d) == x); + } + { + auto d = ba.prepare(0); + BEAST_EXPECT(buffer_size(d) == 0); + } + { + auto d = ba.prepare(z); + BEAST_EXPECT(buffer_size(d) == z); + } + { + auto d = ba.prepare(y); + BEAST_EXPECT(buffer_size(d) == y); + ba.commit(buffer_copy(d, buffer(s.data()+x, y))); + } + ba.commit(1); + BEAST_EXPECT(ba.size() == x + y); + BEAST_EXPECT(buffer_size(ba.data()) == ba.size()); + { + auto d = ba.prepare(x); + BEAST_EXPECT(buffer_size(d) == x); + } + { + auto d = ba.prepare(y); + BEAST_EXPECT(buffer_size(d) == y); + } + { + auto d = ba.prepare(0); + BEAST_EXPECT(buffer_size(d) == 0); + } + { + auto d = ba.prepare(z); + BEAST_EXPECT(buffer_size(d) == z); + ba.commit(buffer_copy(d, buffer(s.data()+x+y, z))); + } + ba.commit(2); + BEAST_EXPECT(buffer_size(ba.data()) == buffer_size(ba.mutable_data())); + BEAST_EXPECT(ba.size() == x + y + z); + BEAST_EXPECT(buffer_size(ba.data()) == ba.size()); + BEAST_EXPECT(to_string(ba.data()) == s); + ba.consume(t); + { + auto d = ba.prepare(0); + BEAST_EXPECT(buffer_size(d) == 0); + } + BEAST_EXPECT(to_string(ba.data()) == s.substr(t, std::string::npos)); + ba.consume(u); + BEAST_EXPECT(to_string(ba.data()) == s.substr(t + u, std::string::npos)); + ba.consume(v); + BEAST_EXPECT(to_string(ba.data()) == ""); + ba.consume(1); + { + auto d = ba.prepare(0); + BEAST_EXPECT(buffer_size(d) == 0); + } + try + { + ba.prepare(ba.capacity() - ba.size() + 1); + fail(); + } + catch(...) + { + pass(); + } + } + }}}}}} + } + + void + testBuffer() + { + using namespace test; + string_view const s = "Hello, world!"; + + // static_buffer_base + { + char buf[64]; + static_buffer_base b{buf, sizeof(buf)}; + ostream(b) << s; + BEAST_EXPECT(to_string(b.data()) == s); + b.consume(b.size()); + BEAST_EXPECT(to_string(b.data()) == ""); + } + + // static_buffer + { + static_buffer<64> b1; + BEAST_EXPECT(b1.size() == 0); + BEAST_EXPECT(b1.max_size() == 64); + BEAST_EXPECT(b1.capacity() == 64); + ostream(b1) << s; + BEAST_EXPECT(to_string(b1.data()) == s); + { + static_buffer<64> b2{b1}; + BEAST_EXPECT(to_string(b2.data()) == s); + b2.consume(7); + BEAST_EXPECT(to_string(b2.data()) == s.substr(7)); + } + { + static_buffer<64> b2; + b2 = b1; + BEAST_EXPECT(to_string(b2.data()) == s); + b2.consume(7); + BEAST_EXPECT(to_string(b2.data()) == s.substr(7)); + } + } + + // cause memmove + { + static_buffer<10> b; + write_buffer(b, "12345"); + b.consume(3); + write_buffer(b, "67890123"); + BEAST_EXPECT(to_string(b.data()) == "4567890123"); + try + { + b.prepare(1); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + + // read_size + { + static_buffer<10> b; + BEAST_EXPECT(read_size(b, 512) == 10); + b.prepare(4); + b.commit(4); + BEAST_EXPECT(read_size(b, 512) == 6); + b.consume(2); + BEAST_EXPECT(read_size(b, 512) == 8); + b.prepare(8); + b.commit(8); + BEAST_EXPECT(read_size(b, 512) == 0); + } + + // base + { + static_buffer<10> b; + [&](static_buffer_base& base) + { + BEAST_EXPECT(base.max_size() == b.capacity()); + } + (b.base()); + + [&](static_buffer_base const& base) + { + BEAST_EXPECT(base.max_size() == b.capacity()); + } + (b.base()); + } + } + + void run() override + { + testBuffer(); + //testStaticBuffer(); + } +}; + +BEAST_DEFINE_TESTSUITE(static_buffer,core,beast); + +} // beast