summaryrefslogtreecommitdiff
path: root/boost/beast/websocket/detail
diff options
context:
space:
mode:
Diffstat (limited to 'boost/beast/websocket/detail')
-rw-r--r--boost/beast/websocket/detail/frame.hpp304
-rw-r--r--boost/beast/websocket/detail/hybi13.hpp75
-rw-r--r--boost/beast/websocket/detail/mask.hpp267
-rw-r--r--boost/beast/websocket/detail/pausation.hpp229
-rw-r--r--boost/beast/websocket/detail/pmd_extension.hpp442
-rw-r--r--boost/beast/websocket/detail/type_traits.hpp36
-rw-r--r--boost/beast/websocket/detail/utf8_checker.hpp344
7 files changed, 1697 insertions, 0 deletions
diff --git a/boost/beast/websocket/detail/frame.hpp b/boost/beast/websocket/detail/frame.hpp
new file mode 100644
index 0000000000..95080aeefc
--- /dev/null
+++ b/boost/beast/websocket/detail/frame.hpp
@@ -0,0 +1,304 @@
+//
+// 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_WEBSOCKET_DETAIL_FRAME_HPP
+#define BOOST_BEAST_WEBSOCKET_DETAIL_FRAME_HPP
+
+#include <boost/beast/websocket/rfc6455.hpp>
+#include <boost/beast/websocket/detail/utf8_checker.hpp>
+#include <boost/beast/core/buffers_suffix.hpp>
+#include <boost/beast/core/flat_static_buffer.hpp>
+#include <boost/beast/core/static_string.hpp>
+#include <boost/asio/buffer.hpp>
+#include <boost/assert.hpp>
+#include <boost/endian/buffers.hpp>
+#include <cstdint>
+
+namespace boost {
+namespace beast {
+namespace websocket {
+namespace detail {
+
+inline
+std::uint16_t
+big_uint16_to_native(void const* buf)
+{
+ auto const p = reinterpret_cast<
+ std::uint8_t const*>(buf);
+ return (p[0]<<8) + p[1];
+}
+
+inline
+std::uint64_t
+big_uint64_to_native(void const* buf)
+{
+ auto const p = reinterpret_cast<
+ std::uint8_t const*>(buf);
+ return
+ (static_cast<std::uint64_t>(p[0])<<56) +
+ (static_cast<std::uint64_t>(p[1])<<48) +
+ (static_cast<std::uint64_t>(p[2])<<40) +
+ (static_cast<std::uint64_t>(p[3])<<32) +
+ (static_cast<std::uint64_t>(p[4])<<24) +
+ (static_cast<std::uint64_t>(p[5])<<16) +
+ (static_cast<std::uint64_t>(p[6])<< 8) +
+ p[7];
+}
+
+inline
+std::uint32_t
+little_uint32_to_native(void const* buf)
+{
+ auto const p = reinterpret_cast<
+ std::uint8_t const*>(buf);
+ return
+ p[0] +
+ (static_cast<std::uint32_t>(p[1])<< 8) +
+ (static_cast<std::uint32_t>(p[2])<<16) +
+ (static_cast<std::uint32_t>(p[3])<<24);
+}
+
+inline
+void
+native_to_little_uint32(std::uint32_t v, void* buf)
+{
+ auto p = reinterpret_cast<std::uint8_t*>(buf);
+ p[0] = v & 0xff;
+ p[1] = (v >> 8) & 0xff;
+ p[2] = (v >> 16) & 0xff;
+ p[3] = (v >> 24) & 0xff;
+}
+
+/** WebSocket frame header opcodes. */
+enum class opcode : std::uint8_t
+{
+ cont = 0,
+ text = 1,
+ binary = 2,
+ rsv3 = 3,
+ rsv4 = 4,
+ rsv5 = 5,
+ rsv6 = 6,
+ rsv7 = 7,
+ close = 8,
+ ping = 9,
+ pong = 10,
+ crsvb = 11,
+ crsvc = 12,
+ crsvd = 13,
+ crsve = 14,
+ crsvf = 15
+};
+
+// Contents of a WebSocket frame header
+struct frame_header
+{
+ std::uint64_t len;
+ std::uint32_t key;
+ opcode op;
+ bool fin : 1;
+ bool mask : 1;
+ bool rsv1 : 1;
+ bool rsv2 : 1;
+ bool rsv3 : 1;
+};
+
+// holds the largest possible frame header
+using fh_buffer =
+ flat_static_buffer<14>;
+
+// holds the largest possible control frame
+using frame_buffer =
+ flat_static_buffer< 2 + 8 + 4 + 125 >;
+
+inline
+bool constexpr
+is_reserved(opcode op)
+{
+ return
+ (op >= opcode::rsv3 && op <= opcode::rsv7) ||
+ (op >= opcode::crsvb && op <= opcode::crsvf);
+}
+
+inline
+bool constexpr
+is_valid(opcode op)
+{
+ return op <= opcode::crsvf;
+}
+
+inline
+bool constexpr
+is_control(opcode op)
+{
+ return op >= opcode::close;
+}
+
+inline
+bool
+is_valid_close_code(std::uint16_t v)
+{
+ switch(v)
+ {
+ case close_code::normal: // 1000
+ case close_code::going_away: // 1001
+ case close_code::protocol_error: // 1002
+ case close_code::unknown_data: // 1003
+ case close_code::bad_payload: // 1007
+ case close_code::policy_error: // 1008
+ case close_code::too_big: // 1009
+ case close_code::needs_extension: // 1010
+ case close_code::internal_error: // 1011
+ case close_code::service_restart: // 1012
+ case close_code::try_again_later: // 1013
+ return true;
+
+ // explicitly reserved
+ case close_code::reserved1: // 1004
+ case close_code::no_status: // 1005
+ case close_code::abnormal: // 1006
+ case close_code::reserved2: // 1014
+ case close_code::reserved3: // 1015
+ return false;
+ }
+ // reserved
+ if(v >= 1016 && v <= 2999)
+ return false;
+ // not used
+ if(v <= 999)
+ return false;
+ return true;
+}
+
+//------------------------------------------------------------------------------
+
+// Write frame header to dynamic buffer
+//
+template<class DynamicBuffer>
+void
+write(DynamicBuffer& db, frame_header const& fh)
+{
+ using boost::asio::buffer;
+ using boost::asio::buffer_copy;
+ using namespace boost::endian;
+ std::size_t n;
+ std::uint8_t b[14];
+ b[0] = (fh.fin ? 0x80 : 0x00) | static_cast<std::uint8_t>(fh.op);
+ if(fh.rsv1)
+ b[0] |= 0x40;
+ if(fh.rsv2)
+ b[0] |= 0x20;
+ if(fh.rsv3)
+ b[0] |= 0x10;
+ b[1] = fh.mask ? 0x80 : 0x00;
+ if(fh.len <= 125)
+ {
+ b[1] |= fh.len;
+ n = 2;
+ }
+ else if(fh.len <= 65535)
+ {
+ b[1] |= 126;
+ ::new(&b[2]) big_uint16_buf_t{
+ (std::uint16_t)fh.len};
+ n = 4;
+ }
+ else
+ {
+ b[1] |= 127;
+ ::new(&b[2]) big_uint64_buf_t{fh.len};
+ n = 10;
+ }
+ if(fh.mask)
+ {
+ native_to_little_uint32(fh.key, &b[n]);
+ n += 4;
+ }
+ db.commit(buffer_copy(
+ db.prepare(n), buffer(b)));
+}
+
+// Read data from buffers
+// This is for ping and pong payloads
+//
+template<class Buffers>
+void
+read_ping(ping_data& data, Buffers const& bs)
+{
+ using boost::asio::buffer_copy;
+ using boost::asio::buffer_size;
+ using boost::asio::mutable_buffer;
+ BOOST_ASSERT(buffer_size(bs) <= data.max_size());
+ data.resize(buffer_size(bs));
+ buffer_copy(mutable_buffer{
+ data.data(), data.size()}, bs);
+}
+
+// Read close_reason, return true on success
+// This is for the close payload
+//
+template<class Buffers>
+void
+read_close(close_reason& cr,
+ Buffers const& bs, close_code& code)
+{
+ using boost::asio::buffer;
+ using boost::asio::buffer_copy;
+ using boost::asio::buffer_size;
+ using namespace boost::endian;
+ auto n = buffer_size(bs);
+ BOOST_ASSERT(n <= 125);
+ if(n == 0)
+ {
+ cr = close_reason{};
+ code = close_code::none;
+ return;
+ }
+ if(n == 1)
+ {
+ code = close_code::protocol_error;
+ return;
+ }
+ buffers_suffix<Buffers> cb(bs);
+ {
+ std::uint8_t b[2];
+ buffer_copy(buffer(b), cb);
+ cr.code = big_uint16_to_native(&b[0]);
+ cb.consume(2);
+ n -= 2;
+ if(! is_valid_close_code(cr.code))
+ {
+ code = close_code::protocol_error;
+ return;
+ }
+ }
+ if(n > 0)
+ {
+ cr.reason.resize(n);
+ buffer_copy(buffer(&cr.reason[0], n), cb);
+ if(! check_utf8(
+ cr.reason.data(), cr.reason.size()))
+ {
+ code = close_code::protocol_error;
+ return;
+ }
+ }
+ else
+ {
+ cr.reason = "";
+ }
+ code = close_code::none;
+}
+
+} // detail
+} // websocket
+} // beast
+} // boost
+
+#endif
diff --git a/boost/beast/websocket/detail/hybi13.hpp b/boost/beast/websocket/detail/hybi13.hpp
new file mode 100644
index 0000000000..b9c67b8200
--- /dev/null
+++ b/boost/beast/websocket/detail/hybi13.hpp
@@ -0,0 +1,75 @@
+//
+// 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_WEBSOCKET_DETAIL_HYBI13_HPP
+#define BOOST_BEAST_WEBSOCKET_DETAIL_HYBI13_HPP
+
+#include <boost/beast/core/static_string.hpp>
+#include <boost/beast/core/string.hpp>
+#include <boost/beast/core/detail/base64.hpp>
+#include <boost/beast/core/detail/sha1.hpp>
+#include <boost/assert.hpp>
+#include <array>
+#include <cstdint>
+#include <string>
+#include <type_traits>
+
+namespace boost {
+namespace beast {
+namespace websocket {
+namespace detail {
+
+using sec_ws_key_type = static_string<
+ beast::detail::base64::encoded_size(16)>;
+
+using sec_ws_accept_type = static_string<
+ beast::detail::base64::encoded_size(20)>;
+
+template<class Gen>
+void
+make_sec_ws_key(sec_ws_key_type& key, Gen& g)
+{
+ char a[16];
+ for(int i = 0; i < 16; i += 4)
+ {
+ auto const v = g();
+ a[i ] = v & 0xff;
+ a[i+1] = (v >> 8) & 0xff;
+ a[i+2] = (v >> 16) & 0xff;
+ a[i+3] = (v >> 24) & 0xff;
+ }
+ key.resize(key.max_size());
+ key.resize(beast::detail::base64::encode(
+ key.data(), &a[0], 16));
+}
+
+template<class = void>
+void
+make_sec_ws_accept(sec_ws_accept_type& accept,
+ string_view key)
+{
+ BOOST_ASSERT(key.size() <= sec_ws_key_type::max_size_n);
+ static_string<sec_ws_key_type::max_size_n + 36> m(key);
+ m.append("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
+ beast::detail::sha1_context ctx;
+ beast::detail::init(ctx);
+ beast::detail::update(ctx, m.data(), m.size());
+ char digest[beast::detail::sha1_context::digest_size];
+ beast::detail::finish(ctx, &digest[0]);
+ accept.resize(accept.max_size());
+ accept.resize(beast::detail::base64::encode(
+ accept.data(), &digest[0], sizeof(digest)));
+}
+
+} // detail
+} // websocket
+} // beast
+} // boost
+
+#endif
diff --git a/boost/beast/websocket/detail/mask.hpp b/boost/beast/websocket/detail/mask.hpp
new file mode 100644
index 0000000000..92ea0a4238
--- /dev/null
+++ b/boost/beast/websocket/detail/mask.hpp
@@ -0,0 +1,267 @@
+//
+// 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_WEBSOCKET_DETAIL_MASK_HPP
+#define BOOST_BEAST_WEBSOCKET_DETAIL_MASK_HPP
+
+#include <boost/beast/core/detail/config.hpp>
+#include <boost/asio/buffer.hpp>
+#include <array>
+#include <climits>
+#include <cstdint>
+#include <random>
+#include <type_traits>
+
+namespace boost {
+namespace beast {
+namespace websocket {
+namespace detail {
+
+// Pseudo-random source of mask keys
+//
+template<class Generator>
+class maskgen_t
+{
+ Generator g_;
+
+public:
+ using result_type =
+ typename Generator::result_type;
+
+ maskgen_t();
+
+ result_type
+ operator()() noexcept;
+
+ void
+ rekey();
+};
+
+template<class Generator>
+maskgen_t<Generator>::maskgen_t()
+{
+ rekey();
+}
+
+template<class Generator>
+auto
+maskgen_t<Generator>::operator()() noexcept ->
+ result_type
+{
+ for(;;)
+ if(auto key = g_())
+ return key;
+}
+
+template<class _>
+void
+maskgen_t<_>::rekey()
+{
+ std::random_device rng;
+#if 0
+ std::array<std::uint32_t, 32> e;
+ for(auto& i : e)
+ i = rng();
+ // VFALCO This constructor causes
+ // address sanitizer to fail, no idea why.
+ std::seed_seq ss(e.begin(), e.end());
+ g_.seed(ss);
+#else
+ g_.seed(rng());
+#endif
+}
+
+// VFALCO NOTE This generator has 5KB of state!
+//using maskgen = maskgen_t<std::mt19937>;
+using maskgen = maskgen_t<std::minstd_rand>;
+
+//------------------------------------------------------------------------------
+
+using prepared_key =
+ std::conditional<sizeof(void*) == 8,
+ std::uint64_t, std::uint32_t>::type;
+
+inline
+void
+prepare_key(std::uint32_t& prepared, std::uint32_t key)
+{
+ prepared = key;
+}
+
+inline
+void
+prepare_key(std::uint64_t& prepared, std::uint32_t key)
+{
+ prepared =
+ (static_cast<std::uint64_t>(key) << 32) | key;
+}
+
+template<class T>
+inline
+typename std::enable_if<std::is_integral<T>::value, T>::type
+ror(T t, unsigned n = 1)
+{
+ auto constexpr bits =
+ static_cast<unsigned>(
+ sizeof(T) * CHAR_BIT);
+ n &= bits-1;
+ return static_cast<T>((t << (bits - n)) | (
+ static_cast<typename std::make_unsigned<T>::type>(t) >> n));
+}
+
+// 32-bit optimized
+//
+template<class = void>
+void
+mask_inplace_fast(
+ boost::asio::mutable_buffer const& b,
+ std::uint32_t& key)
+{
+ auto n = b.size();
+ auto p = reinterpret_cast<std::uint8_t*>(b.data());
+ if(n >= sizeof(key))
+ {
+ // Bring p to 4-byte alignment
+ auto const i = reinterpret_cast<
+ std::uintptr_t>(p) & (sizeof(key)-1);
+ switch(i)
+ {
+ case 1: p[2] ^= static_cast<std::uint8_t>(key >> 16); BOOST_BEAST_FALLTHROUGH;
+ case 2: p[1] ^= static_cast<std::uint8_t>(key >> 8); BOOST_BEAST_FALLTHROUGH;
+ case 3: p[0] ^= static_cast<std::uint8_t>(key);
+ {
+ auto const d = static_cast<unsigned>(sizeof(key) - i);
+ key = ror(key, 8*d);
+ n -= d;
+ p += d;
+ BOOST_BEAST_FALLTHROUGH;
+ }
+ default:
+ break;
+ }
+ }
+
+ // Mask 4 bytes at a time
+ for(auto i = n / sizeof(key); i; --i)
+ {
+ *reinterpret_cast<
+ std::uint32_t*>(p) ^= key;
+ p += sizeof(key);
+ }
+
+ // Leftovers
+ n &= sizeof(key)-1;
+ switch(n)
+ {
+ case 3: p[2] ^= static_cast<std::uint8_t>(key >> 16); BOOST_BEAST_FALLTHROUGH;
+ case 2: p[1] ^= static_cast<std::uint8_t>(key >> 8); BOOST_BEAST_FALLTHROUGH;
+ case 1: p[0] ^= static_cast<std::uint8_t>(key);
+ key = ror(key, static_cast<unsigned>(8*n));
+ BOOST_BEAST_FALLTHROUGH;
+ default:
+ break;
+ }
+}
+
+// 64-bit optimized
+//
+template<class = void>
+void
+mask_inplace_fast(
+ boost::asio::mutable_buffer const& b,
+ std::uint64_t& key)
+{
+ auto n = b.size();
+ auto p = reinterpret_cast<std::uint8_t*>(b.data());
+ if(n >= sizeof(key))
+ {
+ // Bring p to 8-byte alignment
+ auto const i = reinterpret_cast<
+ std::uintptr_t>(p) & (sizeof(key)-1);
+ switch(i)
+ {
+ case 1: p[6] ^= static_cast<std::uint8_t>(key >> 48);
+ case 2: p[5] ^= static_cast<std::uint8_t>(key >> 40);
+ case 3: p[4] ^= static_cast<std::uint8_t>(key >> 32);
+ case 4: p[3] ^= static_cast<std::uint8_t>(key >> 24);
+ case 5: p[2] ^= static_cast<std::uint8_t>(key >> 16);
+ case 6: p[1] ^= static_cast<std::uint8_t>(key >> 8);
+ case 7: p[0] ^= static_cast<std::uint8_t>(key);
+ {
+ auto const d = static_cast<
+ unsigned>(sizeof(key) - i);
+ key = ror(key, 8*d);
+ n -= d;
+ p += d;
+ }
+ default:
+ break;
+ }
+ }
+
+ // Mask 8 bytes at a time
+ for(auto i = n / sizeof(key); i; --i)
+ {
+ *reinterpret_cast<
+ std::uint64_t*>(p) ^= key;
+ p += sizeof(key);
+ }
+
+ // Leftovers
+ n &= sizeof(key)-1;
+ switch(n)
+ {
+ case 7: p[6] ^= static_cast<std::uint8_t>(key >> 48);
+ case 6: p[5] ^= static_cast<std::uint8_t>(key >> 40);
+ case 5: p[4] ^= static_cast<std::uint8_t>(key >> 32);
+ case 4: p[3] ^= static_cast<std::uint8_t>(key >> 24);
+ case 3: p[2] ^= static_cast<std::uint8_t>(key >> 16);
+ case 2: p[1] ^= static_cast<std::uint8_t>(key >> 8);
+ case 1: p[0] ^= static_cast<std::uint8_t>(key);
+ key = ror(key, static_cast<unsigned>(8*n));
+ default:
+ break;
+ }
+}
+
+inline
+void
+mask_inplace(
+ boost::asio::mutable_buffer const& b,
+ std::uint32_t& key)
+{
+ mask_inplace_fast(b, key);
+}
+
+inline
+void
+mask_inplace(
+ boost::asio::mutable_buffer const& b,
+ std::uint64_t& key)
+{
+ mask_inplace_fast(b, key);
+}
+
+// Apply mask in place
+//
+template<class MutableBuffers, class KeyType>
+void
+mask_inplace(
+ MutableBuffers const& bs, KeyType& key)
+{
+ for(boost::asio::mutable_buffer b : bs)
+ mask_inplace(b, key);
+}
+
+} // detail
+} // websocket
+} // beast
+} // boost
+
+#endif
diff --git a/boost/beast/websocket/detail/pausation.hpp b/boost/beast/websocket/detail/pausation.hpp
new file mode 100644
index 0000000000..f51ee10327
--- /dev/null
+++ b/boost/beast/websocket/detail/pausation.hpp
@@ -0,0 +1,229 @@
+//
+// 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_WEBSOCKET_DETAIL_PAUSATION_HPP
+#define BOOST_BEAST_WEBSOCKET_DETAIL_PAUSATION_HPP
+
+#include <boost/beast/core/handler_ptr.hpp>
+#include <boost/asio/associated_allocator.hpp>
+#include <boost/asio/coroutine.hpp>
+#include <boost/assert.hpp>
+#include <array>
+#include <memory>
+#include <new>
+#include <utility>
+
+namespace boost {
+namespace beast {
+namespace websocket {
+namespace detail {
+
+// A container that holds a suspended, asynchronous composed
+// operation. The contained object may be invoked later to
+// resume the operation, or the container may be destroyed.
+//
+class pausation
+{
+ struct base
+ {
+ base() = default;
+ base(base &&) = delete;
+ base(base const&) = delete;
+ virtual ~base() = default;
+ virtual void operator()() = 0;
+ };
+
+ template<class F>
+ struct holder : base
+ {
+ F f;
+
+ holder(holder&&) = default;
+
+ template<class U>
+ explicit
+ holder(U&& u)
+ : f(std::forward<U>(u))
+ {
+ }
+
+ void
+ operator()() override
+ {
+ F f_(std::move(f));
+ this->~holder();
+ // invocation of f_() can
+ // assign a new object to *this.
+ f_();
+ }
+ };
+
+ struct exemplar : boost::asio::coroutine
+ {
+ struct H
+ {
+ void operator()();
+ };
+
+ struct T
+ {
+ using handler_type = H;
+ };
+
+ handler_ptr<T, H> hp;
+
+ void operator()();
+ };
+
+ template<class Op>
+ class saved_op
+ {
+ Op* op_ = nullptr;
+
+ public:
+ ~saved_op()
+ {
+ if(op_)
+ {
+ Op op(std::move(*op_));
+ op_->~Op();
+ typename std::allocator_traits<
+ boost::asio::associated_allocator_t<Op>>::
+ template rebind_alloc<Op> alloc{
+ boost::asio::get_associated_allocator(op)};
+ std::allocator_traits<
+ decltype(alloc)>::deallocate(alloc, op_, 1);
+ }
+ }
+
+ saved_op(saved_op&& other)
+ : op_(other.op_)
+ {
+ other.op_ = nullptr;
+ }
+
+ saved_op& operator=(saved_op&& other)
+ {
+ BOOST_ASSERT(! op_);
+ op_ = other.op_;
+ other.op_ = 0;
+ return *this;
+ }
+
+ explicit
+ saved_op(Op&& op)
+ {
+ typename std::allocator_traits<
+ boost::asio::associated_allocator_t<Op>>::
+ template rebind_alloc<Op> alloc{
+ boost::asio::get_associated_allocator(op)};
+ auto const p = std::allocator_traits<
+ decltype(alloc)>::allocate(alloc, 1);
+ op_ = new(p) Op{std::move(op)};
+ }
+
+ void
+ operator()()
+ {
+ BOOST_ASSERT(op_);
+ Op op{std::move(*op_)};
+ typename std::allocator_traits<
+ boost::asio::associated_allocator_t<Op>>::
+ template rebind_alloc<Op> alloc{
+ boost::asio::get_associated_allocator(op)};
+ std::allocator_traits<
+ decltype(alloc)>::deallocate(alloc, op_, 1);
+ op_ = nullptr;
+ op();
+ }
+ };
+
+ using buf_type = char[sizeof(holder<exemplar>)];
+
+ base* base_ = nullptr;
+ alignas(holder<exemplar>) buf_type buf_;
+
+public:
+ pausation() = default;
+ pausation(pausation const&) = delete;
+ pausation& operator=(pausation const&) = delete;
+
+ ~pausation()
+ {
+ if(base_)
+ base_->~base();
+ }
+
+ pausation(pausation&& other)
+ {
+ boost::ignore_unused(other);
+ BOOST_ASSERT(! other.base_);
+ }
+
+ pausation&
+ operator=(pausation&& other)
+ {
+ boost::ignore_unused(other);
+ BOOST_ASSERT(! base_);
+ BOOST_ASSERT(! other.base_);
+ return *this;
+ }
+
+ template<class F>
+ void
+ emplace(F&& f);
+
+ template<class F>
+ void
+ save(F&& f);
+
+ explicit
+ operator bool() const
+ {
+ return base_ != nullptr;
+ }
+
+ bool
+ maybe_invoke()
+ {
+ if(base_)
+ {
+ auto const basep = base_;
+ base_ = nullptr;
+ (*basep)();
+ return true;
+ }
+ return false;
+ }
+};
+
+template<class F>
+void
+pausation::emplace(F&& f)
+{
+ using type = holder<typename std::decay<F>::type>;
+ static_assert(sizeof(buf_type) >= sizeof(type),
+ "buffer too small");
+ BOOST_ASSERT(! base_);
+ base_ = ::new(buf_) type{std::forward<F>(f)};
+}
+
+template<class F>
+void
+pausation::save(F&& f)
+{
+ emplace(saved_op<F>{std::move(f)});
+}
+
+} // detail
+} // websocket
+} // beast
+} // boost
+
+#endif
diff --git a/boost/beast/websocket/detail/pmd_extension.hpp b/boost/beast/websocket/detail/pmd_extension.hpp
new file mode 100644
index 0000000000..a28b844cd7
--- /dev/null
+++ b/boost/beast/websocket/detail/pmd_extension.hpp
@@ -0,0 +1,442 @@
+//
+// 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_WEBSOCKET_DETAIL_PMD_EXTENSION_HPP
+#define BOOST_BEAST_WEBSOCKET_DETAIL_PMD_EXTENSION_HPP
+
+#include <boost/beast/core/error.hpp>
+#include <boost/beast/core/buffers_suffix.hpp>
+#include <boost/beast/core/read_size.hpp>
+#include <boost/beast/zlib/deflate_stream.hpp>
+#include <boost/beast/zlib/inflate_stream.hpp>
+#include <boost/beast/websocket/option.hpp>
+#include <boost/beast/http/rfc7230.hpp>
+#include <boost/asio/buffer.hpp>
+#include <utility>
+
+namespace boost {
+namespace beast {
+namespace websocket {
+namespace detail {
+
+// permessage-deflate offer parameters
+//
+// "context takeover" means:
+// preserve sliding window across messages
+//
+struct pmd_offer
+{
+ bool accept;
+
+ // 0 = absent, or 8..15
+ int server_max_window_bits;
+
+ // -1 = present, 0 = absent, or 8..15
+ int client_max_window_bits;
+
+ // `true` if server_no_context_takeover offered
+ bool server_no_context_takeover;
+
+ // `true` if client_no_context_takeover offered
+ bool client_no_context_takeover;
+};
+
+template<class = void>
+int
+parse_bits(string_view s)
+{
+ if(s.size() == 0)
+ return -1;
+ if(s.size() > 2)
+ return -1;
+ if(s[0] < '1' || s[0] > '9')
+ return -1;
+ unsigned i = 0;
+ for(auto c : s)
+ {
+ if(c < '0' || c > '9')
+ return -1;
+ auto const i0 = i;
+ i = 10 * i + (c - '0');
+ if(i < i0)
+ return -1;
+ }
+ return static_cast<int>(i);
+}
+
+// Parse permessage-deflate request fields
+//
+template<class Allocator>
+void
+pmd_read(pmd_offer& offer,
+ http::basic_fields<Allocator> const& fields)
+{
+ offer.accept = false;
+ offer.server_max_window_bits= 0;
+ offer.client_max_window_bits = 0;
+ offer.server_no_context_takeover = false;
+ offer.client_no_context_takeover = false;
+
+ http::ext_list list{
+ fields["Sec-WebSocket-Extensions"]};
+ for(auto const& ext : list)
+ {
+ if(iequals(ext.first, "permessage-deflate"))
+ {
+ for(auto const& param : ext.second)
+ {
+ if(iequals(param.first,
+ "server_max_window_bits"))
+ {
+ if(offer.server_max_window_bits != 0)
+ {
+ // The negotiation offer contains multiple
+ // extension parameters with the same name.
+ //
+ return; // MUST decline
+ }
+ if(param.second.empty())
+ {
+ // The negotiation offer extension
+ // parameter is missing the value.
+ //
+ return; // MUST decline
+ }
+ offer.server_max_window_bits =
+ parse_bits(param.second);
+ if( offer.server_max_window_bits < 8 ||
+ offer.server_max_window_bits > 15)
+ {
+ // The negotiation offer contains an
+ // extension parameter with an invalid value.
+ //
+ return; // MUST decline
+ }
+ }
+ else if(iequals(param.first,
+ "client_max_window_bits"))
+ {
+ if(offer.client_max_window_bits != 0)
+ {
+ // The negotiation offer contains multiple
+ // extension parameters with the same name.
+ //
+ return; // MUST decline
+ }
+ if(! param.second.empty())
+ {
+ offer.client_max_window_bits =
+ parse_bits(param.second);
+ if( offer.client_max_window_bits < 8 ||
+ offer.client_max_window_bits > 15)
+ {
+ // The negotiation offer contains an
+ // extension parameter with an invalid value.
+ //
+ return; // MUST decline
+ }
+ }
+ else
+ {
+ offer.client_max_window_bits = -1;
+ }
+ }
+ else if(iequals(param.first,
+ "server_no_context_takeover"))
+ {
+ if(offer.server_no_context_takeover)
+ {
+ // The negotiation offer contains multiple
+ // extension parameters with the same name.
+ //
+ return; // MUST decline
+ }
+ if(! param.second.empty())
+ {
+ // The negotiation offer contains an
+ // extension parameter with an invalid value.
+ //
+ return; // MUST decline
+ }
+ offer.server_no_context_takeover = true;
+ }
+ else if(iequals(param.first,
+ "client_no_context_takeover"))
+ {
+ if(offer.client_no_context_takeover)
+ {
+ // The negotiation offer contains multiple
+ // extension parameters with the same name.
+ //
+ return; // MUST decline
+ }
+ if(! param.second.empty())
+ {
+ // The negotiation offer contains an
+ // extension parameter with an invalid value.
+ //
+ return; // MUST decline
+ }
+ offer.client_no_context_takeover = true;
+ }
+ else
+ {
+ // The negotiation offer contains an extension
+ // parameter not defined for use in an offer.
+ //
+ return; // MUST decline
+ }
+ }
+ offer.accept = true;
+ return;
+ }
+ }
+}
+
+// Set permessage-deflate fields for a client offer
+//
+template<class Allocator>
+void
+pmd_write(http::basic_fields<Allocator>& fields,
+ pmd_offer const& offer)
+{
+ static_string<512> s;
+ s = "permessage-deflate";
+ if(offer.server_max_window_bits != 0)
+ {
+ if(offer.server_max_window_bits != -1)
+ {
+ s += "; server_max_window_bits=";
+ s += to_static_string(
+ offer.server_max_window_bits);
+ }
+ else
+ {
+ s += "; server_max_window_bits";
+ }
+ }
+ if(offer.client_max_window_bits != 0)
+ {
+ if(offer.client_max_window_bits != -1)
+ {
+ s += "; client_max_window_bits=";
+ s += to_static_string(
+ offer.client_max_window_bits);
+ }
+ else
+ {
+ s += "; client_max_window_bits";
+ }
+ }
+ if(offer.server_no_context_takeover)
+ {
+ s += "; server_no_context_takeover";
+ }
+ if(offer.client_no_context_takeover)
+ {
+ s += "; client_no_context_takeover";
+ }
+ fields.set(http::field::sec_websocket_extensions, s);
+}
+
+// Negotiate a permessage-deflate client offer
+//
+template<class Allocator>
+void
+pmd_negotiate(
+ http::basic_fields<Allocator>& fields,
+ pmd_offer& config,
+ pmd_offer const& offer,
+ permessage_deflate const& o)
+{
+ if(! (offer.accept && o.server_enable))
+ {
+ config.accept = false;
+ return;
+ }
+ config.accept = true;
+
+ static_string<512> s = "permessage-deflate";
+
+ config.server_no_context_takeover =
+ offer.server_no_context_takeover ||
+ o.server_no_context_takeover;
+ if(config.server_no_context_takeover)
+ s += "; server_no_context_takeover";
+
+ config.client_no_context_takeover =
+ o.client_no_context_takeover ||
+ offer.client_no_context_takeover;
+ if(config.client_no_context_takeover)
+ s += "; client_no_context_takeover";
+
+ if(offer.server_max_window_bits != 0)
+ config.server_max_window_bits = (std::min)(
+ offer.server_max_window_bits,
+ o.server_max_window_bits);
+ else
+ config.server_max_window_bits =
+ o.server_max_window_bits;
+ if(config.server_max_window_bits < 15)
+ {
+ // ZLib's deflateInit silently treats 8 as
+ // 9 due to a bug, so prevent 8 from being used.
+ //
+ if(config.server_max_window_bits < 9)
+ config.server_max_window_bits = 9;
+
+ s += "; server_max_window_bits=";
+ s += to_static_string(
+ config.server_max_window_bits);
+ }
+
+ switch(offer.client_max_window_bits)
+ {
+ case -1:
+ // extension parameter is present with no value
+ config.client_max_window_bits =
+ o.client_max_window_bits;
+ if(config.client_max_window_bits < 15)
+ {
+ s += "; client_max_window_bits=";
+ s += to_static_string(
+ config.client_max_window_bits);
+ }
+ break;
+
+ case 0:
+ /* extension parameter is absent.
+
+ If a received extension negotiation offer doesn't have the
+ "client_max_window_bits" extension parameter, the corresponding
+ extension negotiation response to the offer MUST NOT include the
+ "client_max_window_bits" extension parameter.
+ */
+ if(o.client_max_window_bits == 15)
+ config.client_max_window_bits = 15;
+ else
+ config.accept = false;
+ break;
+
+ default:
+ // extension parameter has value in [8..15]
+ config.client_max_window_bits = (std::min)(
+ o.client_max_window_bits,
+ offer.client_max_window_bits);
+ s += "; client_max_window_bits=";
+ s += to_static_string(
+ config.client_max_window_bits);
+ break;
+ }
+ if(config.accept)
+ fields.set(http::field::sec_websocket_extensions, s);
+}
+
+// Normalize the server's response
+//
+inline
+void
+pmd_normalize(pmd_offer& offer)
+{
+ if(offer.accept)
+ {
+ if( offer.server_max_window_bits == 0)
+ offer.server_max_window_bits = 15;
+
+ if( offer.client_max_window_bits == 0 ||
+ offer.client_max_window_bits == -1)
+ offer.client_max_window_bits = 15;
+ }
+}
+
+//--------------------------------------------------------------------
+
+// Compress a buffer sequence
+// Returns: `true` if more calls are needed
+//
+template<class DeflateStream, class ConstBufferSequence>
+bool
+deflate(
+ DeflateStream& zo,
+ boost::asio::mutable_buffer& out,
+ buffers_suffix<ConstBufferSequence>& cb,
+ bool fin,
+ std::size_t& total_in,
+ error_code& ec)
+{
+ using boost::asio::buffer;
+ BOOST_ASSERT(out.size() >= 6);
+ zlib::z_params zs;
+ zs.avail_in = 0;
+ zs.next_in = nullptr;
+ zs.avail_out = out.size();
+ zs.next_out = out.data();
+ for(auto in : beast::detail::buffers_range(cb))
+ {
+ zs.avail_in = in.size();
+ if(zs.avail_in == 0)
+ continue;
+ zs.next_in = in.data();
+ zo.write(zs, zlib::Flush::none, ec);
+ if(ec)
+ {
+ if(ec != zlib::error::need_buffers)
+ return false;
+ BOOST_ASSERT(zs.avail_out == 0);
+ BOOST_ASSERT(zs.total_out == out.size());
+ ec.assign(0, ec.category());
+ break;
+ }
+ if(zs.avail_out == 0)
+ {
+ BOOST_ASSERT(zs.total_out == out.size());
+ break;
+ }
+ BOOST_ASSERT(zs.avail_in == 0);
+ }
+ total_in = zs.total_in;
+ cb.consume(zs.total_in);
+ if(zs.avail_out > 0 && fin)
+ {
+ auto const remain = boost::asio::buffer_size(cb);
+ if(remain == 0)
+ {
+ // Inspired by Mark Adler
+ // https://github.com/madler/zlib/issues/149
+ //
+ // VFALCO We could do this flush twice depending
+ // on how much space is in the output.
+ zo.write(zs, zlib::Flush::block, ec);
+ BOOST_ASSERT(! ec || ec == zlib::error::need_buffers);
+ if(ec == zlib::error::need_buffers)
+ ec.assign(0, ec.category());
+ if(ec)
+ return false;
+ if(zs.avail_out >= 6)
+ {
+ zo.write(zs, zlib::Flush::full, ec);
+ BOOST_ASSERT(! ec);
+ // remove flush marker
+ zs.total_out -= 4;
+ out = buffer(out.data(), zs.total_out);
+ return false;
+ }
+ }
+ }
+ ec.assign(0, ec.category());
+ out = buffer(out.data(), zs.total_out);
+ return true;
+}
+
+} // detail
+} // websocket
+} // beast
+} // boost
+
+#endif
diff --git a/boost/beast/websocket/detail/type_traits.hpp b/boost/beast/websocket/detail/type_traits.hpp
new file mode 100644
index 0000000000..6c2806146c
--- /dev/null
+++ b/boost/beast/websocket/detail/type_traits.hpp
@@ -0,0 +1,36 @@
+//
+// 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_WEBSOCKET_DETAIL_TYPE_TRAITS_HPP
+#define BOOST_BEAST_WEBSOCKET_DETAIL_TYPE_TRAITS_HPP
+
+#include <boost/beast/websocket/rfc6455.hpp>
+#include <boost/beast/core/detail/type_traits.hpp>
+
+namespace boost {
+namespace beast {
+namespace websocket {
+namespace detail {
+
+template<class F>
+using is_RequestDecorator =
+ typename beast::detail::is_invocable<F,
+ void(request_type&)>::type;
+
+template<class F>
+using is_ResponseDecorator =
+ typename beast::detail::is_invocable<F,
+ void(response_type&)>::type;
+
+} // detail
+} // websocket
+} // beast
+} // boost
+
+#endif
diff --git a/boost/beast/websocket/detail/utf8_checker.hpp b/boost/beast/websocket/detail/utf8_checker.hpp
new file mode 100644
index 0000000000..e86310f051
--- /dev/null
+++ b/boost/beast/websocket/detail/utf8_checker.hpp
@@ -0,0 +1,344 @@
+//
+// 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_WEBSOCKET_DETAIL_UTF8_CHECKER_HPP
+#define BOOST_BEAST_WEBSOCKET_DETAIL_UTF8_CHECKER_HPP
+
+#include <boost/beast/core/type_traits.hpp>
+#include <boost/asio/buffer.hpp>
+#include <boost/assert.hpp>
+#include <algorithm>
+#include <cstdint>
+
+namespace boost {
+namespace beast {
+namespace websocket {
+namespace detail {
+
+/** A UTF8 validator.
+
+ This validator can be used to check if a buffer containing UTF8 text is
+ valid. The write function may be called incrementally with segmented UTF8
+ sequences. The finish function determines if all processed text is valid.
+*/
+template<class = void>
+class utf8_checker_t
+{
+ std::size_t need_ = 0; // chars we need to finish the code point
+ std::uint8_t* p_ = cp_; // current position in temp buffer
+ std::uint8_t cp_[4]; // a temp buffer for the code point
+
+public:
+ /** Prepare to process text as valid utf8
+ */
+ void
+ reset();
+
+ /** Check that all processed text is valid utf8
+ */
+ bool
+ finish();
+
+ /** Check if text is valid UTF8
+
+ @return `true` if the text is valid utf8 or false otherwise.
+ */
+ bool
+ write(std::uint8_t const* in, std::size_t size);
+
+ /** Check if text is valid UTF8
+
+ @return `true` if the text is valid utf8 or false otherwise.
+ */
+ template<class ConstBufferSequence>
+ bool
+ write(ConstBufferSequence const& bs);
+};
+
+template<class _>
+void
+utf8_checker_t<_>::
+reset()
+{
+ need_ = 0;
+ p_ = cp_;
+}
+
+template<class _>
+bool
+utf8_checker_t<_>::
+finish()
+{
+ auto const success = need_ == 0;
+ reset();
+ return success;
+}
+
+template<class _>
+template<class ConstBufferSequence>
+bool
+utf8_checker_t<_>::
+write(ConstBufferSequence const& bs)
+{
+ static_assert(boost::asio::is_const_buffer_sequence<ConstBufferSequence>::value,
+ "ConstBufferSequence requirements not met");
+ for(auto b : beast::detail::buffers_range(bs))
+ if(! write(reinterpret_cast<
+ std::uint8_t const*>(b.data()),
+ b.size()))
+ return false;
+ return true;
+}
+
+template<class _>
+bool
+utf8_checker_t<_>::
+write(std::uint8_t const* in, std::size_t size)
+{
+ auto const valid =
+ [](std::uint8_t const*& p)
+ {
+ if(p[0] < 128)
+ {
+ ++p;
+ return true;
+ }
+ if((p[0] & 0xe0) == 0xc0)
+ {
+ if( (p[1] & 0xc0) != 0x80 ||
+ (p[0] & 0xfe) == 0xc0) // overlong
+ return false;
+ p += 2;
+ return true;
+ }
+ if((p[0] & 0xf0) == 0xe0)
+ {
+ if( (p[1] & 0xc0) != 0x80
+ || (p[2] & 0xc0) != 0x80
+ || (p[0] == 0xe0 && (p[1] & 0xe0) == 0x80) // overlong
+ || (p[0] == 0xed && (p[1] & 0xe0) == 0xa0) // surrogate
+ //|| (p[0] == 0xef && p[1] == 0xbf && (p[2] & 0xfe) == 0xbe) // U+FFFE or U+FFFF
+ )
+ return false;
+ p += 3;
+ return true;
+ }
+ if((p[0] & 0xf8) == 0xf0)
+ {
+ if( (p[1] & 0xc0) != 0x80
+ || (p[2] & 0xc0) != 0x80
+ || (p[3] & 0xc0) != 0x80
+ || (p[0] == 0xf0 && (p[1] & 0xf0) == 0x80) // overlong
+ || (p[0] == 0xf4 && p[1] > 0x8f) || p[0] > 0xf4 // > U+10FFFF
+ )
+ return false;
+ p += 4;
+ return true;
+ }
+ return false;
+ };
+ auto const fail_fast =
+ [&]()
+ {
+ auto const n = p_ - cp_;
+ switch(n)
+ {
+ default:
+ BOOST_ASSERT(false);
+ BOOST_BEAST_FALLTHROUGH;
+ case 1:
+ cp_[1] = 0x81;
+ BOOST_BEAST_FALLTHROUGH;
+ case 2:
+ cp_[2] = 0x81;
+ BOOST_BEAST_FALLTHROUGH;
+ case 3:
+ cp_[3] = 0x81;
+ break;
+ }
+ std::uint8_t const* p = cp_;
+ return ! valid(p);
+ };
+ auto const needed =
+ [](std::uint8_t const v)
+ {
+ if(v < 128)
+ return 1;
+ if(v < 192)
+ return 0;
+ if(v < 224)
+ return 2;
+ if(v < 240)
+ return 3;
+ if(v < 248)
+ return 4;
+ return 0;
+ };
+
+ auto const end = in + size;
+
+ // Finish up any incomplete code point
+ if(need_ > 0)
+ {
+ // Calculate what we have
+ auto n = (std::min)(size, need_);
+ size -= n;
+ need_ -= n;
+
+ // Add characters to the code point
+ while(n--)
+ *p_++ = *in++;
+ BOOST_ASSERT(p_ <= cp_ + 5);
+
+ // Still incomplete?
+ if(need_ > 0)
+ {
+ // Incomplete code point
+ BOOST_ASSERT(in == end);
+
+ // Do partial validation on the incomplete
+ // code point, this is called "Fail fast"
+ // in Autobahn|Testsuite parlance.
+ return ! fail_fast();
+ }
+
+ // Complete code point, validate it
+ std::uint8_t const* p = &cp_[0];
+ if(! valid(p))
+ return false;
+ p_ = cp_;
+ }
+
+ if(size <= sizeof(std::size_t))
+ goto slow;
+
+ // Align `in` to sizeof(std::size_t) boundary
+ {
+ auto const in0 = in;
+ auto last = reinterpret_cast<std::uint8_t const*>(
+ ((reinterpret_cast<std::uintptr_t>(in) + sizeof(std::size_t) - 1) /
+ sizeof(std::size_t)) * sizeof(std::size_t));
+
+ // Check one character at a time for low-ASCII
+ while(in < last)
+ {
+ if(*in & 0x80)
+ {
+ // Not low-ASCII so switch to slow loop
+ size = size - (in - in0);
+ goto slow;
+ }
+ ++in;
+ }
+ size = size - (in - in0);
+ }
+
+ // Fast loop: Process 4 or 8 low-ASCII characters at a time
+ {
+ auto const in0 = in;
+ auto last = in + size - 7;
+ auto constexpr mask = static_cast<
+ std::size_t>(0x8080808080808080 & ~std::size_t{0});
+ while(in < last)
+ {
+#if 0
+ std::size_t temp;
+ std::memcpy(&temp, in, sizeof(temp));
+ if((temp & mask) != 0)
+#else
+ // Technically UB but works on all known platforms
+ if((*reinterpret_cast<std::size_t const*>(in) & mask) != 0)
+#endif
+ {
+ size = size - (in - in0);
+ goto slow;
+ }
+ in += sizeof(std::size_t);
+ }
+ // There's at least one more full code point left
+ last += 4;
+ while(in < last)
+ if(! valid(in))
+ return false;
+ goto tail;
+ }
+
+slow:
+ // Slow loop: Full validation on one code point at a time
+ {
+ auto last = in + size - 3;
+ while(in < last)
+ if(! valid(in))
+ return false;
+ }
+
+tail:
+ // Handle the remaining bytes. The last
+ // characters could split a code point so
+ // we save the partial code point for later.
+ //
+ // On entry to the loop, `in` points to the
+ // beginning of a code point.
+ //
+ for(;;)
+ {
+ // Number of chars left
+ auto n = end - in;
+ if(! n)
+ break;
+
+ // Chars we need to finish this code point
+ auto const need = needed(*in);
+ if(need == 0)
+ return false;
+ if(need <= n)
+ {
+ // Check a whole code point
+ if(! valid(in))
+ return false;
+ }
+ else
+ {
+ // Calculate how many chars we need
+ // to finish this partial code point
+ need_ = need - n;
+
+ // Save the partial code point
+ while(n--)
+ *p_++ = *in++;
+ BOOST_ASSERT(in == end);
+ BOOST_ASSERT(p_ <= cp_ + 5);
+
+ // Do partial validation on the incomplete
+ // code point, this is called "Fail fast"
+ // in Autobahn|Testsuite parlance.
+ return ! fail_fast();
+ }
+ }
+ return true;
+}
+
+using utf8_checker = utf8_checker_t<>;
+
+template<class = void>
+bool
+check_utf8(char const* p, std::size_t n)
+{
+ utf8_checker c;
+ if(! c.write(reinterpret_cast<const uint8_t*>(p), n))
+ return false;
+ return c.finish();
+}
+
+} // detail
+} // websocket
+} // beast
+} // boost
+
+#endif