// // Copyright (c) 2016-2019 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) // // Official repository: https://github.com/boostorg/beast // #ifndef BOOST_BEAST_IMPL_MULTI_BUFFER_HPP #define BOOST_BEAST_IMPL_MULTI_BUFFER_HPP #include #include #include #include #include #include #include #include #include #include #include #include namespace boost { namespace beast { /* These diagrams illustrate the layout and state variables. 1 Input and output contained entirely in one element: 0 out_ |<------+-----------+--------------------------------+----->| in_pos_ out_pos_ out_end_ 2 Output contained in first and second elements: out_ |<------+-----------+------>| |<-------------------+----->| in_pos_ out_pos_ out_end_ 3 Output contained in the second element: out_ |<------+------------------>| |<----+--------------+----->| in_pos_ out_pos_ out_end_ 4 Output contained in second and third elements: out_ |<------+------->| |<-------+------>| |<---------+----->| in_pos_ out_pos_ out_end_ 5 Input sequence is empty: out_ |<------+------------------>| |<-------------------+----->| out_pos_ out_end_ in_pos_ 6 Output sequence is empty: out_ |<------+------------------>| |<------+------------------>| in_pos_ out_pos_ out_end_ 7 The end of output can point to the end of an element. But out_pos_ should never point to the end: out_ |<------+------------------>| |<------+------------------>| in_pos_ out_pos_ out_end_ 8 When the input sequence entirely fills the last element and the output sequence is empty, out_ will point to the end of the list of buffers, and out_pos_ and out_end_ will be 0: |<------+------------------>| out_ == list_.end() in_pos_ out_pos_ == 0 out_end_ == 0 */ //------------------------------------------------------------------------------ #if BOOST_WORKAROUND(BOOST_MSVC, < 1910) # pragma warning (push) # pragma warning (disable: 4521) // multiple copy constructors specified # pragma warning (disable: 4522) // multiple assignment operators specified #endif template template class basic_multi_buffer::subrange { basic_multi_buffer const* b_; const_iter begin_; const_iter end_; size_type begin_pos_; // offset in begin_ size_type last_pos_; // offset in std::prev(end_) friend class basic_multi_buffer; subrange( basic_multi_buffer const& b, size_type pos, size_type n) noexcept : b_(&b) { auto const set_empty = [&] { begin_ = b_->list_.end(); end_ = b_->list_.end(); begin_pos_ = 0; last_pos_ = 0; }; // VFALCO Handle this trivial case of // pos larger than total size, otherwise // the addition to pos can overflow. //if(pos >= b_->in_size_) // skip unused prefix pos = pos + b_->in_pos_; // iterate the buffers auto it = b_->list_.begin(); // is the list empty? if(it == b_->list_.end()) { set_empty(); return; } // is the requested size zero? if(n == 0) { set_empty(); return; } // get last buffer and its size auto const last = std::prev(b_->list_.end()); auto const last_end = [&] { if(b_->out_end_ == 0) return last->size(); return b_->out_end_; }(); // only one buffer in list? if(it == last) { if(pos >= last_end) { set_empty(); return; } begin_ = it; begin_pos_ = pos; end_ = std::next(it); if(n > last_end - pos) last_pos_ = last_end; else last_pos_ = pos + n; return; } for(;;) { // is pos in this buffer? if(pos < it->size()) { begin_ = it; begin_pos_ = pos; // does this buffer satisfy n? auto const avail = it->size() - pos; if(n <= avail) { end_ = ++it; last_pos_ = pos + n; return; } n -= avail; ++it; break; } pos -= it->size(); ++it; // did we reach the last buffer? if(it == last) { // is pos past the end? if(pos >= last_end) { set_empty(); return; } // satisfy the request begin_ = it; begin_pos_ = pos; end_ = std::next(it); if(n < last_end - pos) last_pos_ = pos + n; else last_pos_ = last_end; return; } } // find pos+n for(;;) { if(it == last) { end_ = ++it; if(n >= last_end) last_pos_ = last_end; else last_pos_ = n; return; } if(n <= it->size()) { end_ = ++it; last_pos_ = n; return; } n -= it->size(); ++it; } } public: using value_type = typename std::conditional< isMutable, net::mutable_buffer, net::const_buffer>::type; class const_iterator; subrange() = delete; #if BOOST_WORKAROUND(BOOST_MSVC, < 1910) subrange(subrange const& other) : b_(other.b_) , begin_(other.begin_) , end_(other.end_) , begin_pos_(other.begin_pos_) , last_pos_(other.last_pos_) { } subrange& operator=(subrange const& other) { b_ = other.b_; begin_ = other.begin_; end_ = other.end_; begin_pos_ = other.begin_pos_; last_pos_ = other.last_pos_; return *this; } #else subrange(subrange const&) = default; subrange& operator=(subrange const&) = default; #endif template< bool isMutable_ = isMutable, class = typename std::enable_if::type> subrange( subrange const& other) noexcept : b_(other.b_) , begin_(other.begin_) , end_(other.end_) , begin_pos_(other.begin_pos_) , last_pos_(other.last_pos_) { } template< bool isMutable_ = isMutable, class = typename std::enable_if::type> subrange& operator=( subrange const& other) noexcept { b_ = other.b_; begin_ = other.begin_; end_ = other.end_; begin_pos_ = other.begin_pos_; last_pos_ = other.last_pos_; return *this; } const_iterator begin() const noexcept; const_iterator end() const noexcept; std::size_t buffer_bytes() const noexcept { return b_->size(); } }; #if BOOST_WORKAROUND(BOOST_MSVC, < 1910) # pragma warning (pop) #endif //------------------------------------------------------------------------------ template template class basic_multi_buffer:: subrange:: const_iterator { friend class subrange; subrange const* sr_ = nullptr; typename list_type::const_iterator it_; const_iterator( subrange const& sr, typename list_type::const_iterator const& it) noexcept : sr_(&sr) , it_(it) { } public: using value_type = typename subrange::value_type; using pointer = value_type const*; using reference = value_type; 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 noexcept { return sr_ == other.sr_ && it_ == other.it_; } bool operator!=( const_iterator const& other) const noexcept { return !(*this == other); } reference operator*() const noexcept { value_type result; BOOST_ASSERT(sr_->last_pos_ != 0); if(it_ == std::prev(sr_->end_)) result = { it_->data(), sr_->last_pos_ }; else result = { it_->data(), it_->size() }; if(it_ == sr_->begin_) result += sr_->begin_pos_; return result; } pointer operator->() const = delete; const_iterator& operator++() noexcept { ++it_; return *this; } const_iterator operator++(int) noexcept { auto temp = *this; ++(*this); return temp; } const_iterator& operator--() noexcept { --it_; return *this; } const_iterator operator--(int) noexcept { auto temp = *this; --(*this); return temp; } }; //------------------------------------------------------------------------------ template template auto basic_multi_buffer:: subrange:: begin() const noexcept -> const_iterator { return const_iterator( *this, begin_); } template template auto basic_multi_buffer:: subrange:: end() const noexcept -> const_iterator { return const_iterator( *this, end_); } //------------------------------------------------------------------------------ template basic_multi_buffer:: ~basic_multi_buffer() { destroy(list_); } template basic_multi_buffer:: basic_multi_buffer() noexcept(default_nothrow) : max_(alloc_traits::max_size(this->get())) , out_(list_.end()) { } template basic_multi_buffer:: basic_multi_buffer( std::size_t limit) noexcept(default_nothrow) : max_(limit) , out_(list_.end()) { } template basic_multi_buffer:: basic_multi_buffer( Allocator const& alloc) noexcept : boost::empty_value( boost::empty_init_t(), alloc) , max_(alloc_traits::max_size(this->get())) , out_(list_.end()) { } template basic_multi_buffer:: basic_multi_buffer( std::size_t limit, Allocator const& alloc) noexcept : boost::empty_value( boost::empty_init_t(), alloc) , max_(limit) , out_(list_.end()) { } template basic_multi_buffer:: basic_multi_buffer( basic_multi_buffer&& other) noexcept : boost::empty_value( boost::empty_init_t(), std::move(other.get())) , max_(other.max_) , in_size_(boost::exchange(other.in_size_, 0)) , in_pos_(boost::exchange(other.in_pos_, 0)) , out_pos_(boost::exchange(other.out_pos_, 0)) , out_end_(boost::exchange(other.out_end_, 0)) { auto const at_end = other.out_ == other.list_.end(); list_ = std::move(other.list_); out_ = at_end ? list_.end() : other.out_; other.out_ = other.list_.end(); } template basic_multi_buffer:: basic_multi_buffer( basic_multi_buffer&& other, Allocator const& alloc) : boost::empty_value( boost::empty_init_t(), alloc) , max_(other.max_) { if(this->get() != other.get()) { out_ = list_.end(); copy_from(other); return; } auto const at_end = other.out_ == other.list_.end(); list_ = std::move(other.list_); out_ = at_end ? list_.end() : other.out_; in_size_ = other.in_size_; in_pos_ = other.in_pos_; out_pos_ = other.out_pos_; out_end_ = other.out_end_; other.in_size_ = 0; other.out_ = other.list_.end(); other.in_pos_ = 0; other.out_pos_ = 0; other.out_end_ = 0; } template basic_multi_buffer:: basic_multi_buffer( basic_multi_buffer const& other) : boost::empty_value( boost::empty_init_t(), alloc_traits:: select_on_container_copy_construction( other.get())) , max_(other.max_) , out_(list_.end()) { copy_from(other); } template basic_multi_buffer:: basic_multi_buffer( basic_multi_buffer const& other, Allocator const& alloc) : boost::empty_value( boost::empty_init_t(), alloc) , max_(other.max_) , out_(list_.end()) { copy_from(other); } template template basic_multi_buffer:: basic_multi_buffer( basic_multi_buffer const& other) : out_(list_.end()) { copy_from(other); } template template basic_multi_buffer:: basic_multi_buffer( basic_multi_buffer const& other, allocator_type const& alloc) : boost::empty_value( boost::empty_init_t(), alloc) , max_(other.max_) , out_(list_.end()) { copy_from(other); } template auto basic_multi_buffer:: operator=(basic_multi_buffer&& other) -> basic_multi_buffer& { if(this == &other) return *this; clear(); max_ = other.max_; move_assign(other, pocma{}); return *this; } template auto basic_multi_buffer:: operator=(basic_multi_buffer const& other) -> basic_multi_buffer& { if(this == &other) return *this; copy_assign(other, pocca{}); return *this; } template template auto basic_multi_buffer:: operator=( basic_multi_buffer const& other) -> basic_multi_buffer& { copy_from(other); return *this; } //------------------------------------------------------------------------------ template std::size_t basic_multi_buffer:: capacity() const noexcept { auto pos = out_; if(pos == list_.end()) return in_size_; auto n = pos->size() - out_pos_; while(++pos != list_.end()) n += pos->size(); return in_size_ + n; } template auto basic_multi_buffer:: data() const noexcept -> const_buffers_type { return const_buffers_type( *this, 0, in_size_); } template auto basic_multi_buffer:: data() noexcept -> mutable_buffers_type { return mutable_buffers_type( *this, 0, in_size_); } template void basic_multi_buffer:: reserve(std::size_t n) { // VFALCO The amount needs to be adjusted for // the sizeof(element) plus padding if(n > alloc_traits::max_size(this->get())) BOOST_THROW_EXCEPTION(std::length_error( "A basic_multi_buffer exceeded the allocator's maximum size")); std::size_t total = in_size_; if(n <= total) return; if(out_ != list_.end()) { total += out_->size() - out_pos_; if(n <= total) return; for(auto it = out_;;) { if(++it == list_.end()) break; total += it->size(); if(n <= total) return; } } BOOST_ASSERT(n > total); (void)prepare(n - size()); } template void basic_multi_buffer:: shrink_to_fit() { // empty list if(list_.empty()) return; // zero readable bytes if(in_size_ == 0) { destroy(list_); list_.clear(); out_ = list_.end(); in_size_ = 0; in_pos_ = 0; out_pos_ = 0; out_end_ = 0; #if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); #endif return; } // one or more unused output buffers if(out_ != list_.end()) { if(out_ != list_.iterator_to(list_.back())) { // unused list list_type extra; extra.splice( extra.end(), list_, std::next(out_), list_.end()); destroy(extra); #if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); #endif } // unused out_ BOOST_ASSERT(out_ == list_.iterator_to(list_.back())); if(out_pos_ == 0) { BOOST_ASSERT(out_ != list_.begin()); auto& e = *out_; list_.erase(out_); out_ = list_.end(); destroy(e); out_end_ = 0; #if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); #endif } } auto const replace = [&](iter pos, element& e) { auto it = list_.insert(pos, e); auto& e0 = *pos; list_.erase(pos); destroy(e0); return it; }; // partial last buffer if(list_.size() > 1 && out_ != list_.end()) { BOOST_ASSERT(out_ == list_.iterator_to(list_.back())); BOOST_ASSERT(out_pos_ != 0); auto& e = alloc(out_pos_); std::memcpy( e.data(), out_->data(), out_pos_); replace(out_, e); out_ = list_.end(); out_pos_ = 0; out_end_ = 0; #if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); #endif } // partial first buffer if(in_pos_ != 0) { if(out_ != list_.begin()) { auto const n = list_.front().size() - in_pos_; auto& e = alloc(n); std::memcpy( e.data(), list_.front().data() + in_pos_, n); replace(list_.begin(), e); in_pos_ = 0; } else { BOOST_ASSERT(list_.size() == 1); BOOST_ASSERT(out_pos_ > in_pos_); auto const n = out_pos_ - in_pos_; auto& e = alloc(n); std::memcpy( e.data(), list_.front().data() + in_pos_, n); replace(list_.begin(), e); in_pos_ = 0; out_ = list_.end(); } #if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); #endif } } template void basic_multi_buffer:: clear() noexcept { out_ = list_.begin(); in_size_ = 0; in_pos_ = 0; out_pos_ = 0; out_end_ = 0; } template auto basic_multi_buffer:: prepare(size_type n) -> mutable_buffers_type { auto const n0 = n; if(in_size_ > max_ || n > (max_ - in_size_)) BOOST_THROW_EXCEPTION(std::length_error{ "basic_multi_buffer too long"}); list_type reuse; std::size_t total = in_size_; // put all empty buffers on reuse list if(out_ != list_.end()) { total += out_->size() - out_pos_; if(out_ != list_.iterator_to(list_.back())) { out_end_ = out_->size(); reuse.splice(reuse.end(), list_, std::next(out_), list_.end()); #if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); #endif } auto const avail = out_->size() - out_pos_; if(n > avail) { out_end_ = out_->size(); n -= avail; } else { out_end_ = out_pos_ + n; n = 0; } #if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); #endif } // get space from reuse buffers while(n > 0 && ! reuse.empty()) { auto& e = reuse.front(); reuse.erase(reuse.iterator_to(e)); list_.push_back(e); total += e.size(); if(n > e.size()) { out_end_ = e.size(); n -= e.size(); } else { out_end_ = n; n = 0; } #if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); #endif } BOOST_ASSERT(total <= max_); if(! reuse.empty() || n > 0) { destroy(reuse); if(n > 0) { std::size_t const growth_factor = 2; std::size_t altn = in_size_ * growth_factor; // Overflow detection: if(in_size_ > altn) altn = (std::numeric_limits::max)(); else altn = (std::max)(512, altn); auto const size = (std::min)( max_ - total, (std::max)(n, altn)); auto& e = alloc(size); list_.push_back(e); if(out_ == list_.end()) out_ = list_.iterator_to(e); out_end_ = n; #if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); #endif } } auto const result = mutable_buffers_type( *this, in_size_, n0); BOOST_ASSERT( net::buffer_size(result) == n0); return result; } template void basic_multi_buffer:: commit(size_type n) noexcept { if(list_.empty()) return; if(out_ == list_.end()) return; auto const back = list_.iterator_to(list_.back()); while(out_ != back) { auto const avail = out_->size() - out_pos_; if(n < avail) { out_pos_ += n; in_size_ += n; #if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); #endif return; } ++out_; n -= avail; out_pos_ = 0; in_size_ += avail; #if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); #endif } n = (std::min)(n, out_end_ - out_pos_); out_pos_ += n; in_size_ += n; if(out_pos_ == out_->size()) { ++out_; out_pos_ = 0; out_end_ = 0; } #if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); #endif } template void basic_multi_buffer:: consume(size_type n) noexcept { if(list_.empty()) return; for(;;) { if(list_.begin() != out_) { auto const avail = list_.front().size() - in_pos_; if(n < avail) { in_size_ -= n; in_pos_ += n; #if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); #endif break; } n -= avail; in_size_ -= avail; in_pos_ = 0; auto& e = list_.front(); list_.erase(list_.iterator_to(e)); destroy(e); #if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); #endif } else { auto const avail = out_pos_ - in_pos_; if(n < avail) { in_size_ -= n; in_pos_ += n; } else { in_size_ = 0; if(out_ != list_.iterator_to(list_.back()) || out_pos_ != out_end_) { in_pos_ = out_pos_; } else { // Input and output sequences are empty, reuse buffer. // Alternatively we could deallocate it. in_pos_ = 0; out_pos_ = 0; out_end_ = 0; } } #if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); #endif break; } } } template template void basic_multi_buffer:: copy_from(basic_multi_buffer const& other) { clear(); max_ = other.max_; if(other.size() == 0) return; commit(net::buffer_copy( prepare(other.size()), other.data())); } template void basic_multi_buffer:: move_assign(basic_multi_buffer& other, std::true_type) noexcept { this->get() = std::move(other.get()); auto const at_end = other.out_ == other.list_.end(); list_ = std::move(other.list_); out_ = at_end ? list_.end() : other.out_; in_size_ = other.in_size_; in_pos_ = other.in_pos_; out_pos_ = other.out_pos_; out_end_ = other.out_end_; max_ = other.max_; other.in_size_ = 0; other.out_ = other.list_.end(); other.in_pos_ = 0; other.out_pos_ = 0; other.out_end_ = 0; } template void basic_multi_buffer:: move_assign(basic_multi_buffer& other, std::false_type) { if(this->get() != other.get()) { copy_from(other); } else { move_assign(other, std::true_type{}); } } template void basic_multi_buffer:: copy_assign( basic_multi_buffer const& other, std::false_type) { copy_from(other); } template void basic_multi_buffer:: copy_assign( basic_multi_buffer const& other, std::true_type) { clear(); this->get() = other.get(); copy_from(other); } template void basic_multi_buffer:: swap(basic_multi_buffer& other) noexcept { swap(other, typename alloc_traits::propagate_on_container_swap{}); } template void basic_multi_buffer:: swap(basic_multi_buffer& other, std::true_type) noexcept { using std::swap; auto const at_end0 = out_ == list_.end(); auto const at_end1 = other.out_ == other.list_.end(); swap(this->get(), other.get()); swap(list_, other.list_); swap(out_, other.out_); if(at_end1) out_ = list_.end(); if(at_end0) other.out_ = other.list_.end(); swap(in_size_, other.in_size_); swap(in_pos_, other.in_pos_); swap(out_pos_, other.out_pos_); swap(out_end_, other.out_end_); } template void basic_multi_buffer:: swap(basic_multi_buffer& other, std::false_type) noexcept { BOOST_ASSERT(this->get() == other.get()); using std::swap; auto const at_end0 = out_ == list_.end(); auto const at_end1 = other.out_ == other.list_.end(); swap(list_, other.list_); swap(out_, other.out_); if(at_end1) out_ = list_.end(); if(at_end0) other.out_ = other.list_.end(); swap(in_size_, other.in_size_); swap(in_pos_, other.in_pos_); swap(out_pos_, other.out_pos_); swap(out_end_, other.out_end_); } template void swap( basic_multi_buffer& lhs, basic_multi_buffer& rhs) noexcept { lhs.swap(rhs); } template void basic_multi_buffer:: destroy(list_type& list) noexcept { for(auto it = list.begin(); it != list.end();) destroy(*it++); } template void basic_multi_buffer:: destroy(const_iter it) { auto& e = list_.erase(it); destroy(e); } template void basic_multi_buffer:: destroy(element& e) { auto a = rebind_type{this->get()}; auto const n = (sizeof(element) + e.size() + sizeof(align_type) - 1) / sizeof(align_type); e.~element(); alloc_traits::deallocate(a, reinterpret_cast(&e), n); } template auto basic_multi_buffer:: alloc(std::size_t size) -> element& { if(size > alloc_traits::max_size(this->get())) BOOST_THROW_EXCEPTION(std::length_error( "A basic_multi_buffer exceeded the allocator's maximum size")); auto a = rebind_type{this->get()}; auto const p = alloc_traits::allocate(a, (sizeof(element) + size + sizeof(align_type) - 1) / sizeof(align_type)); return *(::new(p) element(size)); } template void basic_multi_buffer:: debug_check() const { #ifndef NDEBUG BOOST_ASSERT(buffer_bytes(data()) == in_size_); if(list_.empty()) { BOOST_ASSERT(in_pos_ == 0); BOOST_ASSERT(in_size_ == 0); BOOST_ASSERT(out_pos_ == 0); BOOST_ASSERT(out_end_ == 0); BOOST_ASSERT(out_ == list_.end()); return; } auto const& front = list_.front(); BOOST_ASSERT(in_pos_ < front.size()); if(out_ == list_.end()) { BOOST_ASSERT(out_pos_ == 0); BOOST_ASSERT(out_end_ == 0); } else { auto const& out = *out_; auto const& back = list_.back(); BOOST_ASSERT(out_end_ <= back.size()); BOOST_ASSERT(out_pos_ < out.size()); BOOST_ASSERT(&out != &front || out_pos_ >= in_pos_); BOOST_ASSERT(&out != &front || out_pos_ - in_pos_ == in_size_); BOOST_ASSERT(&out != &back || out_pos_ <= out_end_); } #endif } } // beast } // boost #endif