packet comm refactor and fixes

This commit is contained in:
2025-09-26 08:04:18 -05:00
parent 3a3e73f776
commit ce13149776
4 changed files with 94 additions and 61 deletions

View File

@@ -25,6 +25,7 @@
#include "utils/common.hpp" #include "utils/common.hpp"
namespace repertory::comm { namespace repertory::comm {
inline static constexpr std::uint32_t max_packet_bytes{32U * 1024U * 1024U};
inline constexpr const std::uint8_t max_read_attempts{5U}; inline constexpr const std::uint8_t max_read_attempts{5U};
inline constexpr const std::uint16_t packet_nonce_size{256U}; inline constexpr const std::uint16_t packet_nonce_size{256U};
inline constexpr const std::uint16_t server_handshake_timeout_ms{3000U}; inline constexpr const std::uint16_t server_handshake_timeout_ms{3000U};

View File

@@ -1,17 +1,13 @@
/* /*
Copyright <2018-2025> <scott.e.graves@protonmail.com> Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is copies of the Software, and to permit persons to do so, subject to the
furnished to do so, subject to the following conditions: following conditions: The above copyright notice and this permission notice
shall be included in all copies or substantial portions of the Software. THE
The above copyright notice and this permission notice shall be included in all SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
@@ -24,6 +20,7 @@
#include "comm/packet/packet.hpp" #include "comm/packet/packet.hpp"
#include "types/remote.hpp" #include "types/remote.hpp"
#include "utils/atomic.hpp"
using boost::asio::ip::tcp; using boost::asio::ip::tcp;
@@ -47,24 +44,26 @@ public:
auto operator=(packet_client &&) -> packet_client & = delete; auto operator=(packet_client &&) -> packet_client & = delete;
private: private:
mutable boost::asio::io_context io_context_;
remote::remote_config cfg_; remote::remote_config cfg_;
std::string unique_id_; mutable boost::asio::io_context io_context_;
atomic<std::string> unique_id_;
boost::asio::executor_work_guard<boost::asio::io_context::executor_type>
work_guard_;
private: private:
bool allow_connections_{true}; std::atomic<bool> allow_connections_{true};
boost::asio::ip::basic_resolver<boost::asio::ip::tcp>::results_type atomic<boost::asio::ip::basic_resolver<boost::asio::ip::tcp>::results_type>
resolve_results_; resolve_results_;
std::mutex clients_mutex_; std::mutex clients_mutex_;
std::vector<std::shared_ptr<client>> clients_; std::vector<std::shared_ptr<client>> clients_;
std::vector<std::thread> service_threads_; std::vector<std::thread> service_threads_;
private: private:
static void close(client &cli); static void close(client &cli) noexcept;
void close_all(); void close_all();
void connect(client &cli); [[nodiscard]] auto connect(client &cli) -> bool;
[[nodiscard]] auto get_client() -> std::shared_ptr<client>; [[nodiscard]] auto get_client() -> std::shared_ptr<client>;

View File

@@ -1,17 +1,13 @@
/* /*
Copyright <2018-2025> <scott.e.graves@protonmail.com> Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is copies of the Software, and to permit persons to do so, subject to the
furnished to do so, subject to the following conditions: following conditions: The above copyright notice and this permission notice
shall be included in all copies or substantial portions of the Software. THE
The above copyright notice and this permission notice shall be included in all SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
@@ -26,45 +22,49 @@
#include "types/repertory.hpp" #include "types/repertory.hpp"
#include "utils/collection.hpp" #include "utils/collection.hpp"
#include "utils/common.hpp" #include "utils/common.hpp"
#include "utils/config.hpp"
#include "utils/error_utils.hpp" #include "utils/error_utils.hpp"
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "version.hpp" #include "version.hpp"
#include <utils/config.hpp>
using namespace repertory::comm; using namespace repertory::comm;
namespace repertory { namespace repertory {
packet_client::packet_client(remote::remote_config cfg) packet_client::packet_client(remote::remote_config cfg)
: cfg_(std::move(cfg)), unique_id_(utils::create_uuid_string()) { : cfg_(std::move(cfg)),
io_context_(),
unique_id_(utils::create_uuid_string()),
work_guard_(boost::asio::make_work_guard(io_context_)) {
for (std::uint8_t idx = 0U; idx < cfg.max_connections; ++idx) { for (std::uint8_t idx = 0U; idx < cfg.max_connections; ++idx) {
service_threads_.emplace_back([this]() { io_context_.run(); }); service_threads_.emplace_back([this]() { io_context_.run(); });
} }
} }
packet_client::~packet_client() { packet_client::~packet_client() {
allow_connections_ = false; REPERTORY_USES_FUNCTION_NAME();
close_all();
for (std::size_t idx = 0U; idx < service_threads_.size(); ++idx) { allow_connections_ = false;
io_context_.stop();
try {
close_all();
} catch (...) {
} }
work_guard_.reset();
io_context_.stop();
for (auto &thread : service_threads_) { for (auto &thread : service_threads_) {
if (thread.joinable()) {
thread.join(); thread.join();
} }
}
} }
void packet_client::close(client &cli) { void packet_client::close(client &cli) noexcept {
REPERTORY_USES_FUNCTION_NAME(); boost::system::error_code err1;
cli.socket.shutdown(boost::asio::socket_base::shutdown_both, err1);
try { boost::system::error_code err2;
cli.socket.shutdown(boost::asio::socket_base::shutdown_both); cli.socket.close(err2);
boost::system::error_code err;
[[maybe_unused]] auto res = cli.socket.close(err);
} catch (const std::exception &e) {
utils::error::raise_error(function_name, e, "connection handshake failed");
}
} }
void packet_client::close_all() { void packet_client::close_all() {
@@ -72,9 +72,7 @@ void packet_client::close_all() {
for (auto &cli : clients_) { for (auto &cli : clients_) {
close(*cli); close(*cli);
} }
clients_.clear(); clients_.clear();
io_context_.restart();
resolve_results_ = {}; resolve_results_ = {};
unique_id_ = utils::create_uuid_string(); unique_id_ = utils::create_uuid_string();
} }
@@ -119,11 +117,13 @@ auto packet_client::check_version(std::uint32_t client_version,
return api_error::error; return api_error::error;
} }
void packet_client::connect(client &cli) { auto packet_client::connect(client &cli) -> bool {
try { try {
resolve(); resolve();
connect_with_deadline(io_context_, cli.socket, resolve_results_, boost::asio::ip::basic_resolver<boost::asio::ip::tcp>::results_type cached =
resolve_results_;
connect_with_deadline(io_context_, cli.socket, cached,
std::chrono::milliseconds(cfg_.conn_timeout_ms)); std::chrono::milliseconds(cfg_.conn_timeout_ms));
cli.socket.set_option(boost::asio::ip::tcp::no_delay(true)); cli.socket.set_option(boost::asio::ip::tcp::no_delay(true));
@@ -133,7 +133,7 @@ void packet_client::connect(client &cli) {
std::uint32_t min_version{}; std::uint32_t min_version{};
if (not handshake(cli, io_context_, min_version)) { if (not handshake(cli, io_context_, min_version)) {
close(cli); close(cli);
return; return false;
} }
packet response; packet response;
@@ -141,6 +141,7 @@ void packet_client::connect(client &cli) {
if (res != 0) { if (res != 0) {
throw std::runtime_error(fmt::format("read packet failed|err|{}", res)); throw std::runtime_error(fmt::format("read packet failed|err|{}", res));
} }
return true;
} catch (...) { } catch (...) {
close(cli); close(cli);
resolve_results_ = {}; resolve_results_ = {};
@@ -151,17 +152,19 @@ void packet_client::connect(client &cli) {
auto packet_client::get_client() -> std::shared_ptr<packet_client::client> { auto packet_client::get_client() -> std::shared_ptr<packet_client::client> {
REPERTORY_USES_FUNCTION_NAME(); REPERTORY_USES_FUNCTION_NAME();
unique_mutex_lock clients_lock(clients_mutex_);
if (not allow_connections_) { if (not allow_connections_) {
return nullptr; return nullptr;
} }
try { try {
unique_mutex_lock clients_lock(clients_mutex_);
if (clients_.empty()) { if (clients_.empty()) {
clients_lock.unlock(); clients_lock.unlock();
auto cli = std::make_shared<client>(io_context_); auto cli = std::make_shared<client>(io_context_);
connect(*cli); if (!connect(*cli)) {
return nullptr;
}
return cli; return cli;
} }
@@ -169,7 +172,7 @@ auto packet_client::get_client() -> std::shared_ptr<packet_client::client> {
utils::collection::remove_element(clients_, cli); utils::collection::remove_element(clients_, cli);
return cli; return cli;
} catch (const std::exception &e) { } catch (const std::exception &e) {
utils::error::raise_error(function_name, e, "connection handshake failed"); utils::error::raise_error(function_name, e, "get_client failed");
} }
return nullptr; return nullptr;
@@ -190,7 +193,8 @@ auto packet_client::handshake(client &cli, boost::asio::io_context &ctx,
data_buffer buffer; data_buffer buffer;
{ {
packet tmp; packet tmp;
tmp.encode(utils::get_version_number(REPERTORY_MIN_REMOTE_VERSION)); tmp.encode(utils::get_version_number(project_get_version()));
tmp.encode(~utils::get_version_number(project_get_version()));
tmp.encode(utils::generate_random_string(packet_nonce_size)); tmp.encode(utils::generate_random_string(packet_nonce_size));
tmp.to_buffer(buffer); tmp.to_buffer(buffer);
} }
@@ -198,11 +202,22 @@ auto packet_client::handshake(client &cli, boost::asio::io_context &ctx,
read_exact_with_deadline(ctx, cli.socket, boost::asio::buffer(buffer), read_exact_with_deadline(ctx, cli.socket, boost::asio::buffer(buffer),
std::chrono::milliseconds(cfg_.recv_timeout_ms)); std::chrono::milliseconds(cfg_.recv_timeout_ms));
packet response(buffer); packet response(buffer);
auto res = response.decode(min_version); auto res = response.decode(min_version);
if (res != 0) { if (res != 0) {
throw std::runtime_error("failed to decode server version"); throw std::runtime_error("failed to decode server version");
} }
std::uint32_t min_version_check{};
res = response.decode(min_version_check);
if (res != 0) {
throw std::runtime_error("failed to decode server version");
}
if (min_version_check != ~min_version) {
throw std::runtime_error("failed to decode server version");
}
response.encrypt(cfg_.encryption_token, false); response.encrypt(cfg_.encryption_token, false);
response.to_buffer(buffer); response.to_buffer(buffer);
@@ -210,7 +225,7 @@ auto packet_client::handshake(client &cli, boost::asio::io_context &ctx,
std::chrono::milliseconds(cfg_.send_timeout_ms)); std::chrono::milliseconds(cfg_.send_timeout_ms));
return true; return true;
} catch (const std::exception &e) { } catch (const std::exception &e) {
utils::error::raise_error(function_name, e, "handlshake failed"); utils::error::raise_error(function_name, e, "handshake failed");
} }
return false; return false;
@@ -241,6 +256,11 @@ auto packet_client::read_packet(client &cli, boost::asio::io_context &ctx,
std::uint32_t to_read{}; std::uint32_t to_read{};
std::memcpy(&to_read, buffer.data(), sizeof(to_read)); std::memcpy(&to_read, buffer.data(), sizeof(to_read));
boost::endian::big_to_native_inplace(to_read); boost::endian::big_to_native_inplace(to_read);
if (to_read == 0U || to_read > comm::max_packet_bytes) {
return utils::from_api_error(api_error::comm_error);
}
buffer.resize(to_read); buffer.resize(to_read);
read_exact_with_deadline(ctx, cli.socket, boost::asio::buffer(buffer), read_exact_with_deadline(ctx, cli.socket, boost::asio::buffer(buffer),
@@ -255,7 +275,9 @@ auto packet_client::read_packet(client &cli, boost::asio::io_context &ctx,
} }
void packet_client::resolve() { void packet_client::resolve() {
if (not resolve_results_.empty()) { boost::asio::ip::basic_resolver<boost::asio::ip::tcp>::results_type cached =
resolve_results_;
if (not cached.empty()) {
return; return;
} }
@@ -282,12 +304,14 @@ auto packet_client::send(std::string_view method, packet &request,
REPERTORY_USES_FUNCTION_NAME(); REPERTORY_USES_FUNCTION_NAME();
auto success = false; auto success = false;
packet::error_type ret = utils::from_api_error(api_error::error); auto ret = utils::from_api_error(api_error::error);
request.encode_top(method);
request.encode_top(utils::get_thread_id()); auto base_request = request;
request.encode_top(unique_id_); base_request.encode_top(method);
request.encode_top(PACKET_SERVICE_FLAGS); base_request.encode_top(utils::get_thread_id());
request.encode_top(std::string{project_get_version()}); base_request.encode_top(unique_id_.load());
base_request.encode_top(PACKET_SERVICE_FLAGS);
base_request.encode_top(std::string{project_get_version()});
for (std::uint8_t retry = 1U; for (std::uint8_t retry = 1U;
allow_connections_ && not success && (retry <= max_read_attempts); allow_connections_ && not success && (retry <= max_read_attempts);
@@ -295,12 +319,16 @@ auto packet_client::send(std::string_view method, packet &request,
auto current_client = get_client(); auto current_client = get_client();
if (current_client) { if (current_client) {
try { try {
request.encode_top(current_client->nonce); auto current_request = base_request;
request.encrypt(cfg_.encryption_token); current_request.encode_top(current_client->nonce);
request = current_request;
current_request.encrypt(cfg_.encryption_token);
write_all_with_deadline( write_all_with_deadline(
io_context_, current_client->socket, io_context_, current_client->socket,
boost::asio::buffer(&request[0], request.get_size()), boost::asio::buffer(&current_request[0],
current_request.get_size()),
std::chrono::milliseconds(cfg_.send_timeout_ms)); std::chrono::milliseconds(cfg_.send_timeout_ms));
ret = read_packet(*current_client, response); ret = read_packet(*current_client, response);
@@ -318,7 +346,6 @@ auto packet_client::send(std::string_view method, packet &request,
} }
} catch (const std::exception &e) { } catch (const std::exception &e) {
utils::error::raise_error(function_name, e, "send failed"); utils::error::raise_error(function_name, e, "send failed");
close(*current_client); close(*current_client);
if (allow_connections_ && (retry < max_read_attempts)) { if (allow_connections_ && (retry < max_read_attempts)) {
std::this_thread::sleep_for(1s); std::this_thread::sleep_for(1s);

View File

@@ -92,6 +92,7 @@ auto packet_server::handshake(std::shared_ptr<connection> conn) const -> bool {
data_buffer buffer; data_buffer buffer;
packet request; packet request;
request.encode(utils::get_version_number(REPERTORY_MIN_REMOTE_VERSION)); request.encode(utils::get_version_number(REPERTORY_MIN_REMOTE_VERSION));
request.encode(~utils::get_version_number(REPERTORY_MIN_REMOTE_VERSION));
request.encode(conn->nonce); request.encode(conn->nonce);
request.to_buffer(buffer); request.to_buffer(buffer);
auto to_read{buffer.size() + utils::encryption::encryption_header_size}; auto to_read{buffer.size() + utils::encryption::encryption_header_size};
@@ -276,6 +277,11 @@ void packet_server::read_packet(std::shared_ptr<connection> conn,
} }
}; };
if (data_size > comm::max_packet_bytes) {
throw std::runtime_error(
fmt::format("packet too large|size|{}", data_size));
}
auto should_send_response = true; auto should_send_response = true;
auto response = std::make_shared<packet>(); auto response = std::make_shared<packet>();
conn->buffer.resize(data_size); conn->buffer.resize(data_size);