diff options
Diffstat (limited to 'websocketpp/transport/asio')
-rw-r--r-- | websocketpp/transport/asio/base.hpp | 232 | ||||
-rw-r--r-- | websocketpp/transport/asio/connection.hpp | 1204 | ||||
-rw-r--r-- | websocketpp/transport/asio/endpoint.hpp | 1147 | ||||
-rw-r--r-- | websocketpp/transport/asio/security/base.hpp | 159 | ||||
-rw-r--r-- | websocketpp/transport/asio/security/none.hpp | 370 | ||||
-rw-r--r-- | websocketpp/transport/asio/security/tls.hpp | 484 |
6 files changed, 3596 insertions, 0 deletions
diff --git a/websocketpp/transport/asio/base.hpp b/websocketpp/transport/asio/base.hpp new file mode 100644 index 00000000..b945fe11 --- /dev/null +++ b/websocketpp/transport/asio/base.hpp @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2015, Peter Thorson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the WebSocket++ Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef WEBSOCKETPP_TRANSPORT_ASIO_BASE_HPP +#define WEBSOCKETPP_TRANSPORT_ASIO_BASE_HPP + +#include <websocketpp/common/asio.hpp> +#include <websocketpp/common/cpp11.hpp> +#include <websocketpp/common/functional.hpp> +#include <websocketpp/common/system_error.hpp> +#include <websocketpp/common/type_traits.hpp> + +#include <string> + +namespace websocketpp { +namespace transport { +/// Transport policy that uses asio +/** + * This policy uses a single asio io_service to provide transport + * services to a WebSocket++ endpoint. + */ +namespace asio { + +// Class to manage the memory to be used for handler-based custom allocation. +// It contains a single block of memory which may be returned for allocation +// requests. If the memory is in use when an allocation request is made, the +// allocator delegates allocation to the global heap. +class handler_allocator { +public: + static const size_t size = 1024; + + handler_allocator() : m_in_use(false) {} + +#ifdef _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_ + handler_allocator(handler_allocator const & cpy) = delete; + handler_allocator & operator =(handler_allocator const &) = delete; +#endif + + void * allocate(std::size_t memsize) { + if (!m_in_use && memsize < size) { + m_in_use = true; + return static_cast<void*>(&m_storage); + } else { + return ::operator new(memsize); + } + } + + void deallocate(void * pointer) { + if (pointer == &m_storage) { + m_in_use = false; + } else { + ::operator delete(pointer); + } + } + +private: + // Storage space used for handler-based custom memory allocation. + lib::aligned_storage<size>::type m_storage; + + // Whether the handler-based custom allocation storage has been used. + bool m_in_use; +}; + +// Wrapper class template for handler objects to allow handler memory +// allocation to be customised. Calls to operator() are forwarded to the +// encapsulated handler. +template <typename Handler> +class custom_alloc_handler { +public: + custom_alloc_handler(handler_allocator& a, Handler h) + : allocator_(a), + handler_(h) + {} + + template <typename Arg1> + void operator()(Arg1 arg1) { + handler_(arg1); + } + + template <typename Arg1, typename Arg2> + void operator()(Arg1 arg1, Arg2 arg2) { + handler_(arg1, arg2); + } + + friend void* asio_handler_allocate(std::size_t size, + custom_alloc_handler<Handler> * this_handler) + { + return this_handler->allocator_.allocate(size); + } + + friend void asio_handler_deallocate(void* pointer, std::size_t /*size*/, + custom_alloc_handler<Handler> * this_handler) + { + this_handler->allocator_.deallocate(pointer); + } + +private: + handler_allocator & allocator_; + Handler handler_; +}; + +// Helper function to wrap a handler object to add custom allocation. +template <typename Handler> +inline custom_alloc_handler<Handler> make_custom_alloc_handler( + handler_allocator & a, Handler h) +{ + return custom_alloc_handler<Handler>(a, h); +} + + + + + + + +// Forward declaration of class endpoint so that it can be friended/referenced +// before being included. +template <typename config> +class endpoint; + +typedef lib::function<void (lib::asio::error_code const & ec, + size_t bytes_transferred)> async_read_handler; + +typedef lib::function<void (lib::asio::error_code const & ec, + size_t bytes_transferred)> async_write_handler; + +typedef lib::function<void (lib::error_code const & ec)> pre_init_handler; + +// handle_timer: dynamic parameters, multiple copies +// handle_proxy_write +// handle_proxy_read +// handle_async_write +// handle_pre_init + + +/// Asio transport errors +namespace error { +enum value { + /// Catch-all error for transport policy errors that don't fit in other + /// categories + general = 1, + + /// async_read_at_least call requested more bytes than buffer can store + invalid_num_bytes, + + /// there was an error in the underlying transport library + pass_through, + + /// The connection to the requested proxy server failed + proxy_failed, + + /// Invalid Proxy URI + proxy_invalid, + + /// Invalid host or service + invalid_host_service +}; + +/// Asio transport error category +class category : public lib::error_category { +public: + char const * name() const _WEBSOCKETPP_NOEXCEPT_TOKEN_ { + return "websocketpp.transport.asio"; + } + + std::string message(int value) const { + switch(value) { + case error::general: + return "Generic asio transport policy error"; + case error::invalid_num_bytes: + return "async_read_at_least call requested more bytes than buffer can store"; + case error::pass_through: + return "Underlying Transport Error"; + case error::proxy_failed: + return "Proxy connection failed"; + case error::proxy_invalid: + return "Invalid proxy URI"; + case error::invalid_host_service: + return "Invalid host or service"; + default: + return "Unknown"; + } + } +}; + +/// Get a reference to a static copy of the asio transport error category +inline lib::error_category const & get_category() { + static category instance; + return instance; +} + +/// Create an error code with the given value and the asio transport category +inline lib::error_code make_error_code(error::value e) { + return lib::error_code(static_cast<int>(e), get_category()); +} + +} // namespace error +} // namespace asio +} // namespace transport +} // namespace websocketpp + +_WEBSOCKETPP_ERROR_CODE_ENUM_NS_START_ +template<> struct is_error_code_enum<websocketpp::transport::asio::error::value> +{ + static bool const value = true; +}; +_WEBSOCKETPP_ERROR_CODE_ENUM_NS_END_ +#endif // WEBSOCKETPP_TRANSPORT_ASIO_HPP diff --git a/websocketpp/transport/asio/connection.hpp b/websocketpp/transport/asio/connection.hpp new file mode 100644 index 00000000..8eb8c759 --- /dev/null +++ b/websocketpp/transport/asio/connection.hpp @@ -0,0 +1,1204 @@ +/* + * Copyright (c) 2015, Peter Thorson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the WebSocket++ Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef WEBSOCKETPP_TRANSPORT_ASIO_CON_HPP +#define WEBSOCKETPP_TRANSPORT_ASIO_CON_HPP + +#include <websocketpp/transport/asio/base.hpp> + +#include <websocketpp/transport/base/connection.hpp> + +#include <websocketpp/logger/levels.hpp> +#include <websocketpp/http/constants.hpp> + +#include <websocketpp/base64/base64.hpp> +#include <websocketpp/error.hpp> +#include <websocketpp/uri.hpp> + +#include <websocketpp/common/asio.hpp> +#include <websocketpp/common/chrono.hpp> +#include <websocketpp/common/cpp11.hpp> +#include <websocketpp/common/memory.hpp> +#include <websocketpp/common/functional.hpp> +#include <websocketpp/common/connection_hdl.hpp> + +#include <istream> +#include <sstream> +#include <string> +#include <vector> + +namespace websocketpp { +namespace transport { +namespace asio { + +typedef lib::function<void(connection_hdl)> tcp_init_handler; + +/// Asio based connection transport component +/** + * transport::asio::connection implements a connection transport component using + * Asio that works with the transport::asio::endpoint endpoint transport + * component. + */ +template <typename config> +class connection : public config::socket_type::socket_con_type { +public: + /// Type of this connection transport component + typedef connection<config> type; + /// Type of a shared pointer to this connection transport component + typedef lib::shared_ptr<type> ptr; + + /// Type of the socket connection component + typedef typename config::socket_type::socket_con_type socket_con_type; + /// Type of a shared pointer to the socket connection component + typedef typename socket_con_type::ptr socket_con_ptr; + /// Type of this transport's access logging policy + typedef typename config::alog_type alog_type; + /// Type of this transport's error logging policy + typedef typename config::elog_type elog_type; + + typedef typename config::request_type request_type; + typedef typename request_type::ptr request_ptr; + typedef typename config::response_type response_type; + typedef typename response_type::ptr response_ptr; + + /// Type of a pointer to the Asio io_service being used + typedef lib::asio::io_service * io_service_ptr; + /// Type of a pointer to the Asio io_service::strand being used + typedef lib::shared_ptr<lib::asio::io_service::strand> strand_ptr; + /// Type of a pointer to the Asio timer class + typedef lib::shared_ptr<lib::asio::steady_timer> timer_ptr; + + // connection is friends with its associated endpoint to allow the endpoint + // to call private/protected utility methods that we don't want to expose + // to the public api. + friend class endpoint<config>; + + // generate and manage our own io_service + explicit connection(bool is_server, alog_type & alog, elog_type & elog) + : m_is_server(is_server) + , m_alog(alog) + , m_elog(elog) + { + m_alog.write(log::alevel::devel,"asio con transport constructor"); + } + + /// Get a shared pointer to this component + ptr get_shared() { + return lib::static_pointer_cast<type>(socket_con_type::get_shared()); + } + + bool is_secure() const { + return socket_con_type::is_secure(); + } + + /// Set uri hook + /** + * Called by the endpoint as a connection is being established to provide + * the uri being connected to to the transport layer. + * + * This transport policy doesn't use the uri except to forward it to the + * socket layer. + * + * @since 0.6.0 + * + * @param u The uri to set + */ + void set_uri(uri_ptr u) { + socket_con_type::set_uri(u); + } + + /// Sets the tcp pre init handler + /** + * The tcp pre init handler is called after the raw tcp connection has been + * established but before any additional wrappers (proxy connects, TLS + * handshakes, etc) have been performed. + * + * @since 0.3.0 + * + * @param h The handler to call on tcp pre init. + */ + void set_tcp_pre_init_handler(tcp_init_handler h) { + m_tcp_pre_init_handler = h; + } + + /// Sets the tcp pre init handler (deprecated) + /** + * The tcp pre init handler is called after the raw tcp connection has been + * established but before any additional wrappers (proxy connects, TLS + * handshakes, etc) have been performed. + * + * @deprecated Use set_tcp_pre_init_handler instead + * + * @param h The handler to call on tcp pre init. + */ + void set_tcp_init_handler(tcp_init_handler h) { + set_tcp_pre_init_handler(h); + } + + /// Sets the tcp post init handler + /** + * The tcp post init handler is called after the tcp connection has been + * established and all additional wrappers (proxy connects, TLS handshakes, + * etc have been performed. This is fired before any bytes are read or any + * WebSocket specific handshake logic has been performed. + * + * @since 0.3.0 + * + * @param h The handler to call on tcp post init. + */ + void set_tcp_post_init_handler(tcp_init_handler h) { + m_tcp_post_init_handler = h; + } + + /// Set the proxy to connect through (exception free) + /** + * The URI passed should be a complete URI including scheme. For example: + * http://proxy.example.com:8080/ + * + * The proxy must be set up as an explicit (CONNECT) proxy allowed to + * connect to the port you specify. Traffic to the proxy is not encrypted. + * + * @param uri The full URI of the proxy to connect to. + * + * @param ec A status value + */ + void set_proxy(std::string const & uri, lib::error_code & ec) { + // TODO: return errors for illegal URIs here? + // TODO: should https urls be illegal for the moment? + m_proxy = uri; + m_proxy_data = lib::make_shared<proxy_data>(); + ec = lib::error_code(); + } + + /// Set the proxy to connect through (exception) + void set_proxy(std::string const & uri) { + lib::error_code ec; + set_proxy(uri,ec); + if (ec) { throw exception(ec); } + } + + /// Set the basic auth credentials to use (exception free) + /** + * The URI passed should be a complete URI including scheme. For example: + * http://proxy.example.com:8080/ + * + * The proxy must be set up as an explicit proxy + * + * @param username The username to send + * + * @param password The password to send + * + * @param ec A status value + */ + void set_proxy_basic_auth(std::string const & username, std::string const & + password, lib::error_code & ec) + { + if (!m_proxy_data) { + ec = make_error_code(websocketpp::error::invalid_state); + return; + } + + // TODO: username can't contain ':' + std::string val = "Basic "+base64_encode(username + ":" + password); + m_proxy_data->req.replace_header("Proxy-Authorization",val); + ec = lib::error_code(); + } + + /// Set the basic auth credentials to use (exception) + void set_proxy_basic_auth(std::string const & username, std::string const & + password) + { + lib::error_code ec; + set_proxy_basic_auth(username,password,ec); + if (ec) { throw exception(ec); } + } + + /// Set the proxy timeout duration (exception free) + /** + * Duration is in milliseconds. Default value is based on the transport + * config + * + * @param duration The number of milliseconds to wait before aborting the + * proxy connection. + * + * @param ec A status value + */ + void set_proxy_timeout(long duration, lib::error_code & ec) { + if (!m_proxy_data) { + ec = make_error_code(websocketpp::error::invalid_state); + return; + } + + m_proxy_data->timeout_proxy = duration; + ec = lib::error_code(); + } + + /// Set the proxy timeout duration (exception) + void set_proxy_timeout(long duration) { + lib::error_code ec; + set_proxy_timeout(duration,ec); + if (ec) { throw exception(ec); } + } + + std::string const & get_proxy() const { + return m_proxy; + } + + /// Get the remote endpoint address + /** + * The iostream transport has no information about the ultimate remote + * endpoint. It will return the string "iostream transport". To indicate + * this. + * + * TODO: allow user settable remote endpoint addresses if this seems useful + * + * @return A string identifying the address of the remote endpoint + */ + std::string get_remote_endpoint() const { + lib::error_code ec; + + std::string ret = socket_con_type::get_remote_endpoint(ec); + + if (ec) { + m_elog.write(log::elevel::info,ret); + return "Unknown"; + } else { + return ret; + } + } + + /// Get the connection handle + connection_hdl get_handle() const { + return m_connection_hdl; + } + + /// Call back a function after a period of time. + /** + * Sets a timer that calls back a function after the specified period of + * milliseconds. Returns a handle that can be used to cancel the timer. + * A cancelled timer will return the error code error::operation_aborted + * A timer that expired will return no error. + * + * @param duration Length of time to wait in milliseconds + * + * @param callback The function to call back when the timer has expired + * + * @return A handle that can be used to cancel the timer if it is no longer + * needed. + */ + timer_ptr set_timer(long duration, timer_handler callback) { + timer_ptr new_timer = lib::make_shared<lib::asio::steady_timer>( + lib::ref(*m_io_service), + lib::asio::milliseconds(duration) + ); + + if (config::enable_multithreading) { + new_timer->async_wait(m_strand->wrap(lib::bind( + &type::handle_timer, get_shared(), + new_timer, + callback, + lib::placeholders::_1 + ))); + } else { + new_timer->async_wait(lib::bind( + &type::handle_timer, get_shared(), + new_timer, + callback, + lib::placeholders::_1 + )); + } + + return new_timer; + } + + /// Timer callback + /** + * The timer pointer is included to ensure the timer isn't destroyed until + * after it has expired. + * + * TODO: candidate for protected status + * + * @param post_timer Pointer to the timer in question + * @param callback The function to call back + * @param ec The status code + */ + void handle_timer(timer_ptr, timer_handler callback, + lib::asio::error_code const & ec) + { + if (ec) { + if (ec == lib::asio::error::operation_aborted) { + callback(make_error_code(transport::error::operation_aborted)); + } else { + log_err(log::elevel::info,"asio handle_timer",ec); + callback(make_error_code(error::pass_through)); + } + } else { + callback(lib::error_code()); + } + } + + /// Get a pointer to this connection's strand + strand_ptr get_strand() { + return m_strand; + } + + /// Get the internal transport error code for a closed/failed connection + /** + * Retrieves a machine readable detailed error code indicating the reason + * that the connection was closed or failed. Valid only after the close or + * fail handler is called. + * + * Primarily used if you are using mismatched asio / system_error + * implementations such as `boost::asio` with `std::system_error`. In these + * cases the transport error type is different than the library error type + * and some WebSocket++ functions that return transport errors via the + * library error code type will be coerced into a catch all `pass_through` + * or `tls_error` error. This method will return the original machine + * readable transport error in the native type. + * + * @since 0.7.0 + * + * @return Error code indicating the reason the connection was closed or + * failed + */ + lib::asio::error_code get_transport_ec() const { + return m_tec; + } + + /// Initialize transport for reading + /** + * init_asio is called once immediately after construction to initialize + * Asio components to the io_service + * + * The transport initialization sequence consists of the following steps: + * - Pre-init: the underlying socket is initialized to the point where + * bytes may be written. No bytes are actually written in this stage + * - Proxy negotiation: if a proxy is set, a request is made to it to start + * a tunnel to the final destination. This stage ends when the proxy is + * ready to forward the + * next byte to the remote endpoint. + * - Post-init: Perform any i/o with the remote endpoint, such as setting up + * tunnels for encryption. This stage ends when the connection is ready to + * read or write the WebSocket handshakes. At this point the original + * callback function is called. + */ +protected: + void init(init_handler callback) { + if (m_alog.static_test(log::alevel::devel)) { + m_alog.write(log::alevel::devel,"asio connection init"); + } + + // TODO: pre-init timeout. Right now no implemented socket policies + // actually have an asyncronous pre-init + + socket_con_type::pre_init( + lib::bind( + &type::handle_pre_init, + get_shared(), + callback, + lib::placeholders::_1 + ) + ); + } + + /// initialize the proxy buffers and http parsers + /** + * + * @param authority The address of the server we want the proxy to tunnel to + * in the format of a URI authority (host:port) + * + * @return Status code indicating what errors occurred, if any + */ + lib::error_code proxy_init(std::string const & authority) { + if (!m_proxy_data) { + return websocketpp::error::make_error_code( + websocketpp::error::invalid_state); + } + m_proxy_data->req.set_version("HTTP/1.1"); + m_proxy_data->req.set_method("CONNECT"); + + m_proxy_data->req.set_uri(authority); + m_proxy_data->req.replace_header("Host",authority); + + return lib::error_code(); + } + + /// Finish constructing the transport + /** + * init_asio is called once immediately after construction to initialize + * Asio components to the io_service. + * + * @param io_service A pointer to the io_service to register with this + * connection + * + * @return Status code for the success or failure of the initialization + */ + lib::error_code init_asio (io_service_ptr io_service) { + m_io_service = io_service; + + if (config::enable_multithreading) { + m_strand = lib::make_shared<lib::asio::io_service::strand>( + lib::ref(*io_service)); + } + + lib::error_code ec = socket_con_type::init_asio(io_service, m_strand, + m_is_server); + + return ec; + } + + void handle_pre_init(init_handler callback, lib::error_code const & ec) { + if (m_alog.static_test(log::alevel::devel)) { + m_alog.write(log::alevel::devel,"asio connection handle pre_init"); + } + + if (m_tcp_pre_init_handler) { + m_tcp_pre_init_handler(m_connection_hdl); + } + + if (ec) { + callback(ec); + } + + // If we have a proxy set issue a proxy connect, otherwise skip to + // post_init + if (!m_proxy.empty()) { + proxy_write(callback); + } else { + post_init(callback); + } + } + + void post_init(init_handler callback) { + if (m_alog.static_test(log::alevel::devel)) { + m_alog.write(log::alevel::devel,"asio connection post_init"); + } + + timer_ptr post_timer; + + if (config::timeout_socket_post_init > 0) { + post_timer = set_timer( + config::timeout_socket_post_init, + lib::bind( + &type::handle_post_init_timeout, + get_shared(), + post_timer, + callback, + lib::placeholders::_1 + ) + ); + } + + socket_con_type::post_init( + lib::bind( + &type::handle_post_init, + get_shared(), + post_timer, + callback, + lib::placeholders::_1 + ) + ); + } + + /// Post init timeout callback + /** + * The timer pointer is included to ensure the timer isn't destroyed until + * after it has expired. + * + * @param post_timer Pointer to the timer in question + * @param callback The function to call back + * @param ec The status code + */ + void handle_post_init_timeout(timer_ptr, init_handler callback, + lib::error_code const & ec) + { + lib::error_code ret_ec; + + if (ec) { + if (ec == transport::error::operation_aborted) { + m_alog.write(log::alevel::devel, + "asio post init timer cancelled"); + return; + } + + log_err(log::elevel::devel,"asio handle_post_init_timeout",ec); + ret_ec = ec; + } else { + if (socket_con_type::get_ec()) { + ret_ec = socket_con_type::get_ec(); + } else { + ret_ec = make_error_code(transport::error::timeout); + } + } + + m_alog.write(log::alevel::devel, "Asio transport post-init timed out"); + cancel_socket_checked(); + callback(ret_ec); + } + + /// Post init timeout callback + /** + * The timer pointer is included to ensure the timer isn't destroyed until + * after it has expired. + * + * @param post_timer Pointer to the timer in question + * @param callback The function to call back + * @param ec The status code + */ + void handle_post_init(timer_ptr post_timer, init_handler callback, + lib::error_code const & ec) + { + if (ec == transport::error::operation_aborted || + (post_timer && lib::asio::is_neg(post_timer->expires_from_now()))) + { + m_alog.write(log::alevel::devel,"post_init cancelled"); + return; + } + + if (post_timer) { + post_timer->cancel(); + } + + if (m_alog.static_test(log::alevel::devel)) { + m_alog.write(log::alevel::devel,"asio connection handle_post_init"); + } + + if (m_tcp_post_init_handler) { + m_tcp_post_init_handler(m_connection_hdl); + } + + callback(ec); + } + + void proxy_write(init_handler callback) { + if (m_alog.static_test(log::alevel::devel)) { + m_alog.write(log::alevel::devel,"asio connection proxy_write"); + } + + if (!m_proxy_data) { + m_elog.write(log::elevel::library, + "assertion failed: !m_proxy_data in asio::connection::proxy_write"); + callback(make_error_code(error::general)); + return; + } + + m_proxy_data->write_buf = m_proxy_data->req.raw(); + + m_bufs.push_back(lib::asio::buffer(m_proxy_data->write_buf.data(), + m_proxy_data->write_buf.size())); + + m_alog.write(log::alevel::devel,m_proxy_data->write_buf); + + // Set a timer so we don't wait forever for the proxy to respond + m_proxy_data->timer = this->set_timer( + m_proxy_data->timeout_proxy, + lib::bind( + &type::handle_proxy_timeout, + get_shared(), + callback, + lib::placeholders::_1 + ) + ); + + // Send proxy request + if (config::enable_multithreading) { + lib::asio::async_write( + socket_con_type::get_next_layer(), + m_bufs, + m_strand->wrap(lib::bind( + &type::handle_proxy_write, get_shared(), + callback, + lib::placeholders::_1 + )) + ); + } else { + lib::asio::async_write( + socket_con_type::get_next_layer(), + m_bufs, + lib::bind( + &type::handle_proxy_write, get_shared(), + callback, + lib::placeholders::_1 + ) + ); + } + } + + void handle_proxy_timeout(init_handler callback, lib::error_code const & ec) + { + if (ec == transport::error::operation_aborted) { + m_alog.write(log::alevel::devel, + "asio handle_proxy_write timer cancelled"); + return; + } else if (ec) { + log_err(log::elevel::devel,"asio handle_proxy_write",ec); + callback(ec); + } else { + m_alog.write(log::alevel::devel, + "asio handle_proxy_write timer expired"); + cancel_socket_checked(); + callback(make_error_code(transport::error::timeout)); + } + } + + void handle_proxy_write(init_handler callback, + lib::asio::error_code const & ec) + { + if (m_alog.static_test(log::alevel::devel)) { + m_alog.write(log::alevel::devel, + "asio connection handle_proxy_write"); + } + + m_bufs.clear(); + + // Timer expired or the operation was aborted for some reason. + // Whatever aborted it will be issuing the callback so we are safe to + // return + if (ec == lib::asio::error::operation_aborted || + lib::asio::is_neg(m_proxy_data->timer->expires_from_now())) + { + m_elog.write(log::elevel::devel,"write operation aborted"); + return; + } + + if (ec) { + log_err(log::elevel::info,"asio handle_proxy_write",ec); + m_proxy_data->timer->cancel(); + callback(make_error_code(error::pass_through)); + return; + } + + proxy_read(callback); + } + + void proxy_read(init_handler callback) { + if (m_alog.static_test(log::alevel::devel)) { + m_alog.write(log::alevel::devel,"asio connection proxy_read"); + } + + if (!m_proxy_data) { + m_elog.write(log::elevel::library, + "assertion failed: !m_proxy_data in asio::connection::proxy_read"); + m_proxy_data->timer->cancel(); + callback(make_error_code(error::general)); + return; + } + + if (config::enable_multithreading) { + lib::asio::async_read_until( + socket_con_type::get_next_layer(), + m_proxy_data->read_buf, + "\r\n\r\n", + m_strand->wrap(lib::bind( + &type::handle_proxy_read, get_shared(), + callback, + lib::placeholders::_1, lib::placeholders::_2 + )) + ); + } else { + lib::asio::async_read_until( + socket_con_type::get_next_layer(), + m_proxy_data->read_buf, + "\r\n\r\n", + lib::bind( + &type::handle_proxy_read, get_shared(), + callback, + lib::placeholders::_1, lib::placeholders::_2 + ) + ); + } + } + + /// Proxy read callback + /** + * @param init_handler The function to call back + * @param ec The status code + * @param bytes_transferred The number of bytes read + */ + void handle_proxy_read(init_handler callback, + lib::asio::error_code const & ec, size_t) + { + if (m_alog.static_test(log::alevel::devel)) { + m_alog.write(log::alevel::devel, + "asio connection handle_proxy_read"); + } + + // Timer expired or the operation was aborted for some reason. + // Whatever aborted it will be issuing the callback so we are safe to + // return + if (ec == lib::asio::error::operation_aborted || + lib::asio::is_neg(m_proxy_data->timer->expires_from_now())) + { + m_elog.write(log::elevel::devel,"read operation aborted"); + return; + } + + // At this point there is no need to wait for the timer anymore + m_proxy_data->timer->cancel(); + + if (ec) { + m_elog.write(log::elevel::info, + "asio handle_proxy_read error: "+ec.message()); + callback(make_error_code(error::pass_through)); + } else { + if (!m_proxy_data) { + m_elog.write(log::elevel::library, + "assertion failed: !m_proxy_data in asio::connection::handle_proxy_read"); + callback(make_error_code(error::general)); + return; + } + + std::istream input(&m_proxy_data->read_buf); + + m_proxy_data->res.consume(input); + + if (!m_proxy_data->res.headers_ready()) { + // we read until the headers were done in theory but apparently + // they aren't. Internal endpoint error. + callback(make_error_code(error::general)); + return; + } + + m_alog.write(log::alevel::devel,m_proxy_data->res.raw()); + + if (m_proxy_data->res.get_status_code() != http::status_code::ok) { + // got an error response back + // TODO: expose this error in a programmatically accessible way? + // if so, see below for an option on how to do this. + std::stringstream s; + s << "Proxy connection error: " + << m_proxy_data->res.get_status_code() + << " (" + << m_proxy_data->res.get_status_msg() + << ")"; + m_elog.write(log::elevel::info,s.str()); + callback(make_error_code(error::proxy_failed)); + return; + } + + // we have successfully established a connection to the proxy, now + // we can continue and the proxy will transparently forward the + // WebSocket connection. + + // TODO: decide if we want an on_proxy callback that would allow + // access to the proxy response. + + // free the proxy buffers and req/res objects as they aren't needed + // anymore + m_proxy_data.reset(); + + // Continue with post proxy initialization + post_init(callback); + } + } + + /// read at least num_bytes bytes into buf and then call handler. + void async_read_at_least(size_t num_bytes, char *buf, size_t len, + read_handler handler) + { + if (m_alog.static_test(log::alevel::devel)) { + std::stringstream s; + s << "asio async_read_at_least: " << num_bytes; + m_alog.write(log::alevel::devel,s.str()); + } + + // TODO: safety vs speed ? + // maybe move into an if devel block + /*if (num_bytes > len) { + m_elog.write(log::elevel::devel, + "asio async_read_at_least error::invalid_num_bytes"); + handler(make_error_code(transport::error::invalid_num_bytes), + size_t(0)); + return; + }*/ + + if (config::enable_multithreading) { + lib::asio::async_read( + socket_con_type::get_socket(), + lib::asio::buffer(buf,len), + lib::asio::transfer_at_least(num_bytes), + m_strand->wrap(make_custom_alloc_handler( + m_read_handler_allocator, + lib::bind( + &type::handle_async_read, get_shared(), + handler, + lib::placeholders::_1, lib::placeholders::_2 + ) + )) + ); + } else { + lib::asio::async_read( + socket_con_type::get_socket(), + lib::asio::buffer(buf,len), + lib::asio::transfer_at_least(num_bytes), + make_custom_alloc_handler( + m_read_handler_allocator, + lib::bind( + &type::handle_async_read, get_shared(), + handler, + lib::placeholders::_1, lib::placeholders::_2 + ) + ) + ); + } + + } + + void handle_async_read(read_handler handler, lib::asio::error_code const & ec, + size_t bytes_transferred) + { + m_alog.write(log::alevel::devel, "asio con handle_async_read"); + + // translate asio error codes into more lib::error_codes + lib::error_code tec; + if (ec == lib::asio::error::eof) { + tec = make_error_code(transport::error::eof); + } else if (ec) { + // We don't know much more about the error at this point. As our + // socket/security policy if it knows more: + tec = socket_con_type::translate_ec(ec); + m_tec = ec; + + if (tec == transport::error::tls_error || + tec == transport::error::pass_through) + { + // These are aggregate/catch all errors. Log some human readable + // information to the info channel to give library users some + // more details about why the upstream method may have failed. + log_err(log::elevel::info,"asio async_read_at_least",ec); + } + } + if (handler) { + handler(tec,bytes_transferred); + } else { + // This can happen in cases where the connection is terminated while + // the transport is waiting on a read. + m_alog.write(log::alevel::devel, + "handle_async_read called with null read handler"); + } + } + + /// Initiate a potentially asyncronous write of the given buffer + void async_write(const char* buf, size_t len, write_handler handler) { + m_bufs.push_back(lib::asio::buffer(buf,len)); + + if (config::enable_multithreading) { + lib::asio::async_write( + socket_con_type::get_socket(), + m_bufs, + m_strand->wrap(make_custom_alloc_handler( + m_write_handler_allocator, + lib::bind( + &type::handle_async_write, get_shared(), + handler, + lib::placeholders::_1, lib::placeholders::_2 + ) + )) + ); + } else { + lib::asio::async_write( + socket_con_type::get_socket(), + m_bufs, + make_custom_alloc_handler( + m_write_handler_allocator, + lib::bind( + &type::handle_async_write, get_shared(), + handler, + lib::placeholders::_1, lib::placeholders::_2 + ) + ) + ); + } + } + + /// Initiate a potentially asyncronous write of the given buffers + void async_write(std::vector<buffer> const & bufs, write_handler handler) { + std::vector<buffer>::const_iterator it; + + for (it = bufs.begin(); it != bufs.end(); ++it) { + m_bufs.push_back(lib::asio::buffer((*it).buf,(*it).len)); + } + + if (config::enable_multithreading) { + lib::asio::async_write( + socket_con_type::get_socket(), + m_bufs, + m_strand->wrap(make_custom_alloc_handler( + m_write_handler_allocator, + lib::bind( + &type::handle_async_write, get_shared(), + handler, + lib::placeholders::_1, lib::placeholders::_2 + ) + )) + ); + } else { + lib::asio::async_write( + socket_con_type::get_socket(), + m_bufs, + make_custom_alloc_handler( + m_write_handler_allocator, + lib::bind( + &type::handle_async_write, get_shared(), + handler, + lib::placeholders::_1, lib::placeholders::_2 + ) + ) + ); + } + } + + /// Async write callback + /** + * @param ec The status code + * @param bytes_transferred The number of bytes read + */ + void handle_async_write(write_handler handler, lib::asio::error_code const & ec, size_t) { + m_bufs.clear(); + lib::error_code tec; + if (ec) { + log_err(log::elevel::info,"asio async_write",ec); + tec = make_error_code(transport::error::pass_through); + } + if (handler) { + handler(tec); + } else { + // This can happen in cases where the connection is terminated while + // the transport is waiting on a read. + m_alog.write(log::alevel::devel, + "handle_async_write called with null write handler"); + } + } + + /// Set Connection Handle + /** + * See common/connection_hdl.hpp for information + * + * @param hdl A connection_hdl that the transport will use to refer + * to itself + */ + void set_handle(connection_hdl hdl) { + m_connection_hdl = hdl; + socket_con_type::set_handle(hdl); + } + + /// Trigger the on_interrupt handler + /** + * This needs to be thread safe + */ + lib::error_code interrupt(interrupt_handler handler) { + if (config::enable_multithreading) { + m_io_service->post(m_strand->wrap(handler)); + } else { + m_io_service->post(handler); + } + return lib::error_code(); + } + + lib::error_code dispatch(dispatch_handler handler) { + if (config::enable_multithreading) { + m_io_service->post(m_strand->wrap(handler)); + } else { + m_io_service->post(handler); + } + return lib::error_code(); + } + + /*void handle_interrupt(interrupt_handler handler) { + handler(); + }*/ + + /// close and clean up the underlying socket + void async_shutdown(shutdown_handler callback) { + if (m_alog.static_test(log::alevel::devel)) { + m_alog.write(log::alevel::devel,"asio connection async_shutdown"); + } + + timer_ptr shutdown_timer; + shutdown_timer = set_timer( + config::timeout_socket_shutdown, + lib::bind( + &type::handle_async_shutdown_timeout, + get_shared(), + shutdown_timer, + callback, + lib::placeholders::_1 + ) + ); + + socket_con_type::async_shutdown( + lib::bind( + &type::handle_async_shutdown, + get_shared(), + shutdown_timer, + callback, + lib::placeholders::_1 + ) + ); + } + + /// Async shutdown timeout handler + /** + * @param shutdown_timer A pointer to the timer to keep it in scope + * @param callback The function to call back + * @param ec The status code + */ + void handle_async_shutdown_timeout(timer_ptr, init_handler callback, + lib::error_code const & ec) + { + lib::error_code ret_ec; + + if (ec) { + if (ec == transport::error::operation_aborted) { + m_alog.write(log::alevel::devel, + "asio socket shutdown timer cancelled"); + return; + } + + log_err(log::elevel::devel,"asio handle_async_shutdown_timeout",ec); + ret_ec = ec; + } else { + ret_ec = make_error_code(transport::error::timeout); + } + + m_alog.write(log::alevel::devel, + "Asio transport socket shutdown timed out"); + cancel_socket_checked(); + callback(ret_ec); + } + + void handle_async_shutdown(timer_ptr shutdown_timer, shutdown_handler + callback, lib::asio::error_code const & ec) + { + if (ec == lib::asio::error::operation_aborted || + lib::asio::is_neg(shutdown_timer->expires_from_now())) + { + m_alog.write(log::alevel::devel,"async_shutdown cancelled"); + return; + } + + shutdown_timer->cancel(); + + lib::error_code tec; + if (ec) { + if (ec == lib::asio::error::not_connected) { + // The socket was already closed when we tried to close it. This + // happens periodically (usually if a read or write fails + // earlier and if it is a real error will be caught at another + // level of the stack. + } else { + // We don't know anything more about this error, give our + // socket/security policy a crack at it. + tec = socket_con_type::translate_ec(ec); + m_tec = ec; + + if (tec == transport::error::tls_short_read) { + // TLS short read at this point is somewhat expected if both + // sides try and end the connection at the same time or if + // SSLv2 is being used. In general there is nothing that can + // be done here other than a low level development log. + } else { + // all other errors are effectively pass through errors of + // some sort so print some detail on the info channel for + // library users to look up if needed. + log_err(log::elevel::info,"asio async_shutdown",ec); + } + } + } else { + if (m_alog.static_test(log::alevel::devel)) { + m_alog.write(log::alevel::devel, + "asio con handle_async_shutdown"); + } + } + callback(tec); + } + + /// Cancel the underlying socket and log any errors + void cancel_socket_checked() { + lib::asio::error_code cec = socket_con_type::cancel_socket(); + if (cec) { + if (cec == lib::asio::error::operation_not_supported) { + // cancel not supported on this OS, ignore and log at dev level + m_alog.write(log::alevel::devel, "socket cancel not supported"); + } else { + log_err(log::elevel::warn, "socket cancel failed", cec); + } + } + } + +private: + /// Convenience method for logging the code and message for an error_code + template <typename error_type> + void log_err(log::level l, const char * msg, const error_type & ec) { + std::stringstream s; + s << msg << " error: " << ec << " (" << ec.message() << ")"; + m_elog.write(l,s.str()); + } + + // static settings + const bool m_is_server; + alog_type& m_alog; + elog_type& m_elog; + + struct proxy_data { + proxy_data() : timeout_proxy(config::timeout_proxy) {} + + request_type req; + response_type res; + std::string write_buf; + lib::asio::streambuf read_buf; + long timeout_proxy; + timer_ptr timer; + }; + + std::string m_proxy; + lib::shared_ptr<proxy_data> m_proxy_data; + + // transport resources + io_service_ptr m_io_service; + strand_ptr m_strand; + connection_hdl m_connection_hdl; + + std::vector<lib::asio::const_buffer> m_bufs; + + /// Detailed internal error code + lib::asio::error_code m_tec; + + // Handlers + tcp_init_handler m_tcp_pre_init_handler; + tcp_init_handler m_tcp_post_init_handler; + + handler_allocator m_read_handler_allocator; + handler_allocator m_write_handler_allocator; +}; + + +} // namespace asio +} // namespace transport +} // namespace websocketpp + +#endif // WEBSOCKETPP_TRANSPORT_ASIO_CON_HPP diff --git a/websocketpp/transport/asio/endpoint.hpp b/websocketpp/transport/asio/endpoint.hpp new file mode 100644 index 00000000..46ff24c0 --- /dev/null +++ b/websocketpp/transport/asio/endpoint.hpp @@ -0,0 +1,1147 @@ +/* + * Copyright (c) 2015, Peter Thorson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the WebSocket++ Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef WEBSOCKETPP_TRANSPORT_ASIO_HPP +#define WEBSOCKETPP_TRANSPORT_ASIO_HPP + +#include <websocketpp/transport/base/endpoint.hpp> +#include <websocketpp/transport/asio/connection.hpp> +#include <websocketpp/transport/asio/security/none.hpp> + +#include <websocketpp/uri.hpp> +#include <websocketpp/logger/levels.hpp> + +#include <websocketpp/common/functional.hpp> + +#include <sstream> +#include <string> + +namespace websocketpp { +namespace transport { +namespace asio { + +/// Asio based endpoint transport component +/** + * transport::asio::endpoint implements an endpoint transport component using + * Asio. + */ +template <typename config> +class endpoint : public config::socket_type { +public: + /// Type of this endpoint transport component + typedef endpoint<config> type; + + /// Type of the concurrency policy + typedef typename config::concurrency_type concurrency_type; + /// Type of the socket policy + typedef typename config::socket_type socket_type; + /// Type of the error logging policy + typedef typename config::elog_type elog_type; + /// Type of the access logging policy + typedef typename config::alog_type alog_type; + + /// Type of the socket connection component + typedef typename socket_type::socket_con_type socket_con_type; + /// Type of a shared pointer to the socket connection component + typedef typename socket_con_type::ptr socket_con_ptr; + + /// Type of the connection transport component associated with this + /// endpoint transport component + typedef asio::connection<config> transport_con_type; + /// Type of a shared pointer to the connection transport component + /// associated with this endpoint transport component + typedef typename transport_con_type::ptr transport_con_ptr; + + /// Type of a pointer to the ASIO io_service being used + typedef lib::asio::io_service * io_service_ptr; + /// Type of a shared pointer to the acceptor being used + typedef lib::shared_ptr<lib::asio::ip::tcp::acceptor> acceptor_ptr; + /// Type of a shared pointer to the resolver being used + typedef lib::shared_ptr<lib::asio::ip::tcp::resolver> resolver_ptr; + /// Type of timer handle + typedef lib::shared_ptr<lib::asio::steady_timer> timer_ptr; + /// Type of a shared pointer to an io_service work object + typedef lib::shared_ptr<lib::asio::io_service::work> work_ptr; + + // generate and manage our own io_service + explicit endpoint() + : m_io_service(NULL) + , m_external_io_service(false) + , m_listen_backlog(0) + , m_reuse_addr(false) + , m_state(UNINITIALIZED) + { + //std::cout << "transport::asio::endpoint constructor" << std::endl; + } + + ~endpoint() { + // clean up our io_service if we were initialized with an internal one. + + // Explicitly destroy local objects + m_acceptor.reset(); + m_resolver.reset(); + m_work.reset(); + if (m_state != UNINITIALIZED && !m_external_io_service) { + delete m_io_service; + } + } + + /// transport::asio objects are moveable but not copyable or assignable. + /// The following code sets this situation up based on whether or not we + /// have C++11 support or not +#ifdef _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_ + endpoint(const endpoint & src) = delete; + endpoint& operator= (const endpoint & rhs) = delete; +#else +private: + endpoint(const endpoint & src); + endpoint & operator= (const endpoint & rhs); +public: +#endif // _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_ + +#ifdef _WEBSOCKETPP_MOVE_SEMANTICS_ + endpoint (endpoint && src) + : config::socket_type(std::move(src)) + , m_tcp_pre_init_handler(src.m_tcp_pre_init_handler) + , m_tcp_post_init_handler(src.m_tcp_post_init_handler) + , m_io_service(src.m_io_service) + , m_external_io_service(src.m_external_io_service) + , m_acceptor(src.m_acceptor) + , m_listen_backlog(lib::asio::socket_base::max_connections) + , m_reuse_addr(src.m_reuse_addr) + , m_elog(src.m_elog) + , m_alog(src.m_alog) + , m_state(src.m_state) + { + src.m_io_service = NULL; + src.m_external_io_service = false; + src.m_acceptor = NULL; + src.m_state = UNINITIALIZED; + } + + /*endpoint & operator= (const endpoint && rhs) { + if (this != &rhs) { + m_io_service = rhs.m_io_service; + m_external_io_service = rhs.m_external_io_service; + m_acceptor = rhs.m_acceptor; + m_listen_backlog = rhs.m_listen_backlog; + m_reuse_addr = rhs.m_reuse_addr; + m_state = rhs.m_state; + + rhs.m_io_service = NULL; + rhs.m_external_io_service = false; + rhs.m_acceptor = NULL; + rhs.m_listen_backlog = lib::asio::socket_base::max_connections; + rhs.m_state = UNINITIALIZED; + + // TODO: this needs to be updated + } + return *this; + }*/ +#endif // _WEBSOCKETPP_MOVE_SEMANTICS_ + + /// Return whether or not the endpoint produces secure connections. + bool is_secure() const { + return socket_type::is_secure(); + } + + /// initialize asio transport with external io_service (exception free) + /** + * Initialize the ASIO transport policy for this endpoint using the provided + * io_service object. asio_init must be called exactly once on any endpoint + * that uses transport::asio before it can be used. + * + * @param ptr A pointer to the io_service to use for asio events + * @param ec Set to indicate what error occurred, if any. + */ + void init_asio(io_service_ptr ptr, lib::error_code & ec) { + if (m_state != UNINITIALIZED) { + m_elog->write(log::elevel::library, + "asio::init_asio called from the wrong state"); + using websocketpp::error::make_error_code; + ec = make_error_code(websocketpp::error::invalid_state); + return; + } + + m_alog->write(log::alevel::devel,"asio::init_asio"); + + m_io_service = ptr; + m_external_io_service = true; + m_acceptor = lib::make_shared<lib::asio::ip::tcp::acceptor>( + lib::ref(*m_io_service)); + + m_state = READY; + ec = lib::error_code(); + } + + /// initialize asio transport with external io_service + /** + * Initialize the ASIO transport policy for this endpoint using the provided + * io_service object. asio_init must be called exactly once on any endpoint + * that uses transport::asio before it can be used. + * + * @param ptr A pointer to the io_service to use for asio events + */ + void init_asio(io_service_ptr ptr) { + lib::error_code ec; + init_asio(ptr,ec); + if (ec) { throw exception(ec); } + } + + /// Initialize asio transport with internal io_service (exception free) + /** + * This method of initialization will allocate and use an internally managed + * io_service. + * + * @see init_asio(io_service_ptr ptr) + * + * @param ec Set to indicate what error occurred, if any. + */ + void init_asio(lib::error_code & ec) { + // Use a smart pointer until the call is successful and ownership has + // successfully been taken. Use unique_ptr when available. + // TODO: remove the use of auto_ptr when C++98/03 support is no longer + // necessary. +#ifdef _WEBSOCKETPP_CPP11_MEMORY_ + lib::unique_ptr<lib::asio::io_service> service(new lib::asio::io_service()); +#else + lib::auto_ptr<lib::asio::io_service> service(new lib::asio::io_service()); +#endif + init_asio(service.get(), ec); + if( !ec ) service.release(); // Call was successful, transfer ownership + m_external_io_service = false; + } + + /// Initialize asio transport with internal io_service + /** + * This method of initialization will allocate and use an internally managed + * io_service. + * + * @see init_asio(io_service_ptr ptr) + */ + void init_asio() { + // Use a smart pointer until the call is successful and ownership has + // successfully been taken. Use unique_ptr when available. + // TODO: remove the use of auto_ptr when C++98/03 support is no longer + // necessary. +#ifdef _WEBSOCKETPP_CPP11_MEMORY_ + lib::unique_ptr<lib::asio::io_service> service(new lib::asio::io_service()); +#else + lib::auto_ptr<lib::asio::io_service> service(new lib::asio::io_service()); +#endif + init_asio( service.get() ); + // If control got this far without an exception, then ownership has successfully been taken + service.release(); + m_external_io_service = false; + } + + /// Sets the tcp pre init handler + /** + * The tcp pre init handler is called after the raw tcp connection has been + * established but before any additional wrappers (proxy connects, TLS + * handshakes, etc) have been performed. + * + * @since 0.3.0 + * + * @param h The handler to call on tcp pre init. + */ + void set_tcp_pre_init_handler(tcp_init_handler h) { + m_tcp_pre_init_handler = h; + } + + /// Sets the tcp pre init handler (deprecated) + /** + * The tcp pre init handler is called after the raw tcp connection has been + * established but before any additional wrappers (proxy connects, TLS + * handshakes, etc) have been performed. + * + * @deprecated Use set_tcp_pre_init_handler instead + * + * @param h The handler to call on tcp pre init. + */ + void set_tcp_init_handler(tcp_init_handler h) { + set_tcp_pre_init_handler(h); + } + + /// Sets the tcp post init handler + /** + * The tcp post init handler is called after the tcp connection has been + * established and all additional wrappers (proxy connects, TLS handshakes, + * etc have been performed. This is fired before any bytes are read or any + * WebSocket specific handshake logic has been performed. + * + * @since 0.3.0 + * + * @param h The handler to call on tcp post init. + */ + void set_tcp_post_init_handler(tcp_init_handler h) { + m_tcp_post_init_handler = h; + } + + /// Sets the maximum length of the queue of pending connections. + /** + * Sets the maximum length of the queue of pending connections. Increasing + * this will allow WebSocket++ to queue additional incoming connections. + * Setting it higher may prevent failed connections at high connection rates + * but may cause additional latency. + * + * For this value to take effect you may need to adjust operating system + * settings. + * + * New values affect future calls to listen only. + * + * A value of zero will use the operating system default. This is the + * default value. + * + * @since 0.3.0 + * + * @param backlog The maximum length of the queue of pending connections + */ + void set_listen_backlog(int backlog) { + m_listen_backlog = backlog; + } + + /// Sets whether to use the SO_REUSEADDR flag when opening listening sockets + /** + * Specifies whether or not to use the SO_REUSEADDR TCP socket option. What + * this flag does depends on your operating system. Please consult operating + * system documentation for more details. + * + * New values affect future calls to listen only. + * + * The default is false. + * + * @since 0.3.0 + * + * @param value Whether or not to use the SO_REUSEADDR option + */ + void set_reuse_addr(bool value) { + m_reuse_addr = value; + } + + /// Retrieve a reference to the endpoint's io_service + /** + * The io_service may be an internal or external one. This may be used to + * call methods of the io_service that are not explicitly wrapped by the + * endpoint. + * + * This method is only valid after the endpoint has been initialized with + * `init_asio`. No error will be returned if it isn't. + * + * @return A reference to the endpoint's io_service + */ + lib::asio::io_service & get_io_service() { + return *m_io_service; + } + + /// Get local TCP endpoint + /** + * Extracts the local endpoint from the acceptor. This represents the + * address that WebSocket++ is listening on. + * + * Sets a bad_descriptor error if the acceptor is not currently listening + * or otherwise unavailable. + * + * @since 0.7.0 + * + * @param ec Set to indicate what error occurred, if any. + * @return The local endpoint + */ + lib::asio::ip::tcp::endpoint get_local_endpoint(lib::asio::error_code & ec) { + if (m_acceptor) { + return m_acceptor->local_endpoint(ec); + } else { + ec = lib::asio::error::make_error_code(lib::asio::error::bad_descriptor); + return lib::asio::ip::tcp::endpoint(); + } + } + + /// Set up endpoint for listening manually (exception free) + /** + * Bind the internal acceptor using the specified settings. The endpoint + * must have been initialized by calling init_asio before listening. + * + * @param ep An endpoint to read settings from + * @param ec Set to indicate what error occurred, if any. + */ + void listen(lib::asio::ip::tcp::endpoint const & ep, lib::error_code & ec) + { + if (m_state != READY) { + m_elog->write(log::elevel::library, + "asio::listen called from the wrong state"); + using websocketpp::error::make_error_code; + ec = make_error_code(websocketpp::error::invalid_state); + return; + } + + m_alog->write(log::alevel::devel,"asio::listen"); + + lib::asio::error_code bec; + + m_acceptor->open(ep.protocol(),bec); + if (!bec) { + m_acceptor->set_option(lib::asio::socket_base::reuse_address(m_reuse_addr),bec); + } + if (!bec) { + m_acceptor->bind(ep,bec); + } + if (!bec) { + m_acceptor->listen(m_listen_backlog,bec); + } + if (bec) { + if (m_acceptor->is_open()) { + m_acceptor->close(); + } + log_err(log::elevel::info,"asio listen",bec); + ec = make_error_code(error::pass_through); + } else { + m_state = LISTENING; + ec = lib::error_code(); + } + } + + /// Set up endpoint for listening manually + /** + * Bind the internal acceptor using the settings specified by the endpoint e + * + * @param ep An endpoint to read settings from + */ + void listen(lib::asio::ip::tcp::endpoint const & ep) { + lib::error_code ec; + listen(ep,ec); + if (ec) { throw exception(ec); } + } + + /// Set up endpoint for listening with protocol and port (exception free) + /** + * Bind the internal acceptor using the given internet protocol and port. + * The endpoint must have been initialized by calling init_asio before + * listening. + * + * Common options include: + * - IPv6 with mapped IPv4 for dual stack hosts lib::asio::ip::tcp::v6() + * - IPv4 only: lib::asio::ip::tcp::v4() + * + * @param internet_protocol The internet protocol to use. + * @param port The port to listen on. + * @param ec Set to indicate what error occurred, if any. + */ + template <typename InternetProtocol> + void listen(InternetProtocol const & internet_protocol, uint16_t port, + lib::error_code & ec) + { + lib::asio::ip::tcp::endpoint ep(internet_protocol, port); + listen(ep,ec); + } + + /// Set up endpoint for listening with protocol and port + /** + * Bind the internal acceptor using the given internet protocol and port. + * The endpoint must have been initialized by calling init_asio before + * listening. + * + * Common options include: + * - IPv6 with mapped IPv4 for dual stack hosts lib::asio::ip::tcp::v6() + * - IPv4 only: lib::asio::ip::tcp::v4() + * + * @param internet_protocol The internet protocol to use. + * @param port The port to listen on. + */ + template <typename InternetProtocol> + void listen(InternetProtocol const & internet_protocol, uint16_t port) + { + lib::asio::ip::tcp::endpoint ep(internet_protocol, port); + listen(ep); + } + + /// Set up endpoint for listening on a port (exception free) + /** + * Bind the internal acceptor using the given port. The IPv6 protocol with + * mapped IPv4 for dual stack hosts will be used. If you need IPv4 only use + * the overload that allows specifying the protocol explicitly. + * + * The endpoint must have been initialized by calling init_asio before + * listening. + * + * @param port The port to listen on. + * @param ec Set to indicate what error occurred, if any. + */ + void listen(uint16_t port, lib::error_code & ec) { + listen(lib::asio::ip::tcp::v6(), port, ec); + } + + /// Set up endpoint for listening on a port + /** + * Bind the internal acceptor using the given port. The IPv6 protocol with + * mapped IPv4 for dual stack hosts will be used. If you need IPv4 only use + * the overload that allows specifying the protocol explicitly. + * + * The endpoint must have been initialized by calling init_asio before + * listening. + * + * @param port The port to listen on. + * @param ec Set to indicate what error occurred, if any. + */ + void listen(uint16_t port) { + listen(lib::asio::ip::tcp::v6(), port); + } + + /// Set up endpoint for listening on a host and service (exception free) + /** + * Bind the internal acceptor using the given host and service. More details + * about what host and service can be are available in the Asio + * documentation for ip::basic_resolver_query::basic_resolver_query's + * constructors. + * + * The endpoint must have been initialized by calling init_asio before + * listening. + * + * @param host A string identifying a location. May be a descriptive name or + * a numeric address string. + * @param service A string identifying the requested service. This may be a + * descriptive name or a numeric string corresponding to a port number. + * @param ec Set to indicate what error occurred, if any. + */ + void listen(std::string const & host, std::string const & service, + lib::error_code & ec) + { + using lib::asio::ip::tcp; + tcp::resolver r(*m_io_service); + tcp::resolver::query query(host, service); + tcp::resolver::iterator endpoint_iterator = r.resolve(query); + tcp::resolver::iterator end; + if (endpoint_iterator == end) { + m_elog->write(log::elevel::library, + "asio::listen could not resolve the supplied host or service"); + ec = make_error_code(error::invalid_host_service); + return; + } + listen(*endpoint_iterator,ec); + } + + /// Set up endpoint for listening on a host and service + /** + * Bind the internal acceptor using the given host and service. More details + * about what host and service can be are available in the Asio + * documentation for ip::basic_resolver_query::basic_resolver_query's + * constructors. + * + * The endpoint must have been initialized by calling init_asio before + * listening. + * + * @param host A string identifying a location. May be a descriptive name or + * a numeric address string. + * @param service A string identifying the requested service. This may be a + * descriptive name or a numeric string corresponding to a port number. + * @param ec Set to indicate what error occurred, if any. + */ + void listen(std::string const & host, std::string const & service) + { + lib::error_code ec; + listen(host,service,ec); + if (ec) { throw exception(ec); } + } + + /// Stop listening (exception free) + /** + * Stop listening and accepting new connections. This will not end any + * existing connections. + * + * @since 0.3.0-alpha4 + * @param ec A status code indicating an error, if any. + */ + void stop_listening(lib::error_code & ec) { + if (m_state != LISTENING) { + m_elog->write(log::elevel::library, + "asio::listen called from the wrong state"); + using websocketpp::error::make_error_code; + ec = make_error_code(websocketpp::error::invalid_state); + return; + } + + m_acceptor->close(); + m_state = READY; + ec = lib::error_code(); + } + + /// Stop listening + /** + * Stop listening and accepting new connections. This will not end any + * existing connections. + * + * @since 0.3.0-alpha4 + */ + void stop_listening() { + lib::error_code ec; + stop_listening(ec); + if (ec) { throw exception(ec); } + } + + /// Check if the endpoint is listening + /** + * @return Whether or not the endpoint is listening. + */ + bool is_listening() const { + return (m_state == LISTENING); + } + + /// wraps the run method of the internal io_service object + std::size_t run() { + return m_io_service->run(); + } + + /// wraps the run_one method of the internal io_service object + /** + * @since 0.3.0-alpha4 + */ + std::size_t run_one() { + return m_io_service->run_one(); + } + + /// wraps the stop method of the internal io_service object + void stop() { + m_io_service->stop(); + } + + /// wraps the poll method of the internal io_service object + std::size_t poll() { + return m_io_service->poll(); + } + + /// wraps the poll_one method of the internal io_service object + std::size_t poll_one() { + return m_io_service->poll_one(); + } + + /// wraps the reset method of the internal io_service object + void reset() { + m_io_service->reset(); + } + + /// wraps the stopped method of the internal io_service object + bool stopped() const { + return m_io_service->stopped(); + } + + /// Marks the endpoint as perpetual, stopping it from exiting when empty + /** + * Marks the endpoint as perpetual. Perpetual endpoints will not + * automatically exit when they run out of connections to process. To stop + * a perpetual endpoint call `end_perpetual`. + * + * An endpoint may be marked perpetual at any time by any thread. It must be + * called either before the endpoint has run out of work or before it was + * started + * + * @since 0.3.0 + */ + void start_perpetual() { + m_work = lib::make_shared<lib::asio::io_service::work>( + lib::ref(*m_io_service) + ); + } + + /// Clears the endpoint's perpetual flag, allowing it to exit when empty + /** + * Clears the endpoint's perpetual flag. This will cause the endpoint's run + * method to exit normally when it runs out of connections. If there are + * currently active connections it will not end until they are complete. + * + * @since 0.3.0 + */ + void stop_perpetual() { + m_work.reset(); + } + + /// Call back a function after a period of time. + /** + * Sets a timer that calls back a function after the specified period of + * milliseconds. Returns a handle that can be used to cancel the timer. + * A cancelled timer will return the error code error::operation_aborted + * A timer that expired will return no error. + * + * @param duration Length of time to wait in milliseconds + * @param callback The function to call back when the timer has expired + * @return A handle that can be used to cancel the timer if it is no longer + * needed. + */ + timer_ptr set_timer(long duration, timer_handler callback) { + timer_ptr new_timer = lib::make_shared<lib::asio::steady_timer>( + *m_io_service, + lib::asio::milliseconds(duration) + ); + + new_timer->async_wait( + lib::bind( + &type::handle_timer, + this, + new_timer, + callback, + lib::placeholders::_1 + ) + ); + + return new_timer; + } + + /// Timer handler + /** + * The timer pointer is included to ensure the timer isn't destroyed until + * after it has expired. + * + * @param t Pointer to the timer in question + * @param callback The function to call back + * @param ec A status code indicating an error, if any. + */ + void handle_timer(timer_ptr, timer_handler callback, + lib::asio::error_code const & ec) + { + if (ec) { + if (ec == lib::asio::error::operation_aborted) { + callback(make_error_code(transport::error::operation_aborted)); + } else { + m_elog->write(log::elevel::info, + "asio handle_timer error: "+ec.message()); + log_err(log::elevel::info,"asio handle_timer",ec); + callback(make_error_code(error::pass_through)); + } + } else { + callback(lib::error_code()); + } + } + + /// Accept the next connection attempt and assign it to con (exception free) + /** + * @param tcon The connection to accept into. + * @param callback The function to call when the operation is complete. + * @param ec A status code indicating an error, if any. + */ + void async_accept(transport_con_ptr tcon, accept_handler callback, + lib::error_code & ec) + { + if (m_state != LISTENING) { + using websocketpp::error::make_error_code; + ec = make_error_code(websocketpp::error::async_accept_not_listening); + return; + } + + m_alog->write(log::alevel::devel, "asio::async_accept"); + + if (config::enable_multithreading) { + m_acceptor->async_accept( + tcon->get_raw_socket(), + tcon->get_strand()->wrap(lib::bind( + &type::handle_accept, + this, + callback, + lib::placeholders::_1 + )) + ); + } else { + m_acceptor->async_accept( + tcon->get_raw_socket(), + lib::bind( + &type::handle_accept, + this, + callback, + lib::placeholders::_1 + ) + ); + } + } + + /// Accept the next connection attempt and assign it to con. + /** + * @param tcon The connection to accept into. + * @param callback The function to call when the operation is complete. + */ + void async_accept(transport_con_ptr tcon, accept_handler callback) { + lib::error_code ec; + async_accept(tcon,callback,ec); + if (ec) { throw exception(ec); } + } +protected: + /// Initialize logging + /** + * The loggers are located in the main endpoint class. As such, the + * transport doesn't have direct access to them. This method is called + * by the endpoint constructor to allow shared logging from the transport + * component. These are raw pointers to member variables of the endpoint. + * In particular, they cannot be used in the transport constructor as they + * haven't been constructed yet, and cannot be used in the transport + * destructor as they will have been destroyed by then. + */ + void init_logging(alog_type* a, elog_type* e) { + m_alog = a; + m_elog = e; + } + + void handle_accept(accept_handler callback, lib::asio::error_code const & + asio_ec) + { + lib::error_code ret_ec; + + m_alog->write(log::alevel::devel, "asio::handle_accept"); + + if (asio_ec) { + if (asio_ec == lib::asio::errc::operation_canceled) { + ret_ec = make_error_code(websocketpp::error::operation_canceled); + } else { + log_err(log::elevel::info,"asio handle_accept",asio_ec); + ret_ec = make_error_code(error::pass_through); + } + } + + callback(ret_ec); + } + + /// Initiate a new connection + // TODO: there have to be some more failure conditions here + void async_connect(transport_con_ptr tcon, uri_ptr u, connect_handler cb) { + using namespace lib::asio::ip; + + // Create a resolver + if (!m_resolver) { + m_resolver = lib::make_shared<lib::asio::ip::tcp::resolver>( + lib::ref(*m_io_service)); + } + + tcon->set_uri(u); + + std::string proxy = tcon->get_proxy(); + std::string host; + std::string port; + + if (proxy.empty()) { + host = u->get_host(); + port = u->get_port_str(); + } else { + lib::error_code ec; + + uri_ptr pu = lib::make_shared<uri>(proxy); + + if (!pu->get_valid()) { + cb(make_error_code(error::proxy_invalid)); + return; + } + + ec = tcon->proxy_init(u->get_authority()); + if (ec) { + cb(ec); + return; + } + + host = pu->get_host(); + port = pu->get_port_str(); + } + + tcp::resolver::query query(host,port); + + if (m_alog->static_test(log::alevel::devel)) { + m_alog->write(log::alevel::devel, + "starting async DNS resolve for "+host+":"+port); + } + + timer_ptr dns_timer; + + dns_timer = tcon->set_timer( + config::timeout_dns_resolve, + lib::bind( + &type::handle_resolve_timeout, + this, + dns_timer, + cb, + lib::placeholders::_1 + ) + ); + + if (config::enable_multithreading) { + m_resolver->async_resolve( + query, + tcon->get_strand()->wrap(lib::bind( + &type::handle_resolve, + this, + tcon, + dns_timer, + cb, + lib::placeholders::_1, + lib::placeholders::_2 + )) + ); + } else { + m_resolver->async_resolve( + query, + lib::bind( + &type::handle_resolve, + this, + tcon, + dns_timer, + cb, + lib::placeholders::_1, + lib::placeholders::_2 + ) + ); + } + } + + /// DNS resolution timeout handler + /** + * The timer pointer is included to ensure the timer isn't destroyed until + * after it has expired. + * + * @param dns_timer Pointer to the timer in question + * @param callback The function to call back + * @param ec A status code indicating an error, if any. + */ + void handle_resolve_timeout(timer_ptr, connect_handler callback, + lib::error_code const & ec) + { + lib::error_code ret_ec; + + if (ec) { + if (ec == transport::error::operation_aborted) { + m_alog->write(log::alevel::devel, + "asio handle_resolve_timeout timer cancelled"); + return; + } + + log_err(log::elevel::devel,"asio handle_resolve_timeout",ec); + ret_ec = ec; + } else { + ret_ec = make_error_code(transport::error::timeout); + } + + m_alog->write(log::alevel::devel,"DNS resolution timed out"); + m_resolver->cancel(); + callback(ret_ec); + } + + void handle_resolve(transport_con_ptr tcon, timer_ptr dns_timer, + connect_handler callback, lib::asio::error_code const & ec, + lib::asio::ip::tcp::resolver::iterator iterator) + { + if (ec == lib::asio::error::operation_aborted || + lib::asio::is_neg(dns_timer->expires_from_now())) + { + m_alog->write(log::alevel::devel,"async_resolve cancelled"); + return; + } + + dns_timer->cancel(); + + if (ec) { + log_err(log::elevel::info,"asio async_resolve",ec); + callback(make_error_code(error::pass_through)); + return; + } + + if (m_alog->static_test(log::alevel::devel)) { + std::stringstream s; + s << "Async DNS resolve successful. Results: "; + + lib::asio::ip::tcp::resolver::iterator it, end; + for (it = iterator; it != end; ++it) { + s << (*it).endpoint() << " "; + } + + m_alog->write(log::alevel::devel,s.str()); + } + + m_alog->write(log::alevel::devel,"Starting async connect"); + + timer_ptr con_timer; + + con_timer = tcon->set_timer( + config::timeout_connect, + lib::bind( + &type::handle_connect_timeout, + this, + tcon, + con_timer, + callback, + lib::placeholders::_1 + ) + ); + + if (config::enable_multithreading) { + lib::asio::async_connect( + tcon->get_raw_socket(), + iterator, + tcon->get_strand()->wrap(lib::bind( + &type::handle_connect, + this, + tcon, + con_timer, + callback, + lib::placeholders::_1 + )) + ); + } else { + lib::asio::async_connect( + tcon->get_raw_socket(), + iterator, + lib::bind( + &type::handle_connect, + this, + tcon, + con_timer, + callback, + lib::placeholders::_1 + ) + ); + } + } + + /// Asio connect timeout handler + /** + * The timer pointer is included to ensure the timer isn't destroyed until + * after it has expired. + * + * @param tcon Pointer to the transport connection that is being connected + * @param con_timer Pointer to the timer in question + * @param callback The function to call back + * @param ec A status code indicating an error, if any. + */ + void handle_connect_timeout(transport_con_ptr tcon, timer_ptr, + connect_handler callback, lib::error_code const & ec) + { + lib::error_code ret_ec; + + if (ec) { + if (ec == transport::error::operation_aborted) { + m_alog->write(log::alevel::devel, + "asio handle_connect_timeout timer cancelled"); + return; + } + + log_err(log::elevel::devel,"asio handle_connect_timeout",ec); + ret_ec = ec; + } else { + ret_ec = make_error_code(transport::error::timeout); + } + + m_alog->write(log::alevel::devel,"TCP connect timed out"); + tcon->cancel_socket_checked(); + callback(ret_ec); + } + + void handle_connect(transport_con_ptr tcon, timer_ptr con_timer, + connect_handler callback, lib::asio::error_code const & ec) + { + if (ec == lib::asio::error::operation_aborted || + lib::asio::is_neg(con_timer->expires_from_now())) + { + m_alog->write(log::alevel::devel,"async_connect cancelled"); + return; + } + + con_timer->cancel(); + + if (ec) { + log_err(log::elevel::info,"asio async_connect",ec); + callback(make_error_code(error::pass_through)); + return; + } + + if (m_alog->static_test(log::alevel::devel)) { + m_alog->write(log::alevel::devel, + "Async connect to "+tcon->get_remote_endpoint()+" successful."); + } + + callback(lib::error_code()); + } + + /// Initialize a connection + /** + * init is called by an endpoint once for each newly created connection. + * It's purpose is to give the transport policy the chance to perform any + * transport specific initialization that couldn't be done via the default + * constructor. + * + * @param tcon A pointer to the transport portion of the connection. + * + * @return A status code indicating the success or failure of the operation + */ + lib::error_code init(transport_con_ptr tcon) { + m_alog->write(log::alevel::devel, "transport::asio::init"); + + // Initialize the connection socket component + socket_type::init(lib::static_pointer_cast<socket_con_type, + transport_con_type>(tcon)); + + lib::error_code ec; + + ec = tcon->init_asio(m_io_service); + if (ec) {return ec;} + + tcon->set_tcp_pre_init_handler(m_tcp_pre_init_handler); + tcon->set_tcp_post_init_handler(m_tcp_post_init_handler); + + return lib::error_code(); + } +private: + /// Convenience method for logging the code and message for an error_code + template <typename error_type> + void log_err(log::level l, char const * msg, error_type const & ec) { + std::stringstream s; + s << msg << " error: " << ec << " (" << ec.message() << ")"; + m_elog->write(l,s.str()); + } + + enum state { + UNINITIALIZED = 0, + READY = 1, + LISTENING = 2 + }; + + // Handlers + tcp_init_handler m_tcp_pre_init_handler; + tcp_init_handler m_tcp_post_init_handler; + + // Network Resources + io_service_ptr m_io_service; + bool m_external_io_service; + acceptor_ptr m_acceptor; + resolver_ptr m_resolver; + work_ptr m_work; + + // Network constants + int m_listen_backlog; + bool m_reuse_addr; + + elog_type* m_elog; + alog_type* m_alog; + + // Transport state + state m_state; +}; + +} // namespace asio +} // namespace transport +} // namespace websocketpp + +#endif // WEBSOCKETPP_TRANSPORT_ASIO_HPP diff --git a/websocketpp/transport/asio/security/base.hpp b/websocketpp/transport/asio/security/base.hpp new file mode 100644 index 00000000..0f08f404 --- /dev/null +++ b/websocketpp/transport/asio/security/base.hpp @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2015, Peter Thorson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the WebSocket++ Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef WEBSOCKETPP_TRANSPORT_ASIO_SOCKET_BASE_HPP +#define WEBSOCKETPP_TRANSPORT_ASIO_SOCKET_BASE_HPP + +#include <websocketpp/common/asio.hpp> +#include <websocketpp/common/memory.hpp> +#include <websocketpp/common/functional.hpp> +#include <websocketpp/common/system_error.hpp> +#include <websocketpp/common/cpp11.hpp> +#include <websocketpp/common/connection_hdl.hpp> + +#include <string> + +// Interface that sockets/security policies must implement + +/* + * Endpoint Interface + * + * bool is_secure() const; + * @return Whether or not the endpoint creates secure connections + * + * lib::error_code init(socket_con_ptr scon); + * Called by the transport after a new connection is created to initialize + * the socket component of the connection. + * @param scon Pointer to the socket component of the connection + * @return Error code (empty on success) + */ + + +// Connection +// TODO +// set_hostname(std::string hostname) +// pre_init(init_handler); +// post_init(init_handler); + +namespace websocketpp { +namespace transport { +namespace asio { +namespace socket { + +typedef lib::function<void(lib::asio::error_code const &)> shutdown_handler; + +/** + * The transport::asio::socket::* classes are a set of security/socket related + * policies and support code for the ASIO transport types. + */ + +/// Errors related to asio transport sockets +namespace error { + enum value { + /// Catch-all error for security policy errors that don't fit in other + /// categories + security = 1, + + /// Catch-all error for socket component errors that don't fit in other + /// categories + socket, + + /// A function was called in a state that it was illegal to do so. + invalid_state, + + /// The application was prompted to provide a TLS context and it was + /// empty or otherwise invalid + invalid_tls_context, + + /// TLS Handshake Timeout + tls_handshake_timeout, + + /// pass_through from underlying library + pass_through, + + /// Required tls_init handler not present + missing_tls_init_handler, + + /// TLS Handshake Failed + tls_handshake_failed, + + /// Failed to set TLS SNI hostname + tls_failed_sni_hostname + }; +} // namespace error + +/// Error category related to asio transport socket policies +class socket_category : public lib::error_category { +public: + char const * name() const _WEBSOCKETPP_NOEXCEPT_TOKEN_ { + return "websocketpp.transport.asio.socket"; + } + + std::string message(int value) const { + switch(value) { + case error::security: + return "Security policy error"; + case error::socket: + return "Socket component error"; + case error::invalid_state: + return "Invalid state"; + case error::invalid_tls_context: + return "Invalid or empty TLS context supplied"; + case error::tls_handshake_timeout: + return "TLS handshake timed out"; + case error::pass_through: + return "Pass through from socket policy"; + case error::missing_tls_init_handler: + return "Required tls_init handler not present."; + case error::tls_handshake_failed: + return "TLS handshake failed"; + case error::tls_failed_sni_hostname: + return "Failed to set TLS SNI hostname"; + default: + return "Unknown"; + } + } +}; + +inline lib::error_category const & get_socket_category() { + static socket_category instance; + return instance; +} + +inline lib::error_code make_error_code(error::value e) { + return lib::error_code(static_cast<int>(e), get_socket_category()); +} + +/// Type of asio transport socket policy initialization handlers +typedef lib::function<void(const lib::error_code&)> init_handler; + +} // namespace socket +} // namespace asio +} // namespace transport +} // namespace websocketpp + +#endif // WEBSOCKETPP_TRANSPORT_ASIO_SOCKET_BASE_HPP diff --git a/websocketpp/transport/asio/security/none.hpp b/websocketpp/transport/asio/security/none.hpp new file mode 100644 index 00000000..0e68a65c --- /dev/null +++ b/websocketpp/transport/asio/security/none.hpp @@ -0,0 +1,370 @@ +/* + * Copyright (c) 2015, Peter Thorson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the WebSocket++ Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef WEBSOCKETPP_TRANSPORT_SECURITY_NONE_HPP +#define WEBSOCKETPP_TRANSPORT_SECURITY_NONE_HPP + +#include <websocketpp/uri.hpp> + +#include <websocketpp/transport/base/connection.hpp> +#include <websocketpp/transport/asio/security/base.hpp> + +#include <websocketpp/common/asio.hpp> +#include <websocketpp/common/memory.hpp> + +#include <sstream> +#include <string> + +namespace websocketpp { +namespace transport { +namespace asio { +/// A socket policy for the asio transport that implements a plain, unencrypted +/// socket +namespace basic_socket { + +/// The signature of the socket init handler for this socket policy +typedef lib::function<void(connection_hdl,lib::asio::ip::tcp::socket&)> + socket_init_handler; + +/// Basic Asio connection socket component +/** + * transport::asio::basic_socket::connection implements a connection socket + * component using Asio ip::tcp::socket. + */ +class connection : public lib::enable_shared_from_this<connection> { +public: + /// Type of this connection socket component + typedef connection type; + /// Type of a shared pointer to this connection socket component + typedef lib::shared_ptr<type> ptr; + + /// Type of a pointer to the Asio io_service being used + typedef lib::asio::io_service* io_service_ptr; + /// Type of a pointer to the Asio io_service strand being used + typedef lib::shared_ptr<lib::asio::io_service::strand> strand_ptr; + /// Type of the ASIO socket being used + typedef lib::asio::ip::tcp::socket socket_type; + /// Type of a shared pointer to the socket being used. + typedef lib::shared_ptr<socket_type> socket_ptr; + + explicit connection() : m_state(UNINITIALIZED) { + //std::cout << "transport::asio::basic_socket::connection constructor" + // << std::endl; + } + + /// Get a shared pointer to this component + ptr get_shared() { + return shared_from_this(); + } + + /// Check whether or not this connection is secure + /** + * @return Whether or not this connection is secure + */ + bool is_secure() const { + return false; + } + + /// Set the socket initialization handler + /** + * The socket initialization handler is called after the socket object is + * created but before it is used. This gives the application a chance to + * set any Asio socket options it needs. + * + * @param h The new socket_init_handler + */ + void set_socket_init_handler(socket_init_handler h) { + m_socket_init_handler = h; + } + + /// Retrieve a pointer to the underlying socket + /** + * This is used internally. It can also be used to set socket options, etc + */ + lib::asio::ip::tcp::socket & get_socket() { + return *m_socket; + } + + /// Retrieve a pointer to the underlying socket + /** + * This is used internally. + */ + lib::asio::ip::tcp::socket & get_next_layer() { + return *m_socket; + } + + /// Retrieve a pointer to the underlying socket + /** + * This is used internally. It can also be used to set socket options, etc + */ + lib::asio::ip::tcp::socket & get_raw_socket() { + return *m_socket; + } + + /// Get the remote endpoint address + /** + * The iostream transport has no information about the ultimate remote + * endpoint. It will return the string "iostream transport". To indicate + * this. + * + * TODO: allow user settable remote endpoint addresses if this seems useful + * + * @return A string identifying the address of the remote endpoint + */ + std::string get_remote_endpoint(lib::error_code & ec) const { + std::stringstream s; + + lib::asio::error_code aec; + lib::asio::ip::tcp::endpoint ep = m_socket->remote_endpoint(aec); + + if (aec) { + ec = error::make_error_code(error::pass_through); + s << "Error getting remote endpoint: " << aec + << " (" << aec.message() << ")"; + return s.str(); + } else { + ec = lib::error_code(); + s << ep; + return s.str(); + } + } +protected: + /// Perform one time initializations + /** + * init_asio is called once immediately after construction to initialize + * Asio components to the io_service + * + * @param service A pointer to the endpoint's io_service + * @param strand A shared pointer to the connection's asio strand + * @param is_server Whether or not the endpoint is a server or not. + */ + lib::error_code init_asio (io_service_ptr service, strand_ptr, bool) + { + if (m_state != UNINITIALIZED) { + return socket::make_error_code(socket::error::invalid_state); + } + + m_socket = lib::make_shared<lib::asio::ip::tcp::socket>( + lib::ref(*service)); + + m_state = READY; + + return lib::error_code(); + } + + /// Set uri hook + /** + * Called by the transport as a connection is being established to provide + * the uri being connected to to the security/socket layer. + * + * This socket policy doesn't use the uri so it is ignored. + * + * @since 0.6.0 + * + * @param u The uri to set + */ + void set_uri(uri_ptr) {} + + /// Pre-initialize security policy + /** + * Called by the transport after a new connection is created to initialize + * the socket component of the connection. This method is not allowed to + * write any bytes to the wire. This initialization happens before any + * proxies or other intermediate wrappers are negotiated. + * + * @param callback Handler to call back with completion information + */ + void pre_init(init_handler callback) { + if (m_state != READY) { + callback(socket::make_error_code(socket::error::invalid_state)); + return; + } + + if (m_socket_init_handler) { + m_socket_init_handler(m_hdl,*m_socket); + } + + m_state = READING; + + callback(lib::error_code()); + } + + /// Post-initialize security policy + /** + * Called by the transport after all intermediate proxies have been + * negotiated. This gives the security policy the chance to talk with the + * real remote endpoint for a bit before the websocket handshake. + * + * @param callback Handler to call back with completion information + */ + void post_init(init_handler callback) { + callback(lib::error_code()); + } + + /// Sets the connection handle + /** + * The connection handle is passed to any handlers to identify the + * connection + * + * @param hdl The new handle + */ + void set_handle(connection_hdl hdl) { + m_hdl = hdl; + } + + /// Cancel all async operations on this socket + /** + * Attempts to cancel all async operations on this socket and reports any + * failures. + * + * NOTE: Windows XP and earlier do not support socket cancellation. + * + * @return The error that occurred, if any. + */ + lib::asio::error_code cancel_socket() { + lib::asio::error_code ec; + m_socket->cancel(ec); + return ec; + } + + void async_shutdown(socket::shutdown_handler h) { + lib::asio::error_code ec; + m_socket->shutdown(lib::asio::ip::tcp::socket::shutdown_both, ec); + h(ec); + } + + lib::error_code get_ec() const { + return lib::error_code(); + } + + /// Translate any security policy specific information about an error code + /** + * Translate_ec takes an Asio error code and attempts to convert its value + * to an appropriate websocketpp error code. In the case that the Asio and + * Websocketpp error types are the same (such as using boost::asio and + * boost::system_error or using standalone asio and std::system_error the + * code will be passed through natively. + * + * In the case of a mismatch (boost::asio with std::system_error) a + * translated code will be returned. The plain socket policy does not have + * any additional information so all such errors will be reported as the + * generic transport pass_through error. + * + * @since 0.3.0 + * + * @param ec The error code to translate_ec + * @return The translated error code + */ + template <typename ErrorCodeType> + lib::error_code translate_ec(ErrorCodeType) { + // We don't know any more information about this error so pass through + return make_error_code(transport::error::pass_through); + } + + /// Overload of translate_ec to catch cases where lib::error_code is the + /// same type as lib::asio::error_code + lib::error_code translate_ec(lib::error_code ec) { + // We don't know any more information about this error, but the error is + // the same type as the one we are translating to, so pass through + // untranslated. + return ec; + } +private: + enum state { + UNINITIALIZED = 0, + READY = 1, + READING = 2 + }; + + socket_ptr m_socket; + state m_state; + + connection_hdl m_hdl; + socket_init_handler m_socket_init_handler; +}; + +/// Basic ASIO endpoint socket component +/** + * transport::asio::basic_socket::endpoint implements an endpoint socket + * component that uses Boost ASIO's ip::tcp::socket. + */ +class endpoint { +public: + /// The type of this endpoint socket component + typedef endpoint type; + + /// The type of the corresponding connection socket component + typedef connection socket_con_type; + /// The type of a shared pointer to the corresponding connection socket + /// component. + typedef socket_con_type::ptr socket_con_ptr; + + explicit endpoint() {} + + /// Checks whether the endpoint creates secure connections + /** + * @return Whether or not the endpoint creates secure connections + */ + bool is_secure() const { + return false; + } + + /// Set socket init handler + /** + * The socket init handler is called after a connection's socket is created + * but before it is used. This gives the end application an opportunity to + * set asio socket specific parameters. + * + * @param h The new socket_init_handler + */ + void set_socket_init_handler(socket_init_handler h) { + m_socket_init_handler = h; + } +protected: + /// Initialize a connection + /** + * Called by the transport after a new connection is created to initialize + * the socket component of the connection. + * + * @param scon Pointer to the socket component of the connection + * + * @return Error code (empty on success) + */ + lib::error_code init(socket_con_ptr scon) { + scon->set_socket_init_handler(m_socket_init_handler); + return lib::error_code(); + } +private: + socket_init_handler m_socket_init_handler; +}; + +} // namespace basic_socket +} // namespace asio +} // namespace transport +} // namespace websocketpp + +#endif // WEBSOCKETPP_TRANSPORT_SECURITY_NONE_HPP diff --git a/websocketpp/transport/asio/security/tls.hpp b/websocketpp/transport/asio/security/tls.hpp new file mode 100644 index 00000000..7b32db81 --- /dev/null +++ b/websocketpp/transport/asio/security/tls.hpp @@ -0,0 +1,484 @@ +/* + * Copyright (c) 2015, Peter Thorson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the WebSocket++ Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef WEBSOCKETPP_TRANSPORT_SECURITY_TLS_HPP +#define WEBSOCKETPP_TRANSPORT_SECURITY_TLS_HPP + +#include <websocketpp/transport/asio/security/base.hpp> + +#include <websocketpp/uri.hpp> + +#include <websocketpp/common/asio_ssl.hpp> +#include <websocketpp/common/asio.hpp> +#include <websocketpp/common/connection_hdl.hpp> +#include <websocketpp/common/functional.hpp> +#include <websocketpp/common/memory.hpp> + +#include <sstream> +#include <string> + +namespace websocketpp { +namespace transport { +namespace asio { +/// A socket policy for the asio transport that implements a TLS encrypted +/// socket by wrapping with an asio::ssl::stream +namespace tls_socket { + +/// The signature of the socket_init_handler for this socket policy +typedef lib::function<void(connection_hdl,lib::asio::ssl::stream< + lib::asio::ip::tcp::socket>&)> socket_init_handler; +/// The signature of the tls_init_handler for this socket policy +typedef lib::function<lib::shared_ptr<lib::asio::ssl::context>(connection_hdl)> + tls_init_handler; + +/// TLS enabled Asio connection socket component +/** + * transport::asio::tls_socket::connection implements a secure connection socket + * component that uses Asio's ssl::stream to wrap an ip::tcp::socket. + */ +class connection : public lib::enable_shared_from_this<connection> { +public: + /// Type of this connection socket component + typedef connection type; + /// Type of a shared pointer to this connection socket component + typedef lib::shared_ptr<type> ptr; + + /// Type of the ASIO socket being used + typedef lib::asio::ssl::stream<lib::asio::ip::tcp::socket> socket_type; + /// Type of a shared pointer to the ASIO socket being used + typedef lib::shared_ptr<socket_type> socket_ptr; + /// Type of a pointer to the ASIO io_service being used + typedef lib::asio::io_service * io_service_ptr; + /// Type of a pointer to the ASIO io_service strand being used + typedef lib::shared_ptr<lib::asio::io_service::strand> strand_ptr; + /// Type of a shared pointer to the ASIO TLS context being used + typedef lib::shared_ptr<lib::asio::ssl::context> context_ptr; + + explicit connection() { + //std::cout << "transport::asio::tls_socket::connection constructor" + // << std::endl; + } + + /// Get a shared pointer to this component + ptr get_shared() { + return shared_from_this(); + } + + /// Check whether or not this connection is secure + /** + * @return Whether or not this connection is secure + */ + bool is_secure() const { + return true; + } + + /// Retrieve a pointer to the underlying socket + /** + * This is used internally. It can also be used to set socket options, etc + */ + socket_type::lowest_layer_type & get_raw_socket() { + return m_socket->lowest_layer(); + } + + /// Retrieve a pointer to the layer below the ssl stream + /** + * This is used internally. + */ + socket_type::next_layer_type & get_next_layer() { + return m_socket->next_layer(); + } + + /// Retrieve a pointer to the wrapped socket + /** + * This is used internally. + */ + socket_type & get_socket() { + return *m_socket; + } + + /// Set the socket initialization handler + /** + * The socket initialization handler is called after the socket object is + * created but before it is used. This gives the application a chance to + * set any ASIO socket options it needs. + * + * @param h The new socket_init_handler + */ + void set_socket_init_handler(socket_init_handler h) { + m_socket_init_handler = h; + } + + /// Set TLS init handler + /** + * The tls init handler is called when needed to request a TLS context for + * the library to use. A TLS init handler must be set and it must return a + * valid TLS context in order for this endpoint to be able to initialize + * TLS connections + * + * @param h The new tls_init_handler + */ + void set_tls_init_handler(tls_init_handler h) { + m_tls_init_handler = h; + } + + /// Get the remote endpoint address + /** + * The iostream transport has no information about the ultimate remote + * endpoint. It will return the string "iostream transport". To indicate + * this. + * + * TODO: allow user settable remote endpoint addresses if this seems useful + * + * @return A string identifying the address of the remote endpoint + */ + std::string get_remote_endpoint(lib::error_code & ec) const { + std::stringstream s; + + lib::asio::error_code aec; + lib::asio::ip::tcp::endpoint ep = m_socket->lowest_layer().remote_endpoint(aec); + + if (aec) { + ec = error::make_error_code(error::pass_through); + s << "Error getting remote endpoint: " << aec + << " (" << aec.message() << ")"; + return s.str(); + } else { + ec = lib::error_code(); + s << ep; + return s.str(); + } + } +protected: + /// Perform one time initializations + /** + * init_asio is called once immediately after construction to initialize + * Asio components to the io_service + * + * @param service A pointer to the endpoint's io_service + * @param strand A pointer to the connection's strand + * @param is_server Whether or not the endpoint is a server or not. + */ + lib::error_code init_asio (io_service_ptr service, strand_ptr strand, + bool is_server) + { + if (!m_tls_init_handler) { + return socket::make_error_code(socket::error::missing_tls_init_handler); + } + m_context = m_tls_init_handler(m_hdl); + + if (!m_context) { + return socket::make_error_code(socket::error::invalid_tls_context); + } + m_socket = lib::make_shared<socket_type>( + _WEBSOCKETPP_REF(*service),lib::ref(*m_context)); + + m_io_service = service; + m_strand = strand; + m_is_server = is_server; + + return lib::error_code(); + } + + /// Set hostname hook + /** + * Called by the transport as a connection is being established to provide + * the hostname being connected to to the security/socket layer. + * + * This socket policy uses the hostname to set the appropriate TLS SNI + * header. + * + * @since 0.6.0 + * + * @param u The uri to set + */ + void set_uri(uri_ptr u) { + m_uri = u; + } + + /// Pre-initialize security policy + /** + * Called by the transport after a new connection is created to initialize + * the socket component of the connection. This method is not allowed to + * write any bytes to the wire. This initialization happens before any + * proxies or other intermediate wrappers are negotiated. + * + * @param callback Handler to call back with completion information + */ + void pre_init(init_handler callback) { + // TODO: is this the best way to check whether this function is + // available in the version of OpenSSL being used? + // TODO: consider case where host is an IP address +#if OPENSSL_VERSION_NUMBER >= 0x90812f + if (!m_is_server) { + // For clients on systems with a suitable OpenSSL version, set the + // TLS SNI hostname header so connecting to TLS servers using SNI + // will work. + long res = SSL_set_tlsext_host_name( + get_socket().native_handle(), m_uri->get_host().c_str()); + if (!(1 == res)) { + callback(socket::make_error_code(socket::error::tls_failed_sni_hostname)); + } + } +#endif + + if (m_socket_init_handler) { + m_socket_init_handler(m_hdl,get_socket()); + } + + callback(lib::error_code()); + } + + /// Post-initialize security policy + /** + * Called by the transport after all intermediate proxies have been + * negotiated. This gives the security policy the chance to talk with the + * real remote endpoint for a bit before the websocket handshake. + * + * @param callback Handler to call back with completion information + */ + void post_init(init_handler callback) { + m_ec = socket::make_error_code(socket::error::tls_handshake_timeout); + + // TLS handshake + if (m_strand) { + m_socket->async_handshake( + get_handshake_type(), + m_strand->wrap(lib::bind( + &type::handle_init, get_shared(), + callback, + lib::placeholders::_1 + )) + ); + } else { + m_socket->async_handshake( + get_handshake_type(), + lib::bind( + &type::handle_init, get_shared(), + callback, + lib::placeholders::_1 + ) + ); + } + } + + /// Sets the connection handle + /** + * The connection handle is passed to any handlers to identify the + * connection + * + * @param hdl The new handle + */ + void set_handle(connection_hdl hdl) { + m_hdl = hdl; + } + + void handle_init(init_handler callback,lib::asio::error_code const & ec) { + if (ec) { + m_ec = socket::make_error_code(socket::error::tls_handshake_failed); + } else { + m_ec = lib::error_code(); + } + + callback(m_ec); + } + + lib::error_code get_ec() const { + return m_ec; + } + + /// Cancel all async operations on this socket + /** + * Attempts to cancel all async operations on this socket and reports any + * failures. + * + * NOTE: Windows XP and earlier do not support socket cancellation. + * + * @return The error that occurred, if any. + */ + lib::asio::error_code cancel_socket() { + lib::asio::error_code ec; + get_raw_socket().cancel(ec); + return ec; + } + + void async_shutdown(socket::shutdown_handler callback) { + if (m_strand) { + m_socket->async_shutdown(m_strand->wrap(callback)); + } else { + m_socket->async_shutdown(callback); + } + } + + /// Translate any security policy specific information about an error code + /** + * Translate_ec takes an Asio error code and attempts to convert its value + * to an appropriate websocketpp error code. In the case that the Asio and + * Websocketpp error types are the same (such as using boost::asio and + * boost::system_error or using standalone asio and std::system_error the + * code will be passed through natively. + * + * In the case of a mismatch (boost::asio with std::system_error) a + * translated code will be returned. Any error that is determined to be + * related to TLS but does not have a more specific websocketpp error code + * is returned under the catch all error `tls_error`. Non-TLS related errors + * are returned as the transport generic error `pass_through` + * + * @since 0.3.0 + * + * @param ec The error code to translate_ec + * @return The translated error code + */ + template <typename ErrorCodeType> + lib::error_code translate_ec(ErrorCodeType ec) { + if (ec.category() == lib::asio::error::get_ssl_category()) { + if (ERR_GET_REASON(ec.value()) == SSL_R_SHORT_READ) { + return make_error_code(transport::error::tls_short_read); + } else { + // We know it is a TLS related error, but otherwise don't know + // more. Pass through as TLS generic. + return make_error_code(transport::error::tls_error); + } + } else { + // We don't know any more information about this error so pass + // through + return make_error_code(transport::error::pass_through); + } + } + + /// Overload of translate_ec to catch cases where lib::error_code is the + /// same type as lib::asio::error_code + lib::error_code translate_ec(lib::error_code ec) { + // Normalize the tls_short_read error as it is used by the library and + // needs a consistent value. All other errors pass through natively. + // TODO: how to get the SSL category from std::error? + /*if (ec.category() == lib::asio::error::get_ssl_category()) { + if (ERR_GET_REASON(ec.value()) == SSL_R_SHORT_READ) { + return make_error_code(transport::error::tls_short_read); + } + }*/ + return ec; + } +private: + socket_type::handshake_type get_handshake_type() { + if (m_is_server) { + return lib::asio::ssl::stream_base::server; + } else { + return lib::asio::ssl::stream_base::client; + } + } + + io_service_ptr m_io_service; + strand_ptr m_strand; + context_ptr m_context; + socket_ptr m_socket; + uri_ptr m_uri; + bool m_is_server; + + lib::error_code m_ec; + + connection_hdl m_hdl; + socket_init_handler m_socket_init_handler; + tls_init_handler m_tls_init_handler; +}; + +/// TLS enabled Asio endpoint socket component +/** + * transport::asio::tls_socket::endpoint implements a secure endpoint socket + * component that uses Asio's ssl::stream to wrap an ip::tcp::socket. + */ +class endpoint { +public: + /// The type of this endpoint socket component + typedef endpoint type; + + /// The type of the corresponding connection socket component + typedef connection socket_con_type; + /// The type of a shared pointer to the corresponding connection socket + /// component. + typedef socket_con_type::ptr socket_con_ptr; + + explicit endpoint() {} + + /// Checks whether the endpoint creates secure connections + /** + * @return Whether or not the endpoint creates secure connections + */ + bool is_secure() const { + return true; + } + + /// Set socket init handler + /** + * The socket init handler is called after a connection's socket is created + * but before it is used. This gives the end application an opportunity to + * set asio socket specific parameters. + * + * @param h The new socket_init_handler + */ + void set_socket_init_handler(socket_init_handler h) { + m_socket_init_handler = h; + } + + /// Set TLS init handler + /** + * The tls init handler is called when needed to request a TLS context for + * the library to use. A TLS init handler must be set and it must return a + * valid TLS context in order for this endpoint to be able to initialize + * TLS connections + * + * @param h The new tls_init_handler + */ + void set_tls_init_handler(tls_init_handler h) { + m_tls_init_handler = h; + } +protected: + /// Initialize a connection + /** + * Called by the transport after a new connection is created to initialize + * the socket component of the connection. + * + * @param scon Pointer to the socket component of the connection + * + * @return Error code (empty on success) + */ + lib::error_code init(socket_con_ptr scon) { + scon->set_socket_init_handler(m_socket_init_handler); + scon->set_tls_init_handler(m_tls_init_handler); + return lib::error_code(); + } + +private: + socket_init_handler m_socket_init_handler; + tls_init_handler m_tls_init_handler; +}; + +} // namespace tls_socket +} // namespace asio +} // namespace transport +} // namespace websocketpp + +#endif // WEBSOCKETPP_TRANSPORT_SECURITY_TLS_HPP |