// // Copyright (c) 2013-2016 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_BASIC_STREAMBUF_IPP #define BEAST_IMPL_BASIC_STREAMBUF_IPP #include #include #include #include #include #include namespace beast { /* These diagrams illustrate the layout and state variables. Input and output contained entirely in one element: 0 out_ |<-------------+------------------------------------------->| in_pos_ out_pos_ out_end_ Output contained in first and second elements: out_ |<------+----------+------->| |<----------+-------------->| in_pos_ out_pos_ out_end_ Output contained in the second element: out_ |<------------+------------>| |<----+-------------------->| in_pos_ out_pos_ out_end_ Output contained in second and third elements: out_ |<-----+-------->| |<-------+------>| |<--------------->| in_pos_ out_pos_ out_end_ Input sequence is empty: out_ |<------+------------------>| |<-----------+------------->| out_pos_ out_end_ in_pos_ Output sequence is empty: out_ |<------+------------------>| |<------+------------------>| in_pos_ out_pos_ out_end_ 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_ 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_streambuf::element : public boost::intrusive::list_base_hook< boost::intrusive::link_mode< boost::intrusive::normal_link>> { using size_type = typename std::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_streambuf::const_buffers_type::const_iterator { basic_streambuf const* sb_ = 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_streambuf const& sb, typename list_type::const_iterator const& it) : sb_(&sb) , it_(it) { } bool operator==(const_iterator const& other) const { return sb_ == other.sb_ && 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(), (sb_->out_ == sb_->list_.end() || &e != &*sb_->out_) ? e.size() : sb_->out_pos_} + (&e == &*sb_->list_.begin() ? sb_->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_streambuf::const_buffers_type::const_buffers_type( basic_streambuf const& sb) : sb_(&sb) { } template auto basic_streambuf::const_buffers_type::begin() const -> const_iterator { return const_iterator{*sb_, sb_->list_.begin()}; } template auto basic_streambuf::const_buffers_type::end() const -> const_iterator { return const_iterator{*sb_, sb_->out_ == sb_->list_.end() ? sb_->list_.end() : std::next(sb_->out_)}; } //------------------------------------------------------------------------------ template class basic_streambuf::mutable_buffers_type::const_iterator { basic_streambuf const* sb_ = 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_streambuf const& sb, typename list_type::const_iterator const& it) : sb_(&sb) , it_(it) { } bool operator==(const_iterator const& other) const { return sb_ == other.sb_ && 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(sb_->list_.end()) ? sb_->out_end_ : e.size()} + (&e == &*sb_->out_ ? sb_->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_streambuf::mutable_buffers_type::mutable_buffers_type( basic_streambuf const& sb) : sb_(&sb) { } template auto basic_streambuf::mutable_buffers_type::begin() const -> const_iterator { return const_iterator{*sb_, sb_->out_}; } template auto basic_streambuf::mutable_buffers_type::end() const -> const_iterator { return const_iterator{*sb_, sb_->list_.end()}; } //------------------------------------------------------------------------------ template basic_streambuf::~basic_streambuf() { delete_list(); } template basic_streambuf:: basic_streambuf(basic_streambuf&& other) : detail::empty_base_optimization( std::move(other.member())) , alloc_size_(other.alloc_size_) , in_size_(other.in_size_) , in_pos_(other.in_pos_) , out_pos_(other.out_pos_) , out_end_(other.out_end_) { auto const at_end = other.out_ == other.list_.end(); list_ = std::move(other.list_); out_ = at_end ? list_.end() : other.out_; other.in_size_ = 0; other.out_ = other.list_.end(); other.in_pos_ = 0; other.out_pos_ = 0; other.out_end_ = 0; } template basic_streambuf:: basic_streambuf(basic_streambuf&& other, allocator_type const& alloc) : basic_streambuf(other.alloc_size_, alloc) { using boost::asio::buffer_copy; if(this->member() != other.member()) commit(buffer_copy(prepare(other.size()), other.data())); else move_assign(other, std::true_type{}); } template auto basic_streambuf::operator=( basic_streambuf&& other) -> basic_streambuf& { if(this == &other) return *this; // VFALCO If any memory allocated we could use it first? clear(); alloc_size_ = other.alloc_size_; move_assign(other, std::integral_constant{}); return *this; } template basic_streambuf:: basic_streambuf(basic_streambuf const& other) : basic_streambuf(other.alloc_size_, alloc_traits::select_on_container_copy_construction(other.member())) { commit(boost::asio::buffer_copy(prepare(other.size()), other.data())); } template basic_streambuf:: basic_streambuf(basic_streambuf const& other, allocator_type const& alloc) : basic_streambuf(other.alloc_size_, alloc) { commit(boost::asio::buffer_copy(prepare(other.size()), other.data())); } template auto basic_streambuf::operator=( basic_streambuf const& other) -> basic_streambuf& { if(this == &other) return *this; using boost::asio::buffer_copy; clear(); copy_assign(other, std::integral_constant{}); commit(buffer_copy(prepare(other.size()), other.data())); return *this; } template template basic_streambuf::basic_streambuf( basic_streambuf const& other) : basic_streambuf(other.alloc_size_) { using boost::asio::buffer_copy; commit(buffer_copy(prepare(other.size()), other.data())); } template template basic_streambuf::basic_streambuf( basic_streambuf const& other, allocator_type const& alloc) : basic_streambuf(other.alloc_size_, alloc) { using boost::asio::buffer_copy; commit(buffer_copy(prepare(other.size()), other.data())); } template template auto basic_streambuf::operator=( basic_streambuf const& other) -> basic_streambuf& { using boost::asio::buffer_copy; clear(); commit(buffer_copy(prepare(other.size()), other.data())); return *this; } template basic_streambuf::basic_streambuf( std::size_t alloc_size, Allocator const& alloc) : detail::empty_base_optimization(alloc) , out_(list_.end()) , alloc_size_(alloc_size) { if(alloc_size <= 0) throw std::invalid_argument( "basic_streambuf: invalid alloc_size"); } template auto basic_streambuf::prepare(size_type n) -> mutable_buffers_type { iterator pos = out_; if(pos != list_.end()) { auto const avail = pos->size() - out_pos_; if(n > avail) { n -= avail; out_end_ = pos->size(); while(++pos != list_.end()) { if(n < pos->size()) { out_end_ = n; n = 0; ++pos; break; } n -= pos->size(); } } else { ++pos; out_end_ = out_pos_ + n; n = 0; } } if(n > 0) { assert(pos == list_.end()); for(;;) { auto const size = std::max(alloc_size_, n); auto& e = *reinterpret_cast( alloc_traits::allocate(this->member(), size + sizeof(element))); alloc_traits::construct(this->member(), &e, size); list_.push_back(e); if(out_ == list_.end()) { out_ = list_.iterator_to(e); debug_check(); } if(n <= size) { out_end_ = n; debug_check(); break; } n -= size; debug_check(); } } else { while(pos != list_.end()) { auto& e = *pos++; list_.erase(list_.iterator_to(e)); auto const len = e.size() + sizeof(e); alloc_traits::destroy(this->member(), &e); alloc_traits::deallocate(this->member(), reinterpret_cast(&e), len); } debug_check(); } return mutable_buffers_type(*this); } template void basic_streambuf::commit(size_type n) { if(list_.empty()) return; if(out_ == list_.end()) return; auto const last = std::prev(list_.end()); while(out_ != last) { auto const avail = out_->size() - out_pos_; if(n < avail) { out_pos_ += n; in_size_ += n; debug_check(); return; } ++out_; n -= avail; out_pos_ = 0; in_size_ += avail; debug_check(); } 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; } debug_check(); } template auto basic_streambuf::data() const -> const_buffers_type { return const_buffers_type(*this); } template void basic_streambuf::consume(size_type n) { if(list_.empty()) return; auto pos = list_.begin(); for(;;) { if(pos != out_) { auto const avail = pos->size() - in_pos_; if(n < avail) { in_size_ -= n; in_pos_ += n; debug_check(); break; } n -= avail; in_size_ -= avail; in_pos_ = 0; debug_check(); element& e = *pos++; list_.erase(list_.iterator_to(e)); auto const len = e.size() + sizeof(e); alloc_traits::destroy(this->member(), &e); alloc_traits::deallocate(this->member(), reinterpret_cast(&e), len); } else { auto const avail = out_pos_ - in_pos_; if(n < avail) { in_size_ -= n; in_pos_ += n; } else { in_size_ -= avail; if(out_pos_ != out_end_|| out_ != list_.iterator_to(list_.back())) { in_pos_ = out_pos_; } else { // Use the whole buffer now. // Alternatively we could deallocate it. in_pos_ = 0; out_pos_ = 0; out_end_ = 0; } } debug_check(); break; } } } template void basic_streambuf::clear() { delete_list(); list_.clear(); out_ = list_.begin(); in_size_ = 0; in_pos_ = 0; out_pos_ = 0; out_end_ = 0; } template void basic_streambuf:: move_assign(basic_streambuf& other, std::false_type) { using boost::asio::buffer_copy; if(this->member() != other.member()) { commit(buffer_copy(prepare(other.size()), other.data())); other.clear(); } else move_assign(other, std::true_type{}); } template void basic_streambuf:: move_assign(basic_streambuf& 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 void basic_streambuf:: copy_assign(basic_streambuf const& other, std::false_type) { } template void basic_streambuf:: copy_assign(basic_streambuf const& other, std::true_type) { this->member() = other.member(); } template void basic_streambuf::delete_list() { for(auto iter = list_.begin(); iter != list_.end();) { auto& e = *iter++; auto const n = e.size() + sizeof(e); alloc_traits::destroy(this->member(), &e); alloc_traits::deallocate(this->member(), reinterpret_cast(&e), n); } } // Returns the number of bytes which can be // prepared without causing a memory allocation. template std::size_t basic_streambuf::prepare_size() const { auto pos = out_; if(pos == list_.end()) return 0; auto n = pos->size() - out_pos_; while(++pos != list_.end()) n += pos->size(); return n; } template void basic_streambuf::debug_check() const { #ifndef NDEBUG if(list_.empty()) { assert(in_pos_ == 0); assert(in_size_ == 0); assert(out_pos_ == 0); assert(out_end_ == 0); assert(out_ == list_.end()); return; } auto const& front = list_.front(); assert(in_pos_ < front.size()); if(out_ == list_.end()) { assert(out_pos_ == 0); assert(out_end_ == 0); } else { auto const& out = *out_; auto const& back = list_.back(); assert(out_end_ <= back.size()); assert(out_pos_ < out.size()); assert(&out != &front || out_pos_ >= in_pos_); assert(&out != &front || out_pos_ - in_pos_ == in_size_); assert(&out != &back || out_pos_ <= out_end_); } #endif } template basic_streambuf& operator<<(basic_streambuf& buf, T const& t) { using boost::asio::buffer; using boost::asio::buffer_copy; std::stringstream ss; ss << t; auto const& s = ss.str(); buf.commit(buffer_copy(buf.prepare(s.size()), boost::asio::buffer(s))); return buf; } //------------------------------------------------------------------------------ template std::size_t read_size_helper(basic_streambuf< Allocator> const& streambuf, std::size_t max_size) { return std::min(max_size, std::max(512, streambuf.prepare_size())); } template std::string to_string(basic_streambuf const& streambuf) { using boost::asio::buffer; using boost::asio::buffer_copy; std::string s; s.resize(streambuf.size()); buffer_copy( buffer(&s[0], s.size()), streambuf.data()); return s; } } // beast #endif