summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShobhit Adlakha <adlakhashobhit@gmail.com>2019-05-24 11:11:20 -0400
committerShobhit Adlakha <adlakhashobhit@gmail.com>2019-05-24 11:11:20 -0400
commit74a9ce55a646685d96984055b4a9c07cb08eee3b (patch)
treed80ece46e8f9274bd463a887a12c31ef9626fa25
parent15cc79c54b79f39e359c028a0c7bd296cd038015 (diff)
downloadsdl_core-hotfix/cloud_app_endpoint_with_path.tar.gz
Implemented fix and unit tests for regex endpoint parsing and websocket connectionshotfix/cloud_app_endpoint_with_path
-rw-r--r--src/components/transport_manager/include/transport_manager/cloud/cloud_device.h15
-rw-r--r--src/components/transport_manager/src/cloud/cloud_device.cc28
-rw-r--r--src/components/transport_manager/src/cloud/cloud_websocket_transport_adapter.cc51
-rw-r--r--src/components/transport_manager/src/cloud/websocket_client_connection.cc4
-rw-r--r--src/components/transport_manager/test/CMakeLists.txt7
-rw-r--r--src/components/transport_manager/test/include/transport_manager/cloud/sample_websocket_server.h162
-rw-r--r--src/components/transport_manager/test/sample_websocket_server.cc378
-rw-r--r--src/components/transport_manager/test/transport_adapter_test.cc228
-rw-r--r--src/components/transport_manager/test/websocket_connection_test.cc491
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