From ef6a04dc9751b389635cfec290cd1367616af016 Mon Sep 17 00:00:00 2001 From: dijunkun Date: Thu, 13 Jul 2023 14:17:34 +0800 Subject: [PATCH] Init project --- .gitignore | 10 + README.md | 1 + doc/Architecture.vsdx | Bin 0 -> 50343 bytes src/ice/ice_agent.cpp | 124 + src/ice/ice_agent.h | 42 + src/ice/ice_transport.cpp | 267 ++ src/ice/ice_transport.h | 66 + src/log/log.h | 127 + src/pc/peer_connection.cpp | 113 + src/pc/peer_connection.h | 33 + src/rtc/rtc.cpp | 101 + src/rtc/rtc.h | 30 + src/ws/ws_core.cpp | 136 + src/ws/ws_core.h | 54 + src/ws/ws_transport.cpp | 16 + src/ws/ws_transport.h | 18 + tests/peerconnection/answer.cpp | 10 + tests/peerconnection/offer.cpp | 10 + tests/signal_client/signal_client.cpp | 51 + tests/signal_server/main.cpp | 8 + tests/signal_server/signal_server.cpp | 166 ++ tests/signal_server/signal_server.h | 50 + thirdparty/libjuice/include/juice/juice.h | 176 ++ thirdparty/libjuice/lib/juice.lib | Bin 0 -> 542656 bytes .../cmake/websocketpp-config.cmake | 28 + .../cmake/websocketpp-configVersion.cmake | 88 + .../include/websocketpp/base64/base64.hpp | 178 ++ .../include/websocketpp/client.hpp | 33 + .../websocketpp/include/websocketpp/close.hpp | 353 +++ .../include/websocketpp/common/asio.hpp | 141 + .../include/websocketpp/common/asio_ssl.hpp | 39 + .../include/websocketpp/common/chrono.hpp | 68 + .../websocketpp/common/connection_hdl.hpp | 52 + .../include/websocketpp/common/cpp11.hpp | 162 ++ .../include/websocketpp/common/functional.hpp | 105 + .../include/websocketpp/common/md5.hpp | 448 ++++ .../include/websocketpp/common/memory.hpp | 88 + .../include/websocketpp/common/network.hpp | 106 + .../include/websocketpp/common/platforms.hpp | 46 + .../include/websocketpp/common/random.hpp | 82 + .../include/websocketpp/common/regex.hpp | 59 + .../include/websocketpp/common/stdint.hpp | 73 + .../websocketpp/common/system_error.hpp | 84 + .../include/websocketpp/common/thread.hpp | 88 + .../include/websocketpp/common/time.hpp | 56 + .../websocketpp/common/type_traits.hpp | 65 + .../include/websocketpp/concurrency/basic.hpp | 46 + .../include/websocketpp/concurrency/none.hpp | 80 + .../include/websocketpp/config/asio.hpp | 77 + .../websocketpp/config/asio_client.hpp | 77 + .../websocketpp/config/asio_no_tls.hpp | 73 + .../websocketpp/config/asio_no_tls_client.hpp | 73 + .../websocketpp/config/boost_config.hpp | 72 + .../include/websocketpp/config/core.hpp | 297 +++ .../websocketpp/config/core_client.hpp | 294 ++ .../include/websocketpp/config/debug.hpp | 286 ++ .../include/websocketpp/config/debug_asio.hpp | 77 + .../websocketpp/config/debug_asio_no_tls.hpp | 73 + .../websocketpp/config/minimal_client.hpp | 72 + .../websocketpp/config/minimal_server.hpp | 312 +++ .../include/websocketpp/connection.hpp | 1642 ++++++++++++ .../include/websocketpp/connection_base.hpp | 38 + .../include/websocketpp/endpoint.hpp | 700 +++++ .../include/websocketpp/endpoint_base.hpp | 38 + .../websocketpp/include/websocketpp/error.hpp | 277 ++ .../websocketpp/extensions/extension.hpp | 102 + .../permessage_deflate/disabled.hpp | 129 + .../extensions/permessage_deflate/enabled.hpp | 817 ++++++ .../websocketpp/include/websocketpp/frame.hpp | 864 ++++++ .../include/websocketpp/http/constants.hpp | 308 +++ .../include/websocketpp/http/impl/parser.hpp | 200 ++ .../include/websocketpp/http/impl/request.hpp | 191 ++ .../websocketpp/http/impl/response.hpp | 266 ++ .../include/websocketpp/http/parser.hpp | 629 +++++ .../include/websocketpp/http/request.hpp | 124 + .../include/websocketpp/http/response.hpp | 188 ++ .../websocketpp/impl/connection_impl.hpp | 2375 +++++++++++++++++ .../websocketpp/impl/endpoint_impl.hpp | 269 ++ .../websocketpp/impl/utilities_impl.hpp | 87 + .../include/websocketpp/logger/basic.hpp | 199 ++ .../include/websocketpp/logger/levels.hpp | 203 ++ .../include/websocketpp/logger/stub.hpp | 119 + .../include/websocketpp/logger/syslog.hpp | 146 + .../websocketpp/message_buffer/alloc.hpp | 105 + .../websocketpp/message_buffer/message.hpp | 340 +++ .../websocketpp/message_buffer/pool.hpp | 229 ++ .../include/websocketpp/processors/base.hpp | 299 +++ .../include/websocketpp/processors/hybi00.hpp | 462 ++++ .../include/websocketpp/processors/hybi07.hpp | 78 + .../include/websocketpp/processors/hybi08.hpp | 83 + .../include/websocketpp/processors/hybi13.hpp | 1078 ++++++++ .../websocketpp/processors/processor.hpp | 407 +++ .../include/websocketpp/random/none.hpp | 60 + .../websocketpp/random/random_device.hpp | 80 + .../websocketpp/roles/client_endpoint.hpp | 173 ++ .../websocketpp/roles/server_endpoint.hpp | 195 ++ .../include/websocketpp/server.hpp | 33 + .../include/websocketpp/sha1/sha1.hpp | 189 ++ .../websocketpp/transport/asio/base.hpp | 232 ++ .../websocketpp/transport/asio/connection.hpp | 1197 +++++++++ .../websocketpp/transport/asio/endpoint.hpp | 1182 ++++++++ .../transport/asio/security/base.hpp | 159 ++ .../transport/asio/security/none.hpp | 372 +++ .../transport/asio/security/tls.hpp | 474 ++++ .../websocketpp/transport/base/connection.hpp | 238 ++ .../websocketpp/transport/base/endpoint.hpp | 77 + .../websocketpp/transport/debug/base.hpp | 104 + .../transport/debug/connection.hpp | 412 +++ .../websocketpp/transport/debug/endpoint.hpp | 140 + .../websocketpp/transport/iostream/base.hpp | 133 + .../transport/iostream/connection.hpp | 714 +++++ .../transport/iostream/endpoint.hpp | 222 ++ .../websocketpp/transport/stub/base.hpp | 95 + .../websocketpp/transport/stub/connection.hpp | 286 ++ .../websocketpp/transport/stub/endpoint.hpp | 140 + .../websocketpp/include/websocketpp/uri.hpp | 356 +++ .../include/websocketpp/utf8_validator.hpp | 154 ++ .../include/websocketpp/utilities.hpp | 180 ++ .../include/websocketpp/version.hpp | 61 + xmake.lua | 86 + 120 files changed, 26696 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 doc/Architecture.vsdx create mode 100644 src/ice/ice_agent.cpp create mode 100644 src/ice/ice_agent.h create mode 100644 src/ice/ice_transport.cpp create mode 100644 src/ice/ice_transport.h create mode 100644 src/log/log.h create mode 100644 src/pc/peer_connection.cpp create mode 100644 src/pc/peer_connection.h create mode 100644 src/rtc/rtc.cpp create mode 100644 src/rtc/rtc.h create mode 100644 src/ws/ws_core.cpp create mode 100644 src/ws/ws_core.h create mode 100644 src/ws/ws_transport.cpp create mode 100644 src/ws/ws_transport.h create mode 100644 tests/peerconnection/answer.cpp create mode 100644 tests/peerconnection/offer.cpp create mode 100644 tests/signal_client/signal_client.cpp create mode 100644 tests/signal_server/main.cpp create mode 100644 tests/signal_server/signal_server.cpp create mode 100644 tests/signal_server/signal_server.h create mode 100644 thirdparty/libjuice/include/juice/juice.h create mode 100644 thirdparty/libjuice/lib/juice.lib create mode 100644 thirdparty/websocketpp/cmake/websocketpp-config.cmake create mode 100644 thirdparty/websocketpp/cmake/websocketpp-configVersion.cmake create mode 100644 thirdparty/websocketpp/include/websocketpp/base64/base64.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/client.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/close.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/common/asio.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/common/asio_ssl.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/common/chrono.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/common/connection_hdl.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/common/cpp11.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/common/functional.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/common/md5.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/common/memory.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/common/network.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/common/platforms.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/common/random.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/common/regex.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/common/stdint.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/common/system_error.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/common/thread.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/common/time.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/common/type_traits.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/concurrency/basic.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/concurrency/none.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/config/asio.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/config/asio_client.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/config/asio_no_tls.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/config/asio_no_tls_client.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/config/boost_config.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/config/core.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/config/core_client.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/config/debug.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/config/debug_asio.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/config/debug_asio_no_tls.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/config/minimal_client.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/config/minimal_server.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/connection.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/connection_base.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/endpoint.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/endpoint_base.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/error.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/extensions/extension.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/extensions/permessage_deflate/disabled.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/extensions/permessage_deflate/enabled.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/frame.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/http/constants.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/http/impl/parser.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/http/impl/request.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/http/impl/response.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/http/parser.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/http/request.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/http/response.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/impl/connection_impl.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/impl/endpoint_impl.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/impl/utilities_impl.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/logger/basic.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/logger/levels.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/logger/stub.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/logger/syslog.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/message_buffer/alloc.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/message_buffer/message.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/message_buffer/pool.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/processors/base.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/processors/hybi00.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/processors/hybi07.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/processors/hybi08.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/processors/hybi13.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/processors/processor.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/random/none.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/random/random_device.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/roles/client_endpoint.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/roles/server_endpoint.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/server.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/sha1/sha1.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/transport/asio/base.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/transport/asio/connection.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/transport/asio/endpoint.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/transport/asio/security/base.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/transport/asio/security/none.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/transport/asio/security/tls.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/transport/base/connection.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/transport/base/endpoint.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/transport/debug/base.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/transport/debug/connection.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/transport/debug/endpoint.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/transport/iostream/base.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/transport/iostream/connection.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/transport/iostream/endpoint.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/transport/stub/base.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/transport/stub/connection.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/transport/stub/endpoint.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/uri.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/utf8_validator.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/utilities.hpp create mode 100644 thirdparty/websocketpp/include/websocketpp/version.hpp create mode 100644 xmake.lua diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..88c7a84 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Xmake cache +.xmake/ +build/ + +# MacOS Cache +.DS_Store + +# VSCode cache +.vscode +projectx.code-workspace \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..241ee29 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# projectx diff --git a/doc/Architecture.vsdx b/doc/Architecture.vsdx new file mode 100644 index 0000000000000000000000000000000000000000..695a1349ab47392f1daed614ab82529e12998785 GIT binary patch literal 50343 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 F{{dz(X_o*1 literal 0 HcmV?d00001 diff --git a/src/ice/ice_agent.cpp b/src/ice/ice_agent.cpp new file mode 100644 index 0000000..b01f00d --- /dev/null +++ b/src/ice/ice_agent.cpp @@ -0,0 +1,124 @@ +#include "ice_agent.h" + +#include + +#include + +#include "log.h" + +IceAgent::IceAgent() {} + +IceAgent::~IceAgent() {} + +int IceAgent::CreateIceAgent(juice_cb_state_changed_t on_state_changed, + juice_cb_candidate_t on_candidate, + juice_cb_gathering_done_t on_gathering_done, + juice_cb_recv_t on_recv, void *user_ptr) { + // juice_set_log_level(JUICE_LOG_LEVEL_DEBUG); + + juice_config_t config; + memset(&config, 0, sizeof(config)); + + // STUN server example + config.stun_server_host = "120.77.216.215"; + config.stun_server_port = 3478; + + config.cb_state_changed = on_state_changed; + config.cb_candidate = on_candidate; + config.cb_gathering_done = on_gathering_done; + config.cb_recv = on_recv; + config.user_ptr = user_ptr; + + agent_ = juice_create(&config); + + return 0; +} + +int IceAgent::DestoryIceAgent() { + juice_destroy(agent_); + + return 0; +} + +char *IceAgent::GenerateLocalSdp() { + if (nullptr == agent_) { + LOG_INFO("agent_ is nullptr"); + return nullptr; + } + + juice_get_local_description(agent_, local_sdp_, JUICE_MAX_SDP_STRING_LEN); + LOG_INFO("Generate local sdp:[\n{}]", local_sdp_); + + return local_sdp_; +} + +int IceAgent::SetRemoteSdp(const char *remote_sdp) { + LOG_INFO("Set remote sdp"); + juice_set_remote_description(agent_, remote_sdp); + LOG_INFO("Remote description:[\n{}]", remote_sdp); + + return 0; +} + +int IceAgent::GatherCandidates() { + LOG_INFO("Gather candidates"); + juice_gather_candidates(agent_); + + return 0; +} + +juice_state_t IceAgent::GetIceState() { + state_ = juice_get_state(agent_); + + return state_; +} + +bool IceAgent::GetSelectedCandidates() { + char local[JUICE_MAX_CANDIDATE_SDP_STRING_LEN]; + char remote[JUICE_MAX_CANDIDATE_SDP_STRING_LEN]; + + bool success = state_ == JUICE_STATE_COMPLETED; + if (success &= (juice_get_selected_candidates( + agent_, local, JUICE_MAX_CANDIDATE_SDP_STRING_LEN, remote, + JUICE_MAX_CANDIDATE_SDP_STRING_LEN) == 0)) { + LOG_INFO("Local candidate 1: {}", local); + LOG_INFO("Remote candidate 1: {}", remote); + if ((!strstr(local, "typ host") && !strstr(local, "typ prflx")) || + (!strstr(remote, "typ host") && !strstr(remote, "typ prflx"))) + success = false; // local connection should be possible + } + + return success; +} + +bool IceAgent::GetSelectedAddresses() { + char localAddr[JUICE_MAX_ADDRESS_STRING_LEN]; + char remoteAddr[JUICE_MAX_ADDRESS_STRING_LEN]; + + bool success = state_ == JUICE_STATE_COMPLETED; + if (success &= (juice_get_selected_addresses( + agent_, localAddr, JUICE_MAX_ADDRESS_STRING_LEN, + remoteAddr, JUICE_MAX_ADDRESS_STRING_LEN) == 0)) { + LOG_INFO("Local address 1: {}", localAddr); + LOG_INFO("Remote address 1: {}", remoteAddr); + } + + return success; +} + +int IceAgent::AddRemoteCandidates(const char *remote_candidates) { + juice_add_remote_candidate(agent_, remote_candidates); + + return 0; +} + +int IceAgent::SetRemoteGatheringDone() { + juice_set_remote_gathering_done(agent_); + + return 0; +} + +int IceAgent::Send(const char *data, size_t size) { + juice_send(agent_, data, size); + return 0; +} \ No newline at end of file diff --git a/src/ice/ice_agent.h b/src/ice/ice_agent.h new file mode 100644 index 0000000..ea5171b --- /dev/null +++ b/src/ice/ice_agent.h @@ -0,0 +1,42 @@ +#ifndef _ICE_AGENT_H_ +#define _ICE_AGENT_H_ + +#include "juice/juice.h" + +class IceAgent { + public: + IceAgent(); + ~IceAgent(); + + int CreateIceAgent(juice_cb_state_changed_t on_state_changed, + juice_cb_candidate_t on_candidate, + juice_cb_gathering_done_t on_gathering_done, + juice_cb_recv_t on_recv, void* user_ptr); + + int DestoryIceAgent(); + + char* GenerateLocalSdp(); + + int SetRemoteSdp(const char* remote_sdp); + + int GatherCandidates(); + + juice_state_t GetIceState(); + + bool GetSelectedCandidates(); + + bool GetSelectedAddresses(); + + int AddRemoteCandidates(const char* remote_candidates); + + int SetRemoteGatheringDone(); + + int Send(const char* data, size_t size); + + private: + juice_agent_t* agent_ = nullptr; + char local_sdp_[JUICE_MAX_SDP_STRING_LEN]; + juice_state_t state_; +}; + +#endif \ No newline at end of file diff --git a/src/ice/ice_transport.cpp b/src/ice/ice_transport.cpp new file mode 100644 index 0000000..11ff6a1 --- /dev/null +++ b/src/ice/ice_transport.cpp @@ -0,0 +1,267 @@ +#include "ice_transport.h" + +#include +#include + +#include "log.h" + +using nlohmann::json; + +static const std::map siganl_types{ + {"connection_id", 1}, + {"offer", 2}, + {"transport_id", 3}, + {"remote_sdp", 4}, + {"candidate", 5}}; + +const std::vector ice_status = { + "JUICE_STATE_DISCONNECTED", "JUICE_STATE_GATHERING", + "JUICE_STATE_CONNECTING", "JUICE_STATE_CONNECTED", + "JUICE_STATE_COMPLETED", "JUICE_STATE_FAILED"}; + +IceTransport::IceTransport( + WsTransport *ice_ws_transport, + std::function on_receive_ice_msg) + : ice_ws_transport_(ice_ws_transport), + on_receive_ice_msg_cb_(on_receive_ice_msg) {} + +IceTransport::~IceTransport() {} + +int IceTransport::InitIceTransport() { + ice_agent_ = new IceAgent(); + + ice_agent_->CreateIceAgent( + [](juice_agent_t *agent, juice_state_t state, void *user_ptr) { + LOG_INFO("state_change: {}", ice_status[state]); + }, + [](juice_agent_t *agent, const char *sdp, void *user_ptr) { + LOG_INFO("candadite: {}", sdp); + // trickle + // static_cast(user_ptr)->SendOfferLocalCandidate(sdp); + }, + [](juice_agent_t *agent, void *user_ptr) { + LOG_INFO("gather_done"); + // non-trickle + if (user_ptr) { + static_cast(user_ptr)->GetLocalSdp(); + static_cast(user_ptr)->SendOffer(); + } + }, + [](juice_agent_t *agent, const char *data, size_t size, void *user_ptr) { + LOG_INFO("on_recv"); + if (user_ptr && + static_cast(user_ptr)->on_receive_ice_msg_cb_) { + static_cast(user_ptr)->on_receive_ice_msg_cb_(data, + size); + } + }, + this); + return 0; +} + +int IceTransport::InitIceTransport(std::string const &id) { + transport_id_ = id; + + ice_agent_->CreateIceAgent( + [](juice_agent_t *agent, juice_state_t state, void *user_ptr) { + LOG_INFO("state_change: {}", ice_status[state]); + }, + [](juice_agent_t *agent, const char *sdp, void *user_ptr) { + LOG_INFO("candadite: {}", sdp); + // trickle + // static_cast(user_ptr)->SendAnswerLocalCandidate(sdp); + }, + [](juice_agent_t *agent, void *user_ptr) { + LOG_INFO("gather_done"); + // non-trickle + if (user_ptr) { + static_cast(user_ptr)->CreateAnswer(); + static_cast(user_ptr)->SendAnswer(); + } + }, + [](juice_agent_t *agent, const char *data, size_t size, void *user_ptr) { + LOG_INFO("on_recv"); + if (user_ptr && + static_cast(user_ptr)->on_receive_ice_msg_cb_) { + static_cast(user_ptr)->on_receive_ice_msg_cb_(data, + size); + } + }, + this); + return 0; +} + +int IceTransport::DestroyIceTransport() { + if (ice_agent_) { + delete ice_agent_; + } + return 0; +} + +int IceTransport::CreateTransport() { + LOG_INFO("Create transport"); + offer_peer_ = true; + + // if (SignalStatus::Connected != signal_status_) { + // LOG_ERROR("Not connect to signalserver"); + // return -1; + // } + + json message = {{"type", "create_transport"}}; + if (ice_ws_transport_) { + ice_ws_transport_->Send(message.dump()); + LOG_INFO("Send msg: {}", message.dump().c_str()); + } + + CreateOffer(); + return 0; +} + +int IceTransport::CreateTransport(std::string transport_id) { + LOG_INFO("Join transport"); + offer_peer_ = false; + + // if (SignalStatus::Connected != signal_status_) { + // LOG_ERROR("Not connect to signalserver"); + // return -1; + // } + + QueryRemoteSdp(transport_id); + return 0; +} + +int IceTransport::GatherCandidates() { + ice_agent_->GatherCandidates(); + return 0; +} + +int IceTransport::GetLocalSdp() { + local_sdp_ = ice_agent_->GenerateLocalSdp(); + return 0; +} + +int IceTransport::SetRemoteSdp(const std::string &remote_sdp) { + ice_agent_->SetRemoteSdp(remote_sdp.c_str()); + return 0; +} + +int IceTransport::AddRemoteCandidate(const std::string &remote_candidate) { + ice_agent_->AddRemoteCandidates(remote_candidate.c_str()); + return 0; +} + +int IceTransport::CreateOffer() { + LOG_INFO("Create offer"); + GatherCandidates(); + return 0; +} + +int IceTransport::SendOffer() { + json message = { + {"type", "offer"}, {"transport_id", transport_id_}, {"sdp", local_sdp_}}; + LOG_INFO("Send offer:\n{}", message.dump().c_str()); + + if (ice_ws_transport_) { + ice_ws_transport_->Send(message.dump()); + } + return 0; +} + +int IceTransport::QueryRemoteSdp(std::string transport_id) { + json message = {{"type", "query_remote_sdp"}, + {"transport_id", transport_id_}}; + LOG_INFO("Query remote sdp"); + + if (ice_ws_transport_) { + ice_ws_transport_->Send(message.dump()); + } + return 0; +} + +int IceTransport::CreateAnswer() { + GetLocalSdp(); + return 0; +} + +int IceTransport::SendAnswer() { + json message = { + {"type", "answer"}, {"transport_id", transport_id_}, {"sdp", local_sdp_}}; + LOG_INFO("Send answer:\n{}", message.dump().c_str()); + + if (ice_ws_transport_) { + ice_ws_transport_->Send(message.dump()); + } + return 0; +} + +int IceTransport::SendOfferLocalCandidate(const std::string &remote_candidate) { + json message = {{"type", "offer_candidate"}, + {"transport_id", transport_id_}, + {"sdp", remote_candidate}}; + LOG_INFO("Send candidate:\n{}", message.dump().c_str()); + + if (ice_ws_transport_) { + ice_ws_transport_->Send(message.dump()); + } + return 0; +} + +int IceTransport::SendAnswerLocalCandidate( + const std::string &remote_candidate) { + json message = {{"type", "answer_candidate"}, + {"transport_id", transport_id_}, + {"sdp", remote_candidate}}; + LOG_INFO("Send candidate:\n{}", message.dump().c_str()); + + if (ice_ws_transport_) { + ice_ws_transport_->Send(message.dump()); + } + return 0; +} + +int IceTransport::SendData(const char *data, size_t size) { + ice_agent_->Send(data, size); + return 0; +} + +void IceTransport::OnReceiveMessage(const std::string &msg) { + auto j = json::parse(msg); + LOG_INFO("msg: {}", msg.c_str()); + + std::string type = j["type"]; + auto itr = siganl_types.find(type); + if (itr != siganl_types.end()) { + LOG_INFO("msg type :{}", itr->first); + switch (itr->second) { + case 2: { + break; + } + case 3: { + transport_id_ = j["transport_id"].get(); + LOG_INFO("Receive local peer transport_id [{}]", transport_id_); + // SendOffer(); + break; + } + case 4: { + remote_sdp_ = j["sdp"].get(); + LOG_INFO("Receive remote sdp [{}]", remote_sdp_); + SetRemoteSdp(remote_sdp_); + + if (!offer_peer_) { + GatherCandidates(); + } + break; + } + case 5: { + std::string candidate = j["sdp"].get(); + LOG_INFO("Receive candidate [{}]", candidate); + AddRemoteCandidate(candidate); + break; + } + default: + break; + } + } +} diff --git a/src/ice/ice_transport.h b/src/ice/ice_transport.h new file mode 100644 index 0000000..d5fdc52 --- /dev/null +++ b/src/ice/ice_transport.h @@ -0,0 +1,66 @@ +#ifndef _ICE_TRANSPORT_H_ +#define _ICE_TRANSPORT_H_ + +#include + +#include "ice_agent.h" +#include "ws_transport.h" + +class IceTransport { + public: + IceTransport(WsTransport *ice_ws_transport, + std::function on_receive_ice_msg); + + ~IceTransport(); + + int InitIceTransport(); + int InitIceTransport(std::string const &id); + + int DestroyIceTransport(); + + int CreateTransport(); + int CreateTransport(std::string transport_id); + + int SendData(const char *data, size_t size); + + void OnReceiveUserData(const char *data, size_t size); + + void OnReceiveMessage(const std::string &msg); + + private: + int GatherCandidates(); + + int GetLocalSdp(); + + int QueryRemoteSdp(std::string transport_id); + + int SetRemoteSdp(const std::string &remote_sdp); + + int AddRemoteCandidate(const std::string &remote_candidate); + + int CreateOffer(); + + int SendOffer(); + + int CreateAnswer(); + + int SendAnswer(); + + int SendOfferLocalCandidate(const std::string &remote_candidate); + + int SendAnswerLocalCandidate(const std::string &remote_candidate); + + private: + IceAgent *ice_agent_ = nullptr; + WsTransport *ice_ws_transport_ = nullptr; + std::function on_receive_ice_msg_cb_ = nullptr; + std::string local_sdp_; + std::string remote_sdp_; + std::string local_candidates_; + std::string remote_candidates_; + unsigned int connection_id_ = 0; + std::string transport_id_ = ""; + bool offer_peer_ = true; +}; + +#endif \ No newline at end of file diff --git a/src/log/log.h b/src/log/log.h new file mode 100644 index 0000000..831b37d --- /dev/null +++ b/src/log/log.h @@ -0,0 +1,127 @@ +#ifndef _LOG_H_ +#define _LOG_H_ + +#include +#include +#include +#include +#include + +#include "spdlog/common.h" +#include "spdlog/logger.h" +#include "spdlog/sinks/base_sink.h" +#include "spdlog/sinks/rotating_file_sink.h" +#include "spdlog/sinks/stdout_color_sinks.h" +#include "spdlog/spdlog.h" + +using namespace std::chrono; + +#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO + +// SPDLOG_TRACE(...) +// SPDLOG_DEBUG(...) +// SPDLOG_INFO(...) +// SPDLOG_WARN(...) +// SPDLOG_ERROR(...) +// SPDLOG_CRITICAL(...) + +#ifdef SIGNAL_LOGGER +constexpr auto LOGGER_NAME = "siganl"; +#else +constexpr auto LOGGER_NAME = "rtc"; +#endif + +#define LOG_INFO(...) \ + if (nullptr == spdlog::get(LOGGER_NAME)) { \ + auto now = std::chrono::system_clock::now() + std::chrono::hours(8); \ + auto timet = std::chrono::system_clock::to_time_t(now); \ + auto localTime = *std::gmtime(&timet); \ + std::stringstream ss; \ + std::string filename; \ + ss << LOGGER_NAME; \ + ss << std::put_time(&localTime, "-%Y%m%d-%H%M%S.log"); \ + ss >> filename; \ + std::string path = "logs/" + filename; \ + std::vector sinks; \ + sinks.push_back(std::make_shared()); \ + sinks.push_back(std::make_shared( \ + path, 1048576 * 5, 3)); \ + auto combined_logger = std::make_shared( \ + LOGGER_NAME, begin(sinks), end(sinks)); \ + combined_logger->flush_on(spdlog::level::info); \ + spdlog::register_logger(combined_logger); \ + SPDLOG_LOGGER_INFO(combined_logger, __VA_ARGS__); \ + } else { \ + SPDLOG_LOGGER_INFO(spdlog::get(LOGGER_NAME), __VA_ARGS__); \ + } + +#define LOG_WARN(...) \ + if (nullptr == spdlog::get(LOGGER_NAME)) { \ + auto now = std::chrono::system_clock::now() + std::chrono::hours(8); \ + auto timet = std::chrono::system_clock::to_time_t(now); \ + auto localTime = *std::gmtime(&timet); \ + std::stringstream ss; \ + std::string filename; \ + ss << LOGGER_NAME; \ + ss << std::put_time(&localTime, "-%Y%m%d-%H%M%S.log"); \ + ss >> filename; \ + std::string path = "logs/" + filename; \ + std::vector sinks; \ + sinks.push_back(std::make_shared()); \ + sinks.push_back(std::make_shared( \ + path, 1048576 * 5, 3)); \ + auto combined_logger = std::make_shared( \ + LOGGER_NAME, begin(sinks), end(sinks)); \ + spdlog::register_logger(combined_logger); \ + SPDLOG_LOGGER_WARN(combined_logger, __VA_ARGS__); \ + } else { \ + SPDLOG_LOGGER_WARN(spdlog::get(LOGGER_NAME), __VA_ARGS__); \ + } + +#define LOG_ERROR(...) \ + if (nullptr == spdlog::get(LOGGER_NAME)) { \ + auto now = std::chrono::system_clock::now() + std::chrono::hours(8); \ + auto timet = std::chrono::system_clock::to_time_t(now); \ + auto localTime = *std::gmtime(&timet); \ + std::stringstream ss; \ + std::string filename; \ + ss << LOGGER_NAME; \ + ss << std::put_time(&localTime, "-%Y%m%d-%H%M%S.log"); \ + ss >> filename; \ + std::string path = "logs/" + filename; \ + std::vector sinks; \ + sinks.push_back(std::make_shared()); \ + sinks.push_back(std::make_shared( \ + path, 1048576 * 5, 3)); \ + auto combined_logger = std::make_shared( \ + LOGGER_NAME, begin(sinks), end(sinks)); \ + spdlog::register_logger(combined_logger); \ + SPDLOG_LOGGER_ERROR(combined_logger, __VA_ARGS__); \ + } else { \ + SPDLOG_LOGGER_ERROR(spdlog::get(LOGGER_NAME), __VA_ARGS__); \ + } + +#define LOG_FATAL(...) \ + if (nullptr == spdlog::get(LOGGER_NAME)) { \ + auto now = std::chrono::system_clock::now() + std::chrono::hours(8); \ + auto timet = std::chrono::system_clock::to_time_t(now); \ + auto localTime = *std::gmtime(&timet); \ + std::stringstream ss; \ + std::string filename; \ + ss << LOGGER_NAME; \ + ss << std::put_time(&localTime, "-%Y%m%d-%H%M%S.log"); \ + ss >> filename; \ + std::string path = "logs/" + filename; \ + std::vector sinks; \ + sinks.push_back(std::make_shared()); \ + sinks.push_back(std::make_shared( \ + path, 1048576 * 5, 3)); \ + auto combined_logger = std::make_shared( \ + LOGGER_NAME, begin(sinks), end(sinks)); \ + spdlog::register_logger(combined_logger); \ + SPDLOG_LOGGER_CRITICAL(combined_logger, __VA_ARGS__); \ + } else { \ + SPDLOG_LOGGER_CRITICAL(spdlog::get(LOGGER_NAME), __VA_ARGS__); \ + } + +#endif diff --git a/src/pc/peer_connection.cpp b/src/pc/peer_connection.cpp new file mode 100644 index 0000000..a98cef7 --- /dev/null +++ b/src/pc/peer_connection.cpp @@ -0,0 +1,113 @@ +#include "peer_connection.h" + +#include + +#include "log.h" + +using nlohmann::json; + +static const std::map siganl_types{ + {"connection_id", 1}, + {"offer", 2}, + {"transport_id", 3}, + {"remote_sdp", 4}, + {"candidate", 5}}; + +PeerConnection::PeerConnection() {} + +PeerConnection::~PeerConnection() {} + +int PeerConnection::Init(std::string const &uri) { + on_receive_ws_msg_ = [this](const std::string &msg) { + do { + } while (!ice_transport_); + auto j = json::parse(msg); + std::string type = j["type"]; + auto itr = siganl_types.find(type); + if (itr != siganl_types.end()) { + LOG_INFO("msg type :{}", itr->first); + switch (itr->second) { + case 1: { + connection_id_ = j["connection_id"].get(); + LOG_INFO("Receive local peer connection_id [{}]", connection_id_); + signal_status_ = SignalStatus::Connected; + break; + } + default: { + ice_transport_->OnReceiveMessage(msg); + break; + } + } + } + }; + + on_receive_ice_msg_ = [this](const char *data, size_t size) {}; + + ws_transport_ = new WsTransport(on_receive_ws_msg_); + if (ws_transport_) { + ws_transport_->Connect(uri); + } + + ice_transport_ = new IceTransport(ws_transport_, on_receive_ice_msg_); + ice_transport_->InitIceTransport(); + + do { + LOG_INFO("GetSignalStatus = {}", GetSignalStatus()); + } while (SignalStatus::Connected != GetSignalStatus()); + + ice_transport_->CreateTransport(); + return 0; +} + +int PeerConnection::Init(std::string const &uri, std::string const &id) { + on_receive_ws_msg_ = [this](const std::string &msg) { + do { + } while (!ice_transport_); + auto j = json::parse(msg); + std::string type = j["type"]; + auto itr = siganl_types.find(type); + if (itr != siganl_types.end()) { + LOG_INFO("msg type :{}", itr->first); + switch (itr->second) { + case 1: { + connection_id_ = j["connection_id"].get(); + LOG_INFO("Receive local peer connection_id [{}]", connection_id_); + signal_status_ = SignalStatus::Connected; + break; + } + default: { + ice_transport_->OnReceiveMessage(msg); + break; + } + } + } + }; + + on_receive_ice_msg_ = [this](const char *data, size_t size) {}; + + transport_id_ = id; + + ws_transport_ = new WsTransport(on_receive_ws_msg_); + if (ws_transport_) { + ws_transport_->Connect(uri); + } + + ice_transport_ = new IceTransport(ws_transport_, on_receive_ice_msg_); + ice_transport_->InitIceTransport(id); + + do { + LOG_INFO("GetSignalStatus = {}", GetSignalStatus()); + } while (SignalStatus::Connected != GetSignalStatus()); + + ice_transport_->CreateTransport(transport_id_); + return 0; +} + +int PeerConnection::Destroy() { + if (ws_transport_) { + delete ws_transport_; + } + return 0; +} + +SignalStatus PeerConnection::GetSignalStatus() { return signal_status_; } \ No newline at end of file diff --git a/src/pc/peer_connection.h b/src/pc/peer_connection.h new file mode 100644 index 0000000..5a1fe65 --- /dev/null +++ b/src/pc/peer_connection.h @@ -0,0 +1,33 @@ +#ifndef _PEER_CONNECTION_H_ +#define _PEER_CONNECTION_H_ + +#include + +#include "ice_transport.h" +#include "ws_transport.h" + +enum SignalStatus { Connecting = 0, Connected, Closed }; + +class PeerConnection { + public: + PeerConnection(); + ~PeerConnection(); + + public: + int Init(std::string const &uri); + int Init(std::string const &uri, std::string const &id); + int Destroy(); + + SignalStatus GetSignalStatus(); + + private: + WsTransport *ws_transport_ = nullptr; + IceTransport *ice_transport_ = nullptr; + std::function on_receive_ws_msg_ = nullptr; + std::function on_receive_ice_msg_ = nullptr; + unsigned int connection_id_ = 0; + std::string transport_id_ = ""; + SignalStatus signal_status_ = SignalStatus::Closed; +}; + +#endif \ No newline at end of file diff --git a/src/rtc/rtc.cpp b/src/rtc/rtc.cpp new file mode 100644 index 0000000..5eec4b8 --- /dev/null +++ b/src/rtc/rtc.cpp @@ -0,0 +1,101 @@ +#include "rtc.h" + +#include +#include + +#include "ice_agent.h" +#include "log.h" +#include "peer_connection.h" +#include "ws_transport.h" + +using nlohmann::json; + +static const std::vector siganl_status = {"Connecting", + "Connected", "Closed"}; + +class WsSender : public WsCore { + public: + WsSender() {} + ~WsSender() {} + + void OnReceiveMessage(const std::string &msg) { + LOG_INFO("Receive msg: {}", msg); + } +}; + +static WsSender *ws_client; +static PeerConnection *peer_connection; + +int CreatePeerConnection(const char *uri) { + peer_connection = new PeerConnection(); + peer_connection->Init(uri); + + // do { + // } while (SignalStatus::Connected != peer_connection->GetSignalStatus()); + + // LOG_INFO("Signal status: {}", + // siganl_status[peer_connection->GetSignalStatus()]); + + // peer_connection->CreateTransport(); + // peer_connection->CreateOffer(); + + return 0; +} + +int CreatePeerConnectionWithID(const char *uri, const char *id) { + peer_connection = new PeerConnection(); + peer_connection->Init(uri, id); + + // do { + // } while (SignalStatus::Connected != peer_connection->GetSignalStatus()); + + // LOG_INFO("Signal status: {}", + // siganl_status[peer_connection->GetSignalStatus()]); + + // peer_connection->CreateTransport(id); + + return 0; +} + +int rtc() { + ws_client = new WsSender(); + return 0; +} + +int CreateWsClient(const char *uri) { + ws_client->Connect(uri); + + return 0; +} + +int WsSendMsg(const char *message) { + ws_client->Send(message); + + return 0; +} + +// ws_status GetWsStatus() +// { +// std::string ws_status = ws_client->GetStatus(); + +// if ("Connecting" == ws_status) +// { +// return ws_status::WS_CONNECTING; +// } +// else if ("Open" == ws_status) +// { +// return ws_status::WS_OPEN; +// } +// else if ("Failed" == ws_status) +// { +// return ws_status::WS_FAILED; +// } +// else if ("Closed" == ws_status) +// { +// return ws_status::WS_CLOSED; +// } +// else +// { +// return ws_status::WS_UNKNOWN; +// } +// } \ No newline at end of file diff --git a/src/rtc/rtc.h b/src/rtc/rtc.h new file mode 100644 index 0000000..12a27e7 --- /dev/null +++ b/src/rtc/rtc.h @@ -0,0 +1,30 @@ +#ifndef _RTC_H_ +#define _RTC_H_ + +enum ws_status { WS_CONNECTING = 0, WS_OPEN, WS_FAILED, WS_CLOSED, WS_UNKNOWN }; + +#ifdef __cplusplus +extern "C" { +#endif + +int CreatePeerConnection(const char* uri); + +int CreatePeerConnectionWithID(const char* uri, const char* id); + +int rtc(); + +int ConnectToServer(const char* uri); + +int RegisterPeer(); + +int CreateWsClient(const char* uri); + +int WsSendMsg(const char* message); + +ws_status GetWsStatus(); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/src/ws/ws_core.cpp b/src/ws/ws_core.cpp new file mode 100644 index 0000000..295f68b --- /dev/null +++ b/src/ws/ws_core.cpp @@ -0,0 +1,136 @@ +#include "ws_core.h" + +#include +#include +#include + +#include "log.h" + +WsCore::WsCore() { + m_endpoint_.clear_access_channels(websocketpp::log::alevel::all); + m_endpoint_.clear_error_channels(websocketpp::log::elevel::all); + + m_endpoint_.init_asio(); + m_endpoint_.start_perpetual(); + + m_thread_ = websocketpp::lib::make_shared( + &client::run, &m_endpoint_); +} + +WsCore::~WsCore() { + m_endpoint_.stop_perpetual(); + + if (GetStatus() != "Open") { + // Only close open connections + return; + } + + websocketpp::lib::error_code ec; + m_endpoint_.close(connection_handle_, websocketpp::close::status::going_away, + "", ec); + if (ec) { + LOG_INFO("> Error closing connection {}", ec.message()); + } + + m_thread_->join(); +} + +int WsCore::Connect(std::string const &uri) { + websocketpp::lib::error_code ec; + + client::connection_ptr con = m_endpoint_.get_connection(uri, ec); + + connection_handle_ = con->get_handle(); + + if (ec) { + LOG_INFO("> Connect initialization error: {}", ec.message()); + return -1; + } + + con->set_open_handler(websocketpp::lib::bind( + &WsCore::OnOpen, this, &m_endpoint_, websocketpp::lib::placeholders::_1)); + con->set_fail_handler(websocketpp::lib::bind( + &WsCore::OnFail, this, &m_endpoint_, websocketpp::lib::placeholders::_1)); + con->set_close_handler( + websocketpp::lib::bind(&WsCore::OnClose, this, &m_endpoint_, + websocketpp::lib::placeholders::_1)); + + // con->set_ping_handler(websocketpp::lib::bind( + // &WsCore::on_ping, + // this, + // websocketpp::lib::placeholders::_1, + // websocketpp::lib::placeholders::_2 + // )); + + con->set_pong_handler(websocketpp::lib::bind( + &WsCore::OnPong, this, websocketpp::lib::placeholders::_1, + websocketpp::lib::placeholders::_2)); + + con->set_pong_timeout(1000); + + con->set_pong_timeout_handler(websocketpp::lib::bind( + &WsCore::OnPongTimeout, this, websocketpp::lib::placeholders::_1, + websocketpp::lib::placeholders::_2)); + + con->set_message_handler(websocketpp::lib::bind( + &WsCore::OnMessage, this, websocketpp::lib::placeholders::_1, + websocketpp::lib::placeholders::_2)); + + m_endpoint_.connect(con); + + return 0; +} + +void WsCore::Close(websocketpp::close::status::value code, std::string reason) { + websocketpp::lib::error_code ec; + + m_endpoint_.close(connection_handle_, code, reason, ec); + if (ec) { + LOG_INFO("> Error initiating close: {}", ec.message()); + } +} + +void WsCore::Send(std::string message) { + websocketpp::lib::error_code ec; + + m_endpoint_.send(connection_handle_, message, + websocketpp::frame::opcode::text, ec); + if (ec) { + LOG_INFO("> Error sending message: {}", ec.message()); + return; + } +} + +void WsCore::Ping() { + websocketpp::lib::error_code ec; + + std::string message = "ping"; + + m_endpoint_.ping(connection_handle_, message, ec); + if (ec) { + LOG_INFO("> Error sending ping"); + return; + } +} + +const std::string &WsCore::GetStatus() { return connection_status_; } + +void WsCore::OnOpen(client *c, websocketpp::connection_hdl hdl) { + connection_status_ = "Open"; +} + +void WsCore::OnFail(client *c, websocketpp::connection_hdl hdl) { + connection_status_ = "Failed"; +} + +void WsCore::OnClose(client *c, websocketpp::connection_hdl hdl) { + connection_status_ = "Closed"; +} + +void WsCore::OnPong(websocketpp::connection_hdl, std::string msg) {} + +void WsCore::OnPongTimeout(websocketpp::connection_hdl, std::string msg) {} + +void WsCore::OnMessage(websocketpp::connection_hdl, client::message_ptr &msg) { + OnReceiveMessage(msg->get_payload()); +} \ No newline at end of file diff --git a/src/ws/ws_core.h b/src/ws/ws_core.h new file mode 100644 index 0000000..f31b2fb --- /dev/null +++ b/src/ws/ws_core.h @@ -0,0 +1,54 @@ +#ifndef _WS_CORE_H_ +#define _WS_CORE_H_ + +#include +#include +#include + +#include "websocketpp/client.hpp" +#include "websocketpp/common/memory.hpp" +#include "websocketpp/common/thread.hpp" +#include "websocketpp/config/asio_no_tls_client.hpp" + +typedef websocketpp::client client; + +class WsCore { + public: + WsCore(); + + virtual ~WsCore(); + + int Connect(std::string const &uri); + + void Close(websocketpp::close::status::value code, std::string reason); + + void Send(std::string message); + + void Ping(); + + const std::string &GetStatus(); + + // Callback + void OnOpen(client *c, websocketpp::connection_hdl hdl); + + void OnFail(client *c, websocketpp::connection_hdl hdl); + + void OnClose(client *c, websocketpp::connection_hdl hdl); + + void OnPong(websocketpp::connection_hdl, std::string msg); + + void OnPongTimeout(websocketpp::connection_hdl, std::string msg); + + void OnMessage(websocketpp::connection_hdl, client::message_ptr &msg); + + virtual void OnReceiveMessage(const std::string &msg) = 0; + + private: + client m_endpoint_; + websocketpp::connection_hdl connection_handle_; + websocketpp::lib::shared_ptr m_thread_; + + std::string connection_status_ = "Connecting"; +}; + +#endif \ No newline at end of file diff --git a/src/ws/ws_transport.cpp b/src/ws/ws_transport.cpp new file mode 100644 index 0000000..b6b5864 --- /dev/null +++ b/src/ws/ws_transport.cpp @@ -0,0 +1,16 @@ +#include "ws_transport.h" + +#include "log.h" + +WsTransport::WsTransport( + std::function on_receive_msg_cb) + : on_receive_msg_(on_receive_msg_cb) {} + +WsTransport::~WsTransport() {} + +void WsTransport::OnReceiveMessage(const std::string &msg) { + LOG_INFO("Receive msg: {}", msg); + if (on_receive_msg_) { + on_receive_msg_(msg); + } +} \ No newline at end of file diff --git a/src/ws/ws_transport.h b/src/ws/ws_transport.h new file mode 100644 index 0000000..cd0194a --- /dev/null +++ b/src/ws/ws_transport.h @@ -0,0 +1,18 @@ +#ifndef _WS_TRANSPORT_H_ +#define _WS_TRANSPORT_H_ + +#include "ws_core.h" + +class WsTransport : public WsCore { + public: + WsTransport(std::function on_receive_msg_cb); + ~WsTransport(); + + public: + void OnReceiveMessage(const std::string &msg); + + private: + std::function on_receive_msg_ = nullptr; +}; + +#endif \ No newline at end of file diff --git a/tests/peerconnection/answer.cpp b/tests/peerconnection/answer.cpp new file mode 100644 index 0000000..7728b5f --- /dev/null +++ b/tests/peerconnection/answer.cpp @@ -0,0 +1,10 @@ +#include + +#include "rtc.h" + +int main(int argc, char **argv) { + CreatePeerConnectionWithID("ws://localhost:9002", "000000"); + + getchar(); + return 0; +} diff --git a/tests/peerconnection/offer.cpp b/tests/peerconnection/offer.cpp new file mode 100644 index 0000000..d0e7730 --- /dev/null +++ b/tests/peerconnection/offer.cpp @@ -0,0 +1,10 @@ +#include + +#include "rtc.h" + +int main(int argc, char **argv) { + CreatePeerConnection("ws://localhost:9002"); + + getchar(); + return 0; +} diff --git a/tests/signal_client/signal_client.cpp b/tests/signal_client/signal_client.cpp new file mode 100644 index 0000000..aeac770 --- /dev/null +++ b/tests/signal_client/signal_client.cpp @@ -0,0 +1,51 @@ +#include "ws_client.h" + +#include +#include +#include +#include + +class WsReceiver:public WsCore +{ +public: + WsReceiver(){} + ~WsReceiver(){} + + void OnReceiveMessage(const std::string &msg) + { + LOG_INFO("Receive msg: {}", msg); + } +}; + +int main() +{ + bool done = false; + std::string input; + WsReceiver ws_client; + + LOG_INFO("connect ws://localhost:9002"); + ws_client.Connect("ws://localhost:9002"); + + std::string status1 = ws_client.GetStatus(); + + while("Open" != status1) + { + status1 = ws_client.GetStatus(); + } + + LOG_INFO("Connect successfully!"); + + LOG_INFO("Send message [Hello]"); + ws_client.Send("Hello"); + + LOG_INFO("Send ping"); + ws_client.Ping(); + + LOG_INFO("Close conneciton"); + int close_code = websocketpp::close::status::normal; + std::string reason = "User Close"; + ws_client.Close(close_code, reason); + + getchar(); + return 0; +} \ No newline at end of file diff --git a/tests/signal_server/main.cpp b/tests/signal_server/main.cpp new file mode 100644 index 0000000..2999f82 --- /dev/null +++ b/tests/signal_server/main.cpp @@ -0,0 +1,8 @@ +#include "signal_server.h" + +int main() { + SignalServer s; + // connect ws://localhost:9002 + s.run(); + return 0; +} \ No newline at end of file diff --git a/tests/signal_server/signal_server.cpp b/tests/signal_server/signal_server.cpp new file mode 100644 index 0000000..0350767 --- /dev/null +++ b/tests/signal_server/signal_server.cpp @@ -0,0 +1,166 @@ +#include "signal_server.h" + +#include "log.h" + +static const std::map siganl_types{ + {"create_transport", 1}, {"offer", 2}, {"query_remote_sdp", 3}, + {"answer", 4}, {"offer_candidate", 5}, {"answer_candidate", 6}}; + +std::string gen_random_6() { + static const char alphanum[] = "0123456789"; + std::string tmp_s; + tmp_s.reserve(6); + + for (int i = 0; i < 6; ++i) { + tmp_s += alphanum[rand() % (sizeof(alphanum) - 1)]; + } + + // return tmp_s; + return "000000"; +} + +SignalServer::SignalServer() { + // Set logging settings + server_.set_error_channels(websocketpp::log::elevel::all); + server_.set_access_channels(websocketpp::log::alevel::none); + + // Initialize Asio + server_.init_asio(); + + server_.set_open_handler( + std::bind(&SignalServer::on_open, this, std::placeholders::_1)); + + server_.set_close_handler( + std::bind(&SignalServer::on_close, this, std::placeholders::_1)); + + server_.set_message_handler(std::bind(&SignalServer::on_message, this, + std::placeholders::_1, + std::placeholders::_2)); + + server_.set_ping_handler(bind(&SignalServer::on_ping, this, + std::placeholders::_1, std::placeholders::_2)); + + server_.set_pong_handler(bind(&SignalServer::on_pong, this, + std::placeholders::_1, std::placeholders::_2)); +} + +SignalServer::~SignalServer() {} + +bool SignalServer::on_open(websocketpp::connection_hdl hdl) { + connections_[hdl] = connection_id_; + LOG_INFO("New connection [{}] established", connection_id_++); + + json message = {{"type", "connection_id"}, {"connection_id", connection_id_}}; + server_.send(hdl, message.dump(), websocketpp::frame::opcode::text); + + return true; +} + +bool SignalServer::on_close(websocketpp::connection_hdl hdl) { + LOG_INFO("Connection [{}] closed", connection_id_++); + connections_.erase(hdl); + return true; +} + +bool SignalServer::on_ping(websocketpp::connection_hdl hdl, std::string s) { + /* Do something */ + LOG_INFO("Receive ping"); + return true; +} + +bool SignalServer::on_pong(websocketpp::connection_hdl hdl, std::string s) { + /* Do something */ + LOG_INFO("pong"); + return true; +} + +void SignalServer::run() { + // Listen on port 9002 + server_.listen(9002); + + // Queues a connection accept operation + server_.start_accept(); + + // Start the Asio io_service run loop + server_.run(); +} + +void SignalServer::send_msg(websocketpp::connection_hdl hdl, json message) { + server_.send(hdl, message.dump(), websocketpp::frame::opcode::text); +} + +void SignalServer::on_message(websocketpp::connection_hdl hdl, + server::message_ptr msg) { + std::string payload = msg->get_payload(); + + auto j = json::parse(payload); + + std::string type = j["type"]; + auto itr = siganl_types.find(type); + if (itr != siganl_types.end()) { + LOG_INFO("msg type: {}", itr->first); + switch (itr->second) { + case 1: { + transport_id_ = gen_random_6(); + LOG_INFO("Generate transport_id [{}]", transport_id_); + json message = {{"type", "transport_id"}, + {"transport_id", transport_id_}}; + send_msg(hdl, message); + break; + } + case 2: { + std::string transport_id = j["transport_id"]; + std::string sdp = j["sdp"]; + LOG_INFO("Save transport_id[{}] with offer sdp[{}]", transport_id, sdp); + offer_sdp_map_[transport_id] = sdp; + offer_hdl_map_[transport_id] = hdl; + break; + } + case 3: { + std::string transport_id = j["transport_id"]; + std::string sdp = offer_sdp_map_[transport_id_]; + LOG_INFO("send offer sdp [{}]", sdp.c_str()); + json message = {{"type", "remote_sdp"}, {"sdp", sdp}}; + send_msg(hdl, message); + break; + } + case 4: { + std::string transport_id = j["transport_id"]; + std::string sdp = j["sdp"]; + LOG_INFO("Save transport_id[{}] with answer sdp[{}]", transport_id, + sdp); + answer_sdp_map_[transport_id] = sdp; + answer_hdl_map_[transport_id] = hdl; + LOG_INFO("send answer sdp [{}]", sdp.c_str()); + json message = {{"type", "remote_sdp"}, {"sdp", sdp}}; + send_msg(offer_hdl_map_[transport_id], message); + break; + } + case 5: { + std::string transport_id = j["transport_id"]; + std::string candidate = j["sdp"]; + LOG_INFO("send candidate [{}]", candidate.c_str()); + json message = {{"type", "candidate"}, {"sdp", candidate}}; + send_msg(answer_hdl_map_[transport_id], message); + break; + } + case 6: { + std::string transport_id = j["transport_id"]; + std::string candidate = j["sdp"]; + LOG_INFO("send candidate [{}]", candidate.c_str()); + json message = {{"type", "candidate"}, {"sdp", candidate}}; + send_msg(offer_hdl_map_[transport_id], message); + break; + } + default: + break; + } + } + + // std::string sdp = j["sdp"]; + + // LOG_INFO("Message type: {}", type); + // LOG_INFO("Message body: {}", sdp); + + // server_.send(hdl, msg->get_payload(), msg->get_opcode()); +} \ No newline at end of file diff --git a/tests/signal_server/signal_server.h b/tests/signal_server/signal_server.h new file mode 100644 index 0000000..f19e575 --- /dev/null +++ b/tests/signal_server/signal_server.h @@ -0,0 +1,50 @@ +#ifndef _SIGNAL_SERVER_H_ +#define _SIGNAL_SERVER_H_ + +#include +#include +#include +#include +#include +#include + +using nlohmann::json; + +typedef websocketpp::server server; +typedef unsigned int connection_id; +typedef std::string room_id; + +class SignalServer { + public: + SignalServer(); + ~SignalServer(); + + bool on_open(websocketpp::connection_hdl hdl); + + bool on_close(websocketpp::connection_hdl hdl); + + bool on_ping(websocketpp::connection_hdl hdl, std::string s); + + bool on_pong(websocketpp::connection_hdl hdl, std::string s); + + void run(); + + void on_message(websocketpp::connection_hdl hdl, server::message_ptr msg); + + void send_msg(websocketpp::connection_hdl hdl, json message); + + private: + server server_; + std::map> + connections_; + std::map rooms_; + unsigned int connection_id_ = 0; + std::string transport_id_ = "000000"; + std::map offer_sdp_map_; + std::map answer_sdp_map_; + std::map offer_hdl_map_; + std::map answer_hdl_map_; +}; + +#endif \ No newline at end of file diff --git a/thirdparty/libjuice/include/juice/juice.h b/thirdparty/libjuice/include/juice/juice.h new file mode 100644 index 0000000..9e4b9c1 --- /dev/null +++ b/thirdparty/libjuice/include/juice/juice.h @@ -0,0 +1,176 @@ +/** + * Copyright (c) 2020-2022 Paul-Louis Ageneau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef JUICE_H +#define JUICE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#ifdef JUICE_HAS_EXPORT_HEADER +#include "juice_export.h" +#else // no export header +#ifdef JUICE_STATIC +#define JUICE_EXPORT +#else // dynamic library +#ifdef _WIN32 +#if defined(JUICE_EXPORTS) || defined(juice_EXPORTS) +#define JUICE_EXPORT __declspec(dllexport) // building the library +#else +#define JUICE_EXPORT __declspec(dllimport) // using the library +#endif +#else // not WIN32 +#define JUICE_EXPORT +#endif +#endif +#endif + +#define JUICE_ERR_SUCCESS 0 +#define JUICE_ERR_INVALID -1 // invalid argument +#define JUICE_ERR_FAILED -2 // runtime error +#define JUICE_ERR_NOT_AVAIL -3 // element not available + +// ICE Agent + +#define JUICE_MAX_ADDRESS_STRING_LEN 64 +#define JUICE_MAX_CANDIDATE_SDP_STRING_LEN 256 +#define JUICE_MAX_SDP_STRING_LEN 4096 + +typedef struct juice_agent juice_agent_t; + +typedef enum juice_state { + JUICE_STATE_DISCONNECTED = 0, + JUICE_STATE_GATHERING, + JUICE_STATE_CONNECTING, + JUICE_STATE_CONNECTED, + JUICE_STATE_COMPLETED, + JUICE_STATE_FAILED +} juice_state_t; + +typedef void (*juice_cb_state_changed_t)(juice_agent_t *agent, juice_state_t state, void *user_ptr); +typedef void (*juice_cb_candidate_t)(juice_agent_t *agent, const char *sdp, void *user_ptr); +typedef void (*juice_cb_gathering_done_t)(juice_agent_t *agent, void *user_ptr); +typedef void (*juice_cb_recv_t)(juice_agent_t *agent, const char *data, size_t size, + void *user_ptr); + +typedef struct juice_turn_server { + const char *host; + const char *username; + const char *password; + uint16_t port; +} juice_turn_server_t; + +typedef enum juice_concurrency_mode { + JUICE_CONCURRENCY_MODE_POLL = 0, // Connections share a single thread + JUICE_CONCURRENCY_MODE_MUX, // Connections are multiplexed on a single UDP socket + JUICE_CONCURRENCY_MODE_THREAD, // Each connection runs in its own thread +} juice_concurrency_mode_t; + +typedef struct juice_config { + juice_concurrency_mode_t concurrency_mode; + + const char *stun_server_host; + uint16_t stun_server_port; + + juice_turn_server_t *turn_servers; + int turn_servers_count; + + const char *bind_address; + + uint16_t local_port_range_begin; + uint16_t local_port_range_end; + + juice_cb_state_changed_t cb_state_changed; + juice_cb_candidate_t cb_candidate; + juice_cb_gathering_done_t cb_gathering_done; + juice_cb_recv_t cb_recv; + + void *user_ptr; + +} juice_config_t; + +JUICE_EXPORT juice_agent_t *juice_create(const juice_config_t *config); +JUICE_EXPORT void juice_destroy(juice_agent_t *agent); + +JUICE_EXPORT int juice_gather_candidates(juice_agent_t *agent); +JUICE_EXPORT int juice_get_local_description(juice_agent_t *agent, char *buffer, size_t size); +JUICE_EXPORT int juice_set_remote_description(juice_agent_t *agent, const char *sdp); +JUICE_EXPORT int juice_add_remote_candidate(juice_agent_t *agent, const char *sdp); +JUICE_EXPORT int juice_set_remote_gathering_done(juice_agent_t *agent); +JUICE_EXPORT int juice_send(juice_agent_t *agent, const char *data, size_t size); +JUICE_EXPORT int juice_send_diffserv(juice_agent_t *agent, const char *data, size_t size, int ds); +JUICE_EXPORT juice_state_t juice_get_state(juice_agent_t *agent); +JUICE_EXPORT int juice_get_selected_candidates(juice_agent_t *agent, char *local, size_t local_size, + char *remote, size_t remote_size); +JUICE_EXPORT int juice_get_selected_addresses(juice_agent_t *agent, char *local, size_t local_size, + char *remote, size_t remote_size); +JUICE_EXPORT const char *juice_state_to_string(juice_state_t state); + +// ICE server + +typedef struct juice_server juice_server_t; + +typedef struct juice_server_credentials { + const char *username; + const char *password; + int allocations_quota; +} juice_server_credentials_t; + +typedef struct juice_server_config { + juice_server_credentials_t *credentials; + int credentials_count; + + int max_allocations; + int max_peers; + + const char *bind_address; + const char *external_address; + uint16_t port; + + uint16_t relay_port_range_begin; + uint16_t relay_port_range_end; + + const char *realm; + +} juice_server_config_t; + +JUICE_EXPORT juice_server_t *juice_server_create(const juice_server_config_t *config); +JUICE_EXPORT void juice_server_destroy(juice_server_t *server); + +JUICE_EXPORT uint16_t juice_server_get_port(juice_server_t *server); +JUICE_EXPORT int juice_server_add_credentials(juice_server_t *server, + const juice_server_credentials_t *credentials, + unsigned long lifetime_ms); + +// Logging + +typedef enum juice_log_level { + JUICE_LOG_LEVEL_VERBOSE = 0, + JUICE_LOG_LEVEL_DEBUG, + JUICE_LOG_LEVEL_INFO, + JUICE_LOG_LEVEL_WARN, + JUICE_LOG_LEVEL_ERROR, + JUICE_LOG_LEVEL_FATAL, + JUICE_LOG_LEVEL_NONE +} juice_log_level_t; + +typedef void (*juice_log_cb_t)(juice_log_level_t level, const char *message); + +JUICE_EXPORT void juice_set_log_level(juice_log_level_t level); +JUICE_EXPORT void juice_set_log_handler(juice_log_cb_t cb); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/thirdparty/libjuice/lib/juice.lib b/thirdparty/libjuice/lib/juice.lib new file mode 100644 index 0000000000000000000000000000000000000000..29bf3f362a09595d9050bf5874b503b5e4003981 GIT binary patch literal 542656 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 z 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

er@i_EcUm>d+JK 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≷JJk`-B{NVFP4_1(=dS=`J!j5q?X@AAnDt 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 BZ*u?u literal 0 HcmV?d00001 diff --git a/thirdparty/websocketpp/cmake/websocketpp-config.cmake b/thirdparty/websocketpp/cmake/websocketpp-config.cmake new file mode 100644 index 0000000..4d920bc --- /dev/null +++ b/thirdparty/websocketpp/cmake/websocketpp-config.cmake @@ -0,0 +1,28 @@ +# - Config file for the websocketpp package +# It defines the following variables +# WEBSOCKETPP_FOUND - indicates that the module was found +# WEBSOCKETPP_INCLUDE_DIR - include directories + + +####### Expanded from @PACKAGE_INIT@ by configure_package_config_file() ####### +####### Any changes to this file will be overwritten by the next CMake run #### +####### The input file was websocketpp-config.cmake.in ######## + +get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../" ABSOLUTE) + +macro(set_and_check _var _file) + set(${_var} "${_file}") + if(NOT EXISTS "${_file}") + message(FATAL_ERROR "File or directory ${_file} referenced by variable ${_var} does not exist !") + endif() +endmacro() + +#################################################################################### +set_and_check(WEBSOCKETPP_INCLUDE_DIR "${PACKAGE_PREFIX_DIR}/include") +set(WEBSOCKETPP_FOUND TRUE) + +#This is a bit of a hack, but it works well. It also allows continued support of CMake 2.8 +if(${CMAKE_VERSION} VERSION_GREATER 3.0.0 OR ${CMAKE_VERSION} VERSION_EQUAL 3.0.0) + add_library(websocketpp::websocketpp INTERFACE IMPORTED) + set_target_properties(websocketpp::websocketpp PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${WEBSOCKETPP_INCLUDE_DIR}") +endif() diff --git a/thirdparty/websocketpp/cmake/websocketpp-configVersion.cmake b/thirdparty/websocketpp/cmake/websocketpp-configVersion.cmake new file mode 100644 index 0000000..6ae43cf --- /dev/null +++ b/thirdparty/websocketpp/cmake/websocketpp-configVersion.cmake @@ -0,0 +1,88 @@ +# This is a basic version file for the Config-mode of find_package(). +# It is used by write_basic_package_version_file() as input file for configure_file() +# to create a version-file which can be installed along a config.cmake file. +# +# The created file sets PACKAGE_VERSION_EXACT if the current version string and +# the requested version string are exactly the same and it sets +# PACKAGE_VERSION_COMPATIBLE if the current version is equal to the requested version. +# The tweak version component is ignored. +# The variable CVF_VERSION must be set before calling configure_file(). + + +if (PACKAGE_FIND_VERSION_RANGE) + message(AUTHOR_WARNING + "`find_package()` specify a version range but the version strategy " + "(ExactVersion) of the module `${PACKAGE_FIND_NAME}` is incompatible " + "with this request. Only the lower endpoint of the range will be used.") +endif() + +set(PACKAGE_VERSION "0.8.2") + +if("0.8.2" MATCHES "^([0-9]+)\\.([0-9]+)\\.([0-9]+)") # strip the tweak version + set(CVF_VERSION_MAJOR "${CMAKE_MATCH_1}") + set(CVF_VERSION_MINOR "${CMAKE_MATCH_2}") + set(CVF_VERSION_PATCH "${CMAKE_MATCH_3}") + + if(NOT CVF_VERSION_MAJOR VERSION_EQUAL 0) + string(REGEX REPLACE "^0+" "" CVF_VERSION_MAJOR "${CVF_VERSION_MAJOR}") + endif() + if(NOT CVF_VERSION_MINOR VERSION_EQUAL 0) + string(REGEX REPLACE "^0+" "" CVF_VERSION_MINOR "${CVF_VERSION_MINOR}") + endif() + if(NOT CVF_VERSION_PATCH VERSION_EQUAL 0) + string(REGEX REPLACE "^0+" "" CVF_VERSION_PATCH "${CVF_VERSION_PATCH}") + endif() + + set(CVF_VERSION_NO_TWEAK "${CVF_VERSION_MAJOR}.${CVF_VERSION_MINOR}.${CVF_VERSION_PATCH}") +else() + set(CVF_VERSION_NO_TWEAK "0.8.2") +endif() + +if(PACKAGE_FIND_VERSION MATCHES "^([0-9]+)\\.([0-9]+)\\.([0-9]+)") # strip the tweak version + set(REQUESTED_VERSION_MAJOR "${CMAKE_MATCH_1}") + set(REQUESTED_VERSION_MINOR "${CMAKE_MATCH_2}") + set(REQUESTED_VERSION_PATCH "${CMAKE_MATCH_3}") + + if(NOT REQUESTED_VERSION_MAJOR VERSION_EQUAL 0) + string(REGEX REPLACE "^0+" "" REQUESTED_VERSION_MAJOR "${REQUESTED_VERSION_MAJOR}") + endif() + if(NOT REQUESTED_VERSION_MINOR VERSION_EQUAL 0) + string(REGEX REPLACE "^0+" "" REQUESTED_VERSION_MINOR "${REQUESTED_VERSION_MINOR}") + endif() + if(NOT REQUESTED_VERSION_PATCH VERSION_EQUAL 0) + string(REGEX REPLACE "^0+" "" REQUESTED_VERSION_PATCH "${REQUESTED_VERSION_PATCH}") + endif() + + set(REQUESTED_VERSION_NO_TWEAK + "${REQUESTED_VERSION_MAJOR}.${REQUESTED_VERSION_MINOR}.${REQUESTED_VERSION_PATCH}") +else() + set(REQUESTED_VERSION_NO_TWEAK "${PACKAGE_FIND_VERSION}") +endif() + +if(REQUESTED_VERSION_NO_TWEAK STREQUAL CVF_VERSION_NO_TWEAK) + set(PACKAGE_VERSION_COMPATIBLE TRUE) +else() + set(PACKAGE_VERSION_COMPATIBLE FALSE) +endif() + +if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION) + set(PACKAGE_VERSION_EXACT TRUE) +endif() + + +# if the installed project requested no architecture check, don't perform the check +if("FALSE") + return() +endif() + +# if the installed or the using project don't have CMAKE_SIZEOF_VOID_P set, ignore it: +if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "" OR "8" STREQUAL "") + return() +endif() + +# check that the installed version has the same 32/64bit-ness as the one which is currently searching: +if(NOT CMAKE_SIZEOF_VOID_P STREQUAL "8") + math(EXPR installedBits "8 * 8") + set(PACKAGE_VERSION "${PACKAGE_VERSION} (${installedBits}bit)") + set(PACKAGE_VERSION_UNSUITABLE TRUE) +endif() diff --git a/thirdparty/websocketpp/include/websocketpp/base64/base64.hpp b/thirdparty/websocketpp/include/websocketpp/base64/base64.hpp new file mode 100644 index 0000000..fbc5841 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/base64/base64.hpp @@ -0,0 +1,178 @@ +/* + ****** + base64.hpp is a repackaging of the base64.cpp and base64.h files into a + single header suitable for use as a header only library. This conversion was + done by Peter Thorson (webmaster@zaphoyd.com) in 2012. All modifications to + the code are redistributed under the same license as the original, which is + listed below. + ****** + + base64.cpp and base64.h + + Copyright (C) 2004-2008 René Nyffenegger + + This source code is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. + + 3. This notice may not be removed or altered from any source distribution. + + René Nyffenegger rene.nyffenegger@adp-gmbh.ch + +*/ + +#ifndef _BASE64_HPP_ +#define _BASE64_HPP_ + +#include + +namespace websocketpp { + +static std::string const base64_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +/// Test whether a character is a valid base64 character +/** + * @param c The character to test + * @return true if c is a valid base64 character + */ +static inline bool is_base64(unsigned char c) { + return (c == 43 || // + + (c >= 47 && c <= 57) || // /-9 + (c >= 65 && c <= 90) || // A-Z + (c >= 97 && c <= 122)); // a-z +} + +/// Encode a char buffer into a base64 string +/** + * @param input The input data + * @param len The length of input in bytes + * @return A base64 encoded string representing input + */ +inline std::string base64_encode(unsigned char const * input, size_t len) { + std::string ret; + int i = 0; + int j = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; + + while (len--) { + char_array_3[i++] = *(input++); + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for(i = 0; (i <4) ; i++) { + ret += base64_chars[char_array_4[i]]; + } + i = 0; + } + } + + if (i) { + for(j = i; j < 3; j++) { + char_array_3[j] = '\0'; + } + + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (j = 0; (j < i + 1); j++) { + ret += base64_chars[char_array_4[j]]; + } + + while((i++ < 3)) { + ret += '='; + } + } + + return ret; +} + +/// Encode a string into a base64 string +/** + * @param input The input data + * @return A base64 encoded string representing input + */ +inline std::string base64_encode(std::string const & input) { + return base64_encode( + reinterpret_cast(input.data()), + input.size() + ); +} + +/// Decode a base64 encoded string into a string of raw bytes +/** + * @param input The base64 encoded input data + * @return A string representing the decoded raw bytes + */ +inline std::string base64_decode(std::string const & input) { + size_t in_len = input.size(); + int i = 0; + int j = 0; + int in_ = 0; + unsigned char char_array_4[4], char_array_3[3]; + std::string ret; + + while (in_len-- && ( input[in_] != '=') && is_base64(input[in_])) { + char_array_4[i++] = input[in_]; in_++; + if (i ==4) { + for (i = 0; i <4; i++) { + char_array_4[i] = static_cast(base64_chars.find(char_array_4[i])); + } + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (i = 0; (i < 3); i++) { + ret += char_array_3[i]; + } + i = 0; + } + } + + if (i) { + for (j = i; j <4; j++) + char_array_4[j] = 0; + + for (j = 0; j <4; j++) + char_array_4[j] = static_cast(base64_chars.find(char_array_4[j])); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (j = 0; (j < i - 1); j++) { + ret += static_cast(char_array_3[j]); + } + } + + return ret; +} + +} // namespace websocketpp + +#endif // _BASE64_HPP_ diff --git a/thirdparty/websocketpp/include/websocketpp/client.hpp b/thirdparty/websocketpp/include/websocketpp/client.hpp new file mode 100644 index 0000000..3585e27 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/client.hpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2014, 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_CLIENT_HPP +#define WEBSOCKETPP_CLIENT_HPP + +#include + +#endif //WEBSOCKETPP_CLIENT_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/close.hpp b/thirdparty/websocketpp/include/websocketpp/close.hpp new file mode 100644 index 0000000..77b31b0 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/close.hpp @@ -0,0 +1,353 @@ + +/* + * Copyright (c) 2014, 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_CLOSE_HPP +#define WEBSOCKETPP_CLOSE_HPP + +/** \file + * A package of types and methods for manipulating WebSocket close codes. + */ + +#include +#include +#include +#include + +#include + +namespace websocketpp { +/// A package of types and methods for manipulating WebSocket close codes. +namespace close { +/// A package of types and methods for manipulating WebSocket close status' +namespace status { + /// The type of a close code value. + typedef uint16_t value; + + /// A blank value for internal use. + static value const blank = 0; + + /// Close the connection without a WebSocket close handshake. + /** + * This special value requests that the WebSocket connection be closed + * without performing the WebSocket closing handshake. This does not comply + * with RFC6455, but should be safe to do if necessary. This could be useful + * for clients that need to disconnect quickly and cannot afford the + * complete handshake. + */ + static value const omit_handshake = 1; + + /// Close the connection with a forced TCP drop. + /** + * This special value requests that the WebSocket connection be closed by + * forcibly dropping the TCP connection. This will leave the other side of + * the connection with a broken connection and some expensive timeouts. this + * should not be done except in extreme cases or in cases of malicious + * remote endpoints. + */ + static value const force_tcp_drop = 2; + + /// Normal closure, meaning that the purpose for which the connection was + /// established has been fulfilled. + static value const normal = 1000; + + /// The endpoint was "going away", such as a server going down or a browser + /// navigating away from a page. + static value const going_away = 1001; + + /// A protocol error occurred. + static value const protocol_error = 1002; + + /// The connection was terminated because an endpoint received a type of + /// data it cannot accept. + /** + * (e.g., an endpoint that understands only text data MAY send this if it + * receives a binary message). + */ + static value const unsupported_data = 1003; + + /// A dummy value to indicate that no status code was received. + /** + * This value is illegal on the wire. + */ + static value const no_status = 1005; + + /// A dummy value to indicate that the connection was closed abnormally. + /** + * In such a case there was no close frame to extract a value from. This + * value is illegal on the wire. + */ + static value const abnormal_close = 1006; + + /// An endpoint received message data inconsistent with its type. + /** + * For example: Invalid UTF8 bytes in a text message. + */ + static value const invalid_payload = 1007; + + /// An endpoint received a message that violated its policy. + /** + * This is a generic status code that can be returned when there is no other + * more suitable status code (e.g., 1003 or 1009) or if there is a need to + * hide specific details about the policy. + */ + static value const policy_violation = 1008; + + /// An endpoint received a message too large to process. + static value const message_too_big = 1009; + + /// A client expected the server to accept a required extension request + /** + * The list of extensions that are needed SHOULD appear in the /reason/ part + * of the Close frame. Note that this status code is not used by the server, + * because it can fail the WebSocket handshake instead. + */ + static value const extension_required = 1010; + + /// An endpoint encountered an unexpected condition that prevented it from + /// fulfilling the request. + static value const internal_endpoint_error = 1011; + + /// Indicates that the service is restarted. A client may reconnect and if + /// if it chooses to do so, should reconnect using a randomized delay of + /// 5-30s + static value const service_restart = 1012; + + /// Indicates that the service is experiencing overload. A client should + /// only connect to a different IP (when there are multiple for the target) + /// or reconnect to the same IP upon user action. + static value const try_again_later = 1013; + + /// Indicates that the server was acting as a gateway or proxy and received + /// an invalid response from the upstream server. This is similar to 502 + /// HTTP Status Code. + static value const bad_gateway = 1014; + + /// An endpoint failed to perform a TLS handshake + /** + * Designated for use in applications expecting a status code to indicate + * that the connection was closed due to a failure to perform a TLS + * handshake (e.g., the server certificate can't be verified). This value is + * illegal on the wire. + */ + static value const tls_handshake = 1015; + + /// A generic subprotocol error + /** + * Indicates that a subprotocol error occurred. Typically this involves + * receiving a message that is not formatted as a valid message for the + * subprotocol in use. + */ + static value const subprotocol_error = 3000; + + /// A invalid subprotocol data + /** + * Indicates that data was received that violated the specification of the + * subprotocol in use. + */ + static value const invalid_subprotocol_data = 3001; + + /// First value in range reserved for future protocol use + static value const rsv_start = 1016; + /// Last value in range reserved for future protocol use + static value const rsv_end = 2999; + + /// Test whether a close code is in a reserved range + /** + * @param [in] code The code to test + * @return Whether or not code is reserved + */ + inline bool reserved(value code) { + return ((code >= rsv_start && code <= rsv_end) || + code == 1004); + } + + /// First value in range that is always invalid on the wire + static value const invalid_low = 999; + /// Last value in range that is always invalid on the wire + static value const invalid_high = 5000; + + /// Test whether a close code is invalid on the wire + /** + * @param [in] code The code to test + * @return Whether or not code is invalid on the wire + */ + inline bool invalid(value code) { + return (code <= invalid_low || code >= invalid_high || + code == no_status || code == abnormal_close || + code == tls_handshake); + } + + /// Determine if the code represents an unrecoverable error + /** + * There is a class of errors for which once they are discovered normal + * WebSocket functionality can no longer occur. This function determines + * if a given code is one of these values. This information is used to + * determine if the system has the capability of waiting for a close + * acknowledgement or if it should drop the TCP connection immediately + * after sending its close frame. + * + * @param [in] code The value to test. + * @return True if the code represents an unrecoverable error + */ + inline bool terminal(value code) { + return (code == protocol_error || code == invalid_payload || + code == policy_violation || code == message_too_big || + code == internal_endpoint_error); + } + + /// Return a human readable interpretation of a WebSocket close code + /** + * See https://tools.ietf.org/html/rfc6455#section-7.4 for more details. + * + * @since 0.3.0 + * + * @param [in] code The code to look up. + * @return A human readable interpretation of the code. + */ + inline std::string get_string(value code) { + switch (code) { + case normal: + return "Normal close"; + case going_away: + return "Going away"; + case protocol_error: + return "Protocol error"; + case unsupported_data: + return "Unsupported data"; + case no_status: + return "No status set"; + case abnormal_close: + return "Abnormal close"; + case invalid_payload: + return "Invalid payload"; + case policy_violation: + return "Policy violoation"; + case message_too_big: + return "Message too big"; + case extension_required: + return "Extension required"; + case internal_endpoint_error: + return "Internal endpoint error"; + case service_restart: + return "Service restart"; + case try_again_later: + return "Try again later"; + case bad_gateway: + return "Bad gateway"; + case tls_handshake: + return "TLS handshake failure"; + case subprotocol_error: + return "Generic subprotocol error"; + case invalid_subprotocol_data: + return "Invalid subprotocol data"; + default: + return "Unknown"; + } + } +} // namespace status + +/// Type used to convert close statuses between integer and wire representations +union code_converter { + uint16_t i; + char c[2]; +}; + +/// Extract a close code value from a close payload +/** + * If there is no close value (ie string is empty) status::no_status is + * returned. If a code couldn't be extracted (usually do to a short or + * otherwise mangled payload) status::protocol_error is returned and the ec + * value is flagged as an error. Note that this case is different than the case + * where protocol error is received over the wire. + * + * If the value is in an invalid or reserved range ec is set accordingly. + * + * @param [in] payload Close frame payload value received over the wire. + * @param [out] ec Set to indicate what error occurred, if any. + * @return The extracted value + */ +inline status::value extract_code(std::string const & payload, lib::error_code + & ec) +{ + ec = lib::error_code(); + + if (payload.size() == 0) { + return status::no_status; + } else if (payload.size() == 1) { + ec = make_error_code(error::bad_close_code); + return status::protocol_error; + } + + code_converter val; + + val.c[0] = payload[0]; + val.c[1] = payload[1]; + + status::value code(ntohs(val.i)); + + if (status::invalid(code)) { + ec = make_error_code(error::invalid_close_code); + } + + if (status::reserved(code)) { + ec = make_error_code(error::reserved_close_code); + } + + return code; +} + +/// Extract the reason string from a close payload +/** + * The string should be a valid UTF8 message. error::invalid_utf8 will be set if + * the function extracts a reason that is not valid UTF8. + * + * @param [in] payload The payload string to extract a reason from. + * @param [out] ec Set to indicate what error occurred, if any. + * @return The reason string. + */ +inline std::string extract_reason(std::string const & payload, lib::error_code + & ec) +{ + std::string reason; + ec = lib::error_code(); + + if (payload.size() > 2) { + reason.append(payload.begin()+2,payload.end()); + } + + if (!websocketpp::utf8_validator::validate(reason)) { + ec = make_error_code(error::invalid_utf8); + } + + return reason; +} + +} // namespace close +} // namespace websocketpp + +#endif // WEBSOCKETPP_CLOSE_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/common/asio.hpp b/thirdparty/websocketpp/include/websocketpp/common/asio.hpp new file mode 100644 index 0000000..9ca25f1 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/common/asio.hpp @@ -0,0 +1,141 @@ +/* + * 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_COMMON_ASIO_HPP +#define WEBSOCKETPP_COMMON_ASIO_HPP + +// This file goes to some length to preserve compatibility with versions of +// boost older than 1.49 (where the first modern steady_timer timer based on +// boost/std chrono was introduced. +// +// For the versions older than 1.49, the deadline_timer is used instead. this +// brings in dependencies on boost date_time and it has a different interface +// that is normalized by the `lib::asio::is_neg` and `lib::asio::milliseconds` +// wrappers provided by this file. +// +// The primary reason for this continued support is that boost 1.48 is the +// default and not easily changeable version of boost supplied by the package +// manager of popular Linux distributions like Ubuntu 12.04 LTS. Once the need +// for this has passed this should be cleaned up and simplified. + +#ifdef ASIO_STANDALONE + #include + + #if (ASIO_VERSION/100000) == 1 && ((ASIO_VERSION/100)%1000) < 8 + static_assert(false, "The minimum version of standalone Asio is 1.8.0"); + #endif + + #include + #include + #include +#else + #include + + // See note above about boost <1.49 compatibility. If we are running on + // boost > 1.48 pull in the steady timer and chrono library + #if (BOOST_VERSION/100000) == 1 && ((BOOST_VERSION/100)%1000) > 48 + #include + #include + #endif + + #include + #include +#endif + +namespace websocketpp { +namespace lib { + +#ifdef ASIO_STANDALONE + namespace asio { + using namespace ::asio; + // Here we assume that we will be using std::error_code with standalone + // Asio. This is probably a good assumption, but it is possible in rare + // cases that local Asio versions would be used. + using std::errc; + + // See note above about boost <1.49 compatibility. Because we require + // a standalone Asio version of 1.8+ we are guaranteed to have + // steady_timer available. By convention we require the chrono library + // (either boost or std) for use with standalone Asio. + template + bool is_neg(T duration) { + return duration.count() < 0; + } + inline lib::chrono::milliseconds milliseconds(long duration) { + return lib::chrono::milliseconds(duration); + } + } // namespace asio + +#else + namespace asio { + using namespace boost::asio; + + // See note above about boost <1.49 compatibility + #if (BOOST_VERSION/100000) == 1 && ((BOOST_VERSION/100)%1000) > 48 + // Using boost::asio >=1.49 so we use chrono and steady_timer + template + bool is_neg(T duration) { + return duration.count() < 0; + } + + // If boost believes it has std::chrono available it will use it + // so we should also use it for things that relate to boost, even + // if the library would otherwise use boost::chrono. + #if defined(BOOST_ASIO_HAS_STD_CHRONO) + inline std::chrono::milliseconds milliseconds(long duration) { + return std::chrono::milliseconds(duration); + } + #else + inline lib::chrono::milliseconds milliseconds(long duration) { + return lib::chrono::milliseconds(duration); + } + #endif + #else + // Using boost::asio <1.49 we pretend a deadline timer is a steady + // timer and wrap the negative detection and duration conversion + // appropriately. + typedef boost::asio::deadline_timer steady_timer; + + template + bool is_neg(T duration) { + return duration.is_negative(); + } + inline boost::posix_time::time_duration milliseconds(long duration) { + return boost::posix_time::milliseconds(duration); + } + #endif + + using boost::system::error_code; + namespace errc = boost::system::errc; + } // namespace asio +#endif + + +} // namespace lib +} // namespace websocketpp + +#endif // WEBSOCKETPP_COMMON_ASIO_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/common/asio_ssl.hpp b/thirdparty/websocketpp/include/websocketpp/common/asio_ssl.hpp new file mode 100644 index 0000000..ee1f4ee --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/common/asio_ssl.hpp @@ -0,0 +1,39 @@ +/* + * 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_COMMON_ASIO_SSL_HPP +#define WEBSOCKETPP_COMMON_ASIO_SSL_HPP + +// NOTE: This file must be included before common/asio.hpp + +#ifdef ASIO_STANDALONE + #include +#else + #include +#endif + +#endif // WEBSOCKETPP_COMMON_ASIO_SSL_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/common/chrono.hpp b/thirdparty/websocketpp/include/websocketpp/common/chrono.hpp new file mode 100644 index 0000000..7ac6944 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/common/chrono.hpp @@ -0,0 +1,68 @@ +/* + * 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_COMMON_CHRONO_HPP +#define WEBSOCKETPP_COMMON_CHRONO_HPP + +#include + +// If we've determined that we're in full C++11 mode and the user hasn't +// explicitly disabled the use of C++11 functional header, then prefer it to +// boost. +#if defined _WEBSOCKETPP_CPP11_INTERNAL_ && !defined _WEBSOCKETPP_NO_CPP11_CHRONO_ + #ifndef _WEBSOCKETPP_CPP11_CHRONO_ + #define _WEBSOCKETPP_CPP11_CHRONO_ + #endif +#endif + +// If we're on Visual Studio 2012 or higher and haven't explicitly disabled +// the use of C++11 chrono header then prefer it to boost. +#if defined(_MSC_VER) && _MSC_VER >= 1700 && !defined _WEBSOCKETPP_NO_CPP11_CHRONO_ + #ifndef _WEBSOCKETPP_CPP11_CHRONO_ + #define _WEBSOCKETPP_CPP11_CHRONO_ + #endif +#endif + +#ifdef _WEBSOCKETPP_CPP11_CHRONO_ + #include +#else + #include +#endif + +namespace websocketpp { +namespace lib { + +#ifdef _WEBSOCKETPP_CPP11_CHRONO_ + namespace chrono = std::chrono; +#else + namespace chrono = boost::chrono; +#endif + +} // namespace lib +} // namespace websocketpp + +#endif // WEBSOCKETPP_COMMON_CHRONO_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/common/connection_hdl.hpp b/thirdparty/websocketpp/include/websocketpp/common/connection_hdl.hpp new file mode 100644 index 0000000..3e1944b --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/common/connection_hdl.hpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2014, 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_COMMON_CONNECTION_HDL_HPP +#define WEBSOCKETPP_COMMON_CONNECTION_HDL_HPP + +#include + +namespace websocketpp { + +/// A handle to uniquely identify a connection. +/** + * This type uniquely identifies a connection. It is implemented as a weak + * pointer to the connection in question. This provides uniqueness across + * multiple endpoints and ensures that IDs never conflict or run out. + * + * It is safe to make copies of this handle, store those copies in containers, + * and use them from other threads. + * + * This handle can be upgraded to a full shared_ptr using + * `endpoint::get_con_from_hdl()` from within a handler fired by the connection + * that owns the handler. + */ +typedef lib::weak_ptr connection_hdl; + +} // namespace websocketpp + +#endif // WEBSOCKETPP_COMMON_CONNECTION_HDL_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/common/cpp11.hpp b/thirdparty/websocketpp/include/websocketpp/common/cpp11.hpp new file mode 100644 index 0000000..745ff6a --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/common/cpp11.hpp @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2014, 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_COMMON_CPP11_HPP +#define WEBSOCKETPP_COMMON_CPP11_HPP + +/** + * This header sets up some constants based on the state of C++11 support + */ + +// Hide clang feature detection from other compilers +#ifndef __has_feature // Optional of course. + #define __has_feature(x) 0 // Compatibility with non-clang compilers. +#endif +#ifndef __has_extension + #define __has_extension __has_feature // Compatibility with pre-3.0 compilers. +#endif + +// The code below attempts to use information provided by the build system or +// user supplied defines to selectively enable C++11 language and library +// features. In most cases features that are targeted individually may also be +// selectively disabled via an associated _WEBSOCKETPP_NOXXX_ define. + +#if defined(_WEBSOCKETPP_CPP11_STL_) || __cplusplus >= 201103L || defined(_WEBSOCKETPP_CPP11_STRICT_) + // This check tests for blanket c++11 coverage. It can be activated in one + // of three ways. Either the compiler itself reports that it is a full + // C++11 compiler via the __cplusplus macro or the user/build system + // supplies one of the two preprocessor defines below: + + // This is defined to allow other WebSocket++ common headers to enable + // C++11 features when they are detected by this file rather than + // duplicating the above logic in every common header. + #define _WEBSOCKETPP_CPP11_INTERNAL_ + + // _WEBSOCKETPP_CPP11_STRICT_ + // + // This define reports to WebSocket++ that 100% of the language and library + // features of C++11 are available. Using this define on a non-C++11 + // compiler will result in problems. + + // _WEBSOCKETPP_CPP11_STL_ + // + // This define enables *most* C++11 options that were implemented early on + // by compilers. It is typically used for compilers that have many, but not + // all C++11 features. It should be safe to use on GCC 4.7-4.8 and perhaps + // earlier. + #ifndef _WEBSOCKETPP_NOEXCEPT_TOKEN_ + #define _WEBSOCKETPP_NOEXCEPT_TOKEN_ noexcept + #endif + #ifndef _WEBSOCKETPP_CONSTEXPR_TOKEN_ + #define _WEBSOCKETPP_CONSTEXPR_TOKEN_ constexpr + #endif + #ifndef _WEBSOCKETPP_INITIALIZER_LISTS_ + #define _WEBSOCKETPP_INITIALIZER_LISTS_ + #endif + #ifndef _WEBSOCKETPP_NULLPTR_TOKEN_ + #define _WEBSOCKETPP_NULLPTR_TOKEN_ nullptr + #endif + #ifndef _WEBSOCKETPP_MOVE_SEMANTICS_ + #define _WEBSOCKETPP_MOVE_SEMANTICS_ + #endif + #ifndef _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_ + #define _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_ + #endif + + #ifndef __GNUC__ + // GCC as of version 4.9 (latest) does not support std::put_time yet. + // so ignore it + #define _WEBSOCKETPP_PUTTIME_ + #endif +#else + // In the absence of a blanket define, try to use compiler versions or + // feature testing macros to selectively enable what we can. + + // Test for noexcept + #ifndef _WEBSOCKETPP_NOEXCEPT_TOKEN_ + #ifdef _WEBSOCKETPP_NOEXCEPT_ + // build system says we have noexcept + #define _WEBSOCKETPP_NOEXCEPT_TOKEN_ noexcept + #else + #if __has_feature(cxx_noexcept) + // clang feature detect says we have noexcept + #define _WEBSOCKETPP_NOEXCEPT_TOKEN_ noexcept + #elif defined(_MSC_VER) && _MSC_VER >= 1900 + // Visual Studio 2015+ has noexcept + #define _WEBSOCKETPP_NOEXCEPT_TOKEN_ noexcept + #else + // assume we don't have noexcept + #define _WEBSOCKETPP_NOEXCEPT_TOKEN_ + #endif + #endif + #endif + + // Test for constexpr + #ifndef _WEBSOCKETPP_CONSTEXPR_TOKEN_ + #ifdef _WEBSOCKETPP_CONSTEXPR_ + // build system says we have constexpr + #define _WEBSOCKETPP_CONSTEXPR_TOKEN_ constexpr + #else + #if __has_feature(cxx_constexpr) + // clang feature detect says we have constexpr + #define _WEBSOCKETPP_CONSTEXPR_TOKEN_ constexpr + #elif defined(_MSC_VER) && _MSC_VER >= 1900 + // Visual Studio 2015+ has constexpr + #define _WEBSOCKETPP_CONSTEXPR_TOKEN_ constexpr + #else + // assume we don't have constexpr + #define _WEBSOCKETPP_CONSTEXPR_TOKEN_ + #endif + #endif + #endif + + // Enable initializer lists on clang when available. + #if __has_feature(cxx_generalized_initializers) && !defined(_WEBSOCKETPP_INITIALIZER_LISTS_) + #define _WEBSOCKETPP_INITIALIZER_LISTS_ + #endif + + // Test for nullptr + #ifndef _WEBSOCKETPP_NULLPTR_TOKEN_ + #ifdef _WEBSOCKETPP_NULLPTR_ + // build system says we have nullptr + #define _WEBSOCKETPP_NULLPTR_TOKEN_ nullptr + #else + #if __has_feature(cxx_nullptr) + // clang feature detect says we have nullptr + #define _WEBSOCKETPP_NULLPTR_TOKEN_ nullptr + #elif defined(_MSC_VER) &&_MSC_VER >= 1600 + // Visual Studio version that has nullptr + #define _WEBSOCKETPP_NULLPTR_TOKEN_ nullptr + #else + // assume we don't have nullptr + #define _WEBSOCKETPP_NULLPTR_TOKEN_ 0 + #endif + #endif + #endif +#endif + +#endif // WEBSOCKETPP_COMMON_CPP11_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/common/functional.hpp b/thirdparty/websocketpp/include/websocketpp/common/functional.hpp new file mode 100644 index 0000000..bd8c9fc --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/common/functional.hpp @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2014, 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_COMMON_FUNCTIONAL_HPP +#define WEBSOCKETPP_COMMON_FUNCTIONAL_HPP + +#include + +// If we've determined that we're in full C++11 mode and the user hasn't +// explicitly disabled the use of C++11 functional header, then prefer it to +// boost. +#if defined _WEBSOCKETPP_CPP11_INTERNAL_ && !defined _WEBSOCKETPP_NO_CPP11_FUNCTIONAL_ + #ifndef _WEBSOCKETPP_CPP11_FUNCTIONAL_ + #define _WEBSOCKETPP_CPP11_FUNCTIONAL_ + #endif +#endif + +// If we're on Visual Studio 2010 or higher and haven't explicitly disabled +// the use of C++11 functional header then prefer it to boost. +#if defined(_MSC_VER) && _MSC_VER >= 1600 && !defined _WEBSOCKETPP_NO_CPP11_FUNCTIONAL_ + #ifndef _WEBSOCKETPP_CPP11_FUNCTIONAL_ + #define _WEBSOCKETPP_CPP11_FUNCTIONAL_ + #endif +#endif + + + +#ifdef _WEBSOCKETPP_CPP11_FUNCTIONAL_ + #include +#else + #include + #include + #include +#endif + + + +namespace websocketpp { +namespace lib { + +#ifdef _WEBSOCKETPP_CPP11_FUNCTIONAL_ + using std::function; + using std::bind; + using std::ref; + namespace placeholders = std::placeholders; + + // There are some cases where a C++11 compiler balks at using std::ref + // but a C++03 compiler using boost function requires boost::ref. As such + // lib::ref is not useful in these cases. Instead this macro allows the use + // of boost::ref in the case of a boost compile or no reference wrapper at + // all in the case of a C++11 compile + #define _WEBSOCKETPP_REF(x) x + + template + void clear_function(T & x) { + x = nullptr; + } +#else + using boost::function; + using boost::bind; + using boost::ref; + namespace placeholders { + /// \todo this feels hacky, is there a better way? + using ::_1; + using ::_2; + using ::_3; + } + + // See above definition for more details on what this is and why it exists + #define _WEBSOCKETPP_REF(x) boost::ref(x) + + template + void clear_function(T & x) { + x.clear(); + } +#endif + +} // namespace lib +} // namespace websocketpp + +#endif // WEBSOCKETPP_COMMON_FUNCTIONAL_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/common/md5.hpp b/thirdparty/websocketpp/include/websocketpp/common/md5.hpp new file mode 100644 index 0000000..9850393 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/common/md5.hpp @@ -0,0 +1,448 @@ +/* + md5.hpp is a reformulation of the md5.h and md5.c code from + http://www.opensource.apple.com/source/cups/cups-59/cups/md5.c to allow it to + function as a component of a header only library. This conversion was done by + Peter Thorson (webmaster@zaphoyd.com) in 2012 for the WebSocket++ project. The + changes are released under the same license as the original (listed below) +*/ +/* + Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.h is L. Peter Deutsch + . Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Removed support for non-ANSI compilers; removed + references to Ghostscript; clarified derivation from RFC 1321; + now handles byte order either statically or dynamically. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5); + added conditionalization for C++ compilation from Martin + Purschke . + 1999-05-03 lpd Original version. + */ + +#ifndef WEBSOCKETPP_COMMON_MD5_HPP +#define WEBSOCKETPP_COMMON_MD5_HPP + +/* + * This package supports both compile-time and run-time determination of CPU + * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be + * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is + * defined as non-zero, the code will be compiled to run only on big-endian + * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to + * run on either big- or little-endian CPUs, but will run slightly less + * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined. + */ + +#include +#include +#include + +namespace websocketpp { +/// Provides MD5 hashing functionality +namespace md5 { + +typedef unsigned char md5_byte_t; /* 8-bit byte */ +typedef unsigned int md5_word_t; /* 32-bit word */ + +/* Define the state of the MD5 Algorithm. */ +typedef struct md5_state_s { + md5_word_t count[2]; /* message length in bits, lsw first */ + md5_word_t abcd[4]; /* digest buffer */ + md5_byte_t buf[64]; /* accumulate block */ +} md5_state_t; + +/* Initialize the algorithm. */ +inline void md5_init(md5_state_t *pms); + +/* Append a string to the message. */ +inline void md5_append(md5_state_t *pms, md5_byte_t const * data, size_t nbytes); + +/* Finish the message and return the digest. */ +inline void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); + +#undef ZSW_MD5_BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */ +#ifdef ARCH_IS_BIG_ENDIAN +# define ZSW_MD5_BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1) +#else +# define ZSW_MD5_BYTE_ORDER 0 +#endif + +#define ZSW_MD5_T_MASK ((md5_word_t)~0) +#define ZSW_MD5_T1 /* 0xd76aa478 */ (ZSW_MD5_T_MASK ^ 0x28955b87) +#define ZSW_MD5_T2 /* 0xe8c7b756 */ (ZSW_MD5_T_MASK ^ 0x173848a9) +#define ZSW_MD5_T3 0x242070db +#define ZSW_MD5_T4 /* 0xc1bdceee */ (ZSW_MD5_T_MASK ^ 0x3e423111) +#define ZSW_MD5_T5 /* 0xf57c0faf */ (ZSW_MD5_T_MASK ^ 0x0a83f050) +#define ZSW_MD5_T6 0x4787c62a +#define ZSW_MD5_T7 /* 0xa8304613 */ (ZSW_MD5_T_MASK ^ 0x57cfb9ec) +#define ZSW_MD5_T8 /* 0xfd469501 */ (ZSW_MD5_T_MASK ^ 0x02b96afe) +#define ZSW_MD5_T9 0x698098d8 +#define ZSW_MD5_T10 /* 0x8b44f7af */ (ZSW_MD5_T_MASK ^ 0x74bb0850) +#define ZSW_MD5_T11 /* 0xffff5bb1 */ (ZSW_MD5_T_MASK ^ 0x0000a44e) +#define ZSW_MD5_T12 /* 0x895cd7be */ (ZSW_MD5_T_MASK ^ 0x76a32841) +#define ZSW_MD5_T13 0x6b901122 +#define ZSW_MD5_T14 /* 0xfd987193 */ (ZSW_MD5_T_MASK ^ 0x02678e6c) +#define ZSW_MD5_T15 /* 0xa679438e */ (ZSW_MD5_T_MASK ^ 0x5986bc71) +#define ZSW_MD5_T16 0x49b40821 +#define ZSW_MD5_T17 /* 0xf61e2562 */ (ZSW_MD5_T_MASK ^ 0x09e1da9d) +#define ZSW_MD5_T18 /* 0xc040b340 */ (ZSW_MD5_T_MASK ^ 0x3fbf4cbf) +#define ZSW_MD5_T19 0x265e5a51 +#define ZSW_MD5_T20 /* 0xe9b6c7aa */ (ZSW_MD5_T_MASK ^ 0x16493855) +#define ZSW_MD5_T21 /* 0xd62f105d */ (ZSW_MD5_T_MASK ^ 0x29d0efa2) +#define ZSW_MD5_T22 0x02441453 +#define ZSW_MD5_T23 /* 0xd8a1e681 */ (ZSW_MD5_T_MASK ^ 0x275e197e) +#define ZSW_MD5_T24 /* 0xe7d3fbc8 */ (ZSW_MD5_T_MASK ^ 0x182c0437) +#define ZSW_MD5_T25 0x21e1cde6 +#define ZSW_MD5_T26 /* 0xc33707d6 */ (ZSW_MD5_T_MASK ^ 0x3cc8f829) +#define ZSW_MD5_T27 /* 0xf4d50d87 */ (ZSW_MD5_T_MASK ^ 0x0b2af278) +#define ZSW_MD5_T28 0x455a14ed +#define ZSW_MD5_T29 /* 0xa9e3e905 */ (ZSW_MD5_T_MASK ^ 0x561c16fa) +#define ZSW_MD5_T30 /* 0xfcefa3f8 */ (ZSW_MD5_T_MASK ^ 0x03105c07) +#define ZSW_MD5_T31 0x676f02d9 +#define ZSW_MD5_T32 /* 0x8d2a4c8a */ (ZSW_MD5_T_MASK ^ 0x72d5b375) +#define ZSW_MD5_T33 /* 0xfffa3942 */ (ZSW_MD5_T_MASK ^ 0x0005c6bd) +#define ZSW_MD5_T34 /* 0x8771f681 */ (ZSW_MD5_T_MASK ^ 0x788e097e) +#define ZSW_MD5_T35 0x6d9d6122 +#define ZSW_MD5_T36 /* 0xfde5380c */ (ZSW_MD5_T_MASK ^ 0x021ac7f3) +#define ZSW_MD5_T37 /* 0xa4beea44 */ (ZSW_MD5_T_MASK ^ 0x5b4115bb) +#define ZSW_MD5_T38 0x4bdecfa9 +#define ZSW_MD5_T39 /* 0xf6bb4b60 */ (ZSW_MD5_T_MASK ^ 0x0944b49f) +#define ZSW_MD5_T40 /* 0xbebfbc70 */ (ZSW_MD5_T_MASK ^ 0x4140438f) +#define ZSW_MD5_T41 0x289b7ec6 +#define ZSW_MD5_T42 /* 0xeaa127fa */ (ZSW_MD5_T_MASK ^ 0x155ed805) +#define ZSW_MD5_T43 /* 0xd4ef3085 */ (ZSW_MD5_T_MASK ^ 0x2b10cf7a) +#define ZSW_MD5_T44 0x04881d05 +#define ZSW_MD5_T45 /* 0xd9d4d039 */ (ZSW_MD5_T_MASK ^ 0x262b2fc6) +#define ZSW_MD5_T46 /* 0xe6db99e5 */ (ZSW_MD5_T_MASK ^ 0x1924661a) +#define ZSW_MD5_T47 0x1fa27cf8 +#define ZSW_MD5_T48 /* 0xc4ac5665 */ (ZSW_MD5_T_MASK ^ 0x3b53a99a) +#define ZSW_MD5_T49 /* 0xf4292244 */ (ZSW_MD5_T_MASK ^ 0x0bd6ddbb) +#define ZSW_MD5_T50 0x432aff97 +#define ZSW_MD5_T51 /* 0xab9423a7 */ (ZSW_MD5_T_MASK ^ 0x546bdc58) +#define ZSW_MD5_T52 /* 0xfc93a039 */ (ZSW_MD5_T_MASK ^ 0x036c5fc6) +#define ZSW_MD5_T53 0x655b59c3 +#define ZSW_MD5_T54 /* 0x8f0ccc92 */ (ZSW_MD5_T_MASK ^ 0x70f3336d) +#define ZSW_MD5_T55 /* 0xffeff47d */ (ZSW_MD5_T_MASK ^ 0x00100b82) +#define ZSW_MD5_T56 /* 0x85845dd1 */ (ZSW_MD5_T_MASK ^ 0x7a7ba22e) +#define ZSW_MD5_T57 0x6fa87e4f +#define ZSW_MD5_T58 /* 0xfe2ce6e0 */ (ZSW_MD5_T_MASK ^ 0x01d3191f) +#define ZSW_MD5_T59 /* 0xa3014314 */ (ZSW_MD5_T_MASK ^ 0x5cfebceb) +#define ZSW_MD5_T60 0x4e0811a1 +#define ZSW_MD5_T61 /* 0xf7537e82 */ (ZSW_MD5_T_MASK ^ 0x08ac817d) +#define ZSW_MD5_T62 /* 0xbd3af235 */ (ZSW_MD5_T_MASK ^ 0x42c50dca) +#define ZSW_MD5_T63 0x2ad7d2bb +#define ZSW_MD5_T64 /* 0xeb86d391 */ (ZSW_MD5_T_MASK ^ 0x14792c6e) + +static void md5_process(md5_state_t *pms, md5_byte_t const * data /*[64]*/) { + md5_word_t + a = pms->abcd[0], b = pms->abcd[1], + c = pms->abcd[2], d = pms->abcd[3]; + md5_word_t t; +#if ZSW_MD5_BYTE_ORDER > 0 + /* Define storage only for big-endian CPUs. */ + md5_word_t X[16]; +#else + /* Define storage for little-endian or both types of CPUs. */ + md5_word_t xbuf[16]; + md5_word_t const * X; +#endif + + { +#if ZSW_MD5_BYTE_ORDER == 0 + /* + * Determine dynamically whether this is a big-endian or + * little-endian machine, since we can use a more efficient + * algorithm on the latter. + */ + static int const w = 1; + + if (*((md5_byte_t const *)&w)) /* dynamic little-endian */ +#endif +#if ZSW_MD5_BYTE_ORDER <= 0 /* little-endian */ + { + /* + * On little-endian machines, we can process properly aligned + * data without copying it. + */ + if (!((data - (md5_byte_t const *)0) & 3)) { + /* data are properly aligned */ + X = (md5_word_t const *)data; + } else { + /* not aligned */ + std::memcpy(xbuf, data, 64); + X = xbuf; + } + } +#endif +#if ZSW_MD5_BYTE_ORDER == 0 + else /* dynamic big-endian */ +#endif +#if ZSW_MD5_BYTE_ORDER >= 0 /* big-endian */ + { + /* + * On big-endian machines, we must arrange the bytes in the + * right order. + */ + const md5_byte_t *xp = data; + int i; + +# if ZSW_MD5_BYTE_ORDER == 0 + X = xbuf; /* (dynamic only) */ +# else +# define xbuf X /* (static only) */ +# endif + for (i = 0; i < 16; ++i, xp += 4) + xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); + } +#endif + } + +#define ZSW_MD5_ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + + /* Round 1. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */ +#define ZSW_MD5_F(x, y, z) (((x) & (y)) | (~(x) & (z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + ZSW_MD5_F(b,c,d) + X[k] + Ti;\ + a = ZSW_MD5_ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 7, ZSW_MD5_T1); + SET(d, a, b, c, 1, 12, ZSW_MD5_T2); + SET(c, d, a, b, 2, 17, ZSW_MD5_T3); + SET(b, c, d, a, 3, 22, ZSW_MD5_T4); + SET(a, b, c, d, 4, 7, ZSW_MD5_T5); + SET(d, a, b, c, 5, 12, ZSW_MD5_T6); + SET(c, d, a, b, 6, 17, ZSW_MD5_T7); + SET(b, c, d, a, 7, 22, ZSW_MD5_T8); + SET(a, b, c, d, 8, 7, ZSW_MD5_T9); + SET(d, a, b, c, 9, 12, ZSW_MD5_T10); + SET(c, d, a, b, 10, 17, ZSW_MD5_T11); + SET(b, c, d, a, 11, 22, ZSW_MD5_T12); + SET(a, b, c, d, 12, 7, ZSW_MD5_T13); + SET(d, a, b, c, 13, 12, ZSW_MD5_T14); + SET(c, d, a, b, 14, 17, ZSW_MD5_T15); + SET(b, c, d, a, 15, 22, ZSW_MD5_T16); +#undef SET + + /* Round 2. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ +#define ZSW_MD5_G(x, y, z) (((x) & (z)) | ((y) & ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + ZSW_MD5_G(b,c,d) + X[k] + Ti;\ + a = ZSW_MD5_ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 1, 5, ZSW_MD5_T17); + SET(d, a, b, c, 6, 9, ZSW_MD5_T18); + SET(c, d, a, b, 11, 14, ZSW_MD5_T19); + SET(b, c, d, a, 0, 20, ZSW_MD5_T20); + SET(a, b, c, d, 5, 5, ZSW_MD5_T21); + SET(d, a, b, c, 10, 9, ZSW_MD5_T22); + SET(c, d, a, b, 15, 14, ZSW_MD5_T23); + SET(b, c, d, a, 4, 20, ZSW_MD5_T24); + SET(a, b, c, d, 9, 5, ZSW_MD5_T25); + SET(d, a, b, c, 14, 9, ZSW_MD5_T26); + SET(c, d, a, b, 3, 14, ZSW_MD5_T27); + SET(b, c, d, a, 8, 20, ZSW_MD5_T28); + SET(a, b, c, d, 13, 5, ZSW_MD5_T29); + SET(d, a, b, c, 2, 9, ZSW_MD5_T30); + SET(c, d, a, b, 7, 14, ZSW_MD5_T31); + SET(b, c, d, a, 12, 20, ZSW_MD5_T32); +#undef SET + + /* Round 3. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ +#define ZSW_MD5_H(x, y, z) ((x) ^ (y) ^ (z)) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + ZSW_MD5_H(b,c,d) + X[k] + Ti;\ + a = ZSW_MD5_ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 5, 4, ZSW_MD5_T33); + SET(d, a, b, c, 8, 11, ZSW_MD5_T34); + SET(c, d, a, b, 11, 16, ZSW_MD5_T35); + SET(b, c, d, a, 14, 23, ZSW_MD5_T36); + SET(a, b, c, d, 1, 4, ZSW_MD5_T37); + SET(d, a, b, c, 4, 11, ZSW_MD5_T38); + SET(c, d, a, b, 7, 16, ZSW_MD5_T39); + SET(b, c, d, a, 10, 23, ZSW_MD5_T40); + SET(a, b, c, d, 13, 4, ZSW_MD5_T41); + SET(d, a, b, c, 0, 11, ZSW_MD5_T42); + SET(c, d, a, b, 3, 16, ZSW_MD5_T43); + SET(b, c, d, a, 6, 23, ZSW_MD5_T44); + SET(a, b, c, d, 9, 4, ZSW_MD5_T45); + SET(d, a, b, c, 12, 11, ZSW_MD5_T46); + SET(c, d, a, b, 15, 16, ZSW_MD5_T47); + SET(b, c, d, a, 2, 23, ZSW_MD5_T48); +#undef SET + + /* Round 4. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ +#define ZSW_MD5_I(x, y, z) ((y) ^ ((x) | ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + ZSW_MD5_I(b,c,d) + X[k] + Ti;\ + a = ZSW_MD5_ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 6, ZSW_MD5_T49); + SET(d, a, b, c, 7, 10, ZSW_MD5_T50); + SET(c, d, a, b, 14, 15, ZSW_MD5_T51); + SET(b, c, d, a, 5, 21, ZSW_MD5_T52); + SET(a, b, c, d, 12, 6, ZSW_MD5_T53); + SET(d, a, b, c, 3, 10, ZSW_MD5_T54); + SET(c, d, a, b, 10, 15, ZSW_MD5_T55); + SET(b, c, d, a, 1, 21, ZSW_MD5_T56); + SET(a, b, c, d, 8, 6, ZSW_MD5_T57); + SET(d, a, b, c, 15, 10, ZSW_MD5_T58); + SET(c, d, a, b, 6, 15, ZSW_MD5_T59); + SET(b, c, d, a, 13, 21, ZSW_MD5_T60); + SET(a, b, c, d, 4, 6, ZSW_MD5_T61); + SET(d, a, b, c, 11, 10, ZSW_MD5_T62); + SET(c, d, a, b, 2, 15, ZSW_MD5_T63); + SET(b, c, d, a, 9, 21, ZSW_MD5_T64); +#undef SET + + /* Then perform the following additions. (That is increment each + of the four registers by the value it had before this block + was started.) */ + pms->abcd[0] += a; + pms->abcd[1] += b; + pms->abcd[2] += c; + pms->abcd[3] += d; +} + +void md5_init(md5_state_t *pms) { + pms->count[0] = pms->count[1] = 0; + pms->abcd[0] = 0x67452301; + pms->abcd[1] = /*0xefcdab89*/ ZSW_MD5_T_MASK ^ 0x10325476; + pms->abcd[2] = /*0x98badcfe*/ ZSW_MD5_T_MASK ^ 0x67452301; + pms->abcd[3] = 0x10325476; +} + +void md5_append(md5_state_t *pms, md5_byte_t const * data, size_t nbytes) { + md5_byte_t const * p = data; + size_t left = nbytes; + int offset = (pms->count[0] >> 3) & 63; + md5_word_t nbits = (md5_word_t)(nbytes << 3); + + if (nbytes <= 0) + return; + + /* Update the message length. */ + pms->count[1] += nbytes >> 29; + pms->count[0] += nbits; + if (pms->count[0] < nbits) + pms->count[1]++; + + /* Process an initial partial block. */ + if (offset) { + int copy = (offset + nbytes > 64 ? 64 - offset : static_cast(nbytes)); + + std::memcpy(pms->buf + offset, p, copy); + if (offset + copy < 64) + return; + p += copy; + left -= copy; + md5_process(pms, pms->buf); + } + + /* Process full blocks. */ + for (; left >= 64; p += 64, left -= 64) + md5_process(pms, p); + + /* Process a final partial block. */ + if (left) + std::memcpy(pms->buf, p, left); +} + +void md5_finish(md5_state_t *pms, md5_byte_t digest[16]) { + static md5_byte_t const pad[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + md5_byte_t data[8]; + int i; + + /* Save the length before padding. */ + for (i = 0; i < 8; ++i) + data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); + /* Pad to 56 bytes mod 64. */ + md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); + /* Append the length. */ + md5_append(pms, data, 8); + for (i = 0; i < 16; ++i) + digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); +} + +// some convenience c++ functions +inline std::string md5_hash_string(std::string const & s) { + char digest[16]; + + md5_state_t state; + + md5_init(&state); + md5_append(&state, (md5_byte_t const *)s.c_str(), s.size()); + md5_finish(&state, (md5_byte_t *)digest); + + std::string ret; + ret.resize(16); + std::copy(digest,digest+16,ret.begin()); + + return ret; +} + +const char hexval[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + +inline std::string md5_hash_hex(std::string const & input) { + std::string hash = md5_hash_string(input); + std::string hex; + + for (size_t i = 0; i < hash.size(); i++) { + hex.push_back(hexval[((hash[i] >> 4) & 0xF)]); + hex.push_back(hexval[(hash[i]) & 0x0F]); + } + + return hex; +} + +} // md5 +} // websocketpp + +#endif // WEBSOCKETPP_COMMON_MD5_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/common/memory.hpp b/thirdparty/websocketpp/include/websocketpp/common/memory.hpp new file mode 100644 index 0000000..3df14a6 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/common/memory.hpp @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2014, 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_COMMON_MEMORY_HPP +#define WEBSOCKETPP_COMMON_MEMORY_HPP + +#include + +// If we've determined that we're in full C++11 mode and the user hasn't +// explicitly disabled the use of C++11 memory header, then prefer it to +// boost. +#if defined _WEBSOCKETPP_CPP11_INTERNAL_ && !defined _WEBSOCKETPP_NO_CPP11_MEMORY_ + #ifndef _WEBSOCKETPP_CPP11_MEMORY_ + #define _WEBSOCKETPP_CPP11_MEMORY_ + #endif +#endif + +// If we're on Visual Studio 2010 or higher and haven't explicitly disabled +// the use of C++11 functional header then prefer it to boost. +#if defined(_MSC_VER) && _MSC_VER >= 1600 && !defined _WEBSOCKETPP_NO_CPP11_MEMORY_ + #ifndef _WEBSOCKETPP_CPP11_MEMORY_ + #define _WEBSOCKETPP_CPP11_MEMORY_ + #endif +#endif + + + +#ifdef _WEBSOCKETPP_CPP11_MEMORY_ + #include +#else + #include + #include + #include + #include + #include +#endif + +namespace websocketpp { +namespace lib { + +#ifdef _WEBSOCKETPP_CPP11_MEMORY_ + using std::shared_ptr; + using std::weak_ptr; + using std::enable_shared_from_this; + using std::static_pointer_cast; + using std::make_shared; + using std::unique_ptr; + + typedef std::unique_ptr unique_ptr_uchar_array; +#else + using boost::shared_ptr; + using boost::weak_ptr; + using std::auto_ptr; + using boost::enable_shared_from_this; + using boost::static_pointer_cast; + using boost::make_shared; + + typedef boost::scoped_array unique_ptr_uchar_array; +#endif + +} // namespace lib +} // namespace websocketpp + +#endif // WEBSOCKETPP_COMMON_MEMORY_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/common/network.hpp b/thirdparty/websocketpp/include/websocketpp/common/network.hpp new file mode 100644 index 0000000..fd9dc6c --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/common/network.hpp @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2014, 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_COMMON_NETWORK_HPP +#define WEBSOCKETPP_COMMON_NETWORK_HPP + +// For ntohs and htons +#if defined(_WIN32) + #include +#else + //#include + #include +#endif + +#include + +namespace websocketpp { +namespace lib { +namespace net { + +inline bool is_little_endian() { + short int val = 0x1; + char *ptr = reinterpret_cast(&val); + return (ptr[0] == 1); +} + +#define TYP_INIT 0 +#define TYP_SMLE 1 +#define TYP_BIGE 2 + +/// Convert 64 bit value to network byte order +/** + * This method is prefixed to avoid conflicts with operating system level + * macros for this functionality. + * + * TODO: figure out if it would be beneficial to use operating system level + * macros for this. + * + * @param src The integer in host byte order + * @return src converted to network byte order + */ +inline uint64_t _htonll(uint64_t src) { + static int typ = TYP_INIT; + unsigned char c; + union { + uint64_t ull; + unsigned char c[8]; + } x; + if (typ == TYP_INIT) { + x.ull = 0x01; + typ = (x.c[7] == 0x01ULL) ? TYP_BIGE : TYP_SMLE; + } + if (typ == TYP_BIGE) + return src; + x.ull = src; + c = x.c[0]; x.c[0] = x.c[7]; x.c[7] = c; + c = x.c[1]; x.c[1] = x.c[6]; x.c[6] = c; + c = x.c[2]; x.c[2] = x.c[5]; x.c[5] = c; + c = x.c[3]; x.c[3] = x.c[4]; x.c[4] = c; + return x.ull; +} + +/// Convert 64 bit value to host byte order +/** + * This method is prefixed to avoid conflicts with operating system level + * macros for this functionality. + * + * TODO: figure out if it would be beneficial to use operating system level + * macros for this. + * + * @param src The integer in network byte order + * @return src converted to host byte order + */ +inline uint64_t _ntohll(uint64_t src) { + return _htonll(src); +} + +} // net +} // lib +} // websocketpp + +#endif // WEBSOCKETPP_COMMON_NETWORK_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/common/platforms.hpp b/thirdparty/websocketpp/include/websocketpp/common/platforms.hpp new file mode 100644 index 0000000..d4e3964 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/common/platforms.hpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2014, 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_COMMON_PLATFORMS_HPP +#define WEBSOCKETPP_COMMON_PLATFORMS_HPP + +/** + * This header contains any platform specific preprocessor adjustments that + * don't fit somewhere else better. + */ + +#if defined(_WIN32) && !defined(NOMINMAX) + // don't define min and max macros that conflict with std::min and std::max + #define NOMINMAX +#endif + +// Bump up the variadic parameter max for Visual Studio 2012 +#if defined(_MSC_VER) && _MSC_VER == 1700 + #define _VARIADIC_MAX 8 +#endif + +#endif // WEBSOCKETPP_COMMON_PLATFORMS_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/common/random.hpp b/thirdparty/websocketpp/include/websocketpp/common/random.hpp new file mode 100644 index 0000000..3ef701c --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/common/random.hpp @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2014, 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_COMMON_RANDOM_DEVICE_HPP +#define WEBSOCKETPP_COMMON_RANDOM_DEVICE_HPP + +#include + +// If we've determined that we're in full C++11 mode and the user hasn't +// explicitly disabled the use of C++11 random header, then prefer it to +// boost. +#if defined _WEBSOCKETPP_CPP11_INTERNAL_ && !defined _WEBSOCKETPP_NO_CPP11_RANDOM_DEVICE_ + #ifndef _WEBSOCKETPP_CPP11_RANDOM_DEVICE_ + #define _WEBSOCKETPP_CPP11_RANDOM_DEVICE_ + #endif +#endif + + +// If we're on Visual Studio 2010 or higher and haven't explicitly disabled +// the use of C++11 random header then prefer it to boost. +#if defined(_MSC_VER) && _MSC_VER >= 1600 && !defined _WEBSOCKETPP_NO_CPP11_MEMORY_ + #ifndef _WEBSOCKETPP_CPP11_MEMORY_ + #define _WEBSOCKETPP_CPP11_MEMORY_ + #endif +#endif + + + +#ifdef _WEBSOCKETPP_CPP11_RANDOM_DEVICE_ + #include +#else + #include + + #if (BOOST_VERSION/100000) == 1 && ((BOOST_VERSION/100)%1000) > 46 + #include + #include + #elif (BOOST_VERSION/100000) == 1 && ((BOOST_VERSION/100)%1000) >= 43 + #include + #else + // TODO: static_assert(false, "Could not find a suitable random_device") + #endif +#endif + +namespace websocketpp { +namespace lib { + +#ifdef _WEBSOCKETPP_CPP11_RANDOM_DEVICE_ + using std::random_device; + using std::uniform_int_distribution; +#else + using boost::random::random_device; + using boost::random::uniform_int_distribution; +#endif + +} // namespace lib +} // namespace websocketpp + +#endif // WEBSOCKETPP_COMMON_RANDOM_DEVICE_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/common/regex.hpp b/thirdparty/websocketpp/include/websocketpp/common/regex.hpp new file mode 100644 index 0000000..2d4a50c --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/common/regex.hpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2014, 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_COMMON_REGEX_HPP +#define WEBSOCKETPP_COMMON_REGEX_HPP + +#if defined _WEBSOCKETPP_CPP11_STL_ && !defined _WEBSOCKETPP_NO_CPP11_REGEX_ + #ifndef _WEBSOCKETPP_CPP11_REGEX_ + #define _WEBSOCKETPP_CPP11_REGEX_ + #endif +#endif + +#ifdef _WEBSOCKETPP_CPP11_REGEX_ + #include +#else + #include +#endif + +namespace websocketpp { +namespace lib { + +#ifdef _WEBSOCKETPP_CPP11_REGEX_ + using std::cmatch; + using std::regex; + using std::regex_match; +#else + using boost::cmatch; + using boost::regex; + using boost::regex_match; +#endif + +} // namespace lib +} // namespace websocketpp + +#endif // WEBSOCKETPP_COMMON_REGEX_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/common/stdint.hpp b/thirdparty/websocketpp/include/websocketpp/common/stdint.hpp new file mode 100644 index 0000000..25790c6 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/common/stdint.hpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2014, 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_COMMON_STDINT_HPP +#define WEBSOCKETPP_COMMON_STDINT_HPP + +#ifndef __STDC_LIMIT_MACROS + #define __STDC_LIMIT_MACROS 1 +#endif + +#if defined (_WIN32) && defined (_MSC_VER) && (_MSC_VER < 1600) + #include + + using boost::int8_t; + using boost::int_least8_t; + using boost::int_fast8_t; + using boost::uint8_t; + using boost::uint_least8_t; + using boost::uint_fast8_t; + + using boost::int16_t; + using boost::int_least16_t; + using boost::int_fast16_t; + using boost::uint16_t; + using boost::uint_least16_t; + using boost::uint_fast16_t; + + using boost::int32_t; + using boost::int_least32_t; + using boost::int_fast32_t; + using boost::uint32_t; + using boost::uint_least32_t; + using boost::uint_fast32_t; + + #ifndef BOOST_NO_INT64_T + using boost::int64_t; + using boost::int_least64_t; + using boost::int_fast64_t; + using boost::uint64_t; + using boost::uint_least64_t; + using boost::uint_fast64_t; + #endif + using boost::intmax_t; + using boost::uintmax_t; +#else + #include +#endif + +#endif // WEBSOCKETPP_COMMON_STDINT_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/common/system_error.hpp b/thirdparty/websocketpp/include/websocketpp/common/system_error.hpp new file mode 100644 index 0000000..3cfcf14 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/common/system_error.hpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2014, 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_COMMON_SYSTEM_ERROR_HPP +#define WEBSOCKETPP_COMMON_SYSTEM_ERROR_HPP + + +#include + +// If we've determined that we're in full C++11 mode and the user hasn't +// explicitly disabled the use of C++11 system_error header, then prefer it to +// boost. +#if defined _WEBSOCKETPP_CPP11_INTERNAL_ && !defined _WEBSOCKETPP_NO_CPP11_SYSTEM_ERROR_ + #ifndef _WEBSOCKETPP_CPP11_SYSTEM_ERROR_ + #define _WEBSOCKETPP_CPP11_SYSTEM_ERROR_ + #endif +#endif + +// If we're on Visual Studio 2010 or higher and haven't explicitly disabled +// the use of C++11 system_error header then prefer it to boost. +#if defined(_MSC_VER) && _MSC_VER >= 1600 && !defined _WEBSOCKETPP_NO_CPP11_SYSTEM_ERROR_ + #ifndef _WEBSOCKETPP_CPP11_SYSTEM_ERROR_ + #define _WEBSOCKETPP_CPP11_SYSTEM_ERROR_ + #endif +#endif + + + +#ifdef _WEBSOCKETPP_CPP11_SYSTEM_ERROR_ + #include +#else + #include + #include +#endif + +namespace websocketpp { +namespace lib { + +#ifdef _WEBSOCKETPP_CPP11_SYSTEM_ERROR_ + using std::errc; + using std::error_code; + using std::error_category; + using std::error_condition; + using std::system_error; + #define _WEBSOCKETPP_ERROR_CODE_ENUM_NS_START_ namespace std { + #define _WEBSOCKETPP_ERROR_CODE_ENUM_NS_END_ } +#else + namespace errc = boost::system::errc; + using boost::system::error_code; + using boost::system::error_category; + using boost::system::error_condition; + using boost::system::system_error; + #define _WEBSOCKETPP_ERROR_CODE_ENUM_NS_START_ namespace boost { namespace system { + #define _WEBSOCKETPP_ERROR_CODE_ENUM_NS_END_ }} +#endif + +} // namespace lib +} // namespace websocketpp + +#endif // WEBSOCKETPP_COMMON_SYSTEM_ERROR_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/common/thread.hpp b/thirdparty/websocketpp/include/websocketpp/common/thread.hpp new file mode 100644 index 0000000..bf0c92c --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/common/thread.hpp @@ -0,0 +1,88 @@ +/* + * 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_COMMON_THREAD_HPP +#define WEBSOCKETPP_COMMON_THREAD_HPP + +#include + +// If we autodetect C++11 and haven't been explicitly instructed to not use +// C++11 threads, then set the defines that instructs the rest of this header +// to use C++11 and +#if defined _WEBSOCKETPP_CPP11_INTERNAL_ && !defined _WEBSOCKETPP_NO_CPP11_THREAD_ + // MinGW by default does not support C++11 thread/mutex so even if the + // internal check for C++11 passes, ignore it if we are on MinGW + #if (!defined(__MINGW32__) && !defined(__MINGW64__)) + #ifndef _WEBSOCKETPP_CPP11_THREAD_ + #define _WEBSOCKETPP_CPP11_THREAD_ + #endif + #endif +#endif + +// If we're on Visual Studio 2013 or higher and haven't explicitly disabled +// the use of C++11 thread header then prefer it to boost. +#if defined(_MSC_VER) && _MSC_VER >= 1800 && !defined _WEBSOCKETPP_NO_CPP11_THREAD_ + #ifndef _WEBSOCKETPP_CPP11_THREAD_ + #define _WEBSOCKETPP_CPP11_THREAD_ + #endif +#endif + +#if defined(_WEBSOCKETPP_MINGW_THREAD_) + #include + #include + #include +#elif defined(_WEBSOCKETPP_CPP11_THREAD_) + #include + #include + #include +#else + #include + #include + #include +#endif + +namespace websocketpp { +namespace lib { + +#if defined(_WEBSOCKETPP_CPP11_THREAD_) || defined(_WEBSOCKETPP_MINGW_THREAD_) + using std::mutex; + using std::lock_guard; + using std::thread; + using std::unique_lock; + using std::condition_variable; +#else + using boost::mutex; + using boost::lock_guard; + using boost::thread; + using boost::unique_lock; + using boost::condition_variable; +#endif + +} // namespace lib +} // namespace websocketpp + +#endif // WEBSOCKETPP_COMMON_THREAD_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/common/time.hpp b/thirdparty/websocketpp/include/websocketpp/common/time.hpp new file mode 100644 index 0000000..a53d86f --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/common/time.hpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2014, 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_COMMON_TIME_HPP +#define WEBSOCKETPP_COMMON_TIME_HPP + +#include + +namespace websocketpp { +namespace lib { + +// Code in this header was inspired by the following article and includes some +// code from the related project g2log. The g2log code is public domain licensed +// http://kjellkod.wordpress.com/2013/01/22/exploring-c11-part-2-localtime-and-time-again/ + +/// Thread safe cross platform localtime +inline std::tm localtime(std::time_t const & time) { + std::tm tm_snapshot; +#if (defined(__MINGW32__) || defined(__MINGW64__)) + memcpy(&tm_snapshot, ::localtime(&time), sizeof(std::tm)); +#elif (defined(WIN32) || defined(_WIN32) || defined(__WIN32__)) + localtime_s(&tm_snapshot, &time); +#else + localtime_r(&time, &tm_snapshot); // POSIX +#endif + return tm_snapshot; +} + +} // lib +} // websocketpp + +#endif // WEBSOCKETPP_COMMON_TIME_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/common/type_traits.hpp b/thirdparty/websocketpp/include/websocketpp/common/type_traits.hpp new file mode 100644 index 0000000..1abc33b --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/common/type_traits.hpp @@ -0,0 +1,65 @@ +/* + * 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_COMMON_TYPE_TRAITS_HPP +#define WEBSOCKETPP_COMMON_TYPE_TRAITS_HPP + +#include + +// If we've determined that we're in full C++11 mode and the user hasn't +// explicitly disabled the use of C++11 functional header, then prefer it to +// boost. +#if defined _WEBSOCKETPP_CPP11_INTERNAL_ && !defined _WEBSOCKETPP_NO_CPP11_TYPE_TRAITS_ + #ifndef _WEBSOCKETPP_CPP11_TYPE_TRAITS_ + #define _WEBSOCKETPP_CPP11_TYPE_TRAITS_ + #endif +#endif + + +#ifdef _WEBSOCKETPP_CPP11_TYPE_TRAITS_ + #include +#else + #include +#endif + + + +namespace websocketpp { +namespace lib { + +#ifdef _WEBSOCKETPP_CPP11_TYPE_TRAITS_ + using std::aligned_storage; + using std::is_same; +#else + using boost::aligned_storage; + using boost::is_same; +#endif + +} // namespace lib +} // namespace websocketpp + +#endif // WEBSOCKETPP_COMMON_TYPE_TRAITS_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/concurrency/basic.hpp b/thirdparty/websocketpp/include/websocketpp/concurrency/basic.hpp new file mode 100644 index 0000000..e4a146b --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/concurrency/basic.hpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2014, 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_CONCURRENCY_BASIC_HPP +#define WEBSOCKETPP_CONCURRENCY_BASIC_HPP + +#include + +namespace websocketpp { +namespace concurrency { + +/// Concurrency policy that uses std::mutex / boost::mutex +class basic { +public: + typedef lib::mutex mutex_type; + typedef lib::lock_guard scoped_lock_type; +}; + +} // namespace concurrency +} // namespace websocketpp + +#endif // WEBSOCKETPP_CONCURRENCY_BASIC_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/concurrency/none.hpp b/thirdparty/websocketpp/include/websocketpp/concurrency/none.hpp new file mode 100644 index 0000000..2200a22 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/concurrency/none.hpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2014, 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_CONCURRENCY_NONE_HPP +#define WEBSOCKETPP_CONCURRENCY_NONE_HPP + +namespace websocketpp { + +/// Concurrency handling support +namespace concurrency { + +/// Implementation for no-op locking primitives +namespace none_impl { +/// A fake mutex implementation that does nothing +class fake_mutex { +public: + fake_mutex() {} + ~fake_mutex() {} +}; + +/// A fake lock guard implementation that does nothing +class fake_lock_guard { +public: + explicit fake_lock_guard(fake_mutex) {} + ~fake_lock_guard() {} +}; +} // namespace none_impl + +/// Stub concurrency policy that implements the interface using no-ops. +/** + * This policy documents the concurrency policy interface using no-ops. It can + * be used as a reference or base for building a new concurrency policy. It can + * also be used as is to disable all locking for endpoints used in purely single + * threaded programs. + */ +class none { +public: + /// The type of a mutex primitive + /** + * std::mutex is an example. + */ + typedef none_impl::fake_mutex mutex_type; + + /// The type of a scoped/RAII lock primitive. + /** + * The scoped lock constructor should take a mutex_type as a parameter, + * acquire that lock, and release it in its destructor. std::lock_guard is + * an example. + */ + typedef none_impl::fake_lock_guard scoped_lock_type; +}; + +} // namespace concurrency +} // namespace websocketpp + +#endif // WEBSOCKETPP_CONCURRENCY_ASYNC_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/config/asio.hpp b/thirdparty/websocketpp/include/websocketpp/config/asio.hpp new file mode 100644 index 0000000..ae7e303 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/config/asio.hpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2014, 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_CONFIG_ASIO_TLS_HPP +#define WEBSOCKETPP_CONFIG_ASIO_TLS_HPP + +#include +#include +#include + +// Pull in non-tls config +#include + +// Define TLS config +namespace websocketpp { +namespace config { + +/// Server config with asio transport and TLS enabled +struct asio_tls : public core { + typedef asio_tls type; + typedef core base; + + typedef base::concurrency_type concurrency_type; + + typedef base::request_type request_type; + typedef base::response_type response_type; + + typedef base::message_type message_type; + typedef base::con_msg_manager_type con_msg_manager_type; + typedef base::endpoint_msg_manager_type endpoint_msg_manager_type; + + typedef base::alog_type alog_type; + typedef base::elog_type elog_type; + + typedef base::rng_type rng_type; + + struct transport_config : public base::transport_config { + typedef type::concurrency_type concurrency_type; + typedef type::alog_type alog_type; + typedef type::elog_type elog_type; + typedef type::request_type request_type; + typedef type::response_type response_type; + typedef websocketpp::transport::asio::tls_socket::endpoint socket_type; + }; + + typedef websocketpp::transport::asio::endpoint + transport_type; +}; + +} // namespace config +} // namespace websocketpp + +#endif // WEBSOCKETPP_CONFIG_ASIO_TLS_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/config/asio_client.hpp b/thirdparty/websocketpp/include/websocketpp/config/asio_client.hpp new file mode 100644 index 0000000..32a5425 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/config/asio_client.hpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2014, 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_CONFIG_ASIO_TLS_CLIENT_HPP +#define WEBSOCKETPP_CONFIG_ASIO_TLS_CLIENT_HPP + +#include +#include +#include + +// Pull in non-tls config +#include + +// Define TLS config +namespace websocketpp { +namespace config { + +/// Client config with asio transport and TLS enabled +struct asio_tls_client : public core_client { + typedef asio_tls_client type; + typedef core_client base; + + typedef base::concurrency_type concurrency_type; + + typedef base::request_type request_type; + typedef base::response_type response_type; + + typedef base::message_type message_type; + typedef base::con_msg_manager_type con_msg_manager_type; + typedef base::endpoint_msg_manager_type endpoint_msg_manager_type; + + typedef base::alog_type alog_type; + typedef base::elog_type elog_type; + + typedef base::rng_type rng_type; + + struct transport_config : public base::transport_config { + typedef type::concurrency_type concurrency_type; + typedef type::alog_type alog_type; + typedef type::elog_type elog_type; + typedef type::request_type request_type; + typedef type::response_type response_type; + typedef websocketpp::transport::asio::tls_socket::endpoint socket_type; + }; + + typedef websocketpp::transport::asio::endpoint + transport_type; +}; + +} // namespace config +} // namespace websocketpp + +#endif // WEBSOCKETPP_CONFIG_ASIO_TLS_CLIENT_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/config/asio_no_tls.hpp b/thirdparty/websocketpp/include/websocketpp/config/asio_no_tls.hpp new file mode 100644 index 0000000..4fc8588 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/config/asio_no_tls.hpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2014, 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_CONFIG_ASIO_HPP +#define WEBSOCKETPP_CONFIG_ASIO_HPP + +#include +#include + +namespace websocketpp { +namespace config { + +/// Server config with asio transport and TLS disabled +struct asio : public core { + typedef asio type; + typedef core base; + + typedef base::concurrency_type concurrency_type; + + typedef base::request_type request_type; + typedef base::response_type response_type; + + typedef base::message_type message_type; + typedef base::con_msg_manager_type con_msg_manager_type; + typedef base::endpoint_msg_manager_type endpoint_msg_manager_type; + + typedef base::alog_type alog_type; + typedef base::elog_type elog_type; + + typedef base::rng_type rng_type; + + struct transport_config : public base::transport_config { + typedef type::concurrency_type concurrency_type; + typedef type::alog_type alog_type; + typedef type::elog_type elog_type; + typedef type::request_type request_type; + typedef type::response_type response_type; + typedef websocketpp::transport::asio::basic_socket::endpoint + socket_type; + }; + + typedef websocketpp::transport::asio::endpoint + transport_type; +}; + +} // namespace config +} // namespace websocketpp + +#endif // WEBSOCKETPP_CONFIG_ASIO_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/config/asio_no_tls_client.hpp b/thirdparty/websocketpp/include/websocketpp/config/asio_no_tls_client.hpp new file mode 100644 index 0000000..31d66b7 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/config/asio_no_tls_client.hpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2014, 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_CONFIG_ASIO_CLIENT_HPP +#define WEBSOCKETPP_CONFIG_ASIO_CLIENT_HPP + +#include +#include + +namespace websocketpp { +namespace config { + +/// Client config with asio transport and TLS disabled +struct asio_client : public core_client { + typedef asio_client type; + typedef core_client base; + + typedef base::concurrency_type concurrency_type; + + typedef base::request_type request_type; + typedef base::response_type response_type; + + typedef base::message_type message_type; + typedef base::con_msg_manager_type con_msg_manager_type; + typedef base::endpoint_msg_manager_type endpoint_msg_manager_type; + + typedef base::alog_type alog_type; + typedef base::elog_type elog_type; + + typedef base::rng_type rng_type; + + struct transport_config : public base::transport_config { + typedef type::concurrency_type concurrency_type; + typedef type::alog_type alog_type; + typedef type::elog_type elog_type; + typedef type::request_type request_type; + typedef type::response_type response_type; + typedef websocketpp::transport::asio::basic_socket::endpoint + socket_type; + }; + + typedef websocketpp::transport::asio::endpoint + transport_type; +}; + +} // namespace config +} // namespace websocketpp + +#endif // WEBSOCKETPP_CONFIG_ASIO_CLIENT_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/config/boost_config.hpp b/thirdparty/websocketpp/include/websocketpp/config/boost_config.hpp new file mode 100644 index 0000000..b199f13 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/config/boost_config.hpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2014, 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. + * + */ + + // This header defines WebSocket++ macros for C++11 compatibility based on the + // Boost.Config library. This will correctly configure most target platforms + // simply by including this header before any other WebSocket++ header. + +#ifndef WEBSOCKETPP_CONFIG_BOOST_CONFIG_HPP +#define WEBSOCKETPP_CONFIG_BOOST_CONFIG_HPP + +#include + +// _WEBSOCKETPP_CPP11_MEMORY_ and _WEBSOCKETPP_CPP11_FUNCTIONAL_ presently +// only work if either both or neither is defined. +#if !defined BOOST_NO_CXX11_SMART_PTR && !defined BOOST_NO_CXX11_HDR_FUNCTIONAL + #define _WEBSOCKETPP_CPP11_MEMORY_ + #define _WEBSOCKETPP_CPP11_FUNCTIONAL_ +#endif + +#ifdef BOOST_ASIO_HAS_STD_CHRONO + #define _WEBSOCKETPP_CPP11_CHRONO_ +#endif + +#ifndef BOOST_NO_CXX11_HDR_RANDOM + #define _WEBSOCKETPP_CPP11_RANDOM_DEVICE_ +#endif + +#ifndef BOOST_NO_CXX11_HDR_REGEX + #define _WEBSOCKETPP_CPP11_REGEX_ +#endif + +#ifndef BOOST_NO_CXX11_HDR_SYSTEM_ERROR + #define _WEBSOCKETPP_CPP11_SYSTEM_ERROR_ +#endif + +#ifndef BOOST_NO_CXX11_HDR_THREAD + #define _WEBSOCKETPP_CPP11_THREAD_ +#endif + +#ifndef BOOST_NO_CXX11_HDR_INITIALIZER_LIST + #define _WEBSOCKETPP_INITIALIZER_LISTS_ +#endif + +#define _WEBSOCKETPP_NOEXCEPT_TOKEN_ BOOST_NOEXCEPT +#define _WEBSOCKETPP_CONSTEXPR_TOKEN_ BOOST_CONSTEXPR +// TODO: nullptr support + +#endif // WEBSOCKETPP_CONFIG_BOOST_CONFIG_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/config/core.hpp b/thirdparty/websocketpp/include/websocketpp/config/core.hpp new file mode 100644 index 0000000..5f1160e --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/config/core.hpp @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2014, 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_CONFIG_CORE_HPP +#define WEBSOCKETPP_CONFIG_CORE_HPP + +// Non-Policy common stuff +#include +#include +#include + +// Concurrency +#include + +// Transport +#include + +// HTTP +#include +#include + +// Messages +#include +#include + +// Loggers +#include +#include + +// RNG +#include + +// User stub base classes +#include +#include + +// Extensions +#include + +namespace websocketpp { +namespace config { + +/// Server config with iostream transport +struct core { + typedef core type; + + // Concurrency policy + typedef websocketpp::concurrency::basic concurrency_type; + + // HTTP Parser Policies + typedef http::parser::request request_type; + typedef http::parser::response response_type; + + // Message Policies + typedef message_buffer::message + message_type; + typedef message_buffer::alloc::con_msg_manager + con_msg_manager_type; + typedef message_buffer::alloc::endpoint_msg_manager + endpoint_msg_manager_type; + + /// Logging policies + typedef websocketpp::log::basic elog_type; + typedef websocketpp::log::basic alog_type; + + /// RNG policies + typedef websocketpp::random::none::int_generator rng_type; + + /// Controls compile time enabling/disabling of thread syncronization + /// code Disabling can provide a minor performance improvement to single + /// threaded applications + static bool const enable_multithreading = true; + + struct transport_config { + typedef type::concurrency_type concurrency_type; + typedef type::elog_type elog_type; + typedef type::alog_type alog_type; + typedef type::request_type request_type; + typedef type::response_type response_type; + + /// Controls compile time enabling/disabling of thread syncronization + /// code Disabling can provide a minor performance improvement to single + /// threaded applications + static bool const enable_multithreading = true; + + /// Default timer values (in ms) + + /// Length of time to wait for socket pre-initialization + /** + * Exactly what this includes depends on the socket policy in use + */ + static const long timeout_socket_pre_init = 5000; + + /// Length of time to wait before a proxy handshake is aborted + static const long timeout_proxy = 5000; + + /// Length of time to wait for socket post-initialization + /** + * Exactly what this includes depends on the socket policy in use. + * Often this means the TLS handshake + */ + static const long timeout_socket_post_init = 5000; + + /// Length of time to wait for dns resolution + static const long timeout_dns_resolve = 5000; + + /// Length of time to wait for TCP connect + static const long timeout_connect = 5000; + + /// Length of time to wait for socket shutdown + static const long timeout_socket_shutdown = 5000; + }; + + /// Transport Endpoint Component + typedef websocketpp::transport::iostream::endpoint + transport_type; + + /// User overridable Endpoint base class + typedef websocketpp::endpoint_base endpoint_base; + /// User overridable Connection base class + typedef websocketpp::connection_base connection_base; + + /// Default timer values (in ms) + + /// Length of time before an opening handshake is aborted + static const long timeout_open_handshake = 5000; + /// Length of time before a closing handshake is aborted + static const long timeout_close_handshake = 5000; + /// Length of time to wait for a pong after a ping + static const long timeout_pong = 5000; + + /// WebSocket Protocol version to use as a client + /** + * What version of the WebSocket Protocol to use for outgoing client + * connections. Setting this to a value other than 13 (RFC6455) is not + * recommended. + */ + static const int client_version = 13; // RFC6455 + + /// Default static error logging channels + /** + * Which error logging channels to enable at compile time. Channels not + * enabled here will be unable to be selected by programs using the library. + * This option gives an optimizing compiler the ability to remove entirely + * code to test whether or not to print out log messages on a certain + * channel + * + * Default is all except for development/debug level errors + */ + static const websocketpp::log::level elog_level = + websocketpp::log::elevel::all ^ websocketpp::log::elevel::devel; + + /// Default static access logging channels + /** + * Which access logging channels to enable at compile time. Channels not + * enabled here will be unable to be selected by programs using the library. + * This option gives an optimizing compiler the ability to remove entirely + * code to test whether or not to print out log messages on a certain + * channel + * + * Default is all except for development/debug level access messages + */ + static const websocketpp::log::level alog_level = + websocketpp::log::alevel::all ^ websocketpp::log::alevel::devel; + + /// Size of the per-connection read buffer + /** + * Each connection has an internal buffer of this size. A larger value will + * result in fewer trips through the library and less CPU overhead at the + * expense of increased memory usage per connection. + * + * If your application primarily deals in very large messages you may want + * to try setting this value higher. + * + * If your application has a lot of connections or primarily deals in small + * messages you may want to try setting this smaller. + */ + static const size_t connection_read_buffer_size = 16384; + + /// Drop connections immediately on protocol error. + /** + * Drop connections on protocol error rather than sending a close frame. + * Off by default. This may result in legit messages near the error being + * dropped as well. It may free up resources otherwise spent dealing with + * misbehaving clients. + */ + static const bool drop_on_protocol_error = false; + + /// Suppresses the return of detailed connection close information + /** + * Silence close suppresses the return of detailed connection close + * information during the closing handshake. This information is useful + * for debugging and presenting useful errors to end users but may be + * undesirable for security reasons in some production environments. + * Close reasons could be used by an attacker to confirm that the endpoint + * is out of resources or be used to identify the WebSocket implementation + * in use. + * + * Note: this will suppress *all* close codes, including those explicitly + * sent by local applications. + */ + static const bool silent_close = false; + + /// Default maximum message size + /** + * Default value for the processor's maximum message size. Maximum message size + * determines the point at which the library will fail a connection with the + * message_too_big protocol error. + * + * The default is 32MB + * + * @since 0.3.0 + */ + static const size_t max_message_size = 32000000; + + /// Default maximum http body size + /** + * Default value for the http parser's maximum body size. Maximum body size + * determines the point at which the library will abort reading an HTTP + * connection with the 413/request entity too large error. + * + * The default is 32MB + * + * @since 0.5.0 + */ + static const size_t max_http_body_size = 32000000; + + /// Global flag for enabling/disabling extensions + static const bool enable_extensions = true; + + /// Extension specific settings: + + /// permessage_compress extension + struct permessage_deflate_config { + typedef core::request_type request_type; + + /// If the remote endpoint requests that we reset the compression + /// context after each message should we honor the request? + static const bool allow_disabling_context_takeover = true; + + /// If the remote endpoint requests that we reduce the size of the + /// LZ77 sliding window size this is the lowest value that will be + /// allowed. Values range from 8 to 15. A value of 8 means we will + /// allow any possible window size. A value of 15 means do not allow + /// negotiation of the window size (ie require the default). + static const uint8_t minimum_outgoing_window_bits = 8; + }; + + typedef websocketpp::extensions::permessage_deflate::disabled + permessage_deflate_type; + + /// Autonegotiate permessage-deflate + /** + * Automatically enables the permessage-deflate extension. + * + * For clients this results in a permessage-deflate extension request being + * sent with every request rather than requiring it to be requested manually + * + * For servers this results in accepting the first set of extension settings + * requested by the client that we understand being used. The alternative is + * requiring the extension to be manually negotiated in `validate`. With + * auto-negotiate on, you may still override the auto-negotiate manually if + * needed. + */ + //static const bool autonegotiate_compression = false; +}; + +} // namespace config +} // namespace websocketpp + +#endif // WEBSOCKETPP_CONFIG_CORE_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/config/core_client.hpp b/thirdparty/websocketpp/include/websocketpp/config/core_client.hpp new file mode 100644 index 0000000..5854e04 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/config/core_client.hpp @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2014, 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_CONFIG_CORE_CLIENT_HPP +#define WEBSOCKETPP_CONFIG_CORE_CLIENT_HPP + +// Non-Policy common stuff +#include +#include +#include + +// Concurrency +#ifndef _WEBSOCKETPP_NO_THREADING_ +#include +#else +#include +#endif + +// Transport +#include + +// HTTP +#include +#include + +// Messages +#include +#include + +// Loggers +#include + +// RNG +#include + +// User stub base classes +#include +#include + +// Extensions +#include + +namespace websocketpp { +namespace config { + +/// Client config with iostream transport +struct core_client { + typedef core_client type; + + // Concurrency policy +#ifndef _WEBSOCKETPP_NO_THREADING_ + typedef websocketpp::concurrency::basic concurrency_type; +#else + typedef websocketpp::concurrency::none concurrency_type; +#endif + + // HTTP Parser Policies + typedef http::parser::request request_type; + typedef http::parser::response response_type; + + // Message Policies + typedef message_buffer::message + message_type; + typedef message_buffer::alloc::con_msg_manager + con_msg_manager_type; + typedef message_buffer::alloc::endpoint_msg_manager + endpoint_msg_manager_type; + + /// Logging policies + typedef websocketpp::log::basic elog_type; + typedef websocketpp::log::basic alog_type; + + /// RNG policies + typedef websocketpp::random::random_device::int_generator rng_type; + + /// Controls compile time enabling/disabling of thread syncronization code + /// Disabling can provide a minor performance improvement to single threaded + /// applications + static bool const enable_multithreading = true; + + struct transport_config { + typedef type::concurrency_type concurrency_type; + typedef type::elog_type elog_type; + typedef type::alog_type alog_type; + typedef type::request_type request_type; + typedef type::response_type response_type; + + /// Controls compile time enabling/disabling of thread syncronization + /// code Disabling can provide a minor performance improvement to single + /// threaded applications + static bool const enable_multithreading = true; + + /// Default timer values (in ms) + + /// Length of time to wait for socket pre-initialization + /** + * Exactly what this includes depends on the socket policy in use + */ + static const long timeout_socket_pre_init = 5000; + + /// Length of time to wait before a proxy handshake is aborted + static const long timeout_proxy = 5000; + + /// Length of time to wait for socket post-initialization + /** + * Exactly what this includes depends on the socket policy in use. + * Often this means the TLS handshake + */ + static const long timeout_socket_post_init = 5000; + + /// Length of time to wait for dns resolution + static const long timeout_dns_resolve = 5000; + + /// Length of time to wait for TCP connect + static const long timeout_connect = 5000; + + /// Length of time to wait for socket shutdown + static const long timeout_socket_shutdown = 5000; + }; + + /// Transport Endpoint Component + typedef websocketpp::transport::iostream::endpoint + transport_type; + + /// User overridable Endpoint base class + typedef websocketpp::endpoint_base endpoint_base; + /// User overridable Connection base class + typedef websocketpp::connection_base connection_base; + + /// Default timer values (in ms) + + /// Length of time before an opening handshake is aborted + static const long timeout_open_handshake = 5000; + /// Length of time before a closing handshake is aborted + static const long timeout_close_handshake = 5000; + /// Length of time to wait for a pong after a ping + static const long timeout_pong = 5000; + + /// WebSocket Protocol version to use as a client + /** + * What version of the WebSocket Protocol to use for outgoing client + * connections. Setting this to a value other than 13 (RFC6455) is not + * recommended. + */ + static const int client_version = 13; // RFC6455 + + /// Default static error logging channels + /** + * Which error logging channels to enable at compile time. Channels not + * enabled here will be unable to be selected by programs using the library. + * This option gives an optimizing compiler the ability to remove entirely + * code to test whether or not to print out log messages on a certain + * channel + * + * Default is all except for development/debug level errors + */ + static const websocketpp::log::level elog_level = + websocketpp::log::elevel::all ^ websocketpp::log::elevel::devel; + + /// Default static access logging channels + /** + * Which access logging channels to enable at compile time. Channels not + * enabled here will be unable to be selected by programs using the library. + * This option gives an optimizing compiler the ability to remove entirely + * code to test whether or not to print out log messages on a certain + * channel + * + * Default is all except for development/debug level access messages + */ + static const websocketpp::log::level alog_level = + websocketpp::log::alevel::all ^ websocketpp::log::alevel::devel; + + /// + static const size_t connection_read_buffer_size = 16384; + + /// Drop connections immediately on protocol error. + /** + * Drop connections on protocol error rather than sending a close frame. + * Off by default. This may result in legit messages near the error being + * dropped as well. It may free up resources otherwise spent dealing with + * misbehaving clients. + */ + static const bool drop_on_protocol_error = false; + + /// Suppresses the return of detailed connection close information + /** + * Silence close suppresses the return of detailed connection close + * information during the closing handshake. This information is useful + * for debugging and presenting useful errors to end users but may be + * undesirable for security reasons in some production environments. + * Close reasons could be used by an attacker to confirm that the endpoint + * is out of resources or be used to identify the WebSocket implementation + * in use. + * + * Note: this will suppress *all* close codes, including those explicitly + * sent by local applications. + */ + static const bool silent_close = false; + + /// Default maximum message size + /** + * Default value for the processor's maximum message size. Maximum message size + * determines the point at which the library will fail a connection with the + * message_too_big protocol error. + * + * The default is 32MB + * + * @since 0.3.0 + */ + static const size_t max_message_size = 32000000; + + /// Default maximum http body size + /** + * Default value for the http parser's maximum body size. Maximum body size + * determines the point at which the library will abort reading an HTTP + * connection with the 413/request entity too large error. + * + * The default is 32MB + * + * @since 0.5.0 + */ + static const size_t max_http_body_size = 32000000; + + /// Global flag for enabling/disabling extensions + static const bool enable_extensions = true; + + /// Extension specific settings: + + /// permessage_deflate extension + struct permessage_deflate_config { + typedef core_client::request_type request_type; + + /// If the remote endpoint requests that we reset the compression + /// context after each message should we honor the request? + static const bool allow_disabling_context_takeover = true; + + /// If the remote endpoint requests that we reduce the size of the + /// LZ77 sliding window size this is the lowest value that will be + /// allowed. Values range from 8 to 15. A value of 8 means we will + /// allow any possible window size. A value of 15 means do not allow + /// negotiation of the window size (ie require the default). + static const uint8_t minimum_outgoing_window_bits = 8; + }; + + typedef websocketpp::extensions::permessage_deflate::disabled + permessage_deflate_type; + + /// Autonegotiate permessage-compress + /** + * Automatically enables the permessage-compress extension. + * + * For clients this results in a permessage-compress extension request being + * sent with every request rather than requiring it to be requested manually + * + * For servers this results in accepting the first set of extension settings + * requested by the client that we understand being used. The alternative is + * requiring the extension to be manually negotiated in `validate`. With + * auto-negotiate on, you may still override the auto-negotiate manually if + * needed. + */ + //static const bool autonegotiate_compression = false; +}; + +} // namespace config +} // namespace websocketpp + +#endif // WEBSOCKETPP_CONFIG_CORE_CLIENT_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/config/debug.hpp b/thirdparty/websocketpp/include/websocketpp/config/debug.hpp new file mode 100644 index 0000000..ce75b5c --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/config/debug.hpp @@ -0,0 +1,286 @@ +/* + * Copyright (c) 2014, 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_CONFIG_DEBUG_HPP +#define WEBSOCKETPP_CONFIG_DEBUG_HPP + + + +// Non-Policy common stuff +#include +#include + +// Concurrency +#include + +// Transport +#include + +// HTTP +#include +#include + +// Messages +#include +#include + +// Loggers +#include + +// RNG +#include + +// User stub base classes +#include +#include + +// Extensions +#include + +namespace websocketpp { +namespace config { + +/// Client/Server debug config with iostream transport +struct debug_core { + typedef debug_core type; + + // Concurrency policy + typedef websocketpp::concurrency::basic concurrency_type; + + // HTTP Parser Policies + typedef http::parser::request request_type; + typedef http::parser::response response_type; + + // Message Policies + typedef message_buffer::message + message_type; + typedef message_buffer::alloc::con_msg_manager + con_msg_manager_type; + typedef message_buffer::alloc::endpoint_msg_manager + endpoint_msg_manager_type; + + /// Logging policies + typedef websocketpp::log::basic elog_type; + typedef websocketpp::log::basic alog_type; + + /// RNG policies + typedef websocketpp::random::none::int_generator rng_type; + + /// Controls compile time enabling/disabling of thread syncronization + /// code Disabling can provide a minor performance improvement to single + /// threaded applications + static bool const enable_multithreading = true; + + struct transport_config { + typedef type::concurrency_type concurrency_type; + typedef type::elog_type elog_type; + typedef type::alog_type alog_type; + typedef type::request_type request_type; + typedef type::response_type response_type; + + /// Controls compile time enabling/disabling of thread syncronization + /// code Disabling can provide a minor performance improvement to single + /// threaded applications + static bool const enable_multithreading = true; + + /// Default timer values (in ms) + + /// Length of time to wait for socket pre-initialization + /** + * Exactly what this includes depends on the socket policy in use + */ + static const long timeout_socket_pre_init = 5000; + + /// Length of time to wait before a proxy handshake is aborted + static const long timeout_proxy = 5000; + + /// Length of time to wait for socket post-initialization + /** + * Exactly what this includes depends on the socket policy in use. + * Often this means the TLS handshake + */ + static const long timeout_socket_post_init = 5000; + + /// Length of time to wait for dns resolution + static const long timeout_dns_resolve = 5000; + + /// Length of time to wait for TCP connect + static const long timeout_connect = 5000; + + /// Length of time to wait for socket shutdown + static const long timeout_socket_shutdown = 5000; + }; + + /// Transport Endpoint Component + typedef websocketpp::transport::iostream::endpoint + transport_type; + + /// User overridable Endpoint base class + typedef websocketpp::endpoint_base endpoint_base; + /// User overridable Connection base class + typedef websocketpp::connection_base connection_base; + + /// Default timer values (in ms) + + /// Length of time before an opening handshake is aborted + static const long timeout_open_handshake = 5000; + /// Length of time before a closing handshake is aborted + static const long timeout_close_handshake = 5000; + /// Length of time to wait for a pong after a ping + static const long timeout_pong = 5000; + + /// WebSocket Protocol version to use as a client + /** + * What version of the WebSocket Protocol to use for outgoing client + * connections. Setting this to a value other than 13 (RFC6455) is not + * recommended. + */ + static const int client_version = 13; // RFC6455 + + /// Default static error logging channels + /** + * Which error logging channels to enable at compile time. Channels not + * enabled here will be unable to be selected by programs using the library. + * This option gives an optimizing compiler the ability to remove entirely + * code to test whether or not to print out log messages on a certain + * channel + * + * Default is all except for development/debug level errors + */ + static const websocketpp::log::level elog_level = + websocketpp::log::elevel::all; + + /// Default static access logging channels + /** + * Which access logging channels to enable at compile time. Channels not + * enabled here will be unable to be selected by programs using the library. + * This option gives an optimizing compiler the ability to remove entirely + * code to test whether or not to print out log messages on a certain + * channel + * + * Default is all except for development/debug level access messages + */ + static const websocketpp::log::level alog_level = + websocketpp::log::alevel::all; + + /// + static const size_t connection_read_buffer_size = 16384; + + /// Drop connections immediately on protocol error. + /** + * Drop connections on protocol error rather than sending a close frame. + * Off by default. This may result in legit messages near the error being + * dropped as well. It may free up resources otherwise spent dealing with + * misbehaving clients. + */ + static const bool drop_on_protocol_error = false; + + /// Suppresses the return of detailed connection close information + /** + * Silence close suppresses the return of detailed connection close + * information during the closing handshake. This information is useful + * for debugging and presenting useful errors to end users but may be + * undesirable for security reasons in some production environments. + * Close reasons could be used by an attacker to confirm that the endpoint + * is out of resources or be used to identify the WebSocket implementation + * in use. + * + * Note: this will suppress *all* close codes, including those explicitly + * sent by local applications. + */ + static const bool silent_close = false; + + /// Default maximum message size + /** + * Default value for the processor's maximum message size. Maximum message size + * determines the point at which the library will fail a connection with the + * message_too_big protocol error. + * + * The default is 32MB + * + * @since 0.3.0 + */ + static const size_t max_message_size = 32000000; + + /// Default maximum http body size + /** + * Default value for the http parser's maximum body size. Maximum body size + * determines the point at which the library will abort reading an HTTP + * connection with the 413/request entity too large error. + * + * The default is 32MB + * + * @since 0.5.0 + */ + static const size_t max_http_body_size = 32000000; + + /// Global flag for enabling/disabling extensions + static const bool enable_extensions = true; + + /// Extension specific settings: + + /// permessage_compress extension + struct permessage_deflate_config { + typedef type::request_type request_type; + + /// If the remote endpoint requests that we reset the compression + /// context after each message should we honor the request? + static const bool allow_disabling_context_takeover = true; + + /// If the remote endpoint requests that we reduce the size of the + /// LZ77 sliding window size this is the lowest value that will be + /// allowed. Values range from 8 to 15. A value of 8 means we will + /// allow any possible window size. A value of 15 means do not allow + /// negotiation of the window size (ie require the default). + static const uint8_t minimum_outgoing_window_bits = 8; + }; + + typedef websocketpp::extensions::permessage_deflate::disabled + permessage_deflate_type; + + /// Autonegotiate permessage-deflate + /** + * Automatically enables the permessage-deflate extension. + * + * For clients this results in a permessage-deflate extension request being + * sent with every request rather than requiring it to be requested manually + * + * For servers this results in accepting the first set of extension settings + * requested by the client that we understand being used. The alternative is + * requiring the extension to be manually negotiated in `validate`. With + * auto-negotiate on, you may still override the auto-negotiate manually if + * needed. + */ + //static const bool autonegotiate_compression = false; +}; + +} // namespace config +} // namespace websocketpp + +#endif // WEBSOCKETPP_CONFIG_CORE_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/config/debug_asio.hpp b/thirdparty/websocketpp/include/websocketpp/config/debug_asio.hpp new file mode 100644 index 0000000..4b7bf86 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/config/debug_asio.hpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2014, 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_CONFIG_ASIO_TLS_DEBUG_HPP +#define WEBSOCKETPP_CONFIG_ASIO_TLS_DEBUG_HPP + +#include +#include +#include + +// Pull in non-tls config +#include + +// Define TLS config +namespace websocketpp { +namespace config { + +/// Client/Server debug config with asio transport and TLS enabled +struct debug_asio_tls : public debug_core { + typedef debug_asio_tls type; + typedef debug_core base; + + typedef base::concurrency_type concurrency_type; + + typedef base::request_type request_type; + typedef base::response_type response_type; + + typedef base::message_type message_type; + typedef base::con_msg_manager_type con_msg_manager_type; + typedef base::endpoint_msg_manager_type endpoint_msg_manager_type; + + typedef base::alog_type alog_type; + typedef base::elog_type elog_type; + + typedef base::rng_type rng_type; + + struct transport_config : public base::transport_config { + typedef type::concurrency_type concurrency_type; + typedef type::alog_type alog_type; + typedef type::elog_type elog_type; + typedef type::request_type request_type; + typedef type::response_type response_type; + typedef websocketpp::transport::asio::tls_socket::endpoint socket_type; + }; + + typedef websocketpp::transport::asio::endpoint + transport_type; +}; + +} // namespace config +} // namespace websocketpp + +#endif // WEBSOCKETPP_CONFIG_ASIO_TLS_DEBUG_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/config/debug_asio_no_tls.hpp b/thirdparty/websocketpp/include/websocketpp/config/debug_asio_no_tls.hpp new file mode 100644 index 0000000..de595f6 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/config/debug_asio_no_tls.hpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2014, 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_CONFIG_ASIO_DEBUG_HPP +#define WEBSOCKETPP_CONFIG_ASIO_DEBUG_HPP + +#include +#include + +namespace websocketpp { +namespace config { + +/// Client/Server debug config with asio transport and TLS disabled +struct debug_asio : public debug_core { + typedef debug_asio type; + typedef debug_core base; + + typedef base::concurrency_type concurrency_type; + + typedef base::request_type request_type; + typedef base::response_type response_type; + + typedef base::message_type message_type; + typedef base::con_msg_manager_type con_msg_manager_type; + typedef base::endpoint_msg_manager_type endpoint_msg_manager_type; + + typedef base::alog_type alog_type; + typedef base::elog_type elog_type; + + typedef base::rng_type rng_type; + + struct transport_config : public base::transport_config { + typedef type::concurrency_type concurrency_type; + typedef type::alog_type alog_type; + typedef type::elog_type elog_type; + typedef type::request_type request_type; + typedef type::response_type response_type; + typedef websocketpp::transport::asio::basic_socket::endpoint + socket_type; + }; + + typedef websocketpp::transport::asio::endpoint + transport_type; +}; + +} // namespace config +} // namespace websocketpp + +#endif // WEBSOCKETPP_CONFIG_ASIO_DEBUG_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/config/minimal_client.hpp b/thirdparty/websocketpp/include/websocketpp/config/minimal_client.hpp new file mode 100644 index 0000000..eec6575 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/config/minimal_client.hpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2014, 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_CONFIG_MINIMAL_CLIENT_HPP +#define WEBSOCKETPP_CONFIG_MINIMAL_CLIENT_HPP + +#include + +namespace websocketpp { +namespace config { + +/// Client config with minimal dependencies +/** + * This config strips out as many dependencies as possible. It is suitable for + * use as a base class for custom configs that want to implement or choose their + * own policies for components that even the core config includes. + * + * NOTE: this config stubs out enough that it cannot be used directly. You must + * supply at least a transport policy and a cryptographically secure random + * number generation policy for a config based on `minimal_client` to do + * anything useful. + * + * Present dependency list for minimal_server config: + * + * C++98 STL: + * + * + * + * + * + * + * C++11 STL or Boost + * + * + * + * + * Operating System: + * or + * or (for ntohl.. could potentially bundle this) + * + * @since 0.4.0-dev + */ +typedef minimal_server minimal_client; + +} // namespace config +} // namespace websocketpp + +#endif // WEBSOCKETPP_CONFIG_MINIMAL_CLIENT_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/config/minimal_server.hpp b/thirdparty/websocketpp/include/websocketpp/config/minimal_server.hpp new file mode 100644 index 0000000..ae5d0e8 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/config/minimal_server.hpp @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2014, 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_CONFIG_MINIMAL_HPP +#define WEBSOCKETPP_CONFIG_MINIMAL_HPP + +// Non-Policy common stuff +#include +#include +#include + +// Concurrency +#include + +// Transport +#include + +// HTTP +#include +#include + +// Messages +#include +#include + +// Loggers +#include + +// RNG +#include + +// User stub base classes +#include +#include + +// Extensions +#include + +namespace websocketpp { +namespace config { + +/// Server config with minimal dependencies +/** + * This config strips out as many dependencies as possible. It is suitable for + * use as a base class for custom configs that want to implement or choose their + * own policies for components that even the core config includes. + * + * NOTE: this config stubs out enough that it cannot be used directly. You must + * supply at least a transport policy for a config based on `minimal_server` to + * do anything useful. + * + * Present dependency list for minimal_server config: + * + * C++98 STL: + * + * + * + * + * + * + * C++11 STL or Boost + * + * + * + * + * Operating System: + * or + * or (for ntohl.. could potentially bundle this) + * + * @since 0.4.0-dev + */ +struct minimal_server { + typedef minimal_server type; + + // Concurrency policy + typedef websocketpp::concurrency::none concurrency_type; + + // HTTP Parser Policies + typedef http::parser::request request_type; + typedef http::parser::response response_type; + + // Message Policies + typedef message_buffer::message + message_type; + typedef message_buffer::alloc::con_msg_manager + con_msg_manager_type; + typedef message_buffer::alloc::endpoint_msg_manager + endpoint_msg_manager_type; + + /// Logging policies + typedef websocketpp::log::stub elog_type; + typedef websocketpp::log::stub alog_type; + + /// RNG policies + typedef websocketpp::random::none::int_generator rng_type; + + /// Controls compile time enabling/disabling of thread syncronization + /// code Disabling can provide a minor performance improvement to single + /// threaded applications + static bool const enable_multithreading = true; + + struct transport_config { + typedef type::concurrency_type concurrency_type; + typedef type::elog_type elog_type; + typedef type::alog_type alog_type; + typedef type::request_type request_type; + typedef type::response_type response_type; + + /// Controls compile time enabling/disabling of thread syncronization + /// code Disabling can provide a minor performance improvement to single + /// threaded applications + static bool const enable_multithreading = true; + + /// Default timer values (in ms) + + /// Length of time to wait for socket pre-initialization + /** + * Exactly what this includes depends on the socket policy in use + */ + static const long timeout_socket_pre_init = 5000; + + /// Length of time to wait before a proxy handshake is aborted + static const long timeout_proxy = 5000; + + /// Length of time to wait for socket post-initialization + /** + * Exactly what this includes depends on the socket policy in use. + * Often this means the TLS handshake + */ + static const long timeout_socket_post_init = 5000; + + /// Length of time to wait for dns resolution + static const long timeout_dns_resolve = 5000; + + /// Length of time to wait for TCP connect + static const long timeout_connect = 5000; + + /// Length of time to wait for socket shutdown + static const long timeout_socket_shutdown = 5000; + }; + + /// Transport Endpoint Component + typedef websocketpp::transport::stub::endpoint + transport_type; + + /// User overridable Endpoint base class + typedef websocketpp::endpoint_base endpoint_base; + /// User overridable Connection base class + typedef websocketpp::connection_base connection_base; + + /// Default timer values (in ms) + + /// Length of time before an opening handshake is aborted + static const long timeout_open_handshake = 5000; + /// Length of time before a closing handshake is aborted + static const long timeout_close_handshake = 5000; + /// Length of time to wait for a pong after a ping + static const long timeout_pong = 5000; + + /// WebSocket Protocol version to use as a client + /** + * What version of the WebSocket Protocol to use for outgoing client + * connections. Setting this to a value other than 13 (RFC6455) is not + * recommended. + */ + static const int client_version = 13; // RFC6455 + + /// Default static error logging channels + /** + * Which error logging channels to enable at compile time. Channels not + * enabled here will be unable to be selected by programs using the library. + * This option gives an optimizing compiler the ability to remove entirely + * code to test whether or not to print out log messages on a certain + * channel + * + * Default is all except for development/debug level errors + */ + static const websocketpp::log::level elog_level = + websocketpp::log::elevel::none; + + /// Default static access logging channels + /** + * Which access logging channels to enable at compile time. Channels not + * enabled here will be unable to be selected by programs using the library. + * This option gives an optimizing compiler the ability to remove entirely + * code to test whether or not to print out log messages on a certain + * channel + * + * Default is all except for development/debug level access messages + */ + static const websocketpp::log::level alog_level = + websocketpp::log::alevel::none; + + /// + static const size_t connection_read_buffer_size = 16384; + + /// Drop connections immediately on protocol error. + /** + * Drop connections on protocol error rather than sending a close frame. + * Off by default. This may result in legit messages near the error being + * dropped as well. It may free up resources otherwise spent dealing with + * misbehaving clients. + */ + static const bool drop_on_protocol_error = false; + + /// Suppresses the return of detailed connection close information + /** + * Silence close suppresses the return of detailed connection close + * information during the closing handshake. This information is useful + * for debugging and presenting useful errors to end users but may be + * undesirable for security reasons in some production environments. + * Close reasons could be used by an attacker to confirm that the endpoint + * is out of resources or be used to identify the WebSocket implementation + * in use. + * + * Note: this will suppress *all* close codes, including those explicitly + * sent by local applications. + */ + static const bool silent_close = false; + + /// Default maximum message size + /** + * Default value for the processor's maximum message size. Maximum message size + * determines the point at which the library will fail a connection with the + * message_too_big protocol error. + * + * The default is 32MB + * + * @since 0.4.0-alpha1 + */ + static const size_t max_message_size = 32000000; + + /// Default maximum http body size + /** + * Default value for the http parser's maximum body size. Maximum body size + * determines the point at which the library will abort reading an HTTP + * connection with the 413/request entity too large error. + * + * The default is 32MB + * + * @since 0.5.0 + */ + static const size_t max_http_body_size = 32000000; + + /// Global flag for enabling/disabling extensions + static const bool enable_extensions = true; + + /// Extension specific settings: + + /// permessage_compress extension + struct permessage_deflate_config { + typedef core::request_type request_type; + + /// If the remote endpoint requests that we reset the compression + /// context after each message should we honor the request? + static const bool allow_disabling_context_takeover = true; + + /// If the remote endpoint requests that we reduce the size of the + /// LZ77 sliding window size this is the lowest value that will be + /// allowed. Values range from 8 to 15. A value of 8 means we will + /// allow any possible window size. A value of 15 means do not allow + /// negotiation of the window size (ie require the default). + static const uint8_t minimum_outgoing_window_bits = 8; + }; + + typedef websocketpp::extensions::permessage_deflate::disabled + permessage_deflate_type; + + /// Autonegotiate permessage-deflate + /** + * Automatically enables the permessage-deflate extension. + * + * For clients this results in a permessage-deflate extension request being + * sent with every request rather than requiring it to be requested manually + * + * For servers this results in accepting the first set of extension settings + * requested by the client that we understand being used. The alternative is + * requiring the extension to be manually negotiated in `validate`. With + * auto-negotiate on, you may still override the auto-negotiate manually if + * needed. + */ + //static const bool autonegotiate_compression = false; +}; + +} // namespace config +} // namespace websocketpp + +#endif // WEBSOCKETPP_CONFIG_MINIMAL_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/connection.hpp b/thirdparty/websocketpp/include/websocketpp/connection.hpp new file mode 100644 index 0000000..4a5532d --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/connection.hpp @@ -0,0 +1,1642 @@ +/* + * Copyright (c) 2014, 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_CONNECTION_HPP +#define WEBSOCKETPP_CONNECTION_HPP + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace websocketpp { + +/// The type and function signature of an open handler +/** + * The open handler is called once for every successful WebSocket connection + * attempt. Either the fail handler or the open handler will be called for each + * WebSocket connection attempt. HTTP Connections that did not attempt to + * upgrade the connection to the WebSocket protocol will trigger the http + * handler instead of fail/open. + */ +typedef lib::function open_handler; + +/// The type and function signature of a close handler +/** + * The close handler is called once for every successfully established + * connection after it is no longer capable of sending or receiving new messages + * + * The close handler will be called exactly once for every connection for which + * the open handler was called. + */ +typedef lib::function close_handler; + +/// The type and function signature of a fail handler +/** + * The fail handler is called once for every unsuccessful WebSocket connection + * attempt. Either the fail handler or the open handler will be called for each + * WebSocket connection attempt. HTTP Connections that did not attempt to + * upgrade the connection to the WebSocket protocol will trigger the http + * handler instead of fail/open. + */ +typedef lib::function fail_handler; + +/// The type and function signature of an interrupt handler +/** + * The interrupt handler is called when a connection receives an interrupt + * request from the application. Interrupts allow the application to trigger a + * handler to be run in the absense of a WebSocket level handler trigger (like + * a new message). + * + * This is typically used by another application thread to schedule some tasks + * that can only be run from within the handler chain for thread safety reasons. + */ +typedef lib::function interrupt_handler; + +/// The type and function signature of a ping handler +/** + * The ping handler is called when the connection receives a WebSocket ping + * control frame. The string argument contains the ping payload. The payload is + * a binary string up to 126 bytes in length. The ping handler returns a bool, + * true if a pong response should be sent, false if the pong response should be + * suppressed. + */ +typedef lib::function ping_handler; + +/// The type and function signature of a pong handler +/** + * The pong handler is called when the connection receives a WebSocket pong + * control frame. The string argument contains the pong payload. The payload is + * a binary string up to 126 bytes in length. + */ +typedef lib::function pong_handler; + +/// The type and function signature of a pong timeout handler +/** + * The pong timeout handler is called when a ping goes unanswered by a pong for + * longer than the locally specified timeout period. + */ +typedef lib::function pong_timeout_handler; + +/// The type and function signature of a validate handler +/** + * The validate handler is called after a WebSocket handshake has been received + * and processed but before it has been accepted. This gives the application a + * chance to implement connection details specific policies for accepting + * connections and the ability to negotiate extensions and subprotocols. + * + * The validate handler return value indicates whether or not the connection + * should be accepted. Additional methods may be called during the function to + * set response headers, set HTTP return/error codes, etc. + */ +typedef lib::function validate_handler; + +/// The type and function signature of a http handler +/** + * The http handler is called when an HTTP connection is made that does not + * attempt to upgrade the connection to the WebSocket protocol. This allows + * WebSocket++ servers to respond to these requests with regular HTTP responses. + * + * This can be used to deliver error pages & dashboards and to deliver static + * files such as the base HTML & JavaScript for an otherwise single page + * WebSocket application. + * + * Note: WebSocket++ is designed to be a high performance WebSocket server. It + * is not tuned to provide a full featured, high performance, HTTP web server + * solution. The HTTP handler is appropriate only for low volume HTTP traffic. + * If you expect to serve high volumes of HTTP traffic a dedicated HTTP web + * server is strongly recommended. + * + * The default HTTP handler will return a 426 Upgrade Required error. Custom + * handlers may override the response status code to deliver any type of + * response. + */ +typedef lib::function http_handler; + +// +typedef lib::function read_handler; +typedef lib::function write_frame_handler; + +// constants related to the default WebSocket protocol versions available +#ifdef _WEBSOCKETPP_INITIALIZER_LISTS_ // simplified C++11 version + /// Container that stores the list of protocol versions supported + /** + * @todo Move this to configs to allow compile/runtime disabling or enabling + * of protocol versions + */ + static std::vector const versions_supported = {0,7,8,13}; +#else + /// Helper array to get around lack of initializer lists pre C++11 + static int const helper[] = {0,7,8,13}; + /// Container that stores the list of protocol versions supported + /** + * @todo Move this to configs to allow compile/runtime disabling or enabling + * of protocol versions + */ + static std::vector const versions_supported(helper,helper+4); +#endif + +namespace session { +namespace state { + // externally visible session state (states based on the RFC) + enum value { + connecting = 0, + open = 1, + closing = 2, + closed = 3 + }; +} // namespace state + + +namespace fail { +namespace status { + enum value { + GOOD = 0, // no failure yet! + SYSTEM = 1, // system call returned error, check that code + WEBSOCKET = 2, // websocket close codes contain error + UNKNOWN = 3, // No failure information is available + TIMEOUT_TLS = 4, // TLS handshake timed out + TIMEOUT_WS = 5 // WS handshake timed out + }; +} // namespace status +} // namespace fail + +namespace internal_state { + // More granular internal states. These are used for multi-threaded + // connection synchronization and preventing values that are not yet or no + // longer available from being used. + + enum value { + USER_INIT = 0, + TRANSPORT_INIT = 1, + READ_HTTP_REQUEST = 2, + WRITE_HTTP_REQUEST = 3, + READ_HTTP_RESPONSE = 4, + WRITE_HTTP_RESPONSE = 5, + PROCESS_HTTP_REQUEST = 6, + PROCESS_CONNECTION = 7 + }; +} // namespace internal_state + + +namespace http_state { + // states to keep track of the progress of http connections + + enum value { + init = 0, + deferred = 1, + headers_written = 2, + body_written = 3, + closed = 4 + }; +} // namespace http_state + +} // namespace session + +/// Represents an individual WebSocket connection +template +class connection + : public config::transport_type::transport_con_type + , public config::connection_base +{ +public: + /// Type of this connection + typedef connection type; + /// Type of a shared pointer to this connection + typedef lib::shared_ptr ptr; + /// Type of a weak pointer to this connection + typedef lib::weak_ptr weak_ptr; + + /// Type of the concurrency component of this connection + typedef typename config::concurrency_type concurrency_type; + /// Type of the access logging policy + typedef typename config::alog_type alog_type; + /// Type of the error logging policy + typedef typename config::elog_type elog_type; + + /// Type of the transport component of this connection + typedef typename config::transport_type::transport_con_type + transport_con_type; + /// Type of a shared pointer to the transport component of this connection + typedef typename transport_con_type::ptr transport_con_ptr; + + typedef lib::function termination_handler; + + typedef typename concurrency_type::scoped_lock_type scoped_lock_type; + typedef typename concurrency_type::mutex_type mutex_type; + + typedef typename config::request_type request_type; + typedef typename config::response_type response_type; + + typedef typename config::message_type message_type; + typedef typename message_type::ptr message_ptr; + + typedef typename config::con_msg_manager_type con_msg_manager_type; + typedef typename con_msg_manager_type::ptr con_msg_manager_ptr; + + /// Type of RNG + typedef typename config::rng_type rng_type; + + typedef processor::processor processor_type; + typedef lib::shared_ptr processor_ptr; + + // Message handler (needs to know message type) + typedef lib::function message_handler; + + /// Type of a pointer to a transport timer handle + typedef typename transport_con_type::timer_ptr timer_ptr; + + // Misc Convenience Types + typedef session::internal_state::value istate_type; + +private: + enum terminate_status { + failed = 1, + closed, + unknown + }; +public: + + explicit connection(bool p_is_server, std::string const & ua, const lib::shared_ptr& alog, + const lib::shared_ptr& elog, rng_type & rng) + : transport_con_type(p_is_server, alog, elog) + , m_handle_read_frame(lib::bind( + &type::handle_read_frame, + this, + lib::placeholders::_1, + lib::placeholders::_2 + )) + , m_write_frame_handler(lib::bind( + &type::handle_write_frame, + this, + lib::placeholders::_1 + )) + , m_user_agent(ua) + , m_open_handshake_timeout_dur(config::timeout_open_handshake) + , m_close_handshake_timeout_dur(config::timeout_close_handshake) + , m_pong_timeout_dur(config::timeout_pong) + , m_max_message_size(config::max_message_size) + , m_state(session::state::connecting) + , m_internal_state(session::internal_state::USER_INIT) + , m_msg_manager(new con_msg_manager_type()) + , m_send_buffer_size(0) + , m_write_flag(false) + , m_read_flag(true) + , m_is_server(p_is_server) + , m_alog(alog) + , m_elog(elog) + , m_rng(rng) + , m_local_close_code(close::status::abnormal_close) + , m_remote_close_code(close::status::abnormal_close) + , m_is_http(false) + , m_http_state(session::http_state::init) + , m_was_clean(false) + { + m_alog->write(log::alevel::devel,"connection constructor"); + } + + /// Get a shared pointer to this component + ptr get_shared() { + return lib::static_pointer_cast(transport_con_type::get_shared()); + } + + /////////////////////////// + // Set Handler Callbacks // + /////////////////////////// + + /// Set open handler + /** + * The open handler is called after the WebSocket handshake is complete and + * the connection is considered OPEN. + * + * @param h The new open_handler + */ + void set_open_handler(open_handler h) { + m_open_handler = h; + } + + /// Set close handler + /** + * The close handler is called immediately after the connection is closed. + * + * @param h The new close_handler + */ + void set_close_handler(close_handler h) { + m_close_handler = h; + } + + /// Set fail handler + /** + * The fail handler is called whenever the connection fails while the + * handshake is bring processed. + * + * @param h The new fail_handler + */ + void set_fail_handler(fail_handler h) { + m_fail_handler = h; + } + + /// Set ping handler + /** + * The ping handler is called whenever the connection receives a ping + * control frame. The ping payload is included. + * + * The ping handler's return time controls whether or not a pong is + * sent in response to this ping. Returning false will suppress the + * return pong. If no ping handler is set a pong will be sent. + * + * @param h The new ping_handler + */ + void set_ping_handler(ping_handler h) { + m_ping_handler = h; + } + + /// Set pong handler + /** + * The pong handler is called whenever the connection receives a pong + * control frame. The pong payload is included. + * + * @param h The new pong_handler + */ + void set_pong_handler(pong_handler h) { + m_pong_handler = h; + } + + /// Set pong timeout handler + /** + * If the transport component being used supports timers, the pong timeout + * handler is called whenever a pong control frame is not received with the + * configured timeout period after the application sends a ping. + * + * The config setting `timeout_pong` controls the length of the timeout + * period. It is specified in milliseconds. + * + * This can be used to probe the health of the remote endpoint's WebSocket + * implementation. This does not guarantee that the remote application + * itself is still healthy but can be a useful diagnostic. + * + * Note: receipt of this callback doesn't mean the pong will never come. + * This functionality will not suppress delivery of the pong in question + * should it arrive after the timeout. + * + * @param h The new pong_timeout_handler + */ + void set_pong_timeout_handler(pong_timeout_handler h) { + m_pong_timeout_handler = h; + } + + /// Set interrupt handler + /** + * The interrupt handler is called whenever the connection is manually + * interrupted by the application. + * + * @param h The new interrupt_handler + */ + void set_interrupt_handler(interrupt_handler h) { + m_interrupt_handler = h; + } + + /// Set http handler + /** + * The http handler is called after an HTTP request other than a WebSocket + * upgrade request is received. It allows a WebSocket++ server to respond + * to regular HTTP requests on the same port as it processes WebSocket + * connections. This can be useful for hosting error messages, flash + * policy files, status pages, and other simple HTTP responses. It is not + * intended to be used as a primary web server. + * + * @param h The new http_handler + */ + void set_http_handler(http_handler h) { + m_http_handler = h; + } + + /// Set validate handler + /** + * The validate handler is called after a WebSocket handshake has been + * parsed but before a response is returned. It provides the application + * a chance to examine the request and determine whether or not it wants + * to accept the connection. + * + * Returning false from the validate handler will reject the connection. + * If no validate handler is present, all connections will be allowed. + * + * @param h The new validate_handler + */ + void set_validate_handler(validate_handler h) { + m_validate_handler = h; + } + + /// Set message handler + /** + * The message handler is called after a new message has been received. + * + * @param h The new message_handler + */ + void set_message_handler(message_handler h) { + m_message_handler = h; + } + + ////////////////////////////////////////// + // Connection timeouts and other limits // + ////////////////////////////////////////// + + /// Set open handshake timeout + /** + * Sets the length of time the library will wait after an opening handshake + * has been initiated before cancelling it. This can be used to prevent + * excessive wait times for outgoing clients or excessive resource usage + * from broken clients or DoS attacks on servers. + * + * Connections that time out will have their fail handlers called with the + * open_handshake_timeout error code. + * + * The default value is specified via the compile time config value + * 'timeout_open_handshake'. The default value in the core config + * is 5000ms. A value of 0 will disable the timer entirely. + * + * To be effective, the transport you are using must support timers. See + * the documentation for your transport policy for details about its + * timer support. + * + * @param dur The length of the open handshake timeout in ms + */ + void set_open_handshake_timeout(long dur) { + m_open_handshake_timeout_dur = dur; + } + + /// Set close handshake timeout + /** + * Sets the length of time the library will wait after a closing handshake + * has been initiated before cancelling it. This can be used to prevent + * excessive wait times for outgoing clients or excessive resource usage + * from broken clients or DoS attacks on servers. + * + * Connections that time out will have their close handlers called with the + * close_handshake_timeout error code. + * + * The default value is specified via the compile time config value + * 'timeout_close_handshake'. The default value in the core config + * is 5000ms. A value of 0 will disable the timer entirely. + * + * To be effective, the transport you are using must support timers. See + * the documentation for your transport policy for details about its + * timer support. + * + * @param dur The length of the close handshake timeout in ms + */ + void set_close_handshake_timeout(long dur) { + m_close_handshake_timeout_dur = dur; + } + + /// Set pong timeout + /** + * Sets the length of time the library will wait for a pong response to a + * ping. This can be used as a keepalive or to detect broken connections. + * + * Pong responses that time out will have the pong timeout handler called. + * + * The default value is specified via the compile time config value + * 'timeout_pong'. The default value in the core config + * is 5000ms. A value of 0 will disable the timer entirely. + * + * To be effective, the transport you are using must support timers. See + * the documentation for your transport policy for details about its + * timer support. + * + * @param dur The length of the pong timeout in ms + */ + void set_pong_timeout(long dur) { + m_pong_timeout_dur = dur; + } + + /// Get maximum message size + /** + * Get maximum message size. Maximum message size determines the point at + * which the connection will fail with the message_too_big protocol error. + * + * The default is set by the endpoint that creates the connection. + * + * @since 0.3.0 + */ + size_t get_max_message_size() const { + return m_max_message_size; + } + + /// Set maximum message size + /** + * Set maximum message size. Maximum message size determines the point at + * which the connection will fail with the message_too_big protocol error. + * This value may be changed during the connection. + * + * The default is set by the endpoint that creates the connection. + * + * @since 0.3.0 + * + * @param new_value The value to set as the maximum message size. + */ + void set_max_message_size(size_t new_value) { + m_max_message_size = new_value; + if (m_processor) { + m_processor->set_max_message_size(new_value); + } + } + + /// Get maximum HTTP message body size + /** + * Get maximum HTTP message body size. Maximum message body size determines + * the point at which the connection will stop reading an HTTP request whose + * body is too large. + * + * The default is set by the endpoint that creates the connection. + * + * @since 0.5.0 + * + * @return The maximum HTTP message body size + */ + size_t get_max_http_body_size() const { + return m_request.get_max_body_size(); + } + + /// Set maximum HTTP message body size + /** + * Set maximum HTTP message body size. Maximum message body size determines + * the point at which the connection will stop reading an HTTP request whose + * body is too large. + * + * The default is set by the endpoint that creates the connection. + * + * @since 0.5.0 + * + * @param new_value The value to set as the maximum message size. + */ + void set_max_http_body_size(size_t new_value) { + m_request.set_max_body_size(new_value); + } + + ////////////////////////////////// + // Uncategorized public methods // + ////////////////////////////////// + + /// Get the size of the outgoing write buffer (in payload bytes) + /** + * Retrieves the number of bytes in the outgoing write buffer that have not + * already been dispatched to the transport layer. This represents the bytes + * that are presently cancelable without uncleanly ending the websocket + * connection + * + * This method invokes the m_write_lock mutex + * + * @return The current number of bytes in the outgoing send buffer. + */ + size_t get_buffered_amount() const; + + /// Get the size of the outgoing write buffer (in payload bytes) + /** + * @deprecated use `get_buffered_amount` instead + */ + size_t buffered_amount() const { + return get_buffered_amount(); + } + + //////////////////// + // Action Methods // + //////////////////// + + /// Create a message and then add it to the outgoing send queue + /** + * Convenience method to send a message given a payload string and + * optionally an opcode. Default opcode is utf8 text. + * + * This method locks the m_write_lock mutex + * + * @param payload The payload string to generated the message with + * + * @param op The opcode to generated the message with. Default is + * frame::opcode::text + */ + lib::error_code send(std::string const & payload, frame::opcode::value op = + frame::opcode::text); + + /// Send a message (raw array overload) + /** + * Convenience method to send a message given a raw array and optionally an + * opcode. Default opcode is binary. + * + * This method locks the m_write_lock mutex + * + * @param payload A pointer to the array containing the bytes to send. + * + * @param len Length of the array. + * + * @param op The opcode to generated the message with. Default is + * frame::opcode::binary + */ + lib::error_code send(void const * payload, size_t len, frame::opcode::value + op = frame::opcode::binary); + + /// Add a message to the outgoing send queue + /** + * If presented with a prepared message it is added without validation or + * framing. If presented with an unprepared message it is validated, framed, + * and then added + * + * Errors are returned via an exception + * \todo make exception system_error rather than error_code + * + * This method invokes the m_write_lock mutex + * + * @param msg A message_ptr to the message to send. + */ + lib::error_code send(message_ptr msg); + + /// Asyncronously invoke handler::on_inturrupt + /** + * Signals to the connection to asyncronously invoke the on_inturrupt + * callback for this connection's handler once it is safe to do so. + * + * When the on_inturrupt handler callback is called it will be from + * within the transport event loop with all the thread safety features + * guaranteed by the transport to regular handlers + * + * Multiple inturrupt signals can be active at once on the same connection + * + * @return An error code + */ + lib::error_code interrupt(); + + /// Transport inturrupt callback + void handle_interrupt(); + + /// Pause reading of new data + /** + * Signals to the connection to halt reading of new data. While reading is paused, + * the connection will stop reading from its associated socket. In turn this will + * result in TCP based flow control kicking in and slowing data flow from the remote + * endpoint. + * + * This is useful for applications that push new requests to a queue to be processed + * by another thread and need a way to signal when their request queue is full without + * blocking the network processing thread. + * + * Use `resume_reading()` to resume. + * + * If supported by the transport this is done asynchronously. As such reading may not + * stop until the current read operation completes. Typically you can expect to + * receive no more bytes after initiating a read pause than the size of the read + * buffer. + * + * If reading is paused for this connection already nothing is changed. + */ + lib::error_code pause_reading(); + + /// Pause reading callback + void handle_pause_reading(); + + /// Resume reading of new data + /** + * Signals to the connection to resume reading of new data after it was paused by + * `pause_reading()`. + * + * If reading is not paused for this connection already nothing is changed. + */ + lib::error_code resume_reading(); + + /// Resume reading callback + void handle_resume_reading(); + + /// Send a ping + /** + * Initiates a ping with the given payload/ + * + * There is no feedback directly from ping except in cases of immediately + * detectable errors. Feedback will be provided via on_pong or + * on_pong_timeout callbacks. + * + * Ping locks the m_write_lock mutex + * + * @param payload Payload to be used for the ping + */ + void ping(std::string const & payload); + + /// exception free variant of ping + void ping(std::string const & payload, lib::error_code & ec); + + /// Utility method that gets called back when the ping timer expires + void handle_pong_timeout(std::string payload, lib::error_code const & ec); + + /// Send a pong + /** + * Initiates a pong with the given payload. + * + * There is no feedback from a pong once sent. + * + * Pong locks the m_write_lock mutex + * + * @param payload Payload to be used for the pong + */ + void pong(std::string const & payload); + + /// exception free variant of pong + void pong(std::string const & payload, lib::error_code & ec); + + /// Close the connection + /** + * Initiates the close handshake process. + * + * If close returns successfully the connection will be in the closing + * state and no additional messages may be sent. All messages sent prior + * to calling close will be written out before the connection is closed. + * + * If no reason is specified none will be sent. If no code is specified + * then no code will be sent. + * + * The handler's on_close callback will be called once the close handshake + * is complete. + * + * Reasons will be automatically truncated to the maximum length (123 bytes) + * if necessary. + * + * @param code The close code to send + * @param reason The close reason to send + */ + void close(close::status::value const code, std::string const & reason); + + /// exception free variant of close + void close(close::status::value const code, std::string const & reason, + lib::error_code & ec); + + //////////////////////////////////////////////// + // Pass-through access to the uri information // + //////////////////////////////////////////////// + + /// Returns the secure flag from the connection URI + /** + * This value is available after the HTTP request has been fully read and + * may be called from any thread. + * + * @return Whether or not the connection URI is flagged secure. + */ + bool get_secure() const; + + /// Returns the host component of the connection URI + /** + * This value is available after the HTTP request has been fully read and + * may be called from any thread. + * + * @return The host component of the connection URI + */ + std::string const & get_host() const; + + /// Returns the resource component of the connection URI + /** + * This value is available after the HTTP request has been fully read and + * may be called from any thread. + * + * @return The resource component of the connection URI + */ + std::string const & get_resource() const; + + /// Returns the port component of the connection URI + /** + * This value is available after the HTTP request has been fully read and + * may be called from any thread. + * + * @return The port component of the connection URI + */ + uint16_t get_port() const; + + /// Gets the connection URI + /** + * This should really only be called by internal library methods unless you + * really know what you are doing. + * + * @return A pointer to the connection's URI + */ + uri_ptr get_uri() const; + + /// Sets the connection URI + /** + * This should really only be called by internal library methods unless you + * really know what you are doing. + * + * @param uri The new URI to set + */ + void set_uri(uri_ptr uri); + + ///////////////////////////// + // Subprotocol negotiation // + ///////////////////////////// + + /// Gets the negotated subprotocol + /** + * Retrieves the subprotocol that was negotiated during the handshake. This + * method is valid in the open handler and later. + * + * @return The negotiated subprotocol + */ + std::string const & get_subprotocol() const; + + /// Gets all of the subprotocols requested by the client + /** + * Retrieves the subprotocols that were requested during the handshake. This + * method is valid in the validate handler and later. + * + * @return A vector of the requested subprotocol + */ + std::vector const & get_requested_subprotocols() const; + + /// Adds the given subprotocol string to the request list (exception free) + /** + * Adds a subprotocol to the list to send with the opening handshake. This + * may be called multiple times to request more than one. If the server + * supports one of these, it may choose one. If so, it will return it + * in it's handshake reponse and the value will be available via + * get_subprotocol(). Subprotocol requests should be added in order of + * preference. + * + * @param request The subprotocol to request + * @param ec A reference to an error code that will be filled in the case of + * errors + */ + void add_subprotocol(std::string const & request, lib::error_code & ec); + + /// Adds the given subprotocol string to the request list + /** + * Adds a subprotocol to the list to send with the opening handshake. This + * may be called multiple times to request more than one. If the server + * supports one of these, it may choose one. If so, it will return it + * in it's handshake reponse and the value will be available via + * get_subprotocol(). Subprotocol requests should be added in order of + * preference. + * + * @param request The subprotocol to request + */ + void add_subprotocol(std::string const & request); + + /// Select a subprotocol to use (exception free) + /** + * Indicates which subprotocol should be used for this connection. Valid + * only during the validate handler callback. Subprotocol selected must have + * been requested by the client. Consult get_requested_subprotocols() for a + * list of valid subprotocols. + * + * This member function is valid on server endpoints/connections only + * + * @param value The subprotocol to select + * @param ec A reference to an error code that will be filled in the case of + * errors + */ + void select_subprotocol(std::string const & value, lib::error_code & ec); + + /// Select a subprotocol to use + /** + * Indicates which subprotocol should be used for this connection. Valid + * only during the validate handler callback. Subprotocol selected must have + * been requested by the client. Consult get_requested_subprotocols() for a + * list of valid subprotocols. + * + * This member function is valid on server endpoints/connections only + * + * @param value The subprotocol to select + */ + void select_subprotocol(std::string const & value); + + ///////////////////////////////////////////////////////////// + // Pass-through access to the request and response objects // + ///////////////////////////////////////////////////////////// + + /// Retrieve a request header + /** + * Retrieve the value of a header from the handshake HTTP request. + * + * @param key Name of the header to get + * @return The value of the header + */ + std::string const & get_request_header(std::string const & key) const; + + /// Retrieve a request body + /** + * Retrieve the value of the request body. This value is typically used with + * PUT and POST requests to upload files or other data. Only HTTP + * connections will ever have bodies. WebSocket connection's will always + * have blank bodies. + * + * @return The value of the request body. + */ + std::string const & get_request_body() const; + + /// Retrieve a response header + /** + * Retrieve the value of a header from the handshake HTTP request. + * + * @param key Name of the header to get + * @return The value of the header + */ + std::string const & get_response_header(std::string const & key) const; + + /// Get response HTTP status code + /** + * Gets the response status code + * + * @since 0.7.0 + * + * @return The response status code sent + */ + http::status_code::value get_response_code() const { + return m_response.get_status_code(); + } + + /// Get response HTTP status message + /** + * Gets the response status message + * + * @since 0.7.0 + * + * @return The response status message sent + */ + std::string const & get_response_msg() const { + return m_response.get_status_msg(); + } + + /// Set response status code and message + /** + * Sets the response status code to `code` and looks up the corresponding + * message for standard codes. Non-standard codes will be entered as Unknown + * use set_status(status_code::value,std::string) overload to set both + * values explicitly. + * + * This member function is valid only from the http() and validate() handler + * callbacks. + * + * @param code Code to set + * @param msg Message to set + * @see websocketpp::http::response::set_status + */ + void set_status(http::status_code::value code); + + /// Set response status code and message + /** + * Sets the response status code and message to independent custom values. + * use set_status(status_code::value) to set the code and have the standard + * message be automatically set. + * + * This member function is valid only from the http() and validate() handler + * callbacks. + * + * @param code Code to set + * @param msg Message to set + * @see websocketpp::http::response::set_status + */ + void set_status(http::status_code::value code, std::string const & msg); + + /// Set response body content + /** + * Set the body content of the HTTP response to the parameter string. Note + * set_body will also set the Content-Length HTTP header to the appropriate + * value. If you want the Content-Length header to be something else set it + * to something else after calling set_body + * + * This member function is valid only from the http() and validate() handler + * callbacks. + * + * @param value String data to include as the body content. + * @see websocketpp::http::response::set_body + */ + void set_body(std::string const & value); + + /// Append a header + /** + * If a header with this name already exists the value will be appended to + * the existing header to form a comma separated list of values. Use + * `connection::replace_header` to overwrite existing values. + * + * This member function is valid only from the http() and validate() handler + * callbacks, or to a client connection before connect has been called. + * + * @param key Name of the header to set + * @param val Value to add + * @see replace_header + * @see websocketpp::http::parser::append_header + */ + void append_header(std::string const & key, std::string const & val); + + /// Replace a header + /** + * If a header with this name already exists the old value will be replaced + * Use `connection::append_header` to append to a list of existing values. + * + * This member function is valid only from the http() and validate() handler + * callbacks, or to a client connection before connect has been called. + * + * @param key Name of the header to set + * @param val Value to set + * @see append_header + * @see websocketpp::http::parser::replace_header + */ + void replace_header(std::string const & key, std::string const & val); + + /// Remove a header + /** + * Removes a header from the response. + * + * This member function is valid only from the http() and validate() handler + * callbacks, or to a client connection before connect has been called. + * + * @param key The name of the header to remove + * @see websocketpp::http::parser::remove_header + */ + void remove_header(std::string const & key); + + /// Get request object + /** + * Direct access to request object. This can be used to call methods of the + * request object that are not part of the standard request API that + * connection wraps. + * + * Note use of this method involves using behavior specific to the + * configured HTTP policy. Such behavior may not work with alternate HTTP + * policies. + * + * @since 0.3.0-alpha3 + * + * @return A const reference to the raw request object + */ + request_type const & get_request() const { + return m_request; + } + + /// Get response object + /** + * Direct access to the HTTP response sent or received as a part of the + * opening handshake. This can be used to call methods of the response + * object that are not part of the standard request API that connection + * wraps. + * + * Note use of this method involves using behavior specific to the + * configured HTTP policy. Such behavior may not work with alternate HTTP + * policies. + * + * @since 0.7.0 + * + * @return A const reference to the raw response object + */ + response_type const & get_response() const { + return m_response; + } + + /// Defer HTTP Response until later (Exception free) + /** + * Used in the http handler to defer the HTTP response for this connection + * until later. Handshake timers will be canceled and the connection will be + * left open until `send_http_response` or an equivalent is called. + * + * Warning: deferred connections won't time out and as a result can tie up + * resources. + * + * @since 0.6.0 + * + * @return A status code, zero on success, non-zero otherwise + */ + lib::error_code defer_http_response(); + + /// Send deferred HTTP Response (exception free) + /** + * Sends an http response to an HTTP connection that was deferred. This will + * send a complete response including all headers, status line, and body + * text. The connection will be closed afterwards. + * + * @since 0.6.0 + * + * @param ec A status code, zero on success, non-zero otherwise + */ + void send_http_response(lib::error_code & ec); + + /// Send deferred HTTP Response + void send_http_response(); + + // TODO HTTPNBIO: write_headers + // function that processes headers + status so far and writes it to the wire + // beginning the HTTP response body state. This method will ignore anything + // in the response body. + + // TODO HTTPNBIO: write_body_message + // queues the specified message_buffer for async writing + + // TODO HTTPNBIO: finish connection + // + + // TODO HTTPNBIO: write_response + // Writes the whole response, headers + body and closes the connection + + + + ///////////////////////////////////////////////////////////// + // Pass-through access to the other connection information // + ///////////////////////////////////////////////////////////// + + /// Get Connection Handle + /** + * The connection handle is a token that can be shared outside the + * WebSocket++ core for the purposes of identifying a connection and + * sending it messages. + * + * @return A handle to the connection + */ + connection_hdl get_handle() const { + return m_connection_hdl; + } + + /// Get whether or not this connection is part of a server or client + /** + * @return whether or not the connection is attached to a server endpoint + */ + bool is_server() const { + return m_is_server; + } + + /// Return the same origin policy origin value from the opening request. + /** + * This value is available after the HTTP request has been fully read and + * may be called from any thread. + * + * @return The connection's origin value from the opening handshake. + */ + std::string const & get_origin() const; + + /// Return the connection state. + /** + * Values can be connecting, open, closing, and closed + * + * @return The connection's current state. + */ + session::state::value get_state() const; + + + /// Get the WebSocket close code sent by this endpoint. + /** + * @return The WebSocket close code sent by this endpoint. + */ + close::status::value get_local_close_code() const { + return m_local_close_code; + } + + /// Get the WebSocket close reason sent by this endpoint. + /** + * @return The WebSocket close reason sent by this endpoint. + */ + std::string const & get_local_close_reason() const { + return m_local_close_reason; + } + + /// Get the WebSocket close code sent by the remote endpoint. + /** + * @return The WebSocket close code sent by the remote endpoint. + */ + close::status::value get_remote_close_code() const { + return m_remote_close_code; + } + + /// Get the WebSocket close reason sent by the remote endpoint. + /** + * @return The WebSocket close reason sent by the remote endpoint. + */ + std::string const & get_remote_close_reason() const { + return m_remote_close_reason; + } + + /// Get the internal 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. + * + * @return Error code indicating the reason the connection was closed or + * failed + */ + lib::error_code get_ec() const { + return m_ec; + } + + /// Get a message buffer + /** + * Warning: The API related to directly sending message buffers may change + * before the 1.0 release. If you plan to use it, please keep an eye on any + * breaking changes notifications in future release notes. Also if you have + * any feedback about usage and capabilities now is a great time to provide + * it. + * + * Message buffers are used to store message payloads and other message + * metadata. + * + * The size parameter is a hint only. Your final payload does not need to + * match it. There may be some performance benefits if the initial size + * guess is equal to or slightly higher than the final payload size. + * + * @param op The opcode for the new message + * @param size A hint to optimize the initial allocation of payload space. + * @return A new message buffer + */ + message_ptr get_message(websocketpp::frame::opcode::value op, size_t size) + const + { + return m_msg_manager->get_message(op, size); + } + + //////////////////////////////////////////////////////////////////////// + // The remaining public member functions are for internal/policy use // + // only. Do not call from application code unless you understand what // + // you are doing. // + //////////////////////////////////////////////////////////////////////// + + + + void read_handshake(size_t num_bytes); + + void handle_read_handshake(lib::error_code const & ec, + size_t bytes_transferred); + void handle_read_http_response(lib::error_code const & ec, + size_t bytes_transferred); + + + void handle_write_http_response(lib::error_code const & ec); + void handle_send_http_request(lib::error_code const & ec); + + void handle_open_handshake_timeout(lib::error_code const & ec); + void handle_close_handshake_timeout(lib::error_code const & ec); + + void handle_read_frame(lib::error_code const & ec, size_t bytes_transferred); + void read_frame(); + + /// Get array of WebSocket protocol versions that this connection supports. + std::vector const & get_supported_versions() const; + + /// Sets the handler for a terminating connection. Should only be used + /// internally by the endpoint class. + void set_termination_handler(termination_handler new_handler); + + void terminate(lib::error_code const & ec); + void handle_terminate(terminate_status tstat, lib::error_code const & ec); + + /// Checks if there are frames in the send queue and if there are sends one + /** + * \todo unit tests + * + * This method locks the m_write_lock mutex + */ + void write_frame(); + + /// Process the results of a frame write operation and start the next write + /** + * \todo unit tests + * + * This method locks the m_write_lock mutex + * + * @param terminate Whether or not to terminate the connection upon + * completion of this write. + * + * @param ec A status code from the transport layer, zero on success, + * non-zero otherwise. + */ + void handle_write_frame(lib::error_code const & ec); +// protected: + // This set of methods would really like to be protected, but doing so + // requires that the endpoint be able to friend the connection. This is + // allowed with C++11, but not prior versions + + /// Start the connection state machine + void start(); + + /// Set Connection Handle + /** + * The connection handle is a token that can be shared outside the + * WebSocket++ core for the purposes of identifying a connection and + * sending it messages. + * + * @param hdl A connection_hdl that the connection will use to refer + * to itself. + */ + void set_handle(connection_hdl hdl) { + m_connection_hdl = hdl; + transport_con_type::set_handle(hdl); + } +protected: + void handle_transport_init(lib::error_code const & ec); + + /// Set m_processor based on information in m_request. Set m_response + /// status and return an error code indicating status. + lib::error_code initialize_processor(); + + /// Perform WebSocket handshake validation of m_request using m_processor. + /// set m_response and return an error code indicating status. + lib::error_code process_handshake_request(); +private: + + + /// Completes m_response, serializes it, and sends it out on the wire. + void write_http_response(lib::error_code const & ec); + + /// Sends an opening WebSocket connect request + void send_http_request(); + + /// Alternate path for write_http_response in error conditions + void write_http_response_error(lib::error_code const & ec); + + /// Process control message + /** + * + */ + void process_control_frame(message_ptr msg); + + /// Send close acknowledgement + /** + * If no arguments are present no close code/reason will be specified. + * + * Note: the close code/reason values provided here may be overrided by + * other settings (such as silent close). + * + * @param code The close code to send + * @param reason The close reason to send + * @return A status code, zero on success, non-zero otherwise + */ + lib::error_code send_close_ack(close::status::value code = + close::status::blank, std::string const & reason = std::string()); + + /// Send close frame + /** + * If no arguments are present no close code/reason will be specified. + * + * Note: the close code/reason values provided here may be overrided by + * other settings (such as silent close). + * + * The ack flag determines what to do in the case of a blank status and + * whether or not to terminate the TCP connection after sending it. + * + * @param code The close code to send + * @param reason The close reason to send + * @param ack Whether or not this is an acknowledgement close frame + * @return A status code, zero on success, non-zero otherwise + */ + lib::error_code send_close_frame(close::status::value code = + close::status::blank, std::string const & reason = std::string(), bool ack = false, + bool terminal = false); + + /// Get a pointer to a new WebSocket protocol processor for a given version + /** + * @param version Version number of the WebSocket protocol to get a + * processor for. Negative values indicate invalid/unknown versions and will + * always return a null ptr + * + * @return A shared_ptr to a new instance of the appropriate processor or a + * null ptr if there is no installed processor that matches the version + * number. + */ + processor_ptr get_processor(int version) const; + + /// Add a message to the write queue + /** + * Adds a message to the write queue and updates any associated shared state + * + * Must be called while holding m_write_lock + * + * @todo unit tests + * + * @param msg The message to push + */ + void write_push(message_ptr msg); + + /// Pop a message from the write queue + /** + * Removes and returns a message from the write queue and updates any + * associated shared state. + * + * Must be called while holding m_write_lock + * + * @todo unit tests + * + * @return the message_ptr at the front of the queue + */ + message_ptr write_pop(); + + /// Prints information about the incoming connection to the access log + /** + * Prints information about the incoming connection to the access log. + * Includes: connection type, websocket version, remote endpoint, user agent + * path, status code. + */ + void log_open_result(); + + /// Prints information about a connection being closed to the access log + /** + * Includes: local and remote close codes and reasons + */ + void log_close_result(); + + /// Prints information about a connection being failed to the access log + /** + * Includes: error code and message for why it was failed + */ + void log_fail_result(); + + /// Prints information about HTTP connections + /** + * Includes: TODO + */ + void log_http_result(); + + /// Prints information about an arbitrary error code on the specified channel + template + 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()); + } + + // internal handler functions + read_handler m_handle_read_frame; + write_frame_handler m_write_frame_handler; + + // static settings + std::string const m_user_agent; + + /// Pointer to the connection handle + connection_hdl m_connection_hdl; + + /// Handler objects + open_handler m_open_handler; + close_handler m_close_handler; + fail_handler m_fail_handler; + ping_handler m_ping_handler; + pong_handler m_pong_handler; + pong_timeout_handler m_pong_timeout_handler; + interrupt_handler m_interrupt_handler; + http_handler m_http_handler; + validate_handler m_validate_handler; + message_handler m_message_handler; + + /// constant values + long m_open_handshake_timeout_dur; + long m_close_handshake_timeout_dur; + long m_pong_timeout_dur; + size_t m_max_message_size; + + /// External connection state + /** + * Lock: m_connection_state_lock + */ + session::state::value m_state; + + /// Internal connection state + /** + * Lock: m_connection_state_lock + */ + istate_type m_internal_state; + + mutable mutex_type m_connection_state_lock; + + /// The lock used to protect the message queue + /** + * Serializes access to the write queue as well as shared state within the + * processor. + */ + mutex_type m_write_lock; + + // connection resources + char m_buf[config::connection_read_buffer_size]; + size_t m_buf_cursor; + termination_handler m_termination_handler; + con_msg_manager_ptr m_msg_manager; + timer_ptr m_handshake_timer; + timer_ptr m_ping_timer; + + /// @todo this is not memory efficient. this value is not used after the + /// handshake. + std::string m_handshake_buffer; + + /// Pointer to the processor object for this connection + /** + * The processor provides functionality that is specific to the WebSocket + * protocol version that the client has negotiated. It also contains all of + * the state necessary to encode and decode the incoming and outgoing + * WebSocket byte streams + * + * Use of the prepare_data_frame method requires lock: m_write_lock + */ + processor_ptr m_processor; + + /// Queue of unsent outgoing messages + /** + * Lock: m_write_lock + */ + std::queue m_send_queue; + + /// Size in bytes of the outstanding payloads in the write queue + /** + * Lock: m_write_lock + */ + size_t m_send_buffer_size; + + /// buffer holding the various parts of the current message being writen + /** + * Lock m_write_lock + */ + std::vector m_send_buffer; + + /// a list of pointers to hold on to the messages being written to keep them + /// from going out of scope before the write is complete. + std::vector m_current_msgs; + + /// True if there is currently an outstanding transport write + /** + * Lock m_write_lock + */ + bool m_write_flag; + + /// True if this connection is presently reading new data + bool m_read_flag; + + // connection data + request_type m_request; + response_type m_response; + uri_ptr m_uri; + std::string m_subprotocol; + + // connection data that might not be necessary to keep around for the life + // of the whole connection. + std::vector m_requested_subprotocols; + + bool const m_is_server; + const lib::shared_ptr m_alog; + const lib::shared_ptr m_elog; + + rng_type & m_rng; + + // Close state + /// Close code that was sent on the wire by this endpoint + close::status::value m_local_close_code; + + /// Close reason that was sent on the wire by this endpoint + std::string m_local_close_reason; + + /// Close code that was received on the wire from the remote endpoint + close::status::value m_remote_close_code; + + /// Close reason that was received on the wire from the remote endpoint + std::string m_remote_close_reason; + + /// Detailed internal error code + lib::error_code m_ec; + + /// A flag that gets set once it is determined that the connection is an + /// HTTP connection and not a WebSocket one. + bool m_is_http; + + /// A flag that gets set when the completion of an http connection is + /// deferred until later. + session::http_state::value m_http_state; + + bool m_was_clean; +}; + +} // namespace websocketpp + +#include + +#endif // WEBSOCKETPP_CONNECTION_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/connection_base.hpp b/thirdparty/websocketpp/include/websocketpp/connection_base.hpp new file mode 100644 index 0000000..5515898 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/connection_base.hpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2014, 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_CONNECTION_BASE_HPP +#define WEBSOCKETPP_CONNECTION_BASE_HPP + +namespace websocketpp { + +/// Stub for user supplied base class. +class connection_base {}; + +} // namespace websocketpp + +#endif // WEBSOCKETPP_CONNECTION_BASE_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/endpoint.hpp b/thirdparty/websocketpp/include/websocketpp/endpoint.hpp new file mode 100644 index 0000000..00fee50 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/endpoint.hpp @@ -0,0 +1,700 @@ +/* + * Copyright (c) 2014, 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_ENDPOINT_HPP +#define WEBSOCKETPP_ENDPOINT_HPP + +#include + +#include +#include + +#include + +namespace websocketpp { + +/// Creates and manages connections associated with a WebSocket endpoint +template +class endpoint : public config::transport_type, public config::endpoint_base { +public: + // Import appropriate types from our helper class + // See endpoint_types for more details. + typedef endpoint type; + + /// Type of the transport component of this endpoint + typedef typename config::transport_type transport_type; + /// Type of the concurrency component of this endpoint + typedef typename config::concurrency_type concurrency_type; + + /// Type of the connections that this endpoint creates + typedef connection connection_type; + /// Shared pointer to connection_type + typedef typename connection_type::ptr connection_ptr; + /// Weak pointer to connection type + typedef typename connection_type::weak_ptr connection_weak_ptr; + + /// Type of the transport component of the connections that this endpoint + /// creates + typedef typename transport_type::transport_con_type transport_con_type; + /// Type of a shared pointer to the transport component of the connections + /// that this endpoint creates. + typedef typename transport_con_type::ptr transport_con_ptr; + + /// Type of message_handler + typedef typename connection_type::message_handler message_handler; + /// Type of message pointers that this endpoint uses + typedef typename connection_type::message_ptr message_ptr; + + /// Type of error logger + typedef typename config::elog_type elog_type; + /// Type of access logger + typedef typename config::alog_type alog_type; + + /// Type of our concurrency policy's scoped lock object + typedef typename concurrency_type::scoped_lock_type scoped_lock_type; + /// Type of our concurrency policy's mutex object + typedef typename concurrency_type::mutex_type mutex_type; + + /// Type of RNG + typedef typename config::rng_type rng_type; + + // TODO: organize these + typedef typename connection_type::termination_handler termination_handler; + + // This would be ideal. Requires C++11 though + //friend connection; + + explicit endpoint(bool p_is_server) + : m_alog(new alog_type(config::alog_level, log::channel_type_hint::access)) + , m_elog(new elog_type(config::elog_level, log::channel_type_hint::error)) + , m_user_agent(::websocketpp::user_agent) + , m_open_handshake_timeout_dur(config::timeout_open_handshake) + , m_close_handshake_timeout_dur(config::timeout_close_handshake) + , m_pong_timeout_dur(config::timeout_pong) + , m_max_message_size(config::max_message_size) + , m_max_http_body_size(config::max_http_body_size) + , m_is_server(p_is_server) + { + m_alog->set_channels(config::alog_level); + m_elog->set_channels(config::elog_level); + + m_alog->write(log::alevel::devel, "endpoint constructor"); + + transport_type::init_logging(m_alog, m_elog); + } + + + /// Destructor + ~endpoint() {} + + #ifdef _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_ + // no copy constructor because endpoints are not copyable + endpoint(endpoint &) = delete; + + // no copy assignment operator because endpoints are not copyable + endpoint & operator=(endpoint const &) = delete; + #endif // _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_ + + #ifdef _WEBSOCKETPP_MOVE_SEMANTICS_ + /// Move constructor + endpoint(endpoint && o) + : config::transport_type(std::move(o)) + , config::endpoint_base(std::move(o)) + , m_alog(std::move(o.m_alog)) + , m_elog(std::move(o.m_elog)) + , m_user_agent(std::move(o.m_user_agent)) + , m_open_handler(std::move(o.m_open_handler)) + + , m_close_handler(std::move(o.m_close_handler)) + , m_fail_handler(std::move(o.m_fail_handler)) + , m_ping_handler(std::move(o.m_ping_handler)) + , m_pong_handler(std::move(o.m_pong_handler)) + , m_pong_timeout_handler(std::move(o.m_pong_timeout_handler)) + , m_interrupt_handler(std::move(o.m_interrupt_handler)) + , m_http_handler(std::move(o.m_http_handler)) + , m_validate_handler(std::move(o.m_validate_handler)) + , m_message_handler(std::move(o.m_message_handler)) + + , m_open_handshake_timeout_dur(o.m_open_handshake_timeout_dur) + , m_close_handshake_timeout_dur(o.m_close_handshake_timeout_dur) + , m_pong_timeout_dur(o.m_pong_timeout_dur) + , m_max_message_size(o.m_max_message_size) + , m_max_http_body_size(o.m_max_http_body_size) + + , m_rng(std::move(o.m_rng)) + , m_is_server(o.m_is_server) + {} + + #ifdef _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_ + // no move assignment operator because of const member variables + endpoint & operator=(endpoint &&) = delete; + #endif // _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_ + + #endif // _WEBSOCKETPP_MOVE_SEMANTICS_ + + + /// Returns the user agent string that this endpoint will use + /** + * Returns the user agent string that this endpoint will use when creating + * new connections. + * + * The default value for this version is stored in websocketpp::user_agent + * + * @return The user agent string. + */ + std::string get_user_agent() const { + scoped_lock_type guard(m_mutex); + return m_user_agent; + } + + /// Sets the user agent string that this endpoint will use + /** + * Sets the identifier that this endpoint will use when creating new + * connections. Changing this value will only affect future connections. + * For client endpoints this will be sent as the "User-Agent" header in + * outgoing requests. For server endpoints this will be sent in the "Server" + * response header. + * + * Setting this value to the empty string will suppress the use of the + * Server and User-Agent headers. This is typically done to hide + * implementation details for security purposes. + * + * For best results set this before accepting or opening connections. + * + * The default value for this version is stored in websocketpp::user_agent + * + * This can be overridden on an individual connection basis by setting a + * custom "Server" header during the validate handler or "User-Agent" + * header on a connection before calling connect(). + * + * @param ua The string to set the user agent to. + */ + void set_user_agent(std::string const & ua) { + scoped_lock_type guard(m_mutex); + m_user_agent = ua; + } + + /// Returns whether or not this endpoint is a server. + /** + * @return Whether or not this endpoint is a server + */ + bool is_server() const { + return m_is_server; + } + + /********************************/ + /* Pass-through logging adaptor */ + /********************************/ + + /// Set Access logging channel + /** + * Set the access logger's channel value. The value is a number whose + * interpretation depends on the logging policy in use. + * + * @param channels The channel value(s) to set + */ + void set_access_channels(log::level channels) { + m_alog->set_channels(channels); + } + + /// Clear Access logging channels + /** + * Clear the access logger's channel value. The value is a number whose + * interpretation depends on the logging policy in use. + * + * @param channels The channel value(s) to clear + */ + void clear_access_channels(log::level channels) { + m_alog->clear_channels(channels); + } + + /// Set Error logging channel + /** + * Set the error logger's channel value. The value is a number whose + * interpretation depends on the logging policy in use. + * + * @param channels The channel value(s) to set + */ + void set_error_channels(log::level channels) { + m_elog->set_channels(channels); + } + + /// Clear Error logging channels + /** + * Clear the error logger's channel value. The value is a number whose + * interpretation depends on the logging policy in use. + * + * @param channels The channel value(s) to clear + */ + void clear_error_channels(log::level channels) { + m_elog->clear_channels(channels); + } + + /// Get reference to access logger + /** + * @return A reference to the access logger + */ + alog_type & get_alog() { + return *m_alog; + } + + /// Get reference to error logger + /** + * @return A reference to the error logger + */ + elog_type & get_elog() { + return *m_elog; + } + + /*************************/ + /* Set Handler functions */ + /*************************/ + + void set_open_handler(open_handler h) { + m_alog->write(log::alevel::devel,"set_open_handler"); + scoped_lock_type guard(m_mutex); + m_open_handler = h; + } + void set_close_handler(close_handler h) { + m_alog->write(log::alevel::devel,"set_close_handler"); + scoped_lock_type guard(m_mutex); + m_close_handler = h; + } + void set_fail_handler(fail_handler h) { + m_alog->write(log::alevel::devel,"set_fail_handler"); + scoped_lock_type guard(m_mutex); + m_fail_handler = h; + } + void set_ping_handler(ping_handler h) { + m_alog->write(log::alevel::devel,"set_ping_handler"); + scoped_lock_type guard(m_mutex); + m_ping_handler = h; + } + void set_pong_handler(pong_handler h) { + m_alog->write(log::alevel::devel,"set_pong_handler"); + scoped_lock_type guard(m_mutex); + m_pong_handler = h; + } + void set_pong_timeout_handler(pong_timeout_handler h) { + m_alog->write(log::alevel::devel,"set_pong_timeout_handler"); + scoped_lock_type guard(m_mutex); + m_pong_timeout_handler = h; + } + void set_interrupt_handler(interrupt_handler h) { + m_alog->write(log::alevel::devel,"set_interrupt_handler"); + scoped_lock_type guard(m_mutex); + m_interrupt_handler = h; + } + void set_http_handler(http_handler h) { + m_alog->write(log::alevel::devel,"set_http_handler"); + scoped_lock_type guard(m_mutex); + m_http_handler = h; + } + void set_validate_handler(validate_handler h) { + m_alog->write(log::alevel::devel,"set_validate_handler"); + scoped_lock_type guard(m_mutex); + m_validate_handler = h; + } + void set_message_handler(message_handler h) { + m_alog->write(log::alevel::devel,"set_message_handler"); + scoped_lock_type guard(m_mutex); + m_message_handler = h; + } + + ////////////////////////////////////////// + // Connection timeouts and other limits // + ////////////////////////////////////////// + + /// Set open handshake timeout + /** + * Sets the length of time the library will wait after an opening handshake + * has been initiated before cancelling it. This can be used to prevent + * excessive wait times for outgoing clients or excessive resource usage + * from broken clients or DoS attacks on servers. + * + * Connections that time out will have their fail handlers called with the + * open_handshake_timeout error code. + * + * The default value is specified via the compile time config value + * 'timeout_open_handshake'. The default value in the core config + * is 5000ms. A value of 0 will disable the timer entirely. + * + * To be effective, the transport you are using must support timers. See + * the documentation for your transport policy for details about its + * timer support. + * + * @param dur The length of the open handshake timeout in ms + */ + void set_open_handshake_timeout(long dur) { + scoped_lock_type guard(m_mutex); + m_open_handshake_timeout_dur = dur; + } + + /// Set close handshake timeout + /** + * Sets the length of time the library will wait after a closing handshake + * has been initiated before cancelling it. This can be used to prevent + * excessive wait times for outgoing clients or excessive resource usage + * from broken clients or DoS attacks on servers. + * + * Connections that time out will have their close handlers called with the + * close_handshake_timeout error code. + * + * The default value is specified via the compile time config value + * 'timeout_close_handshake'. The default value in the core config + * is 5000ms. A value of 0 will disable the timer entirely. + * + * To be effective, the transport you are using must support timers. See + * the documentation for your transport policy for details about its + * timer support. + * + * @param dur The length of the close handshake timeout in ms + */ + void set_close_handshake_timeout(long dur) { + scoped_lock_type guard(m_mutex); + m_close_handshake_timeout_dur = dur; + } + + /// Set pong timeout + /** + * Sets the length of time the library will wait for a pong response to a + * ping. This can be used as a keepalive or to detect broken connections. + * + * Pong responses that time out will have the pong timeout handler called. + * + * The default value is specified via the compile time config value + * 'timeout_pong'. The default value in the core config + * is 5000ms. A value of 0 will disable the timer entirely. + * + * To be effective, the transport you are using must support timers. See + * the documentation for your transport policy for details about its + * timer support. + * + * @param dur The length of the pong timeout in ms + */ + void set_pong_timeout(long dur) { + scoped_lock_type guard(m_mutex); + m_pong_timeout_dur = dur; + } + + /// Get default maximum message size + /** + * Get the default maximum message size that will be used for new + * connections created by this endpoint. The maximum message size determines + * the point at which the connection will fail a connection with the + * message_too_big protocol error. + * + * The default is set by the max_message_size value from the template config + * + * @since 0.3.0 + */ + size_t get_max_message_size() const { + return m_max_message_size; + } + + /// Set default maximum message size + /** + * Set the default maximum message size that will be used for new + * connections created by this endpoint. Maximum message size determines the + * point at which the connection will fail a connection with the + * message_too_big protocol error. + * + * The default is set by the max_message_size value from the template config + * + * @since 0.3.0 + * + * @param new_value The value to set as the maximum message size. + */ + void set_max_message_size(size_t new_value) { + m_max_message_size = new_value; + } + + /// Get maximum HTTP message body size + /** + * Get maximum HTTP message body size. Maximum message body size determines + * the point at which the connection will stop reading an HTTP request whose + * body is too large. + * + * The default is set by the max_http_body_size value from the template + * config + * + * @since 0.5.0 + * + * @return The maximum HTTP message body size + */ + size_t get_max_http_body_size() const { + return m_max_http_body_size; + } + + /// Set maximum HTTP message body size + /** + * Set maximum HTTP message body size. Maximum message body size determines + * the point at which the connection will stop reading an HTTP request whose + * body is too large. + * + * The default is set by the max_http_body_size value from the template + * config + * + * @since 0.5.1 + * + * @param new_value The value to set as the maximum message size. + */ + void set_max_http_body_size(size_t new_value) { + m_max_http_body_size = new_value; + } + + /*************************************/ + /* Connection pass through functions */ + /*************************************/ + + /** + * These functions act as adaptors to their counterparts in connection. They + * can produce one additional type of error, the bad_connection error, that + * indicates that the conversion from connection_hdl to connection_ptr + * failed due to the connection not existing anymore. Each method has a + * default and an exception free varient. + */ + + void interrupt(connection_hdl hdl, lib::error_code & ec); + void interrupt(connection_hdl hdl); + + /// Pause reading of new data (exception free) + /** + * Signals to the connection to halt reading of new data. While reading is + * paused, the connection will stop reading from its associated socket. In + * turn this will result in TCP based flow control kicking in and slowing + * data flow from the remote endpoint. + * + * This is useful for applications that push new requests to a queue to be + * processed by another thread and need a way to signal when their request + * queue is full without blocking the network processing thread. + * + * Use `resume_reading()` to resume. + * + * If supported by the transport this is done asynchronously. As such + * reading may not stop until the current read operation completes. + * Typically you can expect to receive no more bytes after initiating a read + * pause than the size of the read buffer. + * + * If reading is paused for this connection already nothing is changed. + */ + void pause_reading(connection_hdl hdl, lib::error_code & ec); + + /// Pause reading of new data + void pause_reading(connection_hdl hdl); + + /// Resume reading of new data (exception free) + /** + * Signals to the connection to resume reading of new data after it was + * paused by `pause_reading()`. + * + * If reading is not paused for this connection already nothing is changed. + */ + void resume_reading(connection_hdl hdl, lib::error_code & ec); + + /// Resume reading of new data + void resume_reading(connection_hdl hdl); + + /// Send deferred HTTP Response + /** + * Sends an http response to an HTTP connection that was deferred. This will + * send a complete response including all headers, status line, and body + * text. The connection will be closed afterwards. + * + * Exception free variant + * + * @since 0.6.0 + * + * @param hdl The connection to send the response on + * @param ec A status code, zero on success, non-zero otherwise + */ + void send_http_response(connection_hdl hdl, lib::error_code & ec); + + /// Send deferred HTTP Response (exception free) + /** + * Sends an http response to an HTTP connection that was deferred. This will + * send a complete response including all headers, status line, and body + * text. The connection will be closed afterwards. + * + * Exception variant + * + * @since 0.6.0 + * + * @param hdl The connection to send the response on + */ + void send_http_response(connection_hdl hdl); + + /// Create a message and add it to the outgoing send queue (exception free) + /** + * Convenience method to send a message given a payload string and an opcode + * + * @param [in] hdl The handle identifying the connection to send via. + * @param [in] payload The payload string to generated the message with + * @param [in] op The opcode to generated the message with. + * @param [out] ec A code to fill in for errors + */ + void send(connection_hdl hdl, std::string const & payload, + frame::opcode::value op, lib::error_code & ec); + /// Create a message and add it to the outgoing send queue + /** + * Convenience method to send a message given a payload string and an opcode + * + * @param [in] hdl The handle identifying the connection to send via. + * @param [in] payload The payload string to generated the message with + * @param [in] op The opcode to generated the message with. + * @param [out] ec A code to fill in for errors + */ + void send(connection_hdl hdl, std::string const & payload, + frame::opcode::value op); + + void send(connection_hdl hdl, void const * payload, size_t len, + frame::opcode::value op, lib::error_code & ec); + void send(connection_hdl hdl, void const * payload, size_t len, + frame::opcode::value op); + + void send(connection_hdl hdl, message_ptr msg, lib::error_code & ec); + void send(connection_hdl hdl, message_ptr msg); + + void close(connection_hdl hdl, close::status::value const code, + std::string const & reason, lib::error_code & ec); + void close(connection_hdl hdl, close::status::value const code, + std::string const & reason); + + /// Send a ping to a specific connection + /** + * @since 0.3.0-alpha3 + * + * @param [in] hdl The connection_hdl of the connection to send to. + * @param [in] payload The payload string to send. + * @param [out] ec A reference to an error code to fill in + */ + void ping(connection_hdl hdl, std::string const & payload, + lib::error_code & ec); + /// Send a ping to a specific connection + /** + * Exception variant of `ping` + * + * @since 0.3.0-alpha3 + * + * @param [in] hdl The connection_hdl of the connection to send to. + * @param [in] payload The payload string to send. + */ + void ping(connection_hdl hdl, std::string const & payload); + + /// Send a pong to a specific connection + /** + * @since 0.3.0-alpha3 + * + * @param [in] hdl The connection_hdl of the connection to send to. + * @param [in] payload The payload string to send. + * @param [out] ec A reference to an error code to fill in + */ + void pong(connection_hdl hdl, std::string const & payload, + lib::error_code & ec); + /// Send a pong to a specific connection + /** + * Exception variant of `pong` + * + * @since 0.3.0-alpha3 + * + * @param [in] hdl The connection_hdl of the connection to send to. + * @param [in] payload The payload string to send. + */ + void pong(connection_hdl hdl, std::string const & payload); + + /// Retrieves a connection_ptr from a connection_hdl (exception free) + /** + * Converting a weak pointer to shared_ptr is not thread safe because the + * pointer could be deleted at any time. + * + * NOTE: This method may be called by handler to upgrade its handle to a + * full connection_ptr. That full connection may then be used safely for the + * remainder of the handler body. get_con_from_hdl and the resulting + * connection_ptr are NOT safe to use outside the handler loop. + * + * @param hdl The connection handle to translate + * + * @return the connection_ptr. May be NULL if the handle was invalid. + */ + connection_ptr get_con_from_hdl(connection_hdl hdl, lib::error_code & ec) { + connection_ptr con = lib::static_pointer_cast( + hdl.lock()); + if (!con) { + ec = error::make_error_code(error::bad_connection); + } + return con; + } + + /// Retrieves a connection_ptr from a connection_hdl (exception version) + connection_ptr get_con_from_hdl(connection_hdl hdl) { + lib::error_code ec; + connection_ptr con = this->get_con_from_hdl(hdl,ec); + if (ec) { + throw exception(ec); + } + return con; + } +protected: + connection_ptr create_connection(); + + lib::shared_ptr m_alog; + lib::shared_ptr m_elog; +private: + // dynamic settings + std::string m_user_agent; + + open_handler m_open_handler; + close_handler m_close_handler; + fail_handler m_fail_handler; + ping_handler m_ping_handler; + pong_handler m_pong_handler; + pong_timeout_handler m_pong_timeout_handler; + interrupt_handler m_interrupt_handler; + http_handler m_http_handler; + validate_handler m_validate_handler; + message_handler m_message_handler; + + long m_open_handshake_timeout_dur; + long m_close_handshake_timeout_dur; + long m_pong_timeout_dur; + size_t m_max_message_size; + size_t m_max_http_body_size; + + rng_type m_rng; + + // static settings + bool const m_is_server; + + // endpoint state + mutable mutex_type m_mutex; +}; + +} // namespace websocketpp + +#include + +#endif // WEBSOCKETPP_ENDPOINT_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/endpoint_base.hpp b/thirdparty/websocketpp/include/websocketpp/endpoint_base.hpp new file mode 100644 index 0000000..6ffb37e --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/endpoint_base.hpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2014, 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_ENDPOINT_BASE_HPP +#define WEBSOCKETPP_ENDPOINT_BASE_HPP + +namespace websocketpp { + +/// Stub for user supplied base class. +class endpoint_base {}; + +} // namespace websocketpp + +#endif // WEBSOCKETPP_ENDPOINT_BASE_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/error.hpp b/thirdparty/websocketpp/include/websocketpp/error.hpp new file mode 100644 index 0000000..c336bb0 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/error.hpp @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2014, 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_ERROR_HPP +#define WEBSOCKETPP_ERROR_HPP + +#include +#include +#include + +#include +#include + +namespace websocketpp { + +/// Combination error code / string type for returning two values +typedef std::pair err_str_pair; + +/// Library level error codes +namespace error { +enum value { + /// Catch-all library error + general = 1, + + /// send attempted when endpoint write queue was full + send_queue_full, + + /// Attempted an operation using a payload that was improperly formatted + /// ex: invalid UTF8 encoding on a text message. + payload_violation, + + /// Attempted to open a secure connection with an insecure endpoint + endpoint_not_secure, + + /// Attempted an operation that required an endpoint that is no longer + /// available. This is usually because the endpoint went out of scope + /// before a connection that it created. + endpoint_unavailable, + + /// An invalid uri was supplied + invalid_uri, + + /// The endpoint is out of outgoing message buffers + no_outgoing_buffers, + + /// The endpoint is out of incoming message buffers + no_incoming_buffers, + + /// The connection was in the wrong state for this operation + invalid_state, + + /// Unable to parse close code + bad_close_code, + + /// Close code is in a reserved range + reserved_close_code, + + /// Close code is invalid + invalid_close_code, + + /// Invalid UTF-8 + invalid_utf8, + + /// Invalid subprotocol + invalid_subprotocol, + + /// An operation was attempted on a connection that did not exist or was + /// already deleted. + bad_connection, + + /// Unit testing utility error code + test, + + /// Connection creation attempted failed + con_creation_failed, + + /// Selected subprotocol was not requested by the client + unrequested_subprotocol, + + /// Attempted to use a client specific feature on a server endpoint + client_only, + + /// Attempted to use a server specific feature on a client endpoint + server_only, + + /// HTTP connection ended + http_connection_ended, + + /// WebSocket opening handshake timed out + open_handshake_timeout, + + /// WebSocket close handshake timed out + close_handshake_timeout, + + /// Invalid port in URI + invalid_port, + + /// An async accept operation failed because the underlying transport has been + /// requested to not listen for new connections anymore. + async_accept_not_listening, + + /// The requested operation was canceled + operation_canceled, + + /// Connection rejected + rejected, + + /// Upgrade Required. This happens if an HTTP request is made to a + /// WebSocket++ server that doesn't implement an http handler + upgrade_required, + + /// Invalid WebSocket protocol version + invalid_version, + + /// Unsupported WebSocket protocol version + unsupported_version, + + /// HTTP parse error + http_parse_error, + + /// Extension negotiation failed + extension_neg_failed +}; // enum value + + +class category : public lib::error_category { +public: + category() {} + + char const * name() const _WEBSOCKETPP_NOEXCEPT_TOKEN_ { + return "websocketpp"; + } + + std::string message(int value) const { + switch(value) { + case error::general: + return "Generic error"; + case error::send_queue_full: + return "send queue full"; + case error::payload_violation: + return "payload violation"; + case error::endpoint_not_secure: + return "endpoint not secure"; + case error::endpoint_unavailable: + return "endpoint not available"; + case error::invalid_uri: + return "invalid uri"; + case error::no_outgoing_buffers: + return "no outgoing message buffers"; + case error::no_incoming_buffers: + return "no incoming message buffers"; + case error::invalid_state: + return "invalid state"; + case error::bad_close_code: + return "Unable to extract close code"; + case error::invalid_close_code: + return "Extracted close code is in an invalid range"; + case error::reserved_close_code: + return "Extracted close code is in a reserved range"; + case error::invalid_utf8: + return "Invalid UTF-8"; + case error::invalid_subprotocol: + return "Invalid subprotocol"; + case error::bad_connection: + return "Bad Connection"; + case error::test: + return "Test Error"; + case error::con_creation_failed: + return "Connection creation attempt failed"; + case error::unrequested_subprotocol: + return "Selected subprotocol was not requested by the client"; + case error::client_only: + return "Feature not available on server endpoints"; + case error::server_only: + return "Feature not available on client endpoints"; + case error::http_connection_ended: + return "HTTP connection ended"; + case error::open_handshake_timeout: + return "The opening handshake timed out"; + case error::close_handshake_timeout: + return "The closing handshake timed out"; + case error::invalid_port: + return "Invalid URI port"; + case error::async_accept_not_listening: + return "Async Accept not listening"; + case error::operation_canceled: + return "Operation canceled"; + case error::rejected: + return "Connection rejected"; + case error::upgrade_required: + return "Upgrade required"; + case error::invalid_version: + return "Invalid version"; + case error::unsupported_version: + return "Unsupported version"; + case error::http_parse_error: + return "HTTP parse error"; + case error::extension_neg_failed: + return "Extension negotiation failed"; + default: + return "Unknown"; + } + } +}; + +inline const lib::error_category& get_category() { + static category instance; + return instance; +} + +inline lib::error_code make_error_code(error::value e) { + return lib::error_code(static_cast(e), get_category()); +} + +} // namespace error +} // namespace websocketpp + +_WEBSOCKETPP_ERROR_CODE_ENUM_NS_START_ +template<> struct is_error_code_enum +{ + static bool const value = true; +}; +_WEBSOCKETPP_ERROR_CODE_ENUM_NS_END_ + +namespace websocketpp { + +class exception : public std::exception { +public: + exception(std::string const & msg, lib::error_code ec = make_error_code(error::general)) + : m_msg(msg.empty() ? ec.message() : msg), m_code(ec) + {} + + explicit exception(lib::error_code ec) + : m_msg(ec.message()), m_code(ec) + {} + + ~exception() throw() {} + + virtual char const * what() const throw() { + return m_msg.c_str(); + } + + lib::error_code code() const throw() { + return m_code; + } + + const std::string m_msg; + lib::error_code m_code; +}; + +} // namespace websocketpp + +#endif // WEBSOCKETPP_ERROR_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/extensions/extension.hpp b/thirdparty/websocketpp/include/websocketpp/extensions/extension.hpp new file mode 100644 index 0000000..cdcfba5 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/extensions/extension.hpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2014, 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_EXTENSION_HPP +#define WEBSOCKETPP_EXTENSION_HPP + +#include +#include + +#include +#include + +namespace websocketpp { + +/** + * Some generic information about extensions + * + * Each extension object has an implemented flag. It can be retrieved by calling + * is_implemented(). This compile time flag indicates whether or not the object + * in question actually implements the extension or if it is a placeholder stub + * + * Each extension object also has an enabled flag. It can be retrieved by + * calling is_enabled(). This runtime flag indicates whether or not the + * extension has been negotiated for this connection. + */ +namespace extensions { + +namespace error { +enum value { + /// Catch all + general = 1, + + /// Extension disabled + disabled +}; + +class category : public lib::error_category { +public: + category() {} + + const char *name() const _WEBSOCKETPP_NOEXCEPT_TOKEN_ { + return "websocketpp.extension"; + } + + std::string message(int value) const { + switch(value) { + case general: + return "Generic extension error"; + case disabled: + return "Use of methods from disabled extension"; + default: + return "Unknown permessage-compress error"; + } + } +}; + +inline lib::error_category const & get_category() { + static category instance; + return instance; +} + +inline lib::error_code make_error_code(error::value e) { + return lib::error_code(static_cast(e), get_category()); +} + +} // namespace error +} // namespace extensions +} // namespace websocketpp + +_WEBSOCKETPP_ERROR_CODE_ENUM_NS_START_ +template<> struct is_error_code_enum + +{ + static const bool value = true; +}; +_WEBSOCKETPP_ERROR_CODE_ENUM_NS_END_ + +#endif // WEBSOCKETPP_EXTENSION_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/extensions/permessage_deflate/disabled.hpp b/thirdparty/websocketpp/include/websocketpp/extensions/permessage_deflate/disabled.hpp new file mode 100644 index 0000000..2063b99 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/extensions/permessage_deflate/disabled.hpp @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2014, 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_EXTENSION_PERMESSAGE_DEFLATE_DISABLED_HPP +#define WEBSOCKETPP_EXTENSION_PERMESSAGE_DEFLATE_DISABLED_HPP + +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace websocketpp { +namespace extensions { +namespace permessage_deflate { + +/// Stub class for use when disabling permessage_deflate extension +/** + * This class is a stub that implements the permessage_deflate interface + * with minimal dependencies. It is used to disable permessage_deflate + * functionality at compile time without loading any unnecessary code. + */ +template +class disabled { + typedef std::pair err_str_pair; + +public: + /// Negotiate extension + /** + * The disabled extension always fails the negotiation with a disabled + * error. + * + * @param offer Attribute from client's offer + * @return Status code and value to return to remote endpoint + */ + err_str_pair negotiate(http::attribute_list const &) { + return make_pair(make_error_code(error::disabled),std::string()); + } + + /// Initialize state + /** + * For the disabled extension state initialization is a no-op. + * + * @param is_server True to initialize as a server, false for a client. + * @return A code representing the error that occurred, if any + */ + lib::error_code init(bool) { + return lib::error_code(); + } + + /// Returns true if the extension is capable of providing + /// permessage_deflate functionality + bool is_implemented() const { + return false; + } + + /// Returns true if permessage_deflate functionality is active for this + /// connection + bool is_enabled() const { + return false; + } + + /// Generate extension offer + /** + * Creates an offer string to include in the Sec-WebSocket-Extensions + * header of outgoing client requests. + * + * @return A WebSocket extension offer string for this extension + */ + std::string generate_offer() const { + return ""; + } + + /// Compress bytes + /** + * @param [in] in String to compress + * @param [out] out String to append compressed bytes to + * @return Error or status code + */ + lib::error_code compress(std::string const &, std::string &) { + return make_error_code(error::disabled); + } + + /// Decompress bytes + /** + * @param buf Byte buffer to decompress + * @param len Length of buf + * @param out String to append decompressed bytes to + * @return Error or status code + */ + lib::error_code decompress(uint8_t const *, size_t, std::string &) { + return make_error_code(error::disabled); + } +}; + +} // namespace permessage_deflate +} // namespace extensions +} // namespace websocketpp + +#endif // WEBSOCKETPP_EXTENSION_PERMESSAGE_DEFLATE_DISABLED_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/extensions/permessage_deflate/enabled.hpp b/thirdparty/websocketpp/include/websocketpp/extensions/permessage_deflate/enabled.hpp new file mode 100644 index 0000000..1ad99cc --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/extensions/permessage_deflate/enabled.hpp @@ -0,0 +1,817 @@ +/* + * 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_PROCESSOR_EXTENSION_PERMESSAGEDEFLATE_HPP +#define WEBSOCKETPP_PROCESSOR_EXTENSION_PERMESSAGEDEFLATE_HPP + + +#include +#include +#include +#include +#include +#include + +#include + +#include "zlib.h" + +#include +#include +#include + +namespace websocketpp { +namespace extensions { + +/// Implementation of RFC 7692, the permessage-deflate WebSocket extension +/** + * ### permessage-deflate interface + * + * **init**\n + * `lib::error_code init(bool is_server)`\n + * Performs initialization + * + * **is_implimented**\n + * `bool is_implimented()`\n + * Returns whether or not the object impliments the extension or not + * + * **is_enabled**\n + * `bool is_enabled()`\n + * Returns whether or not the extension was negotiated for the current + * connection + * + * **generate_offer**\n + * `std::string generate_offer() const`\n + * Create an extension offer string based on local policy + * + * **validate_response**\n + * `lib::error_code validate_response(http::attribute_list const & response)`\n + * Negotiate the parameters of extension use + * + * **negotiate**\n + * `err_str_pair negotiate(http::attribute_list const & attributes)`\n + * Negotiate the parameters of extension use + * + * **compress**\n + * `lib::error_code compress(std::string const & in, std::string & out)`\n + * Compress the bytes in `in` and append them to `out` + * + * **decompress**\n + * `lib::error_code decompress(uint8_t const * buf, size_t len, std::string & + * out)`\n + * Decompress `len` bytes from `buf` and append them to string `out` + */ +namespace permessage_deflate { + +/// Permessage deflate error values +namespace error { +enum value { + /// Catch all + general = 1, + + /// Invalid extension attributes + invalid_attributes, + + /// Invalid extension attribute value + invalid_attribute_value, + + /// Invalid megotiation mode + invalid_mode, + + /// Unsupported extension attributes + unsupported_attributes, + + /// Invalid value for max_window_bits + invalid_max_window_bits, + + /// ZLib Error + zlib_error, + + /// Uninitialized + uninitialized, +}; + +/// Permessage-deflate error category +class category : public lib::error_category { +public: + category() {} + + char const * name() const _WEBSOCKETPP_NOEXCEPT_TOKEN_ { + return "websocketpp.extension.permessage-deflate"; + } + + std::string message(int value) const { + switch(value) { + case general: + return "Generic permessage-compress error"; + case invalid_attributes: + return "Invalid extension attributes"; + case invalid_attribute_value: + return "Invalid extension attribute value"; + case invalid_mode: + return "Invalid permessage-deflate negotiation mode"; + case unsupported_attributes: + return "Unsupported extension attributes"; + case invalid_max_window_bits: + return "Invalid value for max_window_bits"; + case zlib_error: + return "A zlib function returned an error"; + case uninitialized: + return "Deflate extension must be initialized before use"; + default: + return "Unknown permessage-compress error"; + } + } +}; + +/// Get a reference to a static copy of the permessage-deflate error category +inline lib::error_category const & get_category() { + static category instance; + return instance; +} + +/// Create an error code in the permessage-deflate category +inline lib::error_code make_error_code(error::value e) { + return lib::error_code(static_cast(e), get_category()); +} + +} // namespace error +} // namespace permessage_deflate +} // namespace extensions +} // namespace websocketpp + +_WEBSOCKETPP_ERROR_CODE_ENUM_NS_START_ +template<> struct is_error_code_enum + +{ + static bool const value = true; +}; +_WEBSOCKETPP_ERROR_CODE_ENUM_NS_END_ +namespace websocketpp { +namespace extensions { +namespace permessage_deflate { + +/// Default value for server_max_window_bits as defined by RFC 7692 +static uint8_t const default_server_max_window_bits = 15; +/// Minimum value for server_max_window_bits as defined by RFC 7692 +/** + * NOTE: A value of 8 is not actually supported by zlib, the deflate + * library that WebSocket++ uses. To preserve backwards compatibility + * with RFC 7692 and previous versions of the library a value of 8 + * is accepted by the library but will always be negotiated as 9. + */ +static uint8_t const min_server_max_window_bits = 8; +/// Maximum value for server_max_window_bits as defined by RFC 7692 +static uint8_t const max_server_max_window_bits = 15; + +/// Default value for client_max_window_bits as defined by RFC 7692 +static uint8_t const default_client_max_window_bits = 15; +/// Minimum value for client_max_window_bits as defined by RFC 7692 +/** + * NOTE: A value of 8 is not actually supported by zlib, the deflate + * library that WebSocket++ uses. To preserve backwards compatibility + * with RFC 7692 and previous versions of the library a value of 8 + * is accepted by the library but will always be negotiated as 9. + */ +static uint8_t const min_client_max_window_bits = 8; +/// Maximum value for client_max_window_bits as defined by RFC 7692 +static uint8_t const max_client_max_window_bits = 15; + +namespace mode { +enum value { + /// Accept any value the remote endpoint offers + accept = 1, + /// Decline any value the remote endpoint offers. Insist on defaults. + decline, + /// Use the largest value common to both offers + largest, + /// Use the smallest value common to both offers + smallest +}; +} // namespace mode + +template +class enabled { +public: + enabled() + : m_enabled(false) + , m_server_no_context_takeover(false) + , m_client_no_context_takeover(false) + , m_server_max_window_bits(15) + , m_client_max_window_bits(15) + , m_server_max_window_bits_mode(mode::accept) + , m_client_max_window_bits_mode(mode::accept) + , m_initialized(false) + , m_compress_buffer_size(8192) + { + m_dstate.zalloc = Z_NULL; + m_dstate.zfree = Z_NULL; + m_dstate.opaque = Z_NULL; + + m_istate.zalloc = Z_NULL; + m_istate.zfree = Z_NULL; + m_istate.opaque = Z_NULL; + m_istate.avail_in = 0; + m_istate.next_in = Z_NULL; + } + + ~enabled() { + if (!m_initialized) { + return; + } + + int ret = deflateEnd(&m_dstate); + + if (ret != Z_OK) { + //std::cout << "error cleaning up zlib compression state" + // << std::endl; + } + + ret = inflateEnd(&m_istate); + + if (ret != Z_OK) { + //std::cout << "error cleaning up zlib decompression state" + // << std::endl; + } + } + + /// Initialize zlib state + /** + * Note: this should be called *after* the negotiation methods. It will use + * information from the negotiation to determine how to initialize the zlib + * data structures. + * + * @todo memory level, strategy, etc are hardcoded + * + * @param is_server True to initialize as a server, false for a client. + * @return A code representing the error that occurred, if any + */ + lib::error_code init(bool is_server) { + uint8_t deflate_bits; + uint8_t inflate_bits; + + if (is_server) { + deflate_bits = m_server_max_window_bits; + inflate_bits = m_client_max_window_bits; + } else { + deflate_bits = m_client_max_window_bits; + inflate_bits = m_server_max_window_bits; + } + + int ret = deflateInit2( + &m_dstate, + Z_DEFAULT_COMPRESSION, + Z_DEFLATED, + -1*deflate_bits, + 4, // memory level 1-9 + Z_DEFAULT_STRATEGY + ); + + if (ret != Z_OK) { + return make_error_code(error::zlib_error); + } + + ret = inflateInit2( + &m_istate, + -1*inflate_bits + ); + + if (ret != Z_OK) { + return make_error_code(error::zlib_error); + } + + m_compress_buffer.reset(new unsigned char[m_compress_buffer_size]); + m_decompress_buffer.reset(new unsigned char[m_compress_buffer_size]); + if ((m_server_no_context_takeover && is_server) || + (m_client_no_context_takeover && !is_server)) + { + m_flush = Z_FULL_FLUSH; + } else { + m_flush = Z_SYNC_FLUSH; + } + m_initialized = true; + return lib::error_code(); + } + + /// Test if this object implements the permessage-deflate specification + /** + * Because this object does implieent it, it will always return true. + * + * @return Whether or not this object implements permessage-deflate + */ + bool is_implemented() const { + return true; + } + + /// Test if the extension was negotiated for this connection + /** + * Retrieves whether or not this extension is in use based on the initial + * handshake extension negotiations. + * + * @return Whether or not the extension is in use + */ + bool is_enabled() const { + return m_enabled; + } + + /// Reset server's outgoing LZ77 sliding window for each new message + /** + * Enabling this setting will cause the server's compressor to reset the + * compression state (the LZ77 sliding window) for every message. This + * means that the compressor will not look back to patterns in previous + * messages to improve compression. This will reduce the compression + * efficiency for large messages somewhat and small messages drastically. + * + * This option may reduce server compressor memory usage and client + * decompressor memory usage. + * @todo Document to what extent memory usage will be reduced + * + * For clients, this option is dependent on server support. Enabling it + * via this method does not guarantee that it will be successfully + * negotiated, only that it will be requested. + * + * For servers, no client support is required. Enabling this option on a + * server will result in its use. The server will signal to clients that + * the option will be in use so they can optimize resource usage if they + * are able. + */ + void enable_server_no_context_takeover() { + m_server_no_context_takeover = true; + } + + /// Reset client's outgoing LZ77 sliding window for each new message + /** + * Enabling this setting will cause the client's compressor to reset the + * compression state (the LZ77 sliding window) for every message. This + * means that the compressor will not look back to patterns in previous + * messages to improve compression. This will reduce the compression + * efficiency for large messages somewhat and small messages drastically. + * + * This option may reduce client compressor memory usage and server + * decompressor memory usage. + * @todo Document to what extent memory usage will be reduced + * + * This option is supported by all compliant clients and servers. Enabling + * it via either endpoint should be sufficient to ensure it is used. + */ + void enable_client_no_context_takeover() { + m_client_no_context_takeover = true; + } + + /// Limit server LZ77 sliding window size + /** + * The bits setting is the base 2 logarithm of the maximum window size that + * the server must use to compress outgoing messages. The permitted range + * is 9 to 15 inclusive. 9 represents a 512 byte window and 15 a 32KiB + * window. The default setting is 15. + * + * Mode Options: + * - accept: Accept whatever the remote endpoint offers. + * - decline: Decline any offers to deviate from the defaults + * - largest: Accept largest window size acceptable to both endpoints + * - smallest: Accept smallest window size acceptiable to both endpoints + * + * This setting is dependent on server support. A client requesting this + * setting may be rejected by the server or have the exact value used + * adjusted by the server. A server may unilaterally set this value without + * client support. + * + * NOTE: The permessage-deflate spec specifies that a value of 8 is allowed. + * Prior to version 0.8.0 a value of 8 was also allowed by this library. + * zlib, the deflate compression library that WebSocket++ uses has always + * silently adjusted a value of 8 to 9. In recent versions of zlib (1.2.9 + * and greater) a value of 8 is now explicitly rejected. WebSocket++ 0.8.0 + * continues to perform the 8->9 conversion for backwards compatibility + * purposes but this should be considered deprecated functionality. + * + * @param bits The size to request for the outgoing window size + * @param mode The mode to use for negotiating this parameter + * @return A status code + */ + lib::error_code set_server_max_window_bits(uint8_t bits, mode::value mode) { + if (bits < min_server_max_window_bits || bits > max_server_max_window_bits) { + return error::make_error_code(error::invalid_max_window_bits); + } + + // See note in doc comment above about what is happening here + if (bits == 8) { + bits = 9; + } + + m_server_max_window_bits = bits; + m_server_max_window_bits_mode = mode; + + return lib::error_code(); + } + + /// Limit client LZ77 sliding window size + /** + * The bits setting is the base 2 logarithm of the window size that the + * client must use to compress outgoing messages. The permitted range is 9 + * to 15 inclusive. 9 represents a 512 byte window and 15 a 32KiB window. + * The default setting is 15. + * + * Mode Options: + * - accept: Accept whatever the remote endpoint offers. + * - decline: Decline any offers to deviate from the defaults + * - largest: Accept largest window size acceptable to both endpoints + * - smallest: Accept smallest window size acceptiable to both endpoints + * + * This setting is dependent on client support. A client may limit its own + * outgoing window size unilaterally. A server may only limit the client's + * window size if the remote client supports that feature. + * + * NOTE: The permessage-deflate spec specifies that a value of 8 is allowed. + * Prior to version 0.8.0 a value of 8 was also allowed by this library. + * zlib, the deflate compression library that WebSocket++ uses has always + * silently adjusted a value of 8 to 9. In recent versions of zlib (1.2.9 + * and greater) a value of 8 is now explicitly rejected. WebSocket++ 0.8.0 + * continues to perform the 8->9 conversion for backwards compatibility + * purposes but this should be considered deprecated functionality. + * + * @param bits The size to request for the outgoing window size + * @param mode The mode to use for negotiating this parameter + * @return A status code + */ + lib::error_code set_client_max_window_bits(uint8_t bits, mode::value mode) { + if (bits < min_client_max_window_bits || bits > max_client_max_window_bits) { + return error::make_error_code(error::invalid_max_window_bits); + } + + // See note in doc comment above about what is happening here + if (bits == 8) { + bits = 9; + } + + m_client_max_window_bits = bits; + m_client_max_window_bits_mode = mode; + + return lib::error_code(); + } + + /// Generate extension offer + /** + * Creates an offer string to include in the Sec-WebSocket-Extensions + * header of outgoing client requests. + * + * @return A WebSocket extension offer string for this extension + */ + std::string generate_offer() const { + // TODO: this should be dynamically generated based on user settings + return "permessage-deflate; client_no_context_takeover; client_max_window_bits"; + } + + /// Validate extension response + /** + * Confirm that the server has negotiated settings compatible with our + * original offer and apply those settings to the extension state. + * + * @param response The server response attribute list to validate + * @return Validation error or 0 on success + */ + lib::error_code validate_offer(http::attribute_list const &) { + return lib::error_code(); + } + + /// Negotiate extension + /** + * Confirm that the client's extension negotiation offer has settings + * compatible with local policy. If so, generate a reply and apply those + * settings to the extension state. + * + * @param offer Attribute from client's offer + * @return Status code and value to return to remote endpoint + */ + err_str_pair negotiate(http::attribute_list const & offer) { + err_str_pair ret; + + http::attribute_list::const_iterator it; + for (it = offer.begin(); it != offer.end(); ++it) { + if (it->first == "server_no_context_takeover") { + negotiate_server_no_context_takeover(it->second,ret.first); + } else if (it->first == "client_no_context_takeover") { + negotiate_client_no_context_takeover(it->second,ret.first); + } else if (it->first == "server_max_window_bits") { + negotiate_server_max_window_bits(it->second,ret.first); + } else if (it->first == "client_max_window_bits") { + negotiate_client_max_window_bits(it->second,ret.first); + } else { + ret.first = make_error_code(error::invalid_attributes); + } + + if (ret.first) { + break; + } + } + + if (ret.first == lib::error_code()) { + m_enabled = true; + ret.second = generate_response(); + } + + return ret; + } + + /// Compress bytes + /** + * @todo: avail_in/out is 32 bit, need to fix for cases of >32 bit frames + * on 64 bit machines. + * + * @param [in] in String to compress + * @param [out] out String to append compressed bytes to + * @return Error or status code + */ + lib::error_code compress(std::string const & in, std::string & out) { + if (!m_initialized) { + return make_error_code(error::uninitialized); + } + + size_t output; + + if (in.empty()) { + uint8_t buf[6] = {0x02, 0x00, 0x00, 0x00, 0xff, 0xff}; + out.append((char *)(buf),6); + return lib::error_code(); + } + + m_dstate.avail_in = in.size(); + m_dstate.next_in = (unsigned char *)(const_cast(in.data())); + + do { + // Output to local buffer + m_dstate.avail_out = m_compress_buffer_size; + m_dstate.next_out = m_compress_buffer.get(); + + deflate(&m_dstate, m_flush); + + output = m_compress_buffer_size - m_dstate.avail_out; + + out.append((char *)(m_compress_buffer.get()),output); + } while (m_dstate.avail_out == 0); + + return lib::error_code(); + } + + /// Decompress bytes + /** + * @param buf Byte buffer to decompress + * @param len Length of buf + * @param out String to append decompressed bytes to + * @return Error or status code + */ + lib::error_code decompress(uint8_t const * buf, size_t len, std::string & + out) + { + if (!m_initialized) { + return make_error_code(error::uninitialized); + } + + int ret; + + m_istate.avail_in = len; + m_istate.next_in = const_cast(buf); + + do { + m_istate.avail_out = m_compress_buffer_size; + m_istate.next_out = m_decompress_buffer.get(); + + ret = inflate(&m_istate, Z_SYNC_FLUSH); + + if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) { + return make_error_code(error::zlib_error); + } + + out.append( + reinterpret_cast(m_decompress_buffer.get()), + m_compress_buffer_size - m_istate.avail_out + ); + } while (m_istate.avail_out == 0); + + return lib::error_code(); + } +private: + /// Generate negotiation response + /** + * @return Generate extension negotiation reponse string to send to client + */ + std::string generate_response() { + std::string ret = "permessage-deflate"; + + if (m_server_no_context_takeover) { + ret += "; server_no_context_takeover"; + } + + if (m_client_no_context_takeover) { + ret += "; client_no_context_takeover"; + } + + if (m_server_max_window_bits < default_server_max_window_bits) { + std::stringstream s; + s << int(m_server_max_window_bits); + ret += "; server_max_window_bits="+s.str(); + } + + if (m_client_max_window_bits < default_client_max_window_bits) { + std::stringstream s; + s << int(m_client_max_window_bits); + ret += "; client_max_window_bits="+s.str(); + } + + return ret; + } + + /// Negotiate server_no_context_takeover attribute + /** + * @param [in] value The value of the attribute from the offer + * @param [out] ec A reference to the error code to return errors via + */ + void negotiate_server_no_context_takeover(std::string const & value, + lib::error_code & ec) + { + if (!value.empty()) { + ec = make_error_code(error::invalid_attribute_value); + return; + } + + m_server_no_context_takeover = true; + } + + /// Negotiate client_no_context_takeover attribute + /** + * @param [in] value The value of the attribute from the offer + * @param [out] ec A reference to the error code to return errors via + */ + void negotiate_client_no_context_takeover(std::string const & value, + lib::error_code & ec) + { + if (!value.empty()) { + ec = make_error_code(error::invalid_attribute_value); + return; + } + + m_client_no_context_takeover = true; + } + + /// Negotiate server_max_window_bits attribute + /** + * When this method starts, m_server_max_window_bits will contain the server's + * preferred value and m_server_max_window_bits_mode will contain the mode the + * server wants to use to for negotiation. `value` contains the value the + * client requested that we use. + * + * options: + * - decline (ignore value, offer our default instead) + * - accept (use the value requested by the client) + * - largest (use largest value acceptable to both) + * - smallest (use smallest possible value) + * + * NOTE: As a value of 8 is no longer explicitly supported by zlib but might + * be requested for negotiation by an older client/server, if the result of + * the negotiation would be to send a value of 8, a value of 9 is offered + * instead. This ensures that WebSocket++ will only ever negotiate connections + * with compression settings explicitly supported by zlib. + * + * @param [in] value The value of the attribute from the offer + * @param [out] ec A reference to the error code to return errors via + */ + void negotiate_server_max_window_bits(std::string const & value, + lib::error_code & ec) + { + uint8_t bits = uint8_t(atoi(value.c_str())); + + if (bits < min_server_max_window_bits || bits > max_server_max_window_bits) { + ec = make_error_code(error::invalid_attribute_value); + m_server_max_window_bits = default_server_max_window_bits; + return; + } + + switch (m_server_max_window_bits_mode) { + case mode::decline: + m_server_max_window_bits = default_server_max_window_bits; + break; + case mode::accept: + m_server_max_window_bits = bits; + break; + case mode::largest: + m_server_max_window_bits = std::min(bits,m_server_max_window_bits); + break; + case mode::smallest: + m_server_max_window_bits = min_server_max_window_bits; + break; + default: + ec = make_error_code(error::invalid_mode); + m_server_max_window_bits = default_server_max_window_bits; + } + + // See note in doc comment + if (m_server_max_window_bits == 8) { + m_server_max_window_bits = 9; + } + } + + /// Negotiate client_max_window_bits attribute + /** + * When this method starts, m_client_max_window_bits and m_c2s_max_window_mode + * will contain the server's preferred values for window size and + * negotiation mode. + * + * options: + * - decline (ignore value, offer our default instead) + * - accept (use the value requested by the client) + * - largest (use largest value acceptable to both) + * - smallest (use smallest possible value) + * + * NOTE: As a value of 8 is no longer explicitly supported by zlib but might + * be requested for negotiation by an older client/server, if the result of + * the negotiation would be to send a value of 8, a value of 9 is offered + * instead. This ensures that WebSocket++ will only ever negotiate connections + * with compression settings explicitly supported by zlib. + * + * @param [in] value The value of the attribute from the offer + * @param [out] ec A reference to the error code to return errors via + */ + void negotiate_client_max_window_bits(std::string const & value, + lib::error_code & ec) + { + uint8_t bits = uint8_t(atoi(value.c_str())); + + if (value.empty()) { + bits = default_client_max_window_bits; + } else if (bits < min_client_max_window_bits || + bits > max_client_max_window_bits) + { + ec = make_error_code(error::invalid_attribute_value); + m_client_max_window_bits = default_client_max_window_bits; + return; + } + + switch (m_client_max_window_bits_mode) { + case mode::decline: + m_client_max_window_bits = default_client_max_window_bits; + break; + case mode::accept: + m_client_max_window_bits = bits; + break; + case mode::largest: + m_client_max_window_bits = std::min(bits,m_client_max_window_bits); + break; + case mode::smallest: + m_client_max_window_bits = min_client_max_window_bits; + break; + default: + ec = make_error_code(error::invalid_mode); + m_client_max_window_bits = default_client_max_window_bits; + } + + // See note in doc comment + if (m_client_max_window_bits == 8) { + m_client_max_window_bits = 9; + } + } + + bool m_enabled; + bool m_server_no_context_takeover; + bool m_client_no_context_takeover; + uint8_t m_server_max_window_bits; + uint8_t m_client_max_window_bits; + mode::value m_server_max_window_bits_mode; + mode::value m_client_max_window_bits_mode; + + bool m_initialized; + int m_flush; + size_t m_compress_buffer_size; + lib::unique_ptr_uchar_array m_compress_buffer; + lib::unique_ptr_uchar_array m_decompress_buffer; + z_stream m_dstate; + z_stream m_istate; +}; + +} // namespace permessage_deflate +} // namespace extensions +} // namespace websocketpp + +#endif // WEBSOCKETPP_PROCESSOR_EXTENSION_PERMESSAGEDEFLATE_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/frame.hpp b/thirdparty/websocketpp/include/websocketpp/frame.hpp new file mode 100644 index 0000000..fb1b840 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/frame.hpp @@ -0,0 +1,864 @@ +/* + * Copyright (c) 2014, 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_FRAME_HPP +#define WEBSOCKETPP_FRAME_HPP + +#include +#include + +#include +#include + +#include + +namespace websocketpp { +/// Data structures and utility functions for manipulating WebSocket frames +/** + * namespace frame provides a number of data structures and utility functions + * for reading, writing, and manipulating binary encoded WebSocket frames. + */ +namespace frame { + +/// Minimum length of a WebSocket frame header. +static unsigned int const BASIC_HEADER_LENGTH = 2; +/// Maximum length of a WebSocket header +static unsigned int const MAX_HEADER_LENGTH = 14; +/// Maximum length of the variable portion of the WebSocket header +static unsigned int const MAX_EXTENDED_HEADER_LENGTH = 12; + +/// Two byte conversion union +union uint16_converter { + uint16_t i; + uint8_t c[2]; +}; + +/// Four byte conversion union +union uint32_converter { + uint32_t i; + uint8_t c[4]; +}; + +/// Eight byte conversion union +union uint64_converter { + uint64_t i; + uint8_t c[8]; +}; + +/// Constants and utility functions related to WebSocket opcodes +/** + * WebSocket Opcodes are 4 bits. See RFC6455 section 5.2. + */ +namespace opcode { + enum value { + continuation = 0x0, + text = 0x1, + binary = 0x2, + rsv3 = 0x3, + rsv4 = 0x4, + rsv5 = 0x5, + rsv6 = 0x6, + rsv7 = 0x7, + close = 0x8, + ping = 0x9, + pong = 0xA, + control_rsvb = 0xB, + control_rsvc = 0xC, + control_rsvd = 0xD, + control_rsve = 0xE, + control_rsvf = 0xF, + + CONTINUATION = 0x0, + TEXT = 0x1, + BINARY = 0x2, + RSV3 = 0x3, + RSV4 = 0x4, + RSV5 = 0x5, + RSV6 = 0x6, + RSV7 = 0x7, + CLOSE = 0x8, + PING = 0x9, + PONG = 0xA, + CONTROL_RSVB = 0xB, + CONTROL_RSVC = 0xC, + CONTROL_RSVD = 0xD, + CONTROL_RSVE = 0xE, + CONTROL_RSVF = 0xF + }; + + /// Check if an opcode is reserved + /** + * @param v The opcode to test. + * @return Whether or not the opcode is reserved. + */ + inline bool reserved(value v) { + return (v >= rsv3 && v <= rsv7) || + (v >= control_rsvb && v <= control_rsvf); + } + + /// Check if an opcode is invalid + /** + * Invalid opcodes are negative or require greater than 4 bits to store. + * + * @param v The opcode to test. + * @return Whether or not the opcode is invalid. + */ + inline bool invalid(value v) { + return (v > 0xF || v < 0); + } + + /// Check if an opcode is for a control frame + /** + * @param v The opcode to test. + * @return Whether or not the opcode is a control opcode. + */ + inline bool is_control(value v) { + return v >= 0x8; + } +} + +/// Constants related to frame and payload limits +namespace limits { + /// Minimum length of a WebSocket frame header. + static unsigned int const basic_header_length = 2; + + /// Maximum length of a WebSocket header + static unsigned int const max_header_length = 14; + + /// Maximum length of the variable portion of the WebSocket header + static unsigned int const max_extended_header_length = 12; + + /// Maximum size of a basic WebSocket payload + static uint8_t const payload_size_basic = 125; + + /// Maximum size of an extended WebSocket payload (basic payload = 126) + static uint16_t const payload_size_extended = 0xFFFF; // 2^16, 65535 + + /// Maximum size of a jumbo WebSocket payload (basic payload = 127) + static uint64_t const payload_size_jumbo = 0x7FFFFFFFFFFFFFFFLL;//2^63 + + /// Maximum size of close frame reason + /** + * This is payload_size_basic - 2 bytes (as first two bytes are used for + * the close code + */ + static uint8_t const close_reason_size = 123; +} + + +// masks for fields in the basic header +static uint8_t const BHB0_OPCODE = 0x0F; +static uint8_t const BHB0_RSV3 = 0x10; +static uint8_t const BHB0_RSV2 = 0x20; +static uint8_t const BHB0_RSV1 = 0x40; +static uint8_t const BHB0_FIN = 0x80; + +static uint8_t const BHB1_PAYLOAD = 0x7F; +static uint8_t const BHB1_MASK = 0x80; + +static uint8_t const payload_size_code_16bit = 0x7E; // 126 +static uint8_t const payload_size_code_64bit = 0x7F; // 127 + +typedef uint32_converter masking_key_type; + +/// The constant size component of a WebSocket frame header +struct basic_header { + basic_header() : b0(0x00),b1(0x00) {} + + basic_header(uint8_t p0, uint8_t p1) : b0(p0), b1(p1) {} + + basic_header(opcode::value op, uint64_t size, bool fin, bool mask, + bool rsv1 = false, bool rsv2 = false, bool rsv3 = false) : b0(0x00), + b1(0x00) + { + if (fin) { + b0 |= BHB0_FIN; + } + if (rsv1) { + b0 |= BHB0_RSV1; + } + if (rsv2) { + b0 |= BHB0_RSV2; + } + if (rsv3) { + b0 |= BHB0_RSV3; + } + b0 |= (op & BHB0_OPCODE); + + if (mask) { + b1 |= BHB1_MASK; + } + + uint8_t basic_value; + + if (size <= limits::payload_size_basic) { + basic_value = static_cast(size); + } else if (size <= limits::payload_size_extended) { + basic_value = payload_size_code_16bit; + } else { + basic_value = payload_size_code_64bit; + } + + + b1 |= basic_value; + } + + uint8_t b0; + uint8_t b1; +}; + +/// The variable size component of a WebSocket frame header +struct extended_header { + extended_header() { + std::fill_n(this->bytes,MAX_EXTENDED_HEADER_LENGTH,0x00); + } + + extended_header(uint64_t payload_size) { + std::fill_n(this->bytes,MAX_EXTENDED_HEADER_LENGTH,0x00); + + copy_payload(payload_size); + } + + extended_header(uint64_t payload_size, uint32_t masking_key) { + std::fill_n(this->bytes,MAX_EXTENDED_HEADER_LENGTH,0x00); + + // Copy payload size + int offset = copy_payload(payload_size); + + // Copy Masking Key + uint32_converter temp32; + temp32.i = masking_key; + std::copy(temp32.c,temp32.c+4,bytes+offset); + } + + uint8_t bytes[MAX_EXTENDED_HEADER_LENGTH]; +private: + int copy_payload(uint64_t payload_size) { + int payload_offset = 0; + + if (payload_size <= limits::payload_size_basic) { + payload_offset = 8; + } else if (payload_size <= limits::payload_size_extended) { + payload_offset = 6; + } + + uint64_converter temp64; + temp64.i = lib::net::_htonll(payload_size); + std::copy(temp64.c+payload_offset,temp64.c+8,bytes); + + return 8-payload_offset; + } +}; + +bool get_fin(basic_header const &h); +void set_fin(basic_header &h, bool value); +bool get_rsv1(basic_header const &h); +void set_rsv1(basic_header &h, bool value); +bool get_rsv2(basic_header const &h); +void set_rsv2(basic_header &h, bool value); +bool get_rsv3(basic_header const &h); +void set_rsv3(basic_header &h, bool value); +opcode::value get_opcode(basic_header const &h); +bool get_masked(basic_header const &h); +void set_masked(basic_header &h, bool value); +uint8_t get_basic_size(basic_header const &); +size_t get_header_len(basic_header const &); +unsigned int get_masking_key_offset(basic_header const &); + +std::string write_header(basic_header const &, extended_header const &); +masking_key_type get_masking_key(basic_header const &, extended_header const &); +uint16_t get_extended_size(extended_header const &); +uint64_t get_jumbo_size(extended_header const &); +uint64_t get_payload_size(basic_header const &, extended_header const &); + +size_t prepare_masking_key(masking_key_type const & key); +size_t circshift_prepared_key(size_t prepared_key, size_t offset); + +// Functions for performing xor based masking and unmasking +template +void byte_mask(input_iter b, input_iter e, output_iter o, masking_key_type + const & key, size_t key_offset = 0); +template +void byte_mask(iter_type b, iter_type e, masking_key_type const & key, + size_t key_offset = 0); +void word_mask_exact(uint8_t * input, uint8_t * output, size_t length, + masking_key_type const & key); +void word_mask_exact(uint8_t * data, size_t length, masking_key_type const & + key); +size_t word_mask_circ(uint8_t * input, uint8_t * output, size_t length, + size_t prepared_key); +size_t word_mask_circ(uint8_t * data, size_t length, size_t prepared_key); + +/// Check whether the frame's FIN bit is set. +/** + * @param [in] h The basic header to extract from. + * @return True if the header's fin bit is set. + */ +inline bool get_fin(basic_header const & h) { + return ((h.b0 & BHB0_FIN) == BHB0_FIN); +} + +/// Set the frame's FIN bit +/** + * @param [out] h Header to set. + * @param [in] value Value to set it to. + */ +inline void set_fin(basic_header & h, bool value) { + h.b0 = (value ? h.b0 | BHB0_FIN : h.b0 & ~BHB0_FIN); +} + +/// check whether the frame's RSV1 bit is set +/** + * @param [in] h The basic header to extract from. + * @return True if the header's RSV1 bit is set. + */ +inline bool get_rsv1(const basic_header &h) { + return ((h.b0 & BHB0_RSV1) == BHB0_RSV1); +} + +/// Set the frame's RSV1 bit +/** + * @param [out] h Header to set. + * @param [in] value Value to set it to. + */ +inline void set_rsv1(basic_header &h, bool value) { + h.b0 = (value ? h.b0 | BHB0_RSV1 : h.b0 & ~BHB0_RSV1); +} + +/// check whether the frame's RSV2 bit is set +/** + * @param [in] h The basic header to extract from. + * @return True if the header's RSV2 bit is set. + */ +inline bool get_rsv2(const basic_header &h) { + return ((h.b0 & BHB0_RSV2) == BHB0_RSV2); +} + +/// Set the frame's RSV2 bit +/** + * @param [out] h Header to set. + * @param [in] value Value to set it to. + */ +inline void set_rsv2(basic_header &h, bool value) { + h.b0 = (value ? h.b0 | BHB0_RSV2 : h.b0 & ~BHB0_RSV2); +} + +/// check whether the frame's RSV3 bit is set +/** + * @param [in] h The basic header to extract from. + * @return True if the header's RSV3 bit is set. + */ +inline bool get_rsv3(const basic_header &h) { + return ((h.b0 & BHB0_RSV3) == BHB0_RSV3); +} + +/// Set the frame's RSV3 bit +/** + * @param [out] h Header to set. + * @param [in] value Value to set it to. + */ +inline void set_rsv3(basic_header &h, bool value) { + h.b0 = (value ? h.b0 | BHB0_RSV3 : h.b0 & ~BHB0_RSV3); +} + +/// Extract opcode from basic header +/** + * @param [in] h The basic header to extract from. + * @return The opcode value of the header. + */ +inline opcode::value get_opcode(const basic_header &h) { + return opcode::value(h.b0 & BHB0_OPCODE); +} + +/// check whether the frame is masked +/** + * @param [in] h The basic header to extract from. + * @return True if the header mask bit is set. + */ +inline bool get_masked(basic_header const & h) { + return ((h.b1 & BHB1_MASK) == BHB1_MASK); +} + +/// Set the frame's MASK bit +/** + * @param [out] h Header to set. + * @param value Value to set it to. + */ +inline void set_masked(basic_header & h, bool value) { + h.b1 = (value ? h.b1 | BHB1_MASK : h.b1 & ~BHB1_MASK); +} + +/// Extracts the raw payload length specified in the basic header +/** + * A basic WebSocket frame header contains a 7 bit value that represents the + * payload size. There are two reserved values that are used to indicate that + * the actual payload size will not fit in 7 bits and that the full payload + * size is included in a separate field. The values are as follows: + * + * PAYLOAD_SIZE_CODE_16BIT (0x7E) indicates that the actual payload is less + * than 16 bit + * + * PAYLOAD_SIZE_CODE_64BIT (0x7F) indicates that the actual payload is less + * than 63 bit + * + * @param [in] h Basic header to read value from. + * @return The exact size encoded in h. + */ +inline uint8_t get_basic_size(const basic_header &h) { + return h.b1 & BHB1_PAYLOAD; +} + +/// Calculates the full length of the header based on the first bytes. +/** + * A WebSocket frame header always has at least two bytes. Encoded within the + * first two bytes is all the information necessary to calculate the full + * (variable) header length. get_header_len() calculates the full header + * length for the given two byte basic header. + * + * @param h Basic frame header to extract size from. + * @return Full length of the extended header. + */ +inline size_t get_header_len(basic_header const & h) { + // TODO: check extensions? + + // masking key offset represents the space used for the extended length + // fields + size_t size = BASIC_HEADER_LENGTH + get_masking_key_offset(h); + + // If the header is masked there is a 4 byte masking key + if (get_masked(h)) { + size += 4; + } + + return size; +} + +/// Calculate the offset location of the masking key within the extended header +/** + * Calculate the offset location of the masking key within the extended header + * using information from its corresponding basic header + * + * @param h Corresponding basic header to calculate from. + * + * @return byte offset of the first byte of the masking key + */ +inline unsigned int get_masking_key_offset(const basic_header &h) { + if (get_basic_size(h) == payload_size_code_16bit) { + return 2; + } else if (get_basic_size(h) == payload_size_code_64bit) { + return 8; + } else { + return 0; + } +} + +/// Generate a properly sized contiguous string that encodes a full frame header +/** + * Copy the basic header h and extended header e into a properly sized + * contiguous frame header string for the purposes of writing out to the wire. + * + * @param h The basic header to include + * @param e The extended header to include + * + * @return A contiguous string containing h and e + */ +inline std::string prepare_header(const basic_header &h, const + extended_header &e) +{ + std::string ret; + + ret.push_back(char(h.b0)); + ret.push_back(char(h.b1)); + ret.append( + reinterpret_cast(e.bytes), + get_header_len(h)-BASIC_HEADER_LENGTH + ); + + return ret; +} + +/// Extract the masking key from a frame header +/** + * Note that while read and written as an integer at times, this value is not + * an integer and should never be interpreted as one. Big and little endian + * machines will generate and store masking keys differently without issue as + * long as the integer values remain irrelivant. + * + * @param h The basic header to extract from + * @param e The extended header to extract from + * + * @return The masking key as an integer. + */ +inline masking_key_type get_masking_key(const basic_header &h, const + extended_header &e) +{ + masking_key_type temp32; + + if (!get_masked(h)) { + temp32.i = 0; + } else { + unsigned int offset = get_masking_key_offset(h); + std::copy(e.bytes+offset,e.bytes+offset+4,temp32.c); + } + + return temp32; +} + +/// Extract the extended size field from an extended header +/** + * It is the responsibility of the caller to verify that e is a valid extended + * header. This function assumes that e contains an extended payload size. + * + * @param e The extended header to extract from + * + * @return The size encoded in the extended header in host byte order + */ +inline uint16_t get_extended_size(const extended_header &e) { + uint16_converter temp16; + std::copy(e.bytes,e.bytes+2,temp16.c); + return ntohs(temp16.i); +} + +/// Extract the jumbo size field from an extended header +/** + * It is the responsibility of the caller to verify that e is a valid extended + * header. This function assumes that e contains a jumbo payload size. + * + * @param e The extended header to extract from + * + * @return The size encoded in the extended header in host byte order + */ +inline uint64_t get_jumbo_size(const extended_header &e) { + uint64_converter temp64; + std::copy(e.bytes,e.bytes+8,temp64.c); + return lib::net::_ntohll(temp64.i); +} + +/// Extract the full payload size field from a WebSocket header +/** + * It is the responsibility of the caller to verify that h and e together + * represent a valid WebSocket frame header. This function assumes only that h + * and e are valid. It uses information in the basic header to determine where + * to look for the payload_size + * + * @param h The basic header to extract from + * @param e The extended header to extract from + * + * @return The size encoded in the combined header in host byte order. + */ +inline uint64_t get_payload_size(const basic_header &h, const + extended_header &e) +{ + uint8_t val = get_basic_size(h); + + if (val <= limits::payload_size_basic) { + return val; + } else if (val == payload_size_code_16bit) { + return get_extended_size(e); + } else { + return get_jumbo_size(e); + } +} + +/// Extract a masking key into a value the size of a machine word. +/** + * Machine word size must be 4 or 8. + * + * @param key Masking key to extract from + * + * @return prepared key as a machine word + */ +inline size_t prepare_masking_key(const masking_key_type& key) { + size_t low_bits = static_cast(key.i); + + if (sizeof(size_t) == 8) { + uint64_t high_bits = static_cast(key.i); + return static_cast((high_bits << 32) | low_bits); + } else { + return low_bits; + } +} + +/// circularly shifts the supplied prepared masking key by offset bytes +/** + * Prepared_key must be the output of prepare_masking_key with the associated + * restrictions on the machine word size. offset must be greater than or equal + * to zero and less than sizeof(size_t). + */ +inline size_t circshift_prepared_key(size_t prepared_key, size_t offset) { + if (offset == 0) { + return prepared_key; + } + if (lib::net::is_little_endian()) { + size_t temp = prepared_key << (sizeof(size_t)-offset)*8; + return (prepared_key >> offset*8) | temp; + } else { + size_t temp = prepared_key >> (sizeof(size_t)-offset)*8; + return (prepared_key << offset*8) | temp; + } +} + +/// Byte by byte mask/unmask +/** + * Iterator based byte by byte masking and unmasking for WebSocket payloads. + * Performs masking in place using the supplied key offset by the supplied + * offset number of bytes. + * + * This function is simple and can be done in place on input with arbitrary + * lengths and does not vary based on machine word size. It is slow. + * + * @param b Beginning iterator to start masking + * + * @param e Ending iterator to end masking + * + * @param o Beginning iterator to store masked results + * + * @param key 32 bit key to mask with. + * + * @param key_offset offset value to start masking at. + */ +template +void byte_mask(input_iter first, input_iter last, output_iter result, + masking_key_type const & key, size_t key_offset) +{ + size_t key_index = key_offset%4; + while (first != last) { + *result = *first ^ key.c[key_index++]; + key_index %= 4; + ++result; + ++first; + } +} + +/// Byte by byte mask/unmask (in place) +/** + * Iterator based byte by byte masking and unmasking for WebSocket payloads. + * Performs masking in place using the supplied key offset by the supplied + * offset number of bytes. + * + * This function is simple and can be done in place on input with arbitrary + * lengths and does not vary based on machine word size. It is slow. + * + * @param b Beginning iterator to start masking + * + * @param e Ending iterator to end masking + * + * @param key 32 bit key to mask with. + * + * @param key_offset offset value to start masking at. + */ +template +void byte_mask(iter_type b, iter_type e, masking_key_type const & key, + size_t key_offset) +{ + byte_mask(b,e,b,key,key_offset); +} + +/// Exact word aligned mask/unmask +/** + * Balanced combination of byte by byte and circular word by word masking. + * Best used to mask complete messages at once. Has much higher setup costs than + * word_mask_circ but works with exact sized buffers. + * + * Buffer based word by word masking and unmasking for WebSocket payloads. + * Masking is done in word by word chunks with the remainder not divisible by + * the word size done byte by byte. + * + * input and output must both be at least length bytes. Exactly length bytes + * will be written. + * + * @param input buffer to mask or unmask + * + * @param output buffer to store the output. May be the same as input. + * + * @param length length of data buffer + * + * @param key Masking key to use + */ +inline void word_mask_exact(uint8_t* input, uint8_t* output, size_t length, + const masking_key_type& key) +{ + size_t prepared_key = prepare_masking_key(key); + size_t n = length/sizeof(size_t); + size_t* input_word = reinterpret_cast(input); + size_t* output_word = reinterpret_cast(output); + + for (size_t i = 0; i < n; i++) { + output_word[i] = input_word[i] ^ prepared_key; + } + + for (size_t i = n*sizeof(size_t); i < length; i++) { + output[i] = input[i] ^ key.c[i%4]; + } +} + +/// Exact word aligned mask/unmask (in place) +/** + * In place version of word_mask_exact + * + * @see word_mask_exact + * + * @param data buffer to read and write from + * + * @param length length of data buffer + * + * @param key Masking key to use + */ +inline void word_mask_exact(uint8_t* data, size_t length, const + masking_key_type& key) +{ + word_mask_exact(data,data,length,key); +} + +/// Circular word aligned mask/unmask +/** + * Performs a circular mask/unmask in word sized chunks using pre-prepared keys + * that store state between calls. Best for providing streaming masking or + * unmasking of small chunks at a time of a larger message. Requires that the + * underlying allocated size of the data buffer be a multiple of the word size. + * Data in the buffer after `length` will be overwritten only with the same + * values that were originally present. + * + * Buffer based word by word masking and unmasking for WebSocket payloads. + * Performs masking in place using the supplied key. Casts the data buffer to + * an array of size_t's and performs masking word by word. The underlying + * buffer size must be a muliple of the word size. + * + * word_mask returns a copy of prepared_key circularly shifted based on the + * length value. The returned value may be fed back into word_mask when more + * data is available. + * + * input and output must both have length at least: + * ceil(length/sizeof(size_t))*sizeof(size_t) + * Exactly that many bytes will be written, although only exactly length bytes + * will be changed (trailing bytes will be replaced without masking) + * + * @param data Character buffer to mask + * + * @param length Length of data + * + * @param prepared_key Prepared key to use. + * + * @return the prepared_key shifted to account for the input length + */ +inline size_t word_mask_circ(uint8_t * input, uint8_t * output, size_t length, + size_t prepared_key) +{ + size_t n = length / sizeof(size_t); // whole words + size_t l = length - (n * sizeof(size_t)); // remaining bytes + size_t * input_word = reinterpret_cast(input); + size_t * output_word = reinterpret_cast(output); + + // mask word by word + for (size_t i = 0; i < n; i++) { + output_word[i] = input_word[i] ^ prepared_key; + } + + // mask partial word at the end + size_t start = length - l; + uint8_t * byte_key = reinterpret_cast(&prepared_key); + for (size_t i = 0; i < l; ++i) { + output[start+i] = input[start+i] ^ byte_key[i]; + } + + return circshift_prepared_key(prepared_key,l); +} + +/// Circular word aligned mask/unmask (in place) +/** + * In place version of word_mask_circ + * + * @see word_mask_circ + * + * @param data Character buffer to read from and write to + * + * @param length Length of data + * + * @param prepared_key Prepared key to use. + * + * @return the prepared_key shifted to account for the input length + */ +inline size_t word_mask_circ(uint8_t* data, size_t length, size_t prepared_key){ + return word_mask_circ(data,data,length,prepared_key); +} + +/// Circular byte aligned mask/unmask +/** + * Performs a circular mask/unmask in byte sized chunks using pre-prepared keys + * that store state between calls. Best for providing streaming masking or + * unmasking of small chunks at a time of a larger message. Requires that the + * underlying allocated size of the data buffer be a multiple of the word size. + * Data in the buffer after `length` will be overwritten only with the same + * values that were originally present. + * + * word_mask returns a copy of prepared_key circularly shifted based on the + * length value. The returned value may be fed back into byte_mask when more + * data is available. + * + * @param data Character buffer to mask + * + * @param length Length of data + * + * @param prepared_key Prepared key to use. + * + * @return the prepared_key shifted to account for the input length + */ +inline size_t byte_mask_circ(uint8_t * input, uint8_t * output, size_t length, + size_t prepared_key) +{ + uint32_converter key; + key.i = prepared_key; + + for (size_t i = 0; i < length; ++i) { + output[i] = input[i] ^ key.c[i % 4]; + } + + return circshift_prepared_key(prepared_key,length % 4); +} + +/// Circular byte aligned mask/unmask (in place) +/** + * In place version of byte_mask_circ + * + * @see byte_mask_circ + * + * @param data Character buffer to read from and write to + * + * @param length Length of data + * + * @param prepared_key Prepared key to use. + * + * @return the prepared_key shifted to account for the input length + */ +inline size_t byte_mask_circ(uint8_t* data, size_t length, size_t prepared_key){ + return byte_mask_circ(data,data,length,prepared_key); +} + +} // namespace frame +} // namespace websocketpp + +#endif //WEBSOCKETPP_FRAME_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/http/constants.hpp b/thirdparty/websocketpp/include/websocketpp/http/constants.hpp new file mode 100644 index 0000000..ce0cd37 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/http/constants.hpp @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2014, 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 HTTP_CONSTANTS_HPP +#define HTTP_CONSTANTS_HPP + +#include +#include +#include +#include +#include + +namespace websocketpp { +/// HTTP handling support +namespace http { + /// The type of an HTTP attribute list + /** + * The attribute list is an unordered key/value map. Encoded attribute + * values are delimited by semicolons. + */ + typedef std::map attribute_list; + + /// The type of an HTTP parameter list + /** + * The parameter list is an ordered pairing of a parameter and its + * associated attribute list. Encoded parameter values are delimited by + * commas. + */ + typedef std::vector< std::pair > parameter_list; + + /// Literal value of the HTTP header delimiter + static char const header_delimiter[] = "\r\n"; + + /// Literal value of the HTTP header separator + static char const header_separator[] = ":"; + + /// Literal value of an empty header + static std::string const empty_header; + + /// Maximum size in bytes before rejecting an HTTP header as too big. + size_t const max_header_size = 16000; + + /// Default Maximum size in bytes for HTTP message bodies. + size_t const max_body_size = 32000000; + + /// Number of bytes to use for temporary istream read buffers + size_t const istream_buffer = 512; + + /// invalid HTTP token characters + /** + * 0x00 - 0x32, 0x7f-0xff + * ( ) < > @ , ; : \ " / [ ] ? = { } + */ + static char const header_token[] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..0f + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 10..1f + 0,1,0,1,1,1,1,1,0,0,1,1,0,1,1,0, // 20..2f + 1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0, // 30..3f + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 40..4f + 1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1, // 50..5f + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 60..6f + 1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,0, // 70..7f + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 80..8f + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 90..9f + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // a0..af + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // b0..bf + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // c0..cf + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // d0..df + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // e0..ef + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // f0..ff + }; + + /// Is the character a token + inline bool is_token_char(unsigned char c) { + return (header_token[c] == 1); + } + + /// Is the character a non-token + inline bool is_not_token_char(unsigned char c) { + return !header_token[c]; + } + + /// Is the character whitespace + /** + * whitespace is space (32) or horizontal tab (9) + */ + inline bool is_whitespace_char(unsigned char c) { + return (c == 9 || c == 32); + } + + /// Is the character non-whitespace + inline bool is_not_whitespace_char(unsigned char c) { + return (c != 9 && c != 32); + } + + /// HTTP Status codes + namespace status_code { + enum value { + uninitialized = 0, + + continue_code = 100, + switching_protocols = 101, + + ok = 200, + created = 201, + accepted = 202, + non_authoritative_information = 203, + no_content = 204, + reset_content = 205, + partial_content = 206, + + multiple_choices = 300, + moved_permanently = 301, + found = 302, + see_other = 303, + not_modified = 304, + use_proxy = 305, + temporary_redirect = 307, + + bad_request = 400, + unauthorized = 401, + payment_required = 402, + forbidden = 403, + not_found = 404, + method_not_allowed = 405, + not_acceptable = 406, + proxy_authentication_required = 407, + request_timeout = 408, + conflict = 409, + gone = 410, + length_required = 411, + precondition_failed = 412, + request_entity_too_large = 413, + request_uri_too_long = 414, + unsupported_media_type = 415, + request_range_not_satisfiable = 416, + expectation_failed = 417, + im_a_teapot = 418, + upgrade_required = 426, + precondition_required = 428, + too_many_requests = 429, + request_header_fields_too_large = 431, + + internal_server_error = 500, + not_implemented = 501, + bad_gateway = 502, + service_unavailable = 503, + gateway_timeout = 504, + http_version_not_supported = 505, + not_extended = 510, + network_authentication_required = 511 + }; + + // TODO: should this be inline? + inline std::string get_string(value c) { + switch (c) { + case uninitialized: + return "Uninitialized"; + case continue_code: + return "Continue"; + case switching_protocols: + return "Switching Protocols"; + case ok: + return "OK"; + case created: + return "Created"; + case accepted: + return "Accepted"; + case non_authoritative_information: + return "Non Authoritative Information"; + case no_content: + return "No Content"; + case reset_content: + return "Reset Content"; + case partial_content: + return "Partial Content"; + case multiple_choices: + return "Multiple Choices"; + case moved_permanently: + return "Moved Permanently"; + case found: + return "Found"; + case see_other: + return "See Other"; + case not_modified: + return "Not Modified"; + case use_proxy: + return "Use Proxy"; + case temporary_redirect: + return "Temporary Redirect"; + case bad_request: + return "Bad Request"; + case unauthorized: + return "Unauthorized"; + case payment_required: + return "Payment Required"; + case forbidden: + return "Forbidden"; + case not_found: + return "Not Found"; + case method_not_allowed: + return "Method Not Allowed"; + case not_acceptable: + return "Not Acceptable"; + case proxy_authentication_required: + return "Proxy Authentication Required"; + case request_timeout: + return "Request Timeout"; + case conflict: + return "Conflict"; + case gone: + return "Gone"; + case length_required: + return "Length Required"; + case precondition_failed: + return "Precondition Failed"; + case request_entity_too_large: + return "Request Entity Too Large"; + case request_uri_too_long: + return "Request-URI Too Long"; + case unsupported_media_type: + return "Unsupported Media Type"; + case request_range_not_satisfiable: + return "Requested Range Not Satisfiable"; + case expectation_failed: + return "Expectation Failed"; + case im_a_teapot: + return "I'm a teapot"; + case upgrade_required: + return "Upgrade Required"; + case precondition_required: + return "Precondition Required"; + case too_many_requests: + return "Too Many Requests"; + case request_header_fields_too_large: + return "Request Header Fields Too Large"; + case internal_server_error: + return "Internal Server Error"; + case not_implemented: + return "Not Implemented"; + case bad_gateway: + return "Bad Gateway"; + case service_unavailable: + return "Service Unavailable"; + case gateway_timeout: + return "Gateway Timeout"; + case http_version_not_supported: + return "HTTP Version Not Supported"; + case not_extended: + return "Not Extended"; + case network_authentication_required: + return "Network Authentication Required"; + default: + return "Unknown"; + } + } + } + + class exception : public std::exception { + public: + exception(const std::string& log_msg, + status_code::value error_code, + const std::string& error_msg = std::string(), + const std::string& body = std::string()) + : m_msg(log_msg) + , m_error_msg(error_msg) + , m_body(body) + , m_error_code(error_code) {} + + ~exception() throw() {} + + virtual const char* what() const throw() { + return m_msg.c_str(); + } + + std::string m_msg; + std::string m_error_msg; + std::string m_body; + status_code::value m_error_code; + }; +} +} + +#endif // HTTP_CONSTANTS_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/http/impl/parser.hpp b/thirdparty/websocketpp/include/websocketpp/http/impl/parser.hpp new file mode 100644 index 0000000..eea5893 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/http/impl/parser.hpp @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2014, 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 HTTP_PARSER_IMPL_HPP +#define HTTP_PARSER_IMPL_HPP + +#include +#include +#include +#include +#include + +namespace websocketpp { +namespace http { +namespace parser { + +inline void parser::set_version(std::string const & version) { + m_version = version; +} + +inline std::string const & parser::get_header(std::string const & key) const { + header_list::const_iterator h = m_headers.find(key); + + if (h == m_headers.end()) { + return empty_header; + } else { + return h->second; + } +} + +inline bool parser::get_header_as_plist(std::string const & key, + parameter_list & out) const +{ + header_list::const_iterator it = m_headers.find(key); + + if (it == m_headers.end() || it->second.size() == 0) { + return false; + } + + return this->parse_parameter_list(it->second,out); +} + +inline void parser::append_header(std::string const & key, std::string const & + val) +{ + if (std::find_if(key.begin(),key.end(),is_not_token_char) != key.end()) { + throw exception("Invalid header name",status_code::bad_request); + } + + if (this->get_header(key).empty()) { + m_headers[key] = val; + } else { + m_headers[key] += ", " + val; + } +} + +inline void parser::replace_header(std::string const & key, std::string const & + val) +{ + m_headers[key] = val; +} + +inline void parser::remove_header(std::string const & key) { + m_headers.erase(key); +} + +inline void parser::set_body(std::string const & value) { + if (value.size() == 0) { + remove_header("Content-Length"); + m_body.clear(); + return; + } + + // TODO: should this method respect the max size? If so how should errors + // be indicated? + + std::stringstream len; + len << value.size(); + replace_header("Content-Length", len.str()); + m_body = value; +} + +inline bool parser::parse_parameter_list(std::string const & in, + parameter_list & out) const +{ + if (in.size() == 0) { + return false; + } + + std::string::const_iterator it; + it = extract_parameters(in.begin(),in.end(),out); + return (it == in.begin()); +} + +inline bool parser::prepare_body() { + if (!get_header("Content-Length").empty()) { + std::string const & cl_header = get_header("Content-Length"); + char * end; + + // TODO: not 100% sure what the compatibility of this method is. Also, + // I believe this will only work up to 32bit sizes. Is there a need for + // > 4GiB HTTP payloads? + m_body_bytes_needed = std::strtoul(cl_header.c_str(),&end,10); + + if (m_body_bytes_needed > m_body_bytes_max) { + throw exception("HTTP message body too large", + status_code::request_entity_too_large); + } + + m_body_encoding = body_encoding::plain; + return true; + } else if (get_header("Transfer-Encoding") == "chunked") { + // TODO + //m_body_encoding = body_encoding::chunked; + return false; + } else { + return false; + } +} + +inline size_t parser::process_body(char const * buf, size_t len) { + if (m_body_encoding == body_encoding::plain) { + size_t processed = (std::min)(m_body_bytes_needed,len); + m_body.append(buf,processed); + m_body_bytes_needed -= processed; + return processed; + } else if (m_body_encoding == body_encoding::chunked) { + // TODO: + throw exception("Unexpected body encoding", + status_code::internal_server_error); + } else { + throw exception("Unexpected body encoding", + status_code::internal_server_error); + } +} + +inline void parser::process_header(std::string::iterator begin, + std::string::iterator end) +{ + std::string::iterator cursor = std::search( + begin, + end, + header_separator, + header_separator + sizeof(header_separator) - 1 + ); + + if (cursor == end) { + throw exception("Invalid header line",status_code::bad_request); + } + + append_header(strip_lws(std::string(begin,cursor)), + strip_lws(std::string(cursor+sizeof(header_separator)-1,end))); +} + +inline header_list const & parser::get_headers() const { + return m_headers; +} + +inline std::string parser::raw_headers() const { + std::stringstream raw; + + header_list::const_iterator it; + for (it = m_headers.begin(); it != m_headers.end(); it++) { + raw << it->first << ": " << it->second << "\r\n"; + } + + return raw.str(); +} + + + +} // namespace parser +} // namespace http +} // namespace websocketpp + +#endif // HTTP_PARSER_IMPL_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/http/impl/request.hpp b/thirdparty/websocketpp/include/websocketpp/http/impl/request.hpp new file mode 100644 index 0000000..1a2b5bf --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/http/impl/request.hpp @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2014, 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 HTTP_PARSER_REQUEST_IMPL_HPP +#define HTTP_PARSER_REQUEST_IMPL_HPP + +#include +#include +#include + +#include + +namespace websocketpp { +namespace http { +namespace parser { + +inline size_t request::consume(char const * buf, size_t len) { + size_t bytes_processed; + + if (m_ready) {return 0;} + + if (m_body_bytes_needed > 0) { + bytes_processed = process_body(buf,len); + if (body_ready()) { + m_ready = true; + } + return bytes_processed; + } + + // copy new header bytes into buffer + m_buf->append(buf,len); + + // Search for delimiter in buf. If found read until then. If not read all + std::string::iterator begin = m_buf->begin(); + std::string::iterator end; + + for (;;) { + // search for line delimiter + end = std::search( + begin, + m_buf->end(), + header_delimiter, + header_delimiter+sizeof(header_delimiter)-1 + ); + + m_header_bytes += (end-begin+sizeof(header_delimiter)); + + if (m_header_bytes > max_header_size) { + // exceeded max header size + throw exception("Maximum header size exceeded.", + status_code::request_header_fields_too_large); + } + + if (end == m_buf->end()) { + // we are out of bytes. Discard the processed bytes and copy the + // remaining unprecessed bytes to the beginning of the buffer + std::copy(begin,end,m_buf->begin()); + m_buf->resize(static_cast(end-begin)); + m_header_bytes -= m_buf->size(); + + return len; + } + + //the range [begin,end) now represents a line to be processed. + if (end-begin == 0) { + // we got a blank line + if (m_method.empty() || get_header("Host").empty()) { + throw exception("Incomplete Request",status_code::bad_request); + } + + bytes_processed = ( + len - static_cast(m_buf->end()-end) + + sizeof(header_delimiter) - 1 + ); + + // frees memory used temporarily during request parsing + m_buf.reset(); + + // if this was not an upgrade request and has a content length + // continue capturing content-length bytes and expose them as a + // request body. + + if (prepare_body()) { + bytes_processed += process_body(buf+bytes_processed,len-bytes_processed); + if (body_ready()) { + m_ready = true; + } + return bytes_processed; + } else { + m_ready = true; + + // return number of bytes processed (starting bytes - bytes left) + return bytes_processed; + } + } else { + if (m_method.empty()) { + this->process(begin,end); + } else { + this->process_header(begin,end); + } + } + + begin = end+(sizeof(header_delimiter)-1); + } +} + +inline std::string request::raw() const { + // TODO: validation. Make sure all required fields have been set? + std::stringstream ret; + + ret << m_method << " " << m_uri << " " << get_version() << "\r\n"; + ret << raw_headers() << "\r\n" << m_body; + + return ret.str(); +} + +inline std::string request::raw_head() const { + // TODO: validation. Make sure all required fields have been set? + std::stringstream ret; + + ret << m_method << " " << m_uri << " " << get_version() << "\r\n"; + ret << raw_headers() << "\r\n"; + + return ret.str(); +} + +inline void request::set_method(std::string const & method) { + if (std::find_if(method.begin(),method.end(),is_not_token_char) != method.end()) { + throw exception("Invalid method token.",status_code::bad_request); + } + + m_method = method; +} + +inline void request::set_uri(std::string const & uri) { + // TODO: validation? + m_uri = uri; +} + +inline void request::process(std::string::iterator begin, std::string::iterator + end) +{ + std::string::iterator cursor_start = begin; + std::string::iterator cursor_end = std::find(begin,end,' '); + + if (cursor_end == end) { + throw exception("Invalid request line1",status_code::bad_request); + } + + set_method(std::string(cursor_start,cursor_end)); + + cursor_start = cursor_end+1; + cursor_end = std::find(cursor_start,end,' '); + + if (cursor_end == end) { + throw exception("Invalid request line2",status_code::bad_request); + } + + set_uri(std::string(cursor_start,cursor_end)); + set_version(std::string(cursor_end+1,end)); +} + +} // namespace parser +} // namespace http +} // namespace websocketpp + +#endif // HTTP_PARSER_REQUEST_IMPL_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/http/impl/response.hpp b/thirdparty/websocketpp/include/websocketpp/http/impl/response.hpp new file mode 100644 index 0000000..714b94d --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/http/impl/response.hpp @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2014, 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 HTTP_PARSER_RESPONSE_IMPL_HPP +#define HTTP_PARSER_RESPONSE_IMPL_HPP + +#include +#include +#include +#include + +#include + +namespace websocketpp { +namespace http { +namespace parser { + +inline size_t response::consume(char const * buf, size_t len) { + if (m_state == DONE) {return 0;} + + if (m_state == BODY) { + return this->process_body(buf,len); + } + + // copy new header bytes into buffer + m_buf->append(buf,len); + + // Search for delimiter in buf. If found read until then. If not read all + std::string::iterator begin = m_buf->begin(); + std::string::iterator end = begin; + + + for (;;) { + // search for delimiter + end = std::search( + begin, + m_buf->end(), + header_delimiter, + header_delimiter + sizeof(header_delimiter) - 1 + ); + + m_header_bytes += (end-begin+sizeof(header_delimiter)); + + if (m_header_bytes > max_header_size) { + // exceeded max header size + throw exception("Maximum header size exceeded.", + status_code::request_header_fields_too_large); + } + + if (end == m_buf->end()) { + // we are out of bytes. Discard the processed bytes and copy the + // remaining unprecessed bytes to the beginning of the buffer + std::copy(begin,end,m_buf->begin()); + m_buf->resize(static_cast(end-begin)); + + m_read += len; + m_header_bytes -= m_buf->size(); + + return len; + } + + //the range [begin,end) now represents a line to be processed. + + if (end-begin == 0) { + // we got a blank line + if (m_state == RESPONSE_LINE) { + throw exception("Incomplete Request",status_code::bad_request); + } + + // TODO: grab content-length + std::string length = get_header("Content-Length"); + + if (length.empty()) { + // no content length found, read indefinitely + m_read = 0; + } else { + std::istringstream ss(length); + + if ((ss >> m_read).fail()) { + throw exception("Unable to parse Content-Length header", + status_code::bad_request); + } + } + + m_state = BODY; + + // calc header bytes processed (starting bytes - bytes left) + size_t read = ( + len - static_cast(m_buf->end() - end) + + sizeof(header_delimiter) - 1 + ); + + // if there were bytes left process them as body bytes + if (read < len) { + read += this->process_body(buf+read,(len-read)); + } + + // frees memory used temporarily during header parsing + m_buf.reset(); + + return read; + } else { + if (m_state == RESPONSE_LINE) { + this->process(begin,end); + m_state = HEADERS; + } else { + this->process_header(begin,end); + } + } + + begin = end+(sizeof(header_delimiter) - 1); + } +} + +inline size_t response::consume(std::istream & s) { + char buf[istream_buffer]; + size_t bytes_read; + size_t bytes_processed; + size_t total = 0; + + while (s.good()) { + s.getline(buf,istream_buffer); + bytes_read = static_cast(s.gcount()); + + if (s.fail() || s.eof()) { + bytes_processed = this->consume(buf,bytes_read); + total += bytes_processed; + + if (bytes_processed != bytes_read) { + // problem + break; + } + } else if (s.bad()) { + // problem + break; + } else { + // the delimiting newline was found. Replace the trailing null with + // the newline that was discarded, since our raw consume function + // expects the newline to be be there. + buf[bytes_read-1] = '\n'; + bytes_processed = this->consume(buf,bytes_read); + total += bytes_processed; + + if (bytes_processed != bytes_read) { + // problem + break; + } + } + } + + return total; +} + +inline std::string response::raw() const { + // TODO: validation. Make sure all required fields have been set? + + std::stringstream ret; + + ret << get_version() << " " << m_status_code << " " << m_status_msg; + ret << "\r\n" << raw_headers() << "\r\n"; + + ret << m_body; + + return ret.str(); +} + +inline void response::set_status(status_code::value code) { + // TODO: validation? + m_status_code = code; + m_status_msg = get_string(code); +} + +inline void response::set_status(status_code::value code, std::string const & + msg) +{ + // TODO: validation? + m_status_code = code; + m_status_msg = msg; +} + +inline void response::process(std::string::iterator begin, + std::string::iterator end) +{ + std::string::iterator cursor_start = begin; + std::string::iterator cursor_end = std::find(begin,end,' '); + + if (cursor_end == end) { + throw exception("Invalid response line",status_code::bad_request); + } + + set_version(std::string(cursor_start,cursor_end)); + + cursor_start = cursor_end+1; + cursor_end = std::find(cursor_start,end,' '); + + if (cursor_end == end) { + throw exception("Invalid request line",status_code::bad_request); + } + + int code; + + std::istringstream ss(std::string(cursor_start,cursor_end)); + + if ((ss >> code).fail()) { + throw exception("Unable to parse response code",status_code::bad_request); + } + + set_status(status_code::value(code),std::string(cursor_end+1,end)); +} + +inline size_t response::process_body(char const * buf, size_t len) { + // If no content length was set then we read forever and never set m_ready + if (m_read == 0) { + //m_body.append(buf,len); + //return len; + m_state = DONE; + return 0; + } + + // Otherwise m_read is the number of bytes left. + size_t to_read; + + if (len >= m_read) { + // if we have more bytes than we need read, read only the amount needed + // then set done state + to_read = m_read; + m_state = DONE; + } else { + // we need more bytes than are available, read them all + to_read = len; + } + + m_body.append(buf,to_read); + m_read -= to_read; + return to_read; +} + +} // namespace parser +} // namespace http +} // namespace websocketpp + +#endif // HTTP_PARSER_RESPONSE_IMPL_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/http/parser.hpp b/thirdparty/websocketpp/include/websocketpp/http/parser.hpp new file mode 100644 index 0000000..2e5b72e --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/http/parser.hpp @@ -0,0 +1,629 @@ +/* + * Copyright (c) 2014, 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 HTTP_PARSER_HPP +#define HTTP_PARSER_HPP + +#include +#include +#include +#include + +#include +#include + +namespace websocketpp { +namespace http { +namespace parser { + +namespace state { + enum value { + method, + resource, + version, + headers + }; +} + +namespace body_encoding { + enum value { + unknown, + plain, + chunked + }; +} + +typedef std::map header_list; + +/// Read and return the next token in the stream +/** + * Read until a non-token character is found and then return the token and + * iterator to the next character to read + * + * @param begin An iterator to the beginning of the sequence + * @param end An iterator to the end of the sequence + * @return A pair containing the token and an iterator to the next character in + * the stream + */ +template +std::pair extract_token(InputIterator begin, + InputIterator end) +{ + InputIterator it = std::find_if(begin,end,&is_not_token_char); + return std::make_pair(std::string(begin,it),it); +} + +/// Read and return the next quoted string in the stream +/** + * Read a double quoted string starting at `begin`. The quotes themselves are + * stripped. The quoted value is returned along with an iterator to the next + * character to read + * + * @param begin An iterator to the beginning of the sequence + * @param end An iterator to the end of the sequence + * @return A pair containing the string read and an iterator to the next + * character in the stream + */ +template +std::pair extract_quoted_string(InputIterator begin, + InputIterator end) +{ + std::string s; + + if (end == begin) { + return std::make_pair(s,begin); + } + + if (*begin != '"') { + return std::make_pair(s,begin); + } + + InputIterator cursor = begin+1; + InputIterator marker = cursor; + + cursor = std::find(cursor,end,'"'); + + while (cursor != end) { + // either this is the end or a quoted string + if (*(cursor-1) == '\\') { + s.append(marker,cursor-1); + s.append(1,'"'); + ++cursor; + marker = cursor; + } else { + s.append(marker,cursor); + ++cursor; + return std::make_pair(s,cursor); + } + + cursor = std::find(cursor,end,'"'); + } + + return std::make_pair("",begin); +} + +/// Read and discard one unit of linear whitespace +/** + * Read one unit of linear white space and return the iterator to the character + * afterwards. If `begin` is returned, no whitespace was extracted. + * + * @param begin An iterator to the beginning of the sequence + * @param end An iterator to the end of the sequence + * @return An iterator to the character after the linear whitespace read + */ +template +InputIterator extract_lws(InputIterator begin, InputIterator end) { + InputIterator it = begin; + + // strip leading CRLF + if (end-begin > 2 && *begin == '\r' && *(begin+1) == '\n' && + is_whitespace_char(static_cast(*(begin+2)))) + { + it+=3; + } + + it = std::find_if(it,end,&is_not_whitespace_char); + return it; +} + +/// Read and discard linear whitespace +/** + * Read linear white space until a non-lws character is read and return an + * iterator to that character. If `begin` is returned, no whitespace was + * extracted. + * + * @param begin An iterator to the beginning of the sequence + * @param end An iterator to the end of the sequence + * @return An iterator to the character after the linear whitespace read + */ +template +InputIterator extract_all_lws(InputIterator begin, InputIterator end) { + InputIterator old_it; + InputIterator new_it = begin; + + do { + // Pull value from previous iteration + old_it = new_it; + + // look ahead another pass + new_it = extract_lws(old_it,end); + } while (new_it != end && old_it != new_it); + + return new_it; +} + +/// Extract HTTP attributes +/** + * An http attributes list is a semicolon delimited list of key value pairs in + * the format: *( ";" attribute "=" value ) where attribute is a token and value + * is a token or quoted string. + * + * Attributes extracted are appended to the supplied attributes list + * `attributes`. + * + * @param [in] begin An iterator to the beginning of the sequence + * @param [in] end An iterator to the end of the sequence + * @param [out] attributes A reference to the attributes list to append + * attribute/value pairs extracted to + * @return An iterator to the character after the last atribute read + */ +template +InputIterator extract_attributes(InputIterator begin, InputIterator end, + attribute_list & attributes) +{ + InputIterator cursor; + bool first = true; + + if (begin == end) { + return begin; + } + + cursor = begin; + std::pair ret; + + while (cursor != end) { + std::string name; + + cursor = http::parser::extract_all_lws(cursor,end); + if (cursor == end) { + break; + } + + if (first) { + // ignore this check for the very first pass + first = false; + } else { + if (*cursor == ';') { + // advance past the ';' + ++cursor; + } else { + // non-semicolon in this position indicates end end of the + // attribute list, break and return. + break; + } + } + + cursor = http::parser::extract_all_lws(cursor,end); + ret = http::parser::extract_token(cursor,end); + + if (ret.first.empty()) { + // error: expected a token + return begin; + } else { + name = ret.first; + cursor = ret.second; + } + + cursor = http::parser::extract_all_lws(cursor,end); + if (cursor == end || *cursor != '=') { + // if there is an equals sign, read the attribute value. Otherwise + // record a blank value and continue + attributes[name].clear(); + continue; + } + + // advance past the '=' + ++cursor; + + cursor = http::parser::extract_all_lws(cursor,end); + if (cursor == end) { + // error: expected a token or quoted string + return begin; + } + + ret = http::parser::extract_quoted_string(cursor,end); + if (ret.second != cursor) { + attributes[name] = ret.first; + cursor = ret.second; + continue; + } + + ret = http::parser::extract_token(cursor,end); + if (ret.first.empty()) { + // error : expected token or quoted string + return begin; + } else { + attributes[name] = ret.first; + cursor = ret.second; + } + } + + return cursor; +} + +/// Extract HTTP parameters +/** + * An http parameters list is a comma delimited list of tokens followed by + * optional semicolon delimited attributes lists. + * + * Parameters extracted are appended to the supplied parameters list + * `parameters`. + * + * @param [in] begin An iterator to the beginning of the sequence + * @param [in] end An iterator to the end of the sequence + * @param [out] parameters A reference to the parameters list to append + * paramter values extracted to + * @return An iterator to the character after the last parameter read + */ +template +InputIterator extract_parameters(InputIterator begin, InputIterator end, + parameter_list ¶meters) +{ + InputIterator cursor; + + if (begin == end) { + // error: expected non-zero length range + return begin; + } + + cursor = begin; + std::pair ret; + + /** + * LWS + * token + * LWS + * *(";" method-param) + * LWS + * ,=loop again + */ + while (cursor != end) { + std::string parameter_name; + attribute_list attributes; + + // extract any stray whitespace + cursor = http::parser::extract_all_lws(cursor,end); + if (cursor == end) {break;} + + ret = http::parser::extract_token(cursor,end); + + if (ret.first.empty()) { + // error: expected a token + return begin; + } else { + parameter_name = ret.first; + cursor = ret.second; + } + + // Safe break point, insert parameter with blank attributes and exit + cursor = http::parser::extract_all_lws(cursor,end); + if (cursor == end) { + //parameters[parameter_name] = attributes; + parameters.push_back(std::make_pair(parameter_name,attributes)); + break; + } + + // If there is an attribute list, read it in + if (*cursor == ';') { + InputIterator acursor; + + ++cursor; + acursor = http::parser::extract_attributes(cursor,end,attributes); + + if (acursor == cursor) { + // attribute extraction ended in syntax error + return begin; + } + + cursor = acursor; + } + + // insert parameter into output list + //parameters[parameter_name] = attributes; + parameters.push_back(std::make_pair(parameter_name,attributes)); + + cursor = http::parser::extract_all_lws(cursor,end); + if (cursor == end) {break;} + + // if next char is ',' then read another parameter, else stop + if (*cursor != ',') { + break; + } + + // advance past comma + ++cursor; + + if (cursor == end) { + // expected more bytes after a comma + return begin; + } + } + + return cursor; +} + +inline std::string strip_lws(std::string const & input) { + std::string::const_iterator begin = extract_all_lws(input.begin(),input.end()); + if (begin == input.end()) { + return std::string(); + } + + std::string::const_reverse_iterator rbegin = extract_all_lws(input.rbegin(),input.rend()); + if (rbegin == input.rend()) { + return std::string(); + } + + return std::string(begin,rbegin.base()); +} + +/// Base HTTP parser +/** + * Includes methods and data elements common to all types of HTTP messages such + * as headers, versions, bodies, etc. + */ +class parser { +public: + parser() + : m_header_bytes(0) + , m_body_bytes_needed(0) + , m_body_bytes_max(max_body_size) + , m_body_encoding(body_encoding::unknown) {} + + /// Get the HTTP version string + /** + * @return The version string for this parser + */ + std::string const & get_version() const { + return m_version; + } + + /// Set HTTP parser Version + /** + * Input should be in format: HTTP/x.y where x and y are positive integers. + * @todo Does this method need any validation? + * + * @param [in] version The value to set the HTTP version to. + */ + void set_version(std::string const & version); + + /// Get the value of an HTTP header + /** + * @todo Make this method case insensitive. + * + * @param [in] key The name/key of the header to get. + * @return The value associated with the given HTTP header key. + */ + std::string const & get_header(std::string const & key) const; + + /// Extract an HTTP parameter list from a parser header. + /** + * If the header requested doesn't exist or exists and is empty the + * parameter list is valid (but empty). + * + * @param [in] key The name/key of the HTTP header to use as input. + * @param [out] out The parameter list to store extracted parameters in. + * @return Whether or not the input was a valid parameter list. + */ + bool get_header_as_plist(std::string const & key, parameter_list & out) + const; + + /// Return a list of all HTTP headers + /** + * Return a list of all HTTP headers + * + * @since 0.8.0 + * + * @return A list of all HTTP headers + */ + header_list const & get_headers() const; + + /// Append a value to an existing HTTP header + /** + * This method will set the value of the HTTP header `key` with the + * indicated value. If a header with the name `key` already exists, `val` + * will be appended to the existing value. + * + * @todo Make this method case insensitive. + * @todo Should there be any restrictions on which keys are allowed? + * @todo Exception free varient + * + * @see replace_header + * + * @param [in] key The name/key of the header to append to. + * @param [in] val The value to append. + */ + void append_header(std::string const & key, std::string const & val); + + /// Set a value for an HTTP header, replacing an existing value + /** + * This method will set the value of the HTTP header `key` with the + * indicated value. If a header with the name `key` already exists, `val` + * will replace the existing value. + * + * @todo Make this method case insensitive. + * @todo Should there be any restrictions on which keys are allowed? + * @todo Exception free varient + * + * @see append_header + * + * @param [in] key The name/key of the header to append to. + * @param [in] val The value to append. + */ + void replace_header(std::string const & key, std::string const & val); + + /// Remove a header from the parser + /** + * Removes the header entirely from the parser. This is different than + * setting the value of the header to blank. + * + * @todo Make this method case insensitive. + * + * @param [in] key The name/key of the header to remove. + */ + void remove_header(std::string const & key); + + /// Get HTTP body + /** + * Gets the body of the HTTP object + * + * @return The body of the HTTP message. + */ + std::string const & get_body() const { + return m_body; + } + + /// Set body content + /** + * Set the body content of the HTTP response to the parameter string. Note + * set_body will also set the Content-Length HTTP header to the appropriate + * value. If you want the Content-Length header to be something else, do so + * via replace_header("Content-Length") after calling set_body() + * + * @param value String data to include as the body content. + */ + void set_body(std::string const & value); + + /// Get body size limit + /** + * Retrieves the maximum number of bytes to parse & buffer before canceling + * a request. + * + * @since 0.5.0 + * + * @return The maximum length of a message body. + */ + size_t get_max_body_size() const { + return m_body_bytes_max; + } + + /// Set body size limit + /** + * Set the maximum number of bytes to parse and buffer before canceling a + * request. + * + * @since 0.5.0 + * + * @param value The size to set the max body length to. + */ + void set_max_body_size(size_t value) { + m_body_bytes_max = value; + } + + /// Extract an HTTP parameter list from a string. + /** + * @param [in] in The input string. + * @param [out] out The parameter list to store extracted parameters in. + * @return Whether or not the input was a valid parameter list. + */ + bool parse_parameter_list(std::string const & in, parameter_list & out) + const; +protected: + /// Process a header line + /** + * @todo Update this method to be exception free. + * + * @param [in] begin An iterator to the beginning of the sequence. + * @param [in] end An iterator to the end of the sequence. + */ + void process_header(std::string::iterator begin, std::string::iterator end); + + /// Prepare the parser to begin parsing body data + /** + * Inspects headers to determine if the message has a body that needs to be + * read. If so, sets up the necessary state, otherwise returns false. If + * this method returns true and loading the message body is desired call + * `process_body` until it returns zero bytes or an error. + * + * Must not be called until after all headers have been processed. + * + * @since 0.5.0 + * + * @return True if more bytes are needed to load the body, false otherwise. + */ + bool prepare_body(); + + /// Process body data + /** + * Parses body data. + * + * @since 0.5.0 + * + * @param [in] begin An iterator to the beginning of the sequence. + * @param [in] end An iterator to the end of the sequence. + * @return The number of bytes processed + */ + size_t process_body(char const * buf, size_t len); + + /// Check if the parser is done parsing the body + /** + * Behavior before a call to `prepare_body` is undefined. + * + * @since 0.5.0 + * + * @return True if the message body has been completed loaded. + */ + bool body_ready() const { + return (m_body_bytes_needed == 0); + } + + /// Generate and return the HTTP headers as a string + /** + * Each headers will be followed by the \r\n sequence including the last one. + * A second \r\n sequence (blank header) is not appended by this method + * + * @return The HTTP headers as a string. + */ + std::string raw_headers() const; + + std::string m_version; + header_list m_headers; + + size_t m_header_bytes; + + std::string m_body; + size_t m_body_bytes_needed; + size_t m_body_bytes_max; + body_encoding::value m_body_encoding; +}; + +} // namespace parser +} // namespace http +} // namespace websocketpp + +#include + +#endif // HTTP_PARSER_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/http/request.hpp b/thirdparty/websocketpp/include/websocketpp/http/request.hpp new file mode 100644 index 0000000..a3065e5 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/http/request.hpp @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2014, 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 HTTP_PARSER_REQUEST_HPP +#define HTTP_PARSER_REQUEST_HPP + +#include + +#include +#include + +namespace websocketpp { +namespace http { +namespace parser { + +/// Stores, parses, and manipulates HTTP requests +/** + * http::request provides the following functionality for working with HTTP + * requests. + * + * - Initialize request via manually setting each element + * - Initialize request via reading raw bytes and parsing + * - Once initialized, access individual parsed elements + * - Once initialized, read entire request as raw bytes + */ +class request : public parser { +public: + typedef request type; + typedef lib::shared_ptr ptr; + + request() + : m_buf(lib::make_shared()) + , m_ready(false) {} + + /// Process bytes in the input buffer + /** + * Process up to len bytes from input buffer buf. Returns the number of + * bytes processed. Bytes left unprocessed means bytes left over after the + * final header delimiters. + * + * Consume is a streaming processor. It may be called multiple times on one + * request and the full headers need not be available before processing can + * begin. If the end of the request was reached during this call to consume + * the ready flag will be set. Further calls to consume once ready will be + * ignored. + * + * Consume will throw an http::exception in the case of an error. Typical + * error reasons include malformed requests, incomplete requests, and max + * header size being reached. + * + * @param buf Pointer to byte buffer + * @param len Size of byte buffer + * @return Number of bytes processed. + */ + size_t consume(char const * buf, size_t len); + + /// Returns whether or not the request is ready for reading. + bool ready() const { + return m_ready; + } + + /// Returns the full raw request (including the body) + std::string raw() const; + + /// Returns the raw request headers only (similar to an HTTP HEAD request) + std::string raw_head() const; + + /// Set the HTTP method. Must be a valid HTTP token + void set_method(std::string const & method); + + /// Return the request method + std::string const & get_method() const { + return m_method; + } + + /// Set the HTTP uri. Must be a valid HTTP uri + void set_uri(std::string const & uri); + + /// Return the requested URI + std::string const & get_uri() const { + return m_uri; + } + +private: + /// Helper function for message::consume. Process request line + void process(std::string::iterator begin, std::string::iterator end); + + lib::shared_ptr m_buf; + std::string m_method; + std::string m_uri; + bool m_ready; +}; + +} // namespace parser +} // namespace http +} // namespace websocketpp + +#include + +#endif // HTTP_PARSER_REQUEST_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/http/response.hpp b/thirdparty/websocketpp/include/websocketpp/http/response.hpp new file mode 100644 index 0000000..36731d2 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/http/response.hpp @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2014, 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 HTTP_PARSER_RESPONSE_HPP +#define HTTP_PARSER_RESPONSE_HPP + +#include +#include + +#include + +namespace websocketpp { +namespace http { +namespace parser { + +/// Stores, parses, and manipulates HTTP responses +/** + * http::response provides the following functionality for working with HTTP + * responses. + * + * - Initialize response via manually setting each element + * - Initialize response via reading raw bytes and parsing + * - Once initialized, access individual parsed elements + * - Once initialized, read entire response as raw bytes + * + * http::response checks for header completeness separately from the full + * response. Once the header is complete, the Content-Length header is read to + * determine when to stop reading body bytes. If no Content-Length is present + * ready() will never return true. It is the responsibility of the caller to + * consume to determine when the response is complete (ie when the connection + * terminates, or some other metric). + */ +class response : public parser { +public: + typedef response type; + typedef lib::shared_ptr ptr; + + response() + : m_read(0) + , m_buf(lib::make_shared()) + , m_status_code(status_code::uninitialized) + , m_state(RESPONSE_LINE) {} + + /// Process bytes in the input buffer + /** + * Process up to len bytes from input buffer buf. Returns the number of + * bytes processed. Bytes left unprocessed means bytes left over after the + * final header delimiters. + * + * Consume is a streaming processor. It may be called multiple times on one + * response and the full headers need not be available before processing can + * begin. If the end of the response was reached during this call to consume + * the ready flag will be set. Further calls to consume once ready will be + * ignored. + * + * Consume will throw an http::exception in the case of an error. Typical + * error reasons include malformed responses, incomplete responses, and max + * header size being reached. + * + * @param buf Pointer to byte buffer + * @param len Size of byte buffer + * @return Number of bytes processed. + */ + size_t consume(char const * buf, size_t len); + + /// Process bytes in the input buffer (istream version) + /** + * Process bytes from istream s. Returns the number of bytes processed. + * Bytes left unprocessed means bytes left over after the final header + * delimiters. + * + * Consume is a streaming processor. It may be called multiple times on one + * response and the full headers need not be available before processing can + * begin. If the end of the response was reached during this call to consume + * the ready flag will be set. Further calls to consume once ready will be + * ignored. + * + * Consume will throw an http::exception in the case of an error. Typical + * error reasons include malformed responses, incomplete responses, and max + * header size being reached. + * + * @param buf Pointer to byte buffer + * @param len Size of byte buffer + * @return Number of bytes processed. + */ + size_t consume(std::istream & s); + + /// Returns true if the response is ready. + /** + * @note will never return true if the content length header is not present + */ + bool ready() const { + return m_state == DONE; + } + + /// Returns true if the response headers are fully parsed. + bool headers_ready() const { + return (m_state == BODY || m_state == DONE); + } + + /// Returns the full raw response + std::string raw() const; + + /// Set response status code and message + /** + * Sets the response status code to `code` and looks up the corresponding + * message for standard codes. Non-standard codes will be entered as Unknown + * use set_status(status_code::value,std::string) overload to set both + * values explicitly. + * + * @param code Code to set + * @param msg Message to set + */ + void set_status(status_code::value code); + + /// Set response status code and message + /** + * Sets the response status code and message to independent custom values. + * use set_status(status_code::value) to set the code and have the standard + * message be automatically set. + * + * @param code Code to set + * @param msg Message to set + */ + void set_status(status_code::value code, std::string const & msg); + + /// Return the response status code + status_code::value get_status_code() const { + return m_status_code; + } + + /// Return the response status message + const std::string& get_status_msg() const { + return m_status_msg; + } +private: + /// Helper function for consume. Process response line + void process(std::string::iterator begin, std::string::iterator end); + + /// Helper function for processing body bytes + size_t process_body(char const * buf, size_t len); + + enum state { + RESPONSE_LINE = 0, + HEADERS = 1, + BODY = 2, + DONE = 3 + }; + + std::string m_status_msg; + size_t m_read; + lib::shared_ptr m_buf; + status_code::value m_status_code; + state m_state; + +}; + +} // namespace parser +} // namespace http +} // namespace websocketpp + +#include + +#endif // HTTP_PARSER_RESPONSE_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/impl/connection_impl.hpp b/thirdparty/websocketpp/include/websocketpp/impl/connection_impl.hpp new file mode 100644 index 0000000..d635817 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/impl/connection_impl.hpp @@ -0,0 +1,2375 @@ +/* + * Copyright (c) 2014, 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_CONNECTION_IMPL_HPP +#define WEBSOCKETPP_CONNECTION_IMPL_HPP + +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace websocketpp { + +namespace istate = session::internal_state; + +template +void connection::set_termination_handler( + termination_handler new_handler) +{ + m_alog->write(log::alevel::devel, + "connection set_termination_handler"); + + //scoped_lock_type lock(m_connection_state_lock); + + m_termination_handler = new_handler; +} + +template +std::string const & connection::get_origin() const { + //scoped_lock_type lock(m_connection_state_lock); + return m_processor->get_origin(m_request); +} + +template +size_t connection::get_buffered_amount() const { + //scoped_lock_type lock(m_connection_state_lock); + return m_send_buffer_size; +} + +template +session::state::value connection::get_state() const { + //scoped_lock_type lock(m_connection_state_lock); + return m_state; +} + +template +lib::error_code connection::send(std::string const & payload, + frame::opcode::value op) +{ + message_ptr msg = m_msg_manager->get_message(op,payload.size()); + msg->append_payload(payload); + msg->set_compressed(true); + + return send(msg); +} + +template +lib::error_code connection::send(void const * payload, size_t len, + frame::opcode::value op) +{ + message_ptr msg = m_msg_manager->get_message(op,len); + msg->append_payload(payload,len); + + return send(msg); +} + +template +lib::error_code connection::send(typename config::message_type::ptr msg) +{ + if (m_alog->static_test(log::alevel::devel)) { + m_alog->write(log::alevel::devel,"connection send"); + } + + { + scoped_lock_type lock(m_connection_state_lock); + if (m_state != session::state::open) { + return error::make_error_code(error::invalid_state); + } + } + + message_ptr outgoing_msg; + bool needs_writing = false; + + if (msg->get_prepared()) { + outgoing_msg = msg; + + scoped_lock_type lock(m_write_lock); + write_push(outgoing_msg); + needs_writing = !m_write_flag && !m_send_queue.empty(); + } else { + outgoing_msg = m_msg_manager->get_message(); + + if (!outgoing_msg) { + return error::make_error_code(error::no_outgoing_buffers); + } + + scoped_lock_type lock(m_write_lock); + lib::error_code ec = m_processor->prepare_data_frame(msg,outgoing_msg); + + if (ec) { + return ec; + } + + write_push(outgoing_msg); + needs_writing = !m_write_flag && !m_send_queue.empty(); + } + + if (needs_writing) { + transport_con_type::dispatch(lib::bind( + &type::write_frame, + type::get_shared() + )); + } + + return lib::error_code(); +} + +template +void connection::ping(std::string const& payload, lib::error_code& ec) { + if (m_alog->static_test(log::alevel::devel)) { + m_alog->write(log::alevel::devel,"connection ping"); + } + + { + scoped_lock_type lock(m_connection_state_lock); + if (m_state != session::state::open) { + std::stringstream ss; + ss << "connection::ping called from invalid state " << m_state; + m_alog->write(log::alevel::devel,ss.str()); + ec = error::make_error_code(error::invalid_state); + return; + } + } + + message_ptr msg = m_msg_manager->get_message(); + if (!msg) { + ec = error::make_error_code(error::no_outgoing_buffers); + return; + } + + ec = m_processor->prepare_ping(payload,msg); + if (ec) {return;} + + // set ping timer if we are listening for one + if (m_pong_timeout_handler) { + // Cancel any existing timers + if (m_ping_timer) { + m_ping_timer->cancel(); + } + + if (m_pong_timeout_dur > 0) { + m_ping_timer = transport_con_type::set_timer( + m_pong_timeout_dur, + lib::bind( + &type::handle_pong_timeout, + type::get_shared(), + payload, + lib::placeholders::_1 + ) + ); + } + + if (!m_ping_timer) { + // Our transport doesn't support timers + m_elog->write(log::elevel::warn,"Warning: a pong_timeout_handler is \ + set but the transport in use does not support timeouts."); + } + } + + bool needs_writing = false; + { + scoped_lock_type lock(m_write_lock); + write_push(msg); + needs_writing = !m_write_flag && !m_send_queue.empty(); + } + + if (needs_writing) { + transport_con_type::dispatch(lib::bind( + &type::write_frame, + type::get_shared() + )); + } + + ec = lib::error_code(); +} + +template +void connection::ping(std::string const & payload) { + lib::error_code ec; + ping(payload,ec); + if (ec) { + throw exception(ec); + } +} + +template +void connection::handle_pong_timeout(std::string payload, + lib::error_code const & ec) +{ + if (ec) { + if (ec == transport::error::operation_aborted) { + // ignore, this is expected + return; + } + + m_elog->write(log::elevel::devel,"pong_timeout error: "+ec.message()); + return; + } + + if (m_pong_timeout_handler) { + m_pong_timeout_handler(m_connection_hdl,payload); + } +} + +template +void connection::pong(std::string const& payload, lib::error_code& ec) { + if (m_alog->static_test(log::alevel::devel)) { + m_alog->write(log::alevel::devel,"connection pong"); + } + + { + scoped_lock_type lock(m_connection_state_lock); + if (m_state != session::state::open) { + std::stringstream ss; + ss << "connection::pong called from invalid state " << m_state; + m_alog->write(log::alevel::devel,ss.str()); + ec = error::make_error_code(error::invalid_state); + return; + } + } + + message_ptr msg = m_msg_manager->get_message(); + if (!msg) { + ec = error::make_error_code(error::no_outgoing_buffers); + return; + } + + ec = m_processor->prepare_pong(payload,msg); + if (ec) {return;} + + bool needs_writing = false; + { + scoped_lock_type lock(m_write_lock); + write_push(msg); + needs_writing = !m_write_flag && !m_send_queue.empty(); + } + + if (needs_writing) { + transport_con_type::dispatch(lib::bind( + &type::write_frame, + type::get_shared() + )); + } + + ec = lib::error_code(); +} + +template +void connection::pong(std::string const & payload) { + lib::error_code ec; + pong(payload,ec); + if (ec) { + throw exception(ec); + } +} + +template +void connection::close(close::status::value const code, + std::string const & reason, lib::error_code & ec) +{ + if (m_alog->static_test(log::alevel::devel)) { + m_alog->write(log::alevel::devel,"connection close"); + } + + // Truncate reason to maximum size allowable in a close frame. + std::string tr(reason,0,std::min(reason.size(), + frame::limits::close_reason_size)); + + scoped_lock_type lock(m_connection_state_lock); + + if (m_state != session::state::open) { + ec = error::make_error_code(error::invalid_state); + return; + } + + ec = this->send_close_frame(code,tr,false,close::status::terminal(code)); +} + +template +void connection::close(close::status::value const code, + std::string const & reason) +{ + lib::error_code ec; + close(code,reason,ec); + if (ec) { + throw exception(ec); + } +} + +/// Trigger the on_interrupt handler +/** + * This is thread safe if the transport is thread safe + */ +template +lib::error_code connection::interrupt() { + m_alog->write(log::alevel::devel,"connection connection::interrupt"); + return transport_con_type::interrupt( + lib::bind( + &type::handle_interrupt, + type::get_shared() + ) + ); +} + + +template +void connection::handle_interrupt() { + if (m_interrupt_handler) { + m_interrupt_handler(m_connection_hdl); + } +} + +template +lib::error_code connection::pause_reading() { + m_alog->write(log::alevel::devel,"connection connection::pause_reading"); + return transport_con_type::dispatch( + lib::bind( + &type::handle_pause_reading, + type::get_shared() + ) + ); +} + +/// Pause reading handler. Not safe to call directly +template +void connection::handle_pause_reading() { + m_alog->write(log::alevel::devel,"connection connection::handle_pause_reading"); + m_read_flag = false; +} + +template +lib::error_code connection::resume_reading() { + m_alog->write(log::alevel::devel,"connection connection::resume_reading"); + return transport_con_type::dispatch( + lib::bind( + &type::handle_resume_reading, + type::get_shared() + ) + ); +} + +/// Resume reading helper method. Not safe to call directly +template +void connection::handle_resume_reading() { + m_read_flag = true; + read_frame(); +} + + + + + + + + + + + +template +bool connection::get_secure() const { + //scoped_lock_type lock(m_connection_state_lock); + return m_uri->get_secure(); +} + +template +std::string const & connection::get_host() const { + //scoped_lock_type lock(m_connection_state_lock); + return m_uri->get_host(); +} + +template +std::string const & connection::get_resource() const { + //scoped_lock_type lock(m_connection_state_lock); + return m_uri->get_resource(); +} + +template +uint16_t connection::get_port() const { + //scoped_lock_type lock(m_connection_state_lock); + return m_uri->get_port(); +} + +template +uri_ptr connection::get_uri() const { + //scoped_lock_type lock(m_connection_state_lock); + return m_uri; +} + +template +void connection::set_uri(uri_ptr uri) { + //scoped_lock_type lock(m_connection_state_lock); + m_uri = uri; +} + + + + + + +template +std::string const & connection::get_subprotocol() const { + return m_subprotocol; +} + +template +std::vector const & +connection::get_requested_subprotocols() const { + return m_requested_subprotocols; +} + +template +void connection::add_subprotocol(std::string const & value, + lib::error_code & ec) +{ + if (m_is_server) { + ec = error::make_error_code(error::client_only); + return; + } + + // If the value is empty or has a non-RFC2616 token character it is invalid. + if (value.empty() || std::find_if(value.begin(),value.end(), + http::is_not_token_char) != value.end()) + { + ec = error::make_error_code(error::invalid_subprotocol); + return; + } + + m_requested_subprotocols.push_back(value); +} + +template +void connection::add_subprotocol(std::string const & value) { + lib::error_code ec; + this->add_subprotocol(value,ec); + if (ec) { + throw exception(ec); + } +} + + +template +void connection::select_subprotocol(std::string const & value, + lib::error_code & ec) +{ + if (!m_is_server) { + ec = error::make_error_code(error::server_only); + return; + } + + if (value.empty()) { + ec = lib::error_code(); + return; + } + + std::vector::iterator it; + + it = std::find(m_requested_subprotocols.begin(), + m_requested_subprotocols.end(), + value); + + if (it == m_requested_subprotocols.end()) { + ec = error::make_error_code(error::unrequested_subprotocol); + return; + } + + m_subprotocol = value; +} + +template +void connection::select_subprotocol(std::string const & value) { + lib::error_code ec; + this->select_subprotocol(value,ec); + if (ec) { + throw exception(ec); + } +} + + +template +std::string const & +connection::get_request_header(std::string const & key) const { + return m_request.get_header(key); +} + +template +std::string const & +connection::get_request_body() const { + return m_request.get_body(); +} + +template +std::string const & +connection::get_response_header(std::string const & key) const { + return m_response.get_header(key); +} + +// TODO: EXCEPTION_FREE +template +void connection::set_status(http::status_code::value code) +{ + if (m_internal_state != istate::PROCESS_HTTP_REQUEST) { + throw exception("Call to set_status from invalid state", + error::make_error_code(error::invalid_state)); + } + m_response.set_status(code); +} + +// TODO: EXCEPTION_FREE +template +void connection::set_status(http::status_code::value code, + std::string const & msg) +{ + if (m_internal_state != istate::PROCESS_HTTP_REQUEST) { + throw exception("Call to set_status from invalid state", + error::make_error_code(error::invalid_state)); + } + + m_response.set_status(code,msg); +} + +// TODO: EXCEPTION_FREE +template +void connection::set_body(std::string const & value) { + if (m_internal_state != istate::PROCESS_HTTP_REQUEST) { + throw exception("Call to set_status from invalid state", + error::make_error_code(error::invalid_state)); + } + + m_response.set_body(value); +} + +// TODO: EXCEPTION_FREE +template +void connection::append_header(std::string const & key, + std::string const & val) +{ + if (m_is_server) { + if (m_internal_state == istate::PROCESS_HTTP_REQUEST) { + // we are setting response headers for an incoming server connection + m_response.append_header(key,val); + } else { + throw exception("Call to append_header from invalid state", + error::make_error_code(error::invalid_state)); + } + } else { + if (m_internal_state == istate::USER_INIT) { + // we are setting initial headers for an outgoing client connection + m_request.append_header(key,val); + } else { + throw exception("Call to append_header from invalid state", + error::make_error_code(error::invalid_state)); + } + } +} + +// TODO: EXCEPTION_FREE +template +void connection::replace_header(std::string const & key, + std::string const & val) +{ + if (m_is_server) { + if (m_internal_state == istate::PROCESS_HTTP_REQUEST) { + // we are setting response headers for an incoming server connection + m_response.replace_header(key,val); + } else { + throw exception("Call to replace_header from invalid state", + error::make_error_code(error::invalid_state)); + } + } else { + if (m_internal_state == istate::USER_INIT) { + // we are setting initial headers for an outgoing client connection + m_request.replace_header(key,val); + } else { + throw exception("Call to replace_header from invalid state", + error::make_error_code(error::invalid_state)); + } + } +} + +// TODO: EXCEPTION_FREE +template +void connection::remove_header(std::string const & key) +{ + if (m_is_server) { + if (m_internal_state == istate::PROCESS_HTTP_REQUEST) { + // we are setting response headers for an incoming server connection + m_response.remove_header(key); + } else { + throw exception("Call to remove_header from invalid state", + error::make_error_code(error::invalid_state)); + } + } else { + if (m_internal_state == istate::USER_INIT) { + // we are setting initial headers for an outgoing client connection + m_request.remove_header(key); + } else { + throw exception("Call to remove_header from invalid state", + error::make_error_code(error::invalid_state)); + } + } +} + +/// Defer HTTP Response until later +/** + * Used in the http handler to defer the HTTP response for this connection + * until later. Handshake timers will be canceled and the connection will be + * left open until `send_http_response` or an equivalent is called. + * + * Warning: deferred connections won't time out and as a result can tie up + * resources. + * + * @return A status code, zero on success, non-zero otherwise + */ +template +lib::error_code connection::defer_http_response() { + // Cancel handshake timer, otherwise the connection will time out and we'll + // close the connection before the app has a chance to send a response. + if (m_handshake_timer) { + m_handshake_timer->cancel(); + m_handshake_timer.reset(); + } + + // Do something to signal deferral + m_http_state = session::http_state::deferred; + + return lib::error_code(); +} + +/// Send deferred HTTP Response (exception free) +/** + * Sends an http response to an HTTP connection that was deferred. This will + * send a complete response including all headers, status line, and body + * text. The connection will be closed afterwards. + * + * @since 0.6.0 + * + * @param ec A status code, zero on success, non-zero otherwise + */ +template +void connection::send_http_response(lib::error_code & ec) { + { + scoped_lock_type lock(m_connection_state_lock); + if (m_http_state != session::http_state::deferred) { + ec = error::make_error_code(error::invalid_state); + return; + } + + m_http_state = session::http_state::body_written; + } + + this->write_http_response(lib::error_code()); + ec = lib::error_code(); +} + +template +void connection::send_http_response() { + lib::error_code ec; + this->send_http_response(ec); + if (ec) { + throw exception(ec); + } +} + + + + +/******** logic thread ********/ + +template +void connection::start() { + m_alog->write(log::alevel::devel,"connection start"); + + if (m_internal_state != istate::USER_INIT) { + m_alog->write(log::alevel::devel,"Start called in invalid state"); + this->terminate(error::make_error_code(error::invalid_state)); + return; + } + + m_internal_state = istate::TRANSPORT_INIT; + + // Depending on how the transport implements init this function may return + // immediately and call handle_transport_init later or call + // handle_transport_init from this function. + transport_con_type::init( + lib::bind( + &type::handle_transport_init, + type::get_shared(), + lib::placeholders::_1 + ) + ); +} + +template +void connection::handle_transport_init(lib::error_code const & ec) { + m_alog->write(log::alevel::devel,"connection handle_transport_init"); + + lib::error_code ecm = ec; + + if (m_internal_state != istate::TRANSPORT_INIT) { + m_alog->write(log::alevel::devel, + "handle_transport_init must be called from transport init state"); + ecm = error::make_error_code(error::invalid_state); + } + + if (ecm) { + std::stringstream s; + s << "handle_transport_init received error: "<< ecm.message(); + m_elog->write(log::elevel::rerror,s.str()); + + this->terminate(ecm); + return; + } + + // At this point the transport is ready to read and write bytes. + if (m_is_server) { + m_internal_state = istate::READ_HTTP_REQUEST; + this->read_handshake(1); + } else { + // We are a client. Set the processor to the version specified in the + // config file and send a handshake request. + m_internal_state = istate::WRITE_HTTP_REQUEST; + m_processor = get_processor(config::client_version); + this->send_http_request(); + } +} + +template +void connection::read_handshake(size_t num_bytes) { + m_alog->write(log::alevel::devel,"connection read_handshake"); + + if (m_open_handshake_timeout_dur > 0) { + m_handshake_timer = transport_con_type::set_timer( + m_open_handshake_timeout_dur, + lib::bind( + &type::handle_open_handshake_timeout, + type::get_shared(), + lib::placeholders::_1 + ) + ); + } + + transport_con_type::async_read_at_least( + num_bytes, + m_buf, + config::connection_read_buffer_size, + lib::bind( + &type::handle_read_handshake, + type::get_shared(), + lib::placeholders::_1, + lib::placeholders::_2 + ) + ); +} + +// All exit paths for this function need to call write_http_response() or submit +// a new read request with this function as the handler. +template +void connection::handle_read_handshake(lib::error_code const & ec, + size_t bytes_transferred) +{ + m_alog->write(log::alevel::devel,"connection handle_read_handshake"); + + lib::error_code ecm = ec; + + if (!ecm) { + scoped_lock_type lock(m_connection_state_lock); + + if (m_state == session::state::connecting) { + if (m_internal_state != istate::READ_HTTP_REQUEST) { + ecm = error::make_error_code(error::invalid_state); + } + } else if (m_state == session::state::closed) { + // The connection was canceled while the response was being sent, + // usually by the handshake timer. This is basically expected + // (though hopefully rare) and there is nothing we can do so ignore. + m_alog->write(log::alevel::devel, + "handle_read_handshake invoked after connection was closed"); + return; + } else { + ecm = error::make_error_code(error::invalid_state); + } + } + + if (ecm) { + if (ecm == transport::error::eof && m_state == session::state::closed) { + // we expect to get eof if the connection is closed already + m_alog->write(log::alevel::devel, + "got (expected) eof/state error from closed con"); + return; + } + + log_err(log::elevel::rerror,"handle_read_handshake",ecm); + this->terminate(ecm); + return; + } + + // Boundaries checking. TODO: How much of this should be done? + if (bytes_transferred > config::connection_read_buffer_size) { + m_elog->write(log::elevel::fatal,"Fatal boundaries checking error."); + this->terminate(make_error_code(error::general)); + return; + } + + size_t bytes_processed = 0; + try { + bytes_processed = m_request.consume(m_buf,bytes_transferred); + } catch (http::exception &e) { + // All HTTP exceptions will result in this request failing and an error + // response being returned. No more bytes will be read in this con. + m_response.set_status(e.m_error_code,e.m_error_msg); + this->write_http_response_error(error::make_error_code(error::http_parse_error)); + return; + } + + // More paranoid boundaries checking. + // TODO: Is this overkill? + if (bytes_processed > bytes_transferred) { + m_elog->write(log::elevel::fatal,"Fatal boundaries checking error."); + this->terminate(make_error_code(error::general)); + return; + } + + if (m_alog->static_test(log::alevel::devel)) { + std::stringstream s; + s << "bytes_transferred: " << bytes_transferred + << " bytes, bytes processed: " << bytes_processed << " bytes"; + m_alog->write(log::alevel::devel,s.str()); + } + + if (m_request.ready()) { + lib::error_code processor_ec = this->initialize_processor(); + if (processor_ec) { + this->write_http_response_error(processor_ec); + return; + } + + if (m_processor && m_processor->get_version() == 0) { + // Version 00 has an extra requirement to read some bytes after the + // handshake + if (bytes_transferred-bytes_processed >= 8) { + m_request.replace_header( + "Sec-WebSocket-Key3", + std::string(m_buf+bytes_processed,m_buf+bytes_processed+8) + ); + bytes_processed += 8; + } else { + // TODO: need more bytes + m_alog->write(log::alevel::devel,"short key3 read"); + m_response.set_status(http::status_code::internal_server_error); + this->write_http_response_error(processor::error::make_error_code(processor::error::short_key3)); + return; + } + } + + if (m_alog->static_test(log::alevel::devel)) { + m_alog->write(log::alevel::devel,m_request.raw()); + if (!m_request.get_header("Sec-WebSocket-Key3").empty()) { + m_alog->write(log::alevel::devel, + utility::to_hex(m_request.get_header("Sec-WebSocket-Key3"))); + } + } + + // The remaining bytes in m_buf are frame data. Copy them to the + // beginning of the buffer and note the length. They will be read after + // the handshake completes and before more bytes are read. + std::copy(m_buf+bytes_processed,m_buf+bytes_transferred,m_buf); + m_buf_cursor = bytes_transferred-bytes_processed; + + + m_internal_state = istate::PROCESS_HTTP_REQUEST; + + // We have the complete request. Process it. + lib::error_code handshake_ec = this->process_handshake_request(); + + // Write a response if this is a websocket connection or if it is an + // HTTP connection for which the response has not been deferred or + // started yet by a different system (i.e. still in init state). + if (!m_is_http || m_http_state == session::http_state::init) { + this->write_http_response(handshake_ec); + } + } else { + // read at least 1 more byte + transport_con_type::async_read_at_least( + 1, + m_buf, + config::connection_read_buffer_size, + lib::bind( + &type::handle_read_handshake, + type::get_shared(), + lib::placeholders::_1, + lib::placeholders::_2 + ) + ); + } +} + +// write_http_response requires the request to be fully read and the connection +// to be in the PROCESS_HTTP_REQUEST state. In some cases we can detect errors +// before the request is fully read (specifically at a point where we aren't +// sure if the hybi00 key3 bytes need to be read). This method sets the correct +// state and calls write_http_response +template +void connection::write_http_response_error(lib::error_code const & ec) { + if (m_internal_state != istate::READ_HTTP_REQUEST) { + m_alog->write(log::alevel::devel, + "write_http_response_error called in invalid state"); + this->terminate(error::make_error_code(error::invalid_state)); + return; + } + + m_internal_state = istate::PROCESS_HTTP_REQUEST; + + this->write_http_response(ec); +} + +// All exit paths for this function need to call write_http_response() or submit +// a new read request with this function as the handler. +template +void connection::handle_read_frame(lib::error_code const & ec, + size_t bytes_transferred) +{ + //m_alog->write(log::alevel::devel,"connection handle_read_frame"); + + lib::error_code ecm = ec; + + if (!ecm && m_internal_state != istate::PROCESS_CONNECTION) { + ecm = error::make_error_code(error::invalid_state); + } + + if (ecm) { + log::level echannel = log::elevel::rerror; + + if (ecm == transport::error::eof) { + if (m_state == session::state::closed) { + // we expect to get eof if the connection is closed already + // just ignore it + m_alog->write(log::alevel::devel,"got eof from closed con"); + return; + } else if (m_state == session::state::closing && !m_is_server) { + // If we are a client we expect to get eof in the closing state, + // this is a signal to terminate our end of the connection after + // the closing handshake + terminate(lib::error_code()); + return; + } + } else if (ecm == error::invalid_state) { + // In general, invalid state errors in the closed state are the + // result of handlers that were in the system already when the state + // changed and should be ignored as they pose no problems and there + // is nothing useful that we can do about them. + if (m_state == session::state::closed) { + m_alog->write(log::alevel::devel, + "handle_read_frame: got invalid istate in closed state"); + return; + } + } else if (ecm == transport::error::action_after_shutdown) { + echannel = log::elevel::info; + } else { + // TODO: more generally should we do something different here in the + // case that m_state is cosed? Are errors after the connection is + // already closed really an rerror? + } + + + + log_err(echannel, "handle_read_frame", ecm); + this->terminate(ecm); + return; + } + + // Boundaries checking. TODO: How much of this should be done? + /*if (bytes_transferred > config::connection_read_buffer_size) { + m_elog->write(log::elevel::fatal,"Fatal boundaries checking error"); + this->terminate(make_error_code(error::general)); + return; + }*/ + + size_t p = 0; + + if (m_alog->static_test(log::alevel::devel)) { + std::stringstream s; + s << "p = " << p << " bytes transferred = " << bytes_transferred; + m_alog->write(log::alevel::devel,s.str()); + } + + while (p < bytes_transferred) { + if (m_alog->static_test(log::alevel::devel)) { + std::stringstream s; + s << "calling consume with " << bytes_transferred-p << " bytes"; + m_alog->write(log::alevel::devel,s.str()); + } + + lib::error_code consume_ec; + + if (m_alog->static_test(log::alevel::devel)) { + std::stringstream s; + s << "Processing Bytes: " << utility::to_hex(reinterpret_cast(m_buf)+p,bytes_transferred-p); + m_alog->write(log::alevel::devel,s.str()); + } + + p += m_processor->consume( + reinterpret_cast(m_buf)+p, + bytes_transferred-p, + consume_ec + ); + + if (m_alog->static_test(log::alevel::devel)) { + std::stringstream s; + s << "bytes left after consume: " << bytes_transferred-p; + m_alog->write(log::alevel::devel,s.str()); + } + if (consume_ec) { + log_err(log::elevel::rerror, "consume", consume_ec); + + if (config::drop_on_protocol_error) { + this->terminate(consume_ec); + return; + } else { + lib::error_code close_ec; + this->close( + processor::error::to_ws(consume_ec), + consume_ec.message(), + close_ec + ); + + if (close_ec) { + log_err(log::elevel::fatal, "Protocol error close frame ", close_ec); + this->terminate(close_ec); + return; + } + } + return; + } + + if (m_processor->ready()) { + if (m_alog->static_test(log::alevel::devel)) { + std::stringstream s; + s << "Complete message received. Dispatching"; + m_alog->write(log::alevel::devel,s.str()); + } + + message_ptr msg = m_processor->get_message(); + + if (!msg) { + m_alog->write(log::alevel::devel, "null message from m_processor"); + } else if (!is_control(msg->get_opcode())) { + // data message, dispatch to user + if (m_state != session::state::open) { + m_elog->write(log::elevel::warn, "got non-close frame while closing"); + } else if (m_message_handler) { + m_message_handler(m_connection_hdl, msg); + } + } else { + process_control_frame(msg); + } + } + } + + read_frame(); +} + +/// Issue a new transport read unless reading is paused. +template +void connection::read_frame() { + if (!m_read_flag) { + return; + } + + transport_con_type::async_read_at_least( + // std::min wont work with undefined static const values. + // TODO: is there a more elegant way to do this? + // Need to determine if requesting 1 byte or the exact number of bytes + // is better here. 1 byte lets us be a bit more responsive at a + // potential expense of additional runs through handle_read_frame + /*(m_processor->get_bytes_needed() > config::connection_read_buffer_size ? + config::connection_read_buffer_size : m_processor->get_bytes_needed())*/ + 1, + m_buf, + config::connection_read_buffer_size, + m_handle_read_frame + ); +} + +template +lib::error_code connection::initialize_processor() { + m_alog->write(log::alevel::devel,"initialize_processor"); + + // if it isn't a websocket handshake nothing to do. + if (!processor::is_websocket_handshake(m_request)) { + return lib::error_code(); + } + + int version = processor::get_websocket_version(m_request); + + if (version < 0) { + m_alog->write(log::alevel::devel, "BAD REQUEST: can't determine version"); + m_response.set_status(http::status_code::bad_request); + return error::make_error_code(error::invalid_version); + } + + m_processor = get_processor(version); + + // if the processor is not null we are done + if (m_processor) { + return lib::error_code(); + } + + // We don't have a processor for this version. Return bad request + // with Sec-WebSocket-Version header filled with values we do accept + m_alog->write(log::alevel::devel, "BAD REQUEST: no processor for version"); + m_response.set_status(http::status_code::bad_request); + + std::stringstream ss; + std::string sep; + std::vector::const_iterator it; + for (it = versions_supported.begin(); it != versions_supported.end(); it++) + { + ss << sep << *it; + sep = ","; + } + + m_response.replace_header("Sec-WebSocket-Version",ss.str()); + return error::make_error_code(error::unsupported_version); +} + +template +lib::error_code connection::process_handshake_request() { + m_alog->write(log::alevel::devel,"process handshake request"); + + if (!processor::is_websocket_handshake(m_request)) { + // this is not a websocket handshake. Process as plain HTTP + m_alog->write(log::alevel::devel,"HTTP REQUEST"); + + // extract URI from request + m_uri = processor::get_uri_from_host( + m_request, + (transport_con_type::is_secure() ? "https" : "http") + ); + + if (!m_uri->get_valid()) { + m_alog->write(log::alevel::devel, "Bad request: failed to parse uri"); + m_response.set_status(http::status_code::bad_request); + return error::make_error_code(error::invalid_uri); + } + + if (m_http_handler) { + m_is_http = true; + m_http_handler(m_connection_hdl); + + if (m_state == session::state::closed) { + return error::make_error_code(error::http_connection_ended); + } + } else { + set_status(http::status_code::upgrade_required); + return error::make_error_code(error::upgrade_required); + } + + return lib::error_code(); + } + + lib::error_code ec = m_processor->validate_handshake(m_request); + + // Validate: make sure all required elements are present. + if (ec){ + // Not a valid handshake request + m_alog->write(log::alevel::devel, "Bad request " + ec.message()); + m_response.set_status(http::status_code::bad_request); + return ec; + } + + // Read extension parameters and set up values necessary for the end user + // to complete extension negotiation. + std::pair neg_results; + neg_results = m_processor->negotiate_extensions(m_request); + + if (neg_results.first == processor::error::make_error_code(processor::error::extension_parse_error)) { + // There was a fatal error in extension parsing that should result in + // a failed connection attempt. + m_elog->write(log::elevel::info, "Bad request: " + neg_results.first.message()); + m_response.set_status(http::status_code::bad_request); + return neg_results.first; + } else if (neg_results.first) { + // There was a fatal error in extension processing that is probably our + // fault. Consider extension negotiation to have failed and continue as + // if extensions were not supported + m_elog->write(log::elevel::info, + "Extension negotiation failed: " + neg_results.first.message()); + } else { + // extension negotiation succeeded, set response header accordingly + // we don't send an empty extensions header because it breaks many + // clients. + if (neg_results.second.size() > 0) { + m_response.replace_header("Sec-WebSocket-Extensions", + neg_results.second); + } + } + + // extract URI from request + m_uri = m_processor->get_uri(m_request); + + + if (!m_uri->get_valid()) { + m_alog->write(log::alevel::devel, "Bad request: failed to parse uri"); + m_response.set_status(http::status_code::bad_request); + return error::make_error_code(error::invalid_uri); + } + + // extract subprotocols + lib::error_code subp_ec = m_processor->extract_subprotocols(m_request, + m_requested_subprotocols); + + if (subp_ec) { + // should we do anything? + } + + // Ask application to validate the connection + if (!m_validate_handler || m_validate_handler(m_connection_hdl)) { + m_response.set_status(http::status_code::switching_protocols); + + // Write the appropriate response headers based on request and + // processor version + ec = m_processor->process_handshake(m_request,m_subprotocol,m_response); + + if (ec) { + std::stringstream s; + s << "Processing error: " << ec << "(" << ec.message() << ")"; + m_alog->write(log::alevel::devel, s.str()); + + m_response.set_status(http::status_code::internal_server_error); + return ec; + } + } else { + // User application has rejected the handshake + m_alog->write(log::alevel::devel, "USER REJECT"); + + // Use Bad Request if the user handler did not provide a more + // specific http response error code. + // TODO: is there a better default? + if (m_response.get_status_code() == http::status_code::uninitialized) { + m_response.set_status(http::status_code::bad_request); + } + + return error::make_error_code(error::rejected); + } + + return lib::error_code(); +} + +template +void connection::write_http_response(lib::error_code const & ec) { + m_alog->write(log::alevel::devel,"connection write_http_response"); + + if (ec == error::make_error_code(error::http_connection_ended)) { + m_alog->write(log::alevel::http,"An HTTP handler took over the connection."); + return; + } + + if (m_response.get_status_code() == http::status_code::uninitialized) { + m_response.set_status(http::status_code::internal_server_error); + m_ec = error::make_error_code(error::general); + } else { + m_ec = ec; + } + + m_response.set_version("HTTP/1.1"); + + // Set server header based on the user agent settings + if (m_response.get_header("Server").empty()) { + if (!m_user_agent.empty()) { + m_response.replace_header("Server",m_user_agent); + } else { + m_response.remove_header("Server"); + } + } + + // have the processor generate the raw bytes for the wire (if it exists) + if (m_processor) { + m_handshake_buffer = m_processor->get_raw(m_response); + } else { + // a processor wont exist for raw HTTP responses. + m_handshake_buffer = m_response.raw(); + } + + if (m_alog->static_test(log::alevel::devel)) { + m_alog->write(log::alevel::devel,"Raw Handshake response:\n"+m_handshake_buffer); + if (!m_response.get_header("Sec-WebSocket-Key3").empty()) { + m_alog->write(log::alevel::devel, + utility::to_hex(m_response.get_header("Sec-WebSocket-Key3"))); + } + } + + // write raw bytes + transport_con_type::async_write( + m_handshake_buffer.data(), + m_handshake_buffer.size(), + lib::bind( + &type::handle_write_http_response, + type::get_shared(), + lib::placeholders::_1 + ) + ); +} + +template +void connection::handle_write_http_response(lib::error_code const & ec) { + m_alog->write(log::alevel::devel,"handle_write_http_response"); + + lib::error_code ecm = ec; + + if (!ecm) { + scoped_lock_type lock(m_connection_state_lock); + + if (m_state == session::state::connecting) { + if (m_internal_state != istate::PROCESS_HTTP_REQUEST) { + ecm = error::make_error_code(error::invalid_state); + } + } else if (m_state == session::state::closed) { + // The connection was canceled while the response was being sent, + // usually by the handshake timer. This is basically expected + // (though hopefully rare) and there is nothing we can do so ignore. + m_alog->write(log::alevel::devel, + "handle_write_http_response invoked after connection was closed"); + return; + } else { + ecm = error::make_error_code(error::invalid_state); + } + } + + if (ecm) { + if (ecm == transport::error::eof && m_state == session::state::closed) { + // we expect to get eof if the connection is closed already + m_alog->write(log::alevel::devel, + "got (expected) eof/state error from closed con"); + return; + } + + log_err(log::elevel::rerror,"handle_write_http_response",ecm); + this->terminate(ecm); + return; + } + + if (m_handshake_timer) { + m_handshake_timer->cancel(); + m_handshake_timer.reset(); + } + + if (m_response.get_status_code() != http::status_code::switching_protocols) + { + /*if (m_processor || m_ec == error::http_parse_error || + m_ec == error::invalid_version || m_ec == error::unsupported_version + || m_ec == error::upgrade_required) + {*/ + if (!m_is_http) { + std::stringstream s; + s << "Handshake ended with HTTP error: " + << m_response.get_status_code(); + m_elog->write(log::elevel::rerror,s.str()); + } else { + // if this was not a websocket connection, we have written + // the expected response and the connection can be closed. + + this->log_http_result(); + + if (m_ec) { + m_alog->write(log::alevel::devel, + "got to writing HTTP results with m_ec set: "+m_ec.message()); + } + m_ec = make_error_code(error::http_connection_ended); + } + + this->terminate(m_ec); + return; + } + + this->log_open_result(); + + m_internal_state = istate::PROCESS_CONNECTION; + m_state = session::state::open; + + if (m_open_handler) { + m_open_handler(m_connection_hdl); + } + + this->handle_read_frame(lib::error_code(), m_buf_cursor); +} + +template +void connection::send_http_request() { + m_alog->write(log::alevel::devel,"connection send_http_request"); + + // TODO: origin header? + + // Have the protocol processor fill in the appropriate fields based on the + // selected client version + if (m_processor) { + lib::error_code ec; + ec = m_processor->client_handshake_request(m_request,m_uri, + m_requested_subprotocols); + + if (ec) { + log_err(log::elevel::fatal,"Internal library error: Processor",ec); + return; + } + } else { + m_elog->write(log::elevel::fatal,"Internal library error: missing processor"); + return; + } + + // Unless the user has overridden the user agent, send generic WS++ UA. + if (m_request.get_header("User-Agent").empty()) { + if (!m_user_agent.empty()) { + m_request.replace_header("User-Agent",m_user_agent); + } else { + m_request.remove_header("User-Agent"); + } + } + + m_handshake_buffer = m_request.raw(); + + if (m_alog->static_test(log::alevel::devel)) { + m_alog->write(log::alevel::devel,"Raw Handshake request:\n"+m_handshake_buffer); + } + + if (m_open_handshake_timeout_dur > 0) { + m_handshake_timer = transport_con_type::set_timer( + m_open_handshake_timeout_dur, + lib::bind( + &type::handle_open_handshake_timeout, + type::get_shared(), + lib::placeholders::_1 + ) + ); + } + + transport_con_type::async_write( + m_handshake_buffer.data(), + m_handshake_buffer.size(), + lib::bind( + &type::handle_send_http_request, + type::get_shared(), + lib::placeholders::_1 + ) + ); +} + +template +void connection::handle_send_http_request(lib::error_code const & ec) { + m_alog->write(log::alevel::devel,"handle_send_http_request"); + + lib::error_code ecm = ec; + + if (!ecm) { + scoped_lock_type lock(m_connection_state_lock); + + if (m_state == session::state::connecting) { + if (m_internal_state != istate::WRITE_HTTP_REQUEST) { + ecm = error::make_error_code(error::invalid_state); + } else { + m_internal_state = istate::READ_HTTP_RESPONSE; + } + } else if (m_state == session::state::closed) { + // The connection was canceled while the response was being sent, + // usually by the handshake timer. This is basically expected + // (though hopefully rare) and there is nothing we can do so ignore. + m_alog->write(log::alevel::devel, + "handle_send_http_request invoked after connection was closed"); + return; + } else { + ecm = error::make_error_code(error::invalid_state); + } + } + + if (ecm) { + if (ecm == transport::error::eof && m_state == session::state::closed) { + // we expect to get eof if the connection is closed already + m_alog->write(log::alevel::devel, + "got (expected) eof/state error from closed con"); + return; + } + + log_err(log::elevel::rerror,"handle_send_http_request",ecm); + this->terminate(ecm); + return; + } + + transport_con_type::async_read_at_least( + 1, + m_buf, + config::connection_read_buffer_size, + lib::bind( + &type::handle_read_http_response, + type::get_shared(), + lib::placeholders::_1, + lib::placeholders::_2 + ) + ); +} + +template +void connection::handle_read_http_response(lib::error_code const & ec, + size_t bytes_transferred) +{ + m_alog->write(log::alevel::devel,"handle_read_http_response"); + + lib::error_code ecm = ec; + + if (!ecm) { + scoped_lock_type lock(m_connection_state_lock); + + if (m_state == session::state::connecting) { + if (m_internal_state != istate::READ_HTTP_RESPONSE) { + ecm = error::make_error_code(error::invalid_state); + } + } else if (m_state == session::state::closed) { + // The connection was canceled while the response was being sent, + // usually by the handshake timer. This is basically expected + // (though hopefully rare) and there is nothing we can do so ignore. + m_alog->write(log::alevel::devel, + "handle_read_http_response invoked after connection was closed"); + return; + } else { + ecm = error::make_error_code(error::invalid_state); + } + } + + if (ecm) { + if (ecm == transport::error::eof && m_state == session::state::closed) { + // we expect to get eof if the connection is closed already + m_alog->write(log::alevel::devel, + "got (expected) eof/state error from closed con"); + return; + } + + log_err(log::elevel::rerror,"handle_read_http_response",ecm); + this->terminate(ecm); + return; + } + + size_t bytes_processed = 0; + // TODO: refactor this to use error codes rather than exceptions + try { + bytes_processed = m_response.consume(m_buf,bytes_transferred); + } catch (http::exception & e) { + m_elog->write(log::elevel::rerror, + std::string("error in handle_read_http_response: ")+e.what()); + this->terminate(make_error_code(error::general)); + return; + } + + m_alog->write(log::alevel::devel,std::string("Raw response: ")+m_response.raw()); + + if (m_response.headers_ready()) { + if (m_handshake_timer) { + m_handshake_timer->cancel(); + m_handshake_timer.reset(); + } + + lib::error_code validate_ec = m_processor->validate_server_handshake_response( + m_request, + m_response + ); + if (validate_ec) { + log_err(log::elevel::rerror,"Server handshake response",validate_ec); + this->terminate(validate_ec); + return; + } + + // Read extension parameters and set up values necessary for the end + // user to complete extension negotiation. + std::pair neg_results; + neg_results = m_processor->negotiate_extensions(m_response); + + if (neg_results.first) { + // There was a fatal error in extension negotiation. For the moment + // kill all connections that fail extension negotiation. + + // TODO: deal with cases where the response is well formed but + // doesn't match the options requested by the client. Its possible + // that the best behavior in this cases is to log and continue with + // an unextended connection. + m_alog->write(log::alevel::devel, "Extension negotiation failed: " + + neg_results.first.message()); + this->terminate(make_error_code(error::extension_neg_failed)); + // TODO: close connection with reason 1010 (and list extensions) + } + + // response is valid, connection can now be assumed to be open + m_internal_state = istate::PROCESS_CONNECTION; + m_state = session::state::open; + + this->log_open_result(); + + if (m_open_handler) { + m_open_handler(m_connection_hdl); + } + + // The remaining bytes in m_buf are frame data. Copy them to the + // beginning of the buffer and note the length. They will be read after + // the handshake completes and before more bytes are read. + std::copy(m_buf+bytes_processed,m_buf+bytes_transferred,m_buf); + m_buf_cursor = bytes_transferred-bytes_processed; + + this->handle_read_frame(lib::error_code(), m_buf_cursor); + } else { + transport_con_type::async_read_at_least( + 1, + m_buf, + config::connection_read_buffer_size, + lib::bind( + &type::handle_read_http_response, + type::get_shared(), + lib::placeholders::_1, + lib::placeholders::_2 + ) + ); + } +} + +template +void connection::handle_open_handshake_timeout( + lib::error_code const & ec) +{ + if (ec == transport::error::operation_aborted) { + m_alog->write(log::alevel::devel,"open handshake timer cancelled"); + } else if (ec) { + m_alog->write(log::alevel::devel, + "open handle_open_handshake_timeout error: "+ec.message()); + // TODO: ignore or fail here? + } else { + m_alog->write(log::alevel::devel,"open handshake timer expired"); + terminate(make_error_code(error::open_handshake_timeout)); + } +} + +template +void connection::handle_close_handshake_timeout( + lib::error_code const & ec) +{ + if (ec == transport::error::operation_aborted) { + m_alog->write(log::alevel::devel,"asio close handshake timer cancelled"); + } else if (ec) { + m_alog->write(log::alevel::devel, + "asio open handle_close_handshake_timeout error: "+ec.message()); + // TODO: ignore or fail here? + } else { + m_alog->write(log::alevel::devel, "asio close handshake timer expired"); + terminate(make_error_code(error::close_handshake_timeout)); + } +} + +template +void connection::terminate(lib::error_code const & ec) { + if (m_alog->static_test(log::alevel::devel)) { + m_alog->write(log::alevel::devel,"connection terminate"); + } + + // Cancel close handshake timer + if (m_handshake_timer) { + m_handshake_timer->cancel(); + m_handshake_timer.reset(); + } + + terminate_status tstat = unknown; + if (ec) { + m_ec = ec; + m_local_close_code = close::status::abnormal_close; + m_local_close_reason = ec.message(); + } + + // TODO: does any of this need a mutex? + if (m_is_http) { + m_http_state = session::http_state::closed; + } + if (m_state == session::state::connecting) { + m_state = session::state::closed; + tstat = failed; + + // Log fail result here before socket is shut down and we can't get + // the remote address, etc anymore + if (m_ec != error::http_connection_ended) { + log_fail_result(); + } + } else if (m_state != session::state::closed) { + m_state = session::state::closed; + tstat = closed; + } else { + m_alog->write(log::alevel::devel, + "terminate called on connection that was already terminated"); + return; + } + + // TODO: choose between shutdown and close based on error code sent + + transport_con_type::async_shutdown( + lib::bind( + &type::handle_terminate, + type::get_shared(), + tstat, + lib::placeholders::_1 + ) + ); +} + +template +void connection::handle_terminate(terminate_status tstat, + lib::error_code const & ec) +{ + if (m_alog->static_test(log::alevel::devel)) { + m_alog->write(log::alevel::devel,"connection handle_terminate"); + } + + if (ec) { + // there was an error actually shutting down the connection + log_err(log::elevel::devel,"handle_terminate",ec); + } + + // clean shutdown + if (tstat == failed) { + if (m_ec != error::http_connection_ended) { + if (m_fail_handler) { + m_fail_handler(m_connection_hdl); + } + } + } else if (tstat == closed) { + if (m_close_handler) { + m_close_handler(m_connection_hdl); + } + log_close_result(); + } else { + m_elog->write(log::elevel::rerror,"Unknown terminate_status"); + } + + // call the termination handler if it exists + // if it exists it might (but shouldn't) refer to a bad memory location. + // If it does, we don't care and should catch and ignore it. + if (m_termination_handler) { + try { + m_termination_handler(type::get_shared()); + } catch (std::exception const & e) { + m_elog->write(log::elevel::warn, + std::string("termination_handler call failed. Reason was: ")+e.what()); + } + } +} + +template +void connection::write_frame() { + //m_alog->write(log::alevel::devel,"connection write_frame"); + + { + scoped_lock_type lock(m_write_lock); + + // Check the write flag. If true, there is an outstanding transport + // write already. In this case we just return. The write handler will + // start a new write if the write queue isn't empty. If false, we set + // the write flag and proceed to initiate a transport write. + if (m_write_flag) { + return; + } + + // pull off all the messages that are ready to write. + // stop if we get a message marked terminal + message_ptr next_message = write_pop(); + while (next_message) { + m_current_msgs.push_back(next_message); + if (!next_message->get_terminal()) { + next_message = write_pop(); + } else { + next_message = message_ptr(); + } + } + + if (m_current_msgs.empty()) { + // there was nothing to send + return; + } else { + // At this point we own the next messages to be sent and are + // responsible for holding the write flag until they are + // successfully sent or there is some error + m_write_flag = true; + } + } + + typename std::vector::iterator it; + for (it = m_current_msgs.begin(); it != m_current_msgs.end(); ++it) { + std::string const & header = (*it)->get_header(); + std::string const & payload = (*it)->get_payload(); + + m_send_buffer.push_back(transport::buffer(header.c_str(),header.size())); + m_send_buffer.push_back(transport::buffer(payload.c_str(),payload.size())); + } + + // Print detailed send stats if those log levels are enabled + if (m_alog->static_test(log::alevel::frame_header)) { + if (m_alog->dynamic_test(log::alevel::frame_header)) { + std::stringstream general,header,payload; + + general << "Dispatching write containing " << m_current_msgs.size() + <<" message(s) containing "; + header << "Header Bytes: \n"; + payload << "Payload Bytes: \n"; + + size_t hbytes = 0; + size_t pbytes = 0; + + for (size_t i = 0; i < m_current_msgs.size(); i++) { + hbytes += m_current_msgs[i]->get_header().size(); + pbytes += m_current_msgs[i]->get_payload().size(); + + + header << "[" << i << "] (" + << m_current_msgs[i]->get_header().size() << ") " + << utility::to_hex(m_current_msgs[i]->get_header()) << "\n"; + + if (m_alog->static_test(log::alevel::frame_payload)) { + if (m_alog->dynamic_test(log::alevel::frame_payload)) { + payload << "[" << i << "] (" + << m_current_msgs[i]->get_payload().size() << ") ["<get_opcode()<<"] " + << (m_current_msgs[i]->get_opcode() == frame::opcode::text ? + m_current_msgs[i]->get_payload() : + utility::to_hex(m_current_msgs[i]->get_payload()) + ) + << "\n"; + } + } + } + + general << hbytes << " header bytes and " << pbytes << " payload bytes"; + + m_alog->write(log::alevel::frame_header,general.str()); + m_alog->write(log::alevel::frame_header,header.str()); + m_alog->write(log::alevel::frame_payload,payload.str()); + } + } + + transport_con_type::async_write( + m_send_buffer, + m_write_frame_handler + ); +} + +template +void connection::handle_write_frame(lib::error_code const & ec) +{ + if (m_alog->static_test(log::alevel::devel)) { + m_alog->write(log::alevel::devel,"connection handle_write_frame"); + } + + bool terminal = m_current_msgs.back()->get_terminal(); + + m_send_buffer.clear(); + m_current_msgs.clear(); + // TODO: recycle instead of deleting + + if (ec) { + log_err(log::elevel::fatal,"handle_write_frame",ec); + this->terminate(ec); + return; + } + + if (terminal) { + this->terminate(lib::error_code()); + return; + } + + bool needs_writing = false; + { + scoped_lock_type lock(m_write_lock); + + // release write flag + m_write_flag = false; + + needs_writing = !m_send_queue.empty(); + } + + if (needs_writing) { + transport_con_type::dispatch(lib::bind( + &type::write_frame, + type::get_shared() + )); + } +} + +template +std::vector const & connection::get_supported_versions() const +{ + return versions_supported; +} + +template +void connection::process_control_frame(typename config::message_type::ptr msg) +{ + m_alog->write(log::alevel::devel,"process_control_frame"); + + frame::opcode::value op = msg->get_opcode(); + lib::error_code ec; + + std::stringstream s; + s << "Control frame received with opcode " << op; + m_alog->write(log::alevel::control,s.str()); + + if (m_state == session::state::closed) { + m_elog->write(log::elevel::warn,"got frame in state closed"); + return; + } + if (op != frame::opcode::CLOSE && m_state != session::state::open) { + m_elog->write(log::elevel::warn,"got non-close frame in state closing"); + return; + } + + if (op == frame::opcode::PING) { + bool should_reply = true; + + if (m_ping_handler) { + should_reply = m_ping_handler(m_connection_hdl, msg->get_payload()); + } + + if (should_reply) { + this->pong(msg->get_payload(),ec); + if (ec) { + log_err(log::elevel::devel,"Failed to send response pong",ec); + } + } + } else if (op == frame::opcode::PONG) { + if (m_pong_handler) { + m_pong_handler(m_connection_hdl, msg->get_payload()); + } + if (m_ping_timer) { + m_ping_timer->cancel(); + } + } else if (op == frame::opcode::CLOSE) { + m_alog->write(log::alevel::devel,"got close frame"); + // record close code and reason somewhere + + m_remote_close_code = close::extract_code(msg->get_payload(),ec); + if (ec) { + s.str(""); + if (config::drop_on_protocol_error) { + s << "Received invalid close code " << m_remote_close_code + << " dropping connection per config."; + m_elog->write(log::elevel::devel,s.str()); + this->terminate(ec); + } else { + s << "Received invalid close code " << m_remote_close_code + << " sending acknowledgement and closing"; + m_elog->write(log::elevel::devel,s.str()); + ec = send_close_ack(close::status::protocol_error, + "Invalid close code"); + if (ec) { + log_err(log::elevel::devel,"send_close_ack",ec); + } + } + return; + } + + m_remote_close_reason = close::extract_reason(msg->get_payload(),ec); + if (ec) { + if (config::drop_on_protocol_error) { + m_elog->write(log::elevel::devel, + "Received invalid close reason. Dropping connection per config"); + this->terminate(ec); + } else { + m_elog->write(log::elevel::devel, + "Received invalid close reason. Sending acknowledgement and closing"); + ec = send_close_ack(close::status::protocol_error, + "Invalid close reason"); + if (ec) { + log_err(log::elevel::devel,"send_close_ack",ec); + } + } + return; + } + + if (m_state == session::state::open) { + s.str(""); + s << "Received close frame with code " << m_remote_close_code + << " and reason " << m_remote_close_reason; + m_alog->write(log::alevel::devel,s.str()); + + ec = send_close_ack(); + if (ec) { + log_err(log::elevel::devel,"send_close_ack",ec); + } + } else if (m_state == session::state::closing && !m_was_clean) { + // ack of our close + m_alog->write(log::alevel::devel, "Got acknowledgement of close"); + + m_was_clean = true; + + // If we are a server terminate the connection now. Clients should + // leave the connection open to give the server an opportunity to + // initiate the TCP close. The client's timer will handle closing + // its side of the connection if the server misbehaves. + // + // TODO: different behavior if the underlying transport doesn't + // support timers? + if (m_is_server) { + terminate(lib::error_code()); + } + } else { + // spurious, ignore + m_elog->write(log::elevel::devel, "Got close frame in wrong state"); + } + } else { + // got an invalid control opcode + m_elog->write(log::elevel::devel, "Got control frame with invalid opcode"); + // initiate protocol error shutdown + } +} + +template +lib::error_code connection::send_close_ack(close::status::value code, + std::string const & reason) +{ + return send_close_frame(code,reason,true,m_is_server); +} + +template +lib::error_code connection::send_close_frame(close::status::value code, + std::string const & reason, bool ack, bool terminal) +{ + m_alog->write(log::alevel::devel,"send_close_frame"); + + // check for special codes + + // If silent close is set, respect it and blank out close information + // Otherwise use whatever has been specified in the parameters. If + // parameters specifies close::status::blank then determine what to do + // based on whether or not this is an ack. If it is not an ack just + // send blank info. If it is an ack then echo the close information from + // the remote endpoint. + if (config::silent_close) { + m_alog->write(log::alevel::devel,"closing silently"); + m_local_close_code = close::status::no_status; + m_local_close_reason.clear(); + } else if (code != close::status::blank) { + m_alog->write(log::alevel::devel,"closing with specified codes"); + m_local_close_code = code; + m_local_close_reason = reason; + } else if (!ack) { + m_alog->write(log::alevel::devel,"closing with no status code"); + m_local_close_code = close::status::no_status; + m_local_close_reason.clear(); + } else if (m_remote_close_code == close::status::no_status) { + m_alog->write(log::alevel::devel, + "acknowledging a no-status close with normal code"); + m_local_close_code = close::status::normal; + m_local_close_reason.clear(); + } else { + m_alog->write(log::alevel::devel,"acknowledging with remote codes"); + m_local_close_code = m_remote_close_code; + m_local_close_reason = m_remote_close_reason; + } + + std::stringstream s; + s << "Closing with code: " << m_local_close_code << ", and reason: " + << m_local_close_reason; + m_alog->write(log::alevel::devel,s.str()); + + message_ptr msg = m_msg_manager->get_message(); + if (!msg) { + return error::make_error_code(error::no_outgoing_buffers); + } + + lib::error_code ec = m_processor->prepare_close(m_local_close_code, + m_local_close_reason,msg); + if (ec) { + return ec; + } + + // Messages flagged terminal will result in the TCP connection being dropped + // after the message has been written. This is typically used when servers + // send an ack and when any endpoint encounters a protocol error + if (terminal) { + msg->set_terminal(true); + } + + m_state = session::state::closing; + + if (ack) { + m_was_clean = true; + } + + // Start a timer so we don't wait forever for the acknowledgement close + // frame + if (m_close_handshake_timeout_dur > 0) { + m_handshake_timer = transport_con_type::set_timer( + m_close_handshake_timeout_dur, + lib::bind( + &type::handle_close_handshake_timeout, + type::get_shared(), + lib::placeholders::_1 + ) + ); + } + + bool needs_writing = false; + { + scoped_lock_type lock(m_write_lock); + write_push(msg); + needs_writing = !m_write_flag && !m_send_queue.empty(); + } + + if (needs_writing) { + transport_con_type::dispatch(lib::bind( + &type::write_frame, + type::get_shared() + )); + } + + return lib::error_code(); +} + +template +typename connection::processor_ptr +connection::get_processor(int version) const { + // TODO: allow disabling certain versions + + processor_ptr p; + + switch (version) { + case 0: + p = lib::make_shared >( + transport_con_type::is_secure(), + m_is_server, + m_msg_manager + ); + break; + case 7: + p = lib::make_shared >( + transport_con_type::is_secure(), + m_is_server, + m_msg_manager, + lib::ref(m_rng) + ); + break; + case 8: + p = lib::make_shared >( + transport_con_type::is_secure(), + m_is_server, + m_msg_manager, + lib::ref(m_rng) + ); + break; + case 13: + p = lib::make_shared >( + transport_con_type::is_secure(), + m_is_server, + m_msg_manager, + lib::ref(m_rng) + ); + break; + default: + return p; + } + + // Settings not configured by the constructor + p->set_max_message_size(m_max_message_size); + + return p; +} + +template +void connection::write_push(typename config::message_type::ptr msg) +{ + if (!msg) { + return; + } + + m_send_buffer_size += msg->get_payload().size(); + m_send_queue.push(msg); + + if (m_alog->static_test(log::alevel::devel)) { + std::stringstream s; + s << "write_push: message count: " << m_send_queue.size() + << " buffer size: " << m_send_buffer_size; + m_alog->write(log::alevel::devel,s.str()); + } +} + +template +typename config::message_type::ptr connection::write_pop() +{ + message_ptr msg; + + if (m_send_queue.empty()) { + return msg; + } + + msg = m_send_queue.front(); + + m_send_buffer_size -= msg->get_payload().size(); + m_send_queue.pop(); + + if (m_alog->static_test(log::alevel::devel)) { + std::stringstream s; + s << "write_pop: message count: " << m_send_queue.size() + << " buffer size: " << m_send_buffer_size; + m_alog->write(log::alevel::devel,s.str()); + } + return msg; +} + +template +void connection::log_open_result() +{ + std::stringstream s; + + int version; + if (!processor::is_websocket_handshake(m_request)) { + version = -1; + } else { + version = processor::get_websocket_version(m_request); + } + + // Connection Type + s << (version == -1 ? "HTTP" : "WebSocket") << " Connection "; + + // Remote endpoint address + s << transport_con_type::get_remote_endpoint() << " "; + + // Version string if WebSocket + if (version != -1) { + s << "v" << version << " "; + } + + // User Agent + std::string ua = m_request.get_header("User-Agent"); + if (ua.empty()) { + s << "\"\" "; + } else { + // check if there are any quotes in the user agent + s << "\"" << utility::string_replace_all(ua,"\"","\\\"") << "\" "; + } + + // URI + s << (m_uri ? m_uri->get_resource() : "NULL") << " "; + + // Status code + s << m_response.get_status_code(); + + m_alog->write(log::alevel::connect,s.str()); +} + +template +void connection::log_close_result() +{ + std::stringstream s; + + s << "Disconnect " + << "close local:[" << m_local_close_code + << (m_local_close_reason.empty() ? "" : ","+m_local_close_reason) + << "] remote:[" << m_remote_close_code + << (m_remote_close_reason.empty() ? "" : ","+m_remote_close_reason) << "]"; + + m_alog->write(log::alevel::disconnect,s.str()); +} + +template +void connection::log_fail_result() +{ + std::stringstream s; + + int version = processor::get_websocket_version(m_request); + + // Connection Type + s << "WebSocket Connection "; + + // Remote endpoint address & WebSocket version + s << transport_con_type::get_remote_endpoint(); + if (version < 0) { + s << " -"; + } else { + s << " v" << version; + } + + // User Agent + std::string ua = m_request.get_header("User-Agent"); + if (ua.empty()) { + s << " \"\" "; + } else { + // check if there are any quotes in the user agent + s << " \"" << utility::string_replace_all(ua,"\"","\\\"") << "\" "; + } + + // URI + s << (m_uri ? m_uri->get_resource() : "-"); + + // HTTP Status code + s << " " << m_response.get_status_code(); + + // WebSocket++ error code & reason + s << " " << m_ec << " " << m_ec.message(); + + m_alog->write(log::alevel::fail,s.str()); +} + +template +void connection::log_http_result() { + std::stringstream s; + + if (processor::is_websocket_handshake(m_request)) { + m_alog->write(log::alevel::devel,"Call to log_http_result for WebSocket"); + return; + } + + // Connection Type + s << (m_request.get_header("host").empty() ? "-" : m_request.get_header("host")) + << " " << transport_con_type::get_remote_endpoint() + << " \"" << m_request.get_method() + << " " << (m_uri ? m_uri->get_resource() : "-") + << " " << m_request.get_version() << "\" " << m_response.get_status_code() + << " " << m_response.get_body().size(); + + // User Agent + std::string ua = m_request.get_header("User-Agent"); + if (ua.empty()) { + s << " \"\" "; + } else { + // check if there are any quotes in the user agent + s << " \"" << utility::string_replace_all(ua,"\"","\\\"") << "\" "; + } + + m_alog->write(log::alevel::http,s.str()); +} + +} // namespace websocketpp + +#endif // WEBSOCKETPP_CONNECTION_IMPL_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/impl/endpoint_impl.hpp b/thirdparty/websocketpp/include/websocketpp/impl/endpoint_impl.hpp new file mode 100644 index 0000000..3b2136d --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/impl/endpoint_impl.hpp @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2014, 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_ENDPOINT_IMPL_HPP +#define WEBSOCKETPP_ENDPOINT_IMPL_HPP + +#include + +namespace websocketpp { + +template +typename endpoint::connection_ptr +endpoint::create_connection() { + m_alog->write(log::alevel::devel,"create_connection"); + //scoped_lock_type lock(m_state_lock); + + /*if (m_state == STOPPING || m_state == STOPPED) { + return connection_ptr(); + }*/ + + //scoped_lock_type guard(m_mutex); + // Create a connection on the heap and manage it using a shared pointer + connection_ptr con = lib::make_shared(m_is_server, + m_user_agent, m_alog, m_elog, lib::ref(m_rng)); + + connection_weak_ptr w(con); + + // Create a weak pointer on the heap using that shared_ptr. + // Cast that weak pointer to void* and manage it using another shared_ptr + // connection_hdl hdl(reinterpret_cast(new connection_weak_ptr(con))); + + con->set_handle(w); + + // Copy default handlers from the endpoint + con->set_open_handler(m_open_handler); + con->set_close_handler(m_close_handler); + con->set_fail_handler(m_fail_handler); + con->set_ping_handler(m_ping_handler); + con->set_pong_handler(m_pong_handler); + con->set_pong_timeout_handler(m_pong_timeout_handler); + con->set_interrupt_handler(m_interrupt_handler); + con->set_http_handler(m_http_handler); + con->set_validate_handler(m_validate_handler); + con->set_message_handler(m_message_handler); + + if (m_open_handshake_timeout_dur != config::timeout_open_handshake) { + con->set_open_handshake_timeout(m_open_handshake_timeout_dur); + } + if (m_close_handshake_timeout_dur != config::timeout_close_handshake) { + con->set_close_handshake_timeout(m_close_handshake_timeout_dur); + } + if (m_pong_timeout_dur != config::timeout_pong) { + con->set_pong_timeout(m_pong_timeout_dur); + } + if (m_max_message_size != config::max_message_size) { + con->set_max_message_size(m_max_message_size); + } + con->set_max_http_body_size(m_max_http_body_size); + + lib::error_code ec; + + ec = transport_type::init(con); + if (ec) { + m_elog->write(log::elevel::fatal,ec.message()); + return connection_ptr(); + } + + return con; +} + +template +void endpoint::interrupt(connection_hdl hdl, lib::error_code & ec) +{ + connection_ptr con = get_con_from_hdl(hdl,ec); + if (ec) {return;} + + m_alog->write(log::alevel::devel,"Interrupting connection"); + + ec = con->interrupt(); +} + +template +void endpoint::interrupt(connection_hdl hdl) { + lib::error_code ec; + interrupt(hdl,ec); + if (ec) { throw exception(ec); } +} + +template +void endpoint::pause_reading(connection_hdl hdl, lib::error_code & ec) +{ + connection_ptr con = get_con_from_hdl(hdl,ec); + if (ec) {return;} + + ec = con->pause_reading(); +} + +template +void endpoint::pause_reading(connection_hdl hdl) { + lib::error_code ec; + pause_reading(hdl,ec); + if (ec) { throw exception(ec); } +} + +template +void endpoint::resume_reading(connection_hdl hdl, lib::error_code & ec) +{ + connection_ptr con = get_con_from_hdl(hdl,ec); + if (ec) {return;} + + ec = con->resume_reading(); +} + +template +void endpoint::resume_reading(connection_hdl hdl) { + lib::error_code ec; + resume_reading(hdl,ec); + if (ec) { throw exception(ec); } +} + +template +void endpoint::send_http_response(connection_hdl hdl, + lib::error_code & ec) +{ + connection_ptr con = get_con_from_hdl(hdl,ec); + if (ec) {return;} + con->send_http_response(ec); +} + +template +void endpoint::send_http_response(connection_hdl hdl) { + lib::error_code ec; + send_http_response(hdl,ec); + if (ec) { throw exception(ec); } +} + +template +void endpoint::send(connection_hdl hdl, std::string const & payload, + frame::opcode::value op, lib::error_code & ec) +{ + connection_ptr con = get_con_from_hdl(hdl,ec); + if (ec) {return;} + + ec = con->send(payload,op); +} + +template +void endpoint::send(connection_hdl hdl, std::string const & payload, + frame::opcode::value op) +{ + lib::error_code ec; + send(hdl,payload,op,ec); + if (ec) { throw exception(ec); } +} + +template +void endpoint::send(connection_hdl hdl, void const * payload, + size_t len, frame::opcode::value op, lib::error_code & ec) +{ + connection_ptr con = get_con_from_hdl(hdl,ec); + if (ec) {return;} + ec = con->send(payload,len,op); +} + +template +void endpoint::send(connection_hdl hdl, void const * payload, + size_t len, frame::opcode::value op) +{ + lib::error_code ec; + send(hdl,payload,len,op,ec); + if (ec) { throw exception(ec); } +} + +template +void endpoint::send(connection_hdl hdl, message_ptr msg, + lib::error_code & ec) +{ + connection_ptr con = get_con_from_hdl(hdl,ec); + if (ec) {return;} + ec = con->send(msg); +} + +template +void endpoint::send(connection_hdl hdl, message_ptr msg) { + lib::error_code ec; + send(hdl,msg,ec); + if (ec) { throw exception(ec); } +} + +template +void endpoint::close(connection_hdl hdl, close::status::value + const code, std::string const & reason, + lib::error_code & ec) +{ + connection_ptr con = get_con_from_hdl(hdl,ec); + if (ec) {return;} + con->close(code,reason,ec); +} + +template +void endpoint::close(connection_hdl hdl, close::status::value + const code, std::string const & reason) +{ + lib::error_code ec; + close(hdl,code,reason,ec); + if (ec) { throw exception(ec); } +} + +template +void endpoint::ping(connection_hdl hdl, std::string const & + payload, lib::error_code & ec) +{ + connection_ptr con = get_con_from_hdl(hdl,ec); + if (ec) {return;} + con->ping(payload,ec); +} + +template +void endpoint::ping(connection_hdl hdl, std::string const & payload) +{ + lib::error_code ec; + ping(hdl,payload,ec); + if (ec) { throw exception(ec); } +} + +template +void endpoint::pong(connection_hdl hdl, std::string const & payload, + lib::error_code & ec) +{ + connection_ptr con = get_con_from_hdl(hdl,ec); + if (ec) {return;} + con->pong(payload,ec); +} + +template +void endpoint::pong(connection_hdl hdl, std::string const & payload) +{ + lib::error_code ec; + pong(hdl,payload,ec); + if (ec) { throw exception(ec); } +} + +} // namespace websocketpp + +#endif // WEBSOCKETPP_ENDPOINT_IMPL_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/impl/utilities_impl.hpp b/thirdparty/websocketpp/include/websocketpp/impl/utilities_impl.hpp new file mode 100644 index 0000000..4da3fb7 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/impl/utilities_impl.hpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2014, 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_UTILITIES_IMPL_HPP +#define WEBSOCKETPP_UTILITIES_IMPL_HPP + +#include +#include + +namespace websocketpp { +namespace utility { + +inline std::string to_lower(std::string const & in) { + std::string out = in; + std::transform(out.begin(),out.end(),out.begin(),::tolower); + return out; +} + +inline std::string to_hex(std::string const & input) { + std::string output; + std::string hex = "0123456789ABCDEF"; + + for (size_t i = 0; i < input.size(); i++) { + output += hex[(input[i] & 0xF0) >> 4]; + output += hex[input[i] & 0x0F]; + output += " "; + } + + return output; +} + +inline std::string to_hex(uint8_t const * input, size_t length) { + std::string output; + std::string hex = "0123456789ABCDEF"; + + for (size_t i = 0; i < length; i++) { + output += hex[(input[i] & 0xF0) >> 4]; + output += hex[input[i] & 0x0F]; + output += " "; + } + + return output; +} + +inline std::string to_hex(const char* input,size_t length) { + return to_hex(reinterpret_cast(input),length); +} + +inline std::string string_replace_all(std::string subject, std::string const & + search, std::string const & replace) +{ + size_t pos = 0; + while((pos = subject.find(search, pos)) != std::string::npos) { + subject.replace(pos, search.length(), replace); + pos += replace.length(); + } + return subject; +} + +} // namespace utility +} // namespace websocketpp + +#endif // WEBSOCKETPP_UTILITIES_IMPL_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/logger/basic.hpp b/thirdparty/websocketpp/include/websocketpp/logger/basic.hpp new file mode 100644 index 0000000..a61e4b9 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/logger/basic.hpp @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2014, 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_LOGGER_BASIC_HPP +#define WEBSOCKETPP_LOGGER_BASIC_HPP + +/* Need a way to print a message to the log + * + * - timestamps + * - channels + * - thread safe + * - output to stdout or file + * - selective output channels, both compile time and runtime + * - named channels + * - ability to test whether a log message will be printed at compile time + * + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace websocketpp { +namespace log { + +/// Basic logger that outputs to an ostream +template +class basic { +public: + basic(channel_type_hint::value h = + channel_type_hint::access) + : m_static_channels(0xffffffff) + , m_dynamic_channels(0) + , m_out(h == channel_type_hint::error ? &std::cerr : &std::cout) {} + + basic(std::ostream * out) + : m_static_channels(0xffffffff) + , m_dynamic_channels(0) + , m_out(out) {} + + basic(level c, channel_type_hint::value h = + channel_type_hint::access) + : m_static_channels(c) + , m_dynamic_channels(0) + , m_out(h == channel_type_hint::error ? &std::cerr : &std::cout) {} + + basic(level c, std::ostream * out) + : m_static_channels(c) + , m_dynamic_channels(0) + , m_out(out) {} + + /// Destructor + ~basic() {} + + /// Copy constructor + basic(basic const & other) + : m_static_channels(other.m_static_channels) + , m_dynamic_channels(other.m_dynamic_channels) + , m_out(other.m_out) + {} + +#ifdef _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_ + // no copy assignment operator because of const member variables + basic & operator=(basic const &) = delete; +#endif // _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_ + +#ifdef _WEBSOCKETPP_MOVE_SEMANTICS_ + /// Move constructor + basic(basic && other) + : m_static_channels(other.m_static_channels) + , m_dynamic_channels(other.m_dynamic_channels) + , m_out(other.m_out) + {} + +#ifdef _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_ + // no move assignment operator because of const member variables + basic & operator=(basic &&) = delete; +#endif // _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_ + +#endif // _WEBSOCKETPP_MOVE_SEMANTICS_ + + void set_ostream(std::ostream * out = &std::cout) { + m_out = out; + } + + void set_channels(level channels) { + if (channels == names::none) { + clear_channels(names::all); + return; + } + + scoped_lock_type lock(m_lock); + m_dynamic_channels |= (channels & m_static_channels); + } + + void clear_channels(level channels) { + scoped_lock_type lock(m_lock); + m_dynamic_channels &= ~channels; + } + + /// Write a string message to the given channel + /** + * @param channel The channel to write to + * @param msg The message to write + */ + void write(level channel, std::string const & msg) { + scoped_lock_type lock(m_lock); + if (!this->dynamic_test(channel)) { return; } + *m_out << "[" << timestamp << "] " + << "[" << names::channel_name(channel) << "] " + << msg << "\n"; + m_out->flush(); + } + + /// Write a cstring message to the given channel + /** + * @param channel The channel to write to + * @param msg The message to write + */ + void write(level channel, char const * msg) { + scoped_lock_type lock(m_lock); + if (!this->dynamic_test(channel)) { return; } + *m_out << "[" << timestamp << "] " + << "[" << names::channel_name(channel) << "] " + << msg << "\n"; + m_out->flush(); + } + + _WEBSOCKETPP_CONSTEXPR_TOKEN_ bool static_test(level channel) const { + return ((channel & m_static_channels) != 0); + } + + bool dynamic_test(level channel) { + return ((channel & m_dynamic_channels) != 0); + } + +protected: + typedef typename concurrency::scoped_lock_type scoped_lock_type; + typedef typename concurrency::mutex_type mutex_type; + mutex_type m_lock; + +private: + // The timestamp does not include the time zone, because on Windows with the + // default registry settings, the time zone would be written out in full, + // which would be obnoxiously verbose. + // + // TODO: find a workaround for this or make this format user settable + static std::ostream & timestamp(std::ostream & os) { + std::time_t t = std::time(NULL); + std::tm lt = lib::localtime(t); + #ifdef _WEBSOCKETPP_PUTTIME_ + return os << std::put_time(<,"%Y-%m-%d %H:%M:%S"); + #else // Falls back to strftime, which requires a temporary copy of the string. + char buffer[20]; + size_t result = std::strftime(buffer,sizeof(buffer),"%Y-%m-%d %H:%M:%S",<); + return os << (result == 0 ? "Unknown" : buffer); + #endif + } + + level const m_static_channels; + level m_dynamic_channels; + std::ostream * m_out; +}; + +} // log +} // websocketpp + +#endif // WEBSOCKETPP_LOGGER_BASIC_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/logger/levels.hpp b/thirdparty/websocketpp/include/websocketpp/logger/levels.hpp new file mode 100644 index 0000000..3970861 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/logger/levels.hpp @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2014, 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_LOGGER_LEVELS_HPP +#define WEBSOCKETPP_LOGGER_LEVELS_HPP + +#include + +namespace websocketpp { +namespace log { + +/// Type of a channel package +typedef uint32_t level; + +/// Package of values for hinting at the nature of a given logger. +/** + * Used by the library to signal to the logging class a hint that it can use to + * set itself up. For example, the `access` hint indicates that it is an access + * log that might be suitable for being printed to an access log file or to cout + * whereas `error` might be suitable for an error log file or cerr. + */ +struct channel_type_hint { + /// Type of a channel type hint value + typedef uint32_t value; + + /// No information + static value const none = 0; + /// Access log + static value const access = 1; + /// Error log + static value const error = 2; +}; + +/// Package of log levels for logging errors +struct elevel { + /// Special aggregate value representing "no levels" + static level const none = 0x0; + /// Low level debugging information (warning: very chatty) + static level const devel = 0x1; + /// Information about unusual system states or other minor internal library + /// problems, less chatty than devel. + static level const library = 0x2; + /// Information about minor configuration problems or additional information + /// about other warnings. + static level const info = 0x4; + /// Information about important problems not severe enough to terminate + /// connections. + static level const warn = 0x8; + /// Recoverable error. Recovery may mean cleanly closing the connection with + /// an appropriate error code to the remote endpoint. + static level const rerror = 0x10; + /// Unrecoverable error. This error will trigger immediate unclean + /// termination of the connection or endpoint. + static level const fatal = 0x20; + /// Special aggregate value representing "all levels" + static level const all = 0xffffffff; + + /// Get the textual name of a channel given a channel id + /** + * The id must be that of a single channel. Passing an aggregate channel + * package results in undefined behavior. + * + * @param channel The channel id to look up. + * + * @return The name of the specified channel. + */ + static char const * channel_name(level channel) { + switch(channel) { + case devel: + return "devel"; + case library: + return "library"; + case info: + return "info"; + case warn: + return "warning"; + case rerror: + return "error"; + case fatal: + return "fatal"; + default: + return "unknown"; + } + } +}; + +/// Package of log levels for logging access events +struct alevel { + /// Special aggregate value representing "no levels" + static level const none = 0x0; + /// Information about new connections + /** + * One line for each new connection that includes a host of information + * including: the remote address, websocket version, requested resource, + * http code, remote user agent + */ + static level const connect = 0x1; + /// One line for each closed connection. Includes closing codes and reasons. + static level const disconnect = 0x2; + /// One line per control frame + static level const control = 0x4; + /// One line per frame, includes the full frame header + static level const frame_header = 0x8; + /// One line per frame, includes the full message payload (warning: chatty) + static level const frame_payload = 0x10; + /// Reserved + static level const message_header = 0x20; + /// Reserved + static level const message_payload = 0x40; + /// Reserved + static level const endpoint = 0x80; + /// Extra information about opening handshakes + static level const debug_handshake = 0x100; + /// Extra information about closing handshakes + static level const debug_close = 0x200; + /// Development messages (warning: very chatty) + static level const devel = 0x400; + /// Special channel for application specific logs. Not used by the library. + static level const app = 0x800; + /// Access related to HTTP requests + static level const http = 0x1000; + /// One line for each failed WebSocket connection with details + static level const fail = 0x2000; + /// Aggregate package representing the commonly used core access channels + /// Connect, Disconnect, Fail, and HTTP + static level const access_core = 0x00003003; + /// Special aggregate value representing "all levels" + static level const all = 0xffffffff; + + /// Get the textual name of a channel given a channel id + /** + * Get the textual name of a channel given a channel id. The id must be that + * of a single channel. Passing an aggregate channel package results in + * undefined behavior. + * + * @param channel The channelid to look up. + * + * @return The name of the specified channel. + */ + static char const * channel_name(level channel) { + switch(channel) { + case connect: + return "connect"; + case disconnect: + return "disconnect"; + case control: + return "control"; + case frame_header: + return "frame_header"; + case frame_payload: + return "frame_payload"; + case message_header: + return "message_header"; + case message_payload: + return "message_payload"; + case endpoint: + return "endpoint"; + case debug_handshake: + return "debug_handshake"; + case debug_close: + return "debug_close"; + case devel: + return "devel"; + case app: + return "application"; + case http: + return "http"; + case fail: + return "fail"; + default: + return "unknown"; + } + } +}; + +} // logger +} // websocketpp + +#endif //WEBSOCKETPP_LOGGER_LEVELS_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/logger/stub.hpp b/thirdparty/websocketpp/include/websocketpp/logger/stub.hpp new file mode 100644 index 0000000..cc4da33 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/logger/stub.hpp @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2014, 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_LOGGER_STUB_HPP +#define WEBSOCKETPP_LOGGER_STUB_HPP + +#include + +#include + +#include + +namespace websocketpp { +namespace log { + +/// Stub logger that ignores all input +class stub { +public: + /// Construct the logger + /** + * @param hint A channel type specific hint for how to construct the logger + */ + explicit stub(channel_type_hint::value) {} + + /// Construct the logger + /** + * @param default_channels A set of channels to statically enable + * @param hint A channel type specific hint for how to construct the logger + */ + stub(level, channel_type_hint::value) {} + _WEBSOCKETPP_CONSTEXPR_TOKEN_ stub() {} + + /// Dynamically enable the given list of channels + /** + * All operations on the stub logger are no-ops and all arguments are + * ignored + * + * @param channels The package of channels to enable + */ + void set_channels(level) {} + + /// Dynamically disable the given list of channels + /** + * All operations on the stub logger are no-ops and all arguments are + * ignored + * + * @param channels The package of channels to disable + */ + void clear_channels(level) {} + + /// Write a string message to the given channel + /** + * Writing on the stub logger is a no-op and all arguments are ignored + * + * @param channel The channel to write to + * @param msg The message to write + */ + void write(level, std::string const &) {} + + /// Write a cstring message to the given channel + /** + * Writing on the stub logger is a no-op and all arguments are ignored + * + * @param channel The channel to write to + * @param msg The message to write + */ + void write(level, char const *) {} + + /// Test whether a channel is statically enabled + /** + * The stub logger has no channels so all arguments are ignored and + * `static_test` always returns false. + * + * @param channel The package of channels to test + */ + _WEBSOCKETPP_CONSTEXPR_TOKEN_ bool static_test(level) const { + return false; + } + + /// Test whether a channel is dynamically enabled + /** + * The stub logger has no channels so all arguments are ignored and + * `dynamic_test` always returns false. + * + * @param channel The package of channels to test + */ + bool dynamic_test(level) { + return false; + } +}; + +} // log +} // websocketpp + +#endif // WEBSOCKETPP_LOGGER_STUB_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/logger/syslog.hpp b/thirdparty/websocketpp/include/websocketpp/logger/syslog.hpp new file mode 100644 index 0000000..7cb82ae --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/logger/syslog.hpp @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2014, 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. + * + * + * The initial version of this logging policy was contributed to the WebSocket++ + * project by Tom Hughes. + */ + +#ifndef WEBSOCKETPP_LOGGER_SYSLOG_HPP +#define WEBSOCKETPP_LOGGER_SYSLOG_HPP + +#include + +#include + +#include +#include + +namespace websocketpp { +namespace log { + +/// Basic logger that outputs to syslog +template +class syslog : public basic { +public: + typedef basic base; + + /// Construct the logger + /** + * @param hint A channel type specific hint for how to construct the logger + */ + syslog(channel_type_hint::value hint = + channel_type_hint::access) + : basic(hint), m_channel_type_hint(hint) {} + + /// Construct the logger + /** + * @param channels A set of channels to statically enable + * @param hint A channel type specific hint for how to construct the logger + */ + syslog(level channels, channel_type_hint::value hint = + channel_type_hint::access) + : basic(channels, hint), m_channel_type_hint(hint) {} + + /// Write a string message to the given channel + /** + * @param channel The channel to write to + * @param msg The message to write + */ + void write(level channel, std::string const & msg) { + write(channel, msg.c_str()); + } + + /// Write a cstring message to the given channel + /** + * @param channel The channel to write to + * @param msg The message to write + */ + void write(level channel, char const * msg) { + scoped_lock_type lock(base::m_lock); + if (!this->dynamic_test(channel)) { return; } + ::syslog(syslog_priority(channel), "[%s] %s", + names::channel_name(channel), msg); + } +private: + typedef typename base::scoped_lock_type scoped_lock_type; + + /// The default level is used for all access logs and any error logs that + /// don't trivially map to one of the standard syslog levels. + static int const default_level = LOG_INFO; + + /// retrieve the syslog priority code given a WebSocket++ channel + /** + * @param channel The level to look up + * @return The syslog level associated with `channel` + */ + int syslog_priority(level channel) const { + if (m_channel_type_hint == channel_type_hint::access) { + return syslog_priority_access(channel); + } else { + return syslog_priority_error(channel); + } + } + + /// retrieve the syslog priority code given a WebSocket++ error channel + /** + * @param channel The level to look up + * @return The syslog level associated with `channel` + */ + int syslog_priority_error(level channel) const { + switch (channel) { + case elevel::devel: + return LOG_DEBUG; + case elevel::library: + return LOG_DEBUG; + case elevel::info: + return LOG_INFO; + case elevel::warn: + return LOG_WARNING; + case elevel::rerror: + return LOG_ERR; + case elevel::fatal: + return LOG_CRIT; + default: + return default_level; + } + } + + /// retrieve the syslog priority code given a WebSocket++ access channel + /** + * @param channel The level to look up + * @return The syslog level associated with `channel` + */ + _WEBSOCKETPP_CONSTEXPR_TOKEN_ int syslog_priority_access(level) const { + return default_level; + } + + channel_type_hint::value m_channel_type_hint; +}; + +} // log +} // websocketpp + +#endif // WEBSOCKETPP_LOGGER_SYSLOG_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/message_buffer/alloc.hpp b/thirdparty/websocketpp/include/websocketpp/message_buffer/alloc.hpp new file mode 100644 index 0000000..92c0a77 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/message_buffer/alloc.hpp @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2014, 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_MESSAGE_BUFFER_ALLOC_HPP +#define WEBSOCKETPP_MESSAGE_BUFFER_ALLOC_HPP + +#include +#include + +namespace websocketpp { +namespace message_buffer { +namespace alloc { + +/// A connection message manager that allocates a new message for each +/// request. +template +class con_msg_manager + : public lib::enable_shared_from_this > +{ +public: + typedef con_msg_manager type; + typedef lib::shared_ptr ptr; + typedef lib::weak_ptr weak_ptr; + + typedef typename message::ptr message_ptr; + + /// Get an empty message buffer + /** + * @return A shared pointer to an empty new message + */ + message_ptr get_message() { + return message_ptr(lib::make_shared(type::shared_from_this())); + } + + /// Get a message buffer with specified size and opcode + /** + * @param op The opcode to use + * @param size Minimum size in bytes to request for the message payload. + * + * @return A shared pointer to a new message with specified size. + */ + message_ptr get_message(frame::opcode::value op,size_t size) { + return message_ptr(lib::make_shared(type::shared_from_this(),op,size)); + } + + /// Recycle a message + /** + * This method shouldn't be called. If it is, return false to indicate an + * error. The rest of the method recycle chain should notice this and free + * the memory. + * + * @param msg The message to be recycled. + * + * @return true if the message was successfully recycled, false otherwse. + */ + bool recycle(message *) { + return false; + } +}; + +/// An endpoint message manager that allocates a new manager for each +/// connection. +template +class endpoint_msg_manager { +public: + typedef typename con_msg_manager::ptr con_msg_man_ptr; + + /// Get a pointer to a connection message manager + /** + * @return A pointer to the requested connection message manager. + */ + con_msg_man_ptr get_manager() const { + return con_msg_man_ptr(lib::make_shared()); + } +}; + +} // namespace alloc +} // namespace message_buffer +} // namespace websocketpp + +#endif // WEBSOCKETPP_MESSAGE_BUFFER_ALLOC_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/message_buffer/message.hpp b/thirdparty/websocketpp/include/websocketpp/message_buffer/message.hpp new file mode 100644 index 0000000..c70a736 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/message_buffer/message.hpp @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2014, 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_MESSAGE_BUFFER_MESSAGE_HPP +#define WEBSOCKETPP_MESSAGE_BUFFER_MESSAGE_HPP + +#include +#include + +#include + +namespace websocketpp { +namespace message_buffer { + +/* # message: + * object that stores a message while it is being sent or received. Contains + * the message payload itself, the message header, the extension data, and the + * opcode. + * + * # connection_message_manager: + * An object that manages all of the message_buffers associated with a given + * connection. Implements the get_message_buffer(size) method that returns + * a message buffer at least size bytes long. + * + * Message buffers are reference counted with shared ownership semantics. Once + * requested from the manager the requester and it's associated downstream code + * may keep a pointer to the message indefinitely at a cost of extra resource + * usage. Once the reference count drops to the point where the manager is the + * only reference the messages is recycled using whatever method is implemented + * in the manager. + * + * # endpoint_message_manager: + * An object that manages connection_message_managers. Implements the + * get_message_manager() method. This is used once by each connection to + * request the message manager that they are supposed to use to manage message + * buffers for their own use. + * + * TYPES OF CONNECTION_MESSAGE_MANAGERS + * - allocate a message with the exact size every time one is requested + * - maintain a pool of pre-allocated messages and return one when needed. + * Recycle previously used messages back into the pool + * + * TYPES OF ENDPOINT_MESSAGE_MANAGERS + * - allocate a new connection manager for each connection. Message pools + * become connection specific. This increases memory usage but improves + * concurrency. + * - allocate a single connection manager and share a pointer to it with all + * connections created by this endpoint. The message pool will be shared + * among all connections, improving memory usage and performance at the cost + * of reduced concurrency + */ + + +/// Represents a buffer for a single WebSocket message. +/** + * + * + */ +template class con_msg_manager> +class message { +public: + typedef lib::shared_ptr ptr; + + typedef con_msg_manager con_msg_man_type; + typedef typename con_msg_man_type::ptr con_msg_man_ptr; + typedef typename con_msg_man_type::weak_ptr con_msg_man_weak_ptr; + + /// Construct an empty message + /** + * Construct an empty message + */ + message(const con_msg_man_ptr manager) + : m_manager(manager) + , m_prepared(false) + , m_fin(true) + , m_terminal(false) + , m_compressed(false) {} + + /// Construct a message and fill in some values + /** + * + */ + message(const con_msg_man_ptr manager, frame::opcode::value op, size_t size = 128) + : m_manager(manager) + , m_opcode(op) + , m_prepared(false) + , m_fin(true) + , m_terminal(false) + , m_compressed(false) + { + m_payload.reserve(size); + } + + /// Return whether or not the message has been prepared for sending + /** + * The prepared flag indicates that the message has been prepared by a + * websocket protocol processor and is ready to be written to the wire. + * + * @return whether or not the message has been prepared for sending + */ + bool get_prepared() const { + return m_prepared; + } + + /// Set or clear the flag that indicates that the message has been prepared + /** + * This flag should not be set by end user code without a very good reason. + * + * @param value The value to set the prepared flag to + */ + void set_prepared(bool value) { + m_prepared = value; + } + + /// Return whether or not the message is flagged as compressed + /** + * @return whether or not the message is/should be compressed + */ + bool get_compressed() const { + return m_compressed; + } + + /// Set or clear the compression flag + /** + * Setting the compression flag indicates that the data in this message + * would benefit from compression. If both endpoints negotiate a compression + * extension WebSocket++ will attempt to compress messages with this flag. + * Setting this flag does not guarantee that the message will be compressed. + * + * @param value The value to set the compressed flag to + */ + void set_compressed(bool value) { + m_compressed = value; + } + + /// Get whether or not the message is terminal + /** + * Messages can be flagged as terminal, which results in the connection + * being close after they are written rather than the implementation going + * on to the next message in the queue. This is typically used internally + * for close messages only. + * + * @return Whether or not this message is marked terminal + */ + bool get_terminal() const { + return m_terminal; + } + + /// Set the terminal flag + /** + * This flag should not be set by end user code without a very good reason. + * + * @see get_terminal() + * + * @param value The value to set the terminal flag to. + */ + void set_terminal(bool value) { + m_terminal = value; + } + /// Read the fin bit + /** + * A message with the fin bit set will be sent as the last message of its + * sequence. A message with the fin bit cleared will require subsequent + * frames of opcode continuation until one of them has the fin bit set. + * + * The remote end likely will not deliver any bytes until the frame with the fin + * bit set has been received. + * + * @return Whether or not the fin bit is set + */ + bool get_fin() const { + return m_fin; + } + + /// Set the fin bit + /** + * @see get_fin for a more detailed explaination of the fin bit + * + * @param value The value to set the fin bit to. + */ + void set_fin(bool value) { + m_fin = value; + } + + /// Return the message opcode + frame::opcode::value get_opcode() const { + return m_opcode; + } + + /// Set the opcode + void set_opcode(frame::opcode::value op) { + m_opcode = op; + } + + /// Return the prepared frame header + /** + * This value is typically set by a websocket protocol processor + * and shouldn't be tampered with. + */ + std::string const & get_header() const { + return m_header; + } + + /// Set prepared frame header + /** + * Under normal circumstances this should not be called by end users + * + * @param header A string to set the header to. + */ + void set_header(std::string const & header) { + m_header = header; + } + + std::string const & get_extension_data() const { + return m_extension_data; + } + + /// Get a reference to the payload string + /** + * @return A const reference to the message's payload string + */ + std::string const & get_payload() const { + return m_payload; + } + + /// Get a non-const reference to the payload string + /** + * @return A reference to the message's payload string + */ + std::string & get_raw_payload() { + return m_payload; + } + + /// Set payload data + /** + * Set the message buffer's payload to the given value. + * + * @param payload A string to set the payload to. + */ + void set_payload(std::string const & payload) { + m_payload = payload; + } + + /// Set payload data + /** + * Set the message buffer's payload to the given value. + * + * @param payload A pointer to a data array to set to. + * @param len The length of new payload in bytes. + */ + void set_payload(void const * payload, size_t len) { + m_payload.reserve(len); + char const * pl = static_cast(payload); + m_payload.assign(pl, pl + len); + } + + /// Append payload data + /** + * Append data to the message buffer's payload. + * + * @param payload A string containing the data array to append. + */ + void append_payload(std::string const & payload) { + m_payload.append(payload); + } + + /// Append payload data + /** + * Append data to the message buffer's payload. + * + * @param payload A pointer to a data array to append + * @param len The length of payload in bytes + */ + void append_payload(void const * payload, size_t len) { + m_payload.reserve(m_payload.size()+len); + m_payload.append(static_cast(payload),len); + } + + /// Recycle the message + /** + * A request to recycle this message was received. Forward that request to + * the connection message manager for processing. Errors and exceptions + * from the manager's recycle member function should be passed back up the + * call chain. The caller to message::recycle will deal with them. + * + * Recycle must *only* be called by the message shared_ptr's destructor. + * Once recycled successfully, ownership of the memory has been passed to + * another system and must not be accessed again. + * + * @return true if the message was successfully recycled, false otherwise. + */ + bool recycle() { + con_msg_man_ptr shared = m_manager.lock(); + + if (shared) { + return shared->recycle(this); + } else { + return false; + } + } +private: + con_msg_man_weak_ptr m_manager; + std::string m_header; + std::string m_extension_data; + std::string m_payload; + frame::opcode::value m_opcode; + bool m_prepared; + bool m_fin; + bool m_terminal; + bool m_compressed; +}; + +} // namespace message_buffer +} // namespace websocketpp + +#endif // WEBSOCKETPP_MESSAGE_BUFFER_MESSAGE_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/message_buffer/pool.hpp b/thirdparty/websocketpp/include/websocketpp/message_buffer/pool.hpp new file mode 100644 index 0000000..f231268 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/message_buffer/pool.hpp @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2014, 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_MESSAGE_BUFFER_ALLOC_HPP +#define WEBSOCKETPP_MESSAGE_BUFFER_ALLOC_HPP + +#include + +#include + +namespace websocketpp { +namespace message_buffer { + +/* # message: + * object that stores a message while it is being sent or received. Contains + * the message payload itself, the message header, the extension data, and the + * opcode. + * + * # connection_message_manager: + * An object that manages all of the message_buffers associated with a given + * connection. Implements the get_message_buffer(size) method that returns + * a message buffer at least size bytes long. + * + * Message buffers are reference counted with shared ownership semantics. Once + * requested from the manager the requester and it's associated downstream code + * may keep a pointer to the message indefinitely at a cost of extra resource + * usage. Once the reference count drops to the point where the manager is the + * only reference the messages is recycled using whatever method is implemented + * in the manager. + * + * # endpoint_message_manager: + * An object that manages connection_message_managers. Implements the + * get_message_manager() method. This is used once by each connection to + * request the message manager that they are supposed to use to manage message + * buffers for their own use. + * + * TYPES OF CONNECTION_MESSAGE_MANAGERS + * - allocate a message with the exact size every time one is requested + * - maintain a pool of pre-allocated messages and return one when needed. + * Recycle previously used messages back into the pool + * + * TYPES OF ENDPOINT_MESSAGE_MANAGERS + * - allocate a new connection manager for each connection. Message pools + * become connection specific. This increases memory usage but improves + * concurrency. + * - allocate a single connection manager and share a pointer to it with all + * connections created by this endpoint. The message pool will be shared + * among all connections, improving memory usage and performance at the cost + * of reduced concurrency + */ + +/// Custom deleter for use in shared_ptrs to message. +/** + * This is used to catch messages about to be deleted and offer the manager the + * ability to recycle them instead. Message::recycle will return true if it was + * successfully recycled and false otherwise. In the case of exceptions or error + * this deleter frees the memory. + */ +template +void message_deleter(T* msg) { + try { + if (!msg->recycle()) { + delete msg; + } + } catch (...) { + // TODO: is there a better way to ensure this function doesn't throw? + delete msg; + } +} + +/// Represents a buffer for a single WebSocket message. +/** + * + * + */ +template +class message { +public: + typedef lib::shared_ptr ptr; + + typedef typename con_msg_manager::weak_ptr con_msg_man_ptr; + + message(con_msg_man_ptr manager, size_t size = 128) + : m_manager(manager) + , m_payload(size) {} + + frame::opcode::value get_opcode() const { + return m_opcode; + } + const std::string& get_header() const { + return m_header; + } + const std::string& get_extension_data() const { + return m_extension_data; + } + const std::string& get_payload() const { + return m_payload; + } + + /// Recycle the message + /** + * A request to recycle this message was received. Forward that request to + * the connection message manager for processing. Errors and exceptions + * from the manager's recycle member function should be passed back up the + * call chain. The caller to message::recycle will deal with them. + * + * Recycle must *only* be called by the message shared_ptr's destructor. + * Once recycled successfully, ownership of the memory has been passed to + * another system and must not be accessed again. + * + * @return true if the message was successfully recycled, false otherwise. + */ + bool recycle() { + typename con_msg_manager::ptr shared = m_manager.lock(); + + if (shared) { + return shared->(recycle(this)); + } else { + return false; + } + } +private: + con_msg_man_ptr m_manager; + + frame::opcode::value m_opcode; + std::string m_header; + std::string m_extension_data; + std::string m_payload; +}; + +namespace alloc { + +/// A connection message manager that allocates a new message for each +/// request. +template +class con_msg_manager { +public: + typedef lib::shared_ptr ptr; + typedef lib::weak_ptr weak_ptr; + + typedef typename message::ptr message_ptr; + + /// Get a message buffer with specified size + /** + * @param size Minimum size in bytes to request for the message payload. + * + * @return A shared pointer to a new message with specified size. + */ + message_ptr get_message(size_t size) const { + return lib::make_shared(size); + } + + /// Recycle a message + /** + * This method shouldn't be called. If it is, return false to indicate an + * error. The rest of the method recycle chain should notice this and free + * the memory. + * + * @param msg The message to be recycled. + * + * @return true if the message was successfully recycled, false otherwse. + */ + bool recycle(message * msg) { + return false; + } +}; + +/// An endpoint message manager that allocates a new manager for each +/// connection. +template +class endpoint_msg_manager { +public: + typedef typename con_msg_manager::ptr con_msg_man_ptr; + + /// Get a pointer to a connection message manager + /** + * @return A pointer to the requested connection message manager. + */ + con_msg_man_ptr get_manager() const { + return lib::make_shared(); + } +}; + +} // namespace alloc + +namespace pool { + +/// A connection messages manager that maintains a pool of messages that is +/// used to fulfill get_message requests. +class con_msg_manager { + +}; + +/// An endpoint manager that maintains a shared pool of connection managers +/// and returns an appropriate one for the requesting connection. +class endpoint_msg_manager { + +}; + +} // namespace pool + +} // namespace message_buffer +} // namespace websocketpp + +#endif // WEBSOCKETPP_MESSAGE_BUFFER_ALLOC_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/processors/base.hpp b/thirdparty/websocketpp/include/websocketpp/processors/base.hpp new file mode 100644 index 0000000..79e291c --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/processors/base.hpp @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2014, 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_PROCESSOR_BASE_HPP +#define WEBSOCKETPP_PROCESSOR_BASE_HPP + +#include +#include +#include + +#include +#include + +#include + +namespace websocketpp { +namespace processor { + +/// Constants related to processing WebSocket connections +namespace constants { + +static char const upgrade_token[] = "websocket"; +static char const connection_token[] = "Upgrade"; +static char const handshake_guid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + +} // namespace constants + + +/// Processor class related error codes +namespace error_cat { +enum value { + BAD_REQUEST = 0, // Error was the result of improperly formatted user input + INTERNAL_ERROR = 1, // Error was a logic error internal to WebSocket++ + PROTOCOL_VIOLATION = 2, + MESSAGE_TOO_BIG = 3, + PAYLOAD_VIOLATION = 4 // Error was due to receiving invalid payload data +}; +} // namespace error_cat + +/// Error code category and codes used by all processor types +namespace error { +enum processor_errors { + /// Catch-all error for processor policy errors that don't fit in other + /// categories + general = 1, + + /// Error was the result of improperly formatted user input + bad_request, + + /// Processor encountered a protocol violation in an incoming message + protocol_violation, + + /// Processor encountered a message that was too large + message_too_big, + + /// Processor encountered invalid payload data. + invalid_payload, + + /// The processor method was called with invalid arguments + invalid_arguments, + + /// Opcode was invalid for requested operation + invalid_opcode, + + /// Control frame too large + control_too_big, + + /// Illegal use of reserved bit + invalid_rsv_bit, + + /// Fragmented control message + fragmented_control, + + /// Continuation without message + invalid_continuation, + + /// Clients may not send unmasked frames + masking_required, + + /// Servers may not send masked frames + masking_forbidden, + + /// Payload length not minimally encoded + non_minimal_encoding, + + /// Not supported on 32 bit systems + requires_64bit, + + /// Invalid UTF-8 encoding + invalid_utf8, + + /// Operation required not implemented functionality + not_implemented, + + /// Invalid HTTP method + invalid_http_method, + + /// Invalid HTTP version + invalid_http_version, + + /// Invalid HTTP status + invalid_http_status, + + /// Missing Required Header + missing_required_header, + + /// Embedded SHA-1 library error + sha1_library, + + /// No support for this feature in this protocol version. + no_protocol_support, + + /// Reserved close code used + reserved_close_code, + + /// Invalid close code used + invalid_close_code, + + /// Using a reason requires a close code + reason_requires_code, + + /// Error parsing subprotocols + subprotocol_parse_error, + + /// Error parsing extensions + extension_parse_error, + + /// Extension related operation was ignored because extensions are disabled + extensions_disabled, + + /// Short Ke3 read. Hybi00 requires a third key to be read from the 8 bytes + /// after the handshake. Less than 8 bytes were read. + short_key3 +}; + +/// Category for processor errors +class processor_category : public lib::error_category { +public: + processor_category() {} + + char const * name() const _WEBSOCKETPP_NOEXCEPT_TOKEN_ { + return "websocketpp.processor"; + } + + std::string message(int value) const { + switch(value) { + case error::general: + return "Generic processor error"; + case error::bad_request: + return "invalid user input"; + case error::protocol_violation: + return "Generic protocol violation"; + case error::message_too_big: + return "A message was too large"; + case error::invalid_payload: + return "A payload contained invalid data"; + case error::invalid_arguments: + return "invalid function arguments"; + case error::invalid_opcode: + return "invalid opcode"; + case error::control_too_big: + return "Control messages are limited to fewer than 125 characters"; + case error::invalid_rsv_bit: + return "Invalid use of reserved bits"; + case error::fragmented_control: + return "Control messages cannot be fragmented"; + case error::invalid_continuation: + return "Invalid message continuation"; + case error::masking_required: + return "Clients may not send unmasked frames"; + case error::masking_forbidden: + return "Servers may not send masked frames"; + case error::non_minimal_encoding: + return "Payload length was not minimally encoded"; + case error::requires_64bit: + return "64 bit frames are not supported on 32 bit systems"; + case error::invalid_utf8: + return "Invalid UTF8 encoding"; + case error::not_implemented: + return "Operation required not implemented functionality"; + case error::invalid_http_method: + return "Invalid HTTP method."; + case error::invalid_http_version: + return "Invalid HTTP version."; + case error::invalid_http_status: + return "Invalid HTTP status."; + case error::missing_required_header: + return "A required HTTP header is missing"; + case error::sha1_library: + return "SHA-1 library error"; + case error::no_protocol_support: + return "The WebSocket protocol version in use does not support this feature"; + case error::reserved_close_code: + return "Reserved close code used"; + case error::invalid_close_code: + return "Invalid close code used"; + case error::reason_requires_code: + return "Using a close reason requires a valid close code"; + case error::subprotocol_parse_error: + return "Error parsing subprotocol header"; + case error::extension_parse_error: + return "Error parsing extension header"; + case error::extensions_disabled: + return "Extensions are disabled"; + case error::short_key3: + return "Short Hybi00 Key 3 read"; + default: + return "Unknown"; + } + } +}; + +/// Get a reference to a static copy of the processor error category +inline lib::error_category const & get_processor_category() { + static processor_category instance; + return instance; +} + +/// Create an error code with the given value and the processor category +inline lib::error_code make_error_code(error::processor_errors e) { + return lib::error_code(static_cast(e), get_processor_category()); +} + +/// Converts a processor error_code into a websocket close code +/** + * Looks up the appropriate WebSocket close code that should be sent after an + * error of this sort occurred. + * + * If the error is not in the processor category close::status::blank is + * returned. + * + * If the error isn't normally associated with reasons to close a connection + * (such as errors intended to be used internally or delivered to client + * applications, ex: invalid arguments) then + * close::status::internal_endpoint_error is returned. + */ +inline close::status::value to_ws(lib::error_code ec) { + if (ec.category() != get_processor_category()) { + return close::status::blank; + } + + switch (ec.value()) { + case error::protocol_violation: + case error::control_too_big: + case error::invalid_opcode: + case error::invalid_rsv_bit: + case error::fragmented_control: + case error::invalid_continuation: + case error::masking_required: + case error::masking_forbidden: + case error::reserved_close_code: + case error::invalid_close_code: + return close::status::protocol_error; + case error::invalid_payload: + case error::invalid_utf8: + return close::status::invalid_payload; + case error::message_too_big: + return close::status::message_too_big; + default: + return close::status::internal_endpoint_error; + } +} + +} // namespace error +} // namespace processor +} // namespace websocketpp + +_WEBSOCKETPP_ERROR_CODE_ENUM_NS_START_ +template<> struct is_error_code_enum +{ + static bool const value = true; +}; +_WEBSOCKETPP_ERROR_CODE_ENUM_NS_END_ + +#endif //WEBSOCKETPP_PROCESSOR_BASE_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/processors/hybi00.hpp b/thirdparty/websocketpp/include/websocketpp/processors/hybi00.hpp new file mode 100644 index 0000000..1fba38f --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/processors/hybi00.hpp @@ -0,0 +1,462 @@ +/* + * Copyright (c) 2014, 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_PROCESSOR_HYBI00_HPP +#define WEBSOCKETPP_PROCESSOR_HYBI00_HPP + +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace websocketpp { +namespace processor { + +/// Processor for Hybi Draft version 00 +/** + * There are many differences between Hybi 00 and Hybi 13 + */ +template +class hybi00 : public processor { +public: + typedef processor base; + + typedef typename config::request_type request_type; + typedef typename config::response_type response_type; + + typedef typename config::message_type message_type; + typedef typename message_type::ptr message_ptr; + + typedef typename config::con_msg_manager_type::ptr msg_manager_ptr; + + explicit hybi00(bool secure, bool p_is_server, msg_manager_ptr manager) + : processor(secure, p_is_server) + , msg_hdr(0x00) + , msg_ftr(0xff) + , m_state(HEADER) + , m_msg_manager(manager) {} + + int get_version() const { + return 0; + } + + lib::error_code validate_handshake(request_type const & r) const { + if (r.get_method() != "GET") { + return make_error_code(error::invalid_http_method); + } + + if (r.get_version() != "HTTP/1.1") { + return make_error_code(error::invalid_http_version); + } + + // required headers + // Host is required by HTTP/1.1 + // Connection is required by is_websocket_handshake + // Upgrade is required by is_websocket_handshake + if (r.get_header("Sec-WebSocket-Key1").empty() || + r.get_header("Sec-WebSocket-Key2").empty() || + r.get_header("Sec-WebSocket-Key3").empty()) + { + return make_error_code(error::missing_required_header); + } + + return lib::error_code(); + } + + lib::error_code process_handshake(request_type const & req, + std::string const & subprotocol, response_type & res) const + { + char key_final[16]; + + // copy key1 into final key + decode_client_key(req.get_header("Sec-WebSocket-Key1"), &key_final[0]); + + // copy key2 into final key + decode_client_key(req.get_header("Sec-WebSocket-Key2"), &key_final[4]); + + // copy key3 into final key + // key3 should be exactly 8 bytes. If it is more it will be truncated + // if it is less the final key will almost certainly be wrong. + // TODO: decide if it is best to silently fail here or produce some sort + // of warning or exception. + std::string const & key3 = req.get_header("Sec-WebSocket-Key3"); + std::copy(key3.c_str(), + key3.c_str()+(std::min)(static_cast(8), key3.size()), + &key_final[8]); + + res.append_header( + "Sec-WebSocket-Key3", + md5::md5_hash_string(std::string(key_final,16)) + ); + + res.append_header("Upgrade","WebSocket"); + res.append_header("Connection","Upgrade"); + + // Echo back client's origin unless our local application set a + // more restrictive one. + if (res.get_header("Sec-WebSocket-Origin").empty()) { + res.append_header("Sec-WebSocket-Origin",req.get_header("Origin")); + } + + // Echo back the client's request host unless our local application + // set a different one. + if (res.get_header("Sec-WebSocket-Location").empty()) { + uri_ptr uri = get_uri(req); + res.append_header("Sec-WebSocket-Location",uri->str()); + } + + if (!subprotocol.empty()) { + res.replace_header("Sec-WebSocket-Protocol",subprotocol); + } + + return lib::error_code(); + } + + /// Fill in a set of request headers for a client connection request + /** + * The Hybi 00 processor only implements incoming connections so this will + * always return an error. + * + * @param [out] req Set of headers to fill in + * @param [in] uri The uri being connected to + * @param [in] subprotocols The list of subprotocols to request + */ + lib::error_code client_handshake_request(request_type &, uri_ptr, + std::vector const &) const + { + return error::make_error_code(error::no_protocol_support); + } + + /// Validate the server's response to an outgoing handshake request + /** + * The Hybi 00 processor only implements incoming connections so this will + * always return an error. + * + * @param req The original request sent + * @param res The reponse to generate + * @return An error code, 0 on success, non-zero for other errors + */ + lib::error_code validate_server_handshake_response(request_type const &, + response_type &) const + { + return error::make_error_code(error::no_protocol_support); + } + + std::string get_raw(response_type const & res) const { + response_type temp = res; + temp.remove_header("Sec-WebSocket-Key3"); + return temp.raw() + res.get_header("Sec-WebSocket-Key3"); + } + + std::string const & get_origin(request_type const & r) const { + return r.get_header("Origin"); + } + + /// Extracts requested subprotocols from a handshake request + /** + * hybi00 does support subprotocols + * https://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00#section-1.9 + * + * @param [in] req The request to extract from + * @param [out] subprotocol_list A reference to a vector of strings to store + * the results in. + */ + lib::error_code extract_subprotocols(request_type const & req, + std::vector & subprotocol_list) + { + if (!req.get_header("Sec-WebSocket-Protocol").empty()) { + http::parameter_list p; + + if (!req.get_header_as_plist("Sec-WebSocket-Protocol",p)) { + http::parameter_list::const_iterator it; + + for (it = p.begin(); it != p.end(); ++it) { + subprotocol_list.push_back(it->first); + } + } else { + return error::make_error_code(error::subprotocol_parse_error); + } + } + return lib::error_code(); + } + + uri_ptr get_uri(request_type const & request) const { + std::string h = request.get_header("Host"); + + size_t last_colon = h.rfind(":"); + size_t last_sbrace = h.rfind("]"); + + // no : = hostname with no port + // last : before ] = ipv6 literal with no port + // : with no ] = hostname with port + // : after ] = ipv6 literal with port + + if (last_colon == std::string::npos || + (last_sbrace != std::string::npos && last_sbrace > last_colon)) + { + return lib::make_shared(base::m_secure, h, request.get_uri()); + } else { + return lib::make_shared(base::m_secure, + h.substr(0,last_colon), + h.substr(last_colon+1), + request.get_uri()); + } + + // TODO: check if get_uri is a full uri + } + + /// Get hybi00 handshake key3 + /** + * @todo This doesn't appear to be used anymore. It might be able to be + * removed + */ + std::string get_key3() const { + return ""; + } + + /// Process new websocket connection bytes + size_t consume(uint8_t * buf, size_t len, lib::error_code & ec) { + // if in state header we are expecting a 0x00 byte, if we don't get one + // it is a fatal error + size_t p = 0; // bytes processed + size_t l = 0; + + ec = lib::error_code(); + + while (p < len) { + if (m_state == HEADER) { + if (buf[p] == msg_hdr) { + p++; + m_msg_ptr = m_msg_manager->get_message(frame::opcode::text,1); + + if (!m_msg_ptr) { + ec = make_error_code(websocketpp::error::no_incoming_buffers); + m_state = FATAL_ERROR; + } else { + m_state = PAYLOAD; + } + } else { + ec = make_error_code(error::protocol_violation); + m_state = FATAL_ERROR; + } + } else if (m_state == PAYLOAD) { + uint8_t *it = std::find(buf+p,buf+len,msg_ftr); + + // 0 1 2 3 4 5 + // 0x00 0x23 0x23 0x23 0xff 0xXX + + // Copy payload bytes into message + l = static_cast(it-(buf+p)); + m_msg_ptr->append_payload(buf+p,l); + p += l; + + if (it != buf+len) { + // message is done, copy it and the trailing + p++; + // TODO: validation + m_state = READY; + } + } else { + // TODO + break; + } + } + // If we get one, we create a new message and move to application state + + // if in state application we are copying bytes into the output message + // and validating them for UTF8 until we hit a 0xff byte. Once we hit + // 0x00, the message is complete and is dispatched. Then we go back to + // header state. + + //ec = make_error_code(error::not_implemented); + return p; + } + + bool ready() const { + return (m_state == READY); + } + + bool get_error() const { + return false; + } + + message_ptr get_message() { + message_ptr ret = m_msg_ptr; + m_msg_ptr = message_ptr(); + m_state = HEADER; + return ret; + } + + /// Prepare a message for writing + /** + * Performs validation, masking, compression, etc. will return an error if + * there was an error, otherwise msg will be ready to be written + */ + virtual lib::error_code prepare_data_frame(message_ptr in, message_ptr out) + { + if (!in || !out) { + return make_error_code(error::invalid_arguments); + } + + // TODO: check if the message is prepared already + + // validate opcode + if (in->get_opcode() != frame::opcode::text) { + return make_error_code(error::invalid_opcode); + } + + std::string& i = in->get_raw_payload(); + //std::string& o = out->get_raw_payload(); + + // validate payload utf8 + if (!utf8_validator::validate(i)) { + return make_error_code(error::invalid_payload); + } + + // generate header + out->set_header(std::string(reinterpret_cast(&msg_hdr),1)); + + // process payload + out->set_payload(i); + out->append_payload(std::string(reinterpret_cast(&msg_ftr),1)); + + // hybi00 doesn't support compression + // hybi00 doesn't have masking + + out->set_prepared(true); + + return lib::error_code(); + } + + /// Prepare a ping frame + /** + * Hybi 00 doesn't support pings so this will always return an error + * + * @param in The string to use for the ping payload + * @param out The message buffer to prepare the ping in. + * @return Status code, zero on success, non-zero on failure + */ + lib::error_code prepare_ping(std::string const &, message_ptr) const + { + return lib::error_code(error::no_protocol_support); + } + + /// Prepare a pong frame + /** + * Hybi 00 doesn't support pongs so this will always return an error + * + * @param in The string to use for the pong payload + * @param out The message buffer to prepare the pong in. + * @return Status code, zero on success, non-zero on failure + */ + lib::error_code prepare_pong(std::string const &, message_ptr) const + { + return lib::error_code(error::no_protocol_support); + } + + /// Prepare a close frame + /** + * Hybi 00 doesn't support the close code or reason so these parameters are + * ignored. + * + * @param code The close code to send + * @param reason The reason string to send + * @param out The message buffer to prepare the fame in + * @return Status code, zero on success, non-zero on failure + */ + lib::error_code prepare_close(close::status::value, std::string const &, + message_ptr out) const + { + if (!out) { + return lib::error_code(error::invalid_arguments); + } + + std::string val; + val.append(1,'\xff'); + val.append(1,'\x00'); + out->set_payload(val); + out->set_prepared(true); + + return lib::error_code(); + } +private: + void decode_client_key(std::string const & key, char * result) const { + unsigned int spaces = 0; + std::string digits; + uint32_t num; + + // key2 + for (size_t i = 0; i < key.size(); i++) { + if (key[i] == ' ') { + spaces++; + } else if (key[i] >= '0' && key[i] <= '9') { + digits += key[i]; + } + } + + num = static_cast(strtoul(digits.c_str(), NULL, 10)); + if (spaces > 0 && num > 0) { + num = htonl(num/spaces); + std::copy(reinterpret_cast(&num), + reinterpret_cast(&num)+4, + result); + } else { + std::fill(result,result+4,0); + } + } + + enum state { + HEADER = 0, + PAYLOAD = 1, + READY = 2, + FATAL_ERROR = 3 + }; + + uint8_t const msg_hdr; + uint8_t const msg_ftr; + + state m_state; + + msg_manager_ptr m_msg_manager; + message_ptr m_msg_ptr; + utf8_validator::validator m_validator; +}; + +} // namespace processor +} // namespace websocketpp + +#endif //WEBSOCKETPP_PROCESSOR_HYBI00_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/processors/hybi07.hpp b/thirdparty/websocketpp/include/websocketpp/processors/hybi07.hpp new file mode 100644 index 0000000..dfa6ace --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/processors/hybi07.hpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2014, 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_PROCESSOR_HYBI07_HPP +#define WEBSOCKETPP_PROCESSOR_HYBI07_HPP + +#include + +#include +#include + +namespace websocketpp { +namespace processor { + +/// Processor for Hybi Draft version 07 +/** + * The primary difference between 07 and 08 is a version number. + */ +template +class hybi07 : public hybi08 { +public: + typedef typename config::request_type request_type; + + typedef typename config::con_msg_manager_type::ptr msg_manager_ptr; + typedef typename config::rng_type rng_type; + + explicit hybi07(bool secure, bool p_is_server, msg_manager_ptr manager, rng_type& rng) + : hybi08(secure, p_is_server, manager, rng) {} + + /// Fill in a set of request headers for a client connection request + /** + * The Hybi 07 processor only implements incoming connections so this will + * always return an error. + * + * @param [out] req Set of headers to fill in + * @param [in] uri The uri being connected to + * @param [in] subprotocols The list of subprotocols to request + */ + lib::error_code client_handshake_request(request_type &, uri_ptr, + std::vector const &) const + { + return error::make_error_code(error::no_protocol_support); + } + + int get_version() const { + return 7; + } +private: +}; + +} // namespace processor +} // namespace websocketpp + +#endif //WEBSOCKETPP_PROCESSOR_HYBI07_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/processors/hybi08.hpp b/thirdparty/websocketpp/include/websocketpp/processors/hybi08.hpp new file mode 100644 index 0000000..531fcc8 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/processors/hybi08.hpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2014, 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_PROCESSOR_HYBI08_HPP +#define WEBSOCKETPP_PROCESSOR_HYBI08_HPP + +#include + +#include +#include + +namespace websocketpp { +namespace processor { + +/// Processor for Hybi Draft version 08 +/** + * The primary difference between 08 and 13 is a different origin header name + */ +template +class hybi08 : public hybi13 { +public: + typedef hybi08 type; + typedef typename config::request_type request_type; + + typedef typename config::con_msg_manager_type::ptr msg_manager_ptr; + typedef typename config::rng_type rng_type; + + explicit hybi08(bool secure, bool p_is_server, msg_manager_ptr manager, rng_type& rng) + : hybi13(secure, p_is_server, manager, rng) {} + + /// Fill in a set of request headers for a client connection request + /** + * The Hybi 08 processor only implements incoming connections so this will + * always return an error. + * + * @param [out] req Set of headers to fill in + * @param [in] uri The uri being connected to + * @param [in] subprotocols The list of subprotocols to request + */ + lib::error_code client_handshake_request(request_type &, uri_ptr, + std::vector const &) const + { + return error::make_error_code(error::no_protocol_support); + } + + int get_version() const { + return 8; + } + + std::string const & get_origin(request_type const & r) const { + return r.get_header("Sec-WebSocket-Origin"); + } +private: +}; + +} // namespace processor +} // namespace websocketpp + +#endif //WEBSOCKETPP_PROCESSOR_HYBI08_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/processors/hybi13.hpp b/thirdparty/websocketpp/include/websocketpp/processors/hybi13.hpp new file mode 100644 index 0000000..c4c2447 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/processors/hybi13.hpp @@ -0,0 +1,1078 @@ +/* + * 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_PROCESSOR_HYBI13_HPP +#define WEBSOCKETPP_PROCESSOR_HYBI13_HPP + +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +namespace websocketpp { +namespace processor { + +/// Processor for Hybi version 13 (RFC6455) +template +class hybi13 : public processor { +public: + typedef processor base; + + typedef typename config::request_type request_type; + typedef typename config::response_type response_type; + + typedef typename config::message_type message_type; + typedef typename message_type::ptr message_ptr; + + typedef typename config::con_msg_manager_type msg_manager_type; + typedef typename msg_manager_type::ptr msg_manager_ptr; + typedef typename config::rng_type rng_type; + + typedef typename config::permessage_deflate_type permessage_deflate_type; + + typedef std::pair err_str_pair; + + explicit hybi13(bool secure, bool p_is_server, msg_manager_ptr manager, rng_type& rng) + : processor(secure, p_is_server) + , m_msg_manager(manager) + , m_rng(rng) + { + reset_headers(); + } + + int get_version() const { + return 13; + } + + bool has_permessage_deflate() const { + return m_permessage_deflate.is_implemented(); + } + + err_str_pair negotiate_extensions(request_type const & request) { + return negotiate_extensions_helper(request); + } + + err_str_pair negotiate_extensions(response_type const & response) { + return negotiate_extensions_helper(response); + } + + /// Extension negotiation helper function + /** + * This exists mostly because the code for requests and responses is + * identical and I can't have virtual template methods. + */ + template + err_str_pair negotiate_extensions_helper(header_type const & header) { + err_str_pair ret; + + // Respect blanket disabling of all extensions and don't even parse + // the extension header + if (!config::enable_extensions) { + ret.first = make_error_code(error::extensions_disabled); + return ret; + } + + http::parameter_list p; + + bool error = header.get_header_as_plist("Sec-WebSocket-Extensions",p); + + if (error) { + ret.first = make_error_code(error::extension_parse_error); + return ret; + } + + // If there are no extensions parsed then we are done! + if (p.size() == 0) { + return ret; + } + + http::parameter_list::const_iterator it; + + // look through the list of extension requests to find the first + // one that we can accept. + if (m_permessage_deflate.is_implemented()) { + err_str_pair neg_ret; + for (it = p.begin(); it != p.end(); ++it) { + // not a permessage-deflate extension request, ignore + if (it->first != "permessage-deflate") { + continue; + } + + // if we have already successfully negotiated this extension + // then skip any other requests to negotiate the same one + // with different parameters + if (m_permessage_deflate.is_enabled()) { + continue; + } + + // attempt to negotiate this offer + neg_ret = m_permessage_deflate.negotiate(it->second); + + if (neg_ret.first) { + // negotiation offer failed. Do nothing. We will continue + // searching for a permessage-deflate config that succeeds + continue; + } + + // Negotiation tentatively succeeded + + // Actually try to initialize the extension before we + // deem negotiation complete + lib::error_code ec = m_permessage_deflate.init(base::m_server); + + if (ec) { + // Negotiation succeeded but initialization failed this is + // an error that should stop negotiation of permessage + // deflate. Return the reason for the init failure + + ret.first = ec; + break; + } else { + // Successfully initialized, push the negotiated response into + // the reply and stop looking for additional permessage-deflate + // extensions + ret.second += neg_ret.second; + break; + } + } + } + + // support for future extensions would go here. Should check the value of + // ret.first before continuing. Might need to consider whether failure of + // negotiation of an earlier extension should stop negotiation of subsequent + // ones + + return ret; + } + + lib::error_code validate_handshake(request_type const & r) const { + if (r.get_method() != "GET") { + return make_error_code(error::invalid_http_method); + } + + if (r.get_version() != "HTTP/1.1") { + return make_error_code(error::invalid_http_version); + } + + // required headers + // Host is required by HTTP/1.1 + // Connection is required by is_websocket_handshake + // Upgrade is required by is_websocket_handshake + if (r.get_header("Sec-WebSocket-Key").empty()) { + return make_error_code(error::missing_required_header); + } + + return lib::error_code(); + } + + /* TODO: the 'subprotocol' parameter may need to be expanded into a more + * generic struct if other user input parameters to the processed handshake + * are found. + */ + lib::error_code process_handshake(request_type const & request, + std::string const & subprotocol, response_type & response) const + { + std::string server_key = request.get_header("Sec-WebSocket-Key"); + + lib::error_code ec = process_handshake_key(server_key); + + if (ec) { + return ec; + } + + response.replace_header("Sec-WebSocket-Accept",server_key); + response.append_header("Upgrade",constants::upgrade_token); + response.append_header("Connection",constants::connection_token); + + if (!subprotocol.empty()) { + response.replace_header("Sec-WebSocket-Protocol",subprotocol); + } + + return lib::error_code(); + } + + /// Fill in a set of request headers for a client connection request + /** + * @param [out] req Set of headers to fill in + * @param [in] uri The uri being connected to + * @param [in] subprotocols The list of subprotocols to request + */ + lib::error_code client_handshake_request(request_type & req, uri_ptr + uri, std::vector const & subprotocols) const + { + req.set_method("GET"); + req.set_uri(uri->get_resource()); + req.set_version("HTTP/1.1"); + + req.append_header("Upgrade","websocket"); + req.append_header("Connection","Upgrade"); + req.replace_header("Sec-WebSocket-Version","13"); + req.replace_header("Host",uri->get_host_port()); + + if (!subprotocols.empty()) { + std::ostringstream result; + std::vector::const_iterator it = subprotocols.begin(); + result << *it++; + while (it != subprotocols.end()) { + result << ", " << *it++; + } + + req.replace_header("Sec-WebSocket-Protocol",result.str()); + } + + // Generate handshake key + frame::uint32_converter conv; + unsigned char raw_key[16]; + + for (int i = 0; i < 4; i++) { + conv.i = m_rng(); + std::copy(conv.c,conv.c+4,&raw_key[i*4]); + } + + req.replace_header("Sec-WebSocket-Key",base64_encode(raw_key, 16)); + + if (m_permessage_deflate.is_implemented()) { + std::string offer = m_permessage_deflate.generate_offer(); + if (!offer.empty()) { + req.replace_header("Sec-WebSocket-Extensions",offer); + } + } + + return lib::error_code(); + } + + /// Validate the server's response to an outgoing handshake request + /** + * @param req The original request sent + * @param res The reponse to generate + * @return An error code, 0 on success, non-zero for other errors + */ + lib::error_code validate_server_handshake_response(request_type const & req, + response_type& res) const + { + // A valid response has an HTTP 101 switching protocols code + if (res.get_status_code() != http::status_code::switching_protocols) { + return error::make_error_code(error::invalid_http_status); + } + + // And the upgrade token in an upgrade header + std::string const & upgrade_header = res.get_header("Upgrade"); + if (utility::ci_find_substr(upgrade_header, constants::upgrade_token, + sizeof(constants::upgrade_token)-1) == upgrade_header.end()) + { + return error::make_error_code(error::missing_required_header); + } + + // And the websocket token in the connection header + std::string const & con_header = res.get_header("Connection"); + if (utility::ci_find_substr(con_header, constants::connection_token, + sizeof(constants::connection_token)-1) == con_header.end()) + { + return error::make_error_code(error::missing_required_header); + } + + // And has a valid Sec-WebSocket-Accept value + std::string key = req.get_header("Sec-WebSocket-Key"); + lib::error_code ec = process_handshake_key(key); + + if (ec || key != res.get_header("Sec-WebSocket-Accept")) { + return error::make_error_code(error::missing_required_header); + } + + // check extensions + + return lib::error_code(); + } + + std::string get_raw(response_type const & res) const { + return res.raw(); + } + + std::string const & get_origin(request_type const & r) const { + return r.get_header("Origin"); + } + + lib::error_code extract_subprotocols(request_type const & req, + std::vector & subprotocol_list) + { + if (!req.get_header("Sec-WebSocket-Protocol").empty()) { + http::parameter_list p; + + if (!req.get_header_as_plist("Sec-WebSocket-Protocol",p)) { + http::parameter_list::const_iterator it; + + for (it = p.begin(); it != p.end(); ++it) { + subprotocol_list.push_back(it->first); + } + } else { + return error::make_error_code(error::subprotocol_parse_error); + } + } + return lib::error_code(); + } + + uri_ptr get_uri(request_type const & request) const { + return get_uri_from_host(request,(base::m_secure ? "wss" : "ws")); + } + + /// Process new websocket connection bytes + /** + * + * Hybi 13 data streams represent a series of variable length frames. Each + * frame is made up of a series of fixed length fields. The lengths of later + * fields are contained in earlier fields. The first field length is fixed + * by the spec. + * + * This processor represents a state machine that keeps track of what field + * is presently being read and how many more bytes are needed to complete it + * + * + * + * + * Read two header bytes + * Extract full frame length. + * Read extra header bytes + * Validate frame header (including extension validate) + * Read extension data into extension message state object + * Read payload data into payload + * + * @param buf Input buffer + * + * @param len Length of input buffer + * + * @return Number of bytes processed or zero on error + */ + size_t consume(uint8_t * buf, size_t len, lib::error_code & ec) { + size_t p = 0; + + ec = lib::error_code(); + + //std::cout << "consume: " << utility::to_hex(buf,len) << std::endl; + + // Loop while we don't have a message ready and we still have bytes + // left to process. + while (m_state != READY && m_state != FATAL_ERROR && + (p < len || m_bytes_needed == 0)) + { + if (m_state == HEADER_BASIC) { + p += this->copy_basic_header_bytes(buf+p,len-p); + + if (m_bytes_needed > 0) { + continue; + } + + ec = this->validate_incoming_basic_header( + m_basic_header, base::m_server, !m_data_msg.msg_ptr + ); + if (ec) {break;} + + // extract full header size and adjust consume state accordingly + m_state = HEADER_EXTENDED; + m_cursor = 0; + m_bytes_needed = frame::get_header_len(m_basic_header) - + frame::BASIC_HEADER_LENGTH; + } else if (m_state == HEADER_EXTENDED) { + p += this->copy_extended_header_bytes(buf+p,len-p); + + if (m_bytes_needed > 0) { + continue; + } + + ec = validate_incoming_extended_header(m_basic_header,m_extended_header); + if (ec){break;} + + m_state = APPLICATION; + m_bytes_needed = static_cast(get_payload_size(m_basic_header,m_extended_header)); + + // check if this frame is the start of a new message and set up + // the appropriate message metadata. + frame::opcode::value op = frame::get_opcode(m_basic_header); + + // TODO: get_message failure conditions + + if (frame::opcode::is_control(op)) { + m_control_msg = msg_metadata( + m_msg_manager->get_message(op,m_bytes_needed), + frame::get_masking_key(m_basic_header,m_extended_header) + ); + + m_current_msg = &m_control_msg; + } else { + if (!m_data_msg.msg_ptr) { + if (m_bytes_needed > base::m_max_message_size) { + ec = make_error_code(error::message_too_big); + break; + } + + m_data_msg = msg_metadata( + m_msg_manager->get_message(op,m_bytes_needed), + frame::get_masking_key(m_basic_header,m_extended_header) + ); + + if (m_permessage_deflate.is_enabled()) { + m_data_msg.msg_ptr->set_compressed(frame::get_rsv1(m_basic_header)); + } + } else { + // Fetch the underlying payload buffer from the data message we + // are writing into. + std::string & out = m_data_msg.msg_ptr->get_raw_payload(); + + if (out.size() + m_bytes_needed > base::m_max_message_size) { + ec = make_error_code(error::message_too_big); + break; + } + + // Each frame starts a new masking key. All other state + // remains between frames. + m_data_msg.prepared_key = prepare_masking_key( + frame::get_masking_key( + m_basic_header, + m_extended_header + ) + ); + + out.reserve(out.size() + m_bytes_needed); + } + m_current_msg = &m_data_msg; + } + } else if (m_state == EXTENSION) { + m_state = APPLICATION; + } else if (m_state == APPLICATION) { + size_t bytes_to_process = (std::min)(m_bytes_needed,len-p); + + if (bytes_to_process > 0) { + p += this->process_payload_bytes(buf+p,bytes_to_process,ec); + + if (ec) {break;} + } + + if (m_bytes_needed > 0) { + continue; + } + + // If this was the last frame in the message set the ready flag. + // Otherwise, reset processor state to read additional frames. + if (frame::get_fin(m_basic_header)) { + ec = finalize_message(); + if (ec) { + break; + } + } else { + this->reset_headers(); + } + } else { + // shouldn't be here + ec = make_error_code(error::general); + return 0; + } + } + + return p; + } + + /// Perform any finalization actions on an incoming message + /** + * Called after the full message is received. Provides the opportunity for + * extensions to complete any data post processing as well as final UTF8 + * validation checks for text messages. + * + * @return A code indicating errors, if any + */ + lib::error_code finalize_message() { + std::string & out = m_current_msg->msg_ptr->get_raw_payload(); + + // if the frame is compressed, append the compression + // trailer and flush the compression buffer. + if (m_permessage_deflate.is_enabled() + && m_current_msg->msg_ptr->get_compressed()) + { + uint8_t trailer[4] = {0x00, 0x00, 0xff, 0xff}; + + // Decompress current buffer into the message buffer + lib::error_code ec; + ec = m_permessage_deflate.decompress(trailer,4,out); + if (ec) { + return ec; + } + } + + // ensure that text messages end on a valid UTF8 code point + if (frame::get_opcode(m_basic_header) == frame::opcode::TEXT) { + if (!m_current_msg->validator.complete()) { + return make_error_code(error::invalid_utf8); + } + } + + m_state = READY; + + return lib::error_code(); + } + + void reset_headers() { + m_state = HEADER_BASIC; + m_bytes_needed = frame::BASIC_HEADER_LENGTH; + + m_basic_header.b0 = 0x00; + m_basic_header.b1 = 0x00; + + std::fill_n( + m_extended_header.bytes, + frame::MAX_EXTENDED_HEADER_LENGTH, + 0x00 + ); + } + + /// Test whether or not the processor has a message ready + bool ready() const { + return (m_state == READY); + } + + message_ptr get_message() { + if (!ready()) { + return message_ptr(); + } + message_ptr ret = m_current_msg->msg_ptr; + m_current_msg->msg_ptr.reset(); + + if (frame::opcode::is_control(ret->get_opcode())) { + m_control_msg.msg_ptr.reset(); + } else { + m_data_msg.msg_ptr.reset(); + } + + this->reset_headers(); + + return ret; + } + + /// Test whether or not the processor is in a fatal error state. + bool get_error() const { + return m_state == FATAL_ERROR; + } + + size_t get_bytes_needed() const { + return m_bytes_needed; + } + + /// Prepare a user data message for writing + /** + * Performs validation, masking, compression, etc. will return an error if + * there was an error, otherwise msg will be ready to be written + * + * TODO: tests + * + * @param in An unprepared message to prepare + * @param out A message to be overwritten with the prepared message + * @return error code + */ + virtual lib::error_code prepare_data_frame(message_ptr in, message_ptr out) + { + if (!in || !out) { + return make_error_code(error::invalid_arguments); + } + + frame::opcode::value op = in->get_opcode(); + + // validate opcode: only regular data frames + if (frame::opcode::is_control(op)) { + return make_error_code(error::invalid_opcode); + } + + std::string& i = in->get_raw_payload(); + std::string& o = out->get_raw_payload(); + + // validate payload utf8 + if (op == frame::opcode::TEXT && !utf8_validator::validate(i)) { + return make_error_code(error::invalid_payload); + } + + frame::masking_key_type key; + bool masked = !base::m_server; + bool compressed = m_permessage_deflate.is_enabled() + && in->get_compressed(); + bool fin = in->get_fin(); + + if (masked) { + // Generate masking key. + key.i = m_rng(); + } else { + key.i = 0; + } + + // prepare payload + if (compressed) { + // compress and store in o after header. + m_permessage_deflate.compress(i,o); + + if (o.size() < 4) { + return make_error_code(error::general); + } + + // Strip trailing 4 0x00 0x00 0xff 0xff bytes before writing to the + // wire + o.resize(o.size()-4); + + // mask in place if necessary + if (masked) { + this->masked_copy(o,o,key); + } + } else { + // no compression, just copy data into the output buffer + o.resize(i.size()); + + // if we are masked, have the masking function write to the output + // buffer directly to avoid another copy. If not masked, copy + // directly without masking. + if (masked) { + this->masked_copy(i,o,key); + } else { + std::copy(i.begin(),i.end(),o.begin()); + } + } + + // generate header + frame::basic_header h(op,o.size(),fin,masked,compressed); + + if (masked) { + frame::extended_header e(o.size(),key.i); + out->set_header(frame::prepare_header(h,e)); + } else { + frame::extended_header e(o.size()); + out->set_header(frame::prepare_header(h,e)); + } + + out->set_prepared(true); + out->set_opcode(op); + + return lib::error_code(); + } + + /// Get URI + lib::error_code prepare_ping(std::string const & in, message_ptr out) const { + return this->prepare_control(frame::opcode::PING,in,out); + } + + lib::error_code prepare_pong(std::string const & in, message_ptr out) const { + return this->prepare_control(frame::opcode::PONG,in,out); + } + + virtual lib::error_code prepare_close(close::status::value code, + std::string const & reason, message_ptr out) const + { + if (close::status::reserved(code)) { + return make_error_code(error::reserved_close_code); + } + + if (close::status::invalid(code) && code != close::status::no_status) { + return make_error_code(error::invalid_close_code); + } + + if (code == close::status::no_status && reason.size() > 0) { + return make_error_code(error::reason_requires_code); + } + + if (reason.size() > frame:: limits::payload_size_basic-2) { + return make_error_code(error::control_too_big); + } + + std::string payload; + + if (code != close::status::no_status) { + close::code_converter val; + val.i = htons(code); + + payload.resize(reason.size()+2); + + payload[0] = val.c[0]; + payload[1] = val.c[1]; + + std::copy(reason.begin(),reason.end(),payload.begin()+2); + } + + return this->prepare_control(frame::opcode::CLOSE,payload,out); + } +protected: + /// Convert a client handshake key into a server response key in place + lib::error_code process_handshake_key(std::string & key) const { + key.append(constants::handshake_guid); + + unsigned char message_digest[20]; + sha1::calc(key.c_str(),key.length(),message_digest); + key = base64_encode(message_digest,20); + + return lib::error_code(); + } + + /// Reads bytes from buf into m_basic_header + size_t copy_basic_header_bytes(uint8_t const * buf, size_t len) { + if (len == 0 || m_bytes_needed == 0) { + return 0; + } + + if (len > 1) { + // have at least two bytes + if (m_bytes_needed == 2) { + m_basic_header.b0 = buf[0]; + m_basic_header.b1 = buf[1]; + m_bytes_needed -= 2; + return 2; + } else { + m_basic_header.b1 = buf[0]; + m_bytes_needed--; + return 1; + } + } else { + // have exactly one byte + if (m_bytes_needed == 2) { + m_basic_header.b0 = buf[0]; + m_bytes_needed--; + return 1; + } else { + m_basic_header.b1 = buf[0]; + m_bytes_needed--; + return 1; + } + } + } + + /// Reads bytes from buf into m_extended_header + size_t copy_extended_header_bytes(uint8_t const * buf, size_t len) { + size_t bytes_to_read = (std::min)(m_bytes_needed,len); + + std::copy(buf,buf+bytes_to_read,m_extended_header.bytes+m_cursor); + m_cursor += bytes_to_read; + m_bytes_needed -= bytes_to_read; + + return bytes_to_read; + } + + /// Reads bytes from buf into message payload + /** + * This function performs unmasking and uncompression, validates the + * decoded bytes, and writes them to the appropriate message buffer. + * + * This member function will use the input buffer as stratch space for its + * work. The raw input bytes will not be preserved. This applies only to the + * bytes actually needed. At most min(m_bytes_needed,len) will be processed. + * + * @param buf Input/working buffer + * @param len Length of buf + * @return Number of bytes processed or zero in case of an error + */ + size_t process_payload_bytes(uint8_t * buf, size_t len, lib::error_code& ec) + { + // unmask if masked + if (frame::get_masked(m_basic_header)) { + m_current_msg->prepared_key = frame::byte_mask_circ( + buf, len, m_current_msg->prepared_key); + // TODO: SIMD masking + } + + std::string & out = m_current_msg->msg_ptr->get_raw_payload(); + size_t offset = out.size(); + + // decompress message if needed. + if (m_permessage_deflate.is_enabled() + && m_current_msg->msg_ptr->get_compressed()) + { + // Decompress current buffer into the message buffer + ec = m_permessage_deflate.decompress(buf,len,out); + if (ec) { + return 0; + } + } else { + // No compression, straight copy + out.append(reinterpret_cast(buf),len); + } + + // validate unmasked, decompressed values + if (m_current_msg->msg_ptr->get_opcode() == frame::opcode::TEXT) { + if (!m_current_msg->validator.decode(out.begin()+offset,out.end())) { + ec = make_error_code(error::invalid_utf8); + return 0; + } + } + + m_bytes_needed -= len; + + return len; + } + + /// Validate an incoming basic header + /** + * Validates an incoming hybi13 basic header. + * + * @param h The basic header to validate + * @param is_server Whether or not the endpoint that received this frame + * is a server. + * @param new_msg Whether or not this is the first frame of the message + * @return 0 on success or a non-zero error code on failure + */ + lib::error_code validate_incoming_basic_header(frame::basic_header const & h, + bool is_server, bool new_msg) const + { + frame::opcode::value op = frame::get_opcode(h); + + // Check control frame size limit + if (frame::opcode::is_control(op) && + frame::get_basic_size(h) > frame::limits::payload_size_basic) + { + return make_error_code(error::control_too_big); + } + + // Check that RSV bits are clear + // The only RSV bits allowed are rsv1 if the permessage_compress + // extension is enabled for this connection and the message is not + // a control message. + // + // TODO: unit tests for this + if (frame::get_rsv1(h) && (!m_permessage_deflate.is_enabled() + || frame::opcode::is_control(op))) + { + return make_error_code(error::invalid_rsv_bit); + } + + if (frame::get_rsv2(h) || frame::get_rsv3(h)) { + return make_error_code(error::invalid_rsv_bit); + } + + // Check for reserved opcodes + if (frame::opcode::reserved(op)) { + return make_error_code(error::invalid_opcode); + } + + // Check for invalid opcodes + // TODO: unit tests for this? + if (frame::opcode::invalid(op)) { + return make_error_code(error::invalid_opcode); + } + + // Check for fragmented control message + if (frame::opcode::is_control(op) && !frame::get_fin(h)) { + return make_error_code(error::fragmented_control); + } + + // Check for continuation without an active message + if (new_msg && op == frame::opcode::CONTINUATION) { + return make_error_code(error::invalid_continuation); + } + + // Check for new data frame when expecting continuation + if (!new_msg && !frame::opcode::is_control(op) && + op != frame::opcode::CONTINUATION) + { + return make_error_code(error::invalid_continuation); + } + + // Servers should reject any unmasked frames from clients. + // Clients should reject any masked frames from servers. + if (is_server && !frame::get_masked(h)) { + return make_error_code(error::masking_required); + } else if (!is_server && frame::get_masked(h)) { + return make_error_code(error::masking_forbidden); + } + + return lib::error_code(); + } + + /// Validate an incoming extended header + /** + * Validates an incoming hybi13 full header. + * + * @todo unit test for the >32 bit frames on 32 bit systems case + * + * @param h The basic header to validate + * @param e The extended header to validate + * @return An error_code, non-zero values indicate why the validation + * failed + */ + lib::error_code validate_incoming_extended_header(frame::basic_header h, + frame::extended_header e) const + { + uint8_t basic_size = frame::get_basic_size(h); + uint64_t payload_size = frame::get_payload_size(h,e); + + // Check for non-minimally encoded payloads + if (basic_size == frame::payload_size_code_16bit && + payload_size <= frame::limits::payload_size_basic) + { + return make_error_code(error::non_minimal_encoding); + } + + if (basic_size == frame::payload_size_code_64bit && + payload_size <= frame::limits::payload_size_extended) + { + return make_error_code(error::non_minimal_encoding); + } + + // Check for >32bit frames on 32 bit systems + if (sizeof(size_t) == 4 && (payload_size >> 32)) { + return make_error_code(error::requires_64bit); + } + + return lib::error_code(); + } + + /// Copy and mask/unmask in one operation + /** + * Reads input from one string and writes unmasked output to another. + * + * @param [in] i The input string. + * @param [out] o The output string. + * @param [in] key The masking key to use for masking/unmasking + */ + void masked_copy (std::string const & i, std::string & o, + frame::masking_key_type key) const + { + frame::byte_mask(i.begin(),i.end(),o.begin(),key); + // TODO: SIMD masking + } + + /// Generic prepare control frame with opcode and payload. + /** + * Internal control frame building method. Handles validation, masking, etc + * + * @param op The control opcode to use + * @param payload The payload to use + * @param out The message buffer to store the prepared frame in + * @return Status code, zero on success, non-zero on error + */ + lib::error_code prepare_control(frame::opcode::value op, + std::string const & payload, message_ptr out) const + { + if (!out) { + return make_error_code(error::invalid_arguments); + } + + if (!frame::opcode::is_control(op)) { + return make_error_code(error::invalid_opcode); + } + + if (payload.size() > frame::limits::payload_size_basic) { + return make_error_code(error::control_too_big); + } + + frame::masking_key_type key; + bool masked = !base::m_server; + + frame::basic_header h(op,payload.size(),true,masked); + + std::string & o = out->get_raw_payload(); + o.resize(payload.size()); + + if (masked) { + // Generate masking key. + key.i = m_rng(); + + frame::extended_header e(payload.size(),key.i); + out->set_header(frame::prepare_header(h,e)); + this->masked_copy(payload,o,key); + } else { + frame::extended_header e(payload.size()); + out->set_header(frame::prepare_header(h,e)); + std::copy(payload.begin(),payload.end(),o.begin()); + } + + out->set_opcode(op); + out->set_prepared(true); + + return lib::error_code(); + } + + enum state { + HEADER_BASIC = 0, + HEADER_EXTENDED = 1, + EXTENSION = 2, + APPLICATION = 3, + READY = 4, + FATAL_ERROR = 5 + }; + + /// This data structure holds data related to processing a message, such as + /// the buffer it is being written to, its masking key, its UTF8 validation + /// state, and sometimes its compression state. + struct msg_metadata { + msg_metadata() {} + msg_metadata(message_ptr m, size_t p) : msg_ptr(m),prepared_key(p) {} + msg_metadata(message_ptr m, frame::masking_key_type p) + : msg_ptr(m) + , prepared_key(prepare_masking_key(p)) {} + + message_ptr msg_ptr; // pointer to the message data buffer + size_t prepared_key; // prepared masking key + utf8_validator::validator validator; // utf8 validation state + }; + + // Basic header of the frame being read + frame::basic_header m_basic_header; + + // Pointer to a manager that can create message buffers for us. + msg_manager_ptr m_msg_manager; + + // Number of bytes needed to complete the current operation + size_t m_bytes_needed; + + // Number of extended header bytes read + size_t m_cursor; + + // Metadata for the current data msg + msg_metadata m_data_msg; + // Metadata for the current control msg + msg_metadata m_control_msg; + + // Pointer to the metadata associated with the frame being read + msg_metadata * m_current_msg; + + // Extended header of current frame + frame::extended_header m_extended_header; + + rng_type & m_rng; + + // Overall state of the processor + state m_state; + + // Extensions + permessage_deflate_type m_permessage_deflate; +}; + +} // namespace processor +} // namespace websocketpp + +#endif //WEBSOCKETPP_PROCESSOR_HYBI13_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/processors/processor.hpp b/thirdparty/websocketpp/include/websocketpp/processors/processor.hpp new file mode 100644 index 0000000..ee782a5 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/processors/processor.hpp @@ -0,0 +1,407 @@ +/* + * 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_PROCESSOR_HPP +#define WEBSOCKETPP_PROCESSOR_HPP + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace websocketpp { +/// Processors encapsulate the protocol rules specific to each WebSocket version +/** + * The processors namespace includes a number of free functions that operate on + * various WebSocket related data structures and perform processing that is not + * related to specific versions of the protocol. + * + * It also includes the abstract interface for the protocol specific processing + * engines. These engines wrap all of the logic necessary for parsing and + * validating WebSocket handshakes and messages of specific protocol version + * and set of allowed extensions. + * + * An instance of a processor represents the state of a single WebSocket + * connection of the associated version. One processor instance is needed per + * logical WebSocket connection. + */ +namespace processor { + +/// Determine whether or not a generic HTTP request is a WebSocket handshake +/** + * @param r The HTTP request to read. + * + * @return True if the request is a WebSocket handshake, false otherwise + */ +template +bool is_websocket_handshake(request_type& r) { + using utility::ci_find_substr; + + std::string const & upgrade_header = r.get_header("Upgrade"); + + if (ci_find_substr(upgrade_header, constants::upgrade_token, + sizeof(constants::upgrade_token)-1) == upgrade_header.end()) + { + return false; + } + + std::string const & con_header = r.get_header("Connection"); + + if (ci_find_substr(con_header, constants::connection_token, + sizeof(constants::connection_token)-1) == con_header.end()) + { + return false; + } + + return true; +} + +/// Extract the version from a WebSocket handshake request +/** + * A blank version header indicates a spec before versions were introduced. + * The only such versions in shipping products are Hixie Draft 75 and Hixie + * Draft 76. Draft 75 is present in Chrome 4-5 and Safari 5.0.0, Draft 76 (also + * known as hybi 00 is present in Chrome 6-13 and Safari 5.0.1+. As + * differentiating between these two sets of browsers is very difficult and + * Safari 5.0.1+ accounts for the vast majority of cases in the wild this + * function assumes that all handshakes without a valid version header are + * Hybi 00. + * + * @param r The WebSocket handshake request to read. + * + * @return The WebSocket handshake version or -1 if there was an extraction + * error. + */ +template +int get_websocket_version(request_type& r) { + if (!r.ready()) { + return -2; + } + + if (r.get_header("Sec-WebSocket-Version").empty()) { + return 0; + } + + int version; + std::istringstream ss(r.get_header("Sec-WebSocket-Version")); + + if ((ss >> version).fail()) { + return -1; + } + + return version; +} + +/// Extract a URI ptr from the host header of the request +/** + * @param request The request to extract the Host header from. + * + * @param scheme The scheme under which this request was received (ws, wss, + * http, https, etc) + * + * @return A uri_pointer that encodes the value of the host header. + */ +template +uri_ptr get_uri_from_host(request_type & request, std::string scheme) { + std::string h = request.get_header("Host"); + + size_t last_colon = h.rfind(":"); + size_t last_sbrace = h.rfind("]"); + + // no : = hostname with no port + // last : before ] = ipv6 literal with no port + // : with no ] = hostname with port + // : after ] = ipv6 literal with port + if (last_colon == std::string::npos || + (last_sbrace != std::string::npos && last_sbrace > last_colon)) + { + return lib::make_shared(scheme, h, request.get_uri()); + } else { + return lib::make_shared(scheme, + h.substr(0,last_colon), + h.substr(last_colon+1), + request.get_uri()); + } +} + +/// WebSocket protocol processor abstract base class +template +class processor { +public: + typedef processor type; + typedef typename config::request_type request_type; + typedef typename config::response_type response_type; + typedef typename config::message_type::ptr message_ptr; + typedef std::pair err_str_pair; + + explicit processor(bool secure, bool p_is_server) + : m_secure(secure) + , m_server(p_is_server) + , m_max_message_size(config::max_message_size) + {} + + virtual ~processor() {} + + /// Get the protocol version of this processor + virtual int get_version() const = 0; + + /// Get maximum message size + /** + * Get maximum message size. Maximum message size determines the point at which the + * processor will fail a connection with the message_too_big protocol error. + * + * The default is retrieved from the max_message_size value from the template config + * + * @since 0.3.0 + */ + size_t get_max_message_size() const { + return m_max_message_size; + } + + /// Set maximum message size + /** + * Set maximum message size. Maximum message size determines the point at which the + * processor will fail a connection with the message_too_big protocol error. + * + * The default is retrieved from the max_message_size value from the template config + * + * @since 0.3.0 + * + * @param new_value The value to set as the maximum message size. + */ + void set_max_message_size(size_t new_value) { + m_max_message_size = new_value; + } + + /// Returns whether or not the permessage_compress extension is implemented + /** + * Compile time flag that indicates whether this processor has implemented + * the permessage_compress extension. By default this is false. + */ + virtual bool has_permessage_compress() const { + return false; + } + + /// Initializes extensions based on the Sec-WebSocket-Extensions header + /** + * Reads the Sec-WebSocket-Extensions header and determines if any of the + * requested extensions are supported by this processor. If they are their + * settings data is initialized and an extension string to send to the + * is returned. + * + * @param request The request or response headers to look at. + */ + virtual err_str_pair negotiate_extensions(request_type const &) { + return err_str_pair(); + } + + /// Initializes extensions based on the Sec-WebSocket-Extensions header + /** + * Reads the Sec-WebSocket-Extensions header and determines if any of the + * requested extensions were accepted by the server. If they are their + * settings data is initialized. If they are not a list of required + * extensions (if any) is returned. This list may be sent back to the server + * as a part of the 1010/Extension required close code. + * + * @param response The request or response headers to look at. + */ + virtual err_str_pair negotiate_extensions(response_type const &) { + return err_str_pair(); + } + + /// validate a WebSocket handshake request for this version + /** + * @param request The WebSocket handshake request to validate. + * is_websocket_handshake(request) must be true and + * get_websocket_version(request) must equal this->get_version(). + * + * @return A status code, 0 on success, non-zero for specific sorts of + * failure + */ + virtual lib::error_code validate_handshake(request_type const & request) const = 0; + + /// Calculate the appropriate response for this websocket request + /** + * @param req The request to process + * + * @param subprotocol The subprotocol in use + * + * @param res The response to store the processed response in + * + * @return An error code, 0 on success, non-zero for other errors + */ + virtual lib::error_code process_handshake(request_type const & req, + std::string const & subprotocol, response_type& res) const = 0; + + /// Fill in an HTTP request for an outgoing connection handshake + /** + * @param req The request to process. + * + * @return An error code, 0 on success, non-zero for other errors + */ + virtual lib::error_code client_handshake_request(request_type & req, + uri_ptr uri, std::vector const & subprotocols) const = 0; + + /// Validate the server's response to an outgoing handshake request + /** + * @param req The original request sent + * @param res The reponse to generate + * @return An error code, 0 on success, non-zero for other errors + */ + virtual lib::error_code validate_server_handshake_response(request_type + const & req, response_type & res) const = 0; + + /// Given a completed response, get the raw bytes to put on the wire + virtual std::string get_raw(response_type const & request) const = 0; + + /// Return the value of the header containing the CORS origin. + virtual std::string const & get_origin(request_type const & request) const = 0; + + /// Extracts requested subprotocols from a handshake request + /** + * Extracts a list of all subprotocols that the client has requested in the + * given opening handshake request. + * + * @param [in] req The request to extract from + * @param [out] subprotocol_list A reference to a vector of strings to store + * the results in. + */ + virtual lib::error_code extract_subprotocols(const request_type & req, + std::vector & subprotocol_list) = 0; + + /// Extracts client uri from a handshake request + virtual uri_ptr get_uri(request_type const & request) const = 0; + + /// process new websocket connection bytes + /** + * WebSocket connections are a continous stream of bytes that must be + * interpreted by a protocol processor into discrete frames. + * + * @param buf Buffer from which bytes should be read. + * @param len Length of buffer + * @param ec Reference to an error code to return any errors in + * @return Number of bytes processed + */ + virtual size_t consume(uint8_t *buf, size_t len, lib::error_code & ec) = 0; + + /// Checks if there is a message ready + /** + * Checks if the most recent consume operation processed enough bytes to + * complete a new WebSocket message. The message can be retrieved by calling + * get_message() which will reset the internal state to not-ready and allow + * consume to read more bytes. + * + * @return Whether or not a message is ready. + */ + virtual bool ready() const = 0; + + /// Retrieves the most recently processed message + /** + * Retrieves a shared pointer to the recently completed message if there is + * one. If ready() returns true then there is a message available. + * Retrieving the message with get_message will reset the state of ready. + * As such, each new message may be retrieved only once. Calling get_message + * when there is no message available will result in a null pointer being + * returned. + * + * @return A pointer to the most recently processed message or a null shared + * pointer. + */ + virtual message_ptr get_message() = 0; + + /// Tests whether the processor is in a fatal error state + virtual bool get_error() const = 0; + + /// Retrieves the number of bytes presently needed by the processor + /// This value may be used as a hint to the transport layer as to how many + /// bytes to wait for before running consume again. + virtual size_t get_bytes_needed() const { + return 1; + } + + /// Prepare a data message for writing + /** + * Performs validation, masking, compression, etc. will return an error if + * there was an error, otherwise msg will be ready to be written + */ + virtual lib::error_code prepare_data_frame(message_ptr in, message_ptr out) = 0; + + /// Prepare a ping frame + /** + * Ping preparation is entirely state free. There is no payload validation + * other than length. Payload need not be UTF-8. + * + * @param in The string to use for the ping payload + * @param out The message buffer to prepare the ping in. + * @return Status code, zero on success, non-zero on failure + */ + virtual lib::error_code prepare_ping(std::string const & in, message_ptr out) const + = 0; + + /// Prepare a pong frame + /** + * Pong preparation is entirely state free. There is no payload validation + * other than length. Payload need not be UTF-8. + * + * @param in The string to use for the pong payload + * @param out The message buffer to prepare the pong in. + * @return Status code, zero on success, non-zero on failure + */ + virtual lib::error_code prepare_pong(std::string const & in, message_ptr out) const + = 0; + + /// Prepare a close frame + /** + * Close preparation is entirely state free. The code and reason are both + * subject to validation. Reason must be valid UTF-8. Code must be a valid + * un-reserved WebSocket close code. Use close::status::no_status to + * indicate no code. If no code is supplied a reason may not be specified. + * + * @param code The close code to send + * @param reason The reason string to send + * @param out The message buffer to prepare the fame in + * @return Status code, zero on success, non-zero on failure + */ + virtual lib::error_code prepare_close(close::status::value code, + std::string const & reason, message_ptr out) const = 0; +protected: + bool const m_secure; + bool const m_server; + size_t m_max_message_size; +}; + +} // namespace processor +} // namespace websocketpp + +#endif //WEBSOCKETPP_PROCESSOR_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/random/none.hpp b/thirdparty/websocketpp/include/websocketpp/random/none.hpp new file mode 100644 index 0000000..ecb2d0f --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/random/none.hpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2014, 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_RANDOM_NONE_HPP +#define WEBSOCKETPP_RANDOM_NONE_HPP + +namespace websocketpp { +/// Random number generation policies +namespace random { +/// Stub RNG policy that always returns 0 +namespace none { + +/// Thread safe stub "random" integer generator. +/** + * This template class provides a random integer stub. The interface mimics the + * WebSocket++ RNG generator classes but the generater function always returns + * zero. This can be used to stub out the RNG for unit and performance testing. + * + * Call operator() to generate the next number + */ +template +class int_generator { + public: + int_generator() {} + + /// advances the engine's state and returns the generated value + int_type operator()() { + return 0; + } +}; + +} // namespace none +} // namespace random +} // namespace websocketpp + +#endif //WEBSOCKETPP_RANDOM_NONE_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/random/random_device.hpp b/thirdparty/websocketpp/include/websocketpp/random/random_device.hpp new file mode 100644 index 0000000..65bbe60 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/random/random_device.hpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2014, 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_RANDOM_RANDOM_DEVICE_HPP +#define WEBSOCKETPP_RANDOM_RANDOM_DEVICE_HPP + +#include + +namespace websocketpp { +namespace random { +/// RNG policy based on std::random_device or boost::random_device +namespace random_device { + +/// Thread safe non-deterministic random integer generator. +/** + * This template class provides thread safe non-deterministic random integer + * generation. Numbers are produced in a uniformly distributed range from the + * smallest to largest value that int_type can store. + * + * Thread-safety is provided via locking based on the concurrency template + * parameter. + * + * Non-deterministic RNG is provided via websocketpp::lib which uses either + * C++11 or Boost 1.47+'s random_device class. + * + * Call operator() to generate the next number + */ +template +class int_generator { + public: + typedef typename concurrency::scoped_lock_type scoped_lock_type; + typedef typename concurrency::mutex_type mutex_type; + + /// constructor + //mac TODO: figure out if signed types present a range problem + int_generator() {} + + /// advances the engine's state and returns the generated value + int_type operator()() { + scoped_lock_type guard(m_lock); + return m_dis(m_rng); + } + private: + + + lib::random_device m_rng; + lib::uniform_int_distribution m_dis; + + mutex_type m_lock; +}; + +} // namespace random_device +} // namespace random +} // namespace websocketpp + +#endif //WEBSOCKETPP_RANDOM_RANDOM_DEVICE_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/roles/client_endpoint.hpp b/thirdparty/websocketpp/include/websocketpp/roles/client_endpoint.hpp new file mode 100644 index 0000000..734678d --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/roles/client_endpoint.hpp @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2014, 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_CLIENT_ENDPOINT_HPP +#define WEBSOCKETPP_CLIENT_ENDPOINT_HPP + +#include +#include + +#include + +#include + +#include + +namespace websocketpp { + +/// Client endpoint role based on the given config +/** + * + */ +template +class client : public endpoint,config> { +public: + /// Type of this endpoint + typedef client type; + + /// Type of the endpoint concurrency component + typedef typename config::concurrency_type concurrency_type; + /// Type of the endpoint transport component + typedef typename config::transport_type transport_type; + + /// Type of the connections this server will create + typedef connection connection_type; + /// Type of a shared pointer to the connections this server will create + typedef typename connection_type::ptr connection_ptr; + + /// Type of the connection transport component + typedef typename transport_type::transport_con_type transport_con_type; + /// Type of a shared pointer to the connection transport component + typedef typename transport_con_type::ptr transport_con_ptr; + + /// Type of the endpoint component of this server + typedef endpoint endpoint_type; + + friend class connection; + + explicit client() : endpoint_type(false) + { + endpoint_type::m_alog->write(log::alevel::devel, "client constructor"); + } + + /// Get a new connection + /** + * Creates and returns a pointer to a new connection to the given URI + * suitable for passing to connect(connection_ptr). This method allows + * applying connection specific settings before performing the opening + * handshake. + * + * @param [in] location URI to open the connection to as a uri_ptr + * @param [out] ec An status code indicating failure reasons, if any + * + * @return A connection_ptr to the new connection + */ + connection_ptr get_connection(uri_ptr location, lib::error_code & ec) { + if (location->get_secure() && !transport_type::is_secure()) { + ec = error::make_error_code(error::endpoint_not_secure); + return connection_ptr(); + } + + connection_ptr con = endpoint_type::create_connection(); + + if (!con) { + ec = error::make_error_code(error::con_creation_failed); + return con; + } + + con->set_uri(location); + + ec = lib::error_code(); + return con; + } + + /// Get a new connection (string version) + /** + * Creates and returns a pointer to a new connection to the given URI + * suitable for passing to connect(connection_ptr). This overload allows + * default construction of the uri_ptr from a standard string. + * + * @param [in] u URI to open the connection to as a string + * @param [out] ec An status code indicating failure reasons, if any + * + * @return A connection_ptr to the new connection + */ + connection_ptr get_connection(std::string const & u, lib::error_code & ec) { + uri_ptr location = lib::make_shared(u); + + if (!location->get_valid()) { + ec = error::make_error_code(error::invalid_uri); + return connection_ptr(); + } + + return get_connection(location, ec); + } + + /// Begin the connection process for the given connection + /** + * Initiates the opening connection handshake for connection con. Exact + * behavior depends on the underlying transport policy. + * + * @param con The connection to connect + * + * @return The pointer to the connection originally passed in. + */ + connection_ptr connect(connection_ptr con) { + // Ask transport to perform a connection + transport_type::async_connect( + lib::static_pointer_cast(con), + con->get_uri(), + lib::bind( + &type::handle_connect, + this, + con, + lib::placeholders::_1 + ) + ); + + return con; + } +private: + // handle_connect + void handle_connect(connection_ptr con, lib::error_code const & ec) { + if (ec) { + con->terminate(ec); + + endpoint_type::m_elog->write(log::elevel::rerror, + "handle_connect error: "+ec.message()); + } else { + endpoint_type::m_alog->write(log::alevel::connect, + "Successful connection"); + + con->start(); + } + } +}; + +} // namespace websocketpp + +#endif //WEBSOCKETPP_CLIENT_ENDPOINT_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/roles/server_endpoint.hpp b/thirdparty/websocketpp/include/websocketpp/roles/server_endpoint.hpp new file mode 100644 index 0000000..9a71cde --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/roles/server_endpoint.hpp @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2014, 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_SERVER_ENDPOINT_HPP +#define WEBSOCKETPP_SERVER_ENDPOINT_HPP + +#include + +#include + +#include + +namespace websocketpp { + +/// Server endpoint role based on the given config +/** + * + */ +template +class server : public endpoint,config> { +public: + /// Type of this endpoint + typedef server type; + + /// Type of the endpoint concurrency component + typedef typename config::concurrency_type concurrency_type; + /// Type of the endpoint transport component + typedef typename config::transport_type transport_type; + + /// Type of the connections this server will create + typedef connection connection_type; + /// Type of a shared pointer to the connections this server will create + typedef typename connection_type::ptr connection_ptr; + + /// Type of the connection transport component + typedef typename transport_type::transport_con_type transport_con_type; + /// Type of a shared pointer to the connection transport component + typedef typename transport_con_type::ptr transport_con_ptr; + + /// Type of the endpoint component of this server + typedef endpoint endpoint_type; + + friend class connection; + + explicit server() : endpoint_type(true) + { + endpoint_type::m_alog->write(log::alevel::devel, "server constructor"); + } + + /// Destructor + ~server() {} + +#ifdef _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_ + // no copy constructor because endpoints are not copyable + server(server &) = delete; + + // no copy assignment operator because endpoints are not copyable + server & operator=(server const &) = delete; +#endif // _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_ + +#ifdef _WEBSOCKETPP_MOVE_SEMANTICS_ + /// Move constructor + server(server && o) : endpoint,config>(std::move(o)) {} + +#ifdef _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_ + // no move assignment operator because of const member variables + server & operator=(server &&) = delete; +#endif // _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_ + +#endif // _WEBSOCKETPP_MOVE_SEMANTICS_ + + /// Create and initialize a new connection + /** + * The connection will be initialized and ready to begin. Call its start() + * method to begin the processing loop. + * + * Note: The connection must either be started or terminated using + * connection::terminate in order to avoid memory leaks. + * + * @return A pointer to the new connection. + */ + connection_ptr get_connection() { + return endpoint_type::create_connection(); + } + + /// Starts the server's async connection acceptance loop (exception free) + /** + * Initiates the server connection acceptance loop. Must be called after + * listen. This method will have no effect until the underlying io_service + * starts running. It may be called after the io_service is already running. + * + * Refer to documentation for the transport policy you are using for + * instructions on how to stop this acceptance loop. + * + * @param [out] ec A status code indicating an error, if any. + */ + void start_accept(lib::error_code & ec) { + if (!transport_type::is_listening()) { + ec = error::make_error_code(error::async_accept_not_listening); + return; + } + + ec = lib::error_code(); + connection_ptr con = get_connection(); + + if (!con) { + ec = error::make_error_code(error::con_creation_failed); + return; + } + + transport_type::async_accept( + lib::static_pointer_cast(con), + lib::bind(&type::handle_accept,this,con,lib::placeholders::_1), + ec + ); + + if (ec && con) { + // If the connection was constructed but the accept failed, + // terminate the connection to prevent memory leaks + con->terminate(lib::error_code()); + } + } + + /// Starts the server's async connection acceptance loop + /** + * Initiates the server connection acceptance loop. Must be called after + * listen. This method will have no effect until the underlying io_service + * starts running. It may be called after the io_service is already running. + * + * Refer to documentation for the transport policy you are using for + * instructions on how to stop this acceptance loop. + */ + void start_accept() { + lib::error_code ec; + start_accept(ec); + if (ec) { + throw exception(ec); + } + } + + /// Handler callback for start_accept + void handle_accept(connection_ptr con, lib::error_code const & ec) { + if (ec) { + con->terminate(ec); + + if (ec == error::operation_canceled) { + endpoint_type::m_elog->write(log::elevel::info, + "handle_accept error: "+ec.message()); + } else { + endpoint_type::m_elog->write(log::elevel::rerror, + "handle_accept error: "+ec.message()); + } + } else { + con->start(); + } + + lib::error_code start_ec; + start_accept(start_ec); + if (start_ec == error::async_accept_not_listening) { + endpoint_type::m_elog->write(log::elevel::info, + "Stopping acceptance of new connections because the underlying transport is no longer listening."); + } else if (start_ec) { + endpoint_type::m_elog->write(log::elevel::rerror, + "Restarting async_accept loop failed: "+ec.message()); + } + } +}; + +} // namespace websocketpp + +#endif //WEBSOCKETPP_SERVER_ENDPOINT_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/server.hpp b/thirdparty/websocketpp/include/websocketpp/server.hpp new file mode 100644 index 0000000..ce46eaa --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/server.hpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2014, 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_SERVER_HPP +#define WEBSOCKETPP_SERVER_HPP + +#include + +#endif //WEBSOCKETPP_SERVER_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/sha1/sha1.hpp b/thirdparty/websocketpp/include/websocketpp/sha1/sha1.hpp new file mode 100644 index 0000000..43a8433 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/sha1/sha1.hpp @@ -0,0 +1,189 @@ +/* +***** +sha1.hpp is a repackaging of the sha1.cpp and sha1.h files from the smallsha1 +library (http://code.google.com/p/smallsha1/) into a single header suitable for +use as a header only library. This conversion was done by Peter Thorson +(webmaster@zaphoyd.com) in 2013. All modifications to the code are redistributed +under the same license as the original, which is listed below. +***** + + Copyright (c) 2011, Micael Hildenborg + 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 Micael Hildenborg 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 Micael Hildenborg ''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 Micael Hildenborg 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 SHA1_DEFINED +#define SHA1_DEFINED + +namespace websocketpp { +namespace sha1 { + +namespace { // local + +// Rotate an integer value to left. +inline unsigned int rol(unsigned int value, unsigned int steps) { + return ((value << steps) | (value >> (32 - steps))); +} + +// Sets the first 16 integers in the buffert to zero. +// Used for clearing the W buffert. +inline void clearWBuffert(unsigned int * buffert) +{ + for (int pos = 16; --pos >= 0;) + { + buffert[pos] = 0; + } +} + +inline void innerHash(unsigned int * result, unsigned int * w) +{ + unsigned int a = result[0]; + unsigned int b = result[1]; + unsigned int c = result[2]; + unsigned int d = result[3]; + unsigned int e = result[4]; + + int round = 0; + + #define sha1macro(func,val) \ + { \ + const unsigned int t = rol(a, 5) + (func) + e + val + w[round]; \ + e = d; \ + d = c; \ + c = rol(b, 30); \ + b = a; \ + a = t; \ + } + + while (round < 16) + { + sha1macro((b & c) | (~b & d), 0x5a827999) + ++round; + } + while (round < 20) + { + w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1); + sha1macro((b & c) | (~b & d), 0x5a827999) + ++round; + } + while (round < 40) + { + w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1); + sha1macro(b ^ c ^ d, 0x6ed9eba1) + ++round; + } + while (round < 60) + { + w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1); + sha1macro((b & c) | (b & d) | (c & d), 0x8f1bbcdc) + ++round; + } + while (round < 80) + { + w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1); + sha1macro(b ^ c ^ d, 0xca62c1d6) + ++round; + } + + #undef sha1macro + + result[0] += a; + result[1] += b; + result[2] += c; + result[3] += d; + result[4] += e; +} + +} // namespace + +/// Calculate a SHA1 hash +/** + * @param src points to any kind of data to be hashed. + * @param bytelength the number of bytes to hash from the src pointer. + * @param hash should point to a buffer of at least 20 bytes of size for storing + * the sha1 result in. + */ +inline void calc(void const * src, size_t bytelength, unsigned char * hash) { + // Init the result array. + unsigned int result[5] = { 0x67452301, 0xefcdab89, 0x98badcfe, + 0x10325476, 0xc3d2e1f0 }; + + // Cast the void src pointer to be the byte array we can work with. + unsigned char const * sarray = (unsigned char const *) src; + + // The reusable round buffer + unsigned int w[80]; + + // Loop through all complete 64byte blocks. + + size_t endCurrentBlock; + size_t currentBlock = 0; + + if (bytelength >= 64) { + size_t const endOfFullBlocks = bytelength - 64; + + while (currentBlock <= endOfFullBlocks) { + endCurrentBlock = currentBlock + 64; + + // Init the round buffer with the 64 byte block data. + for (int roundPos = 0; currentBlock < endCurrentBlock; currentBlock += 4) + { + // This line will swap endian on big endian and keep endian on + // little endian. + w[roundPos++] = (unsigned int) sarray[currentBlock + 3] + | (((unsigned int) sarray[currentBlock + 2]) << 8) + | (((unsigned int) sarray[currentBlock + 1]) << 16) + | (((unsigned int) sarray[currentBlock]) << 24); + } + innerHash(result, w); + } + } + + // Handle the last and not full 64 byte block if existing. + endCurrentBlock = bytelength - currentBlock; + clearWBuffert(w); + size_t lastBlockBytes = 0; + for (;lastBlockBytes < endCurrentBlock; ++lastBlockBytes) { + w[lastBlockBytes >> 2] |= (unsigned int) sarray[lastBlockBytes + currentBlock] << ((3 - (lastBlockBytes & 3)) << 3); + } + + w[lastBlockBytes >> 2] |= 0x80 << ((3 - (lastBlockBytes & 3)) << 3); + if (endCurrentBlock >= 56) { + innerHash(result, w); + clearWBuffert(w); + } + w[15] = bytelength << 3; + innerHash(result, w); + + // Store hash in result pointer, and make sure we get in in the correct + // order on both endian models. + for (int hashByte = 20; --hashByte >= 0;) { + hash[hashByte] = (result[hashByte >> 2] >> (((3 - hashByte) & 0x3) << 3)) & 0xff; + } +} + +} // namespace sha1 +} // namespace websocketpp + +#endif // SHA1_DEFINED diff --git a/thirdparty/websocketpp/include/websocketpp/transport/asio/base.hpp b/thirdparty/websocketpp/include/websocketpp/transport/asio/base.hpp new file mode 100644 index 0000000..d4e5b5d --- /dev/null +++ b/thirdparty/websocketpp/include/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 +#include +#include +#include +#include + +#include + +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(&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::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 +class custom_alloc_handler { +public: + custom_alloc_handler(handler_allocator& a, Handler h) + : allocator_(a), + handler_(h) + {} + + template + void operator()(Arg1 arg1) { + handler_(arg1); + } + + template + void operator()(Arg1 arg1, Arg2 arg2) { + handler_(arg1, arg2); + } + + friend void* asio_handler_allocate(std::size_t size, + custom_alloc_handler * this_handler) + { + return this_handler->allocator_.allocate(size); + } + + friend void asio_handler_deallocate(void* pointer, std::size_t /*size*/, + custom_alloc_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 +inline custom_alloc_handler make_custom_alloc_handler( + handler_allocator & a, Handler h) +{ + return custom_alloc_handler(a, h); +} + + + + + + + +// Forward declaration of class endpoint so that it can be friended/referenced +// before being included. +template +class endpoint; + +typedef lib::function async_read_handler; + +typedef lib::function async_write_handler; + +typedef lib::function 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(e), get_category()); +} + +} // namespace error +} // namespace asio +} // namespace transport +} // namespace websocketpp + +_WEBSOCKETPP_ERROR_CODE_ENUM_NS_START_ +template<> struct is_error_code_enum +{ + static bool const value = true; +}; +_WEBSOCKETPP_ERROR_CODE_ENUM_NS_END_ +#endif // WEBSOCKETPP_TRANSPORT_ASIO_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/transport/asio/connection.hpp b/thirdparty/websocketpp/include/websocketpp/transport/asio/connection.hpp new file mode 100644 index 0000000..e25157e --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/transport/asio/connection.hpp @@ -0,0 +1,1197 @@ +/* + * 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 + +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace websocketpp { +namespace transport { +namespace asio { + +typedef lib::function 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 +class connection : public config::socket_type::socket_con_type { +public: + /// Type of this connection transport component + typedef connection type; + /// Type of a shared pointer to this connection transport component + typedef lib::shared_ptr 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 strand_ptr; + /// Type of a pointer to the Asio timer class + typedef lib::shared_ptr 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; + + // generate and manage our own io_service + explicit connection(bool is_server, const lib::shared_ptr & alog, const lib::shared_ptr & 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(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(); + 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( + new lib::asio::steady_timer( + *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.reset(new lib::asio::io_service::strand(*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 const & bufs, write_handler handler) { + std::vector::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; + + // 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 + 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; + lib::shared_ptr m_alog; + lib::shared_ptr 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 m_proxy_data; + + // transport resources + io_service_ptr m_io_service; + strand_ptr m_strand; + connection_hdl m_connection_hdl; + + std::vector 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/thirdparty/websocketpp/include/websocketpp/transport/asio/endpoint.hpp b/thirdparty/websocketpp/include/websocketpp/transport/asio/endpoint.hpp new file mode 100644 index 0000000..bb6cbd9 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/transport/asio/endpoint.hpp @@ -0,0 +1,1182 @@ +/* + * 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 +#include +#include + +#include +#include + +#include +#include + +#include +#include + +namespace websocketpp { +namespace transport { +namespace asio { + +/// Asio based endpoint transport component +/** + * transport::asio::endpoint implements an endpoint transport component using + * Asio. + */ +template +class endpoint : public config::socket_type { +public: + /// Type of this endpoint transport component + typedef endpoint 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 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 acceptor_ptr; + /// Type of a shared pointer to the resolver being used + typedef lib::shared_ptr resolver_ptr; + /// Type of timer handle + typedef lib::shared_ptr timer_ptr; + /// Type of a shared pointer to an io_service work object + typedef lib::shared_ptr work_ptr; + + /// Type of socket pre-bind handler + typedef lib::function tcp_pre_bind_handler; + + // generate and manage our own io_service + explicit endpoint() + : m_io_service(NULL) + , m_external_io_service(false) + , m_listen_backlog(lib::asio::socket_base::max_connections) + , 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.reset(new lib::asio::ip::tcp::acceptor(*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 service(new lib::asio::io_service()); +#else + lib::auto_ptr 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 service(new lib::asio::io_service()); +#else + lib::auto_ptr 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 bind handler + /** + * The tcp pre bind handler is called after the listen acceptor has + * been created but before the socket bind is performed. + * + * @since 0.8.0 + * + * @param h The handler to call on tcp pre bind init. + */ + void set_tcp_pre_bind_handler(tcp_pre_bind_handler h) { + m_tcp_pre_bind_handler = h; + } + + /// 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. + * + * The default value is specified as *::asio::socket_base::max_connections + * which uses the operating system defined maximum queue length. Your OS + * may restrict or silently lower this value. A value of zero may cause + * all connections to be rejected. + * + * @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. There + * may be security consequences to enabling this option. + * + * New values affect future calls to listen only so set this value prior to + * calling listen. + * + * 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) {ec = clean_up_listen_after_error(bec);return;} + + m_acceptor->set_option(lib::asio::socket_base::reuse_address(m_reuse_addr),bec); + if (bec) {ec = clean_up_listen_after_error(bec);return;} + + // if a TCP pre-bind handler is present, run it + if (m_tcp_pre_bind_handler) { + ec = m_tcp_pre_bind_handler(m_acceptor); + if (ec) { + ec = clean_up_listen_after_error(ec); + return; + } + } + + m_acceptor->bind(ep,bec); + if (bec) {ec = clean_up_listen_after_error(bec);return;} + + m_acceptor->listen(m_listen_backlog,bec); + if (bec) {ec = clean_up_listen_after_error(bec);return;} + + // Success + 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 + 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 + 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.reset(new lib::asio::io_service::work(*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( + *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(socket_con_type::translate_ec(ec)); + } + } 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 || !m_acceptor) { + 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(const lib::shared_ptr& a, const lib::shared_ptr& 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 = socket_con_type::translate_ec(asio_ec); + } + } + + 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.reset(new lib::asio::ip::tcp::resolver(*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(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(socket_con_type::translate_ec(ec)); + 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(socket_con_type::translate_ec(ec)); + 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(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 + 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()); + } + + /// Helper for cleaning up in the listen method after an error + template + lib::error_code clean_up_listen_after_error(error_type const & ec) { + if (m_acceptor->is_open()) { + m_acceptor->close(); + } + log_err(log::elevel::info,"asio listen",ec); + return socket_con_type::translate_ec(ec); + } + + enum state { + UNINITIALIZED = 0, + READY = 1, + LISTENING = 2 + }; + + // Handlers + tcp_pre_bind_handler m_tcp_pre_bind_handler; + 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; + + lib::shared_ptr m_elog; + lib::shared_ptr m_alog; + + // Transport state + state m_state; +}; + +} // namespace asio +} // namespace transport +} // namespace websocketpp + +#endif // WEBSOCKETPP_TRANSPORT_ASIO_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/transport/asio/security/base.hpp b/thirdparty/websocketpp/include/websocketpp/transport/asio/security/base.hpp new file mode 100644 index 0000000..441c614 --- /dev/null +++ b/thirdparty/websocketpp/include/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 +#include +#include +#include +#include +#include + +#include + +// 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 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(e), get_socket_category()); +} + +/// Type of asio transport socket policy initialization handlers +typedef lib::function init_handler; + +} // namespace socket +} // namespace asio +} // namespace transport +} // namespace websocketpp + +#endif // WEBSOCKETPP_TRANSPORT_ASIO_SOCKET_BASE_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/transport/asio/security/none.hpp b/thirdparty/websocketpp/include/websocketpp/transport/asio/security/none.hpp new file mode 100644 index 0000000..a108885 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/transport/asio/security/none.hpp @@ -0,0 +1,372 @@ +/* + * 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 + +#include +#include + +#include +#include + +#include +#include + +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 + 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 { +public: + /// Type of this connection socket component + typedef connection type; + /// Type of a shared pointer to this connection socket component + typedef lib::shared_ptr 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 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_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.reset(new lib::asio::ip::tcp::socket(*service)); + + if (m_socket_init_handler) { + m_socket_init_handler(m_hdl, *m_socket); + } + + 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; + } + + 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(); + } + +public: + /// 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 + static + 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); + } + + static + /// 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/thirdparty/websocketpp/include/websocketpp/transport/asio/security/tls.hpp b/thirdparty/websocketpp/include/websocketpp/transport/asio/security/tls.hpp new file mode 100644 index 0000000..660c55e --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/transport/asio/security/tls.hpp @@ -0,0 +1,474 @@ +/* + * 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 + +#include + +#include +#include +#include +#include +#include + +#include +#include + +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&)> socket_init_handler; +/// The signature of the tls_init_handler for this socket policy +typedef lib::function(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 { +public: + /// Type of this connection socket component + typedef connection type; + /// Type of a shared pointer to this connection socket component + typedef lib::shared_ptr ptr; + + /// Type of the ASIO socket being used + typedef lib::asio::ssl::stream socket_type; + /// Type of a shared pointer to the ASIO socket being used + typedef lib::shared_ptr 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 strand_ptr; + /// Type of a shared pointer to the ASIO TLS context being used + typedef lib::shared_ptr 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.reset(new socket_type(*service, *m_context)); + + if (m_socket_init_handler) { + m_socket_init_handler(m_hdl, get_socket()); + } + + 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 + + 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); + } + } + +public: + /// 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 + static + lib::error_code translate_ec(ErrorCodeType ec) { + if (ec.category() == lib::asio::error::get_ssl_category()) { + // 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); + } + } + + static + /// 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) { + 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 diff --git a/thirdparty/websocketpp/include/websocketpp/transport/base/connection.hpp b/thirdparty/websocketpp/include/websocketpp/transport/base/connection.hpp new file mode 100644 index 0000000..ce9a4f9 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/transport/base/connection.hpp @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2014, 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_BASE_CON_HPP +#define WEBSOCKETPP_TRANSPORT_BASE_CON_HPP + +#include +#include +#include +#include + +#include + +namespace websocketpp { +/// Transport policies provide network connectivity and timers +/** + * ### Connection Interface + * + * Transport connection components needs to provide: + * + * **init**\n + * `void init(init_handler handler)`\n + * Called once shortly after construction to give the policy the chance to + * perform one time initialization. When complete, the policy must call the + * supplied `init_handler` to continue setup. The handler takes one argument + * with the error code if any. If an error is returned here setup will fail and + * the connection will be aborted or terminated. + * + * WebSocket++ will call init only once. The transport must call `handler` + * exactly once. + * + * **async_read_at_least**\n + * `void async_read_at_least(size_t num_bytes, char *buf, size_t len, + * read_handler handler)`\n + * start an async read for at least num_bytes and at most len + * bytes into buf. Call handler when done with number of bytes read. + * + * WebSocket++ promises to have only one async_read_at_least in flight at a + * time. The transport must promise to only call read_handler once per async + * read. + * + * **async_write**\n + * `void async_write(const char* buf, size_t len, write_handler handler)`\n + * `void async_write(std::vector & bufs, write_handler handler)`\n + * Start a write of all of the data in buf or bufs. In second case data is + * written sequentially and in place without copying anything to a temporary + * location. + * + * Websocket++ promises to have only one async_write in flight at a time. + * The transport must promise to only call the write_handler once per async + * write + * + * **set_handle**\n + * `void set_handle(connection_hdl hdl)`\n + * Called by WebSocket++ to let this policy know the hdl to the connection. It + * may be stored for later use or ignored/discarded. This handle should be used + * if the policy adds any connection handlers. Connection handlers must be + * called with the handle as the first argument so that the handler code knows + * which connection generated the callback. + * + * **set_timer**\n + * `timer_ptr set_timer(long duration, timer_handler handler)`\n + * WebSocket++ uses the timers provided by the transport policy as the + * implementation of timers is often highly coupled with the implementation of + * the networking event loops. + * + * Transport timer support is an optional feature. A transport method may elect + * to implement a dummy timer object and have this method return an empty + * pointer. If so, all timer related features of WebSocket++ core will be + * disabled. This includes many security features designed to prevent denial of + * service attacks. Use timer-free transport policies with caution. + * + * **get_remote_endpoint**\n + * `std::string get_remote_endpoint()`\n + * retrieve address of remote endpoint + * + * **is_secure**\n + * `void is_secure()`\n + * whether or not the connection to the remote endpoint is secure + * + * **dispatch**\n + * `lib::error_code dispatch(dispatch_handler handler)`: invoke handler within + * the transport's event system if it uses one. Otherwise, this method should + * simply call `handler` immediately. + * + * **async_shutdown**\n + * `void async_shutdown(shutdown_handler handler)`\n + * Perform any cleanup necessary (if any). Call `handler` when complete. + */ +namespace transport { + +/// The type and signature of the callback passed to the init hook +typedef lib::function init_handler; + +/// The type and signature of the callback passed to the read method +typedef lib::function read_handler; + +/// The type and signature of the callback passed to the write method +typedef lib::function write_handler; + +/// The type and signature of the callback passed to the read method +typedef lib::function timer_handler; + +/// The type and signature of the callback passed to the shutdown method +typedef lib::function shutdown_handler; + +/// The type and signature of the callback passed to the interrupt method +typedef lib::function interrupt_handler; + +/// The type and signature of the callback passed to the dispatch method +typedef lib::function dispatch_handler; + +/// A simple utility buffer class +struct buffer { + buffer(char const * b, size_t l) : buf(b),len(l) {} + + char const * buf; + size_t len; +}; + +/// Generic transport related errors +namespace error { +enum value { + /// Catch-all error for transport policy errors that don't fit in other + /// categories + general = 1, + + /// underlying transport pass through + pass_through, + + /// async_read_at_least call requested more bytes than buffer can store + invalid_num_bytes, + + /// async_read called while another async_read was in progress + double_read, + + /// Operation aborted + operation_aborted, + + /// Operation not supported + operation_not_supported, + + /// End of file + eof, + + /// TLS short read + tls_short_read, + + /// Timer expired + timeout, + + /// read or write after shutdown + action_after_shutdown, + + /// Other TLS error + tls_error +}; + +class category : public lib::error_category { + public: + category() {} + + char const * name() const _WEBSOCKETPP_NOEXCEPT_TOKEN_ { + return "websocketpp.transport"; + } + + std::string message(int value) const { + switch(value) { + case general: + return "Generic transport policy error"; + case pass_through: + return "Underlying Transport Error"; + case invalid_num_bytes: + return "async_read_at_least call requested more bytes than buffer can store"; + case operation_aborted: + return "The operation was aborted"; + case operation_not_supported: + return "The operation is not supported by this transport"; + case eof: + return "End of File"; + case tls_short_read: + return "TLS Short Read"; + case timeout: + return "Timer Expired"; + case action_after_shutdown: + return "A transport action was requested after shutdown"; + case tls_error: + return "Generic TLS related error"; + default: + return "Unknown"; + } + } +}; + +inline lib::error_category const & get_category() { + static category instance; + return instance; +} + +inline lib::error_code make_error_code(error::value e) { + return lib::error_code(static_cast(e), get_category()); +} + +} // namespace error +} // namespace transport +} // namespace websocketpp +_WEBSOCKETPP_ERROR_CODE_ENUM_NS_START_ +template<> struct is_error_code_enum +{ + static bool const value = true; +}; +_WEBSOCKETPP_ERROR_CODE_ENUM_NS_END_ + +#endif // WEBSOCKETPP_TRANSPORT_BASE_CON_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/transport/base/endpoint.hpp b/thirdparty/websocketpp/include/websocketpp/transport/base/endpoint.hpp new file mode 100644 index 0000000..408b6c8 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/transport/base/endpoint.hpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2014, 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_BASE_HPP +#define WEBSOCKETPP_TRANSPORT_BASE_HPP + +#include +#include + +namespace websocketpp { +/// Transport policies provide network connectivity and timers +/** + * ### Endpoint Interface + * + * Transport endpoint components needs to provide: + * + * **init**\n + * `lib::error_code init(transport_con_ptr tcon)`\n + * 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. + * + * **is_secure**\n + * `bool is_secure() const`\n + * Test whether the transport component of this endpoint is capable of secure + * connections. + * + * **async_connect**\n + * `void async_connect(transport_con_ptr tcon, uri_ptr location, + * connect_handler handler)`\n + * Initiate a connection to `location` using the given connection `tcon`. `tcon` + * is a pointer to the transport connection component of the connection. When + * complete, `handler` should be called with the the connection's + * `connection_hdl` and any error that occurred. + * + * **init_logging** + * `void init_logging(const lib::shared_ptr& a, const lib::shared_ptr& e)`\n + * Called once after construction to provide pointers to the endpoint's access + * and error loggers. These may be stored and used to log messages or ignored. + */ +namespace transport { + +/// The type and signature of the callback passed to the accept method +typedef lib::function accept_handler; + +/// The type and signature of the callback passed to the connect method +typedef lib::function connect_handler; + +} // namespace transport +} // namespace websocketpp + +#endif // WEBSOCKETPP_TRANSPORT_BASE_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/transport/debug/base.hpp b/thirdparty/websocketpp/include/websocketpp/transport/debug/base.hpp new file mode 100644 index 0000000..18494da --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/transport/debug/base.hpp @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2014, 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_DEBUG_BASE_HPP +#define WEBSOCKETPP_TRANSPORT_DEBUG_BASE_HPP + +#include +#include + +#include + +namespace websocketpp { +namespace transport { +/// Debug transport policy that is used for various mocking and stubbing duties +/// in unit tests. +namespace debug { + +/// debug transport errors +namespace error { +enum value { + /// Catch-all error for transport policy errors that don't fit in other + /// categories + general = 1, + + /// not implemented + not_implemented, + + invalid_num_bytes, + + double_read +}; + +/// debug transport error category +class category : public lib::error_category { + public: + category() {} + + char const * name() const _WEBSOCKETPP_NOEXCEPT_TOKEN_ { + return "websocketpp.transport.debug"; + } + + std::string message(int value) const { + switch(value) { + case general: + return "Generic stub transport policy error"; + case not_implemented: + return "feature not implemented"; + case invalid_num_bytes: + return "Invalid number of bytes"; + case double_read: + return "Read while another read was outstanding"; + default: + return "Unknown"; + } + } +}; + +/// Get a reference to a static copy of the debug transport error category +inline lib::error_category const & get_category() { + static category instance; + return instance; +} + +/// Get an error code with the given value and the debug transport category +inline lib::error_code make_error_code(error::value e) { + return lib::error_code(static_cast(e), get_category()); +} + +} // namespace error +} // namespace debug +} // namespace transport +} // namespace websocketpp +_WEBSOCKETPP_ERROR_CODE_ENUM_NS_START_ +template<> struct is_error_code_enum +{ + static bool const value = true; +}; +_WEBSOCKETPP_ERROR_CODE_ENUM_NS_END_ + +#endif // WEBSOCKETPP_TRANSPORT_DEBUG_BASE_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/transport/debug/connection.hpp b/thirdparty/websocketpp/include/websocketpp/transport/debug/connection.hpp new file mode 100644 index 0000000..0cf75b1 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/transport/debug/connection.hpp @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2014, 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_DEBUG_CON_HPP +#define WEBSOCKETPP_TRANSPORT_DEBUG_CON_HPP + +#include + +#include + +#include +#include + +#include +#include +#include + +#include +#include + +namespace websocketpp { +namespace transport { +namespace debug { + +/// Empty timer class to stub out for timer functionality that stub +/// transport doesn't support +struct timer { + void cancel() {} +}; + +template +class connection : public lib::enable_shared_from_this< connection > { +public: + /// Type of this connection transport component + typedef connection type; + /// Type of a shared pointer to this connection transport component + typedef lib::shared_ptr ptr; + + /// transport concurrency policy + typedef typename config::concurrency_type concurrency_type; + /// 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; + + // Concurrency policy types + typedef typename concurrency_type::scoped_lock_type scoped_lock_type; + typedef typename concurrency_type::mutex_type mutex_type; + + typedef lib::shared_ptr timer_ptr; + + explicit connection(bool is_server, const lib::shared_ptr & alog, const lib::shared_ptr & elog) + : m_reading(false), m_is_server(is_server), m_alog(alog), m_elog(elog) + { + m_alog->write(log::alevel::devel,"debug con transport constructor"); + } + + /// Get a shared pointer to this component + ptr get_shared() { + return type::shared_from_this(); + } + + /// Set whether or not this connection is secure + /** + * Todo: docs + * + * @since 0.3.0-alpha4 + * + * @param value Whether or not this connection is secure. + */ + void set_secure(bool) {} + + /// Tests whether or not the underlying transport is secure + /** + * TODO: docs + * + * @return Whether or not the underlying transport is secure + */ + bool is_secure() const { + return false; + } + + /// Set uri hook + /** + * Called by the endpoint as a connection is being established to provide + * the uri being connected to to the transport layer. + * + * Implementation is optional and can be ignored if the transport has no + * need for this information. + * + * @since 0.6.0 + * + * @param u The uri to set + */ + void set_uri(uri_ptr) {} + + /// Set human readable remote endpoint address + /** + * Sets the remote endpoint address returned by `get_remote_endpoint`. This + * value should be a human readable string that describes the remote + * endpoint. Typically an IP address or hostname, perhaps with a port. But + * may be something else depending on the nature of the underlying + * transport. + * + * If none is set a default is returned. + * + * @since 0.3.0-alpha4 + * + * @param value The remote endpoint address to set. + */ + void set_remote_endpoint(std::string) {} + + /// Get human readable remote endpoint address + /** + * TODO: docs + * + * This value is used in access and error logs and is available to the end + * application for including in user facing interfaces and messages. + * + * @return A string identifying the address of the remote endpoint + */ + std::string get_remote_endpoint() const { + return "unknown (debug transport)"; + } + + /// Get the connection handle + /** + * @return The handle for this connection. + */ + connection_hdl get_handle() const { + return connection_hdl(); + } + + /// Call back a function after a period of time. + /** + * Timers are not implemented in this transport. The timer pointer will + * always be empty. The handler will never be called. + * + * @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, timer_handler handler) { + m_alog->write(log::alevel::devel,"debug connection set timer"); + m_timer_handler = handler; + return timer_ptr(); + } + + /// Manual input supply (read all) + /** + * Similar to read_some, but continues to read until all bytes in the + * supplied buffer have been read or the connection runs out of read + * requests. + * + * This method still may not read all of the bytes in the input buffer. if + * it doesn't it indicates that the connection was most likely closed or + * is in an error state where it is no longer accepting new input. + * + * @since 0.3.0 + * + * @param buf Char buffer to read into the websocket + * @param len Length of buf + * @return The number of characters from buf actually read. + */ + size_t read_all(char const * buf, size_t len) { + size_t total_read = 0; + size_t temp_read = 0; + + do { + temp_read = this->read_some_impl(buf+total_read,len-total_read); + total_read += temp_read; + } while (temp_read != 0 && total_read < len); + + return total_read; + } + + // debug stuff to invoke the async handlers + void expire_timer(lib::error_code const & ec) { + m_timer_handler(ec); + } + + void fullfil_write() { + m_write_handler(lib::error_code()); + } +protected: + /// Initialize the connection transport + /** + * Initialize the connection's transport component. + * + * @param handler The `init_handler` to call when initialization is done + */ + void init(init_handler handler) { + m_alog->write(log::alevel::devel,"debug connection init"); + handler(lib::error_code()); + } + + /// Initiate an async_read for at least num_bytes bytes into buf + /** + * Initiates an async_read request for at least num_bytes bytes. The input + * will be read into buf. A maximum of len bytes will be input. When the + * operation is complete, handler will be called with the status and number + * of bytes read. + * + * This method may or may not call handler from within the initial call. The + * application should be prepared to accept either. + * + * The application should never call this method a second time before it has + * been called back for the first read. If this is done, the second read + * will be called back immediately with a double_read error. + * + * If num_bytes or len are zero handler will be called back immediately + * indicating success. + * + * @param num_bytes Don't call handler until at least this many bytes have + * been read. + * @param buf The buffer to read bytes into + * @param len The size of buf. At maximum, this many bytes will be read. + * @param handler The callback to invoke when the operation is complete or + * ends in an error + */ + void async_read_at_least(size_t num_bytes, char * buf, size_t len, + read_handler handler) + { + std::stringstream s; + s << "debug_con async_read_at_least: " << num_bytes; + m_alog->write(log::alevel::devel,s.str()); + + if (num_bytes > len) { + handler(make_error_code(error::invalid_num_bytes),size_t(0)); + return; + } + + if (m_reading == true) { + handler(make_error_code(error::double_read),size_t(0)); + return; + } + + if (num_bytes == 0 || len == 0) { + handler(lib::error_code(),size_t(0)); + return; + } + + m_buf = buf; + m_len = len; + m_bytes_needed = num_bytes; + m_read_handler = handler; + m_cursor = 0; + m_reading = true; + } + + /// Asyncronous Transport Write + /** + * Write len bytes in buf to the output stream. Call handler to report + * success or failure. handler may or may not be called during async_write, + * but it must be safe for this to happen. + * + * Will return 0 on success. + * + * @param buf buffer to read bytes from + * @param len number of bytes to write + * @param handler Callback to invoke with operation status. + */ + void async_write(char const *, size_t, write_handler handler) { + m_alog->write(log::alevel::devel,"debug_con async_write"); + m_write_handler = handler; + } + + /// Asyncronous Transport Write (scatter-gather) + /** + * Write a sequence of buffers to the output stream. Call handler to report + * success or failure. handler may or may not be called during async_write, + * but it must be safe for this to happen. + * + * Will return 0 on success. + * + * @param bufs vector of buffers to write + * @param handler Callback to invoke with operation status. + */ + void async_write(std::vector const &, write_handler handler) { + m_alog->write(log::alevel::devel,"debug_con async_write buffer list"); + m_write_handler = handler; + } + + /// Set Connection Handle + /** + * @param hdl The new handle + */ + void set_handle(connection_hdl) {} + + /// Call given handler back within the transport's event system (if present) + /** + * Invoke a callback within the transport's event system if it has one. If + * it doesn't, the handler will be invoked immediately before this function + * returns. + * + * @param handler The callback to invoke + * + * @return Whether or not the transport was able to register the handler for + * callback. + */ + lib::error_code dispatch(dispatch_handler handler) { + handler(); + return lib::error_code(); + } + + /// Perform cleanup on socket shutdown_handler + /** + * @param h The `shutdown_handler` to call back when complete + */ + void async_shutdown(shutdown_handler handler) { + handler(lib::error_code()); + } + + size_t read_some_impl(char const * buf, size_t len) { + m_alog->write(log::alevel::devel,"debug_con read_some"); + + if (!m_reading) { + m_elog->write(log::elevel::devel,"write while not reading"); + return 0; + } + + size_t bytes_to_copy = (std::min)(len,m_len-m_cursor); + + std::copy(buf,buf+bytes_to_copy,m_buf+m_cursor); + + m_cursor += bytes_to_copy; + + if (m_cursor >= m_bytes_needed) { + complete_read(lib::error_code()); + } + + return bytes_to_copy; + } + + /// Signal that a requested read is complete + /** + * Sets the reading flag to false and returns the handler that should be + * called back with the result of the read. The cursor position that is sent + * is whatever the value of m_cursor is. + * + * It MUST NOT be called when m_reading is false. + * it MUST be called while holding the read lock + * + * It is important to use this method rather than directly setting/calling + * m_read_handler back because this function makes sure to delete the + * locally stored handler which contains shared pointers that will otherwise + * cause circular reference based memory leaks. + * + * @param ec The error code to forward to the read handler + */ + void complete_read(lib::error_code const & ec) { + m_reading = false; + + read_handler handler = m_read_handler; + m_read_handler = read_handler(); + + handler(ec,m_cursor); + } +private: + timer_handler m_timer_handler; + + // Read space (Protected by m_read_mutex) + char * m_buf; + size_t m_len; + size_t m_bytes_needed; + read_handler m_read_handler; + size_t m_cursor; + + // transport resources + connection_hdl m_connection_hdl; + write_handler m_write_handler; + shutdown_handler m_shutdown_handler; + + bool m_reading; + bool const m_is_server; + bool m_is_secure; + lib::shared_ptr m_alog; + lib::shared_ptr m_elog; + std::string m_remote_endpoint; +}; + + +} // namespace debug +} // namespace transport +} // namespace websocketpp + +#endif // WEBSOCKETPP_TRANSPORT_DEBUG_CON_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/transport/debug/endpoint.hpp b/thirdparty/websocketpp/include/websocketpp/transport/debug/endpoint.hpp new file mode 100644 index 0000000..46049bd --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/transport/debug/endpoint.hpp @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2014, 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_DEBUG_HPP +#define WEBSOCKETPP_TRANSPORT_DEBUG_HPP + +#include +#include + +#include +#include + +namespace websocketpp { +namespace transport { +namespace debug { + +template +class endpoint { +public: + /// Type of this endpoint transport component + typedef endpoint type; + /// Type of a pointer to this endpoint transport component + typedef lib::shared_ptr ptr; + + /// Type of this endpoint's concurrency policy + typedef typename config::concurrency_type concurrency_type; + /// Type of this endpoint's error logging policy + typedef typename config::elog_type elog_type; + /// Type of this endpoint's access logging policy + typedef typename config::alog_type alog_type; + + /// Type of this endpoint transport component's associated connection + /// transport component. + typedef debug::connection transport_con_type; + /// Type of a shared pointer to this endpoint transport component's + /// associated connection transport component + typedef typename transport_con_type::ptr transport_con_ptr; + + // generate and manage our own io_service + explicit endpoint() + { + //std::cout << "transport::iostream::endpoint constructor" << std::endl; + } + + /// Set whether or not endpoint can create secure connections + /** + * TODO: docs + * + * Setting this value only indicates whether or not the endpoint is capable + * of producing and managing secure connections. Connections produced by + * this endpoint must also be individually flagged as secure if they are. + * + * @since 0.3.0-alpha4 + * + * @param value Whether or not the endpoint can create secure connections. + */ + void set_secure(bool) {} + + /// Tests whether or not the underlying transport is secure + /** + * TODO: docs + * + * @return Whether or not the underlying transport is secure + */ + bool is_secure() const { + return false; + } +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. + * + * @param a A pointer to the access logger to use. + * @param e A pointer to the error logger to use. + */ + void init_logging(lib::shared_ptr, lib::shared_ptr) {} + + /// Initiate a new connection + /** + * @param tcon A pointer to the transport connection component of the + * connection to connect. + * @param u A URI pointer to the URI to connect to. + * @param cb The function to call back with the results when complete. + */ + void async_connect(transport_con_ptr, uri_ptr, connect_handler cb) { + cb(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) { + return lib::error_code(); + } +private: + +}; + +} // namespace debug +} // namespace transport +} // namespace websocketpp + +#endif // WEBSOCKETPP_TRANSPORT_DEBUG_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/transport/iostream/base.hpp b/thirdparty/websocketpp/include/websocketpp/transport/iostream/base.hpp new file mode 100644 index 0000000..caa8172 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/transport/iostream/base.hpp @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2014, 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_IOSTREAM_BASE_HPP +#define WEBSOCKETPP_TRANSPORT_IOSTREAM_BASE_HPP + +#include +#include +#include +#include + +#include + +#include +#include + +namespace websocketpp { +namespace transport { +/// Transport policy that uses STL iostream for I/O and does not support timers +namespace iostream { + +/// The type and signature of the callback used by iostream transport to write +typedef lib::function + write_handler; + +/// The type and signature of the callback used by iostream transport to perform +/// vectored writes. +/** + * If a vectored write handler is not set the standard write handler will be + * called multiple times. + */ +typedef lib::function const + & bufs)> vector_write_handler; + +/// The type and signature of the callback used by iostream transport to signal +/// a transport shutdown. +typedef lib::function shutdown_handler; + +/// iostream 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, + + /// async_read called while another async_read was in progress + double_read, + + /// An operation that requires an output stream was attempted before + /// setting one. + output_stream_required, + + /// stream error + bad_stream +}; + +/// iostream transport error category +class category : public lib::error_category { + public: + category() {} + + char const * name() const _WEBSOCKETPP_NOEXCEPT_TOKEN_ { + return "websocketpp.transport.iostream"; + } + + std::string message(int value) const { + switch(value) { + case general: + return "Generic iostream transport policy error"; + case invalid_num_bytes: + return "async_read_at_least call requested more bytes than buffer can store"; + case double_read: + return "Async read already in progress"; + case output_stream_required: + return "An output stream to be set before async_write can be used"; + case bad_stream: + return "A stream operation returned ios::bad"; + default: + return "Unknown"; + } + } +}; + +/// Get a reference to a static copy of the iostream transport error category +inline lib::error_category const & get_category() { + static category instance; + return instance; +} + +/// Get an error code with the given value and the iostream transport category +inline lib::error_code make_error_code(error::value e) { + return lib::error_code(static_cast(e), get_category()); +} + +} // namespace error +} // namespace iostream +} // namespace transport +} // namespace websocketpp +_WEBSOCKETPP_ERROR_CODE_ENUM_NS_START_ +template<> struct is_error_code_enum +{ + static bool const value = true; +}; +_WEBSOCKETPP_ERROR_CODE_ENUM_NS_END_ + +#endif // WEBSOCKETPP_TRANSPORT_IOSTREAM_BASE_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/transport/iostream/connection.hpp b/thirdparty/websocketpp/include/websocketpp/transport/iostream/connection.hpp new file mode 100644 index 0000000..d7290c0 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/transport/iostream/connection.hpp @@ -0,0 +1,714 @@ +/* + * Copyright (c) 2014, 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_IOSTREAM_CON_HPP +#define WEBSOCKETPP_TRANSPORT_IOSTREAM_CON_HPP + +#include + +#include + +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace websocketpp { +namespace transport { +namespace iostream { + +/// Empty timer class to stub out for timer functionality that iostream +/// transport doesn't support +struct timer { + void cancel() {} +}; + +template +class connection : public lib::enable_shared_from_this< connection > { +public: + /// Type of this connection transport component + typedef connection type; + /// Type of a shared pointer to this connection transport component + typedef lib::shared_ptr ptr; + + /// transport concurrency policy + typedef typename config::concurrency_type concurrency_type; + /// 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; + + // Concurrency policy types + typedef typename concurrency_type::scoped_lock_type scoped_lock_type; + typedef typename concurrency_type::mutex_type mutex_type; + + typedef lib::shared_ptr timer_ptr; + + explicit connection(bool is_server, const lib::shared_ptr & alog, const lib::shared_ptr & elog) + : m_output_stream(NULL) + , m_reading(false) + , m_is_server(is_server) + , m_is_secure(false) + , m_alog(alog) + , m_elog(elog) + , m_remote_endpoint("iostream transport") + { + m_alog->write(log::alevel::devel,"iostream con transport constructor"); + } + + /// Get a shared pointer to this component + ptr get_shared() { + return type::shared_from_this(); + } + + /// Register a std::ostream with the transport for writing output + /** + * Register a std::ostream with the transport. All future writes will be + * done to this output stream. + * + * @param o A pointer to the ostream to use for output. + */ + void register_ostream(std::ostream * o) { + // TODO: lock transport state? + scoped_lock_type lock(m_read_mutex); + m_output_stream = o; + } + + /// 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 so it is ignored. + * + * @since 0.6.0 + * + * @param u The uri to set + */ + void set_uri(uri_ptr) {} + + /// Overloaded stream input operator + /** + * Attempts to read input from the given stream into the transport. Bytes + * will be extracted from the input stream to fulfill any pending reads. + * Input in this manner will only read until the current read buffer has + * been filled. Then it will signal the library to process the input. If the + * library's input handler adds a new async_read, additional bytes will be + * read, otherwise the input operation will end. + * + * When this function returns one of the following conditions is true: + * - There is no outstanding read operation + * - There are no more bytes available in the input stream + * + * You can use tellg() on the input stream to determine if all of the input + * bytes were read or not. + * + * If there is no pending read operation when the input method is called, it + * will return immediately and tellg() will not have changed. + */ + friend std::istream & operator>> (std::istream & in, type & t) { + // this serializes calls to external read. + scoped_lock_type lock(t.m_read_mutex); + + t.read(in); + + return in; + } + + /// Manual input supply (read some) + /** + * Copies bytes from buf into WebSocket++'s input buffers. Bytes will be + * copied from the supplied buffer to fulfill any pending library reads. It + * will return the number of bytes successfully processed. If there are no + * pending reads read_some will return immediately. Not all of the bytes may + * be able to be read in one call. + * + * @since 0.3.0-alpha4 + * + * @param buf Char buffer to read into the websocket + * @param len Length of buf + * @return The number of characters from buf actually read. + */ + size_t read_some(char const * buf, size_t len) { + // this serializes calls to external read. + scoped_lock_type lock(m_read_mutex); + + return this->read_some_impl(buf,len); + } + + /// Manual input supply (read all) + /** + * Similar to read_some, but continues to read until all bytes in the + * supplied buffer have been read or the connection runs out of read + * requests. + * + * This method still may not read all of the bytes in the input buffer. if + * it doesn't it indicates that the connection was most likely closed or + * is in an error state where it is no longer accepting new input. + * + * @since 0.3.0 + * + * @param buf Char buffer to read into the websocket + * @param len Length of buf + * @return The number of characters from buf actually read. + */ + size_t read_all(char const * buf, size_t len) { + // this serializes calls to external read. + scoped_lock_type lock(m_read_mutex); + + size_t total_read = 0; + size_t temp_read = 0; + + do { + temp_read = this->read_some_impl(buf+total_read,len-total_read); + total_read += temp_read; + } while (temp_read != 0 && total_read < len); + + return total_read; + } + + /// Manual input supply (DEPRECATED) + /** + * @deprecated DEPRECATED in favor of read_some() + * @see read_some() + */ + size_t readsome(char const * buf, size_t len) { + return this->read_some(buf,len); + } + + /// Signal EOF + /** + * Signals to the transport that data stream being read has reached EOF and + * that no more bytes may be read or written to/from the transport. + * + * @since 0.3.0-alpha4 + */ + void eof() { + // this serializes calls to external read. + scoped_lock_type lock(m_read_mutex); + + if (m_reading) { + complete_read(make_error_code(transport::error::eof)); + } + } + + /// Signal transport error + /** + * Signals to the transport that a fatal data stream error has occurred and + * that no more bytes may be read or written to/from the transport. + * + * @since 0.3.0-alpha4 + */ + void fatal_error() { + // this serializes calls to external read. + scoped_lock_type lock(m_read_mutex); + + if (m_reading) { + complete_read(make_error_code(transport::error::pass_through)); + } + } + + /// Set whether or not this connection is secure + /** + * The iostream transport does not provide any security features. As such + * it defaults to returning false when `is_secure` is called. However, the + * iostream transport may be used to wrap an external socket API that may + * provide secure transport. This method allows that external API to flag + * whether or not this connection is secure so that users of the WebSocket++ + * API will get more accurate information. + * + * @since 0.3.0-alpha4 + * + * @param value Whether or not this connection is secure. + */ + void set_secure(bool value) { + m_is_secure = value; + } + + /// Tests whether or not the underlying transport is secure + /** + * iostream transport will return false always because it has no information + * about the ultimate remote endpoint. This may or may not be accurate + * depending on the real source of bytes being input. The `set_secure` + * method may be used to flag connections that are secured by an external + * API + * + * @return Whether or not the underlying transport is secure + */ + bool is_secure() const { + return m_is_secure; + } + + /// Set human readable remote endpoint address + /** + * Sets the remote endpoint address returned by `get_remote_endpoint`. This + * value should be a human readable string that describes the remote + * endpoint. Typically an IP address or hostname, perhaps with a port. But + * may be something else depending on the nature of the underlying + * transport. + * + * If none is set the default is "iostream transport". + * + * @since 0.3.0-alpha4 + * + * @param value The remote endpoint address to set. + */ + void set_remote_endpoint(std::string value) { + m_remote_endpoint = value; + } + + /// Get human readable remote endpoint address + /** + * The iostream transport has no information about the ultimate remote + * endpoint. It will return the string "iostream transport". The + * `set_remote_endpoint` method may be used by external network code to set + * a more accurate value. + * + * This value is used in access and error logs and is available to the end + * application for including in user facing interfaces and messages. + * + * @return A string identifying the address of the remote endpoint + */ + std::string get_remote_endpoint() const { + return m_remote_endpoint; + } + + /// Get the connection handle + /** + * @return The handle for this connection. + */ + connection_hdl get_handle() const { + return m_connection_hdl; + } + + /// Call back a function after a period of time. + /** + * Timers are not implemented in this transport. The timer pointer will + * always be empty. The handler will never be called. + * + * @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, timer_handler) { + return timer_ptr(); + } + + /// Sets the write handler + /** + * The write handler is called when the iostream transport receives data + * that needs to be written to the appropriate output location. This handler + * can be used in place of registering an ostream for output. + * + * The signature of the handler is + * `lib::error_code (connection_hdl, char const *, size_t)` The + * code returned will be reported and logged by the core library. + * + * See also, set_vector_write_handler, for an optional write handler that + * allows more efficient handling of multiple writes at once. + * + * @see set_vector_write_handler + * + * @since 0.5.0 + * + * @param h The handler to call when data is to be written. + */ + void set_write_handler(write_handler h) { + m_write_handler = h; + } + + /// Sets the vectored write handler + /** + * The vectored write handler is called when the iostream transport receives + * multiple chunks of data that need to be written to the appropriate output + * location. This handler can be used in conjunction with the write_handler + * in place of registering an ostream for output. + * + * The sequence of buffers represents bytes that should be written + * consecutively and it is suggested to group the buffers into as few next + * layer packets as possible. Vector write is used to allow implementations + * that support it to coalesce writes into a single TCP packet or TLS + * segment for improved efficiency. + * + * This is an optional handler. If it is not defined then multiple calls + * will be made to the standard write handler. + * + * The signature of the handler is + * `lib::error_code (connection_hdl, std::vector + * const & bufs)`. The code returned will be reported and logged by the core + * library. The `websocketpp::transport::buffer` type is a struct with two + * data members. buf (char const *) and len (size_t). + * + * @since 0.6.0 + * + * @param h The handler to call when vectored data is to be written. + */ + void set_vector_write_handler(vector_write_handler h) { + m_vector_write_handler = h; + } + + /// Sets the shutdown handler + /** + * The shutdown handler is called when the iostream transport receives a + * notification from the core library that it is finished with all read and + * write operations and that the underlying transport can be cleaned up. + * + * If you are using iostream transport with another socket library, this is + * a good time to close/shutdown the socket for this connection. + * + * The signature of the handler is `lib::error_code (connection_hdl)`. The + * code returned will be reported and logged by the core library. + * + * @since 0.5.0 + * + * @param h The handler to call on connection shutdown. + */ + void set_shutdown_handler(shutdown_handler h) { + m_shutdown_handler = h; + } +protected: + /// Initialize the connection transport + /** + * Initialize the connection's transport component. + * + * @param handler The `init_handler` to call when initialization is done + */ + void init(init_handler handler) { + m_alog->write(log::alevel::devel,"iostream connection init"); + handler(lib::error_code()); + } + + /// Initiate an async_read for at least num_bytes bytes into buf + /** + * Initiates an async_read request for at least num_bytes bytes. The input + * will be read into buf. A maximum of len bytes will be input. When the + * operation is complete, handler will be called with the status and number + * of bytes read. + * + * This method may or may not call handler from within the initial call. The + * application should be prepared to accept either. + * + * The application should never call this method a second time before it has + * been called back for the first read. If this is done, the second read + * will be called back immediately with a double_read error. + * + * If num_bytes or len are zero handler will be called back immediately + * indicating success. + * + * @param num_bytes Don't call handler until at least this many bytes have + * been read. + * @param buf The buffer to read bytes into + * @param len The size of buf. At maximum, this many bytes will be read. + * @param handler The callback to invoke when the operation is complete or + * ends in an error + */ + void async_read_at_least(size_t num_bytes, char *buf, size_t len, + read_handler handler) + { + std::stringstream s; + s << "iostream_con async_read_at_least: " << num_bytes; + m_alog->write(log::alevel::devel,s.str()); + + if (num_bytes > len) { + handler(make_error_code(error::invalid_num_bytes),size_t(0)); + return; + } + + if (m_reading == true) { + handler(make_error_code(error::double_read),size_t(0)); + return; + } + + if (num_bytes == 0 || len == 0) { + handler(lib::error_code(),size_t(0)); + return; + } + + m_buf = buf; + m_len = len; + m_bytes_needed = num_bytes; + m_read_handler = handler; + m_cursor = 0; + m_reading = true; + } + + /// Asyncronous Transport Write + /** + * Write len bytes in buf to the output method. Call handler to report + * success or failure. handler may or may not be called during async_write, + * but it must be safe for this to happen. + * + * Will return 0 on success. Other possible errors (not exhaustive) + * output_stream_required: No output stream was registered to write to + * bad_stream: a ostream pass through error + * + * This method will attempt to write to the registered ostream first. If an + * ostream is not registered it will use the write handler. If neither are + * registered then an error is passed up to the connection. + * + * @param buf buffer to read bytes from + * @param len number of bytes to write + * @param handler Callback to invoke with operation status. + */ + void async_write(char const * buf, size_t len, transport::write_handler + handler) + { + m_alog->write(log::alevel::devel,"iostream_con async_write"); + // TODO: lock transport state? + + lib::error_code ec; + + if (m_output_stream) { + m_output_stream->write(buf,len); + + if (m_output_stream->bad()) { + ec = make_error_code(error::bad_stream); + } + } else if (m_write_handler) { + ec = m_write_handler(m_connection_hdl, buf, len); + } else { + ec = make_error_code(error::output_stream_required); + } + + handler(ec); + } + + /// Asyncronous Transport Write (scatter-gather) + /** + * Write a sequence of buffers to the output method. Call handler to report + * success or failure. handler may or may not be called during async_write, + * but it must be safe for this to happen. + * + * Will return 0 on success. Other possible errors (not exhaustive) + * output_stream_required: No output stream was registered to write to + * bad_stream: a ostream pass through error + * + * This method will attempt to write to the registered ostream first. If an + * ostream is not registered it will use the write handler. If neither are + * registered then an error is passed up to the connection. + * + * @param bufs vector of buffers to write + * @param handler Callback to invoke with operation status. + */ + void async_write(std::vector const & bufs, transport::write_handler + handler) + { + m_alog->write(log::alevel::devel,"iostream_con async_write buffer list"); + // TODO: lock transport state? + + lib::error_code ec; + + if (m_output_stream) { + std::vector::const_iterator it; + for (it = bufs.begin(); it != bufs.end(); it++) { + m_output_stream->write((*it).buf,(*it).len); + + if (m_output_stream->bad()) { + ec = make_error_code(error::bad_stream); + break; + } + } + } else if (m_vector_write_handler) { + ec = m_vector_write_handler(m_connection_hdl, bufs); + } else if (m_write_handler) { + std::vector::const_iterator it; + for (it = bufs.begin(); it != bufs.end(); it++) { + ec = m_write_handler(m_connection_hdl, (*it).buf, (*it).len); + if (ec) {break;} + } + + } else { + ec = make_error_code(error::output_stream_required); + } + + handler(ec); + } + + /// Set Connection Handle + /** + * @param hdl The new handle + */ + void set_handle(connection_hdl hdl) { + m_connection_hdl = hdl; + } + + /// Call given handler back within the transport's event system (if present) + /** + * Invoke a callback within the transport's event system if it has one. If + * it doesn't, the handler will be invoked immediately before this function + * returns. + * + * @param handler The callback to invoke + * + * @return Whether or not the transport was able to register the handler for + * callback. + */ + lib::error_code dispatch(dispatch_handler handler) { + handler(); + return lib::error_code(); + } + + /// Perform cleanup on socket shutdown_handler + /** + * If a shutdown handler is set, call it and pass through its return error + * code. Otherwise assume there is nothing to do and pass through a success + * code. + * + * @param handler The `shutdown_handler` to call back when complete + */ + void async_shutdown(transport::shutdown_handler handler) { + lib::error_code ec; + + if (m_shutdown_handler) { + ec = m_shutdown_handler(m_connection_hdl); + } + + handler(ec); + } +private: + void read(std::istream &in) { + m_alog->write(log::alevel::devel,"iostream_con read"); + + while (in.good()) { + if (!m_reading) { + m_elog->write(log::elevel::devel,"write while not reading"); + break; + } + + in.read(m_buf+m_cursor,static_cast(m_len-m_cursor)); + + if (in.gcount() == 0) { + m_elog->write(log::elevel::devel,"read zero bytes"); + break; + } + + m_cursor += static_cast(in.gcount()); + + // TODO: error handling + if (in.bad()) { + m_reading = false; + complete_read(make_error_code(error::bad_stream)); + } + + if (m_cursor >= m_bytes_needed) { + m_reading = false; + complete_read(lib::error_code()); + } + } + } + + size_t read_some_impl(char const * buf, size_t len) { + m_alog->write(log::alevel::devel,"iostream_con read_some"); + + if (!m_reading) { + m_elog->write(log::elevel::devel,"write while not reading"); + return 0; + } + + size_t bytes_to_copy = (std::min)(len,m_len-m_cursor); + + std::copy(buf,buf+bytes_to_copy,m_buf+m_cursor); + + m_cursor += bytes_to_copy; + + if (m_cursor >= m_bytes_needed) { + complete_read(lib::error_code()); + } + + return bytes_to_copy; + } + + /// Signal that a requested read is complete + /** + * Sets the reading flag to false and returns the handler that should be + * called back with the result of the read. The cursor position that is sent + * is whatever the value of m_cursor is. + * + * It MUST NOT be called when m_reading is false. + * it MUST be called while holding the read lock + * + * It is important to use this method rather than directly setting/calling + * m_read_handler back because this function makes sure to delete the + * locally stored handler which contains shared pointers that will otherwise + * cause circular reference based memory leaks. + * + * @param ec The error code to forward to the read handler + */ + void complete_read(lib::error_code const & ec) { + m_reading = false; + + read_handler handler = m_read_handler; + m_read_handler = read_handler(); + + handler(ec,m_cursor); + } + + // Read space (Protected by m_read_mutex) + char * m_buf; + size_t m_len; + size_t m_bytes_needed; + read_handler m_read_handler; + size_t m_cursor; + + // transport resources + std::ostream * m_output_stream; + connection_hdl m_connection_hdl; + write_handler m_write_handler; + vector_write_handler m_vector_write_handler; + shutdown_handler m_shutdown_handler; + + bool m_reading; + bool const m_is_server; + bool m_is_secure; + lib::shared_ptr m_alog; + lib::shared_ptr m_elog; + std::string m_remote_endpoint; + + // This lock ensures that only one thread can edit read data for this + // connection. This is a very coarse lock that is basically locked all the + // time. The nature of the connection is such that it cannot be + // parallelized, the locking is here to prevent intra-connection concurrency + // in order to allow inter-connection concurrency. + mutex_type m_read_mutex; +}; + + +} // namespace iostream +} // namespace transport +} // namespace websocketpp + +#endif // WEBSOCKETPP_TRANSPORT_IOSTREAM_CON_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/transport/iostream/endpoint.hpp b/thirdparty/websocketpp/include/websocketpp/transport/iostream/endpoint.hpp new file mode 100644 index 0000000..14ee42a --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/transport/iostream/endpoint.hpp @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2014, 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_IOSTREAM_HPP +#define WEBSOCKETPP_TRANSPORT_IOSTREAM_HPP + +#include +#include + +#include +#include + +#include + +#include + +namespace websocketpp { +namespace transport { +namespace iostream { + +template +class endpoint { +public: + /// Type of this endpoint transport component + typedef endpoint type; + /// Type of a pointer to this endpoint transport component + typedef lib::shared_ptr ptr; + + /// Type of this endpoint's concurrency policy + typedef typename config::concurrency_type concurrency_type; + /// Type of this endpoint's error logging policy + typedef typename config::elog_type elog_type; + /// Type of this endpoint's access logging policy + typedef typename config::alog_type alog_type; + + /// Type of this endpoint transport component's associated connection + /// transport component. + typedef iostream::connection transport_con_type; + /// Type of a shared pointer to this endpoint transport component's + /// associated connection transport component + typedef typename transport_con_type::ptr transport_con_ptr; + + // generate and manage our own io_service + explicit endpoint() : m_output_stream(NULL), m_is_secure(false) + { + //std::cout << "transport::iostream::endpoint constructor" << std::endl; + } + + /// Register a default output stream + /** + * The specified output stream will be assigned to future connections as the + * default output stream. + * + * @param o The ostream to use as the default output stream. + */ + void register_ostream(std::ostream * o) { + m_alog->write(log::alevel::devel,"register_ostream"); + m_output_stream = o; + } + + /// Set whether or not endpoint can create secure connections + /** + * The iostream transport does not provide any security features. As such + * it defaults to returning false when `is_secure` is called. However, the + * iostream transport may be used to wrap an external socket API that may + * provide secure transport. This method allows that external API to flag + * whether or not it can create secure connections so that users of the + * WebSocket++ API will get more accurate information. + * + * Setting this value only indicates whether or not the endpoint is capable + * of producing and managing secure connections. Connections produced by + * this endpoint must also be individually flagged as secure if they are. + * + * @since 0.3.0-alpha4 + * + * @param value Whether or not the endpoint can create secure connections. + */ + void set_secure(bool value) { + m_is_secure = value; + } + + /// Tests whether or not the underlying transport is secure + /** + * iostream transport will return false by default because it has no + * information about the ultimate remote endpoint. This may or may not be + * accurate depending on the real source of bytes being input. `set_secure` + * may be used by a wrapper API to correct the return value in the case that + * secure connections are in fact possible. + * + * @return Whether or not the underlying transport is secure + */ + bool is_secure() const { + return m_is_secure; + } + + /// Sets the write handler + /** + * The write handler is called when the iostream transport receives data + * that needs to be written to the appropriate output location. This handler + * can be used in place of registering an ostream for output. + * + * The signature of the handler is + * `lib::error_code (connection_hdl, char const *, size_t)` The + * code returned will be reported and logged by the core library. + * + * @since 0.5.0 + * + * @param h The handler to call on connection shutdown. + */ + void set_write_handler(write_handler h) { + m_write_handler = h; + } + + /// Sets the shutdown handler + /** + * The shutdown handler is called when the iostream transport receives a + * notification from the core library that it is finished with all read and + * write operations and that the underlying transport can be cleaned up. + * + * If you are using iostream transport with another socket library, this is + * a good time to close/shutdown the socket for this connection. + * + * The signature of the handler is lib::error_code (connection_hdl). The + * code returned will be reported and logged by the core library. + * + * @since 0.5.0 + * + * @param h The handler to call on connection shutdown. + */ + void set_shutdown_handler(shutdown_handler h) { + m_shutdown_handler = h; + } +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. + * + * @param a A pointer to the access logger to use. + * @param e A pointer to the error logger to use. + */ + void init_logging(lib::shared_ptr a, lib::shared_ptr e) { + m_elog = e; + m_alog = a; + } + + /// Initiate a new connection + /** + * @param tcon A pointer to the transport connection component of the + * connection to connect. + * @param u A URI pointer to the URI to connect to. + * @param cb The function to call back with the results when complete. + */ + void async_connect(transport_con_ptr, uri_ptr, connect_handler cb) { + cb(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) { + tcon->register_ostream(m_output_stream); + if (m_shutdown_handler) { + tcon->set_shutdown_handler(m_shutdown_handler); + } + if (m_write_handler) { + tcon->set_write_handler(m_write_handler); + } + return lib::error_code(); + } +private: + std::ostream * m_output_stream; + shutdown_handler m_shutdown_handler; + write_handler m_write_handler; + + lib::shared_ptr m_elog; + lib::shared_ptr m_alog; + bool m_is_secure; +}; + + +} // namespace iostream +} // namespace transport +} // namespace websocketpp + +#endif // WEBSOCKETPP_TRANSPORT_IOSTREAM_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/transport/stub/base.hpp b/thirdparty/websocketpp/include/websocketpp/transport/stub/base.hpp new file mode 100644 index 0000000..51568d5 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/transport/stub/base.hpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2014, 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_STUB_BASE_HPP +#define WEBSOCKETPP_TRANSPORT_STUB_BASE_HPP + +#include +#include + +#include + +namespace websocketpp { +namespace transport { +/// Stub transport policy that has no input or output. +namespace stub { + +/// stub transport errors +namespace error { +enum value { + /// Catch-all error for transport policy errors that don't fit in other + /// categories + general = 1, + + /// not implemented + not_implemented +}; + +/// stub transport error category +class category : public lib::error_category { + public: + category() {} + + char const * name() const _WEBSOCKETPP_NOEXCEPT_TOKEN_ { + return "websocketpp.transport.stub"; + } + + std::string message(int value) const { + switch(value) { + case general: + return "Generic stub transport policy error"; + case not_implemented: + return "feature not implemented"; + default: + return "Unknown"; + } + } +}; + +/// Get a reference to a static copy of the stub transport error category +inline lib::error_category const & get_category() { + static category instance; + return instance; +} + +/// Get an error code with the given value and the stub transport category +inline lib::error_code make_error_code(error::value e) { + return lib::error_code(static_cast(e), get_category()); +} + +} // namespace error +} // namespace stub +} // namespace transport +} // namespace websocketpp +_WEBSOCKETPP_ERROR_CODE_ENUM_NS_START_ +template<> struct is_error_code_enum +{ + static bool const value = true; +}; +_WEBSOCKETPP_ERROR_CODE_ENUM_NS_END_ + +#endif // WEBSOCKETPP_TRANSPORT_STUB_BASE_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/transport/stub/connection.hpp b/thirdparty/websocketpp/include/websocketpp/transport/stub/connection.hpp new file mode 100644 index 0000000..175c1c3 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/transport/stub/connection.hpp @@ -0,0 +1,286 @@ +/* + * Copyright (c) 2014, 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_STUB_CON_HPP +#define WEBSOCKETPP_TRANSPORT_STUB_CON_HPP + +#include + +#include + +#include + +#include +#include +#include + +#include +#include + +namespace websocketpp { +namespace transport { +namespace stub { + +/// Empty timer class to stub out for timer functionality that stub +/// transport doesn't support +struct timer { + void cancel() {} +}; + +template +class connection : public lib::enable_shared_from_this< connection > { +public: + /// Type of this connection transport component + typedef connection type; + /// Type of a shared pointer to this connection transport component + typedef lib::shared_ptr ptr; + + /// transport concurrency policy + typedef typename config::concurrency_type concurrency_type; + /// 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; + + // Concurrency policy types + typedef typename concurrency_type::scoped_lock_type scoped_lock_type; + typedef typename concurrency_type::mutex_type mutex_type; + + typedef lib::shared_ptr timer_ptr; + + explicit connection(bool is_server, const lib::shared_ptr & alog, const lib::shared_ptr & elog) + : m_alog(alog), m_elog(elog) + { + m_alog->write(log::alevel::devel,"stub con transport constructor"); + } + + /// Get a shared pointer to this component + ptr get_shared() { + return type::shared_from_this(); + } + + /// Set whether or not this connection is secure + /** + * Todo: docs + * + * @since 0.3.0-alpha4 + * + * @param value Whether or not this connection is secure. + */ + void set_secure(bool value) {} + + /// Tests whether or not the underlying transport is secure + /** + * TODO: docs + * + * @return Whether or not the underlying transport is secure + */ + bool is_secure() const { + return false; + } + + /// Set uri hook + /** + * Called by the endpoint as a connection is being established to provide + * the uri being connected to to the transport layer. + * + * Implementation is optional and can be ignored if the transport has no + * need for this information. + * + * @since 0.6.0 + * + * @param u The uri to set + */ + void set_uri(uri_ptr) {} + + /// Set human readable remote endpoint address + /** + * Sets the remote endpoint address returned by `get_remote_endpoint`. This + * value should be a human readable string that describes the remote + * endpoint. Typically an IP address or hostname, perhaps with a port. But + * may be something else depending on the nature of the underlying + * transport. + * + * If none is set a default is returned. + * + * @since 0.3.0-alpha4 + * + * @param value The remote endpoint address to set. + */ + void set_remote_endpoint(std::string value) {} + + /// Get human readable remote endpoint address + /** + * TODO: docs + * + * This value is used in access and error logs and is available to the end + * application for including in user facing interfaces and messages. + * + * @return A string identifying the address of the remote endpoint + */ + std::string get_remote_endpoint() const { + return "unknown (stub transport)"; + } + + /// Get the connection handle + /** + * @return The handle for this connection. + */ + connection_hdl get_handle() const { + return connection_hdl(); + } + + /// Call back a function after a period of time. + /** + * Timers are not implemented in this transport. The timer pointer will + * always be empty. The handler will never be called. + * + * @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 handler) { + return timer_ptr(); + } +protected: + /// Initialize the connection transport + /** + * Initialize the connection's transport component. + * + * @param handler The `init_handler` to call when initialization is done + */ + void init(init_handler handler) { + m_alog->write(log::alevel::devel,"stub connection init"); + handler(make_error_code(error::not_implemented)); + } + + /// Initiate an async_read for at least num_bytes bytes into buf + /** + * Initiates an async_read request for at least num_bytes bytes. The input + * will be read into buf. A maximum of len bytes will be input. When the + * operation is complete, handler will be called with the status and number + * of bytes read. + * + * This method may or may not call handler from within the initial call. The + * application should be prepared to accept either. + * + * The application should never call this method a second time before it has + * been called back for the first read. If this is done, the second read + * will be called back immediately with a double_read error. + * + * If num_bytes or len are zero handler will be called back immediately + * indicating success. + * + * @param num_bytes Don't call handler until at least this many bytes have + * been read. + * @param buf The buffer to read bytes into + * @param len The size of buf. At maximum, this many bytes will be read. + * @param handler The callback to invoke when the operation is complete or + * ends in an error + */ + void async_read_at_least(size_t num_bytes, char * buf, size_t len, + read_handler handler) + { + m_alog->write(log::alevel::devel, "stub_con async_read_at_least"); + handler(make_error_code(error::not_implemented), 0); + } + + /// Asyncronous Transport Write + /** + * Write len bytes in buf to the output stream. Call handler to report + * success or failure. handler may or may not be called during async_write, + * but it must be safe for this to happen. + * + * Will return 0 on success. + * + * @param buf buffer to read bytes from + * @param len number of bytes to write + * @param handler Callback to invoke with operation status. + */ + void async_write(char const * buf, size_t len, write_handler handler) { + m_alog->write(log::alevel::devel,"stub_con async_write"); + handler(make_error_code(error::not_implemented)); + } + + /// Asyncronous Transport Write (scatter-gather) + /** + * Write a sequence of buffers to the output stream. Call handler to report + * success or failure. handler may or may not be called during async_write, + * but it must be safe for this to happen. + * + * Will return 0 on success. + * + * @param bufs vector of buffers to write + * @param handler Callback to invoke with operation status. + */ + void async_write(std::vector const & bufs, write_handler handler) { + m_alog->write(log::alevel::devel,"stub_con async_write buffer list"); + handler(make_error_code(error::not_implemented)); + } + + /// Set Connection Handle + /** + * @param hdl The new handle + */ + void set_handle(connection_hdl hdl) {} + + /// Call given handler back within the transport's event system (if present) + /** + * Invoke a callback within the transport's event system if it has one. If + * it doesn't, the handler will be invoked immediately before this function + * returns. + * + * @param handler The callback to invoke + * + * @return Whether or not the transport was able to register the handler for + * callback. + */ + lib::error_code dispatch(dispatch_handler handler) { + handler(); + return lib::error_code(); + } + + /// Perform cleanup on socket shutdown_handler + /** + * @param h The `shutdown_handler` to call back when complete + */ + void async_shutdown(shutdown_handler handler) { + handler(lib::error_code()); + } +private: + // member variables! + lib::shared_ptr m_alog; + lib::shared_ptr m_elog; +}; + + +} // namespace stub +} // namespace transport +} // namespace websocketpp + +#endif // WEBSOCKETPP_TRANSPORT_STUB_CON_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/transport/stub/endpoint.hpp b/thirdparty/websocketpp/include/websocketpp/transport/stub/endpoint.hpp new file mode 100644 index 0000000..d958c0f --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/transport/stub/endpoint.hpp @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2014, 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_STUB_HPP +#define WEBSOCKETPP_TRANSPORT_STUB_HPP + +#include +#include + +#include +#include + +namespace websocketpp { +namespace transport { +namespace stub { + +template +class endpoint { +public: + /// Type of this endpoint transport component + typedef endpoint type; + /// Type of a pointer to this endpoint transport component + typedef lib::shared_ptr ptr; + + /// Type of this endpoint's concurrency policy + typedef typename config::concurrency_type concurrency_type; + /// Type of this endpoint's error logging policy + typedef typename config::elog_type elog_type; + /// Type of this endpoint's access logging policy + typedef typename config::alog_type alog_type; + + /// Type of this endpoint transport component's associated connection + /// transport component. + typedef stub::connection transport_con_type; + /// Type of a shared pointer to this endpoint transport component's + /// associated connection transport component + typedef typename transport_con_type::ptr transport_con_ptr; + + // generate and manage our own io_service + explicit endpoint() + { + //std::cout << "transport::iostream::endpoint constructor" << std::endl; + } + + /// Set whether or not endpoint can create secure connections + /** + * TODO: docs + * + * Setting this value only indicates whether or not the endpoint is capable + * of producing and managing secure connections. Connections produced by + * this endpoint must also be individually flagged as secure if they are. + * + * @since 0.3.0-alpha4 + * + * @param value Whether or not the endpoint can create secure connections. + */ + void set_secure(bool value) {} + + /// Tests whether or not the underlying transport is secure + /** + * TODO: docs + * + * @return Whether or not the underlying transport is secure + */ + bool is_secure() const { + return false; + } +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. + * + * @param a A pointer to the access logger to use. + * @param e A pointer to the error logger to use. + */ + void init_logging(alog_type * a, elog_type * e) {} + + /// Initiate a new connection + /** + * @param tcon A pointer to the transport connection component of the + * connection to connect. + * @param u A URI pointer to the URI to connect to. + * @param cb The function to call back with the results when complete. + */ + void async_connect(transport_con_ptr tcon, uri_ptr u, connect_handler cb) { + cb(make_error_code(error::not_implemented)); + } + + /// 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) { + return make_error_code(error::not_implemented); + } +private: + +}; + +} // namespace stub +} // namespace transport +} // namespace websocketpp + +#endif // WEBSOCKETPP_TRANSPORT_STUB_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/uri.hpp b/thirdparty/websocketpp/include/websocketpp/uri.hpp new file mode 100644 index 0000000..1c379e3 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/uri.hpp @@ -0,0 +1,356 @@ +/* + * Copyright (c) 2014, 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_URI_HPP +#define WEBSOCKETPP_URI_HPP + +#include + +#include +#include + +#include +#include +#include + +namespace websocketpp { + +// TODO: figure out why this fixes horrible linking errors. + +/// Default port for ws:// +static uint16_t const uri_default_port = 80; +/// Default port for wss:// +static uint16_t const uri_default_secure_port = 443; + +class uri { +public: + explicit uri(std::string const & uri_string) : m_valid(false) { + std::string::const_iterator it; + std::string::const_iterator temp; + + int state = 0; + + it = uri_string.begin(); + size_t uri_len = uri_string.length(); + + if (uri_len >= 7 && std::equal(it,it+6,"wss://")) { + m_secure = true; + m_scheme = "wss"; + it += 6; + } else if (uri_len >= 6 && std::equal(it,it+5,"ws://")) { + m_secure = false; + m_scheme = "ws"; + it += 5; + } else if (uri_len >= 8 && std::equal(it,it+7,"http://")) { + m_secure = false; + m_scheme = "http"; + it += 7; + } else if (uri_len >= 9 && std::equal(it,it+8,"https://")) { + m_secure = true; + m_scheme = "https"; + it += 8; + } else { + return; + } + + // extract host. + // either a host string + // an IPv4 address + // or an IPv6 address + if (*it == '[') { + ++it; + // IPv6 literal + // extract IPv6 digits until ] + + // TODO: this doesn't work on g++... not sure why + //temp = std::find(it,it2,']'); + + temp = it; + while (temp != uri_string.end()) { + if (*temp == ']') { + break; + } + ++temp; + } + + if (temp == uri_string.end()) { + return; + } else { + // validate IPv6 literal parts + // can contain numbers, a-f and A-F + m_host.append(it,temp); + } + it = temp+1; + if (it == uri_string.end()) { + state = 2; + } else if (*it == '/') { + state = 2; + ++it; + } else if (*it == ':') { + state = 1; + ++it; + } else { + // problem + return; + } + } else { + // IPv4 or hostname + // extract until : or / + while (state == 0) { + if (it == uri_string.end()) { + state = 2; + break; + } else if (*it == '/') { + state = 2; + } else if (*it == ':') { + // end hostname start port + state = 1; + } else { + m_host += *it; + } + ++it; + } + } + + // parse port + std::string port; + while (state == 1) { + if (it == uri_string.end()) { + // state is not used after this point presently. + // this should be re-enabled if it ever is needed in a future + // refactoring + //state = 3; + break; + } else if (*it == '/') { + state = 3; + } else { + port += *it; + } + ++it; + } + + lib::error_code ec; + m_port = get_port_from_string(port, ec); + + if (ec) { + return; + } + + m_resource = "/"; + m_resource.append(it,uri_string.end()); + + + m_valid = true; + } + + uri(bool secure, std::string const & host, uint16_t port, + std::string const & resource) + : m_scheme(secure ? "wss" : "ws") + , m_host(host) + , m_resource(resource.empty() ? "/" : resource) + , m_port(port) + , m_secure(secure) + , m_valid(true) {} + + uri(bool secure, std::string const & host, std::string const & resource) + : m_scheme(secure ? "wss" : "ws") + , m_host(host) + , m_resource(resource.empty() ? "/" : resource) + , m_port(secure ? uri_default_secure_port : uri_default_port) + , m_secure(secure) + , m_valid(true) {} + + uri(bool secure, std::string const & host, std::string const & port, + std::string const & resource) + : m_scheme(secure ? "wss" : "ws") + , m_host(host) + , m_resource(resource.empty() ? "/" : resource) + , m_secure(secure) + { + lib::error_code ec; + m_port = get_port_from_string(port,ec); + m_valid = !ec; + } + + uri(std::string const & scheme, std::string const & host, uint16_t port, + std::string const & resource) + : m_scheme(scheme) + , m_host(host) + , m_resource(resource.empty() ? "/" : resource) + , m_port(port) + , m_secure(scheme == "wss" || scheme == "https") + , m_valid(true) {} + + uri(std::string scheme, std::string const & host, std::string const & resource) + : m_scheme(scheme) + , m_host(host) + , m_resource(resource.empty() ? "/" : resource) + , m_port((scheme == "wss" || scheme == "https") ? uri_default_secure_port : uri_default_port) + , m_secure(scheme == "wss" || scheme == "https") + , m_valid(true) {} + + uri(std::string const & scheme, std::string const & host, + std::string const & port, std::string const & resource) + : m_scheme(scheme) + , m_host(host) + , m_resource(resource.empty() ? "/" : resource) + , m_secure(scheme == "wss" || scheme == "https") + { + lib::error_code ec; + m_port = get_port_from_string(port,ec); + m_valid = !ec; + } + + bool get_valid() const { + return m_valid; + } + + bool get_secure() const { + return m_secure; + } + + std::string const & get_scheme() const { + return m_scheme; + } + + std::string const & get_host() const { + return m_host; + } + + std::string get_host_port() const { + if (m_port == (m_secure ? uri_default_secure_port : uri_default_port)) { + return m_host; + } else { + std::stringstream p; + p << m_host << ":" << m_port; + return p.str(); + } + } + + std::string get_authority() const { + std::stringstream p; + p << m_host << ":" << m_port; + return p.str(); + } + + uint16_t get_port() const { + return m_port; + } + + std::string get_port_str() const { + std::stringstream p; + p << m_port; + return p.str(); + } + + std::string const & get_resource() const { + return m_resource; + } + + std::string str() const { + std::stringstream s; + + s << m_scheme << "://" << m_host; + + if (m_port != (m_secure ? uri_default_secure_port : uri_default_port)) { + s << ":" << m_port; + } + + s << m_resource; + return s.str(); + } + + /// Return the query portion + /** + * Returns the query portion (after the ?) of the URI or an empty string if + * there is none. + * + * @return query portion of the URI. + */ + std::string get_query() const { + std::size_t found = m_resource.find('?'); + if (found != std::string::npos) { + return m_resource.substr(found + 1); + } else { + return ""; + } + } + + // get fragment + + // hi <3 + + // get the string representation of this URI + + //std::string base() const; // is this still needed? + + // setter methods set some or all (in the case of parse) based on the input. + // These functions throw a uri_exception on failure. + /*void set_uri(const std::string& uri); + + void set_secure(bool secure); + void set_host(const std::string& host); + void set_port(uint16_t port); + void set_port(const std::string& port); + void set_resource(const std::string& resource);*/ +private: + uint16_t get_port_from_string(std::string const & port, lib::error_code & + ec) const + { + ec = lib::error_code(); + + if (port.empty()) { + return (m_secure ? uri_default_secure_port : uri_default_port); + } + + unsigned int t_port = static_cast(atoi(port.c_str())); + + if (t_port > 65535) { + ec = error::make_error_code(error::invalid_port); + } + + if (t_port == 0) { + ec = error::make_error_code(error::invalid_port); + } + + return static_cast(t_port); + } + + std::string m_scheme; + std::string m_host; + std::string m_resource; + uint16_t m_port; + bool m_secure; + bool m_valid; +}; + +/// Pointer to a URI +typedef lib::shared_ptr uri_ptr; + +} // namespace websocketpp + +#endif // WEBSOCKETPP_URI_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/utf8_validator.hpp b/thirdparty/websocketpp/include/websocketpp/utf8_validator.hpp new file mode 100644 index 0000000..f71f0a2 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/utf8_validator.hpp @@ -0,0 +1,154 @@ +/* + * The following code is adapted from code originally written by Bjoern + * Hoehrmann . See + * http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. + * + * The original license: + * + * Copyright (c) 2008-2009 Bjoern Hoehrmann + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. +*/ + +#ifndef UTF8_VALIDATOR_HPP +#define UTF8_VALIDATOR_HPP + +#include + +#include + +namespace websocketpp { +namespace utf8_validator { + +/// State that represents a valid utf8 input sequence +static unsigned int const utf8_accept = 0; +/// State that represents an invalid utf8 input sequence +static unsigned int const utf8_reject = 1; + +/// Lookup table for the UTF8 decode state machine +static uint8_t const utf8d[] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df + 0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef + 0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff + 0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2 + 1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4 + 1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6 + 1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8 +}; + +/// Decode the next byte of a UTF8 sequence +/** + * @param [out] state The decoder state to advance + * @param [out] codep The codepoint to fill in + * @param [in] byte The byte to input + * @return The ending state of the decode operation + */ +inline uint32_t decode(uint32_t * state, uint32_t * codep, uint8_t byte) { + uint32_t type = utf8d[byte]; + + *codep = (*state != utf8_accept) ? + (byte & 0x3fu) | (*codep << 6) : + (0xff >> type) & (byte); + + *state = utf8d[256 + *state*16 + type]; + return *state; +} + +/// Provides streaming UTF8 validation functionality +class validator { +public: + /// Construct and initialize the validator + validator() : m_state(utf8_accept),m_codepoint(0) {} + + /// Advance the state of the validator with the next input byte + /** + * @param byte The byte to advance the validation state with + * @return Whether or not the byte resulted in a validation error. + */ + bool consume (uint8_t byte) { + if (utf8_validator::decode(&m_state,&m_codepoint,byte) == utf8_reject) { + return false; + } + return true; + } + + /// Advance validator state with input from an iterator pair + /** + * @param begin Input iterator to the start of the input range + * @param end Input iterator to the end of the input range + * @return Whether or not decoding the bytes resulted in a validation error. + */ + template + bool decode (iterator_type begin, iterator_type end) { + for (iterator_type it = begin; it != end; ++it) { + unsigned int result = utf8_validator::decode( + &m_state, + &m_codepoint, + static_cast(*it) + ); + + if (result == utf8_reject) { + return false; + } + } + return true; + } + + /// Return whether the input sequence ended on a valid utf8 codepoint + /** + * @return Whether or not the input sequence ended on a valid codepoint. + */ + bool complete() { + return m_state == utf8_accept; + } + + /// Reset the validator to decode another message + void reset() { + m_state = utf8_accept; + m_codepoint = 0; + } +private: + uint32_t m_state; + uint32_t m_codepoint; +}; + +/// Validate a UTF8 string +/** + * convenience function that creates a validator, validates a complete string + * and returns the result. + */ +inline bool validate(std::string const & s) { + validator v; + if (!v.decode(s.begin(),s.end())) { + return false; + } + return v.complete(); +} + +} // namespace utf8_validator +} // namespace websocketpp + +#endif // UTF8_VALIDATOR_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/utilities.hpp b/thirdparty/websocketpp/include/websocketpp/utilities.hpp new file mode 100644 index 0000000..0a48bfa --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/utilities.hpp @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2014, 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_UTILITIES_HPP +#define WEBSOCKETPP_UTILITIES_HPP + +#include + +#include +#include +#include + +namespace websocketpp { +/// Generic non-websocket specific utility functions and data structures +namespace utility { + +/// Helper functor for case insensitive find +/** + * Based on code from + * http://stackoverflow.com/questions/3152241/case-insensitive-stdstring-find + * + * templated version of my_equal so it could work with both char and wchar_t + */ +template +struct my_equal { + /// Construct the functor with the given locale + /** + * @param [in] loc The locale to use for determining the case of values + */ + my_equal(std::locale const & loc ) : m_loc(loc) {} + + /// Perform a case insensitive comparison + /** + * @param ch1 The first value to compare + * @param ch2 The second value to compare + * @return Whether or not the two values are equal when both are converted + * to uppercase using the given locale. + */ + bool operator()(charT ch1, charT ch2) { + return std::toupper(ch1, m_loc) == std::toupper(ch2, m_loc); + } +private: + std::locale const & m_loc; +}; + +/// Helper less than functor for case insensitive find +/** + * Based on code from + * http://stackoverflow.com/questions/3152241/case-insensitive-stdstring-find + */ +struct ci_less { + // case-independent (ci) compare_less binary function + struct nocase_compare { + bool operator() (unsigned char const & c1, unsigned char const & c2) const { + return tolower (c1) < tolower (c2); + } + }; + bool operator() (std::string const & s1, std::string const & s2) const { + return std::lexicographical_compare + (s1.begin (), s1.end (), // source range + s2.begin (), s2.end (), // dest range + nocase_compare ()); // comparison + } +}; + +/// Find substring (case insensitive) +/** + * @param [in] haystack The string to search in + * @param [in] needle The string to search for + * @param [in] loc The locale to use for determining the case of values. + * Defaults to the current locale. + * @return An iterator to the first element of the first occurrance of needle in + * haystack. If the sequence is not found, the function returns + * haystack.end() + */ +template +typename T::const_iterator ci_find_substr(T const & haystack, T const & needle, + std::locale const & loc = std::locale()) +{ + return std::search( haystack.begin(), haystack.end(), + needle.begin(), needle.end(), my_equal(loc) ); +} + +/// Find substring (case insensitive) +/** + * @todo Is this still used? This method may not make sense.. should use + * iterators or be less generic. As is it is too tightly coupled to std::string + * + * @param [in] haystack The string to search in + * @param [in] needle The string to search for as a char array of values + * @param [in] size Length of needle + * @param [in] loc The locale to use for determining the case of values. + * Defaults to the current locale. + * @return An iterator to the first element of the first occurrance of needle in + * haystack. If the sequence is not found, the function returns + * haystack.end() + */ +template +typename T::const_iterator ci_find_substr(T const & haystack, + typename T::value_type const * needle, typename T::size_type size, + std::locale const & loc = std::locale()) +{ + return std::search( haystack.begin(), haystack.end(), + needle, needle+size, my_equal(loc) ); +} + +/// Convert a string to lowercase +/** + * @param [in] in The string to convert + * @return The converted string + */ +std::string to_lower(std::string const & in); + +/// Replace all occurrances of a substring with another +/** + * @param [in] subject The string to search in + * @param [in] search The string to search for + * @param [in] replace The string to replace with + * @return A copy of `subject` with all occurances of `search` replaced with + * `replace` + */ +std::string string_replace_all(std::string subject, std::string const & search, + std::string const & replace); + +/// Convert std::string to ascii printed string of hex digits +/** + * @param [in] input The string to print + * @return A copy of `input` converted to the printable representation of the + * hex values of its data. + */ +std::string to_hex(std::string const & input); + +/// Convert byte array (uint8_t) to ascii printed string of hex digits +/** + * @param [in] input The byte array to print + * @param [in] length The length of input + * @return A copy of `input` converted to the printable representation of the + * hex values of its data. + */ +std::string to_hex(uint8_t const * input, size_t length); + +/// Convert char array to ascii printed string of hex digits +/** + * @param [in] input The char array to print + * @param [in] length The length of input + * @return A copy of `input` converted to the printable representation of the + * hex values of its data. + */ +std::string to_hex(char const * input, size_t length); + +} // namespace utility +} // namespace websocketpp + +#include + +#endif // WEBSOCKETPP_UTILITIES_HPP diff --git a/thirdparty/websocketpp/include/websocketpp/version.hpp b/thirdparty/websocketpp/include/websocketpp/version.hpp new file mode 100644 index 0000000..3500d84 --- /dev/null +++ b/thirdparty/websocketpp/include/websocketpp/version.hpp @@ -0,0 +1,61 @@ +/* + * 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_VERSION_HPP +#define WEBSOCKETPP_VERSION_HPP + +/// Namespace for the WebSocket++ project +namespace websocketpp { + +/* + other places where version information is kept + - readme.md + - changelog.md + - Doxyfile + - CMakeLists.txt +*/ + +/// Library major version number +static int const major_version = 0; +/// Library minor version number +static int const minor_version = 8; +/// Library patch version number +static int const patch_version = 2; +/// Library pre-release flag +/** + * This is a textual flag indicating the type and number for pre-release + * versions (dev, alpha, beta, rc). This will be blank for release versions. + */ + +static char const prerelease_flag[] = ""; + +/// Default user agent string +static char const user_agent[] = "WebSocket++/0.8.2"; + +} // namespace websocketpp + +#endif // WEBSOCKETPP_VERSION_HPP diff --git a/xmake.lua b/xmake.lua new file mode 100644 index 0000000..6e97152 --- /dev/null +++ b/xmake.lua @@ -0,0 +1,86 @@ +set_project("projectx") +set_version("0.0.1") + +add_rules("mode.release", "mode.debug") +set_languages("c++17") + +add_rules("mode.release", "mode.debug") + +add_requires("asio 1.24.0", "nlohmann_json", "spdlog 1.11.0") + +add_defines("JUICE_STATIC") +add_defines("ASIO_STANDALONE","_WEBSOCKETPP_CPP11_INTERNAL_", "ASIO_HAS_STD_TYPE_TRAITS", "ASIO_HAS_STD_SHARED_PTR", + "ASIO_HAS_STD_ADDRESSOF", "ASIO_HAS_STD_ATOMIC", "ASIO_HAS_STD_CHRONO", "ASIO_HAS_CSTDINT", "ASIO_HAS_STD_ARRAY", + "ASIO_HAS_STD_SYSTEM_ERROR") + +add_links("ws2_32", "Bcrypt") +add_cxflags("-MD") +add_packages("spdlog") + +target("ice") + set_kind("static") + add_deps("log", "ws") + add_packages("asio", "nlohmann_json") + add_files("src/ice/*.cpp") + add_links("juice") + add_includedirs("src/ws") + add_includedirs("thirdparty/libjuice/include", {public = true}) + add_linkdirs("thirdparty/libjuice/lib") + +target("ws") + set_kind("static") + add_deps("log") + add_files("src/ws/*.cpp") + add_packages("asio") + add_includedirs("thirdparty/websocketpp/include", {public = true}) + +target("log") + set_kind("headeronly") + add_packages("spdlog") + add_headerfiles("src/log/log.h") + add_includedirs("src/log", {public = true}) + +target("pc") + set_kind("static") + add_deps("log") + add_deps("ws", "ice") + add_files("src/pc/*.cpp") + add_packages("asio", "nlohmann_json") + add_includedirs("src/ws", "src/ice") + +target("projectx") + set_kind("shared") + add_deps("log") + add_deps("ice", "ws", "pc") + add_files("src/rtc/*.cpp") + add_packages("asio", "nlohmann_json") + add_includedirs("src/rtc", "src/ice", "src/ws", "src/pc") + add_rules("utils.symbols.export_all", {export_classes = true}) + -- set_policy("build.merge_archive", true) + -- set_targetdir("$(projectdir)/libdrtc/lib") + +target("signal_server") + set_kind("binary") + add_deps("log") + add_files("tests/signal_server/*.cpp") + add_packages("asio", "nlohmann_json", "spdlog") + add_includedirs("thirdparty/websocketpp/include") + +-- target("signal_client") +-- set_kind("binary") +-- add_deps("ws") +-- add_files("tests/signal_client/signal_client.cpp") +-- add_packages("asio") +-- add_includedirs("src/ws", "thirdparty/websocketpp/include") + +target("Offer") + set_kind("binary") + add_deps("projectx") + add_files("tests/peerconnection/offer.cpp") + add_includedirs("src/rtc") + +target("Answer") + set_kind("binary") + add_deps("projectx") + add_files("tests/peerconnection/answer.cpp") + add_includedirs("src/rtc") \ No newline at end of file