//------------------------------------------------------------------------------ /* This file is part of Beast: https://github.com/vinniefalco/Beast Copyright 2013, Vinnie Falco Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== #ifndef BEAST_ASIO_BASIC_STREAMBUF_IPP_INCLUDED #define BEAST_ASIO_BASIC_STREAMBUF_IPP_INCLUDED #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) : 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) : 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