diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9ae17eb7..6fc3f715 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@ Version 173:
* Fix buffers_adapter max_size
* Fix buffers_prefix iterator decrement
* buffers_adapter improvements
+* Add icy_stream Shoutcast stream filter
--------------------------------------------------------------------------------
diff --git a/doc/qbk/quickref.xml b/doc/qbk/quickref.xml
index 6559e2f1..760fcf0d 100644
--- a/doc/qbk/quickref.xml
+++ b/doc/qbk/quickref.xml
@@ -303,6 +303,7 @@
flat_streamssl_stream
+ http::icy_streamtest::fail_counttest::stream
diff --git a/doc/source.dox b/doc/source.dox
index 52ddb673..b12527ac 100644
--- a/doc/source.dox
+++ b/doc/source.dox
@@ -106,6 +106,7 @@ INPUT = \
$(LIB_DIR)/include/boost/beast/ \
$(LIB_DIR)/include/boost/beast/core \
$(LIB_DIR)/include/boost/beast/experimental/core \
+ $(LIB_DIR)/include/boost/beast/experimental/http \
$(LIB_DIR)/include/boost/beast/experimental/test \
$(LIB_DIR)/include/boost/beast/http \
$(LIB_DIR)/include/boost/beast/websocket \
diff --git a/include/boost/beast/experimental/core/flat_stream.hpp b/include/boost/beast/experimental/core/flat_stream.hpp
index 329a4ae6..ab22e544 100644
--- a/include/boost/beast/experimental/core/flat_stream.hpp
+++ b/include/boost/beast/experimental/core/flat_stream.hpp
@@ -10,6 +10,7 @@
#ifndef BOOST_BEAST_CORE_FLAT_STREAM_HPP
#define BOOST_BEAST_CORE_FLAT_STREAM_HPP
+#include
#include
#include
#include
diff --git a/include/boost/beast/experimental/core/ssl_stream.hpp b/include/boost/beast/experimental/core/ssl_stream.hpp
index ca05ba02..b61bfbde 100644
--- a/include/boost/beast/experimental/core/ssl_stream.hpp
+++ b/include/boost/beast/experimental/core/ssl_stream.hpp
@@ -10,6 +10,8 @@
#ifndef BOOST_BEAST_CORE_SSL_STREAM_HPP
#define BOOST_BEAST_CORE_SSL_STREAM_HPP
+#include
+
// This include is necessary to work with `ssl::stream` and `boost::beast::websocket::stream`
#include
diff --git a/include/boost/beast/experimental/http/icy_stream.hpp b/include/boost/beast/experimental/http/icy_stream.hpp
new file mode 100644
index 00000000..b597b244
--- /dev/null
+++ b/include/boost/beast/experimental/http/icy_stream.hpp
@@ -0,0 +1,345 @@
+//
+// Copyright (c) 2018 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_HTTP_ICY_STREAM_HPP
+#define BOOST_BEAST_HTTP_ICY_STREAM_HPP
+
+#include
+#include
+#include
+#include
+#include
+
+namespace boost {
+namespace beast {
+namespace http {
+
+/** Stream wrapper to process Shoutcast HTTP responses
+
+ This wrapper replaces the word "ICY" in the first
+ HTTP response received on the connection, with "HTTP/1.1".
+ This allows the Beast parser to be used with Shoutcast
+ servers, which send a non-standard HTTP message as the
+ response.
+
+ For asynchronous operations, the application must ensure
+ that they are are all performed within the same implicit
+ or explicit strand.
+
+ @par Thread Safety
+ @e Distinct @e objects: Safe.@n
+ @e Shared @e objects: Unsafe.
+ The application must also ensure that all asynchronous
+ operations are performed within the same implicit or explicit strand.
+
+ @par Example
+
+ To use the @ref stream template with an `ip::tcp::socket`,
+ you would write:
+
+ @code
+ http::icy_stream is{io_context};
+ @endcode
+ Alternatively, you can write:
+ @code
+ ip::tcp::socket sock{io_context};
+ http::icy_stream is{sock};
+ @endcode
+
+ @tparam NextLayer The type representing the next layer, to which
+ data will be read and written during operations. For synchronous
+ operations, the type must support the @b SyncStream concept.
+ For asynchronous operations, the type must support the
+ @b AsyncStream concept.
+
+ @note A stream object must not be moved or destroyed while there
+ are pending asynchronous operations associated with it.
+
+ @par Concepts
+ @b AsyncStream,
+ @b SyncStream
+*/
+template
+class icy_stream
+{
+ template class read_op;
+
+ NextLayer stream_;
+ bool detect_ = true;
+ unsigned char copy_ = 0;
+ char buf_[8];
+
+ static
+ boost::asio::const_buffer
+ version()
+ {
+ return {"HTTP/1.1", 8};
+ }
+
+public:
+ /// The type of the next layer.
+ using next_layer_type =
+ typename std::remove_reference::type;
+
+ /// The type of the lowest layer.
+ using lowest_layer_type = boost::beast::get_lowest_layer;
+
+ /// The type of the executor associated with the object.
+ using executor_type = typename next_layer_type::executor_type;
+
+ icy_stream(icy_stream&&) = default;
+ icy_stream(icy_stream const&) = default;
+ icy_stream& operator=(icy_stream&&) = default;
+ icy_stream& operator=(icy_stream const&) = default;
+
+ /** Destructor
+
+ The treatment of pending operations will be the same as that
+ of the next layer.
+ */
+ ~icy_stream() = default;
+
+ /** Constructor
+
+ Arguments, if any, are forwarded to the next layer's constructor.
+ */
+ template
+ explicit
+ icy_stream(Args&&... args);
+
+ //--------------------------------------------------------------------------
+
+ /** Get the executor associated with the object.
+
+ This function may be used to obtain the executor object that the
+ stream uses to dispatch handlers for asynchronous operations.
+
+ @return A copy of the executor that stream will use to dispatch handlers.
+ */
+ executor_type
+ get_executor() noexcept
+ {
+ return stream_.get_executor();
+ }
+
+ /** Get a reference to the next layer
+
+ This function returns a reference to the next layer
+ in a stack of stream layers.
+
+ @return A reference to the next layer in the stack of
+ stream layers.
+ */
+ next_layer_type&
+ next_layer()
+ {
+ return stream_;
+ }
+
+ /** Get a reference to the next layer
+
+ This function returns a reference to the next layer in a
+ stack of stream layers.
+
+ @return A reference to the next layer in the stack of
+ stream layers.
+ */
+ next_layer_type const&
+ next_layer() const
+ {
+ return stream_;
+ }
+
+ /** Get a reference to the lowest layer
+
+ This function returns a reference to the lowest layer
+ in a stack of stream layers.
+
+ @return A reference to the lowest layer in the stack of
+ stream layers.
+ */
+ lowest_layer_type&
+ lowest_layer()
+ {
+ return stream_.lowest_layer();
+ }
+
+ /** Get a reference to the lowest layer
+
+ This function returns a reference to the lowest layer
+ in a stack of stream layers.
+
+ @return A reference to the lowest layer in the stack of
+ stream layers. Ownership is not transferred to the caller.
+ */
+ lowest_layer_type const&
+ lowest_layer() const
+ {
+ return stream_.lowest_layer();
+ }
+
+ //--------------------------------------------------------------------------
+
+ /** Read some data from the stream.
+
+ This function is used to read data from the stream. The function call will
+ block until one or more bytes of data has been read successfully, or until
+ an error occurs.
+
+ @param buffers The buffers into which the data will be read.
+
+ @returns The number of bytes read.
+
+ @throws boost::system::system_error Thrown on failure.
+
+ @note The `read_some` operation may not read all of the requested number of
+ bytes. Consider using the function `boost::asio::read` if you need to ensure
+ that the requested amount of data is read before the blocking operation
+ completes.
+ */
+ template
+ std::size_t
+ read_some(MutableBufferSequence const& buffers);
+
+ /** Read some data from the stream.
+
+ This function is used to read data from the stream. The function call will
+ block until one or more bytes of data has been read successfully, or until
+ an error occurs.
+
+ @param buffers The buffers into which the data will be read.
+
+ @param ec Set to indicate what error occurred, if any.
+
+ @returns The number of bytes read.
+
+ @note The `read_some` operation may not read all of the requested number of
+ bytes. Consider using the function `boost::asio::read` if you need to ensure
+ that the requested amount of data is read before the blocking operation
+ completes.
+ */
+ template
+ std::size_t
+ read_some(
+ MutableBufferSequence const& buffers,
+ error_code& ec);
+
+ /** Start an asynchronous read.
+
+ This function is used to asynchronously read one or more bytes of data from
+ the stream. The function call always returns immediately.
+
+ @param buffers The buffers into which the data will be read. Although the
+ buffers object may be copied as necessary, ownership of the underlying
+ buffers is retained by the caller, which must guarantee that they remain
+ valid until the handler is called.
+
+ @param handler The handler to be called when the read operation completes.
+ Copies will be made of the handler as required. The equivalent function
+ signature of the handler must be:
+ @code void handler(
+ const boost::system::error_code& error, // Result of operation.
+ std::size_t bytes_transferred // Number of bytes read.
+ ); @endcode
+
+ @note The `read_some` operation may not read all of the requested number of
+ bytes. Consider using the function `boost::asio::async_read` if you need
+ to ensure that the requested amount of data is read before the asynchronous
+ operation completes.
+ */
+ template<
+ class MutableBufferSequence,
+ class ReadHandler>
+ BOOST_ASIO_INITFN_RESULT_TYPE(
+ ReadHandler, void(error_code, std::size_t))
+ async_read_some(
+ MutableBufferSequence const& buffers,
+ ReadHandler&& handler);
+
+ /** Write some data to the stream.
+
+ This function is used to write data on the stream. The function call will
+ block until one or more bytes of data has been written successfully, or
+ until an error occurs.
+
+ @param buffers The data to be written.
+
+ @returns The number of bytes written.
+
+ @throws boost::system::system_error Thrown on failure.
+
+ @note The `write_some` operation may not transmit all of the data to the
+ peer. Consider using the function `boost::asio::write` if you need to
+ ensure that all data is written before the blocking operation completes.
+ */
+ template
+ std::size_t
+ write_some(ConstBufferSequence const& buffers);
+
+ /** Write some data to the stream.
+
+ This function is used to write data on the stream. The function call will
+ block until one or more bytes of data has been written successfully, or
+ until an error occurs.
+
+ @param buffers The data to be written.
+
+ @param ec Set to indicate what error occurred, if any.
+
+ @returns The number of bytes written.
+
+ @note The `write_some` operation may not transmit all of the data to the
+ peer. Consider using the function `boost::asio::write` if you need to
+ ensure that all data is written before the blocking operation completes.
+ */
+ template
+ std::size_t
+ write_some(
+ ConstBufferSequence const& buffers,
+ error_code& ec);
+
+ /** Start an asynchronous write.
+
+ This function is used to asynchronously write one or more bytes of data to
+ the stream. The function call always returns immediately.
+
+ @param buffers The data to be written to the stream. Although the buffers
+ object may be copied as necessary, ownership of the underlying buffers is
+ retained by the caller, which must guarantee that they remain valid until
+ the handler is called.
+
+ @param handler The handler to be called when the write operation completes.
+ Copies will be made of the handler as required. The equivalent function
+ signature of the handler must be:
+ @code void handler(
+ const boost::system::error_code& error, // Result of operation.
+ std::size_t bytes_transferred // Number of bytes written.
+ ); @endcode
+
+ @note The `async_write_some` operation may not transmit all of the data to
+ the peer. Consider using the function `boost::asio::async_write` if you need
+ to ensure that all data is written before the asynchronous operation completes.
+ */
+ template<
+ class ConstBufferSequence,
+ class WriteHandler>
+ BOOST_ASIO_INITFN_RESULT_TYPE(
+ WriteHandler, void(error_code, std::size_t))
+ async_write_some(
+ ConstBufferSequence const& buffers,
+ WriteHandler&& handler);
+};
+
+} // http
+} // beast
+} // boost
+
+#include
+
+#endif
diff --git a/include/boost/beast/experimental/http/impl/icy_stream.ipp b/include/boost/beast/experimental/http/impl/icy_stream.ipp
new file mode 100644
index 00000000..8e32dd0a
--- /dev/null
+++ b/include/boost/beast/experimental/http/impl/icy_stream.ipp
@@ -0,0 +1,628 @@
+//
+// Copyright (c) 2018 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_CORE_IMPL_ICY_STREAM_IPP
+#define BOOST_BEAST_CORE_IMPL_ICY_STREAM_IPP
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace boost {
+namespace beast {
+namespace http {
+
+namespace detail {
+
+template
+class dynamic_buffer_ref
+{
+ DynamicBuffer& b_;
+
+public:
+ using const_buffers_type =
+ typename DynamicBuffer::const_buffers_type;
+
+ using mutable_buffers_type =
+ typename DynamicBuffer::mutable_buffers_type;
+
+ dynamic_buffer_ref(dynamic_buffer_ref&&) = default;
+
+ explicit
+ dynamic_buffer_ref(DynamicBuffer& b)
+ : b_(b)
+ {
+ }
+
+ std::size_t
+ size() const
+ {
+ return b_.size();
+ }
+
+ std::size_t
+ max_size() const
+ {
+ return b_.max_size();
+ }
+
+ std::size_t
+ capacity() const
+ {
+ return b_.capacity();
+ }
+
+ const_buffers_type
+ data() const
+ {
+ return b_.data();
+ }
+
+ mutable_buffers_type
+ prepare(std::size_t n)
+ {
+ return b_.prepare(n);
+ }
+
+ void
+ commit(std::size_t n)
+ {
+ b_.commit(n);
+ }
+
+ void
+ consume(std::size_t n)
+ {
+ b_.consume(n);
+ }
+};
+
+template
+typename std::enable_if<
+ boost::asio::is_dynamic_buffer::value,
+ dynamic_buffer_ref>::type
+ref(DynamicBuffer& b)
+{
+ return dynamic_buffer_ref(b);
+}
+
+template
+void
+buffer_shift(MutableBuffers const& out, ConstBuffers const& in)
+{
+ using boost::asio::buffer_size;
+ auto in_pos = boost::asio::buffer_sequence_end(in);
+ auto out_pos = boost::asio::buffer_sequence_end(out);
+ auto const in_begin = boost::asio::buffer_sequence_begin(in);
+ auto const out_begin = boost::asio::buffer_sequence_begin(out);
+ BOOST_ASSERT(buffer_size(in) == buffer_size(out));
+ if(in_pos == in_begin || out_pos == out_begin)
+ return;
+ boost::asio::const_buffer cb{*--in_pos};
+ boost::asio::mutable_buffer mb{*--out_pos};
+ for(;;)
+ {
+ if(mb.size() >= cb.size())
+ {
+ std::memmove(
+ reinterpret_cast(
+ mb.data()) + mb.size() - cb.size(),
+ cb.data(),
+ cb.size());
+ mb = boost::asio::mutable_buffer{
+ mb.data(), mb.size() - cb.size()};
+ if(in_pos == in_begin)
+ break;
+ cb = *--in_pos;
+ }
+ else
+ {
+ std::memmove(
+ mb.data(),
+ reinterpret_cast(
+ cb.data()) + cb.size() - mb.size(),
+ mb.size());
+ cb = boost::asio::const_buffer{
+ cb.data(), cb.size() - mb.size()};
+ if(out_pos == out_begin)
+ break;
+ mb = *--out_pos;
+ }
+ }
+}
+
+template
+class match_icy
+{
+ bool& match_;
+
+public:
+ using result_type = std::pair;
+ explicit
+ match_icy(bool& b)
+ : match_(b)
+ {
+ }
+
+ result_type
+ operator()(FwdIt first, FwdIt last) const
+ {
+ auto it = first;
+ if(it == last)
+ return {first, false};
+ if(*it != 'I')
+ return {last, true};
+ if(++it == last)
+ return {first, false};
+ if(*it != 'C')
+ return {last, true};
+ if(++it == last)
+ return {first, false};
+ if(*it != 'Y')
+ return {last, true};
+ match_ = true;
+ return {last, true};
+ };
+};
+
+} // detail
+
+template
+template
+class icy_stream::read_op
+ : public boost::asio::coroutine
+{
+ using alloc_type = typename
+ boost::asio::associated_allocator_t::template
+ rebind::other;
+
+ struct data
+ {
+ icy_stream& s;
+ buffers_adapter b;
+ bool match = false;
+
+ data(
+ Handler const&,
+ icy_stream& s_,
+ MutableBufferSequence const& b_)
+ : s(s_)
+ , b(b_)
+ {
+ }
+ };
+
+ handler_ptr d_;
+
+public:
+ read_op(read_op&&) = default;
+ read_op(read_op const&) = delete;
+
+ template
+ read_op(
+ DeducedHandler&& h,
+ icy_stream& s,
+ MutableBufferSequence const& b)
+ : d_(std::forward(h), s, b)
+ {
+ }
+
+ using allocator_type =
+ boost::asio::associated_allocator_t;
+
+ allocator_type
+ get_allocator() const noexcept
+ {
+ return (boost::asio::get_associated_allocator)(d_.handler());
+ }
+
+ using executor_type = boost::asio::associated_executor_t<
+ Handler, decltype(std::declval().get_executor())>;
+
+ executor_type
+ get_executor() const noexcept
+ {
+ return (boost::asio::get_associated_executor)(
+ d_.handler(), d_->s.get_executor());
+ }
+
+ void
+ operator()(
+ boost::system::error_code ec,
+ std::size_t bytes_transferred);
+
+ template
+ friend
+ void asio_handler_invoke(Function&& f, read_op* op)
+ {
+ using boost::asio::asio_handler_invoke;
+ asio_handler_invoke(f, std::addressof(op->d_.handler()));
+ }
+};
+
+template
+template
+void
+icy_stream::
+read_op::
+operator()(
+ error_code ec,
+ std::size_t bytes_transferred)
+{
+ using boost::asio::buffer_copy;
+ using boost::asio::buffer_size;
+ using iterator = boost::asio::buffers_iterator<
+ typename detail::dynamic_buffer_ref<
+ buffers_adapter>::const_buffers_type>;
+ auto& d = *d_;
+ BOOST_ASIO_CORO_REENTER(*this)
+ {
+ if(d.b.max_size() == 0)
+ {
+ BOOST_ASIO_CORO_YIELD
+ boost::asio::post(d.s.get_executor(),
+ bind_handler(std::move(*this), ec, 0));
+ goto upcall;
+ }
+ if(! d.s.detect_)
+ {
+ if(d.s.copy_ > 0)
+ {
+ auto const n = buffer_copy(
+ d.b.prepare(std::min(
+ d.s.copy_, d.b.max_size())),
+ boost::asio::buffer(d.s.buf_));
+ d.b.commit(n);
+ d.s.copy_ = static_cast(
+ d.s.copy_ - n);
+ if(d.s.copy_ > 0)
+ std::memmove(
+ d.s.buf_,
+ &d.s.buf_[n],
+ d.s.copy_);
+ }
+ if(d.b.size() < d.b.max_size())
+ {
+ BOOST_ASIO_CORO_YIELD
+ d.s.next_layer().async_read_some(
+ d.b.prepare(d.b.max_size() - d.b.size()),
+ std::move(*this));
+ d.b.commit(bytes_transferred);
+ }
+ bytes_transferred = d.b.size();
+ goto upcall;
+ }
+
+ d.s.detect_ = false;
+ if(d.b.max_size() < 8)
+ {
+ BOOST_ASIO_CORO_YIELD
+ boost::asio::async_read(
+ d.s.next_layer(),
+ boost::asio::buffer(d.s.buf_, 3),
+ std::move(*this));
+ if(ec)
+ goto upcall;
+ auto n = bytes_transferred;
+ BOOST_ASSERT(n == 3);
+ if(
+ d.s.buf_[0] != 'I' ||
+ d.s.buf_[1] != 'C' ||
+ d.s.buf_[2] != 'Y')
+ {
+ buffer_copy(
+ d.b.value(),
+ boost::asio::buffer(d.s.buf_, n));
+ if(d.b.max_size() < 3)
+ {
+ d.s.copy_ = static_cast(
+ 3 - d.b.max_size());
+ std::memmove(
+ d.s.buf_,
+ &d.s.buf_[d.b.max_size()],
+ d.s.copy_);
+
+ }
+ bytes_transferred = (std::min)(
+ n, d.b.max_size());
+ goto upcall;
+ }
+ d.s.copy_ = static_cast(
+ buffer_copy(
+ boost::asio::buffer(d.s.buf_),
+ icy_stream::version() + d.b.max_size()));
+ bytes_transferred = buffer_copy(
+ d.b.value(),
+ icy_stream::version());
+ goto upcall;
+ }
+
+ BOOST_ASIO_CORO_YIELD
+ boost::asio::async_read_until(
+ d.s.next_layer(),
+ detail::ref(d.b),
+ detail::match_icy(d.match),
+ std::move(*this));
+ if(ec)
+ goto upcall;
+ {
+ auto n = bytes_transferred;
+ BOOST_ASSERT(n == d.b.size());
+ if(! d.match)
+ goto upcall;
+ if(d.b.size() + 5 > d.b.max_size())
+ {
+ d.s.copy_ = static_cast(
+ n + 5 - d.b.max_size());
+ std::copy(
+ boost::asio::buffers_begin(d.b.value()) + n - d.s.copy_,
+ boost::asio::buffers_begin(d.b.value()) + n,
+ d.s.buf_);
+ n = d.b.max_size() - 5;
+ }
+ {
+ buffers_suffix> dest(
+ boost::in_place_init, d.b.value());
+ dest.consume(5);
+ detail::buffer_shift(
+ buffers_prefix(n, dest),
+ buffers_prefix(n, d.b.value()));
+ buffer_copy(d.b.value(), icy_stream::version());
+ n += 5;
+ bytes_transferred = n;
+ }
+ }
+ upcall:
+ d_.invoke(ec, bytes_transferred);
+ }
+}
+
+//------------------------------------------------------------------------------
+
+template
+template
+icy_stream::
+icy_stream(Args&&... args)
+ : stream_(std::forward(args)...)
+{
+}
+
+template
+template
+std::size_t
+icy_stream::
+read_some(MutableBufferSequence const& buffers)
+{
+ static_assert(boost::beast::is_sync_read_stream::value,
+ "SyncReadStream requirements not met");
+ static_assert(boost::asio::is_mutable_buffer_sequence<
+ MutableBufferSequence>::value,
+ "MutableBufferSequence requirements not met");
+ error_code ec;
+ auto n = read_some(buffers, ec);
+ if(ec)
+ BOOST_THROW_EXCEPTION(boost::system::system_error{ec});
+ return n;
+}
+
+template
+template
+std::size_t
+icy_stream::
+read_some(MutableBufferSequence const& buffers, error_code& ec)
+{
+ static_assert(boost::beast::is_sync_read_stream::value,
+ "SyncReadStream requirements not met");
+ static_assert(boost::asio::is_mutable_buffer_sequence<
+ MutableBufferSequence>::value,
+ "MutableBufferSequence requirements not met");
+ using boost::asio::buffer_copy;
+ using boost::asio::buffer_size;
+ using iterator = boost::asio::buffers_iterator<
+ typename detail::dynamic_buffer_ref<
+ buffers_adapter>::const_buffers_type>;
+ buffers_adapter b(buffers);
+ if(b.max_size() == 0)
+ {
+ ec.assign(0, ec.category());
+ return 0;
+ }
+ if(! detect_)
+ {
+ if(copy_ > 0)
+ {
+ auto const n = buffer_copy(
+ b.prepare(std::min(
+ copy_, b.max_size())),
+ boost::asio::buffer(buf_));
+ b.commit(n);
+ copy_ = static_cast(
+ copy_ - n);
+ if(copy_ > 0)
+ std::memmove(
+ buf_,
+ &buf_[n],
+ copy_);
+ }
+ if(b.size() < b.max_size())
+ b.commit(stream_.read_some(
+ b.prepare(b.max_size() - b.size()), ec));
+ return b.size();
+ }
+
+ detect_ = false;
+ if(b.max_size() < 8)
+ {
+ auto n = boost::asio::read(
+ stream_,
+ boost::asio::buffer(buf_, 3),
+ ec);
+ if(ec)
+ return 0;
+ BOOST_ASSERT(n == 3);
+ if(
+ buf_[0] != 'I' ||
+ buf_[1] != 'C' ||
+ buf_[2] != 'Y')
+ {
+ buffer_copy(
+ buffers,
+ boost::asio::buffer(buf_, n));
+ if(b.max_size() < 3)
+ {
+ copy_ = static_cast(
+ 3 - b.max_size());
+ std::memmove(
+ buf_,
+ &buf_[b.max_size()],
+ copy_);
+
+ }
+ return (std::min)(n, b.max_size());
+ }
+ copy_ = static_cast(
+ buffer_copy(
+ boost::asio::buffer(buf_),
+ version() + b.max_size()));
+ return buffer_copy(
+ buffers,
+ version());
+ }
+
+ bool match = false;
+ auto n = boost::asio::read_until(
+ stream_,
+ detail::ref(b),
+ detail::match_icy(match),
+ ec);
+ if(ec)
+ return n;
+ BOOST_ASSERT(n == b.size());
+ if(! match)
+ return n;
+ if(b.size() + 5 > b.max_size())
+ {
+ copy_ = static_cast(
+ n + 5 - b.max_size());
+ std::copy(
+ boost::asio::buffers_begin(buffers) + n - copy_,
+ boost::asio::buffers_begin(buffers) + n,
+ buf_);
+ n = b.max_size() - 5;
+ }
+ buffers_suffix> dest(
+ boost::in_place_init, buffers);
+ dest.consume(5);
+ detail::buffer_shift(
+ buffers_prefix(n, dest),
+ buffers_prefix(n, buffers));
+ buffer_copy(buffers, version());
+ n += 5;
+ return n;
+}
+
+template
+template<
+ class MutableBufferSequence,
+ class ReadHandler>
+BOOST_ASIO_INITFN_RESULT_TYPE(
+ ReadHandler, void(error_code, std::size_t))
+icy_stream::
+async_read_some(
+ MutableBufferSequence const& buffers,
+ ReadHandler&& handler)
+{
+ static_assert(boost::beast::is_async_read_stream::value,
+ "AsyncReadStream requirements not met");
+ static_assert(boost::asio::is_mutable_buffer_sequence<
+ MutableBufferSequence >::value,
+ "MutableBufferSequence requirements not met");
+ BOOST_BEAST_HANDLER_INIT(
+ ReadHandler, void(error_code, std::size_t));
+ read_op<
+ MutableBufferSequence,
+ BOOST_ASIO_HANDLER_TYPE(
+ ReadHandler, void(error_code, std::size_t))>{
+ std::move(init.completion_handler), *this, buffers}(
+ {}, 0);
+ return init.result.get();
+}
+
+template
+template
+std::size_t
+icy_stream::
+write_some(MutableBufferSequence const& buffers)
+{
+ static_assert(boost::beast::is_sync_write_stream::value,
+ "SyncWriteStream requirements not met");
+ static_assert(boost::asio::is_const_buffer_sequence<
+ MutableBufferSequence>::value,
+ "MutableBufferSequence requirements not met");
+ return stream_.write_some(buffers);
+}
+
+template
+template
+std::size_t
+icy_stream::
+write_some(MutableBufferSequence const& buffers, error_code& ec)
+{
+ static_assert(boost::beast::is_sync_write_stream::value,
+ "SyncWriteStream requirements not met");
+ static_assert(boost::asio::is_const_buffer_sequence<
+ MutableBufferSequence>::value,
+ "MutableBufferSequence requirements not met");
+ return stream_.write_some(buffers, ec);
+}
+
+template
+template<
+ class MutableBufferSequence,
+ class WriteHandler>
+BOOST_ASIO_INITFN_RESULT_TYPE(
+ WriteHandler, void(error_code, std::size_t))
+icy_stream::
+async_write_some(
+ MutableBufferSequence const& buffers,
+ WriteHandler&& handler)
+{
+ static_assert(boost::beast::is_async_write_stream::value,
+ "AsyncWriteStream requirements not met");
+ static_assert(boost::asio::is_const_buffer_sequence<
+ MutableBufferSequence>::value,
+ "MutableBufferSequence requirements not met");
+ return stream_.async_write_some(buffers, std::forward(handler));
+}
+
+} // http
+} // beast
+} // boost
+
+#endif
diff --git a/include/boost/beast/experimental/test/stream.hpp b/include/boost/beast/experimental/test/stream.hpp
index d583e796..17f8c55a 100644
--- a/include/boost/beast/experimental/test/stream.hpp
+++ b/include/boost/beast/experimental/test/stream.hpp
@@ -73,7 +73,7 @@ namespace test {
This allows predefined test vectors to be set up for testing
read algorithms.
- @li The stream may be constructed with a @ref fail count. The
+ @li The stream may be constructed with a fail count. The
stream will eventually fail with a predefined error after a
certain number of operations, where the number of operations
is controlled by the test. When a test loops over a range of
diff --git a/test/beast/experimental/CMakeLists.txt b/test/beast/experimental/CMakeLists.txt
index 795df861..012907bc 100644
--- a/test/beast/experimental/CMakeLists.txt
+++ b/test/beast/experimental/CMakeLists.txt
@@ -19,6 +19,7 @@ add_executable (tests-beast-experimental
Jamfile
error.cpp
flat_stream.cpp
+ icy_stream.cpp
ssl_stream.cpp
stream.cpp
)
diff --git a/test/beast/experimental/Jamfile b/test/beast/experimental/Jamfile
index 040b0a1c..b96b8ebc 100644
--- a/test/beast/experimental/Jamfile
+++ b/test/beast/experimental/Jamfile
@@ -10,6 +10,7 @@
local SOURCES =
error.cpp
flat_stream.cpp
+ icy_stream.cpp
ssl_stream.cpp
stream.cpp
;
diff --git a/test/beast/experimental/flat_stream.cpp b/test/beast/experimental/flat_stream.cpp
index 977c7d54..ba02f835 100644
--- a/test/beast/experimental/flat_stream.cpp
+++ b/test/beast/experimental/flat_stream.cpp
@@ -103,7 +103,7 @@ public:
}
};
-BEAST_DEFINE_TESTSUITE(beast,example,flat_stream);
+BEAST_DEFINE_TESTSUITE(beast,core,flat_stream);
} // beast
} // boost
diff --git a/test/beast/experimental/icy_stream.cpp b/test/beast/experimental/icy_stream.cpp
new file mode 100644
index 00000000..8205ee13
--- /dev/null
+++ b/test/beast/experimental/icy_stream.cpp
@@ -0,0 +1,127 @@
+//
+// Copyright (c) 2018 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
+//
+
+// Test that header file is self-contained.
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace boost {
+namespace beast {
+namespace http {
+
+class icy_stream_test
+ : public unit_test::suite
+{
+public:
+ void
+ doMatrix(string_view in, string_view out)
+ {
+ using boost::asio::mutable_buffer;
+ boost::asio::io_context ioc;
+ auto len = out.size() + 8;
+ std::unique_ptr p(new char[len]);
+ for(std::size_t i = 1; i < len; ++i)
+ {
+ std::array mbs{
+ mutable_buffer(p.get(), i),
+ mutable_buffer(p.get() + i, len - i)};
+ for(std::size_t j = 1; j < in.size(); ++j)
+ {
+ for(std::size_t k = 1; k < len; ++k)
+ {
+ // sync
+ {
+ buffers_adapter ba(mbs);
+ std::memset(p.get(), 0, len);
+
+ icy_stream is{ioc};
+ is.next_layer().read_size(j);
+ is.next_layer().append(in);
+ connect(is.next_layer()).close();
+
+ error_code ec;
+ for(;;)
+ {
+ ba.commit(is.read_some(
+ ba.prepare(read_size(ba, k)), ec));
+ if(ec)
+ break;
+ }
+ if(! BEAST_EXPECTS(
+ ec == boost::asio::error::eof, ec.message()))
+ continue;
+ auto const s = buffers_to_string(ba.data());
+ BEAST_EXPECTS(s == out, s);
+ }
+ // async
+ {
+ buffers_adapter ba(mbs);
+ std::memset(p.get(), 0, len);
+
+ icy_stream is{ioc};
+ is.next_layer().read_size(j);
+ is.next_layer().append(in);
+ connect(is.next_layer()).close();
+
+ error_code ec;
+ for(;;)
+ {
+ is.async_read_some(
+ ba.prepare(read_size(ba, k)),
+ [&ec, &ba](error_code ec_, std::size_t n)
+ {
+ ec = ec_;
+ ba.commit(n);
+ });
+ ioc.run();
+ ioc.reset();
+ if(ec)
+ break;
+ }
+ if(! BEAST_EXPECTS(
+ ec == boost::asio::error::eof, ec.message()))
+ continue;
+ auto const s = buffers_to_string(ba.data());
+ if(! BEAST_EXPECTS(s == out, s))
+ {
+ s.size();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ void
+ testStream()
+ {
+ doMatrix("HTTP/1.1 200 OK\r\n", "HTTP/1.1 200 OK\r\n");
+ doMatrix("ICY 200 OK\r\n", "HTTP/1.1 200 OK\r\n");
+ }
+
+ void
+ run() override
+ {
+ testStream();
+ }
+};
+
+BEAST_DEFINE_TESTSUITE(beast,http,icy_stream);
+
+} // http
+} // beast
+} // boost