From 1485d5ec8bc8243d723f1c22f3b1cd708785c377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Korina=20=C5=A0imi=C4=8Devi=C4=87?= Date: Thu, 18 Jan 2024 10:37:13 +0100 Subject: [PATCH] Add enhanced auth unit tests Summary: related to T12015 Reviewers: ivica Reviewed By: ivica Subscribers: miljen, iljazovic Differential Revision: https://repo.mireo.local/D27425 --- test/include/test_common/packet_util.hpp | 27 ++- test/integration/coroutine.cpp | 3 +- test/unit/compilation_checks.cpp | 6 +- test/unit/connect_op.cpp | 242 ++++++++++++++++++++++- 4 files changed, 265 insertions(+), 13 deletions(-) diff --git a/test/include/test_common/packet_util.hpp b/test/include/test_common/packet_util.hpp index 886d3f2..b49d25a 100644 --- a/test/include/test_common/packet_util.hpp +++ b/test/include/test_common/packet_util.hpp @@ -61,6 +61,18 @@ inline std::string_view code_to_str(control_code_e code) { } } +template +inline std::string to_readable_props(Props props) { + std::ostringstream stream; + props.visit([&stream](const auto&, const auto& v) -> bool { + if constexpr (is_optional) + if (v.has_value()) + stream << *v << " "; + return true; + }); + return stream.str(); +} + inline std::string to_readable_packet(std::string packet) { auto control_byte = uint8_t(*packet.data()); auto code = extract_code(control_byte); @@ -70,10 +82,7 @@ inline std::string to_readable_packet(std::string packet) { std::ostringstream stream; - if ( - code == control_code_e::connect || code == control_code_e::connack || - code == control_code_e::disconnect - ) { + if (code == control_code_e::connack || code == control_code_e::disconnect) { stream << code_to_str(code); return stream.str(); } @@ -83,6 +92,13 @@ inline std::string to_readable_packet(std::string packet) { begin, packet.cend(), decoders::basic::varint_ ); + if (code == control_code_e::connect) { + auto connect = decoders::decode_connect(*varlen, begin); + auto& [cli_id, uname, pwd, keep_alive, clean_start, props, will] = *connect; + stream << code_to_str(code); + stream << " props: " << to_readable_props(props); + return stream.str(); + } if (code == control_code_e::publish) { auto publish = decoders::decode_publish( control_byte, *varlen, begin @@ -91,6 +107,9 @@ inline std::string to_readable_packet(std::string packet) { stream << code_to_str(code); stream << (packet_id ? " " + std::to_string(*packet_id) : ""); stream << " flags: " << std::bitset<8>(flags); + stream << " topic: " << topic; + stream << " payload: " << payload; + stream << " props: " << to_readable_props(props); return stream.str(); } diff --git a/test/integration/coroutine.cpp b/test/integration/coroutine.cpp index bfd164f..65efd0f 100644 --- a/test/integration/coroutine.cpp +++ b/test/integration/coroutine.cpp @@ -112,8 +112,7 @@ BOOST_AUTO_TEST_CASE(tcp_client_check) { using client_type = mqtt_client; client_type c(ioc, ""); - c.credentials("tcp-tester", "", "") - .brokers("broker.hivemq.com", 1883) + c.brokers("broker.hivemq.com", 1883) .will({ "test/mqtt-test", "Client disconnected!", qos_e::at_least_once }) .async_run(asio::detached); diff --git a/test/unit/compilation_checks.cpp b/test/unit/compilation_checks.cpp index cd239a4..e5074fe 100644 --- a/test/unit/compilation_checks.cpp +++ b/test/unit/compilation_checks.cpp @@ -20,8 +20,7 @@ using namespace async_mqtt5; BOOST_AUTO_TEST_SUITE(traits/*, *boost::unit_test::disabled()*/) -class good_authenticator { -public: +struct good_authenticator { good_authenticator() = default; template @@ -44,8 +43,7 @@ class good_authenticator { } }; -class bad_authenticator { -public: +struct bad_authenticator { bad_authenticator() = default; void async_auth(std::string /* data */) {} diff --git a/test/unit/connect_op.cpp b/test/unit/connect_op.cpp index a0869ea..ac6dda0 100644 --- a/test/unit/connect_op.cpp +++ b/test/unit/connect_op.cpp @@ -18,7 +18,7 @@ using namespace async_mqtt5; BOOST_AUTO_TEST_SUITE(connect_op/*, *boost::unit_test::disabled()*/) struct shared_test_data { - error_code success{}; + error_code success {}; error_code fail = asio::error::not_connected; const std::string connect = encoders::encode_connect( @@ -33,7 +33,7 @@ using test::after; using std::chrono_literals::operator ""ms; void run_unit_test( - test::msg_exchange broker_side, + detail::mqtt_ctx mqtt_ctx, test::msg_exchange broker_side, asio::any_completion_handler op_handler ) { constexpr int expected_handlers_called = 1; @@ -46,7 +46,6 @@ void run_unit_test( ); test::test_stream stream(executor); - detail::mqtt_ctx mqtt_ctx; authority_path ap; auto eps = asio::ip::tcp::resolver(executor).resolve("127.0.0.1", ""); @@ -64,6 +63,15 @@ void run_unit_test( BOOST_CHECK(broker.received_all_expected()); } +void run_unit_test( + test::msg_exchange broker_side, + asio::any_completion_handler op_handler +) { + return run_unit_test( + detail::mqtt_ctx(), std::move(broker_side), std::move(op_handler) + ); +} + BOOST_FIXTURE_TEST_CASE(successfully_connect, shared_test_data) { test::msg_exchange broker_side; broker_side @@ -222,4 +230,232 @@ BOOST_FIXTURE_TEST_CASE(receive_unexpected_auth, shared_test_data) { run_unit_test(std::move(broker_side), std::move(handler)); } +// enhanced auth + +struct test_authenticator { + test_authenticator() = default; + + template + decltype(auto) async_auth( + auth_step_e step, std::string data, + CompletionToken&& token + ) { + using error_code = boost::system::error_code; + using Signature = void (error_code, std::string); + + auto initiate = [](auto handler, auth_step_e, std::string) { + asio::dispatch( + asio::prepend(std::move(handler), error_code {}, "") + ); + }; + + return asio::async_initiate( + initiate, token, step, std::move(data) + ); + } + + std::string_view method() const { + return "method"; + } +}; + +struct shared_test_auth_data { + error_code success {}; + error_code fail = asio::error::not_connected; + + const std::string connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 10, false, init_connect_props(), std::nullopt + ); + + const std::string connack = encoders::encode_connack( + true, reason_codes::success.value(), {} + ); + + const std::string auth_challenge = encoders::encode_auth( + reason_codes::continue_authentication.value(), init_auth_props() + ); + + const std::string auth_response = auth_challenge; + + connect_props init_connect_props() { + connect_props cprops; + cprops[prop::authentication_method] = "method"; + cprops[prop::authentication_data] = ""; + return cprops; + } + + auth_props init_auth_props() { + auth_props aprops; + aprops[prop::authentication_method] = "method"; + aprops[prop::authentication_data] = ""; + return aprops; + } +}; + +BOOST_FIXTURE_TEST_CASE(successful_auth, shared_test_auth_data) { + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(2ms)) + .reply_with(auth_challenge, after(3ms)) + .expect(auth_response) + .complete_with(success, after(2ms)) + .reply_with(connack, after(4ms)); + + auto handler = [&](error_code ec) { + BOOST_CHECK(ec == success); + }; + + detail::mqtt_ctx mqtt_ctx; + mqtt_ctx.co_props = init_connect_props(); + mqtt_ctx.authenticator = test_authenticator(); + run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); +} + +BOOST_FIXTURE_TEST_CASE(malformed_auth_rc, shared_test_auth_data) { + const std::string malformed_auth_challenge = encoders::encode_auth( + reason_codes::server_busy.value(), init_auth_props() + ); + + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(2ms)) + .reply_with(malformed_auth_challenge, after(3ms)); + + auto handler = [&](error_code ec) { + BOOST_CHECK(ec == client::error::malformed_packet); + }; + + detail::mqtt_ctx mqtt_ctx; + mqtt_ctx.co_props = init_connect_props(); + mqtt_ctx.authenticator = test_authenticator(); + run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); +} + +BOOST_FIXTURE_TEST_CASE(mismatched_auth_method, shared_test_auth_data) { + auth_props aprops; + aprops[prop::authentication_method] = "wrong method"; + + const std::string mismatched_auth_challenge = encoders::encode_auth( + reason_codes::continue_authentication.value(), aprops + ); + + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(2ms)) + .reply_with(mismatched_auth_challenge, after(3ms)); + + auto handler = [&](error_code ec) { + BOOST_CHECK(ec == client::error::malformed_packet); + }; + + detail::mqtt_ctx mqtt_ctx; + mqtt_ctx.co_props = init_connect_props(); + mqtt_ctx.authenticator = test_authenticator(); + run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); +} + +BOOST_FIXTURE_TEST_CASE(fail_to_send_auth, shared_test_auth_data) { + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(2ms)) + .reply_with(auth_challenge, after(3ms)) + .expect(auth_response) + .complete_with(fail, after(2ms)); + + auto handler = [&](error_code ec) { + BOOST_CHECK(ec == fail); + }; + + detail::mqtt_ctx mqtt_ctx; + mqtt_ctx.co_props = init_connect_props(); + mqtt_ctx.authenticator = test_authenticator(); + run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); +} + +template +struct fail_test_authenticator { + fail_test_authenticator() = default; + + template + decltype(auto) async_auth( + auth_step_e step, std::string data, + CompletionToken&& token + ) { + using error_code = boost::system::error_code; + using Signature = void (error_code, std::string); + + auto initiate = [](auto handler, auth_step_e step, std::string) { + error_code ec; + if (fail_on_step == step) + ec = asio::error::no_recovery; + + asio::dispatch( + asio::prepend(std::move(handler), ec, "") + ); + }; + + return asio::async_initiate( + initiate, token, step, std::move(data) + ); + } + + std::string_view method() const { + return "method"; + } +}; + +BOOST_FIXTURE_TEST_CASE(auth_step_client_initial_fail, shared_test_auth_data) { + test::msg_exchange broker_side; + + auto handler = [&](error_code ec) { + BOOST_CHECK(ec == asio::error::try_again); + }; + + detail::mqtt_ctx mqtt_ctx; + mqtt_ctx.co_props = init_connect_props(); + mqtt_ctx.authenticator = fail_test_authenticator(); + run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); +} + +BOOST_FIXTURE_TEST_CASE(auth_step_server_challenge_fail, shared_test_auth_data) { + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(2ms)) + .reply_with(auth_challenge, after(3ms)); + + auto handler = [&](error_code ec) { + BOOST_CHECK(ec == asio::error::try_again); + }; + + detail::mqtt_ctx mqtt_ctx; + mqtt_ctx.co_props = init_connect_props(); + mqtt_ctx.authenticator = fail_test_authenticator(); + run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); +} + +BOOST_FIXTURE_TEST_CASE(auth_step_server_final_fail, shared_test_auth_data) { + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(2ms)) + .reply_with(auth_challenge, after(3ms)) + .expect(auth_response) + .complete_with(success, after(2ms)) + .reply_with(connack, after(4ms)); + + auto handler = [&](error_code ec) { + BOOST_CHECK(ec == asio::error::try_again); + }; + + detail::mqtt_ctx mqtt_ctx; + mqtt_ctx.co_props = init_connect_props(); + mqtt_ctx.authenticator = fail_test_authenticator(); + run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); +} + BOOST_AUTO_TEST_SUITE_END();