diff --git a/Source/cryptalgo/SecureSocketPort.cpp b/Source/cryptalgo/SecureSocketPort.cpp index 764b94398..5299331d3 100644 --- a/Source/cryptalgo/SecureSocketPort.cpp +++ b/Source/cryptalgo/SecureSocketPort.cpp @@ -131,36 +131,46 @@ SecureSocketPort::Handler::~Handler() { } uint32_t SecureSocketPort::Handler::Initialize() { - uint32_t success = Core::ERROR_NONE; - - _context = SSL_CTX_new(TLS_method()); - - _ssl = SSL_new(static_cast(_context)); - SSL_set_fd(static_cast(_ssl), static_cast(*this).Descriptor()); - SSL_CTX_set_options(static_cast(_context), SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); - - // Trust the same certificates as any other application - if (SSL_CTX_set_default_verify_paths(static_cast(_context)) == 1) { - success = Core::SocketPort::Initialize(); - } else { - TRACE_L1("OpenSSL failed to load certificate store"); - success = Core::ERROR_GENERAL; + uint32_t success = Core::ERROR_GENERAL; + + if ((_context = SSL_CTX_new(TLS_method())) != nullptr) { + if ( // Returns bit-mask after adding options + ((SSL_CTX_set_options(static_cast(_context), SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3) & (SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3)) == (SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3)) + && // Trust the same certificates as any other application + (SSL_CTX_set_default_verify_paths(static_cast(_context)) == 1) + && ((_ssl = SSL_new(static_cast(_context))) != nullptr) + && (SSL_set_fd(static_cast(_ssl), static_cast(*this).Descriptor()) == 1) + ) { + success = Core::SocketPort::Initialize(); + } else { + TRACE_L1("OpenSSL failed to set protocol level or load certificate store"); + success = Core::ERROR_GENERAL; + } } return success; } int32_t SecureSocketPort::Handler::Read(uint8_t buffer[], const uint16_t length) const { - int32_t result = SSL_read(static_cast(_ssl), buffer, length); + int32_t result = 0; - if (_handShaking != CONNECTED) { - const_cast(*this).Update(); + if (_handShaking == CONNECTED) { + int value = SSL_read(static_cast(_ssl), buffer, length); + result = (value > 0 ? value : 0); } + return (result); } int32_t SecureSocketPort::Handler::Write(const uint8_t buffer[], const uint16_t length) { - return (SSL_write(static_cast(_ssl), buffer, length)); + int32_t result = 0; + + if (_handShaking == CONNECTED) { + int value = (SSL_write(static_cast(_ssl), buffer, length)); + result = (value > 0 ? value : 0); + } + + return (result); } @@ -212,29 +222,26 @@ void SecureSocketPort::Handler::ValidateHandShake() { } void SecureSocketPort::Handler::Update() { - if (IsOpen() == true) { - int result; - + if (IsOpen() == true && _ssl != nullptr) { if (_handShaking == IDLE) { - SSL_set_tlsext_host_name(static_cast(_ssl), RemoteNode().HostName().c_str()); - result = SSL_connect(static_cast(_ssl)); - if (result == 1) { + int result{0}; + + if( (result = SSL_set_tlsext_host_name(static_cast(_ssl), RemoteNode().HostName().c_str()) == 1) + && (result = SSL_connect(static_cast(_ssl)) == 1) + ) { ValidateHandShake(); - } - else { + } else { // Support non-blocking SocketPorts, result taken from SSL_connect result = SSL_get_error(static_cast(_ssl), result); if ((result == SSL_ERROR_WANT_READ) || (result == SSL_ERROR_WANT_WRITE)) { _handShaking = EXCHANGE; } } - } - else if (_handShaking == EXCHANGE) { + } else if (_handShaking == EXCHANGE) { if (SSL_do_handshake(static_cast(_ssl)) == 1) { ValidateHandShake(); } } - } - else if (_ssl != nullptr) { + } else if (_ssl != nullptr) { _handShaking = IDLE; _parent.StateChange(); } diff --git a/Source/cryptalgo/SecureSocketPort.h b/Source/cryptalgo/SecureSocketPort.h index 280a44ae3..49c5d5ed1 100644 --- a/Source/cryptalgo/SecureSocketPort.h +++ b/Source/cryptalgo/SecureSocketPort.h @@ -105,10 +105,14 @@ namespace Crypto { // Signal a state change, Opened, Closed or Accepted void StateChange() override { + if (IsOpen() && _context == nullptr) { // Initialize may not yet have happened + Initialize(); + } - ASSERT(_context != nullptr); - Update(); - }; + if (_ssl != nullptr) { + Update(); + } + }; inline uint32_t Callback(IValidator* callback) { uint32_t result = Core::ERROR_ILLEGAL_STATE; diff --git a/Tests/unit/core/test_websocket.cpp b/Tests/unit/core/test_websocket.cpp new file mode 100644 index 000000000..a2cb1f4f7 --- /dev/null +++ b/Tests/unit/core/test_websocket.cpp @@ -0,0 +1,1151 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#ifndef MODULE_NAME +#include "../Module.h" +#endif + +#include +#include +#include + +#include "../IPTestAdministrator.h" + +namespace Thunder { +namespace Tests { +namespace Core { + + class CustomSocketStream : public ::Thunder::Core::SocketStream { + public: + CustomSocketStream( + const SOCKET& socket + , const ::Thunder::Core::NodeId& localNode + , const uint16_t sendBufferSize + , const uint16_t receiveBufferSize + , const std::string& prefix + ) + : SocketStream(false, socket, localNode, sendBufferSize, receiveBufferSize) + , _prefix { prefix } + { + } + + CustomSocketStream( + const bool + , const ::Thunder::Core::NodeId& localNode + , const ::Thunder::Core::NodeId& remoteNode + , const uint16_t sendBufferSize + , const uint16_t receiveBufferSize + , const std::string& prefix + ) + : SocketStream(false, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) + , _prefix { prefix } + { + } + + ~CustomSocketStream() + { +#ifdef _VERBOSE + std::cout.flush(); +#endif + } + + // Raw TCP data + int32_t Read(uint8_t buffer[], const uint16_t length) const override + { +#ifdef _VERBOSE + std::cout << std::dec <<__LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; +#endif + + int32_t count = SocketPort::Read(buffer, length); + +#ifdef _VERBOSE + if (count > 0) { + std::cout << " |--> buffer ( " << count << "/" << length << " ) = "; + for (int32_t index = 0; index < count; index++) { + std::cout << std::hex << static_cast(buffer[index]) << " "; + } + std::cout << "\n"; + } +#endif + + return count; + } + + // Raw TCP data + int32_t Write(const uint8_t buffer[], const uint16_t length) override + { +#ifdef _VERBOSE + std::cout << std::dec <<__LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; +#endif + + int32_t count = SocketPort::Write(buffer, length); + +#ifdef _VERBOSE + if (count > 0) { + std::cout << " |--> buffer ( " << count << "/"<< length <<" ) = "; + for (int32_t index = 0; index < count; index++) { + std::cout << std::hex << static_cast(buffer[index]) << " "; + } + std::cout << "\n"; + } +#endif + + return count; + } + + private: + + const std::string _prefix; + }; + + template + class WebSocketClient : public ::Thunder::Web::WebSocketClientType { + public : + + template + WebSocketClient( + const string& path + , const string& protocol + , const string& query + , const string& origin + , const bool binary + , const bool masking + , Args&&... args + ) + : ::Thunder::Web::WebSocketClientType(path, protocol, query, origin, binary, masking, /* */ std::forward(args)... /**/) + { + } + + ~WebSocketClient() override = default; + + // Non-idle then data available to send + bool IsIdle() const override { return _post.size() == 0; } + + // Allow for eventfull state updates in this class + void StateChange() override + { +#ifdef _VERBOSE + std::cout << std::dec << __LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; + + // Socket port open AND upgraded to WebSocket + std::cout << " |--> IsOpen() = " << this->IsOpen() << "\n"; + // Link will not accept new messages as they arrive + std::cout << " |--> IsSuspended() = " << this->IsSuspended() << "\n"; + // Socket has been closed (removed), link cannot receive new TCP data + std::cout << " |--> IsClosed() = " << this->IsClosed() << "\n"; + // Regular HTTP connection, no upgraded connection + std::cout << " |--> IsWebServer() = " << this->IsWebServer() << "\n"; + // Upgrade in progress + std::cout << " |--> IsUpgrading() = " << this->IsUpgrading() << "\n"; + // Upgraded connection + std::cout << " |--> IsWebSocket() = " << this->IsWebSocket() << "\n"; + // Finishing frame received + std::cout << " |--> IsCompleted() = " << this->IsCompleted() << "\n"; +#endif + } + + // Reflects payload, effective after upgrade + uint16_t SendData(uint8_t* dataFrame, const uint16_t maxSendSize) override + { +#ifdef _VERBOSE + std::cout << std::dec << __LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; +#endif + + size_t count = 0; + + if ( dataFrame != nullptr + && maxSendSize > 0 + && !IsIdle() + && ::Thunder::Web::WebSocketClientType::IsOpen() + && ::Thunder::Web::WebSocketClientType::IsWebSocket() // Redundant, covered by IsOpen + && ::Thunder::Web::WebSocketClientType::IsCompleted() + ) { + std::basic_string& message = _post.front(); + + count = std::min(message.size(), static_cast(maxSendSize)); + + /* void* */ memcpy(dataFrame, message.data(), count); + +#ifdef _VERBOSE + std::cout << " |--> dataFrame ( " << count << "/" << maxSendSize << " ) = "; + for (int32_t index = 0; index < count; index++) { + std::cout << std::hex << static_cast(dataFrame[index]) << " "; + } + std::cout << "\n"; +#endif + + if (count == message.size()) { + /* iterator */ _post.erase(_post.begin()); + } else { + /* this */ message.erase(0, count); + + // Trigger a call to SendData for remaining data + ::Thunder::Web::WebSocketClientType::Link().Trigger(); + } + } + + return count; + } + + // Reflects payload, effective after upgrade + uint16_t ReceiveData(uint8_t* dataFrame, const uint16_t receivedSize) override + { +#ifdef _VERBOSE + std::cout << std::dec << __LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; + + if (receivedSize > 0) { + std::cout << " |--> dataFrame ( " << receivedSize << " ) = "; + for (int32_t index = 0; index < receivedSize; index++) { + std::cout << std::hex << static_cast(dataFrame[index]) << " "; + } + std::cout << "\n"; + } +#endif + + // Echo the data in reverse order + + std::basic_string message{ dataFrame, receivedSize }; + + std::reverse(message.begin(), message.end()); + + return Submit(message) ? message.size() : 0; + } + + bool Submit(const std::basic_string& message) + { + size_t count = _post.size(); + + _post.emplace_back(message); + + ::Thunder::Web::WebSocketClientType::Link().Trigger(); + + return count < _post.size(); + } + + private: + + std::vector> _post; // Send message queue + }; + + template + class WebSocketServer : public ::Thunder::Web::WebSocketServerType { + public : + + // SocketServerType defines SocketHandler of type SocketListener. SocketListener triggers Accept on StateChange, effectively, calling SocketServerType::Accept(SOCKET, NodeId) which creates a WebSocketServer with these parameters + WebSocketServer(const SOCKET& socket, const ::Thunder::Core::NodeId remoteNode, ::Thunder::Core::SocketServerType>*) + // Initially this should be defined as a regular TCP socket + : ::Thunder::Web::WebSocketServerType(false /* binary*/, false /*masking */, socket, remoteNode, SENDBUFFERSIZE /* send buffer size */, RECEIVEBUFFERSIZE /* receive buffer size */, "WebSocketServerType") + { + } + + ~WebSocketServer() override = default; + + // Non-idle then data available to send + bool IsIdle() const override { return _post.size() == 0; } + + // Allow for eventfull state updates in this class + void StateChange() override + { +#ifdef _VERBOSE + std::cout << std::dec << __LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; + + // Socket port open AND upgraded to WebSocket + std::cout << " |--> IsOpen() = " << this->IsOpen() << "\n"; + // Link will not accept new messages as they arrive + std::cout << " |--> IsSuspended() = " << this->IsSuspended() << "\n"; + // Socket has been closed (removed), link cannot receive new TCP data + std::cout << " |--> IsClosed() = " << this->IsClosed() << "\n"; + // Regular HTTP connection, no upgraded connection + std::cout << " |--> IsWebServer() = " << this->IsWebServer() << "\n"; + // Upgrade in progress + std::cout << " |--> IsUpgrading() = " << this->IsUpgrading() << "\n"; + // Upgraded connection + std::cout << " |--> IsWebSocket() = " << this->IsWebSocket() << "\n"; + // Finishing frame received + std::cout << " |--> IsCompleted() = " << this->IsCompleted() << "\n"; +#endif + } + + // Reflects payload, effective after upgrade + uint16_t SendData(uint8_t* dataFrame, const uint16_t maxSendSize) override + { +#ifdef _VERBOSE + std::cout << std::dec << __LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; +#endif + + size_t count = 0; + + if ( dataFrame != nullptr + && maxSendSize > 0 + && !IsIdle() + && ::Thunder::Web::WebSocketServerType::IsOpen() + && ::Thunder::Web::WebSocketServerType::IsWebSocket() // Redundant, covered by IsOpen + && ::Thunder::Web::WebSocketServerType::IsCompleted() + ) { + std::basic_string& message = _post.front(); + + count = std::min(message.size(), static_cast(maxSendSize)); + + /* void* */ memcpy(dataFrame, message.data(), count); + +#ifdef _VERBOSE + std::cout << " |--> dataFrame (" << count << " ) = "; + for (int32_t index = 0; index < count; index++) { + std::cout << std::hex << static_cast(dataFrame[index]) << " "; + } + std::cout << "\n"; +#endif + + if (count == message.size()) { + /* iterator */ _post.erase(_post.begin()); + } else { + /* this */ message.erase(0, count); + + // Trigger a call to SendData for remaining data + ::Thunder::Web::WebSocketServerType::Link().Trigger(); + } + } + + return count; + } + + // Reflects payload, effective after upgrade + uint16_t ReceiveData(uint8_t* dataFrame, const uint16_t receivedSize) override { +#ifdef _VERBOSE + std::cout << std::dec << __LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; +#endif + + if (receivedSize > 0) { + _response.emplace_back(std::basic_string{ dataFrame, receivedSize }); + +#ifdef _VERBOSE + std::cout << " |--> dataFrame ( " << receivedSize << " ) = "; + for (int32_t index = 0; index < receivedSize; index++) { + std::cout << std::hex << static_cast(dataFrame[index]) << " "; + } + std::cout << "\n"; +#endif + } + + return receivedSize; + } + + // Put data in the queue to send (to the (connected) client) + bool Submit(const std::basic_string& message) + { + size_t count = _post.size(); + + _post.emplace_back(message); + + // Trigger a call to SendData + ::Thunder::Web::WebSocketServerType::Link().Trigger(); + + return count < _post.size(); + } + + std::basic_string Response() + { +#ifdef _VERBOSE + std::cout << std::dec << __LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; +#endif + + std::basic_string message; + + if (_response.size() > 0) { + message = _response.front(); + _response.erase(_response.begin()); + +#ifdef _VERBOSE + std::cout << " |--> message ( " << message.size() << " ) = "; + for (int32_t index = 0; index < message.size(); index++) { + std::cout << std::hex << static_cast(message[index]) << " "; + } + std::cout << "\n"; +#endif + } + + return message; + } + + private: + + std::vector> _post; // Send message queue + std::vector> _response; // Receive message queue + }; + + class CustomSecureSocketStream : public ::Thunder::Crypto::SecureSocketPort { + private : + + // Validat eclient certificate + class Validator : public ::Thunder::Crypto::SecureSocketPort::IValidator { + public: + + Validator() = default; + ~Validator() = default; + + bool Validate(const Certificate& certificate) const override { + // Print certificate properties +#ifdef _VERBOSE + std::cout << std::dec <<__LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; + std::cout << " |--> Issuer = " << certificate.Issuer() << "\n"; + std::cout << " |--> Subject = " << certificate.Subject() << "\n"; + std::cout << " |--> Valid from = " << certificate.ValidFrom().ToRFC1123() << "\n"; + std::cout << " |--> Valid until = " << certificate.ValidTill().ToRFC1123() << "\n"; +#endif + return true; // Always accept + } + }; + + public : + + // In essence, all parameters to SecureSocket are passed to a base class SocketPort + CustomSecureSocketStream( + const SOCKET& socket + , const ::Thunder::Core::NodeId& localNode + , const uint16_t sendBufferSize + , const uint16_t receiveBufferSize + , const std::string& prefix + ) + : ::Thunder::Crypto::SecureSocketPort(::Thunder::Core::SocketPort::STREAM, socket, localNode, sendBufferSize, receiveBufferSize) + , _prefix{ prefix } + , _validator{} + { + // Validate custom (sefl signed) certificates + uint32_t result = Callback(&_validator); + } + + CustomSecureSocketStream( + const bool + , const ::Thunder::Core::NodeId& localNode + , const ::Thunder::Core::NodeId& remoteNode + , const uint16_t sendBufferSize + , const uint16_t receiveBufferSize + , const std::string& prefix + ) + : ::Thunder::Crypto::SecureSocketPort(::Thunder::Core::SocketPort::STREAM, localNode, remoteNode, sendBufferSize, receiveBufferSize, sendBufferSize, receiveBufferSize) + , _prefix{ prefix } + , _validator{} + { + // Validate custom (self signed) client certificates + uint32_t result = Callback(&_validator); + } + + ~CustomSecureSocketStream() + { + #ifdef _VERBOSE + std::cout.flush(); +#endif + } + + private: + const std::string _prefix; + Validator _validator; + }; + + TEST(WebSocket, DISABLED_OpeningServerPort) + { + const TCHAR localHostName[] {"127.0.0.1"}; + + constexpr uint16_t tcpServerPort {12345}; // TCP, default 80 or 443 (SSL) + constexpr uint32_t tcpProtocol {0}; // HTTP or HTTPS but can only be set on raw sockets + + // The minimum size is determined by the HTTP upgrade process. The limit here is above that threshold. + constexpr uint16_t sendBufferSize {1024}; + constexpr uint16_t receiveBufferSize {1024}; + + constexpr uint32_t maxWaitTimeMs = 4000; + + const ::Thunder::Core::NodeId localNode {localHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + // This is a listening socket as result of using SocketServerType which enables listening + ::Thunder::Core::SocketServerType> server(localNode /* listening node*/); + + ASSERT_EQ(server.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + SleepMs(maxWaitTimeMs); + + // Obtain the endpoint at the server side for each (remotely) connected client + auto it = server.Clients(); + + if (it.Next()) { + // Unless a client has send an upgrade request we cannot send data out although we might be calling WebSocket functionality + + if (it.Client()->IsOpen()) { + // No data should be transferred to the remote client + } + } + + SleepMs(maxWaitTimeMs); + + EXPECT_EQ(server.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + } + + TEST(WebSocket, DISABLED_OpeningClientPort) + { + const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol + const std::string webSocketURIQuery; // HTTP URI part, absent query allowe + const std::string webSocketOrigin; // Optional, set by browser clients + constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + constexpr bool masking {true}; // Flag set by client to enable masking + + const TCHAR remoteHostName[] {"127.0.0.1"}; + + constexpr uint16_t tcpServerPort {12345}; // TCP, default 80 or 443 (SSL) + constexpr uint32_t tcpProtocol {0}; // HTTP or HTTPS but can only be set on raw sockets + + constexpr bool rawSocket {false}; + + // The minimum size is determined by the HTTP upgrade process. The limit here is above that threshold. + constexpr uint16_t sendBufferSize {1024}; + constexpr uint16_t receiveBufferSize {1024}; + + constexpr uint32_t maxWaitTimeMs = 4000; + + const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize, "WebSocketClient"); + + SleepMs(maxWaitTimeMs); + + ASSERT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + SleepMs(maxWaitTimeMs); + + EXPECT_EQ(client.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + } + + TEST(WebSocket, DISABLED_UnsecuredSocketUpgrade) + { + const TCHAR hostName[] {"127.0.0.1"}; + + // Some aliases + const auto& remoteHostName = hostName; + const auto& localHostName = hostName; + + constexpr uint16_t tcpServerPort {12345}; // TCP, default 80 or 443 (SSL) + constexpr uint32_t tcpProtocol {0}; // HTTP or HTTPS but can only be set on raw sockets + + // The minimum size is determined by the HTTP upgrade process. The limit here is above that threshold. + constexpr uint16_t sendBufferSize {1024}; + constexpr uint16_t receiveBufferSize {1024}; + + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 8, maxWaitTimeMs = 8000, maxInitTime = 2000; + constexpr uint8_t maxRetries = 10; + + IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { + const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol + const std::string webSocketURIQuery; // HTTP URI part, absent query allowe + const std::string webSocketOrigin; // Optional, set by browser clients + constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + constexpr bool masking {true}; // Flag set by client to enable masking + + constexpr bool rawSocket {false}; + + const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize, "WebSocketClient"); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(client.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator::Callback callback_parent = [&](IPTestAdministrator& testAdmin) { + const ::Thunder::Core::NodeId localNode {localHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + // This is a listening socket as result of using SocketServerType which enables listening + ::Thunder::Core::SocketServerType> server(localNode /* listening node*/); + + ASSERT_EQ(server.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + // A small delay so the child can be set up +// SleepMs(maxInitTime); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(server.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator testAdmin(callback_parent, callback_child, initHandshakeValue, maxWaitTime); + + // Code after this line is executed by both parent and child + + ::Thunder::Core::Singleton::Dispose(); + } + + TEST(WebSocket, DISABLED_UnsecuredSocketServerPingClientPong) + { + const TCHAR hostName[] {"127.0.0.1"}; + + // Some aliases + const auto& remoteHostName = hostName; + const auto& localHostName = hostName; + + constexpr uint16_t tcpServerPort {12346}; // TCP, default 80 or 443 (SSL) + constexpr uint32_t tcpProtocol {0}; // HTTP or HTTPS but can only be set on raw sockets + + // The minimum size is determined by the HTTP upgrade process. The limit here is above that threshold. + constexpr uint16_t sendBufferSize {1024}; + constexpr uint16_t receiveBufferSize {1024}; + + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 8, maxWaitTimeMs = 8000, maxInitTime = 2000; + constexpr uint8_t maxRetries = 10; + + IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { + const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol + const std::string webSocketURIQuery; // HTTP URI part, absent query allowe + const std::string webSocketOrigin; // Optional, set by browser clients + constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + constexpr bool masking {true}; // Flag set by client to enable masking + + constexpr bool rawSocket {false}; + + const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize, "WebSocketClient"); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + // Avoid premature shutdown() at the other side + SleepMs(maxWaitTimeMs); + + EXPECT_EQ(client.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator::Callback callback_parent = [&](IPTestAdministrator& testAdmin) { + const ::Thunder::Core::NodeId localNode {localHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + // This is a listening socket as result of using SocketServerType which enables listening + ::Thunder::Core::SocketServerType> server(localNode /* listening node*/); + + ASSERT_EQ(server.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + // A small delay so the child can be set up +// SleepMs(maxInitTime); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + // Obtain the endpoint at the server side for each (remotely) connected client + auto it = server.Clients(); + + if (it.Next()) { + // Unless a client has send an upgrade request we cannot send data out although we might be calling WebSocket functionality + + if (it.Client()->IsOpen()) { + /* void */ it.Client()->Ping(); + } + } + + // Allow some time to receive the PONG response + SleepMs(maxWaitTimeMs); + + EXPECT_EQ(server.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator testAdmin(callback_parent, callback_child, initHandshakeValue, maxWaitTime); + + // Code after this line is executed by both parent and child + + ::Thunder::Core::Singleton::Dispose(); + } + + TEST(WebSocket, DISABLED_UnsecuredSocketServerUnsollicitedPong) + { + const TCHAR hostName[] {"127.0.0.1"}; + + // Some aliases + const auto& remoteHostName = hostName; + const auto& localHostName = hostName; + + constexpr uint16_t tcpServerPort {12346}; // TCP, default 80 or 443 (SSL) + constexpr uint32_t tcpProtocol {0}; // HTTP or HTTPS but can only be set on raw sockets + + // The minimum size is determined by the HTTP upgrade process. The limit here is above that threshold. + constexpr uint16_t sendBufferSize {1024}; + constexpr uint16_t receiveBufferSize {1024}; + + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 8, maxWaitTimeMs = 8000, maxInitTime = 2000; + constexpr uint8_t maxRetries = 10; + + IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { + const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol + const std::string webSocketURIQuery; // HTTP URI part, absent query allowe + const std::string webSocketOrigin; // Optional, set by browser clients + constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + constexpr bool masking {true}; // Flag set by client to enable masking + + constexpr bool rawSocket {false}; + + const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize, "WebSocketClient"); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + // Allow some time to receive the PONG + SleepMs(maxWaitTimeMs); + + EXPECT_EQ(client.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator::Callback callback_parent = [&](IPTestAdministrator& testAdmin) { + const ::Thunder::Core::NodeId localNode {localHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + // This is a listening socket as result of using SocketServerType which enables listening + ::Thunder::Core::SocketServerType> server(localNode /* listening node*/); + + ASSERT_EQ(server.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + // A small delay so the child can be set up +// SleepMs(maxInitTime); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + // Obtain the endpoint at the server side for each (remotely) connected client + auto it = server.Clients(); + + if (it.Next()) { + // Unless a client has send an upgrade request we cannot send data out although we might be calling WebSocket functionality + + if (it.Client()->IsOpen()) { + /* void */ it.Client()->Pong(); + } + } + + // Avoid premature shutdown() at the receiving side + SleepMs(maxWaitTimeMs); + + EXPECT_EQ(server.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator testAdmin(callback_parent, callback_child, initHandshakeValue, maxWaitTime); + + // Code after this line is executed by both parent and child + + ::Thunder::Core::Singleton::Dispose(); + } + + TEST(WebSocket, DISABLED_UnsecuredSocketClientUnsollicitedPong) + { + const TCHAR hostName[] {"127.0.0.1"}; + + // Some aliases + const auto& remoteHostName = hostName; + const auto& localHostName = hostName; + + constexpr uint16_t tcpServerPort {12346}; // TCP, default 80 or 443 (SSL) + constexpr uint32_t tcpProtocol {0}; // HTTP or HTTPS but can only be set on raw sockets + + // The minimum size is determined by the HTTP upgrade process. The limit here is above that threshold. + constexpr uint16_t sendBufferSize {1024}; + constexpr uint16_t receiveBufferSize {1024}; + + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 8, maxWaitTimeMs = 8000, maxInitTime = 2000; + constexpr uint8_t maxRetries = 10; + + IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { + const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol + const std::string webSocketURIQuery; // HTTP URI part, absent query allowe + const std::string webSocketOrigin; // Optional, set by browser clients + constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + constexpr bool masking {true}; // Flag set by client to enable masking + + constexpr bool rawSocket {false}; + + const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize, "WebSocketClient"); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + client.Pong(); + + // Avoid premature shutdown() at the other side + SleepMs(maxWaitTimeMs); + + EXPECT_EQ(client.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator::Callback callback_parent = [&](IPTestAdministrator& testAdmin) { + const ::Thunder::Core::NodeId localNode {localHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + // This is a listening socket as result of using SocketServerType which enables listening + ::Thunder::Core::SocketServerType> server(localNode /* listening node*/); + + ASSERT_EQ(server.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + // A small delay so the child can be set up +// SleepMs(maxInitTime); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + // Obtain the endpoint at the server side for each (remotely) connected client + auto it = server.Clients(); + + // Allow some time to receive the PONG + SleepMs(maxWaitTimeMs); + + EXPECT_EQ(server.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator testAdmin(callback_parent, callback_child, initHandshakeValue, maxWaitTime); + + // Code after this line is executed by both parent and child + + ::Thunder::Core::Singleton::Dispose(); + } + + TEST(WebSocket, DISABLED_UnsecuredSocketDataExchange) + { + const TCHAR hostName[] {"127.0.0.1"}; + + // Some aliases + const auto& remoteHostName = hostName; + const auto& localHostName = hostName; + + constexpr uint16_t tcpServerPort {12346}; // TCP, default 80 or 443 (SSL) + constexpr uint32_t tcpProtocol {0}; // HTTP or HTTPS but can only be set on raw sockets + + // The minimum size is determined by the HTTP upgrade process. The limit here is above that threshold. + constexpr uint16_t sendBufferSize {1024}; + constexpr uint16_t receiveBufferSize {1024}; + + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 8, maxWaitTimeMs = 8000, maxInitTime = 2000; + constexpr uint8_t maxRetries = 10; + + IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { + const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol + const std::string webSocketURIQuery; // HTTP URI part, absent query allowe + const std::string webSocketOrigin; // Optional, set by browser clients + constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + constexpr bool masking {true}; // Flag set by client to enable masking + + constexpr bool rawSocket {false}; + + const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize, "WebSocketClient"); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + // Avoid premature shutdown() at the other side + SleepMs(maxWaitTimeMs); + + EXPECT_EQ(client.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator::Callback callback_parent = [&](IPTestAdministrator& testAdmin) { + const ::Thunder::Core::NodeId localNode {localHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + // This is a listening socket as result of using SocketServerType which enables listening + ::Thunder::Core::SocketServerType> server(localNode /* listening node*/); + + ASSERT_EQ(server.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + // A small delay so the child can be set up +// SleepMs(maxInitTime); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + // Obtain the endpoint at the server side for each (remotely) connected client + auto it = server.Clients(); + + constexpr uint8_t data[] = { 0xF, 0xE, 0xD, 0xC, 0xB, 0xA, 0x9, 0x8, 0x7, 0x6, 0x5, 0x3, 0x2, 0x1, 0x0 }; + + if (it.Next()) { + // Unless a client has send an upgrade request we cannot send data out although we might be calling WebSocket functionality + + if (it.Client()->IsOpen()) { + /* bool */ it.Client()->Submit(std::basic_string{ data, sizeof(data) }); + } + } + + // Allow some time to receive the response + SleepMs(maxWaitTimeMs); + + std::basic_string response{ data, sizeof(data) }; + std::reverse(response.begin(), response.end()); + + // A simple poll to keep it simple + EXPECT_TRUE( it.IsValid() + && (it.Client()->Response() == response) + ); + + EXPECT_EQ(server.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator testAdmin(callback_parent, callback_child, initHandshakeValue, maxWaitTime); + + // Code after this line is executed by both parent and child + + ::Thunder::Core::Singleton::Dispose(); + } + + TEST(WebSocket, DISABLED_UnsecuredSocketMultiFrameDataExchange) + { + const TCHAR hostName[] {"127.0.0.1"}; + + // Some aliases + const auto& remoteHostName = hostName; + const auto& localHostName = hostName; + + constexpr uint16_t tcpServerPort {12346}; // TCP, default 80 or 443 (SSL) + constexpr uint32_t tcpProtocol {0}; // HTTP or HTTPS but can only be set on raw sockets + + // The minimum size is determined by the HTTP upgrade process. The limit here is above that threshold. + constexpr uint16_t sendBufferSize {75}; + constexpr uint16_t receiveBufferSize {75}; + + constexpr uint32_t initHandshakeValue = 0, maxWaitTime = 8, maxWaitTimeMs = 8000, maxInitTime = 2000; + constexpr uint8_t maxRetries = 10; + + constexpr uint16_t nagglesTimeoutMs = 250; // Typical is 200 milliseconds + + IPTestAdministrator::Callback callback_child = [&](IPTestAdministrator& testAdmin) { + const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol + const std::string webSocketURIQuery; // HTTP URI part, absent query allowe + const std::string webSocketOrigin; // Optional, set by browser clients + constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + constexpr bool masking {true}; // Flag set by client to enable masking + + constexpr bool rawSocket {false}; + + const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize, "WebSocketClient"); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + ASSERT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + +#ifdef _VERBOSE + std::cout << std::dec <<__LINE__ << " : " << __PRETTY_FUNCTION__ << "\n"; + std::cout << " |--> SendBufferSize = " << client.Link().SendBufferSize() << "\n"; + std::cout << " |--> ReceiveBufferSize = " << client.Link().ReceiveBufferSize() << "\n"; + std::cout << " |--> SocketSendBufferSize = " << client.Link().SocketSendBufferSize() << "\n"; + std::cout << " |--> SocketReceiveBufferSize = " << client.Link().SocketReceiveBufferSize() << "\n"; +#endif + + // Avoid premature shutdown() at the other side + SleepMs(maxWaitTimeMs); + + EXPECT_EQ(client.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator::Callback callback_parent = [&](IPTestAdministrator& testAdmin) { + const ::Thunder::Core::NodeId localNode {localHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + // This is a listening socket as result of using SocketServerType which enables listening + ::Thunder::Core::SocketServerType> server(localNode /* listening node*/); + + ASSERT_EQ(server.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + // A small delay so the child can be set up +// SleepMs(maxInitTime); + + EXPECT_EQ(testAdmin.Signal(initHandshakeValue, maxRetries), ::Thunder::Core::ERROR_NONE); + + EXPECT_EQ(testAdmin.Wait(initHandshakeValue), ::Thunder::Core::ERROR_NONE); + + // Obtain the endpoint at the server side for each (remotely) connected client + auto it = server.Clients(); + + // Do not use '\0' as a marker as std::basic_strng<> assumes it is an end of string + + constexpr uint8_t data[] = { 0xF, 0xE, 0xD, 0xC, 0xB, 0xA, 0x9, 0x8, 0x7, 0x6, 0x5, 0x3, 0x2, 0x1, 0x10 }; + + std::basic_string message; + + if (it.Next()) { + // Unless a client has send an upgrade request we cannot send data out although we might be calling WebSocket functionality + + if (it.Client()->IsOpen()) { + // Construct a message larger than the buffer size to force use of continuation frames + size_t count = (it.Client()->Link().SendBufferSize() / sizeof(data) + 1 ) * sizeof(data); + + ASSERT_LE(count, message.max_size()); + + message.resize(count); + + for (size_t index = 0; index < count; index += sizeof(data) ) { + message.replace(index, sizeof(data), data); + } + + /* bool */ it.Client()->Submit(std::basic_string{ message.data(), count }); + } + } + + // Allow some time to receive the response + SleepMs(maxWaitTimeMs); + + std::reverse(message.begin(), message.end()); + + std::basic_string response; + + response.reserve( message.size() ); + + // A simple poll to keep it simple + for (int8_t retry = 0; retry < maxRetries; ++retry) { + SleepMs(nagglesTimeoutMs); // Naggle's typical delay, perhaps a bit more + + if (it.IsValid()) { + response = it.Client()->Response() + response; + } + } + + EXPECT_TRUE( response.size() == message.size() + && response == message + ); + + EXPECT_EQ(server.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + }; + + IPTestAdministrator testAdmin(callback_parent, callback_child, initHandshakeValue, maxWaitTime); + + // Code after this line is executed by both parent and child + + ::Thunder::Core::Singleton::Dispose(); + } + + TEST(WebSocket, DISABLED_OpeningSecuredServerPort) + { + const TCHAR localHostName[] {"127.0.0.1"}; + + constexpr uint16_t tcpServerPort {12345}; // TCP, default 80 or 443 (SSL) + constexpr uint32_t tcpProtocol {0}; // HTTP or HTTPS but can only be set on raw sockets + + // The minimum size is determined by the HTTP upgrade process. The limit here is above that threshold. + constexpr uint16_t sendBufferSize {1024}; + constexpr uint16_t receiveBufferSize {1024}; + + constexpr uint32_t maxWaitTimeMs = 4000; + + const ::Thunder::Core::NodeId localNode {localHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + // This is a listening socket as result of using SocketServerType which enables listening + ::Thunder::Core::SocketServerType> server(localNode /* listening node*/); + + ASSERT_EQ(server.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + +// SleepMs(maxWaitTimeMs); + + // Obtain the endpoint at the server side for each (remotely) connected client + auto it = server.Clients(); + + if (it.Next()) { + // Unless a client has send an upgrade request we cannot send data out although we might be calling WebSocket functionality + if (it.Client()->IsOpen()) { + // No data should be transferred to the remote client + } else { + } + } + + SleepMs(4*maxWaitTimeMs); + + + EXPECT_EQ(server.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + } + + TEST(WebSocket, OpeningSecuredClientPort) + { + const std::string webSocketURIPath; // HTTP URI part, empty path allowed + const std::string webSocketProtocol; // Optional HTTP field, WebSocket SubProtocol, ie, Sec-WebSocket-Protocol + const std::string webSocketURIQuery; // HTTP URI part, absent query allowe + const std::string webSocketOrigin; // Optional, set by browser clients + constexpr bool binary {false}; // Flag to indicate WebSocket opcode 0x1 (test frame) or 0x2 (binary frame) + constexpr bool masking {true}; // Flag set by client to enable masking + + const TCHAR remoteHostName[] {"127.0.0.1"}; + + constexpr uint16_t tcpServerPort {12345}; // TCP, default 80 or 443 (SSL) + constexpr uint32_t tcpProtocol {0}; // HTTP or HTTPS but can only be set on raw sockets + + constexpr bool rawSocket {false}; + + // The minimum size is determined by the HTTP upgrade process. The limit here is above that threshold. + constexpr uint16_t sendBufferSize {1024}; + constexpr uint16_t receiveBufferSize {1024}; + + constexpr uint32_t maxWaitTimeMs = 4000; + + const ::Thunder::Core::NodeId remoteNode {remoteHostName, tcpServerPort, ::Thunder::Core::NodeId::TYPE_IPV4, tcpProtocol}; + + WebSocketClient client(webSocketURIPath, webSocketProtocol, webSocketURIQuery, webSocketOrigin, false, true, rawSocket, remoteNode.AnyInterface(), remoteNode, sendBufferSize, receiveBufferSize, "WebSocketClient"); + +// SleepMs(maxWaitTimeMs); + + ASSERT_EQ(client.Open(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + + SleepMs(maxWaitTimeMs); + + EXPECT_EQ(client.Close(maxWaitTimeMs), ::Thunder::Core::ERROR_NONE); + } + +} // Core +} // Tests +} // Thunder