diff --git a/CMakeLists.txt b/CMakeLists.txt index abfb06e..a6b09b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -118,6 +118,13 @@ set(util_source_files src/util/exception_location_helpers.hpp + src/util/file_operations_helpers.hpp + src/util/file_operations_helpers.cpp + + src/util/hex_value_fwd.hpp + src/util/hex_value.hpp + src/util/hex_value.cpp + src/util/flag_set_fwd.hpp src/util/flag_set.hpp @@ -436,6 +443,10 @@ set(binsrv_source_files src/binsrv/basic_logger.hpp src/binsrv/basic_logger.cpp + src/binsrv/basic_keyring_fwd.hpp + src/binsrv/basic_keyring.hpp + src/binsrv/basic_keyring.cpp + src/binsrv/basic_storage_backend_fwd.hpp src/binsrv/basic_storage_backend.hpp src/binsrv/basic_storage_backend.cpp @@ -447,12 +458,22 @@ set(binsrv_source_files src/binsrv/cout_logger.hpp src/binsrv/cout_logger.cpp + src/binsrv/encryption_config_fwd.hpp + src/binsrv/encryption_config.hpp + src/binsrv/encryption_config.cpp + + src/binsrv/encryption_format_type_fwd.hpp + src/binsrv/encryption_format_type.hpp + src/binsrv/exception_handling_helpers.hpp src/binsrv/exception_handling_helpers.cpp src/binsrv/file_logger.hpp src/binsrv/file_logger.cpp + src/binsrv/file_keyring.hpp + src/binsrv/file_keyring.cpp + src/binsrv/filesystem_storage_backend.hpp src/binsrv/filesystem_storage_backend.cpp @@ -465,6 +486,16 @@ set(binsrv_source_files src/binsrv/logger_factory.hpp src/binsrv/logger_factory.cpp + src/binsrv/keyring_factory.hpp + src/binsrv/keyring_factory.cpp + + src/binsrv/keyring_record_collection_fwd.hpp + src/binsrv/keyring_record_collection.hpp + src/binsrv/keyring_record_collection.cpp + + src/binsrv/keyring_record_fwd.hpp + src/binsrv/keyring_record.hpp + src/binsrv/main_config_fwd.hpp src/binsrv/main_config.hpp src/binsrv/main_config.cpp diff --git a/README.md b/README.md index 443be41..a5855be 100644 --- a/README.md +++ b/README.md @@ -495,7 +495,11 @@ The Percona Binary Log Server configuration file has the following format. "uri": "https://key_id:secret@192.168.0.100:9000/binsrv-bucket/vault", "fs_buffer_directory": "/tmp/binsrv", "checkpoint_size": "128M", - "checkpoint_interval": "30s" + "checkpoint_interval": "30s", + "encryption": { + "format": "generic", + "keyring_uri": "file:///var/lib/pbs/keyring/keyring_data.json" + } } } ``` @@ -612,6 +616,36 @@ For example: ##### Checkpointing on S3 Please note that S3 API does not provide a way to append a portion of data to an existing object. Currently, in our S3 storage backend "append" operations are implemented as complete object overwrites meaning data re-uploads. Practically, if your typical binlog file size is '1G' and you set `` to '256M', you will upload '256M + 512M + 768M + 1024M = 2560M' (about 2.5 times more then your binlog file size in this example). So, keep balance between the value of this parameter and your typical binlog size. Similar concerns can be raised regarding enabling ``. +#### \ section +If this section is present, then all the binlog data files will be encrypted before written to the storage. +- `` - specifies the encryption format (currently only `generic` is supported). +- `` - specifies location of the keyring JSON data file (currently only 'file://' scheme is supported meaning that the file should be taken from the local file sytem from the path specified in this URI, e.g. `file:///var/lib/pbs/keyring/keyring_data.json`). + +##### Keyring file format +```json +{ + "version": 1, + "keys": [ + { + "id": "alpha", + "algorithm": "AES-128-ECB", + "data_hex": "00112233445566778899AABBCCDDEEFF" + }, + { + "id": "beta", + "algorithm": "AES-256-GCM", + "data_hex": "00112233445566778899AABBCCDDEEFFFFEEDDCCBBAA998877665544332211" + } + ] +} +``` +Keyring JSON file should represent a top-level JSON object with the following keys. +- `version` - currently should always be equal to `1`. +- `keys` - should be an array of objects tith the following keys + - `id` - a unique string identifier of the key in the keyring. + - `algorithm` - the name of the symmetric cypher which should be used with this key (e.g `AES-256-GCM`). + - `data_hex` - key bytes in hex format (typically `16`, `24`, or `32` bytes, meaning `32`, `48`, or `64` characters) + ### Resuming previous operation Running the utility for the second time (in any mode) results in resuming streaming from the position at which the previous run finished. diff --git a/keyring_data.json b/keyring_data.json new file mode 100644 index 0000000..4340b49 --- /dev/null +++ b/keyring_data.json @@ -0,0 +1,15 @@ +{ + "version": 1, + "keys": [ + { + "id": "alpha", + "algorithm": "AES-128-ECB", + "data_hex": "00112233445566778899AABBCCDDEEFF" + }, + { + "id": "beta", + "algorithm": "AES-256-GCM", + "data_hex": "00112233445566778899AABBCCDDEEFFFFEEDDCCBBAA998877665544332211" + } + ] +} diff --git a/main_config.json b/main_config.json index 0d88637..dce9160 100644 --- a/main_config.json +++ b/main_config.json @@ -41,6 +41,10 @@ "uri": "file:///home/user/vault", "fs_buffer_directory": "/tmp/binsrv", "checkpoint_size": "2M", - "checkpoint_interval": "30s" + "checkpoint_interval": "30s", + "encryption": { + "format": "generic", + "keyring_uri": "file:///home/user/keyring/keyring/keyring_data.json" + } } } diff --git a/src/app.cpp b/src/app.cpp index 9d30f06..54cbc08 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -39,8 +39,12 @@ #include "app_version.hpp" +#include "binsrv/basic_keyring.hpp" #include "binsrv/basic_logger.hpp" +// needed for encryption_format_type's operator << +#include "binsrv/encryption_format_type.hpp" // IWYU pragma: keep #include "binsrv/exception_handling_helpers.hpp" +#include "binsrv/keyring_factory.hpp" #include "binsrv/log_severity.hpp" #include "binsrv/logger_factory.hpp" #include "binsrv/main_config.hpp" @@ -248,6 +252,15 @@ void log_replication_config_info( } } +void log_encryption_config_info( + binsrv::basic_logger &logger, + const binsrv::encryption_config &encryption_config) { + log_config_param<"format">(logger, encryption_config, + "binlog storage encryption format"); + log_config_param<"keyring_uri">(logger, encryption_config, + "binlog storage encryption keyring URI"); +} + void log_storage_config_info(binsrv::basic_logger &logger, const binsrv::storage_config &storage_config) { @@ -263,6 +276,10 @@ void log_storage_config_info(binsrv::basic_logger &logger, logger, storage_config, "binlog storage backend checkpointing size"); log_config_param<"checkpoint_interval">( logger, storage_config, "binlog storage backend checkpointing interval"); + const auto &optional_encryption_config{storage_config.get<"encryption">()}; + if (optional_encryption_config.has_value()) { + log_encryption_config_info(logger, *optional_encryption_config); + } } void log_storage_info(binsrv::basic_logger &logger, @@ -1310,6 +1327,15 @@ int main(int argc, char *argv[]) { const auto verify_checksum{replication_config.get<"verify_checksum">()}; const auto replication_mode{replication_config.get<"mode">()}; const auto optional_rewrite_config{replication_config.get<"rewrite">()}; + const auto &optional_encryption_config{storage_config.get<"encryption">()}; + + binsrv::basic_keyring_ptr keyring; + if (optional_encryption_config.has_value()) { + keyring = binsrv::keyring_factory::create( + optional_encryption_config->get<"keyring_uri">()); + logger->log(binsrv::log_severity::info, + "initialized keyring: " + keyring->get_description()); + } binsrv::storage storage{storage_config, binsrv::storage_construction_mode_type::streaming, diff --git a/src/binsrv/basic_keyring.cpp b/src/binsrv/basic_keyring.cpp new file mode 100644 index 0000000..20077b2 --- /dev/null +++ b/src/binsrv/basic_keyring.cpp @@ -0,0 +1,35 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#include "binsrv/basic_keyring.hpp" + +#include +#include + +#include "binsrv/keyring_record_fwd.hpp" + +namespace binsrv { + +basic_keyring::~basic_keyring() = default; + +[[nodiscard]] const keyring_record & +basic_keyring::get_key(std::string_view key_id) { + return do_get_key(key_id); +} +[[nodiscard]] std::string basic_keyring::get_description() const { + return do_get_description(); +} + +} // namespace binsrv diff --git a/src/binsrv/basic_keyring.hpp b/src/binsrv/basic_keyring.hpp new file mode 100644 index 0000000..6ec3b50 --- /dev/null +++ b/src/binsrv/basic_keyring.hpp @@ -0,0 +1,48 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef BINSRV_BASIC_KEYRING_HPP +#define BINSRV_BASIC_KEYRING_HPP + +#include "binsrv/basic_keyring_fwd.hpp" // IWYU pragma: export + +#include "binsrv/keyring_record_fwd.hpp" + +#include + +namespace binsrv { + +class [[nodiscard]] basic_keyring { +public: + basic_keyring() = default; + basic_keyring(const basic_keyring &) = delete; + basic_keyring(basic_keyring &&) noexcept = delete; + basic_keyring &operator=(const basic_keyring &) = delete; + basic_keyring &operator=(basic_keyring &&) = delete; + + virtual ~basic_keyring(); + + [[nodiscard]] const keyring_record &get_key(std::string_view key_id); + [[nodiscard]] std::string get_description() const; + +private: + [[nodiscard]] virtual const keyring_record & + do_get_key(std::string_view key_id) = 0; + [[nodiscard]] virtual std::string do_get_description() const = 0; +}; + +} // namespace binsrv + +#endif // BINSRV_BASIC_KEYRING_HPP diff --git a/src/binsrv/basic_keyring_fwd.hpp b/src/binsrv/basic_keyring_fwd.hpp new file mode 100644 index 0000000..88bc2dc --- /dev/null +++ b/src/binsrv/basic_keyring_fwd.hpp @@ -0,0 +1,29 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef BINSRV_BASIC_KEYRING_FWD_HPP +#define BINSRV_BASIC_KEYRING_FWD_HPP + +#include + +namespace binsrv { + +class basic_keyring; + +using basic_keyring_ptr = std::shared_ptr; + +} // namespace binsrv + +#endif // BINSRV_BASIC_KEYRING_FWD_HPP diff --git a/src/binsrv/basic_logger.cpp b/src/binsrv/basic_logger.cpp index 1c426d6..1f13d49 100644 --- a/src/binsrv/basic_logger.cpp +++ b/src/binsrv/basic_logger.cpp @@ -30,6 +30,8 @@ namespace binsrv { basic_logger::basic_logger(log_severity min_level) noexcept : min_level_{min_level} {} +basic_logger::~basic_logger() = default; + void basic_logger::log(log_severity level, std::string_view message) { if (level >= min_level_) { // the length of the longest log severity label diff --git a/src/binsrv/basic_logger.hpp b/src/binsrv/basic_logger.hpp index 109d166..4853a15 100644 --- a/src/binsrv/basic_logger.hpp +++ b/src/binsrv/basic_logger.hpp @@ -31,7 +31,7 @@ class [[nodiscard]] basic_logger { basic_logger(basic_logger &&) = delete; basic_logger &operator=(basic_logger &&) = delete; - virtual ~basic_logger() = default; + virtual ~basic_logger(); [[nodiscard]] log_severity get_min_level() const noexcept { return min_level_; diff --git a/src/binsrv/encryption_config.cpp b/src/binsrv/encryption_config.cpp new file mode 100644 index 0000000..daf0596 --- /dev/null +++ b/src/binsrv/encryption_config.cpp @@ -0,0 +1,33 @@ +// Copyright (c) 2023-2026 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#include "binsrv/encryption_config.hpp" + +#include + +#include "binsrv/encryption_format_type.hpp" + +#include "util/exception_location_helpers.hpp" + +namespace binsrv { + +void encryption_config::validate() const { + if (get<"format">() != encryption_format_type::generic) { + util::exception_location().raise( + "error validating storage encryption config: unsupported format"); + } +} + +} // namespace binsrv diff --git a/src/binsrv/encryption_config.hpp b/src/binsrv/encryption_config.hpp new file mode 100644 index 0000000..4b54375 --- /dev/null +++ b/src/binsrv/encryption_config.hpp @@ -0,0 +1,42 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef BINSRV_ENCRYPTION_CONFIG_HPP +#define BINSRV_ENCRYPTION_CONFIG_HPP + +#include "binsrv/encryption_config_fwd.hpp" // IWYU pragma: export + +#include + +#include "binsrv/encryption_format_type_fwd.hpp" + +#include "util/nv_tuple.hpp" + +namespace binsrv { + +struct [[nodiscard]] encryption_config + : util::nv_tuple< + // clang-format off + util::nv<"format", encryption_format_type>, + util::nv<"keyring_uri", std::string> + // clang-format on + > { + + void validate() const; +}; + +} // namespace binsrv + +#endif // BINSRV_ENCRYPTION_CONFIG_HPP diff --git a/src/binsrv/encryption_config_fwd.hpp b/src/binsrv/encryption_config_fwd.hpp new file mode 100644 index 0000000..890770b --- /dev/null +++ b/src/binsrv/encryption_config_fwd.hpp @@ -0,0 +1,28 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef BINSRV_ENCRYPTION_CONFIG_FWD_HPP +#define BINSRV_ENCRYPTION_CONFIG_FWD_HPP + +#include + +namespace binsrv { + +struct encryption_config; +using optional_encryption_config = std::optional; + +} // namespace binsrv + +#endif // BINSRV_ENCRYPTION_CONFIG_FWD_HPP diff --git a/src/binsrv/encryption_format_type.hpp b/src/binsrv/encryption_format_type.hpp new file mode 100644 index 0000000..4aaa561 --- /dev/null +++ b/src/binsrv/encryption_format_type.hpp @@ -0,0 +1,95 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef BINSRV_ENCRYPTION_FORMAT_TYPE_HPP +#define BINSRV_ENCRYPTION_FORMAT_TYPE_HPP + +#include "binsrv/encryption_format_type_fwd.hpp" // IWYU pragma: export + +#include +#include +#include +#include +#include +#include + +#include "util/conversion_helpers.hpp" + +namespace binsrv { + +// NOLINTBEGIN(cppcoreguidelines-macro-usage) +// clang-format off +#define BINSRV_ENCRYPTION_FORMAT_TYPE_X_SEQUENCE() \ + BINSRV_ENCRYPTION_FORMAT_TYPE_X_MACRO(generic) +// clang-format on + +#define BINSRV_ENCRYPTION_FORMAT_TYPE_X_MACRO(X) X +enum class encryption_format_type : std::uint8_t { + BINSRV_ENCRYPTION_FORMAT_TYPE_X_SEQUENCE(), + delimiter +}; +#undef BINSRV_ENCRYPTION_FORMAT_TYPE_X_MACRO + +inline std::string_view +to_string_view(encryption_format_type encryption_format) noexcept { + using namespace std::string_view_literals; +#define BINSRV_ENCRYPTION_FORMAT_TYPE_X_MACRO(X) #X##sv + static constexpr std::array labels{BINSRV_ENCRYPTION_FORMAT_TYPE_X_SEQUENCE(), + ""sv}; +#undef BINSRV_ENCRYPTION_FORMAT_TYPE_X_MACRO + const auto index{util::enum_to_index( + std::min(encryption_format_type::delimiter, encryption_format))}; + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) + return labels[index]; +} +#undef BINSRV_ENCRYPTION_FORMAT_TYPE_X_SEQUENCE +// NOLINTEND(cppcoreguidelines-macro-usage) + +template + requires std::same_as +std::basic_ostream & +operator<<(std::basic_ostream &output, + encryption_format_type encryption_format) { + return output << to_string_view(encryption_format); +} + +template + requires std::same_as +std::basic_istream & +operator>>(std::basic_istream &input, + encryption_format_type &encryption_format) { + std::string encryption_format_str; + input >> encryption_format_str; + if (!input) { + return input; + } + std::size_t index{0U}; + const auto max_index = util::enum_to_index(encryption_format_type::delimiter); + while (index < max_index && + to_string_view(util::index_to_enum(index)) != + encryption_format_str) { + ++index; + } + if (index < max_index) { + encryption_format = util::index_to_enum(index); + } else { + input.setstate(std::ios_base::failbit); + } + return input; +} + +} // namespace binsrv + +#endif // BINSRV_ENCRYPTION_FORMAT_TYPE_HPP diff --git a/src/binsrv/encryption_format_type_fwd.hpp b/src/binsrv/encryption_format_type_fwd.hpp new file mode 100644 index 0000000..70753b6 --- /dev/null +++ b/src/binsrv/encryption_format_type_fwd.hpp @@ -0,0 +1,47 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef BINSRV_ENCRYPTION_FORMAT_TYPE_FWD_HPP +#define BINSRV_ENCRYPTION_FORMAT_TYPE_FWD_HPP + +#include +#include +#include + +#include "util/nv_tuple_json_support.hpp" + +namespace binsrv { + +enum class encryption_format_type : std::uint8_t; + +template + requires std::same_as +std::basic_ostream & +operator<<(std::basic_ostream &output, + encryption_format_type encryption_format); + +template + requires std::same_as +std::basic_istream & +operator>>(std::basic_istream &input, + encryption_format_type &encryption_format); + +} // namespace binsrv + +template <> +struct util::is_string_convertible + : std::true_type {}; + +#endif // BINSRV_ENCRYPTION_FORMAT_TYPE_FWD_HPP diff --git a/src/binsrv/events/event_fwd.hpp b/src/binsrv/events/event_fwd.hpp index fa0c3d1..b402184 100644 --- a/src/binsrv/events/event_fwd.hpp +++ b/src/binsrv/events/event_fwd.hpp @@ -27,6 +27,7 @@ class event; std::ostream &operator<<(std::ostream &output, const event &obj); inline constexpr std::size_t expected_max_event_length{256U}; +// TODO: in c++26 change to std::inplace_vector using event_storage = boost::container::small_vector; diff --git a/src/binsrv/file_keyring.cpp b/src/binsrv/file_keyring.cpp new file mode 100644 index 0000000..92bb846 --- /dev/null +++ b/src/binsrv/file_keyring.cpp @@ -0,0 +1,94 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#include "binsrv/file_keyring.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "binsrv/keyring_record.hpp" +#include "binsrv/keyring_record_collection.hpp" + +#include "util/exception_location_helpers.hpp" + +namespace binsrv { + +file_keyring::file_keyring(std::string_view keyring_uri) : key_file_path_{} { + const auto uri_parse_result{boost::urls::parse_absolute_uri(keyring_uri)}; + if (!uri_parse_result) { + util::exception_location().raise( + "invalid keyring URI"); + } + const auto &uri{*uri_parse_result}; + + if (uri.scheme_id() != boost::urls::scheme::file || + uri.scheme() != uri_schema) { + util::exception_location().raise( + "URI of invalid scheme provided"); + } + if (uri.host_type() != boost::urls::host_type::name || !uri.host().empty()) { + util::exception_location().raise( + "file URI must not have host"); + } + if (uri.has_port()) { + util::exception_location().raise( + "file URI must not have port"); + } + if (uri.has_userinfo()) { + util::exception_location().raise( + "file URI must not have userinfo"); + } + if (uri.has_query()) { + util::exception_location().raise( + "file URI must not have query"); + } + if (uri.has_fragment()) { + util::exception_location().raise( + "file URI must not have fragment"); + } + + key_file_path_ = uri.path(); + + if (!std::filesystem::exists(key_file_path_)) { + util::exception_location().raise( + "key file path does not exist"); + } + if (!std::filesystem::is_regular_file(key_file_path_)) { + util::exception_location().raise( + "key file path is not a regular file"); + } + + keyring_records_ = + std::make_unique(key_file_path_.string()); +} + +file_keyring::~file_keyring() = default; + +[[nodiscard]] const keyring_record & +file_keyring::do_get_key(std::string_view key_id) { + return keyring_records_->get_key(key_id); +} +[[nodiscard]] std::string file_keyring::do_get_description() const { + return keyring_records_->get_description(); +} +} // namespace binsrv diff --git a/src/binsrv/file_keyring.hpp b/src/binsrv/file_keyring.hpp new file mode 100644 index 0000000..54efd92 --- /dev/null +++ b/src/binsrv/file_keyring.hpp @@ -0,0 +1,59 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef BINSRV_FILE_KEYRING_HPP +#define BINSRV_FILE_KEYRING_HPP + +#include +#include +#include + +#include "binsrv/basic_keyring.hpp" // IWYU pragma: export + +#include "binsrv/keyring_record_collection_fwd.hpp" +#include "binsrv/keyring_record_fwd.hpp" + +namespace binsrv { + +class [[nodiscard]] file_keyring final : public basic_keyring { +public: + static constexpr std::string_view uri_schema{"file"}; + + explicit file_keyring(std::string_view keyring_uri); + + file_keyring(const file_keyring &) = delete; + file_keyring(file_keyring &&) noexcept = delete; + file_keyring &operator=(const file_keyring &) = delete; + file_keyring &operator=(file_keyring &&) = delete; + + ~file_keyring() override; + + [[nodiscard]] const std::filesystem::path & + get_key_file_path() const noexcept { + return key_file_path_; + } + +private: + std::filesystem::path key_file_path_; + keyring_record_collection_ptr keyring_records_; + + [[nodiscard]] const keyring_record & + do_get_key(std::string_view key_id) override; + [[nodiscard]] std::string do_get_description() const override; +}; + +} // namespace binsrv + +#endif // BINSRV_FILE_KEYRING_HPP diff --git a/src/binsrv/filesystem_storage_backend.cpp b/src/binsrv/filesystem_storage_backend.cpp index 5a0ae64..ed06275 100644 --- a/src/binsrv/filesystem_storage_backend.cpp +++ b/src/binsrv/filesystem_storage_backend.cpp @@ -37,6 +37,7 @@ #include "util/byte_span.hpp" #include "util/exception_location_helpers.hpp" +#include "util/file_operations_helpers.hpp" #include "util/native_file_operations_helpers.hpp" namespace binsrv { @@ -130,39 +131,8 @@ filesystem_storage_backend::do_list_objects() { [[nodiscard]] std::string filesystem_storage_backend::do_get_object(std::string_view name) { const auto object_path{get_object_path(name)}; - - // opening in binary mode - std::ifstream object_ifs{}; - object_ifs.rdbuf()->pubsetbuf(nullptr, 0U); - object_ifs.open(object_path, std::ios_base::in | std::ios_base::binary); - if (!object_ifs.is_open()) { - util::exception_location().raise( - "cannot open underlying object file"); - } - if (!object_ifs.seekg(0, std::ios_base::end)) { - util::exception_location().raise( - "cannot seek underlying object file to the end"); - } - const std::streampos end_pos{object_ifs.tellg()}; - const auto end_offset{static_cast(end_pos)}; - if (!object_ifs.seekg(0, std::ios_base::beg)) { - util::exception_location().raise( - "cannot seek underlying object file to the beginning"); - } - - const auto file_size{static_cast(end_offset)}; - if (file_size > max_memory_object_size) { - util::exception_location().raise( - "underlying object file is too large to be loaded in memory"); - } - - std::string file_content(file_size, 'x'); - if (!object_ifs.read(std::data(file_content), - static_cast(file_size))) { - util::exception_location().raise( - "cannot read underlying object file content"); - } - return file_content; + return util::read_file_content(object_path, max_memory_object_size, + "underlying object file"); } void filesystem_storage_backend::do_put_object(std::string_view name, @@ -182,27 +152,8 @@ void filesystem_storage_backend::do_put_object(std::string_view name, auto tmp_object_path = object_path; tmp_object_path += tmp_object_suffix; - // opening in binary mode with truncating - std::ofstream object_ofs{}; - object_ofs.rdbuf()->pubsetbuf(nullptr, 0U); - object_ofs.open(tmp_object_path, std::ios_base::out | std::ios_base::binary | - std::ios_base::trunc); - if (!object_ofs.is_open()) { - util::exception_location().raise( - "cannot open underlying tmp object file for writing"); - } - const auto content_sv = util::as_string_view(content); - if (!object_ofs.write(std::data(content_sv), - static_cast(std::size(content_sv)))) { - util::exception_location().raise( - "cannot write data to underlying tmp object file"); - } - // explicit close so a failed flush surfaces before the rename - object_ofs.close(); - if (object_ofs.fail()) { - util::exception_location().raise( - "cannot close underlying tmp object file"); - } + util::write_file_content(tmp_object_path, util::as_string_view(content), + "underlying tmp object file"); // make the tmp file's content durable before the rename swaps it util::fsync(tmp_object_path); diff --git a/src/binsrv/gtids/common_types.hpp b/src/binsrv/gtids/common_types.hpp index 76ad2b1..17f44d2 100644 --- a/src/binsrv/gtids/common_types.hpp +++ b/src/binsrv/gtids/common_types.hpp @@ -42,6 +42,7 @@ inline constexpr gno_t max_gno{ std::numeric_limits>::max()}; inline constexpr std::size_t expected_max_gtid_set_length{96U}; +// TODO: in c++26 change to std::inplace_vector using gtid_set_storage = boost::container::small_vector; diff --git a/src/binsrv/keyring_factory.cpp b/src/binsrv/keyring_factory.cpp new file mode 100644 index 0000000..50b32f8 --- /dev/null +++ b/src/binsrv/keyring_factory.cpp @@ -0,0 +1,47 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#include "binsrv/keyring_factory.hpp" + +#include +#include +#include + +#include + +#include "binsrv/basic_keyring_fwd.hpp" +#include "binsrv/file_keyring.hpp" + +#include "util/exception_location_helpers.hpp" + +namespace binsrv { + +basic_keyring_ptr keyring_factory::create(std::string_view keyring_uri) { + const auto uri_parse_result{boost::urls::parse_absolute_uri(keyring_uri)}; + if (!uri_parse_result) { + util::exception_location().raise( + "invalid keyring URI"); + } + + const auto &uri{*uri_parse_result}; + if (uri.scheme() == file_keyring::uri_schema) { + return std::make_shared(keyring_uri); + } + + util::exception_location().raise( + "unsupported keyring URI scheme"); +} + +} // namespace binsrv diff --git a/src/binsrv/keyring_factory.hpp b/src/binsrv/keyring_factory.hpp new file mode 100644 index 0000000..b7b39e9 --- /dev/null +++ b/src/binsrv/keyring_factory.hpp @@ -0,0 +1,31 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef BINSRV_KEYRING_FACTORY_HPP +#define BINSRV_KEYRING_FACTORY_HPP + +#include + +#include "binsrv/basic_keyring_fwd.hpp" + +namespace binsrv { + +struct [[nodiscard]] keyring_factory { + [[nodiscard]] static basic_keyring_ptr create(std::string_view keyring_uri); +}; + +} // namespace binsrv + +#endif // BINSRV_KEYRING_FACTORY_HPP diff --git a/src/binsrv/keyring_record.hpp b/src/binsrv/keyring_record.hpp new file mode 100644 index 0000000..9d74302 --- /dev/null +++ b/src/binsrv/keyring_record.hpp @@ -0,0 +1,39 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef BINSRV_KEYRING_RECORD_HPP +#define BINSRV_KEYRING_RECORD_HPP + +#include "binsrv/keyring_record_fwd.hpp" // IWYU pragma: export + +#include + +#include "util/hex_value.hpp" +#include "util/nv_tuple.hpp" + +namespace binsrv { + +// clang-format off +struct [[nodiscard]] keyring_record + : util::nv_tuple< + util::nv<"id", std::string>, + util::nv<"algorithm", std::string>, + util::nv<"data_hex", util::hex_value> + > {}; +// clang-format on + +} // namespace binsrv + +#endif // BINSRV_KEYRING_RECORD_HPP diff --git a/src/binsrv/keyring_record_collection.cpp b/src/binsrv/keyring_record_collection.cpp new file mode 100644 index 0000000..63c680d --- /dev/null +++ b/src/binsrv/keyring_record_collection.cpp @@ -0,0 +1,80 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#include "binsrv/keyring_record_collection.hpp" + +#include +#include +#include +#include +#include + +#include + +#include "binsrv/keyring_record.hpp" + +#include "util/exception_location_helpers.hpp" +#include "util/file_operations_helpers.hpp" +#include "util/nv_tuple_from_json.hpp" + +namespace binsrv { + +keyring_record_collection::keyring_record_collection(std::string_view file_name) + : impl_{} { + static constexpr std::size_t max_file_size{1048576U}; + const auto data = util::read_file_content(file_name, max_file_size, + "keyring record collection file"); + auto json_value = boost::json::parse(data); + util::nv_tuple_from_json(json_value, impl_); + + validate(); +} + +[[nodiscard]] const keyring_record & +keyring_record_collection::get_key(std::string_view key_id) const { + const auto &keys{root().get<"keys">()}; + const auto key_it = + std::ranges::find_if(keys, [key_id](const keyring_record &key) { + return key.get<"id">() == key_id; + }); + if (key_it == std::end(keys)) { + util::exception_location().raise("key not found"); + } + return *key_it; +} + +void keyring_record_collection::validate() const { + if (root().get<"version">() != expected_keyring_record_collection_version) { + util::exception_location().raise( + "unsupported keyring record collection version"); + } +} + +[[nodiscard]] std::string keyring_record_collection::get_description() const { + std::string result{}; + const auto &keys{root().get<"keys">()}; + result += std::to_string(keys.size()); + result += " key(s):"; + for (const auto &key : keys) { + result += ' '; + result += key.get<"id">(); + result += '('; + result += key.get<"algorithm">(); + result += ')'; + } + return result; +} + +} // namespace binsrv diff --git a/src/binsrv/keyring_record_collection.hpp b/src/binsrv/keyring_record_collection.hpp new file mode 100644 index 0000000..1fd58d1 --- /dev/null +++ b/src/binsrv/keyring_record_collection.hpp @@ -0,0 +1,58 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef BINSRV_KEYRING_RECORD_COLLECTION_HPP +#define BINSRV_KEYRING_RECORD_COLLECTION_HPP + +#include "binsrv/keyring_record_collection_fwd.hpp" // IWYU pragma: export + +#include +#include +#include +#include + +#include "binsrv/keyring_record_fwd.hpp" + +#include "util/nv_tuple.hpp" + +namespace binsrv { + +class [[nodiscard]] keyring_record_collection { +private: + using key_collection = std::vector; + + using impl_type = util::nv_tuple< + // clang-format off + util::nv<"version", std::uint32_t>, + util::nv<"keys", key_collection> + // clang-format on + >; + +public: + explicit keyring_record_collection(std::string_view file_name); + + [[nodiscard]] const auto &root() const noexcept { return impl_; } + [[nodiscard]] const keyring_record &get_key(std::string_view key_id) const; + [[nodiscard]] std::string get_description() const; + +private: + impl_type impl_; + + void validate() const; +}; + +} // namespace binsrv + +#endif // BINSRV_KEYRING_RECORD_COLLECTION_HPP diff --git a/src/binsrv/keyring_record_collection_fwd.hpp b/src/binsrv/keyring_record_collection_fwd.hpp new file mode 100644 index 0000000..c41bd29 --- /dev/null +++ b/src/binsrv/keyring_record_collection_fwd.hpp @@ -0,0 +1,32 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef BINSRV_KEYRING_RECORD_COLLECTION_FWD_HPP +#define BINSRV_KEYRING_RECORD_COLLECTION_FWD_HPP + +#include +#include + +namespace binsrv { + +class keyring_record_collection; +using keyring_record_collection_ptr = + std::unique_ptr; + +inline constexpr std::uint32_t expected_keyring_record_collection_version{1U}; + +} // namespace binsrv + +#endif // BINSRV_KEYRING_RECORD_COLLECTION_FWD_HPP diff --git a/src/binsrv/keyring_record_fwd.hpp b/src/binsrv/keyring_record_fwd.hpp new file mode 100644 index 0000000..e6e42f4 --- /dev/null +++ b/src/binsrv/keyring_record_fwd.hpp @@ -0,0 +1,25 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef BINSRV_KEYRING_RECORD_FWD_HPP +#define BINSRV_KEYRING_RECORD_FWD_HPP + +namespace binsrv { + +struct keyring_record; + +} // namespace binsrv + +#endif // BINSRV_KEYRING_RECORD_FWD_HPP diff --git a/src/binsrv/main_config.cpp b/src/binsrv/main_config.cpp index 03570be..95e5682 100644 --- a/src/binsrv/main_config.cpp +++ b/src/binsrv/main_config.cpp @@ -16,15 +16,14 @@ #include "binsrv/main_config.hpp" #include -#include -#include -#include #include #include #include #include +// Needed for encryption_format's operator << +#include "binsrv/encryption_format_type.hpp" // IWYU pragma: keep // Needed for log_severity's operator << #include "binsrv/log_severity.hpp" // IWYU pragma: keep // Needed for replication_mode_type's operator << @@ -36,6 +35,7 @@ #include "easymysql/ssl_mode_type.hpp" // IWYU pragma: keep #include "util/exception_location_helpers.hpp" +#include "util/file_operations_helpers.hpp" #include "util/nv_tuple_from_json.hpp" namespace binsrv { @@ -43,28 +43,12 @@ namespace binsrv { main_config::main_config(std::string_view file_name) { static constexpr std::size_t max_file_size{1048576U}; - const std::filesystem::path file_path{file_name}; - std::ifstream ifs{file_path}; - if (!ifs.is_open()) { - util::exception_location().raise( - "cannot open configuration file"); - } - auto file_size = std::filesystem::file_size(file_path); - if (file_size == 0ULL) { + const auto file_content = + util::read_file_content(file_name, max_file_size, "configuration file"); + if (file_content.empty()) { util::exception_location().raise( "configuration file is empty"); } - if (file_size > max_file_size) { - util::exception_location().raise( - "configuration file is too large"); - } - - std::string file_content(file_size, 'x'); - if (!ifs.read(std::data(file_content), - static_cast(file_size))) { - util::exception_location().raise( - "cannot read configuration file content"); - } auto json_value = boost::json::parse(file_content); util::nv_tuple_from_json(json_value, impl_); @@ -74,6 +58,7 @@ main_config::main_config(std::string_view file_name) { void main_config::validate() const { root().get<"connection">().validate(); + root().get<"storage">().validate(); root().get<"replication">().validate(); } diff --git a/src/binsrv/storage_config.cpp b/src/binsrv/storage_config.cpp index 31707f7..75d0c85 100644 --- a/src/binsrv/storage_config.cpp +++ b/src/binsrv/storage_config.cpp @@ -29,4 +29,11 @@ namespace binsrv { return masked_uri.c_str(); } +void storage_config::validate() const { + const auto &optional_encryption{get<"encryption">()}; + if (optional_encryption.has_value()) { + optional_encryption->validate(); + } +} + } // namespace binsrv diff --git a/src/binsrv/storage_config.hpp b/src/binsrv/storage_config.hpp index 53217b4..cb7e43c 100644 --- a/src/binsrv/storage_config.hpp +++ b/src/binsrv/storage_config.hpp @@ -20,6 +20,7 @@ #include +#include "binsrv/encryption_config.hpp" // IWYU pragma: export #include "binsrv/size_unit.hpp" #include "binsrv/storage_backend_type_fwd.hpp" #include "binsrv/time_unit.hpp" @@ -36,9 +37,15 @@ struct [[nodiscard]] storage_config util::nv<"uri", std::string>, util::nv<"fs_buffer_directory", util::optional_string>, util::nv<"checkpoint_size", optional_size_unit>, - util::nv<"checkpoint_interval", optional_time_unit> + util::nv<"checkpoint_interval", optional_time_unit>, + util::nv<"encryption", optional_encryption_config> > { + [[nodiscard]] bool has_encryption() const noexcept { + return get<"encryption">().has_value(); + } [[nodiscard]] std::string get_masked_uri() const; + + void validate() const; }; // clang-format on diff --git a/src/util/file_operations_helpers.cpp b/src/util/file_operations_helpers.cpp new file mode 100644 index 0000000..8a62f59 --- /dev/null +++ b/src/util/file_operations_helpers.cpp @@ -0,0 +1,96 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#include "util/file_operations_helpers.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util/exception_location_helpers.hpp" + +namespace util { + +[[nodiscard]] std::string read_file_content(const std::filesystem::path &path, + std::size_t max_size, + std::string_view error_label) { + // opening in binary mode + std::ifstream ifs{}; + ifs.rdbuf()->pubsetbuf(nullptr, 0U); + ifs.open(path, std::ios_base::in | std::ios_base::binary); + if (!ifs.is_open()) { + util::exception_location().raise( + "cannot open " + std::string{error_label}); + } + if (!ifs.seekg(0, std::ios_base::end)) { + util::exception_location().raise( + "cannot seek " + std::string{error_label} + " to the end"); + } + const std::streampos end_pos{ifs.tellg()}; + const auto end_offset{static_cast(end_pos)}; + if (!ifs.seekg(0, std::ios_base::beg)) { + util::exception_location().raise( + "cannot seek " + std::string{error_label} + " to the beginning"); + } + + const auto file_size{static_cast(end_offset)}; + if (file_size > max_size) { + util::exception_location().raise( + std::string{error_label} + " is too large to be loaded in memory"); + } + + std::string file_content(file_size, 'x'); + if (!ifs.read(std::data(file_content), + static_cast(file_size))) { + util::exception_location().raise( + "cannot read " + std::string{error_label} + " content"); + } + return file_content; +} + +void write_file_content(const std::filesystem::path &path, + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + std::string_view content, + std::string_view error_label) { + // opening in binary mode with truncating + std::ofstream ofs{}; + ofs.rdbuf()->pubsetbuf(nullptr, 0U); + ofs.open(path, + std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); + if (!ofs.is_open()) { + util::exception_location().raise( + "cannot open " + std::string{error_label} + " for writing"); + } + + if (!ofs.write(std::data(content), + static_cast(std::size(content)))) { + util::exception_location().raise( + "cannot write data to " + std::string{error_label}); + } + + // explicit close so a failed flush is reported to the caller + ofs.close(); + if (ofs.fail()) { + util::exception_location().raise( + "cannot close " + std::string{error_label}); + } +} + +} // namespace util diff --git a/src/util/file_operations_helpers.hpp b/src/util/file_operations_helpers.hpp new file mode 100644 index 0000000..828a2fb --- /dev/null +++ b/src/util/file_operations_helpers.hpp @@ -0,0 +1,35 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef UTIL_FILE_OPERATIONS_HELPERS_HPP +#define UTIL_FILE_OPERATIONS_HELPERS_HPP + +#include +#include +#include +#include + +namespace util { + +[[nodiscard]] std::string read_file_content(const std::filesystem::path &path, + std::size_t max_size, + std::string_view error_label); + +void write_file_content(const std::filesystem::path &path, + std::string_view content, std::string_view error_label); + +} // namespace util + +#endif // UTIL_FILE_OPERATIONS_HELPERS_HPP diff --git a/src/util/hex_value.cpp b/src/util/hex_value.cpp new file mode 100644 index 0000000..e846a2b --- /dev/null +++ b/src/util/hex_value.cpp @@ -0,0 +1,94 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#include "util/hex_value.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "util/byte_span.hpp" +#include "util/exception_location_helpers.hpp" + +namespace util { + +hex_value::hex_value(const_byte_span data) + : data_{std::begin(data), std::end(data)} {} + +hex_value::hex_value(std::string_view value_hex) { + if ((std::size(value_hex) % 2U) != 0U) { + exception_location().raise( + "invalid hex_value length"); + } + + data_.resize(std::size(value_hex) / 2U); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + char *const out_it{reinterpret_cast(std::data(data_))}; + try { + boost::algorithm::unhex(value_hex, out_it); + } catch (const std::exception &) { + exception_location().raise( + "invalid hex_value characters"); + } +} + +[[nodiscard]] bool hex_value::try_parse(std::string_view value_sv, + hex_value &value) noexcept { + try { + value = hex_value(value_sv); + return true; + } catch (const std::exception &) { + return false; + } +} + +[[nodiscard]] std::string hex_value::to_hex_string() const { + std::string result(std::size(data_) * 2U, '\0'); + const auto data_sv{util::as_string_view(get_data())}; + boost::algorithm::hex(data_sv, std::begin(result)); + return result; +} + +std::ostream &operator<<(std::ostream &output, const hex_value &value) { + return output << value.to_hex_string(); +} + +std::istream &operator>>(std::istream &input, hex_value &value) { + std::string value_str; + input >> value_str; + if (!input) { + return input; + } + + hex_value parsed_value; + if (!hex_value::try_parse(value_str, parsed_value)) { + input.setstate(std::ios_base::failbit); + return input; + } + + value = std::move(parsed_value); + return input; +} + +} // namespace util diff --git a/src/util/hex_value.hpp b/src/util/hex_value.hpp new file mode 100644 index 0000000..3350761 --- /dev/null +++ b/src/util/hex_value.hpp @@ -0,0 +1,65 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef UTIL_HEX_VALUE_HPP +#define UTIL_HEX_VALUE_HPP + +#include "util/hex_value_fwd.hpp" // IWYU pragma: export + +#include +#include +#include + +#include + +#include "util/byte_span_fwd.hpp" + +namespace util { + +class [[nodiscard]] hex_value { +public: + // small_vector is expected to have 24 bytes overhead + static constexpr std::size_t inline_capacity{40U}; + // TODO: in c++26 change to std::inplace_vector + using buffer_type = + boost::container::small_vector; + + hex_value() = default; + explicit hex_value(const_byte_span data); + explicit hex_value(std::string_view value_hex); + + [[nodiscard]] static bool try_parse(std::string_view value_sv, + hex_value &value) noexcept; + + [[nodiscard]] std::string to_hex_string() const; + + [[nodiscard]] const_byte_span get_data() const noexcept { + return {std::data(data_), std::size(data_)}; + } + + [[nodiscard]] bool is_empty() const noexcept { return data_.empty(); } + [[nodiscard]] std::size_t get_size() const noexcept { + return std::size(data_); + } + + friend bool operator==(const hex_value &, const hex_value &) = default; + +private: + buffer_type data_; +}; + +} // namespace util + +#endif // UTIL_HEX_VALUE_HPP diff --git a/src/util/hex_value_fwd.hpp b/src/util/hex_value_fwd.hpp new file mode 100644 index 0000000..db7c907 --- /dev/null +++ b/src/util/hex_value_fwd.hpp @@ -0,0 +1,36 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef UTIL_HEX_VALUE_FWD_HPP +#define UTIL_HEX_VALUE_FWD_HPP + +#include + +#include "util/nv_tuple_json_support.hpp" + +namespace util { + +class hex_value; + +std::ostream &operator<<(std::ostream &output, const hex_value &value); + +std::istream &operator>>(std::istream &input, hex_value &value); + +} // namespace util + +template <> +struct util::is_string_convertible : std::true_type {}; + +#endif // UTIL_HEX_VALUE_FWD_HPP