Skip to content

Commit

Permalink
Add enhanced auth unit tests
Browse files Browse the repository at this point in the history
Summary: related to T12015

Reviewers: ivica

Reviewed By: ivica

Subscribers: miljen, iljazovic

Differential Revision: https://repo.mireo.local/D27425
  • Loading branch information
ksimicevic committed Jan 18, 2024
1 parent 6267c5f commit 1485d5e
Show file tree
Hide file tree
Showing 4 changed files with 265 additions and 13 deletions.
27 changes: 23 additions & 4 deletions test/include/test_common/packet_util.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,18 @@ inline std::string_view code_to_str(control_code_e code) {
}
}

template <typename Props>
inline std::string to_readable_props(Props props) {
std::ostringstream stream;
props.visit([&stream](const auto&, const auto& v) -> bool {
if constexpr (is_optional<decltype(v)>)
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);
Expand All @@ -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();
}
Expand All @@ -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
Expand All @@ -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();
}

Expand Down
3 changes: 1 addition & 2 deletions test/integration/coroutine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,7 @@ BOOST_AUTO_TEST_CASE(tcp_client_check) {
using client_type = mqtt_client<stream_type>;
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);

Expand Down
6 changes: 2 additions & 4 deletions test/unit/compilation_checks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <typename CompletionToken>
Expand All @@ -44,8 +43,7 @@ class good_authenticator {
}
};

class bad_authenticator {
public:
struct bad_authenticator {
bad_authenticator() = default;

void async_auth(std::string /* data */) {}
Expand Down
242 changes: 239 additions & 3 deletions test/unit/connect_op.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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<void(error_code)> op_handler
) {
constexpr int expected_handlers_called = 1;
Expand All @@ -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", "");

Expand All @@ -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<void(error_code)> 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
Expand Down Expand Up @@ -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 <typename CompletionToken>
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<CompletionToken, Signature>(
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 <auth_step_e fail_on_step>
struct fail_test_authenticator {
fail_test_authenticator() = default;

template <typename CompletionToken>
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<CompletionToken, Signature>(
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<auth_step_e::client_initial>();
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<auth_step_e::server_challenge>();
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<auth_step_e::server_final>();
run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler));
}

BOOST_AUTO_TEST_SUITE_END();

0 comments on commit 1485d5e

Please sign in to comment.