diff options
-rw-r--r-- | src/components/transport_manager/test/include/transport_manager/cloud/sample_websocket_server.h | 215 | ||||
-rw-r--r-- | src/components/transport_manager/test/websocket_connection_test.cc | 190 |
2 files changed, 399 insertions, 6 deletions
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 index 40fbd7dcc1..421462d4d5 100644 --- 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 @@ -39,9 +39,11 @@ #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 <cstdlib> @@ -56,6 +58,7 @@ 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> //------------------------------------------------------------------------------ @@ -230,4 +233,216 @@ class WSSession : public std::enable_shared_from_this<WSSession> { std::shared_ptr<WSServer> ws_; }; +// Accepts incoming connections and launches the sessions +class WSSSession : public std::enable_shared_from_this<WSSSession> { + private: + // Echoes back all received WebSocket messages + class WSSServer : public std::enable_shared_from_this<WSSServer> { + public: + // Take ownership of the socket + WSSServer(tcp::socket&& socket, ssl::context& ctx) + : wss_(std::move(socket), ctx) {} + + // Start the asynchronous operation + void run() { + // Perform the SSL handshake + wss_.next_layer().async_handshake(ssl::stream_base::server, + std::bind(&WSSServer::on_handshake, + shared_from_this(), + std::placeholders::_1)); + } + + void on_handshake(beast::error_code ec) { + std::cout << "DEBUG: SSL Handshake" << std::endl; + if (ec) + return fail(ec, "handshake"); + + // Accept the websocket handshake + wss_.async_accept(std::bind( + &WSSServer::on_accept, shared_from_this(), std::placeholders::_1)); + } + + void on_accept(beast::error_code ec) { + std::cout << "DEBUG: Server accepted connection" << std::endl; + if (ec) + return fail(ec, "accept"); + + // Read a message + // do_read(); + } + + // void + // do_read() + // { + // // Read a message into our buffer + // wss_.async_read( + // buffer_, + // std::bind( + // &WSSServer::on_read, + // shared_from_this(), + // std::placeholders::_1, + // std::placeholders::_2)); + // } + + // void + // on_read( + // beast::error_code ec, + // std::size_t bytes_transferred) + // { + // boost::ignore_unused(bytes_transferred); + + // // This indicates that the WSSServer was closed + // if(ec == websocket::error::closed) + // return; + + // if(ec) + // fail(ec, "read"); + + // // Echo the message + // wss_.text(wss_.got_text()); + // wss_.async_write( + // buffer_.data(), + // std::bind( + // &WSSServer::on_write, + // shared_from_this(), + // std::placeholders::_1, + // std::placeholders::_2)); + // } + + // void + // on_write( + // beast::error_code ec, + // std::size_t bytes_transferred) + // { + // boost::ignore_unused(bytes_transferred); + + // if(ec) + // return fail(ec, "write"); + + // // Clear the buffer + // buffer_.consume(buffer_.size()); + + // // Do another read + // do_read(); + // } + private: + websocket::stream<ssl::stream<tcp::socket> > wss_; + beast::flat_buffer buffer_; + }; + + public: + 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) { + beast::error_code ec; + endpoint_ = {boost::asio::ip::make_address(address), port}; + + std::cout << "DEBUG: Adding certificate" << std::endl; + // Load the certificate + ctx_.use_certificate( + boost::asio::buffer(certificate.c_str(), certificate.size()), + ssl::context::file_format::pem, + ec); + if (ec) { + fail(ec, "Load certficate"); + return; + } + + std::cout << "DEBUG: Adding private key" << std::endl; + // 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(ec, "Load private key"); + return; + } + + // Open the acceptor + acceptor_.open(endpoint_.protocol(), ec); + if (ec) { + fail(ec, "open"); + return; + } + + // Allow address reuse + acceptor_.set_option(net::socket_base::reuse_address(true), ec); + if (ec) { + fail(ec, "set_option"); + return; + } + + std::cout << "DEBUG: Binding to host:" << address << ", port:" << port + << std::endl; + // Bind to the server address + acceptor_.bind(endpoint_, ec); + if (ec) { + fail(ec, "bind"); + return; + } + + std::cout << "DEBUG: Start listening for connections" << std::endl; + // Start listening for connections + acceptor_.listen(net::socket_base::max_listen_connections, ec); + if (ec) { + fail(ec, "listen"); + return; + } + } + + // Start accepting incoming connections + void run() { + do_accept(); + } + + void stop() { + std::cout << "SERVER: Closing connection" << std::endl; + + try { + ioc_.stop(); + acceptor_.close(); + } catch (...) { + std::cout << "SERVER: Failed to close connection" << std::endl; + } + } + + private: + void do_accept() { + if (acceptor_.is_open()) { + std::cout << "DEBUG: Waiting to accept incoming connections" << std::endl; + // The new connection gets its own strand + acceptor_.async_accept(socket_, + std::bind(&WSSSession::on_accept, + shared_from_this(), + std::placeholders::_1)); + ioc_.run(); + } + } + + void on_accept(boost::system::error_code ec) { + std::cout << "DEBUG: Accepted incoming connection" << std::endl; + if (ec) { + fail(ec, "accept"); + ioc_.stop(); + return; + } else { + std::cout << "DEBUG: Creating secure websocket server" << std::endl; + // Create the session and run it + wss_ = std::make_shared<WSSServer>(std::move(socket_), ctx_); + wss_->run(); + } + } + + private: + boost::asio::io_context ioc_; + tcp::acceptor acceptor_; + tcp::socket socket_; + ssl::context ctx_; + tcp::endpoint endpoint_; + std::shared_ptr<WSSServer> wss_; +}; + #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/websocket_connection_test.cc b/src/components/transport_manager/test/websocket_connection_test.cc index e86bab8c6c..a2af3893ba 100644 --- a/src/components/transport_manager/test/websocket_connection_test.cc +++ b/src/components/transport_manager/test/websocket_connection_test.cc @@ -53,19 +53,23 @@ using namespace ::transport_manager::transport_adapter; class WebsocketConnectionTest : public ::testing::Test { public: - void StartServer() { + void StartWSServer() { ws_session = std::make_shared<WSSession>(kHost, kPort); ws_session->run(); } + void StartWSSServer() { + wss_session = + std::make_shared<WSSSession>(kHost, kPort, kCertificate, kPrivateKey); + wss_session->run(); + } + protected: WebsocketConnectionTest() : last_state_("app_storage_folder", "app_info_storage") {} ~WebsocketConnectionTest() {} void SetUp() OVERRIDE { - dev_id = "ws://" + kHost + ":" + std::to_string(kPort) + "/"; - uniq_id = dev_id; app_handle = 0; ON_CALL(transport_manager_settings, use_last_state()) .WillByDefault(Return(true)); @@ -79,9 +83,97 @@ class WebsocketConnectionTest : public ::testing::Test { std::string kHost = "127.0.0.1"; uint16_t kPort = 8080; std::shared_ptr<WSSession> ws_session; + std::shared_ptr<WSSSession> wss_session; + // Sample certificate for localhost + std::string kCertificate = + "-----BEGIN " + "CERTIFICATE-----\nMIIC/" + "jCCAeagAwIBAgIJAIZjLucUID1mMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV\nBAMMCTEyNy4" + "wLjAuMTAeFw0xOTA1MDYxNDA1MzdaFw0yMjA1MDUxNDA1MzdaMBQx\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"; + // Sample private key + std::string kPrivateKey = + "-----BEGIN PRIVATE " + "KEY-----" + "\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC3xOUIXNph0yLs\nd9Je" + "3hxYxS3c2HlxPL3GlyqHWpQhvBJEcaIwzn1L/" + "stg1ScxwDIhsTBQBHEs+9ZP\noxWcITRvDy2NG9+" + "goZ1LKxpDkiVqtzilBa4PCsTvQLfXSybGvkdDdA9WzKD2HaQo\nt5tG9KtiEx3ueqirky06y" + "D4mvqlxhFQ+xKBIVPbZ8JXMBKbFFETFwp9vhYR8qjfc\nIxZJXZruJPFNziTiuN/OS/" + "uESaCFfHbGGsADAH/lrsclqoj0Ez8a/Kt92WZus9F6\n4LIMR2AgrK8sfC/VG/" + "WcLtJonu3i+t62Ha/" + "OoFC75xdW2HDrpyhXO9awWBWpWw+m\nGLK3u995AgMBAAECggEBALR7lukgsIY1I+" + "6UO7NadwKkHUfx/" + "0u8eTIKkwU+a4+" + "6\nM0KvS7idhCdYBYyDq6vL9DBs4mMkCbdBWxj5taaSYfnVMUqOHpKXR3Fk0rWcWk01\nx1c" + "jffBeBk5oBGZY86trg3f3C32XGVq+" + "f9RRhxooAA4hclsecnuX32sE8S2CQc4u\nm5HkpmUYXcFtmxaK6WT9mQDcsv/" + "29IY+2AxX4U+" + "yNywPrDZKDJPzom3v0FNb7GWr\nW41ALJrreCq7MBEJibEHjeXBjkhUgXjC9VLs+" + "tFbdqYwDaPfuEIHSeUV6GNpkhvm\nKuBcRCjrmGMwHcqzGOIr4uokWXcXOR2oLRMT7qlrduU" + "CgYEA3qP+lx0jiuhMaUrL\nhEAgHQP533jFnxSyMFiTn9Eg4uF6DguswJ7mUS11/" + "8EhROthn8advoJe1dXpz3SP\nn6TSR/" + "vxN6q46WsDspxtCrVBbF3R5m+FEgiZ+vnTsT3e52ecdskCkJ6/XjE8IU0B\nDR89DKtCZ/" + "vN2uDWvhcuDQyz6l8CgYEA003gEPMJy9Z8mfnRxMMv1LuNi0gbFQWS\nkyUTVwzxZ63B50N9" + "WQZoa8HBUftfPupK4YcLV7nxwE9WYxMSbceiXboTI/Pjj4QM\n2O+uKDQjGA441Bb2bH/" + "AVOz+vhhAc2glMncI5UvjUA2gfiy+JcBUDbQNHR/ux4Nd\ntz/R1W0itScCgYEAmM/" + "K0IGJgbALskFKCs3QvNmbycylJ5kW7KP/" + "PzRU9CR6l+Vf\nfx4RSyp+" + "0ToH0bwVV4sFflsRKIgYYPHKGnMQeaPtXp3pKRzwfslq9myOKQkEJrvo\nhAYvWdnbeM3Ujt" + "PIyqcRAZ5UDxyP/" + "vNRg2YriuSJQcHM+" + "yxTeEmErCRJ4NUCgYEA\nlKh2GhaSbsjgcodyUAauMPEeXL4G55w7CbCM0bJ2Z+" + "WzxFsT5bz/W8g9lMrPsHiu\n48nZbgeQkCaA9UTmszs+/Me7TZD5KO/" + "TBhBhq0E662KeEoBxL9YU3uq5Mc3oEglU\nGhPquz7PlnNv1TTvNaoAuH9StonPuKewGAsbO" + "hcE0wMCgYAjokPhXZi0z3YgwAEY\nCkSLMiHvAn+gd/pr7GIhvfR4MkxEFBoj1GxBJM/By4/" + "tHYMJQCuxFVZqPYH/" + "AWQ2\noVeuFoEjZXaGNV4SfkhNW7W+Btqt+aXT5+8F176+" + "cvG4uex59XGWG52wKOgLFkmk\nJ00j679GO7h2Fpt4I9bml2VfWw==\n-----END " + "PRIVATE KEY-----\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) { +TEST_F(WebsocketConnectionTest, WSConnection_SUCCESS) { + dev_id = "ws://" + kHost + ":" + std::to_string(kPort) + "/"; + uniq_id = dev_id; + transport_manager::transport_adapter::CloudAppProperties properties{ .endpoint = dev_id, .certificate = "no cert", @@ -91,7 +183,7 @@ TEST_F(WebsocketConnectionTest, WSConnection) { .hybrid_app_preference = "CLOUD"}; // Start server - std::thread t1(&WebsocketConnectionTest::StartServer, this); + std::thread t1(&WebsocketConnectionTest::StartWSServer, this); sleep(1); // Start client @@ -102,7 +194,6 @@ TEST_F(WebsocketConnectionTest, WSConnection) { ta_cloud->CreateDevice(uniq_id); auto connection = cta.FindPendingConnection(dev_id, app_handle); - std::cout << "Pending connection: " << (connection != NULL) << std::endl; EXPECT_NE(connection, nullptr); @@ -124,6 +215,93 @@ TEST_F(WebsocketConnectionTest, WSConnection) { t1.join(); } +TEST_F(WebsocketConnectionTest, WSSConnection_SUCCESS) { + dev_id = "wss://" + kHost + ":" + std::to_string(kPort) + "/"; + uniq_id = dev_id; + transport_manager::transport_adapter::CloudAppProperties properties{ + .endpoint = dev_id, + .certificate = kCertificate, + .enabled = true, + .auth_token = "auth_token", + .cloud_transport_type = "WSS", + .hybrid_app_preference = "CLOUD"}; + + // Start server + std::thread t1(&WebsocketConnectionTest::StartWSSServer, this); + sleep(1); + + // Start client + CloudWebsocketTransportAdapter cta(last_state_, transport_manager_settings); + cta.SetAppCloudTransportConfig(uniq_id, properties); + + TransportAdapterImpl* ta_cloud = &cta; + ta_cloud->CreateDevice(uniq_id); + + auto connection = cta.FindPendingConnection(dev_id, app_handle); + + EXPECT_NE(connection, nullptr); + + std::shared_ptr<WebsocketClientConnection> wss_connection = + std::dynamic_pointer_cast<WebsocketClientConnection>(connection); + + EXPECT_NE(wss_connection.use_count(), 0); + + // 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_IncorrectCert) { + dev_id = "wss://" + kHost + ":" + std::to_string(kPort) + "/"; + uniq_id = dev_id; + transport_manager::transport_adapter::CloudAppProperties properties{ + .endpoint = dev_id, + .certificate = kIncorrectCertificate, + .enabled = true, + .auth_token = "auth_token", + .cloud_transport_type = "WSS", + .hybrid_app_preference = "CLOUD"}; + + // Start server + std::thread t1(&WebsocketConnectionTest::StartWSSServer, this); + sleep(1); + + // Start client + CloudWebsocketTransportAdapter cta(last_state_, transport_manager_settings); + cta.SetAppCloudTransportConfig(uniq_id, properties); + + TransportAdapterImpl* ta_cloud = &cta; + ta_cloud->CreateDevice(uniq_id); + + auto connection = cta.FindPendingConnection(dev_id, app_handle); + + EXPECT_NE(connection, nullptr); + + std::shared_ptr<WebsocketClientConnection> wss_connection = + std::dynamic_pointer_cast<WebsocketClientConnection>(connection); + + EXPECT_NE(wss_connection.use_count(), 0); + + // 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 |