diff options
9 files changed, 1331 insertions, 33 deletions
diff --git a/src/components/transport_manager/include/transport_manager/cloud/cloud_device.h b/src/components/transport_manager/include/transport_manager/cloud/cloud_device.h index 47a82e7921..9c25be2a3e 100644 --- a/src/components/transport_manager/include/transport_manager/cloud/cloud_device.h +++ b/src/components/transport_manager/include/transport_manager/cloud/cloud_device.h @@ -43,22 +43,33 @@ namespace transport_manager { namespace transport_adapter { +struct CloudAppEndpoint { + std::string host; + std::string port; + std::string path; + std::string query; + std::string fragment; +}; + class CloudDevice : public Device { public: CloudDevice(std::string& host, std::string& port, std::string& name); + CloudDevice(CloudAppEndpoint endpoint, std::string& name); + virtual const std::string& GetHost() const; virtual const std::string& GetPort() const; + virtual const std::string GetTarget() const; + protected: virtual bool IsSameAs(const Device* other_device) const; virtual ApplicationList GetApplicationList() const; private: - const std::string host_; - const std::string port_; + const CloudAppEndpoint endpoint_; }; } // namespace transport_adapter diff --git a/src/components/transport_manager/src/cloud/cloud_device.cc b/src/components/transport_manager/src/cloud/cloud_device.cc index 35510f4cf8..c1ad186ded 100644 --- a/src/components/transport_manager/src/cloud/cloud_device.cc +++ b/src/components/transport_manager/src/cloud/cloud_device.cc @@ -42,7 +42,15 @@ CREATE_LOGGERPTR_GLOBAL(logger_, "TransportManager") CloudDevice::CloudDevice(std::string& host, std::string& port, std::string& name) - : Device(name, std::string(name)), host_(host), port_(port) {} + : Device(name, std::string(name)) + , endpoint_(CloudAppEndpoint{.host = host, + .port = port, + .path = "/", + .query = "", + .fragment = ""}) {} + +CloudDevice::CloudDevice(CloudAppEndpoint endpoint, std::string& name) + : Device(name, std::string(name)), endpoint_(endpoint) {} bool CloudDevice::IsSameAs(const Device* other) const { LOG4CXX_TRACE(logger_, "enter. device: " << other); @@ -54,12 +62,18 @@ bool CloudDevice::IsSameAs(const Device* other) const { return false; } - if (host_ != other_cloud_device->GetHost()) { + if (GetHost() != other_cloud_device->GetHost()) { + return false; + } + + if (GetPort() != other_cloud_device->GetPort()) { return false; } - if (port_ != other_cloud_device->GetPort()) { + + if (GetTarget() != other_cloud_device->GetTarget()) { return false; } + return true; } @@ -68,11 +82,15 @@ ApplicationList CloudDevice::GetApplicationList() const { } const std::string& CloudDevice::GetHost() const { - return host_; + return endpoint_.host; } const std::string& CloudDevice::GetPort() const { - return port_; + return endpoint_.port; +} + +const std::string CloudDevice::GetTarget() const { + return endpoint_.path + endpoint_.query + endpoint_.fragment; } } // namespace transport_adapter diff --git a/src/components/transport_manager/src/cloud/cloud_websocket_transport_adapter.cc b/src/components/transport_manager/src/cloud/cloud_websocket_transport_adapter.cc index 452a4c197c..622531b120 100644 --- a/src/components/transport_manager/src/cloud/cloud_websocket_transport_adapter.cc +++ b/src/components/transport_manager/src/cloud/cloud_websocket_transport_adapter.cc @@ -80,15 +80,20 @@ void CloudWebsocketTransportAdapter::CreateDevice(const std::string& uid) { return; } - // Port after second colon in valid endpoint string - std::size_t pos_port = uid.find(":"); - pos_port = uid.find(":", pos_port + 1); + std::string protocol_pattern = "(wss?)"; + std::string host_pattern = + "(([^?#%\\\\/@:\\s]{1,})\\:?([^?#%\\\\/@\\s]*)\\@?([^?#%\\\\/\\s]*))"; + std::string port_pattern = "(\\d{2,5})"; + // Optional parameters + std::string path_pattern = "((\\/[^\\/#?\\s]+)*)?\\/?"; + std::string query_pattern = "(\\?[^=&#\\s]*=?[^#\\s]*&?)?"; + std::string fragment_pattern = "(#[^\\s]*)?"; // Extract host and port from endpoint string - boost::regex group_pattern( - "(wss?:\\/\\/)([A-Z\\d\\.-]{2,}\\.?([A-Z]{2,})?)(:)(\\d{2,5})(\\/" - "[A-Z\\d\\.-]+)*\\/?", - boost::regex::icase); + boost::regex group_pattern(protocol_pattern + ":\\/\\/" + host_pattern + ":" + + port_pattern + path_pattern + query_pattern + + fragment_pattern, + boost::regex::icase); boost::smatch results; std::string str = uid; @@ -97,20 +102,30 @@ void CloudWebsocketTransportAdapter::CreateDevice(const std::string& uid) { return; } - std::string host = results[2]; - std::string port = results[5]; + LOG4CXX_DEBUG(logger_, "#Results: " << results.size()); + std::string results_str; + for (size_t i = 0; i < results.size(); i++) { + results_str += " R[" + std::to_string(i) + "]:"; + results_str += + (results[i].length() != 0) ? results[i] : std::string("<EMPTY>"); + } + LOG4CXX_DEBUG(logger_, "Results: " << results_str); - LOG4CXX_DEBUG(logger_, - "Results: " << results[0] << " " << results[1] << " " - << results[2] << " " << results[3] << " " - << results[4] << " " << results[5] << " "); std::string device_id = uid; + CloudAppEndpoint endpoint{.host = results[2], + .port = results[6], + .path = results[7] + "/", + .query = results[9], + .fragment = results[10]}; + LOG4CXX_DEBUG(logger_, - "Creating Cloud Device For Host: " << host - << " and Port: " << port); + "Creating Cloud Device For Host: " + << endpoint.host << " at Port: " << endpoint.port + << " with Target: " + << (endpoint.path + endpoint.query + endpoint.fragment)); - auto cloud_device = std::make_shared<CloudDevice>(host, port, device_id); + auto cloud_device = std::make_shared<CloudDevice>(endpoint, device_id); DeviceVector devices{cloud_device}; @@ -125,5 +140,5 @@ void CloudWebsocketTransportAdapter::CreateDevice(const std::string& uid) { return; } -} -} +} // namespace transport_adapter +} // namespace transport_manager diff --git a/src/components/transport_manager/src/cloud/websocket_client_connection.cc b/src/components/transport_manager/src/cloud/websocket_client_connection.cc index bb654822c3..1718e5ca1b 100644 --- a/src/components/transport_manager/src/cloud/websocket_client_connection.cc +++ b/src/components/transport_manager/src/cloud/websocket_client_connection.cc @@ -160,11 +160,11 @@ TransportAdapter::Error WebsocketClientConnection::Start() { // Perform websocket handshake if (cloud_properties.cloud_transport_type == "WS") { - ws_.handshake(host, "/", ec); + ws_.handshake(host, cloud_device->GetTarget(), ec); } #ifdef ENABLE_SECURITY else if (cloud_properties.cloud_transport_type == "WSS") { - wss_.handshake(host, "/", ec); + wss_.handshake(host, cloud_device->GetTarget(), ec); } #endif // ENABLE_SECURITY if (ec) { diff --git a/src/components/transport_manager/test/CMakeLists.txt b/src/components/transport_manager/test/CMakeLists.txt index 5288d8c697..f6e1212b36 100644 --- a/src/components/transport_manager/test/CMakeLists.txt +++ b/src/components/transport_manager/test/CMakeLists.txt @@ -46,6 +46,13 @@ set(EXCLUDE_PATHS raw_message_matcher.cc ) +if (NOT BUILD_CLOUD_APP_SUPPORT) + list(APPEND EXCLUDE_PATHS + ${CMAKE_CURRENT_SOURCE_DIR}/websocket_connection_test.cc + ${CMAKE_CURRENT_SOURCE_DIR}/sample_websocket_server.cc + ) +endif() + collect_sources(SOURCES "${CMAKE_CURRENT_SOURCE_DIR}" "${EXCLUDE_PATHS}") set(PLATFORM_DEPENDENT_SOURCES) diff --git a/src/components/transport_manager/test/include/transport_manager/cloud/sample_websocket_server.h b/src/components/transport_manager/test/include/transport_manager/cloud/sample_websocket_server.h new file mode 100644 index 0000000000..f785263c98 --- /dev/null +++ b/src/components/transport_manager/test/include/transport_manager/cloud/sample_websocket_server.h @@ -0,0 +1,162 @@ + +/* + * Copyright (c) 2019, Ford Motor Company + * 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 Ford Motor Company 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 THE COPYRIGHT HOLDER OR CONTRIBUTORS 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 SRC_COMPONENTS_TRANSPORT_MANAGER_TEST_INCLUDE_TRANSPORT_MANAGER_CLOUD_SAMPLE_WEBSOCKET_SERVER_H_ +#define SRC_COMPONENTS_TRANSPORT_MANAGER_TEST_INCLUDE_TRANSPORT_MANAGER_CLOUD_SAMPLE_WEBSOCKET_SERVER_H_ + +#include <boost/asio/bind_executor.hpp> +#include <boost/asio/ip/tcp.hpp> +#include <boost/asio/placeholders.hpp> +#include <boost/asio/ssl/stream.hpp> +#include <boost/asio/strand.hpp> +#include <boost/beast/core.hpp> +#include <boost/beast/websocket.hpp> +#include <boost/beast/websocket/ssl.hpp> +#include <boost/make_shared.hpp> +#include <boost/thread/thread.hpp> +#include <iostream> +#include <queue> +#include <set> +#include <string> +#include <thread> + +namespace sample { +namespace websocket { + +namespace beast = boost::beast; // from <boost/beast.hpp> +namespace http = beast::http; // from <boost/beast/http.hpp> +namespace websocket = beast::websocket; // from <boost/beast/websocket.hpp> +namespace net = boost::asio; // from <boost/asio.hpp> +namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp> +using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp> + +// Accepts incoming connections and launches the WSServer +class WSSession { + private: + class WSServer { + public: + explicit WSServer(tcp::socket&& socket); + void AddURLRoute(const std::string& route); + // Start the asynchronous operation + void Run(); + + private: + void OnWebsocketHandshake(const boost::system::error_code& ec); + void OnAccept(beast::error_code ec); + // Check if route can be handled by the server + bool CanHandleRoute(const std::string& route); + std::string ParseRouteFromTarget(const std::string& target); + + websocket::stream<tcp::socket> ws_; + beast::flat_buffer buffer_; + boost::asio::strand<boost::asio::io_context::executor_type> strand_; + http::request<http::string_body> req_; + std::set<std::string> url_routes_; + }; + + public: + WSSession(const std::string& address, uint16_t port); + // Start Accepting incoming connections + void Run(); + void Stop(); + // Add route endpoint which can be handled the server + void AddRoute(const std::string& route); + + private: + void on_accept(boost::system::error_code ec); + boost::asio::io_context ioc_; + const std::string& address_; + uint16_t port_; + tcp::acceptor acceptor_; + tcp::socket socket_; + beast::flat_buffer buffer_; + boost::asio::ip::tcp::endpoint endpoint_; + std::shared_ptr<WSServer> ws_; + std::queue<std::string> buffered_routes_; +}; + +// Accepts incoming connections and launches the sessions +class WSSSession { + private: + class WSSServer { + public: + // Take ownership of the socket + WSSServer(tcp::socket&& socket, ssl::context& ctx); + void AddURLRoute(const std::string& route); + // Start the asynchronous operation + void Run(); + + private: + void OnSSLHandshake(beast::error_code ec); + void OnWebsocketHandshake(const boost::system::error_code& ec); + void OnAccept(beast::error_code ec); + + // Check if route can be handled by the server + bool CanHandleRoute(const std::string& route); + std::string ParseRouteFromTarget(const std::string& target); + + websocket::stream<ssl::stream<tcp::socket> > wss_; + beast::flat_buffer buffer_; + http::request<http::string_body> req_; + std::set<std::string> url_routes_; + }; + + public: + WSSSession(const std::string& address, + uint16_t port, + const std::string& certificate, + const std::string& private_key); + // Start accepting incoming connections + void Run(); + void Stop(); + // Add route endpoint which can be handled the server + void AddRoute(const std::string& route); + + private: + void do_accept(); + void on_accept(boost::system::error_code ec); + + private: + boost::asio::io_context ioc_; + tcp::acceptor acceptor_; + tcp::socket socket_; + ssl::context ctx_; + tcp::endpoint endpoint_; + std::shared_ptr<WSSServer> wss_; + std::queue<std::string> buffered_routes_; +}; + +} // namespace websocket +} // namespace sample + +#endif // SRC_COMPONENTS_TRANSPORT_MANAGER_TEST_INCLUDE_TRANSPORT_MANAGER_CLOUD_SAMPLE_WEBSOCKET_SERVER_H_
\ No newline at end of file diff --git a/src/components/transport_manager/test/sample_websocket_server.cc b/src/components/transport_manager/test/sample_websocket_server.cc new file mode 100644 index 0000000000..2cb45242aa --- /dev/null +++ b/src/components/transport_manager/test/sample_websocket_server.cc @@ -0,0 +1,378 @@ +/* + * Copyright (c) 2019, Ford Motor Company + * 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 Ford Motor Company 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 THE COPYRIGHT HOLDER OR CONTRIBUTORS 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. + */ + +#include "transport_manager/cloud/sample_websocket_server.h" + +namespace { +// Report a failure +void Fail(char const* tag, boost::system::error_code ec) { + std::cerr << tag << ": " << ec.message() << "\n"; +} +} // namespace + +namespace sample { +namespace websocket { + +WSSession::WSServer::WSServer(tcp::socket&& socket) + : ws_(std::move(socket)), strand_(ws_.get_executor()) {} + +void WSSession::WSServer::AddURLRoute(const std::string& target) { + url_routes_.insert(ParseRouteFromTarget(target)); +} + +void WSSession::WSServer::Run() { + req_ = {}; + http::async_read( + ws_.next_layer(), + buffer_, + req_, + std::bind(&WSServer::OnWebsocketHandshake, this, std::placeholders::_1)); +} + +void WSSession::WSServer::OnWebsocketHandshake( + const boost::system::error_code& ec) { + if (ec) { + return Fail("ERROR_HTTP_REQUEST_READ", ec); + } + if (websocket::is_upgrade(req_)) { + // Check target + std::string route = ParseRouteFromTarget(req_.target().to_string()); + if (!CanHandleRoute(route)) { + auto error = boost::system::errc::make_error_code( + boost::system::errc::address_not_available); + ws_.next_layer().close(); + return Fail("ERROR_INVALID_TARGET", error); + } + // Accept the websocket handshake + ws_.async_accept( + req_, + boost::asio::bind_executor( + strand_, + std::bind(&WSServer::OnAccept, this, std::placeholders::_1))); + } +} + +void WSSession::WSServer::OnAccept(beast::error_code ec) { + if (ec) { + return Fail("ERROR_WEBSOCKET_HANDSHAKE", ec); + } +} + +bool WSSession::WSServer::CanHandleRoute(const std::string& route) { + if (url_routes_.find(route) == url_routes_.end()) { + return false; + } + return true; +} + +std::string WSSession::WSServer::ParseRouteFromTarget( + const std::string& target) { + std::string route = target; + // Remove fragment + auto fragment_pos = route.find('#'); + if (fragment_pos != std::string::npos) { + route = route.substr(0, fragment_pos); + } + // Remove query + auto query_pos = route.find('?'); + if (query_pos != std::string::npos) { + route = route.substr(0, query_pos); + } + + return route; +} + +WSSession::WSSession(const std::string& address, uint16_t port) + : address_(address) + , port_(port) + , acceptor_(ioc_) + , socket_(ioc_) + , ws_(nullptr) { + endpoint_ = {boost::asio::ip::make_address(address), port}; + boost::system::error_code error; + + // Open the acceptor + acceptor_.open(endpoint_.protocol(), error); + if (error) { + Fail("ERROR_ACCEPTOR_OPEN", error); + return; + } + + // Allow address reuse + acceptor_.set_option(boost::asio::socket_base::reuse_address(true), error); + if (error) { + Fail("ERROR_SET_OPTION", error); + return; + } + + // Bind to the server address + acceptor_.bind(endpoint_, error); + if (error) { + Fail("ERROR_BIND", error); + return; + } + + // Start listening for connections + acceptor_.listen(boost::asio::socket_base::max_listen_connections, error); + if (error) { + Fail("ERROR_LISTEN", error); + return; + } +} + +void WSSession::Run() { + if (acceptor_.is_open()) { + acceptor_.async_accept( + socket_, std::bind(&WSSession::on_accept, this, std::placeholders::_1)); + ioc_.run(); + } +} + +void WSSession::Stop() { + try { + ioc_.stop(); + acceptor_.close(); + } catch (...) { + std::cerr << "Failed to close connection" << std::endl; + } +} + +void WSSession::AddRoute(const std::string& route) { + if (ws_ == nullptr) { + buffered_routes_.push(route); + return; + } + ws_->AddURLRoute(route); +} + +void WSSession::on_accept(boost::system::error_code ec) { + if (ec) { + Fail("ERROR_ON_ACCEPT", ec); + ioc_.stop(); + return; + } + + // Make websocket object and start + ws_ = std::make_shared<WSServer>(std::move(socket_)); + // Load routes stored in buffer + while (!buffered_routes_.empty()) { + ws_->AddURLRoute(buffered_routes_.front()); + buffered_routes_.pop(); + } + ws_->Run(); +} + +WSSSession::WSSServer::WSSServer(tcp::socket&& socket, ssl::context& ctx) + : wss_(std::move(socket), ctx) {} + +void WSSSession::WSSServer::AddURLRoute(const std::string& target) { + url_routes_.insert(ParseRouteFromTarget(target)); +} +void WSSSession::WSSServer::Run() { + // Perform the SSL handshake + wss_.next_layer().async_handshake( + ssl::stream_base::server, + std::bind(&WSSServer::OnSSLHandshake, this, std::placeholders::_1)); +} + +void WSSSession::WSSServer::OnSSLHandshake(beast::error_code ec) { + if (ec) { + return Fail("ERROR_SSL_HANDSHAKE", ec); + } + + req_ = {}; + http::async_read( + wss_.next_layer(), + buffer_, + req_, + std::bind(&WSSServer::OnWebsocketHandshake, this, std::placeholders::_1)); +} + +void WSSSession::WSSServer::OnWebsocketHandshake( + const boost::system::error_code& ec) { + if (ec) { + return Fail("ERROR_HTTP_REQUEST_READ", ec); + } + if (websocket::is_upgrade(req_)) { + // Check target + std::string route = ParseRouteFromTarget(req_.target().to_string()); + if (!CanHandleRoute(route)) { + auto error = boost::system::errc::make_error_code( + boost::system::errc::address_not_available); + wss_.next_layer().next_layer().close(); + return Fail("ERROR_INVALID_TARGET", error); + } + // Accept the websocket handshake + wss_.async_accept( + req_, std::bind(&WSSServer::OnAccept, this, std::placeholders::_1)); + } +} + +void WSSSession::WSSServer::OnAccept(beast::error_code ec) { + if (ec) { + return Fail("ERROR_ON_ACCEPT", ec); + } +} + +bool WSSSession::WSSServer::CanHandleRoute(const std::string& route) { + if (url_routes_.find(route) == url_routes_.end()) { + return false; + } + return true; +} + +std::string WSSSession::WSSServer::ParseRouteFromTarget( + const std::string& target) { + std::string route = target; + // Remove fragment + auto fragment_pos = route.find('#'); + if (fragment_pos != std::string::npos) { + route = route.substr(0, fragment_pos); + } + // Remove query + auto query_pos = route.find('?'); + if (query_pos != std::string::npos) { + route = route.substr(0, query_pos); + } + + return route; +} + +WSSSession::WSSSession(const std::string& address, + uint16_t port, + const std::string& certificate, + const std::string& private_key) + : acceptor_(ioc_) + , socket_(ioc_) + , ctx_(ssl::context::sslv23_server) + , wss_(nullptr) { + beast::error_code ec; + endpoint_ = {boost::asio::ip::make_address(address), port}; + + // Load the certificate + ctx_.use_certificate( + boost::asio::buffer(certificate.c_str(), certificate.size()), + ssl::context::file_format::pem, + ec); + if (ec) { + Fail("ERROR_USE_CERTIFICATE", ec); + return; + } + + // Load the private key + ctx_.use_rsa_private_key( + boost::asio::buffer(private_key.c_str(), private_key.size()), + ssl::context::file_format::pem, + ec); + if (ec) { + Fail("ERROR_USE_RSA_PRIVATE_KEY", ec); + return; + } + + // Open the acceptor + acceptor_.open(endpoint_.protocol(), ec); + if (ec) { + Fail("EEROR_ACCEPTOR_OPEN", ec); + return; + } + + // Allow address reuse + acceptor_.set_option(net::socket_base::reuse_address(true), ec); + if (ec) { + Fail("ERROR_SET_OPTION", ec); + return; + } + + // Bind to the server address + acceptor_.bind(endpoint_, ec); + if (ec) { + Fail("ERROR_BIND", ec); + return; + } + + // Start listening for connections + acceptor_.listen(net::socket_base::max_listen_connections, ec); + if (ec) { + Fail("ERROR_LISTEN", ec); + return; + } +} + +// Start accepting incoming connections +void WSSSession::Run() { + do_accept(); +} + +void WSSSession::Stop() { + try { + ioc_.stop(); + acceptor_.close(); + } catch (...) { + std::cerr << "Failed to close connection" << std::endl; + } +} + +void WSSSession::AddRoute(const std::string& route) { + if (wss_ == nullptr) { + buffered_routes_.push(route); + return; + } + wss_->AddURLRoute(route); +} + +void WSSSession::do_accept() { + if (acceptor_.is_open()) { + acceptor_.async_accept( + socket_, + std::bind(&WSSSession::on_accept, this, std::placeholders::_1)); + ioc_.run(); + } +} + +void WSSSession::on_accept(boost::system::error_code ec) { + if (ec) { + Fail("ERROR_ON_ACCEPT", ec); + ioc_.stop(); + return; + } + // Create the session and run it + wss_ = std::make_shared<WSSServer>(std::move(socket_), ctx_); + // Load routes stored in buffer + while (!buffered_routes_.empty()) { + wss_->AddURLRoute(buffered_routes_.front()); + buffered_routes_.pop(); + } + wss_->Run(); +} + +} // namespace websocket +} // namespace sample
\ No newline at end of file diff --git a/src/components/transport_manager/test/transport_adapter_test.cc b/src/components/transport_manager/test/transport_adapter_test.cc index 1c707678a0..35630e45dd 100644 --- a/src/components/transport_manager/test/transport_adapter_test.cc +++ b/src/components/transport_manager/test/transport_adapter_test.cc @@ -41,23 +41,25 @@ #include "transport_manager/transport_adapter/mock_transport_adapter_listener.h" #include "utils/test_async_waiter.h" +#include "protocol/raw_message.h" +#include "transport_manager/cloud/cloud_device.h" +#include "transport_manager/cloud/cloud_websocket_transport_adapter.h" +#include "transport_manager/transport_adapter/connection.h" +#include "transport_manager/transport_adapter/transport_adapter_controller.h" #include "transport_manager/transport_adapter/transport_adapter_impl.h" #include "transport_manager/transport_adapter/transport_adapter_listener.h" -#include "transport_manager/transport_adapter/transport_adapter_controller.h" -#include "transport_manager/transport_adapter/connection.h" -#include "protocol/raw_message.h" -#include "resumption/last_state_impl.h" #include "config_profile/profile.h" +#include "resumption/last_state_impl.h" namespace test { namespace components { namespace transport_manager_test { -using ::testing::Return; -using ::testing::ReturnRef; using ::testing::_; using ::testing::NiceMock; +using ::testing::Return; +using ::testing::ReturnRef; using namespace ::transport_manager; using namespace ::protocol_handler; @@ -84,6 +86,143 @@ class TransportAdapterTest : public ::testing::Test { std::string dev_id; std::string uniq_id; int app_handle; + +#if defined(CLOUD_APP_WEBSOCKET_TRANSPORT_SUPPORT) + struct TestEndpoint { + std::string test_string; + std::string host; + std::string port; + std::string target; + }; + + CloudAppProperties test_cloud_properties_{.endpoint = "", + .certificate = "no cert", + .enabled = true, + .auth_token = "no auth token", + .cloud_transport_type = "WS", + .hybrid_app_preference = "CLOUD"}; + std::vector<std::string> kWebsocketProtocols{"ws://", "wss://"}; + + std::vector<TestEndpoint> kValidTestEndpoints{ + // Host and port + TestEndpoint{"localhost:40/", "localhost", "40", "/"}, + TestEndpoint{"[::1]:40", "[::1]", "40", "/"}, + TestEndpoint{"username:password@localhost.com:80", + "username:password@localhost.com", + "80", + "/"}, + // With path + TestEndpoint{ + "localhost:440/file.html", "localhost", "440", "/file.html/"}, + TestEndpoint{"101.180.1.213:23234/folder/img_index(1)/file.html", + "101.180.1.213", + "23234", + "/folder/img_index(1)/file.html/"}, + TestEndpoint{ + "[2600:3c00::f03c:91ff:fe73:2b08]:31333/folder/img_index(1)/" + "file.html", + "[2600:3c00::f03c:91ff:fe73:2b08]", + "31333", + "/folder/img_index(1)/file.html/"}, + // With query and/or fragment + TestEndpoint{ + "username@localhost:22/folder/img_index(1)/" + "file.html?eventId=2345&eventName='some%20event'&eventSuccess=true", + "username@localhost", + "22", + "/folder/img_index(1)/file.html/" + "?eventId=2345&eventName='some%20event'&eventSuccess=" + "true"}, + TestEndpoint{ + "username@localhost.com:80/folder/img_index(1)/" + "file.html?eventId=2345&eventName='some%20event'&" + "eventSuccess=true#section1", + "username@localhost.com", + "80", + "/folder/img_index(1)/file.html/" + "?eventId=2345&eventName='some%20event'&eventSuccess=true#" + "section1"}, + TestEndpoint{ + "localhost:443/" + "?eventId=2345&eventName='some%20event'&eventSuccess=true#section1", + "localhost", + "443", + "/?eventId=2345&eventName='some%20event'&eventSuccess=true#" + "section1"}, + TestEndpoint{"a1-b2.com:443/folder/img_index(1)/file.html#section1", + "a1-b2.com", + "443", + "/folder/img_index(1)/file.html/#section1"}, + TestEndpoint{"a1-b2.com:23#section1", "a1-b2.com", "23", "/#section1"}}; + + std::vector<TestEndpoint> kInvalidTestEndpoints{ + // Invalid hostname + TestEndpoint{"/localhost:80", "localhost", "80", "/"}, + TestEndpoint{"local?host:80", "local?host", "80", "/"}, + TestEndpoint{"local#host:80", "local#host", "80", "/"}, + TestEndpoint{"local\%host:80", "local\%host", "80", "/"}, + TestEndpoint{"local\\host:80", "local\\host", "80", "/"}, + TestEndpoint{"local/host:80", "local/host", "80", "/"}, + TestEndpoint{"local host:80", "local host", "80", "/"}, + TestEndpoint{"local\thost:80", "local\thost", "80", "/"}, + TestEndpoint{":80#section1", "", "80", "/#section1"}, + // Invalid port + TestEndpoint{"username:password@localhost.com", + "username:password@localhost.com", + "", + "/"}, + TestEndpoint{"username:password@localhost.com:5", + "username:password@localhost.com", + "5", + "/"}, + TestEndpoint{"201.123.213:2h32/", "201.123.213", "2h32", "/"}}; + std::vector<TestEndpoint> kIncorrectTestEndpoints{ + // Incorrect port number + TestEndpoint{"201.123.1.213:232454/folder/img_index(1)/file.html", + "201.123.1.213", + "232454", + "/folder/img_index(1)/file.html/"}, + // Incorrect path + TestEndpoint{"201.123.1.213:232//folder/img_index(1)/file.html", + "201.123.1.213", + "232", + "//folder/img_index(1)/file.html/"}, + TestEndpoint{"201.123.1.213:232/folder/img_index(1)//file.html", + "201.123.1.213", + "232", + "/folder/img_index(1)//file.html/"}, + TestEndpoint{"201.123.1.213:232/folder/img index(1)//file.html", + "201.123.1.213", + "232", + "/folder/img index(1)//file.html/"}, + TestEndpoint{"201.123.1.213:232/folder/img\tindex(1)//file.html", + "201.123.1.213", + "232", + "/folder/img\tindex(1)//file.html/"}, + // Incorrect query + TestEndpoint{ + "username@localhost:443/?eventId=2345&eventName='some " + "event'&eventSuccess=true", + "username@localhost", + "443", + "?eventId=2345&eventName='some event'&eventSuccess=true"}, + TestEndpoint{ + "username@localhost:443/" + "?eventId=2345&eventName='some\tevent'&eventSuccess=true", + "username@localhost", + "443", + "?eventId=2345&eventName='some\tevent'&eventSuccess=true"}, + // Incorrect fragment + TestEndpoint{"a1(b2).com:80/folder/img_index(1)/file.html#section 1", + "a1(b2).com", + "80", + "/folder/img_index(1)/file.html#section 1"}, + TestEndpoint{"a1(b2).com:80/folder/img_index(1)/file.html#section\t1", + "a1(b2).com", + "80", + "/folder/img_index(1)/file.html#section\t1"}}; + +#endif // CLOUD_APP_WEBSOCKET_TRANSPORT_SUPPORT }; TEST_F(TransportAdapterTest, Init) { @@ -609,6 +748,83 @@ TEST_F(TransportAdapterTest, EXPECT_CALL(*serverMock, Terminate()); } + +TEST_F(TransportAdapterTest, WebsocketEndpointParsing_SUCCESS) { + std::shared_ptr<CloudWebsocketTransportAdapter> cta = + std::make_shared<CloudWebsocketTransportAdapter>( + last_state_, transport_manager_settings); + + for (auto protocol : kWebsocketProtocols) { + for (auto endpoint : kValidTestEndpoints) { + test_cloud_properties_.endpoint = protocol + endpoint.test_string; + cta->SetAppCloudTransportConfig("cloud app", test_cloud_properties_); + + auto ta = std::dynamic_pointer_cast<TransportAdapter>(cta); + ASSERT_NE(ta.use_count(), 0); + + ta->CreateDevice(test_cloud_properties_.endpoint); + + auto device = cta->FindDevice(test_cloud_properties_.endpoint); + ASSERT_NE(device.use_count(), 0); + + std::shared_ptr<CloudDevice> cloud_device = + std::dynamic_pointer_cast<CloudDevice>(device); + ASSERT_NE(cloud_device.use_count(), 0); + + EXPECT_EQ(cloud_device->GetHost(), endpoint.host); + EXPECT_EQ(cloud_device->GetPort(), endpoint.port); + EXPECT_EQ(cloud_device->GetTarget(), endpoint.target); + } + } +} +TEST_F(TransportAdapterTest, WebsocketEndpointParsing_INVALID) { + std::shared_ptr<CloudWebsocketTransportAdapter> cta = + std::make_shared<CloudWebsocketTransportAdapter>( + last_state_, transport_manager_settings); + + for (auto protocol : kWebsocketProtocols) { + for (auto endpoint : kInvalidTestEndpoints) { + test_cloud_properties_.endpoint = protocol + endpoint.test_string; + cta->SetAppCloudTransportConfig("cloud app", test_cloud_properties_); + + auto ta = std::dynamic_pointer_cast<TransportAdapter>(cta); + ASSERT_NE(ta.use_count(), 0); + + ta->CreateDevice(test_cloud_properties_.endpoint); + + auto device = cta->FindDevice(test_cloud_properties_.endpoint); + EXPECT_EQ(device.use_count(), 0); + } + } +} +TEST_F(TransportAdapterTest, WebsocketEndpointParsing_INCORRECT) { + std::shared_ptr<CloudWebsocketTransportAdapter> cta = + std::make_shared<CloudWebsocketTransportAdapter>( + last_state_, transport_manager_settings); + + for (auto protocol : kWebsocketProtocols) { + for (auto endpoint : kIncorrectTestEndpoints) { + test_cloud_properties_.endpoint = protocol + endpoint.test_string; + cta->SetAppCloudTransportConfig("cloud app", test_cloud_properties_); + + auto ta = std::dynamic_pointer_cast<TransportAdapter>(cta); + ASSERT_NE(ta.use_count(), 0); + + ta->CreateDevice(test_cloud_properties_.endpoint); + + auto device = cta->FindDevice(test_cloud_properties_.endpoint); + ASSERT_NE(device.use_count(), 0); + + std::shared_ptr<CloudDevice> cloud_device = + std::dynamic_pointer_cast<CloudDevice>(device); + ASSERT_NE(cloud_device.use_count(), 0); + + EXPECT_FALSE(cloud_device->GetHost() == endpoint.host && + cloud_device->GetPort() == endpoint.port && + cloud_device->GetTarget() == endpoint.target); + } + } +} #endif // CLOUD_APP_WEBSOCKET_TRANSPORT_SUPPORT TEST_F(TransportAdapterTest, DisconnectDevice_DeviceAddedConnectionCreated) { diff --git a/src/components/transport_manager/test/websocket_connection_test.cc b/src/components/transport_manager/test/websocket_connection_test.cc new file mode 100644 index 0000000000..db3bc598b1 --- /dev/null +++ b/src/components/transport_manager/test/websocket_connection_test.cc @@ -0,0 +1,491 @@ +/* + * Copyright (c) 2019, Ford Motor Company + * 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 Ford Motor Company 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 THE COPYRIGHT HOLDER OR CONTRIBUTORS 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. + */ + +#include "gtest/gtest.h" +#include "resumption/last_state_impl.h" +#include "transport_manager/cloud/cloud_websocket_transport_adapter.h" +#include "transport_manager/cloud/sample_websocket_server.h" +#include "transport_manager/cloud/websocket_client_connection.h" +#include "transport_manager/transport_adapter/connection.h" +#include "transport_manager/transport_adapter/transport_adapter_impl.h" + +#include "transport_manager/mock_transport_manager_settings.h" + +namespace test { +namespace components { +namespace transport_manager_test { + +using ::testing::_; +using ::testing::NiceMock; +using ::testing::Return; +using namespace ::transport_manager; +using namespace ::transport_manager::transport_adapter; +namespace websocket = sample::websocket; + +class WebsocketConnectionTest : public ::testing::Test { + public: + struct WebsocketClient { + std::shared_ptr<CloudWebsocketTransportAdapter> adapter; + std::shared_ptr<WebsocketClientConnection> connection; + }; + + void InitWebsocketClient( + const transport_manager::transport_adapter::CloudAppProperties& + properties, + WebsocketClient& client_out) { + uniq_id = dev_id = properties.endpoint; + + client_out = + WebsocketClient{std::make_shared<CloudWebsocketTransportAdapter>( + last_state_, transport_manager_settings), + nullptr}; + client_out.adapter->SetAppCloudTransportConfig(uniq_id, properties); + + TransportAdapterImpl* ta_cloud = + dynamic_cast<TransportAdapterImpl*>(client_out.adapter.get()); + ta_cloud->CreateDevice(uniq_id); + + auto connection = client_out.adapter->FindPendingConnection(dev_id, 0); + + ASSERT_NE(connection, nullptr); + + client_out.connection = + std::dynamic_pointer_cast<WebsocketClientConnection>(connection); + + ASSERT_NE(client_out.connection.use_count(), 0); + } + + void StartWSServer(std::string path) { + ws_session = std::make_shared<websocket::WSSession>(kHost, kPort); + ws_session->AddRoute(path); + ws_session->Run(); + } + + void StartWSSServer(std::string path) { + wss_session = std::make_shared<websocket::WSSSession>( + kHost, kPort, kCertificate, kPrivateKey); + wss_session->AddRoute(path); + wss_session->Run(); + } + + protected: + WebsocketConnectionTest() + : last_state_("app_storage_folder", "app_info_storage") {} + + ~WebsocketConnectionTest() {} + + void SetUp() OVERRIDE { + ON_CALL(transport_manager_settings, use_last_state()) + .WillByDefault(Return(true)); + } + + NiceMock<MockTransportManagerSettings> transport_manager_settings; + resumption::LastStateImpl last_state_; + std::string dev_id; + std::string uniq_id; + std::shared_ptr<websocket::WSSession> ws_session; + std::shared_ptr<websocket::WSSSession> wss_session; + WebsocketClient ws_client; + std::string kHost = "127.0.0.1"; + uint16_t kPort = 8080; + std::string kPath = "/folder/file.html/"; + std::string kQuery = "?eventId=2345&eventName='Test'&expectedResult=true"; + std::string kFragment = "#section_1"; + + // Sample certificate for localhost + std::string kCertificate = + "-----BEGIN " + "CERTIFICATE-----\nMIIDqTCCApECCQC/" + "5LlQ+" + "GLgqTANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAwx\nMjcuMC4wLjEgQ0EwIBcNMTkwNTA" + "2MTkzMjM2WhgPMjExOTA0MTIxOTMyMzZaMBQx\nEjAQBgNVBAMMCTEyNy4wLjAuMTCCAiIwD" + "QYJKoZIhvcNAQEBBQADggIPADCCAgoC\nggIBALbeIoAHFaFeNqbMnhtAO5QTgRd0X9qxpB0" + "d4M0dEeog2l/+inNA/eu+CCvm\nJj2I8+6MWH2TUrl/" + "2xfhHjzsMrtISCdpNcjaNzCnvi8ZcspFi3evknvs3+uo2/" + "wn\nyNZ04jp0oQ0k1cZ6WBpLTYV7WgmueemiWEiAfw+YE+wtRg+" + "0H7rksrbpeNPnxQHX\nBDDkWqwvfkD15Sd0XFQkW27K72/" + "et2uKuAcJHCIUbsA4iZyJw4Uu4eusy7W5kddX\nThE7Y1WqTXyA4j/" + "ZCYigXmsHbWrikPLVXbORhiMF4/60u8RDs0jI8xtgRQhLR9Vp\n3xys7/" + "5tHZX00s6x6OVy8aMSZIIVS0eWoVN8bGd9B4+fDMOcNT0YODeQA10w/" + "85P\nEiZDQ8AxneQkh8H3qjD2+" + "G9oHZaTk0zHTyaKRg3xGP3N9C96onaJX4Rm6nNvApO8\nU7lQ+xHkLwjKCQke39W+" + "r3FHwwQvUDeoJBXf6iVkIMFoUPAHNZqKf9Db6KKIEp8i\nDgIhbBxiB4MSn36Zly4SOMojyM" + "ZFri+" + "HzMbuHFlm8e8QRWGmM4UM2rHIpl4OJolg\nejesDqO8YZR5mZXV0FJRiPgLo2Q4OTtb3tEHJ" + "ZLmlT+" + "f42bge4ZCZmGPrkNfr68Y\nDEnJ6z4ksOVkMefOp2SNYLYYGPYyiKwa9qVkH9Obkect1omNA" + "gMBAAEwDQYJKoZI\nhvcNAQELBQADggEBABFJQtOTftrzbLBA5Vm6aPkbUyxhcaOpz+d+" + "Ljd6pIs4H+" + "Eb\nXkoHhmay4stZkDc2HVSKESZloI3Ylup8z3aRJjfOexJqHlYzk2vraYryWm8odgID\n5V" + "B0Zle8ofpHTJw1LCOXHkzKt1G6vdQpuq/" + "4OKpmggaIqqEC1bfOYZt5t6vIx3OF\nxjPz91NTR9gZ4lKrho1+sfS+" + "jbSjKkqVAhE4yTpKLPHRshQnBFEhvATXNvdZGftF\n+tXxqKsBZ9aX0/" + "YmPFIXFcjdjSSiuq1F1DjiqIZo88qfa9jlTg6VJdayyQ/" + "cu41C\nBucY8YTF0Ui8ccIS55h2UTzPy5/4PbrwI4P+Zgo=\n-----END " + "CERTIFICATE-----\n"; + // Sample private key + std::string kPrivateKey = + "-----BEGIN RSA PRIVATE " + "KEY-----\nMIIJKAIBAAKCAgEAtt4igAcVoV42psyeG0A7lBOBF3Rf2rGkHR3gzR0R6iDaX/" + "6K\nc0D9674IK+YmPYjz7oxYfZNSuX/" + "bF+EePOwyu0hIJ2k1yNo3MKe+LxlyykWLd6+S\ne+zf66jb/" + "CfI1nTiOnShDSTVxnpYGktNhXtaCa556aJYSIB/" + "D5gT7C1GD7QfuuSy\ntul40+fFAdcEMORarC9+" + "QPXlJ3RcVCRbbsrvb963a4q4BwkcIhRuwDiJnInDhS7h\n66zLtbmR11dOETtjVapNfIDiP9" + "kJiKBeawdtauKQ8tVds5GGIwXj/rS7xEOzSMjz\nG2BFCEtH1WnfHKzv/" + "m0dlfTSzrHo5XLxoxJkghVLR5ahU3xsZ30Hj58Mw5w1PRg4\nN5ADXTD/" + "zk8SJkNDwDGd5CSHwfeqMPb4b2gdlpOTTMdPJopGDfEY/" + "c30L3qidolf\nhGbqc28Ck7xTuVD7EeQvCMoJCR7f1b6vcUfDBC9QN6gkFd/" + "qJWQgwWhQ8Ac1mop/" + "\n0NvooogSnyIOAiFsHGIHgxKffpmXLhI4yiPIxkWuL4fMxu4cWWbx7xBFYaYzhQza\nscim" + "Xg4miWB6N6wOo7xhlHmZldXQUlGI+AujZDg5O1ve0QclkuaVP5/" + "jZuB7hkJm\nYY+uQ1+" + "vrxgMScnrPiSw5WQx586nZI1gthgY9jKIrBr2pWQf05uR5y3WiY0CAwEA\nAQKCAgBGSDGyS" + "wbBMliG2vWZO6KqUqS2wv9kKgoNNsKDkryj42SKqGXFziDJTgwN\n8zKXS9+Uu1P3T3vn13/" + "5OYhJme4VlL5Gh2UogNXdWVr69yjrHLdxlIUUJAIbrJZ/" + "\n3zqNUfbwyIptZs7SrYrW8EInHzWHqwsoBEEx/" + "FDZSXW+u9fFiVD4n5UgP7M0nktV\nXbI6qElBDC/V/" + "6vG8i3aGO8bMdu8fzi3mGUKLzIk1v2J2zDofPosYcxqq8rPWTb4\nMJHMhaqz7fRB+" + "bb7GwtS+2/Oathe0B0td1u//Bo1s7ng1s2jrPFm8/" + "SbfPCLM4O0\nPjCF8OF8Q6uvSp0K283LAdZk+liuDztc/" + "Ed8mcnCZQhBp86mJQi0Jj3Mje7prOAY\nXojMroYj7r2igCJvcwGb1y6zZWSj3jXuHze3bLy" + "fv7pCD+hkiZR7mZhQpOhQRZaU\ntdFGc+" + "DuykxKPqVjAPy7iVQXYnMjpo36SGIWoZLuepQto3GvU6czyOwhK0/" + "Mwbwd\nZpYpLH3L9IetY8GcPefmUR5wQUlUTrpxgGElIzkkWW8zmUWBXwrGbAtN1HJWpJdN" + "\neVshKod2fo03IQMPywSdENCJVeSrgRMuwPyFaOM+" + "CVrBJwD66K9YWn4cVRUIZsTq\nAXhQ8DzF+WCOZshhMUbCJX+KpcOFI8nxOrPp+" + "J1s1YpLLvdmcQKCAQEA7bwvOiCD\nHvaqpYg1jJak6l/" + "iY3QIOOpFyjfYrQXS0BNRmmxK8Lzevi/" + "NPTqu146QKDaIGvzu\n+" + "bXnuV1LmZqnOm5J6Kdx0Mk4Fb88akRtS9gOdzU7WWMYIGkeF1oih0ZrhHuIey6e\nCeLSmJh" + "UDaTIWcCirALRHcLWUS6BTGGuE+up5QG7djIW/" + "LD17lhGE6fXPlbYXb7l\nPbYwL1Yr0urIp9Un+zrExw+77TTGK7T37T1ZJv46qnro0/" + "HK8XxZ+" + "JNc18F763O4\nPmwu8KWrx4qnyPYAuB1rbVntK6UxYks9jvk93XecCUP6HHp0I0tV/" + "kBNd2+" + "18zux\n033xFEVKrUksmwKCAQEAxOrPGyBVFp29V4gQsMQdmafkDAgCLREgjQTOamFsYL5+" + "\nZWq+6VhT/" + "B650sIoRdX0N8timnSLBfjeqaBG5VVhhyKZhP5hulfRjALhdbGBOYvf\n0gLSffImwWdYQfx" + "jtCSO+" + "XCLVdAJGOOWeBVVKzH18ZvCFQwUr3Yrp7KmKWKAqgf7\n6rYovWh8T5LLYS0NzXCEwf9nJ0N" + "JMOy7I9B7EtF8Cs6tK3aaHVqDz7zufwosS7gI\n3aI51Qh4a5D1p95+" + "YU09beWjWGYnPiCKk4D47zaeOe7OQINaWugExlelHtJh9unO\nnhOFXhziav2Kxq1CICAuXl" + "Vx3A+gn/cU3niNHz2A9wKCAQEAws+aw78ws4bef5cG\nipZHveek1GqY8krHtdXdsKs8/" + "VVXYXusTWn3/VGelbYo4GrqpolJLxRloCr4IGXb\nNZwNvUvzNLtCAR1i4C89irdX+Paro/" + "PzFmSluKlrByfNc5y5Lm8sgATLbL56ZKEu\n/58wrpu0sc/" + "9HK40gYHiYn0I8ToElqy8uTaCr78zSIT9p826DFOOKgPsRo2tHp02\nfDf5Bc8eXDjkV1sFX" + "HQKkHZTVA0ZqWJbIKhncoaJDyofcBsR0ZuzuFWzfTOZo4mf\nInz00TEFldpF1e4C8+" + "kCdtHBOA/2Ki2Bp/YUVpHh6aoqZZa75Euehhs8tVpW242M\njEOSUQKCAQAM64sjMH/kt/" + "zQXXEa6AM5LbbcwznBUzpbhlE00aeWwWjxpotYLB92\nj12J4otZ6avYbVPO5o6omaeiYY3F" + "RlDb2P1RqI8o9tIc6aN5YWglKnRJBz5gXR8F\n2Y4E5lZ0X2GyJBxASSIPq/" + "8Xae7ooqKMc7fMQbqpuIssuaAFXx0qCtQQllsd8lkV\nr4AApEAflp5fTC6seNG4kA/" + "HTcqFdZE59E2QaHu8KVA0tSTA2R4G6dBLGnXI8IFW\nLXCwzvxjzfmV2FdbWXiBrwjonLG4o" + "FDJZE3MFdI73LVTfjSrTQp4dObFoGpDvols\nk64jUwLfsLzaG6kY0z2qwT9xSV+" + "ZCSQJAoIBADsSBeyELc5KnmOhT0Xue2o0bkAS\n8KcGWdAoQlaLCIs3vtdlC7DXDXO1l8FkT" + "Qrd+GwP3eWDq6ymtO4vkJ3m4cZ1PEsv\n6Qet7qtkVhUfa+" + "FYKtDzSMRuSaFPW3Ro1PesFa1H3y+sw5HL36vhRaSK+T4Wy5k2\nf7EwNZIf/" + "ZZeA+" + "sEtqj33obOBIiuU9htAjN81Kz4a0Xlh6jc4q6iwYKL8nZ76JYV\n8hXWIz6OXxLXE158+" + "QtJSZaRCvdr6L5UtWfMPKSMqgfhXfTYViPDleQCkJ132mIS\nH28UoET0Y5wI8M6pMkWpSqW" + "WcKPFGwyLInvHdxgnTAsutowkldA7qFwoRz4=\n-----END RSA PRIVATE KEY-----\n"; + // Sample CA certificate(used to sign the server certificate) + std::string kCACertificate = + "-----BEGIN " + "CERTIFICATE-----" + "\nMIIDBjCCAe6gAwIBAgIJAPyCrKRDl3SWMA0GCSqGSIb3DQEBCwUAMBcxFTATBgNV\nBAMM" + "DDEyNy4wLjAuMSBDQTAgFw0xOTA1MDYxOTMyMzZaGA8yMTE5MDQxMjE5MzIz\nNlowFzEVMB" + "MGA1UEAwwMMTI3LjAuMC4xIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC\nAQ8AMIIBCgKCAQEA" + "xrNHoY5+" + "JCgnyvEdG2dvvOaZ3sg6uhuF5Ssb5snyo9ixrxO5\nZkDGWwUqFO9PiYuO5ovNeq9LZtPG6s" + "K8zsCS062ChZX/7tZHndf4MKCUDzv/" + "LPHe\nnROoPi9n2FAiOPctY5hpgpIJDPI5Ofx0el2KPeFGeUO/" + "W3yqnfol1ZqzZ2h3uErR\noHkgT2Ja4K+5gnPkr/" + "RluJIu3AmWYw4eKi8i3+" + "PoThGmCFvoGfcWvRoctgnOHYHE\n4bDRirXL9nGkZ5FMzOVDeoIAEAShOqJwL08VcY+Pg/" + "qFQjzRrTmiKgm6QsHNnm0q\nzg70XaD88VJimiGYZOuJHNZpX8o0W+1Ls/" + "NbawIDAQABo1MwUTAdBgNVHQ4EFgQU\nkW3hgFWYMpVUkq9AlzGlI3awTGEwHwYDVR0jBBgw" + "FoAUkW3hgFWYMpVUkq9AlzGl\nI3awTGEwDwYDVR0TAQH/BAUwAwEB/" + "zANBgkqhkiG9w0BAQsFAAOCAQEAZeMkILiG\neEcELWb8cktP3LzvS47O8hys9+" + "6TFmftuO7kjDBd9YH2v8iQ7qCcUvMJ7bqL5RP1\nQssKfNOHZtw/" + "MMcKE6E3nl4yQSKc8ymivvYSVu5SYtQCedcgfRLb5zvVxXw8JmCp\nGNS3/" + "OlIYZAamh76GxkKSaV3tv0XZB6n3rUVQZShncFbMpyJRW0XWxReGWrhXv4s\nNxMeC1r07EE" + "WIDecv8KKf1F8uT4UF48HnC0VBpXiOyDGvn35NiKp+" + "Q5k7QV6jdCS\ngPRcnZhs6jiU0jnV8C9A1A+" + "3pXSSPrAed7tvECOgHCfS10CLsLWsLuSjc93BE5Vt\nav7kmxSwrdvQ2A==\n-----END " + "CERTIFICATE-----\n"; + std::string kIncorrectCertificate = + "-----BEGIN " + "CERTIFICATE-----\nMIIC/" + "jCCAeagAwIBAgIJAIZjLucUID1mMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV\nBAMMCTEyNy4" + "wLjAuMTAeFw0xOTA1MDYxNDA1MzdaFw0yOjA1MDZxNDA1MzdaMBQx\nEjAQBgNVBAMMCTEyN" + "y4wLjAuMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\nggEBALfE5Qhc2mHTIux30l7" + "eHFjFLdzYeXE8vcaXKodalCG8EkRxojDOfUv+y2DV\nJzHAMiGxMFAEcSz71k+" + "jFZwhNG8PLY0b36ChnUsrGkOSJWq3OKUFrg8KxO9At9dL\nJsa+" + "R0N0D1bMoPYdpCi3m0b0q2ITHe56qKuTLTrIPia+" + "qXGEVD7EoEhU9tnwlcwE\npsUURMXCn2+FhHyqN9wjFkldmu4k8U3OJOK4385L+" + "4RJoIV8dsYawAMAf+" + "WuxyWq\niPQTPxr8q33ZZm6z0XrgsgxHYCCsryx8L9Ub9Zwu0mie7eL63rYdr86gULvnF1bY" + "\ncOunKFc71rBYFalbD6YYsre733kCAwEAAaNTMFEwHQYDVR0OBBYEFKW9ByUNG84Z\nYiSc" + "hUrB7KV9FinZMB8GA1UdIwQYMBaAFKW9ByUNG84ZYiSchUrB7KV9FinZMA8G\nA1UdEwEB/" + "wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAHYROS1EL2qnrwmAsR6c\nqo8yrk8qzLKt4os" + "41nv7QMUQFIO+QLPE8SbH4fK1YOMMUJ4ppARQBDaIIR3UEhp1\nZHT/" + "wGjK9qxcuQ1EXLyHOY0rxS5q57dYGxOyQo4v6IoLVMZ1ij+RJGPYI/" + "2fDXs0\nbDldeJyC1voHmG6lqTN5nLG7Y3j9j6rtSqJyRz5muaecQNiFPQOM2OTp0rC4VeAF" + "\ndejmTmLhUFVuHMzLF+" + "bpzsN76GnnQquJy2jexzFoWgbEfFVLKuyhTHQAalRb4ccq\nXCIx1hecNDYRY3Sc2Gzv5qxk" + "kWF8zqltT/0d5tx0JwN3k5nP4SlaEldFvD6BELxy\nVkU=\n-----END " + "CERTIFICATE-----\n"; +}; + +TEST_F(WebsocketConnectionTest, WSConnection_SUCCESS) { + transport_manager::transport_adapter::CloudAppProperties properties{ + .endpoint = "ws://" + kHost + ":" + std::to_string(kPort), + .certificate = "no cert", + .enabled = true, + .auth_token = "auth_token", + .cloud_transport_type = "WS", + .hybrid_app_preference = "CLOUD"}; + + // Start server + std::thread t1(&WebsocketConnectionTest::StartWSServer, this, "/"); + usleep(5000); + + // Start client + InitWebsocketClient(properties, ws_client); + std::shared_ptr<WebsocketClientConnection> ws_connection = + ws_client.connection; + + // Check websocket connection + TransportAdapter::Error ret_code = ws_connection->Start(); + EXPECT_EQ(TransportAdapter::OK, ret_code); + + // Stop client + ret_code = ws_connection->Disconnect(); + EXPECT_EQ(TransportAdapter::OK, ret_code); + + // Stop server thread + ws_session->Stop(); + t1.join(); +} + +TEST_F(WebsocketConnectionTest, WSConnection_SUCCESS_ValidTarget) { + transport_manager::transport_adapter::CloudAppProperties properties{ + .endpoint = "ws://" + kHost + ":" + std::to_string(kPort) + kPath + + kQuery + kFragment, + .certificate = "no cert", + .enabled = true, + .auth_token = "auth_token", + .cloud_transport_type = "WS", + .hybrid_app_preference = "CLOUD"}; + + // Start server + std::thread t1(&WebsocketConnectionTest::StartWSServer, this, kPath); + usleep(5000); + + // Start client + InitWebsocketClient(properties, ws_client); + std::shared_ptr<WebsocketClientConnection> ws_connection = + ws_client.connection; + + // Check websocket connection + TransportAdapter::Error ret_code = ws_connection->Start(); + EXPECT_EQ(TransportAdapter::OK, ret_code); + + // Stop client + ret_code = ws_connection->Disconnect(); + EXPECT_EQ(TransportAdapter::OK, ret_code); + + // Stop server thread + ws_session->Stop(); + t1.join(); +} + +TEST_F(WebsocketConnectionTest, WSConnection_FAILURE_InvalidTarget) { + transport_manager::transport_adapter::CloudAppProperties properties{ + .endpoint = "ws://" + kHost + ":" + std::to_string(kPort) + kPath + + kQuery + kFragment, + .certificate = "no cert", + .enabled = true, + .auth_token = "auth_token", + .cloud_transport_type = "WS", + .hybrid_app_preference = "CLOUD"}; + + // Start server + std::thread t1(&WebsocketConnectionTest::StartWSServer, this, "/"); + usleep(5000); + + // Start client + InitWebsocketClient(properties, ws_client); + std::shared_ptr<WebsocketClientConnection> ws_connection = + ws_client.connection; + + // Check websocket connection + TransportAdapter::Error ret_code = ws_connection->Start(); + EXPECT_EQ(TransportAdapter::FAIL, ret_code); + + // Stop client + ret_code = ws_connection->Disconnect(); + EXPECT_EQ(TransportAdapter::OK, ret_code); + + // Stop server thread + ws_session->Stop(); + t1.join(); +} + +TEST_F(WebsocketConnectionTest, WSSConnection_SUCCESS) { + transport_manager::transport_adapter::CloudAppProperties properties{ + .endpoint = "wss://" + kHost + ":" + std::to_string(kPort), + .certificate = kCACertificate, + .enabled = true, + .auth_token = "auth_token", + .cloud_transport_type = "WSS", + .hybrid_app_preference = "CLOUD"}; + + // Start server + std::thread t1(&WebsocketConnectionTest::StartWSSServer, this, "/"); + usleep(5000); + + // Start client + InitWebsocketClient(properties, ws_client); + std::shared_ptr<WebsocketClientConnection> wss_connection = + ws_client.connection; + + // Check websocket connection + TransportAdapter::Error ret_code = wss_connection->Start(); + EXPECT_EQ(TransportAdapter::OK, ret_code); + + // Stop client + ret_code = wss_connection->Disconnect(); + EXPECT_EQ(TransportAdapter::OK, ret_code); + + // Stop server thread + wss_session->Stop(); + t1.join(); +} + +TEST_F(WebsocketConnectionTest, WSSConnection_SUCCESS_ValidTarget) { + transport_manager::transport_adapter::CloudAppProperties properties{ + .endpoint = "wss://" + kHost + ":" + std::to_string(kPort) + kPath, + .certificate = kCACertificate, + .enabled = true, + .auth_token = "auth_token", + .cloud_transport_type = "WSS", + .hybrid_app_preference = "CLOUD"}; + + // Start server + std::thread t1(&WebsocketConnectionTest::StartWSSServer, + this, + (kPath + kQuery + kFragment)); + usleep(5000); + + // Start client + InitWebsocketClient(properties, ws_client); + std::shared_ptr<WebsocketClientConnection> wss_connection = + ws_client.connection; + + // Check websocket connection + TransportAdapter::Error ret_code = wss_connection->Start(); + EXPECT_EQ(TransportAdapter::OK, ret_code); + + // Stop client + ret_code = wss_connection->Disconnect(); + EXPECT_EQ(TransportAdapter::OK, ret_code); + + // Stop server thread + wss_session->Stop(); + t1.join(); +} + +TEST_F(WebsocketConnectionTest, WSSConnection_FAILURE_InvalidTarget) { + transport_manager::transport_adapter::CloudAppProperties properties{ + .endpoint = "wss://" + kHost + ":" + std::to_string(kPort), + .certificate = kCACertificate, + .enabled = true, + .auth_token = "auth_token", + .cloud_transport_type = "WSS", + .hybrid_app_preference = "CLOUD"}; + + // Start server + std::thread t1(&WebsocketConnectionTest::StartWSSServer, this, kPath); + usleep(5000); + + // Start client + InitWebsocketClient(properties, ws_client); + std::shared_ptr<WebsocketClientConnection> wss_connection = + ws_client.connection; + + // Check websocket connection + TransportAdapter::Error ret_code = wss_connection->Start(); + EXPECT_EQ(TransportAdapter::FAIL, ret_code); + + // Stop client + ret_code = wss_connection->Disconnect(); + EXPECT_EQ(TransportAdapter::OK, ret_code); + + // Stop server thread + wss_session->Stop(); + t1.join(); +} + +TEST_F(WebsocketConnectionTest, WSSConnection_FAILURE_IncorrectCert) { + transport_manager::transport_adapter::CloudAppProperties properties{ + .endpoint = "wss://" + kHost + ":" + std::to_string(kPort), + .certificate = kIncorrectCertificate, + .enabled = true, + .auth_token = "auth_token", + .cloud_transport_type = "WSS", + .hybrid_app_preference = "CLOUD"}; + + // Start server + std::thread t1(&WebsocketConnectionTest::StartWSSServer, this, "/"); + usleep(5000); + + // Start client + InitWebsocketClient(properties, ws_client); + std::shared_ptr<WebsocketClientConnection> wss_connection = + ws_client.connection; + + // Check websocket connection + TransportAdapter::Error ret_code = wss_connection->Start(); + EXPECT_EQ(TransportAdapter::FAIL, ret_code); + + // Stop client + ret_code = wss_connection->Disconnect(); + EXPECT_EQ(TransportAdapter::OK, ret_code); + + // Stop server thread + wss_session->Stop(); + t1.join(); +} +} // namespace transport_manager_test +} // namespace components +} // namespace test |