Skip to content

Commit cae65ee

Browse files
committed
Initial implementation of secure websockets on websocketpp.
1 parent 95e5918 commit cae65ee

File tree

3 files changed

+128
-53
lines changed

3 files changed

+128
-53
lines changed

Release/src/websockets/client/ws_client.cpp

Lines changed: 118 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#pragma GCC diagnostic ignored "-Wconversion"
4141
#pragma GCC diagnostic ignored "-Wunused-parameter"
4242
#pragma GCC diagnostic ignored "-Wignored-qualifiers"
43+
#include <websocketpp/config/asio_client.hpp>
4344
#include <websocketpp/config/asio_no_tls_client.hpp>
4445
#include <websocketpp/client.hpp>
4546
#pragma GCC diagnostic pop
@@ -55,6 +56,9 @@
5556
#else
5657
#define _WEBSOCKETPP_NULLPTR_TOKEN_ 0
5758
#endif
59+
#ifndef _MS_WINDOWS
60+
#include <websocketpp/config/asio_client.hpp>
61+
#endif
5862
#include <websocketpp/config/asio_no_tls_client.hpp>
5963
#include <websocketpp/client.hpp>
6064
#pragma warning( pop )
@@ -83,8 +87,7 @@ class wspp_client : public _websocket_client_impl, public std::enable_shared_fro
8387
{
8488
private:
8589
enum State {
86-
UNINITIALIZED,
87-
INITIALIZED,
90+
CREATED,
8891
CONNECTING,
8992
CONNECTED,
9093
CLOSING,
@@ -95,23 +98,9 @@ class wspp_client : public _websocket_client_impl, public std::enable_shared_fro
9598
wspp_client(websocket_client_config config) :
9699
_websocket_client_impl(std::move(config)),
97100
m_work(utility::details::make_unique<boost::asio::io_service::work>(m_service)),
98-
m_state(UNINITIALIZED),
101+
m_state(CREATED),
99102
m_num_sends(0)
100-
{
101-
// TODO tls here.
102-
initialize_impl<websocketpp::config::asio_client>();
103-
}
104-
105-
template <typename WebsocketConfigType>
106-
void initialize_impl()
107-
{
108-
auto &client = boost::get<websocketpp::client<WebsocketConfigType>>(m_client);
109-
110-
client.clear_access_channels(websocketpp::log::alevel::all);
111-
client.clear_error_channels(websocketpp::log::alevel::all);
112-
client.init_asio(&m_service);
113-
m_state = INITIALIZED;
114-
}
103+
{}
115104

116105
~wspp_client()
117106
{
@@ -123,11 +112,10 @@ class wspp_client : public _websocket_client_impl, public std::enable_shared_fro
123112

124113
// Now, what states could we be in?
125114
switch (m_state) {
126-
case UNINITIALIZED:
127115
case DESTROYED:
128116
// These should be impossible
129117
std::abort();
130-
case INITIALIZED:
118+
case CREATED:
131119
case CLOSED:
132120
// In these cases, nothing need be done.
133121
lock.unlock();
@@ -167,14 +155,22 @@ class wspp_client : public _websocket_client_impl, public std::enable_shared_fro
167155

168156
pplx::task<void> close(websocket_close_status status, const utility::string_t& reason)
169157
{
170-
// TODO tls here.
171-
return close_impl<websocketpp::config::asio_client>(status, reason);
158+
#ifndef _MS_WINDOWS
159+
if (m_client->is_tls_client())
160+
{
161+
return close_impl<websocketpp::config::asio_tls_client>(status, reason);
162+
}
163+
else
164+
#endif
165+
{
166+
return close_impl<websocketpp::config::asio_client>(status, reason);
167+
}
172168
}
173169

174-
template <typename WebsocketConfigType>
170+
template <typename WebsocketConfig>
175171
pplx::task<void> close_impl(websocket_close_status status, const utility::string_t& reason)
176172
{
177-
auto &client = boost::get<websocketpp::client<WebsocketConfigType>>(m_client);
173+
auto &client = m_client->client<WebsocketConfig>();
178174

179175
std::lock_guard<std::mutex> lock(m_receive_queue_lock);
180176
if (m_state == CONNECTED)
@@ -193,16 +189,43 @@ class wspp_client : public _websocket_client_impl, public std::enable_shared_fro
193189

194190
pplx::task<void> connect()
195191
{
196-
// TODO tls here.
197-
return connect_impl<websocketpp::config::asio_client>();
192+
#ifndef _MS_WINDOWS
193+
if (m_uri.scheme() == U("wss"))
194+
{
195+
m_client = std::unique_ptr<websocketpp_client_base>(new websocketpp_tls_client());
196+
197+
// Options specific to TLS client.
198+
auto &client = m_client->client<websocketpp::config::asio_tls_client>();
199+
client.set_tls_init_handler([this](websocketpp::connection_hdl)
200+
{
201+
auto sslContext = websocketpp::lib::shared_ptr<boost::asio::ssl::context>(new boost::asio::ssl::context(boost::asio::ssl::context::sslv23));
202+
sslContext->set_default_verify_paths();
203+
sslContext->set_options(boost::asio::ssl::context::default_workarounds);
204+
sslContext->set_verify_mode(boost::asio::ssl::context::verify_peer);
205+
// Like done with the http_client certificate verification needs more steps for iOS and Android.
206+
sslContext->set_verify_callback(boost::asio::ssl::rfc2818_verification(m_uri.host()));
207+
return sslContext;
208+
});
209+
return connect_impl<websocketpp::config::asio_tls_client>();
210+
}
211+
else
212+
#endif
213+
{
214+
m_client = std::unique_ptr<websocketpp_client_base>(new websocketpp_client());
215+
return connect_impl<websocketpp::config::asio_client>();
216+
}
198217
}
199-
218+
200219
template <typename WebsocketConfigType>
201220
pplx::task<void> connect_impl()
202221
{
203-
auto &client = boost::get<websocketpp::client<WebsocketConfigType>>(m_client);
222+
auto &client = m_client->client<WebsocketConfigType>();
204223

205-
_ASSERTE(m_state == INITIALIZED);
224+
client.clear_access_channels(websocketpp::log::alevel::all);
225+
client.clear_error_channels(websocketpp::log::alevel::all);
226+
client.init_asio(&m_service);
227+
228+
_ASSERTE(m_state == CREATED);
206229
client.set_open_handler([this](websocketpp::connection_hdl)
207230
{
208231
_ASSERTE(m_state == CONNECTING);
@@ -219,7 +242,7 @@ class wspp_client : public _websocket_client_impl, public std::enable_shared_fro
219242
m_connect_tce.set_exception(websocket_exception("Connection attempt failed."));
220243
});
221244

222-
client.set_message_handler([this](websocketpp::connection_hdl, WebsocketConfigType::message_type::ptr msg)
245+
client.set_message_handler([this](websocketpp::connection_hdl, websocketpp::config::asio_client::message_type::ptr msg)
223246
{
224247
_ASSERTE(m_state >= CONNECTED && m_state < CLOSED);
225248
websocket_incoming_message ws_incoming_message;
@@ -283,7 +306,7 @@ class wspp_client : public _websocket_client_impl, public std::enable_shared_fro
283306
}
284307

285308
// Add any request headers specified by the user.
286-
const auto & headers = config().headers();
309+
const auto & headers = m_config.headers();
287310
for (const auto & header : headers)
288311
{
289312
if (!utility::details::str_icmp(header.first, g_subProtocolHeader))
@@ -295,7 +318,7 @@ class wspp_client : public _websocket_client_impl, public std::enable_shared_fro
295318
// Add any specified subprotocols.
296319
if (headers.has(g_subProtocolHeader))
297320
{
298-
const std::vector<utility::string_t> protocols = config().subprotocols();
321+
const std::vector<utility::string_t> protocols = m_config.subprotocols();
299322
for (const auto & value : protocols)
300323
{
301324
con->add_subprotocol(utility::conversions::to_utf8string(value), ec);
@@ -395,7 +418,16 @@ class wspp_client : public _websocket_client_impl, public std::enable_shared_fro
395418

396419
void send_msg(websocket_outgoing_message &msg)
397420
{
398-
send_msg_impl<websocketpp::config::asio_client>(msg);
421+
#ifndef _MS_WINDOWS
422+
if (m_client->is_tls_client())
423+
{
424+
send_msg_impl<websocketpp::config::asio_tls_client>(msg);
425+
}
426+
else
427+
#endif
428+
{
429+
send_msg_impl<websocketpp::config::asio_client>(msg);
430+
}
399431
}
400432

401433
template <typename WebsocketClientType>
@@ -445,7 +477,7 @@ class wspp_client : public _websocket_client_impl, public std::enable_shared_fro
445477
// First try to acquire the data (Get a pointer to the next already allocated contiguous block of data)
446478
// If acquire succeeds, send the data over the socket connection, there is no copy of data from stream to temporary buffer.
447479
// If acquire fails, copy the data to a temporary buffer managed by sp_allocated and send it over the socket connection.
448-
std::shared_ptr<uint8_t> sp_allocated(nullptr, [](uint8_t *) {});
480+
std::shared_ptr<uint8_t> sp_allocated;
449481
size_t acquired_size = 0;
450482
uint8_t* ptr;
451483
auto read_task = pplx::task_from_result();
@@ -473,27 +505,27 @@ class wspp_client : public _websocket_client_impl, public std::enable_shared_fro
473505
}
474506
else
475507
{
476-
// Acquire succeeded, assign the acquired pointer to sp_allocated. Keep an empty custom destructor
508+
// Acquire succeeded, assign the acquired pointer to sp_allocated. Use an empty custom destructor
477509
// so that the data is not released when sp_allocated goes out of scope. The streambuf will manage its memory.
478510
sp_allocated.reset(ptr, [](uint8_t *) {});
479511
}
480512

481-
auto client = boost::get<websocketpp::client<WebsocketClientType>>(&m_client);
482-
read_task.then([this_client, client, msg, sp_allocated, length]()
513+
read_task.then([this_client, msg, sp_allocated, length]()
483514
{
515+
auto &client = this_client->m_client->client<WebsocketClientType>();
484516
websocketpp::lib::error_code ec;
485517
switch (msg._m_impl->message_type())
486518
{
487519
case websocket_message_type::text_message:
488-
client->send(
520+
client.send(
489521
this_client->m_con,
490522
sp_allocated.get(),
491523
length,
492524
websocketpp::frame::opcode::text,
493525
ec);
494526
break;
495527
case websocket_message_type::binary_message:
496-
client->send(
528+
client.send(
497529
this_client->m_con,
498530
sp_allocated.get(),
499531
length,
@@ -570,8 +602,53 @@ class wspp_client : public _websocket_client_impl, public std::enable_shared_fro
570602
std::unique_ptr<boost::asio::io_service::work> m_work;
571603
std::thread m_thread;
572604

573-
boost::variant<
574-
websocketpp::client<websocketpp::config::asio_client>> m_client;
605+
// Perform type erasure to set the websocketpp client in use at runtime
606+
// after construction based on the URI.
607+
struct websocketpp_client_base
608+
{
609+
virtual ~websocketpp_client_base() _noexcept {}
610+
template <typename WebsocketConfig>
611+
websocketpp::client<WebsocketConfig> & client()
612+
{
613+
if(is_tls_client())
614+
{
615+
return reinterpret_cast<websocketpp::client<WebsocketConfig> &>(tls_client());
616+
}
617+
else
618+
{
619+
return reinterpret_cast<websocketpp::client<WebsocketConfig> &>(non_tls_client());
620+
}
621+
}
622+
virtual websocketpp::client<websocketpp::config::asio_client> & non_tls_client()
623+
{
624+
throw std::bad_cast();
625+
}
626+
virtual websocketpp::client<websocketpp::config::asio_tls_client> & tls_client()
627+
{
628+
throw std::bad_cast();
629+
}
630+
virtual bool is_tls_client() const = 0;
631+
};
632+
struct websocketpp_client : websocketpp_client_base
633+
{
634+
websocketpp::client<websocketpp::config::asio_client> & non_tls_client() override
635+
{
636+
return m_client;
637+
}
638+
bool is_tls_client() const override { return false; }
639+
websocketpp::client<websocketpp::config::asio_client> m_client;
640+
};
641+
struct websocketpp_tls_client : websocketpp_client_base
642+
{
643+
websocketpp::client<websocketpp::config::asio_tls_client> & tls_client() override
644+
{
645+
return m_client;
646+
}
647+
bool is_tls_client() const override { return true; }
648+
websocketpp::client<websocketpp::config::asio_tls_client> m_client;
649+
};
650+
std::unique_ptr<websocketpp_client_base> m_client;
651+
575652
websocketpp::connection_hdl m_con;
576653

577654
pplx::task_completion_event<void> m_connect_tce;

Release/src/websockets/client/ws_winrt.cpp

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,10 @@ class winrt_client : public _websocket_client_impl, public std::enable_shared_fr
7979
m_client_closed(false)
8080
{
8181
m_msg_websocket = ref new MessageWebSocket();
82-
const auto &config = config();
8382

8483
// Sets the HTTP request headers to the HTTP request message used in the WebSocket protocol handshake
8584
const utility::string_t protocolHeader(_XPLATSTR("Sec-WebSocket-Protocol"));
86-
const auto & headers = config.headers();
85+
const auto & headers = m_config.headers();
8786
for (const auto & header : headers)
8887
{
8988
// Unfortunately the MessageWebSocket API throws a COMException if you try to set the
@@ -97,18 +96,18 @@ class winrt_client : public _websocket_client_impl, public std::enable_shared_fr
9796
// Add any specified subprotocols.
9897
if (headers.has(protocolHeader))
9998
{
100-
const std::vector<utility::string_t> protocols = config.subprotocols();
99+
const std::vector<utility::string_t> protocols = m_config.subprotocols();
101100
for (const auto & value : protocols)
102101
{
103102
m_msg_websocket->Control->SupportedProtocols->Append(Platform::StringReference(value.c_str()));
104103
}
105104
}
106105

107-
if (config.credentials().is_set())
106+
if (m_config.credentials().is_set())
108107
{
109108
m_msg_websocket->Control->ServerCredential = ref new Windows::Security::Credentials::PasswordCredential("WebSocketClientCredentialResource",
110-
Platform::StringReference(config.credentials().username().c_str()),
111-
Platform::StringReference(config.credentials().password().c_str()));
109+
Platform::StringReference(m_config.credentials().username().c_str()),
110+
Platform::StringReference(m_config.credentials().password().c_str()));
112111
}
113112

114113
m_context = ref new ReceiveContext([=](websocket_incoming_message &msg)
@@ -170,7 +169,7 @@ class winrt_client : public _websocket_client_impl, public std::enable_shared_fr
170169

171170
pplx::task<void> connect()
172171
{
173-
const auto &proxy = config().proxy();
172+
const auto &proxy = m_config.proxy();
174173
if(!proxy.is_default())
175174
{
176175
return pplx::task_from_exception<void>(websocket_exception("Only a default proxy server is supported."));
@@ -296,7 +295,7 @@ class winrt_client : public _websocket_client_impl, public std::enable_shared_fr
296295
// First try to acquire the data (Get a pointer to the next already allocated contiguous block of data)
297296
// If acquire succeeds, send the data over the socket connection, there is no copy of data from stream to temporary buffer.
298297
// If acquire fails, copy the data to a temporary buffer managed by sp_allocated and send it over the socket connection.
299-
std::shared_ptr<uint8_t> sp_allocated(nullptr, [](uint8_t *) { } );
298+
std::shared_ptr<uint8_t> sp_allocated;
300299
size_t acquired_size = 0;
301300
uint8_t *ptr;
302301
auto read_task = pplx::task_from_result();
@@ -324,7 +323,7 @@ class winrt_client : public _websocket_client_impl, public std::enable_shared_fr
324323
}
325324
else
326325
{
327-
// Acquire succeeded, assign the acquired pointer to sp_allocated. Keep an empty custom destructor
326+
// Acquire succeeded, assign the acquired pointer to sp_allocated. Use an empty custom destructor
328327
// so that the data is not released when sp_allocated goes out of scope. The streambuf will manage its memory.
329328
sp_allocated.reset(ptr, [](uint8_t *) {});
330329
}

Release/tests/Functional/websockets/client/authentication_tests.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,8 @@ TEST_FIXTURE(uri_address, auth_with_credentials, "Ignore", "245")
8686
}
8787
#endif
8888

89-
// Secure websockets on implemented yet on non-WinRT - 255
90-
#if defined(__cplusplus_winrt)
91-
// Send and receive text message over SSL
89+
// Secure websockets on implemented yet on non-WinRT Windows - 255
90+
#if defined(__cplusplus_winrt) || !defined(_MS_WINDOWS)
9291
TEST_FIXTURE(uri_address, ssl_test)
9392
{
9493
websocket_client client;

0 commit comments

Comments
 (0)