//----------------------------------------------------------------------------- // ___ __ _ _ // / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __ // / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ / // / ___/ (_| | | \__ \ __/ /__| | | | | < // \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ . // //----------------------------------------------------------------------------- // Author: Kurt Sassenrath // Module: Client // // Client implementation. Most of this should be agnostic to the targeted // platform (e.g. Linux vs Windows). // // Copyright (c) 2023 Kurt Sassenrath. // // License TBD. //----------------------------------------------------------------------------- #include "client.h" #include "parselink/logging.h" #include "parselink/utility/ctstring.h" #include "parselink/msgpack/core/packer.h" #include #include #include #include #include #include #include using namespace parselink; using namespace parselink::client; namespace net = boost::asio; using net::awaitable; using net::co_spawn; using net::detached; using net::use_awaitable; using udp = net::ip::udp; namespace { logging::logger logger{"client"}; constexpr auto no_ex_coro = net::as_tuple(use_awaitable); class simple_client { public: simple_client(config const& cfg) noexcept; std::error_code run() noexcept; private: awaitable connect_to_server() noexcept; awaitable connect_to_websocket() noexcept; net::io_context io_context_; std::string server_address_; std::uint16_t server_port_; std::string websocket_address_; std::uint16_t websocket_port_; }; static_assert(interface); simple_client::simple_client(config const& cfg) noexcept : io_context_{1} { server_address_ = cfg.server_address.empty() ? "localhost" : cfg.server_address; server_port_ = !cfg.server_port ? 9001 : cfg.server_port; websocket_address_ = cfg.websocket_address.empty() ? "localhost" : cfg.websocket_address; websocket_port_ = !cfg.websocket_port ? 10501 : cfg.websocket_port; logger.debug("Creating parselink client. Configured server: {}:{}, " "websocket: {}:{}.", server_address_, server_port_, websocket_address_, websocket_port_); }; awaitable simple_client::connect_to_server() noexcept { logger.debug("Connecting to parselink server..."); udp::resolver resolver(io_context_); auto [ec, results] = co_await resolver.async_resolve( {server_address_, std::to_string(server_port_)}, no_ex_coro); if (ec) { logger.error("Unable to resolve {}:{}: {}", server_address_, server_port_, ec); co_return; } else if (results.empty()) { logger.error("Unable to resolve {}:{} to an endpoint.", server_address_, server_port_); co_return; } for (auto const& r : results) { udp::socket socket(io_context_); socket.open(r.endpoint().protocol()); logger.debug("Connecting to {}", r.endpoint()); std::array buff; msgpack::packer packer(buff); packer.pack("error"); packer.pack(msgpack::map_desc{2}); auto span = packer.subspan(); auto [ec, bw] = co_await socket.async_send_to( net::buffer(span.data(), span.size()), r.endpoint(), no_ex_coro); if (ec) { logger.error("connection to {} failed: {}", results.begin()->endpoint(), ec); continue; } } logger.error("Unable to connect to any resolved endpoints."); co_return; } awaitable simple_client::connect_to_websocket() noexcept { logger.debug("Connecting to websocket server..."); co_return; } std::error_code simple_client::run() noexcept { logger.debug("Starting client."); net::signal_set signals(io_context_, SIGINT, SIGTERM); signals.async_wait([&](auto sig, auto g) { logger.info("Received signal: {} {}. Shutting down.", sig, g); }); co_spawn(io_context_, connect_to_websocket(), detached); co_spawn(io_context_, connect_to_server(), detached); io_context_.run(); return std::make_error_code(std::errc::no_link); }; } // anonymous namespace std::error_code parselink::client::create_and_run(config const& cfg) noexcept { simple_client client(cfg); return client.run(); }