// // Copyright (c) 2016-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) // // Official repository: https://github.com/boostorg/beast // #ifndef BOOST_BEAST_IMPL_MULTI_BUFFER_IPP #define BOOST_BEAST_IMPL_MULTI_BUFFER_IPP #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 */ template class basic_multi_buffer::element : public boost::intrusive::list_base_hook< boost::intrusive::link_mode< boost::intrusive::normal_link>> { using size_type = typename detail::allocator_traits::size_type; size_type const size_; public: element(element const&) = delete; element& operator=(element const&) = delete; explicit element(size_type n) : size_(n) { } size_type size() const { return size_; } char* data() const { return const_cast( reinterpret_cast(this + 1)); } }; template class basic_multi_buffer::const_buffers_type { basic_multi_buffer const* b_; friend class basic_multi_buffer; explicit const_buffers_type(basic_multi_buffer const& b); public: using value_type = boost::asio::const_buffer; class const_iterator; const_buffers_type() = delete; const_buffers_type(const_buffers_type const&) = default; const_buffers_type& operator=(const_buffers_type const&) = default; const_iterator begin() const; const_iterator end() const; friend std::size_t buffer_size(const_buffers_type const& buffers) { return buffers.b_->size(); } }; template class basic_multi_buffer::mutable_buffers_type { basic_multi_buffer const* b_; friend class basic_multi_buffer; explicit mutable_buffers_type(basic_multi_buffer const& b); public: using value_type = mutable_buffer; class const_iterator; mutable_buffers_type() = delete; mutable_buffers_type(mutable_buffers_type const&) = default; mutable_buffers_type& operator=(mutable_buffers_type const&) = default; const_iterator begin() const; const_iterator end() const; }; //------------------------------------------------------------------------------ template class basic_multi_buffer::const_buffers_type::const_iterator { basic_multi_buffer const* b_ = nullptr; typename list_type::const_iterator it_; public: using value_type = typename const_buffers_type::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&& other) = default; const_iterator(const_iterator const& other) = default; const_iterator& operator=(const_iterator&& other) = default; const_iterator& operator=(const_iterator const& other) = default; const_iterator(basic_multi_buffer const& b, typename list_type::const_iterator const& it) : b_(&b) , it_(it) { } bool operator==(const_iterator const& other) const { return b_ == other.b_ && it_ == other.it_; } bool operator!=(const_iterator const& other) const { return !(*this == other); } reference operator*() const { auto const& e = *it_; return value_type{e.data(), (b_->out_ == b_->list_.end() || &e != &*b_->out_) ? e.size() : b_->out_pos_} + (&e == &*b_->list_.begin() ? b_->in_pos_ : 0); } pointer operator->() const = delete; const_iterator& operator++() { ++it_; return *this; } const_iterator operator++(int) { auto temp = *this; ++(*this); return temp; } const_iterator& operator--() { --it_; return *this; } const_iterator operator--(int) { auto temp = *this; --(*this); return temp; } }; template basic_multi_buffer:: const_buffers_type:: const_buffers_type( basic_multi_buffer const& b) : b_(&b) { } template auto basic_multi_buffer:: const_buffers_type:: begin() const -> const_iterator { return const_iterator{*b_, b_->list_.begin()}; } template auto basic_multi_buffer:: const_buffers_type:: end() const -> const_iterator { return const_iterator{*b_, b_->out_ == b_->list_.end() ? b_->list_.end() : std::next(b_->out_)}; } //------------------------------------------------------------------------------ template class basic_multi_buffer::mutable_buffers_type::const_iterator { basic_multi_buffer const* b_ = nullptr; typename list_type::const_iterator it_; public: using value_type = typename mutable_buffers_type::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&& other) = default; const_iterator(const_iterator const& other) = default; const_iterator& operator=(const_iterator&& other) = default; const_iterator& operator=(const_iterator const& other) = default; const_iterator(basic_multi_buffer const& b, typename list_type::const_iterator const& it) : b_(&b) , it_(it) { } bool operator==(const_iterator const& other) const { return b_ == other.b_ && it_ == other.it_; } bool operator!=(const_iterator const& other) const { return !(*this == other); } reference operator*() const { auto const& e = *it_; return value_type{e.data(), &e == &*std::prev(b_->list_.end()) ? b_->out_end_ : e.size()} + (&e == &*b_->out_ ? b_->out_pos_ : 0); } pointer operator->() const = delete; const_iterator& operator++() { ++it_; return *this; } const_iterator operator++(int) { auto temp = *this; ++(*this); return temp; } const_iterator& operator--() { --it_; return *this; } const_iterator operator--(int) { auto temp = *this; --(*this); return temp; } }; template basic_multi_buffer:: mutable_buffers_type:: mutable_buffers_type( basic_multi_buffer const& b) : b_(&b) { } template auto basic_multi_buffer:: mutable_buffers_type:: begin() const -> const_iterator { return const_iterator{*b_, b_->out_}; } template auto basic_multi_buffer:: mutable_buffers_type:: end() const -> const_iterator { return const_iterator{*b_, b_->list_.end()}; } //------------------------------------------------------------------------------ template basic_multi_buffer:: ~basic_multi_buffer() { delete_list(); } template basic_multi_buffer:: basic_multi_buffer() : out_(list_.end()) { } template basic_multi_buffer:: basic_multi_buffer(std::size_t limit) : max_(limit) , out_(list_.end()) { } template basic_multi_buffer:: basic_multi_buffer(Allocator const& alloc) : detail::empty_base_optimization< base_alloc_type>(alloc) , out_(list_.end()) { } template basic_multi_buffer:: basic_multi_buffer(std::size_t limit, Allocator const& alloc) : detail::empty_base_optimization< base_alloc_type>(alloc) , max_(limit) , out_(list_.end()) { } template basic_multi_buffer:: basic_multi_buffer(basic_multi_buffer&& other) : detail::empty_base_optimization< base_alloc_type>(std::move(other.member())) , 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) : detail::empty_base_optimization< base_alloc_type>(alloc) , max_(other.max_) { if(this->member() != other.member()) { out_ = list_.end(); copy_from(other); other.reset(); } else { 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) : detail::empty_base_optimization< base_alloc_type>(alloc_traits:: select_on_container_copy_construction( other.member())) , max_(other.max_) , out_(list_.end()) { copy_from(other); } template basic_multi_buffer:: basic_multi_buffer(basic_multi_buffer const& other, Allocator const& alloc) : detail::empty_base_optimization< base_alloc_type>(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) : detail::empty_base_optimization< base_alloc_type>(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; reset(); max_ = other.max_; move_assign(other, std::integral_constant{}); return *this; } template auto basic_multi_buffer:: operator=(basic_multi_buffer const& other) -> basic_multi_buffer& { if(this == &other) return *this; copy_assign(other, std::integral_constant{}); return *this; } template template auto basic_multi_buffer:: operator=( basic_multi_buffer const& other) -> basic_multi_buffer& { reset(); max_ = other.max_; copy_from(other); return *this; } template std::size_t basic_multi_buffer:: capacity() const { 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 -> const_buffers_type { return const_buffers_type(*this); } template auto basic_multi_buffer:: prepare(size_type n) -> mutable_buffers_type { if(in_size_ + n > max_) BOOST_THROW_EXCEPTION(std::length_error{ "dynamic buffer overflow"}); 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) { for(auto it = reuse.begin(); it != reuse.end();) { auto& e = *it++; reuse.erase(list_.iterator_to(e)); auto const len = sizeof(e) + e.size(); alloc_traits::destroy(this->member(), &e); alloc_traits::deallocate(this->member(), reinterpret_cast(&e), len); } if(n > 0) { static auto const growth_factor = 2.0f; auto const size = (std::min)( max_ - total, (std::max)({ static_cast( in_size_ * growth_factor - in_size_), 512, n})); auto& e = *reinterpret_cast(static_cast< void*>(alloc_traits::allocate(this->member(), sizeof(element) + size))); alloc_traits::construct(this->member(), &e, 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 } } return mutable_buffers_type(*this); } template void basic_multi_buffer:: commit(size_type n) { 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) { 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)); auto const len = sizeof(e) + e.size(); alloc_traits::destroy(this->member(), &e); alloc_traits::deallocate(this->member(), reinterpret_cast(&e), len); #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 inline void basic_multi_buffer:: delete_list() { for(auto iter = list_.begin(); iter != list_.end();) { auto& e = *iter++; auto const len = sizeof(e) + e.size(); alloc_traits::destroy(this->member(), &e); alloc_traits::deallocate(this->member(), reinterpret_cast(&e), len); } } template inline void basic_multi_buffer:: reset() { delete_list(); list_.clear(); out_ = list_.end(); in_size_ = 0; in_pos_ = 0; out_pos_ = 0; out_end_ = 0; } template template inline void basic_multi_buffer:: copy_from(DynamicBuffer const& buffer) { if(buffer.size() == 0) return; using boost::asio::buffer_copy; commit(buffer_copy( prepare(buffer.size()), buffer.data())); } template inline void basic_multi_buffer:: move_assign(basic_multi_buffer& other, std::false_type) { if(this->member() != other.member()) { copy_from(other); other.reset(); } else { move_assign(other, std::true_type{}); } } template inline void basic_multi_buffer:: move_assign(basic_multi_buffer& other, std::true_type) { this->member() = std::move(other.member()); 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 inline void basic_multi_buffer:: copy_assign( basic_multi_buffer const& other, std::false_type) { reset(); max_ = other.max_; copy_from(other); } template inline void basic_multi_buffer:: copy_assign( basic_multi_buffer const& other, std::true_type) { reset(); max_ = other.max_; this->member() = other.member(); copy_from(other); } template inline void basic_multi_buffer:: swap(basic_multi_buffer& other) { swap(other, typename alloc_traits::propagate_on_container_swap{}); } template inline void basic_multi_buffer:: swap(basic_multi_buffer& other, std::true_type) { using std::swap; auto const at_end0 = out_ == list_.end(); auto const at_end1 = other.out_ == other.list_.end(); swap(this->member(), other.member()); 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 inline void basic_multi_buffer:: swap(basic_multi_buffer& other, std::false_type) { BOOST_ASSERT(this->member() == other.member()); 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) { lhs.swap(rhs); } template void basic_multi_buffer:: debug_check() const { #ifndef NDEBUG using boost::asio::buffer_size; BOOST_ASSERT(buffer_size(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