//------------------------------------------------------------------------------ /* This file is part of rippled: https://github.com/ripple/rippled Copyright (c) 2016 Ripple Labs Inc. 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 RIPPLE_WSPROTO_BASIC_SOCKET_H_INCLUDED #define RIPPLE_WSPROTO_BASIC_SOCKET_H_INCLUDED #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //------------------------------------------------------------------------------ #if 0//BOOST_VERSION >= 105800 #include template Int native_to_big(Int n); { return boost::endian::native_to_big(n); } template Int big_to_native(Int b) { return boost::endian::big_to_native(b); } #else template Int native_to_big(Int n); template Int big_to_native(Int b); template<> inline std::uint16_t native_to_big(std::uint16_t n) { std::uint8_t* p = reinterpret_cast(&n); std::swap(p[0], p[1]); return n; } template<> inline std::uint64_t native_to_big(std::uint64_t n) { return 0; } template<> inline std::uint16_t big_to_native(std::uint16_t b) { std::uint8_t* p = reinterpret_cast(&b); std::swap(p[0], p[1]); return b; } template<> inline std::uint64_t big_to_native(std::uint64_t b) { return 0; } #endif //------------------------------------------------------------------------------ namespace wsproto { struct frame_header { int op; bool fin; bool mask; bool rsv1; bool rsv2; bool rsv3; std::uint64_t len; std::array key; // next offset in key, [0-4) int offset = 0; }; namespace detail { // decode first 2 bytes of frame header // return: number of additional bytes needed template std::size_t decode_fh1(frame_header& fh, std::uint8_t const* p) { std::size_t need; fh.len = p[1] & 0x7f; switch(fh.len) { case 126: need = 2; break; case 127: need = 8; break; default: need = 0; } if((fh.mask = (p[1] & 0x80))) need += 4; fh.op = p[0] & 0x0f; fh.fin = p[0] & 0x80; fh.rsv1 = p[0] & 0x40; fh.rsv2 = p[0] & 0x20; fh.rsv3 = p[0] & 0x10; fh.offset = 0; return need; } // decode remainder of frame header template void decode_fh2(frame_header& fh, std::uint8_t const* p) { switch(fh.len) { case 126: fh.len = (std::uint64_t(p[0])<<8) + p[1]; p += 2; break; case 127: fh.len = 0; for(int i = 0; i < 8; ++i) fh.len = (fh.len << 8) + p[i]; p += 8; break; default: break; } if(fh.mask) std::memcpy(fh.key.data(), p, 4); } template< class StreamBuf, class ConstBuffers> void write_frame_payload(StreamBuf& sb, ConstBuffers const& cb) { using namespace boost::asio; sb.commit(buffer_copy( sb.prepare(buffer_size(cb)), cb)); } template< class StreamBuf, class ConstBuffers > void write_frame(StreamBuf& sb, ConstBuffers const& cb) { using namespace boost::asio; int const op = 1; bool const fin = true; bool const mask = false; std::array b; b[0] = (fin ? 0x80 : 0x00) | op; b[1] = mask ? 0x80 : 0x00; auto const len = buffer_size(cb); if (len <= 125) { b[1] |= len; sb.commit(buffer_copy( sb.prepare(2), buffer(&b[0], 2))); } else if (len <= 65535) { b[1] |= 126; std::uint16_t& d = *reinterpret_cast< std::uint16_t*>(&b[2]); d = native_to_big(len); sb.commit(buffer_copy( sb.prepare(4), buffer(&b[0], 4))); } else { b[1] |= 127; std::uint64_t& d = *reinterpret_cast< std::uint64_t*>(&b[2]); d = native_to_big(len); sb.commit(buffer_copy( sb.prepare(10), buffer(&b[0], 10))); } if(mask) { } write_frame_payload(sb, cb); } //------------------------------------------------------------------------------ // establish client connection template class connect_op { using error_code = boost::system::error_code; struct data { Stream& s; Handler h; data(Stream& s_, Handler&& h_) : s(s_) , h(std::forward(h_)) { } }; std::shared_ptr d_; public: connect_op(Stream& s_, Handler&& h_) : d_(std::make_shared( s_, std::forward(h_))) { } }; // read a frame header template class read_fh_op { using error_code = boost::system::error_code; struct data { Stream& s; frame_header& fh; Handler h; int state = 0; std::array buf; data(Stream& s_, frame_header& fh_, Handler&& h_) : s(s_) , fh(fh_) , h(std::forward(h_)) { } }; std::shared_ptr d_; public: read_fh_op(read_fh_op&&) = default; read_fh_op(read_fh_op const&) = default; read_fh_op(Stream& s, frame_header& fh, Handler&& h) : d_(std::make_shared(s, fh, std::forward(h))) { using namespace boost::asio; async_read(d_->s, mutable_buffers_1( d_->buf.data(), 2), std::move(*this)); } void operator()(error_code const& ec, std::size_t bytes_transferred) { using namespace boost::asio; if(ec == error::operation_aborted) return; if(! ec) { if(d_->state == 0) { d_->state = 1; async_read(d_->s, mutable_buffers_1( d_->buf.data(), detail::decode_fh1( d_->fh, d_->buf.data())), std::move(*this)); return; } detail::decode_fh2( d_->fh, d_->buf.data()); } return d_->s.get_io_service().wrap( std::move(d_->h))(ec); } friend auto asio_handler_allocate( std::size_t size, read_fh_op* op) { return boost_asio_handler_alloc_helpers:: allocate(size, op->d_->h); } friend auto asio_handler_deallocate( void* p, std::size_t size, read_fh_op* op) { return boost_asio_handler_alloc_helpers:: deallocate(p, size, op->d_->h); } friend auto asio_handler_is_continuation( read_fh_op* op) { return (op->d_->state != 0) ? true : boost_asio_handler_cont_helpers:: is_continuation(op->d_->h); } template friend auto asio_handler_invoke( Function&& f, read_fh_op* op) { return boost_asio_handler_invoke_helpers:: invoke(f, op->d_->h); } }; // read a frame body template struct read_op { using error_code = boost::system::error_code; struct data { Stream& s; frame_header fh; MutableBuffers b; Handler h; data(Stream& s_, frame_header const& fh_, MutableBuffers const& b_, Handler&& h_) : s(s_) , fh(fh_) , b(b_) , h(std::forward(h_)) { } }; std::shared_ptr d_; public: read_op(read_op&&) = default; read_op(read_op const&) = default; read_op(Stream& s, frame_header const& fh, MutableBuffers const& b, Handler&& h) : d_(std::make_shared(s, fh, b, std::forward(h))) { using namespace boost::asio; async_read(d_->s, d_->b, std::move(*this)); } void operator()(error_code const& ec, std::size_t bytes_transferred) { using namespace boost::asio; if(ec == error::operation_aborted) return; if(d_->fh.mask) { // apply mask key // adjust d_->fh.offset } return d_->s.get_io_service().wrap( std::move(d_->h))(ec, std::move(d_->fh), bytes_transferred); } friend auto asio_handler_allocate( std::size_t size, read_op* op) { return boost_asio_handler_alloc_helpers:: allocate(size, op->d_->h); } friend auto asio_handler_deallocate( void* p, std::size_t size, read_op* op) { return boost_asio_handler_alloc_helpers:: deallocate(p, size, op->d_->h); } friend auto asio_handler_is_continuation( read_op* op) { return boost_asio_handler_cont_helpers:: is_continuation(op->d_->h); } template friend auto asio_handler_invoke( Function&& f, read_op* op) { return boost_asio_handler_invoke_helpers:: invoke(f, op->d_->h); } }; } // detail //------------------------------------------------------------------------------ template < class Stream, class Allocator = std::allocator > class basic_socket { private: static_assert(! std::is_const::value, ""); using error_code = boost::system::error_code; Stream s_; boost::asio::io_service::strand st_; public: using next_layer_type = std::remove_reference_t; using lowest_layer_type = typename next_layer_type::lowest_layer_type; basic_socket(basic_socket&&) = default; basic_socket(basic_socket const&) = delete; basic_socket& operator= (basic_socket const&) = delete; basic_socket& operator= (basic_socket&&) = delete; template explicit basic_socket(Args&&... args) : s_(std::forward(args)...) , st_(s_.get_io_service()) { } ~basic_socket() { } boost::asio::io_service& get_io_service() { return s_.lowest_layer().get_io_service(); } next_layer_type& next_layer() { return s_; } next_layer_type const& next_layer() const { return s_; } lowest_layer_type& lowest_layer() { return s_.lowest_layer(); } lowest_layer_type const& lowest_layer() const { return s_.lowest_layer(); } public: /** Request a WebSocket upgrade. Requirements: Stream is connected. */ void connect(error_code& ec) { using namespace boost::asio; boost::asio::write(s_, buffer( "GET / HTTP/1.1\r\n" "Host: 127.0.0.1\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n"), ec); if(ec) return; streambuf sb; read_until(s_, sb, "\r\n\r\n"); using namespace beast; deprecated_http::body b; deprecated_http::message m; deprecated_http::parser p(m, b, false); auto const used = p.write(sb.data(), ec); if (ec || ! p.complete()) throw std::runtime_error(ec.message()); sb.consume(used); } // write a text message template void write(ConstBuffers const& cb) { boost::asio::streambuf sb; wsproto::detail::write_frame(sb, cb); boost::asio::write(s_, sb.data()); } public: // read a frame header asynchronously template void async_read_fh(frame_header& fh, Handler&& h) { static_assert(beast::is_call_possible::value, "Type does not meet the handler requirements"); detail::read_fh_op{ s_, fh, std::forward(h)}; } // read a frame body asynchronously // requires buffer_size(b) == fh.len template void async_read(frame_header const& fh, MutableBuffers const& b, Handler&& h) { static_assert(beast::is_call_possible::value, "Type does not meet the handler requirements"); detail::read_op{ s_, fh, b, std::forward(h)}; } }; } // wsproto #endif