diff --git a/agent_lib/CMakeLists.txt b/agent_lib/CMakeLists.txt index 87840a559..748dd4234 100644 --- a/agent_lib/CMakeLists.txt +++ b/agent_lib/CMakeLists.txt @@ -131,6 +131,7 @@ set(AGENT_SOURCES # src/mqtt HEADER_FILE_ONLY + "${CMAKE_CURRENT_SOURCE_DIR}/../src/mqtt/mqtt_authorization.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/../src/mqtt/mqtt_client.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/../src/mqtt/mqtt_server.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/../src/mqtt/mqtt_client_impl.hpp" diff --git a/src/mqtt/mqtt_authorization.hpp b/src/mqtt/mqtt_authorization.hpp new file mode 100644 index 000000000..35576c766 --- /dev/null +++ b/src/mqtt/mqtt_authorization.hpp @@ -0,0 +1,108 @@ +// +// Copyright Copyright 2009-2022, AMT – The Association For Manufacturing Technology (“AMT”) +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#pragma once + +#include "source/adapter/adapter.hpp" +#include "source/adapter/adapter_pipeline.hpp" + +using namespace std; +using namespace mtconnect; +using namespace mtconnect::configuration; + +namespace mtconnect { + namespace mqtt_client { + + class MqttTopicPermission + { + enum AuthorizationType + { + Allow, + Deny + }; + + enum TopicMode + { + Subscribe, + Publish, + Both + }; + + void MqttTopicPermission(const std::string &topic, const std::string& clientId) + { + m_topic = topic; + m_clientId = clientId; + m_type = AuthorizationType::Allow; + m_mode = TopicMode::Subscribe; + } + + void MqttTopicPermission(const std::string& topic, const std::string& clientId, + AuthorizationType type) + { + m_topic = topic; + m_clientId = clientId; + m_type = type; + m_mode = TopicMode::Subscribe; + } + + void MqttTopicPermission(const std::string& topic, const std::string& clientId, + AuthorizationType type, TopicMode mode) + { + m_topic = topic; + m_clientId = clientId; + m_type = type; + m_mode = mode; + } + + protected: + TopicMode m_mode; + AuthorizationType m_type; + std::string m_clientId; + std::string m_topic; + }; + + class MqttAuthorization : public std::enable_shared_from_this + { + public: + MqttAuthorization(const ConfigOptions &options) : m_options(options) + { + m_clientId = GetOption(options, configuration::MqttClientId); + m_username = GetOption(options, configuration::MqttUserName); + m_password = GetOption(options, configuration::MqttPassword); + } + + virtual ~MqttAuthorization() = default; + + bool checkCredentials() + { + if (!m_username && !m_password) + { + LOG(error) << "MQTT USERNAME_OR_PASSWORD are Not Available"; + return false; + } + + return true; + } + + protected: + std::optional m_username; + std::optional m_password; + std::string m_clientId; + ConfigOptions m_options; + }; + } // namespace MqttAuthorization +} // namespace mtconnect diff --git a/src/mqtt/mqtt_client_impl.hpp b/src/mqtt/mqtt_client_impl.hpp index 25a986f1f..c895211cb 100644 --- a/src/mqtt/mqtt_client_impl.hpp +++ b/src/mqtt/mqtt_client_impl.hpp @@ -134,7 +134,7 @@ namespace mtconnect { else { LOG(debug) << "No connect handler, setting connected"; - m_connected = true; + m_connected = true; } } else @@ -224,9 +224,9 @@ namespace mtconnect { } LOG(debug) << "Subscribing to topic: " << topic; - m_clientId = derived().getClient()->acquire_unique_packet_id(); + m_packetId = derived().getClient()->acquire_unique_packet_id(); derived().getClient()->async_subscribe( - m_clientId, topic.c_str(), mqtt::qos::at_least_once, [topic](mqtt::error_code ec) { + m_packetId, topic.c_str(), mqtt::qos::at_least_once, [topic](mqtt::error_code ec) { if (ec) { LOG(error) << "Subscribe failed: " << topic << ": " << ec.message(); @@ -251,9 +251,9 @@ namespace mtconnect { return false; } - m_clientId = derived().getClient()->acquire_unique_packet_id(); + m_packetId = derived().getClient()->acquire_unique_packet_id(); derived().getClient()->async_publish( - m_clientId, topic, payload, mqtt::qos::at_least_once | mqtt::retain::yes, + m_packetId, topic, payload, mqtt::qos::at_least_once | mqtt::retain::yes, [topic](mqtt::error_code ec) { if (ec) { @@ -332,7 +332,7 @@ namespace mtconnect { unsigned int m_port {1883}; - std::uint16_t m_clientId {0}; + std::uint16_t m_packetId {0}; std::optional m_username; @@ -425,16 +425,7 @@ namespace mtconnect { if (cacert) { m_client->get_ssl_context().load_verify_file(*cacert); - } - auto private_key = GetOption(m_options, configuration::MqttPrivateKey); - auto cert = GetOption(m_options, configuration::MqttCert); - if (private_key && cert) - { - m_client->get_ssl_context().set_verify_mode(boost::asio::ssl::verify_peer); - m_client->get_ssl_context().use_certificate_chain_file(*cert); - m_client->get_ssl_context().use_private_key_file(*private_key, - boost::asio::ssl::context::pem); - } + } } return m_client; diff --git a/src/mqtt/mqtt_server_impl.hpp b/src/mqtt/mqtt_server_impl.hpp index 3461cfad8..eba69851f 100644 --- a/src/mqtt/mqtt_server_impl.hpp +++ b/src/mqtt/mqtt_server_impl.hpp @@ -300,18 +300,39 @@ namespace mtconnect { boost::asio::ssl::context ctx(boost::asio::ssl::context::tlsv12); ctx.set_options(boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::single_dh_use); - auto serverPrivateKey = GetOption(m_options, configuration::TlsPrivateKey); - auto serverCert = GetOption(m_options, configuration::TlsCertificateChain); - ctx.use_certificate_chain_file(*serverCert); - // ctx.use_tmp_dh_file(*GetOption(m_options, configuration::TlsDHKey)); - ctx.use_private_key_file(*serverPrivateKey, boost::asio::ssl::context::pem); - if (HasOption(m_options, configuration::TlsCertificatePassword)) + if (HasOption(m_options, configuration::TlsCertificateChain) && + HasOption(m_options, configuration::TlsPrivateKey) && + HasOption(m_options, configuration::TlsDHKey)) { - ctx.set_password_callback( - [this](size_t, boost::asio::ssl::context_base::password_purpose) -> string { - return *GetOption(m_options, configuration::TlsCertificatePassword); - }); + LOG(info) << "Server: Initializing TLS support"; + if (HasOption(m_options, configuration::TlsCertificatePassword)) + { + ctx.set_password_callback( + [this](size_t, boost::asio::ssl::context_base::password_purpose) -> string { + return *GetOption(m_options, configuration::TlsCertificatePassword); + }); + } + + auto serverPrivateKey = GetOption(m_options, configuration::TlsPrivateKey); + auto serverCert = GetOption(m_options, configuration::TlsCertificateChain); + auto serverDHKey = GetOption(m_options, configuration::TlsDHKey); + + ctx.use_certificate_chain_file(*serverCert); + ctx.use_private_key_file(*serverPrivateKey, boost::asio::ssl::context::pem); + ctx.use_tmp_dh_file(*serverDHKey); + + if (IsOptionSet(m_options, configuration::TlsVerifyClientCertificate)) + { + LOG(info) << "Server: Will only accept client connections with valid certificates"; + + ctx.set_verify_mode(boost::asio::ssl::verify_peer); + if (HasOption(m_options, configuration::TlsClientCAs)) + { + LOG(info) << "Server: Adding Client Certificates."; + ctx.load_verify_file(*GetOption(m_options, configuration::TlsClientCAs)); + } + } } m_server.emplace(boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), m_port), std::move(ctx), m_ioContext); @@ -346,24 +367,15 @@ namespace mtconnect { boost::asio::ssl::context ctx(boost::asio::ssl::context::tlsv12); ctx.set_options(boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::single_dh_use); - auto serverPrivateKey = GetOption(m_options, configuration::TlsPrivateKey); - auto serverCert = GetOption(m_options, configuration::TlsCertificateChain); - ctx.use_certificate_chain_file(*serverCert); - // ctx.use_tmp_dh_file(*GetOption(m_options, configuration::TlsDHKey)); - ctx.use_private_key_file(*serverPrivateKey, boost::asio::ssl::context::pem); - /*if (IsOptionSet(m_options, configuration::TlsVerifyClientCertificate)) + if (HasOption(m_options, configuration::TlsCertificateChain) && + HasOption(m_options, configuration::TlsPrivateKey)) { - LOG(info) << "Server: Will only accept client connections with valid certificates"; - - ctx.set_verify_mode(boost::asio::ssl::verify_peer | - boost::asio::ssl::verify_fail_if_no_peer_cert); - if (HasOption(m_options, configuration::MqttCaCert)) - { - LOG(info) << "Server: Adding Client Certificates."; - ctx.load_verify_file(*GetOption(m_options, configuration::MqttCaCert)); - } - }*/ + auto serverPrivateKey = GetOption(m_options, configuration::TlsPrivateKey); + auto serverCert = GetOption(m_options, configuration::TlsCertificateChain); + ctx.use_certificate_chain_file(*serverCert); + ctx.use_private_key_file(*serverPrivateKey, boost::asio::ssl::context::pem); + } m_server.emplace(boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), m_port), std::move(ctx), m_ioContext); diff --git a/test/mqtt_isolated_test.cpp b/test/mqtt_isolated_test.cpp index e626c8702..ad08765a2 100644 --- a/test/mqtt_isolated_test.cpp +++ b/test/mqtt_isolated_test.cpp @@ -47,6 +47,7 @@ const string MqttClientKey {PROJECT_ROOT_DIR "/test/resources/client.key"}; const string ServerCertFile(PROJECT_ROOT_DIR "/test/resources/user.crt"); const string ServerKeyFile {PROJECT_ROOT_DIR "/test/resources/user.key"}; const string ServerDhFile {PROJECT_ROOT_DIR "/test/resources/dh2048.pem"}; +const string ClientCA(PROJECT_ROOT_DIR "/test/resources/clientca.crt"); class MqttIsolatedUnitTest : public testing::Test { @@ -58,46 +59,40 @@ class MqttIsolatedUnitTest : public testing::Test } void TearDown() override - { + { if (m_client) { + m_client->stop(); - m_agentTestHelper->m_ioContext.run_for(100ms); + while (m_agentTestHelper->m_ioContext.run_one_for(10ms)) + ; m_client.reset(); } + if (m_server) { m_server->stop(); m_agentTestHelper->m_ioContext.run_for(500ms); m_server.reset(); } + m_agentTestHelper.reset(); m_jsonPrinter.reset(); } void createServer(const ConfigOptions &options) - { + { bool withTlsOption = IsOptionSet(options, configuration::MqttTls); - ConfigOptions opts(options); - MergeOptions(opts, {{ServerIp, "127.0.0.1"s}, - {MqttPort, 0}, - {MqttTls, withTlsOption}, - {AutoAvailable, false}, - {TlsCertificateChain, ServerCertFile}, - {TlsPrivateKey, ServerKeyFile}, - {TlsCertificatePassword, "mtconnect"s}, - {RealTime, false}}); - if (withTlsOption) { - m_server = - make_shared(m_agentTestHelper->m_ioContext, opts); + m_server = make_shared(m_agentTestHelper->m_ioContext, + options); } else { - m_server = - make_shared(m_agentTestHelper->m_ioContext, opts); + m_server = make_shared(m_agentTestHelper->m_ioContext, + options); } } @@ -138,17 +133,11 @@ class MqttIsolatedUnitTest : public testing::Test void createClient(const ConfigOptions &options, unique_ptr &&handler) { - bool withTlsOption = IsOptionSet(options, configuration::MqttTls); + bool withTlsOption = IsOptionSet(options, configuration::MqttTls); ConfigOptions opts(options); - MergeOptions(opts, {{MqttHost, "127.0.0.1"s}, - {MqttPort, m_port}, - {MqttTls, withTlsOption}, - {AutoAvailable, false}, - {MqttCaCert, MqttClientCACert}, - {MqttCert, MqttClientCert}, - {MqttPrivateKey, MqttClientKey}, - {RealTime, false}}); + + MergeOptions(opts, {{MqttPort, m_port}}); if (withTlsOption) { @@ -163,7 +152,7 @@ class MqttIsolatedUnitTest : public testing::Test } bool startClient() - { + { bool started = m_client && m_client->start(); if (started) { @@ -180,9 +169,16 @@ class MqttIsolatedUnitTest : public testing::Test uint16_t m_port {0}; }; + TEST_F(MqttIsolatedUnitTest, mqtt_client_should_connect_to_broker) { - ConfigOptions options; + ConfigOptions options{{ServerIp, "127.0.0.1"s}, + {MqttPort, 0}, + {MqttTls, false}, + {AutoAvailable, false}, + {MqttCaCert, MqttClientCACert}, + {RealTime, false}}; + createServer(options); startServer(); @@ -200,7 +196,11 @@ TEST_F(MqttIsolatedUnitTest, mqtt_client_should_connect_to_broker) TEST_F(MqttIsolatedUnitTest, mqtt_tcp_client_should_receive_loopback_publication) { - ConfigOptions options; + ConfigOptions options {{ServerIp, "127.0.0.1"s}, + {MqttPort, 0}, + {MqttTls, false}, + {AutoAvailable, false}, + {RealTime, false}}; createServer(options); startServer(); @@ -215,6 +215,148 @@ TEST_F(MqttIsolatedUnitTest, mqtt_tcp_client_should_receive_loopback_publication client->set_clean_session(true); client->set_keep_alive_sec(30); + client->set_connack_handler( + [&client, &pid_sub1](bool sp, mqtt::connect_return_code connack_return_code) { + std::cout << "Connack handler called" << std::endl; + std::cout << "Session Present: " << std::boolalpha << sp << std::endl; + std::cout << "Connack Return Code: " << connack_return_code << std::endl; + if (connack_return_code == mqtt::connect_return_code::accepted) + { + pid_sub1 = client->acquire_unique_packet_id(); + + client->async_subscribe(pid_sub1, "mqtt_tcp_client_cpp/topic1", MQTT_NS::qos::at_most_once, + //[optional] checking async_subscribe completion code + [](MQTT_NS::error_code ec) { + EXPECT_FALSE(ec); + std::cout << "async_tcp_subscribe callback: " << ec.message() + << std::endl; + }); + } + return true; + }); + client->set_close_handler([] { std::cout << "closed" << std::endl; }); + + client->set_suback_handler([&client, &pid_sub1](std::uint16_t packet_id, + std::vector results) { + std::cout << "suback received. packet_id: " << packet_id << std::endl; + for (auto const &e : results) + { + std::cout << "subscribe result: " << e << std::endl; + } + + if (packet_id == pid_sub1) + { + client->async_publish("mqtt_tcp_client_cpp/topic1", "test1", MQTT_NS::qos::at_most_once, + //[optional] checking async_publish completion code + [](MQTT_NS::error_code ec) { + EXPECT_FALSE(ec); + + std::cout << "async_tcp_publish callback: " << ec.message() << std::endl; + EXPECT_EQ(ec.message(), "Success"); + }); + return true; + } + + return true; + }); + + client->set_close_handler([] { std::cout << "closed" << std::endl; }); + + client->set_suback_handler([&client, &pid_sub1](std::uint16_t packet_id, + std::vector results) { + std::cout << "suback received. packet_id: " << packet_id << std::endl; + for (auto const &e : results) + { + std::cout << "subscribe result: " << e << std::endl; + } + + if (packet_id == pid_sub1) + { + client->async_publish("mqtt_tcp_client_cpp/topic1", "test1", MQTT_NS::qos::at_most_once, + //[optional] checking async_publish completion code + [packet_id](MQTT_NS::error_code ec) { + EXPECT_FALSE(ec); + + std::cout << "async_tcp_publish callback: " << ec.message() << std::endl; + ASSERT_TRUE(packet_id); + }); + } + + return true; + }); + + bool received = false; + client->set_publish_handler([&client, &received](mqtt::optional packet_id, + mqtt::publish_options pubopts, + mqtt::buffer topic_name, mqtt::buffer contents) { + std::cout << "publish received." + << " dup: " << pubopts.get_dup() << " qos: " << pubopts.get_qos() + << " retain: " << pubopts.get_retain() << std::endl; + if (packet_id) + std::cout << "packet_id: " << *packet_id << std::endl; + std::cout << "topic_name: " << topic_name << std::endl; + std::cout << "contents: " << contents << std::endl; + + EXPECT_EQ("mqtt_tcp_client_cpp/topic1", topic_name); + EXPECT_EQ("test1", contents); + + client->async_disconnect(); + received = true; + return true; + }); + + client->async_connect(); + + m_agentTestHelper->m_ioContext.run(); + + ASSERT_TRUE(received); +} + +TEST_F(MqttIsolatedUnitTest, mqtt_tls_client_should_receive_loopback_publication) +{ + GTEST_SKIP(); + + ConfigOptions options {{ServerIp, "127.0.0.1"s}, + {MqttPort, 0}, + {MqttTls, true}, + {AutoAvailable, false}, + {TlsCertificateChain, ServerCertFile}, + {TlsPrivateKey, ServerKeyFile}, + {TlsDHKey, ServerDhFile}, + {TlsVerifyClientCertificate,true}, + {TlsClientCAs, MqttClientCACert}, + {MqttCaCert, MqttClientCACert}, + {MqttCert, MqttClientCert}, + {MqttPrivateKey, MqttClientKey}, + {RealTime, false}}; + + createServer(options); + startServer(); + + ASSERT_NE(0, m_port); + + std::uint16_t pid_sub1; + + auto client = mqtt::make_tls_async_client(m_agentTestHelper->m_ioContext, "localhost", m_port); + auto cacert = GetOption(options, configuration::MqttCaCert); + if (cacert) + { + client->get_ssl_context().load_verify_file(*cacert); + } + + auto private_key = GetOption(options, configuration::MqttPrivateKey); + auto cert = GetOption(options, configuration::MqttCert); + if (private_key && cert) + { + client->get_ssl_context().set_verify_mode(boost::asio::ssl::verify_peer); + client->get_ssl_context().use_certificate_chain_file(*cert); + client->get_ssl_context().use_private_key_file(*private_key, boost::asio::ssl::context::pem); + } + + client->set_client_id("cliendId1"); + client->set_clean_session(true); + client->set_keep_alive_sec(30); + client->set_connack_handler([&client, &pid_sub1](bool sp, mqtt::connect_return_code connack_return_code) { std::cout << "Connack handler called" << std::endl; @@ -224,11 +366,11 @@ TEST_F(MqttIsolatedUnitTest, mqtt_tcp_client_should_receive_loopback_publication { pid_sub1 = client->acquire_unique_packet_id(); - client->async_subscribe(pid_sub1, "mqtt_tcp_client_cpp/topic1", MQTT_NS::qos::at_most_once, + client->async_subscribe(pid_sub1, "mqtt_tls_client_cpp/topic1", MQTT_NS::qos::at_most_once, //[optional] checking async_subscribe completion code [](MQTT_NS::error_code ec) { EXPECT_FALSE(ec); - std::cout << "async_tcp_subscribe callback: " << ec.message() + std::cout << "async_tls_subscribe callback: " << ec.message() << std::endl; }); } @@ -246,12 +388,12 @@ TEST_F(MqttIsolatedUnitTest, mqtt_tcp_client_should_receive_loopback_publication if (packet_id == pid_sub1) { - client->async_publish("mqtt_tcp_client_cpp/topic1", "test1", MQTT_NS::qos::at_most_once, + client->async_publish("mqtt_tls_client_cpp/topic1", "test1", MQTT_NS::qos::at_most_once, //[optional] checking async_publish completion code [](MQTT_NS::error_code ec) { EXPECT_FALSE(ec); - std::cout << "async_tcp_publish callback: " << ec.message() + std::cout << "async_tls_publish callback: " << ec.message() << std::endl; EXPECT_EQ(ec.message(), "Success"); }); @@ -273,12 +415,12 @@ TEST_F(MqttIsolatedUnitTest, mqtt_tcp_client_should_receive_loopback_publication if (packet_id == pid_sub1) { - client->async_publish("mqtt_tcp_client_cpp/topic1", "test1", MQTT_NS::qos::at_most_once, + client->async_publish("mqtt_tls_client_cpp/topic1", "test1", MQTT_NS::qos::at_most_once, //[optional] checking async_publish completion code [packet_id](MQTT_NS::error_code ec) { EXPECT_FALSE(ec); - std::cout << "async_tcp_publish callback: " << ec.message() + std::cout << "async_tls_publish callback: " << ec.message() << std::endl; ASSERT_TRUE(packet_id); }); @@ -299,7 +441,7 @@ TEST_F(MqttIsolatedUnitTest, mqtt_tcp_client_should_receive_loopback_publication std::cout << "topic_name: " << topic_name << std::endl; std::cout << "contents: " << contents << std::endl; - EXPECT_EQ("mqtt_tcp_client_cpp/topic1", topic_name); + EXPECT_EQ("mqtt_tls_client_cpp/topic1", topic_name); EXPECT_EQ("test1", contents); client->async_disconnect(); @@ -318,7 +460,19 @@ TEST_F(MqttIsolatedUnitTest, should_connect_using_tls) { GTEST_SKIP(); - ConfigOptions options {{configuration::MqttTls, true}}; + ConfigOptions options {{ServerIp, "127.0.0.1"s}, + {MqttPort, 0}, + {MqttTls, true}, + {AutoAvailable, false}, + {TlsCertificateChain, ServerCertFile}, + {TlsPrivateKey, ServerKeyFile}, + {TlsDHKey, ServerDhFile}, + /*{TlsVerifyClientCertificate, true}, + {TlsClientCAs, ClientCA},*/ + {MqttCaCert, MqttClientCACert}, + /* {MqttCert, MqttClientCert}, + {MqttPrivateKey, MqttClientKey},*/ + {RealTime, false}}; createServer(options); @@ -339,14 +493,14 @@ TEST_F(MqttIsolatedUnitTest, should_connect_using_tls_ws) { GTEST_SKIP(); - ConfigOptions options; - MergeOptions(options, {{ServerIp, "127.0.0.1"s}, + ConfigOptions options {{ServerIp, "127.0.0.1"s}, {MqttPort, 0}, {MqttTls, true}, {AutoAvailable, false}, {TlsCertificateChain, ServerCertFile}, {TlsPrivateKey, ServerKeyFile}, - {RealTime, false}}); + {MqttCaCert, MqttClientCACert}, + {RealTime, false}}; m_server = make_shared(m_agentTestHelper->m_ioContext, options); @@ -357,15 +511,8 @@ TEST_F(MqttIsolatedUnitTest, should_connect_using_tls_ws) auto handler = make_unique(); - ConfigOptions opts; - MergeOptions(opts, {{MqttHost, "127.0.0.1"s}, - {MqttPort, m_port}, - {MqttTls, true}, - {AutoAvailable, false}, - {MqttCaCert, MqttClientCACert}, - {MqttCert, MqttClientCert}, - {MqttPrivateKey, MqttClientKey}, - {RealTime, false}}); + ConfigOptions opts(options); + MergeOptions(opts, {{MqttPort, m_port}}); m_client = make_shared(m_agentTestHelper->m_ioContext, opts, move(handler)); @@ -375,4 +522,38 @@ TEST_F(MqttIsolatedUnitTest, should_connect_using_tls_ws) ASSERT_TRUE(m_client->isConnected()); } -TEST_F(MqttIsolatedUnitTest, should_conenct_using_authentication) { GTEST_SKIP(); } +TEST_F(MqttIsolatedUnitTest, should_conenct_using_tls_authentication) +{ + GTEST_SKIP(); + + ConfigOptions options {{ServerIp, "127.0.0.1"s}, + {MqttPort, 0}, + {MqttTls, true}, + {AutoAvailable, false}, + {TlsCertificateChain, ServerCertFile}, + {TlsPrivateKey, ServerKeyFile}, + {TlsDHKey, ServerDhFile}, + {TlsVerifyClientCertificate, true}, + {TlsClientCAs, ClientCA}, + {MqttCaCert, MqttClientCACert}, + {MqttCert, MqttClientCert}, + {MqttPrivateKey, MqttClientKey}, + {RealTime, false}}; + + createServer(options); + + startServer(); + + ASSERT_NE(0, m_port); + + auto handler = make_unique(); + + ConfigOptions opts(options); + MergeOptions(opts, {{MqttPort, m_port}}); + + createClient(opts, move(handler)); + + ASSERT_TRUE(startClient()); + + ASSERT_TRUE(m_client->isConnected()); +}