From 14bca520d924fbfd982095bd85b1969371704168 Mon Sep 17 00:00:00 2001 From: Bruno Iljazovic Date: Tue, 6 May 2025 09:54:40 +0200 Subject: [PATCH] Fix parsing of URI paths in hosts string Summary: Fixes #31 Reviewers: ivica Reviewed By: ivica Subscribers: korina, miljen Differential Revision: https://repo.mireo.local/D35126 --- include/boost/mqtt5/impl/endpoints.hpp | 4 +- test/unit/endpoints.cpp | 133 +++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 test/unit/endpoints.cpp diff --git a/include/boost/mqtt5/impl/endpoints.hpp b/include/boost/mqtt5/impl/endpoints.hpp index 820a035..5ae041b 100644 --- a/include/boost/mqtt5/impl/endpoints.hpp +++ b/include/boost/mqtt5/impl/endpoints.hpp @@ -215,8 +215,8 @@ public: auto host_ = as_(+unreserved_)[to_(host)]; auto port_ = as_(':' >> +digit_)[to_(port)]; - auto path_ = as_(x3::char_('/') >> *unreserved_)[to_(path)]; - auto uri_ = *x3::omit[x3::space] >> (host_ >> *port_ >> *path_) >> + auto path_ = as_(+(x3::char_('/') >> *unreserved_))[to_(path)]; + auto uri_ = *x3::omit[x3::space] >> (host_ >> -port_ >> -path_) >> (*x3::omit[x3::space] >> x3::omit[separator_ | x3::eoi]); for (auto b = hosts.begin(); b != hosts.end(); ) { diff --git a/test/unit/endpoints.cpp b/test/unit/endpoints.cpp new file mode 100644 index 0000000..65a5077 --- /dev/null +++ b/test/unit/endpoints.cpp @@ -0,0 +1,133 @@ +// +// Copyright (c) 2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include + +#include + +#include +#ifdef BOOST_ASIO_HAS_CO_AWAIT + +#include +#include +#include +#include + +#include + +using namespace boost::mqtt5; + +BOOST_AUTO_TEST_SUITE(resolve_op) + +struct shared_test_data { + error_code success {}; + + asio::io_context ioc; + asio::steady_timer timer; + detail::log_invoke logger; + detail::endpoints ep; + + shared_test_data() + : timer(ioc.get_executor()), ep(ioc.get_executor(), timer, logger) {} +}; + +using namespace std::chrono_literals; +constexpr auto use_nothrow_awaitable = asio::as_tuple(asio::use_awaitable); + +BOOST_FIXTURE_TEST_CASE(empty_path, shared_test_data) { + bool finished = false; + ep.brokers("", 1000); + + asio::co_spawn(ioc, [&]() -> asio::awaitable { + auto [ec, eps, ap] = co_await ep.async_next_endpoint(use_nothrow_awaitable); + BOOST_TEST(ec == asio::error::host_not_found); + }, [&finished](auto eptr) { finished = !eptr; }); + + ioc.run_for(1s); + BOOST_TEST(finished); +} + +BOOST_FIXTURE_TEST_CASE(single_host, shared_test_data) { + bool finished = false; + ep.brokers("127.0.0.1", 1000); + + asio::co_spawn(ioc, [&]() -> asio::awaitable { + auto [ec, eps, ap] = co_await ep.async_next_endpoint(use_nothrow_awaitable); + BOOST_TEST(ec == success); + BOOST_TEST(!eps.empty()); + BOOST_TEST(ap.host == "127.0.0.1"); + BOOST_TEST(ap.port == "1000"); + BOOST_TEST(ap.path.empty()); + + std::tie(ec, eps, ap) = co_await ep.async_next_endpoint(use_nothrow_awaitable); + BOOST_TEST(ec == asio::error::try_again); + }, [&finished](auto eptr) { finished = !eptr; }); + + ioc.run_for(1s); + BOOST_TEST(finished); +} + +BOOST_FIXTURE_TEST_CASE(multiple_hosts, shared_test_data) { + bool finished = false; + // "example.invalid" will not be resolved + ep.brokers("127.0.0.1:1001,127.0.0.1/path1, example.invalid, localhost:1002/path1/path-2/path.3_", 1000); + + asio::co_spawn(ioc, [&]() -> asio::awaitable { + for (size_t i = 0; i < 3; ++i) { + auto [ec, eps, ap] = co_await ep.async_next_endpoint(use_nothrow_awaitable); + BOOST_TEST(ec == success); + BOOST_TEST(!eps.empty()); + BOOST_TEST(ap.host == "127.0.0.1"); + BOOST_TEST(ap.port == "1001"); + BOOST_TEST(ap.path.empty()); + + std::tie(ec, eps, ap) = co_await ep.async_next_endpoint(use_nothrow_awaitable); + BOOST_TEST(ec == success); + BOOST_TEST(!eps.empty()); + BOOST_TEST(ap.host == "127.0.0.1"); + BOOST_TEST(ap.port == "1000"); + BOOST_TEST(ap.path == "/path1"); + + std::tie(ec, eps, ap) = co_await ep.async_next_endpoint(use_nothrow_awaitable); + BOOST_TEST(ec == success); + BOOST_TEST(!eps.empty()); + BOOST_TEST(ap.host == "localhost"); + BOOST_TEST(ap.port == "1002"); + BOOST_TEST(ap.path == "/path1/path-2/path.3_"); + + std::tie(ec, eps, ap) = co_await ep.async_next_endpoint(use_nothrow_awaitable); + BOOST_TEST(ec == asio::error::try_again); + } + }, [&finished](auto eptr) { finished = !eptr; }); + + ioc.run_for(1s); + BOOST_TEST(finished); +} + +BOOST_FIXTURE_TEST_CASE(parse_failure, shared_test_data) { + bool finished = false; + ep.brokers("127.0.0.1,127.0.0.1::1883,127.0.0.1", 1000); + + asio::co_spawn(ioc, [&]() -> asio::awaitable { + auto [ec, eps, ap] = co_await ep.async_next_endpoint(use_nothrow_awaitable); + BOOST_TEST(ec == success); + BOOST_TEST(!eps.empty()); + BOOST_TEST(ap.host == "127.0.0.1"); + BOOST_TEST(ap.port == "1000"); + BOOST_TEST(ap.path.empty()); + + std::tie(ec, eps, ap) = co_await ep.async_next_endpoint(use_nothrow_awaitable); + BOOST_TEST(ec == asio::error::try_again); + }, [&finished](auto eptr) { finished = !eptr; }); + + ioc.run_for(1s); + BOOST_TEST(finished); +} + +BOOST_AUTO_TEST_SUITE_END(); + +#endif // BOOST_ASIO_HAS_CO_AWAIT