initial commit
Some checks failed
sgraves/cpp-build-system_mac/pipeline/head There was a failure building this commit
sgraves/cpp-build-system/pipeline/head There was a failure building this commit

This commit is contained in:
2025-10-17 07:44:16 -05:00
parent 933c973c79
commit 92e3e495ce
1099 changed files with 102722 additions and 0 deletions

View File

@@ -0,0 +1,63 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
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,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef FIFTHGRID_TEST_INCLUDE_TEST_HPP_
#define FIFTHGRID_TEST_INCLUDE_TEST_HPP_
#if defined(U)
#undef U
#endif // defined(U)
#include "gmock/gmock.h"
#include "gtest/gtest.h"
using ::testing::_;
using namespace ::testing;
#define COMMA ,
#include "utils/all.hpp"
namespace fifthgrid::test {
[[nodiscard]] auto create_random_file(std::size_t size)
-> utils::file::i_file &;
[[nodiscard]] auto
generate_test_file_name(std::string_view file_name_no_extension) -> std::string;
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
template <typename buffer_t, typename result_t>
static void decrypt_and_verify(const buffer_t &buffer, std::string_view token,
result_t &result) {
EXPECT_TRUE(utils::encryption::decrypt_data(token, buffer, result));
}
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
auto generate_test_directory() -> utils::file::i_directory &;
[[nodiscard]] auto get_test_config_dir() -> std::string;
[[nodiscard]] auto get_test_input_dir() -> std::string;
[[nodiscard]] auto get_test_output_dir() -> std::string;
} // namespace fifthgrid::test
#endif // FIFTHGRID_TEST_INCLUDE_TEST_HPP_

148
support/test/src/test.cpp Normal file
View File

@@ -0,0 +1,148 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
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,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "test.hpp"
extern int PROJECT_TEST_RESULT;
namespace {
static std::recursive_mutex file_mtx{};
static std::vector<std::unique_ptr<fifthgrid::utils::file::i_fs_item>>
generated_files{};
struct file_deleter final {
std::string test_output_dir;
~file_deleter() {
generated_files.clear();
if (PROJECT_TEST_RESULT == 0) {
EXPECT_TRUE(fifthgrid::utils::file::directory(test_output_dir)
.remove_recursively());
}
}
};
const auto deleter{
std::make_unique<file_deleter>(fifthgrid::test::get_test_output_dir()),
};
} // namespace
namespace fifthgrid::test {
auto create_random_file(std::size_t size) -> utils::file::i_file & {
auto path = generate_test_file_name("random");
auto file = utils::file::file::open_or_create_file(path);
EXPECT_TRUE(*file);
if (*file) {
data_buffer buf(size);
#if defined(PROJECT_ENABLE_LIBSODIUM)
randombytes_buf(buf.data(), buf.size());
#else // !defined(PROJECT_ENABLE_LIBSODIUM)
thread_local std::mt19937 gen{
static_cast<std::uint_fast32_t>(std::time(nullptr)) ^
static_cast<std::uint_fast32_t>(std::random_device{}()),
};
std::uniform_int_distribution<std::uint8_t> dis(0U, 255U);
std::generate(buf.begin(), buf.end(), [&]() -> auto { return dis(gen); });
#endif // defined(PROJECT_ENABLE_LIBSODIUM)
std::size_t bytes_written{};
EXPECT_TRUE(file->write(buf, 0U, &bytes_written));
EXPECT_EQ(size, bytes_written);
EXPECT_EQ(size, file->size());
}
recur_mutex_lock lock{file_mtx};
generated_files.emplace_back(std::move(file));
return *dynamic_cast<utils::file::i_file *>(generated_files.back().get());
}
auto generate_test_directory() -> utils::file::i_directory & {
auto path = utils::path::combine(
get_test_output_dir(),
{
std::string{"test_dir"} + std::to_string(generated_files.size()),
});
recur_mutex_lock lock{file_mtx};
generated_files.emplace_back(std::unique_ptr<utils::file::i_fs_item>(
new utils::file::directory{path}));
auto &ret =
*dynamic_cast<utils::file::i_directory *>(generated_files.back().get());
EXPECT_TRUE(ret.create_directory());
return ret;
}
auto generate_test_file_name(std::string_view file_name_no_extension)
-> std::string {
auto path = utils::path::combine(
get_test_output_dir(), {
std::string{file_name_no_extension} +
std::to_string(generated_files.size()),
});
recur_mutex_lock lock{file_mtx};
generated_files.emplace_back(
std::unique_ptr<utils::file::i_file>(new utils::file::file{path}));
return generated_files.back()->get_path();
}
auto get_test_config_dir() -> std::string {
static auto test_path = ([]() -> std::string {
auto dir = utils::get_environment_variable("PROJECT_TEST_CONFIG_DIR");
return utils::path::combine(dir.empty() ? "." : dir, {"test_config"});
})();
return test_path;
}
auto get_test_input_dir() -> std::string {
static auto test_path = ([]() -> std::string {
auto dir = utils::get_environment_variable("PROJECT_TEST_INPUT_DIR");
return utils::path::combine(dir.empty() ? "." : dir, {"test_input"});
})();
return test_path;
}
auto get_test_output_dir() -> std::string {
static auto test_path = ([]() -> std::string {
auto temp = utils::file::create_temp_name("project_test");
#if defined(_WIN32)
auto path = utils::path::combine("%TEMP%", {temp});
#else // !defined(_WIN32)
auto path = utils::path::combine("/tmp", {temp});
#endif // defined(_WIN32)
if (not utils::file::directory(path).exists()) {
EXPECT_TRUE(utils::file::directory{path}.create_directory());
}
return path;
})();
return test_path;
}
} // namespace fifthgrid::test

View File

@@ -0,0 +1,73 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
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,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "test.hpp"
namespace {
struct config final {
std::string a;
std::string b;
};
} // namespace
namespace fifthgrid {
TEST(utils_atomic_test, atomic_primitive) {
utils::atomic<std::uint16_t> value;
value = 5U;
EXPECT_EQ(5U, static_cast<std::uint16_t>(value));
EXPECT_EQ(5U, value.load());
value.store(6U);
EXPECT_EQ(6U, static_cast<std::uint16_t>(value));
EXPECT_EQ(6U, value.load());
}
TEST(utils_atomic_test, atomic_primitive_equality) {
utils::atomic<std::uint16_t> value1{5U};
utils::atomic<std::uint16_t> value2{5U};
EXPECT_EQ(value1, value1);
EXPECT_EQ(value2, value2);
EXPECT_EQ(value1, value2);
EXPECT_EQ(static_cast<std::uint16_t>(value1), 5U);
EXPECT_EQ(static_cast<std::uint16_t>(value2), 5U);
}
TEST(utils_atomic_test, atomic_primitive_inequality) {
utils::atomic<std::uint16_t> value1{5U};
utils::atomic<std::uint16_t> value2{6U};
EXPECT_NE(value1, value2);
EXPECT_NE(static_cast<std::uint16_t>(value1), 6U);
EXPECT_NE(static_cast<std::uint16_t>(value2), 5U);
}
TEST(utils_atomic_test, atomic_struct) {
utils::atomic<config> value{
config{
.a = "a",
.b = "b",
},
};
auto data = static_cast<config>(value);
EXPECT_STREQ("a", data.a.c_str());
EXPECT_STREQ("b", data.b.c_str());
}
} // namespace fifthgrid

View File

@@ -0,0 +1,389 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
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,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "test.hpp"
using macaron::Base64::Decode;
using macaron::Base64::Encode;
using macaron::Base64::EncodeUrlSafe;
namespace {
[[nodiscard]] auto decode_to_string(std::string_view str) -> std::string {
auto vec = Decode(str);
return {vec.begin(), vec.end()};
}
[[nodiscard]] auto standard_to_url_safe(std::string str, bool keep_padding)
-> std::string {
for (auto &cur_ch : str) {
if (cur_ch == '+') {
cur_ch = '-';
} else if (cur_ch == '/') {
cur_ch = '_';
}
}
if (not keep_padding) {
while (not str.empty() && str.back() == '=') {
str.pop_back();
}
}
return str;
}
} // namespace
TEST(utils_base64, rfc4648_known_vectors_standard_padded) {
struct vec_case {
std::string_view in;
std::string_view b64;
};
const std::array<vec_case, 7> vectors{{
{"", ""},
{"f", "Zg=="},
{"fo", "Zm8="},
{"foo", "Zm9v"},
{"foob", "Zm9vYg=="},
{"fooba", "Zm9vYmE="},
{"foobar", "Zm9vYmFy"},
}};
for (const auto &vec_entry : vectors) {
const auto enc_str =
Encode(reinterpret_cast<const unsigned char *>(vec_entry.in.data()),
vec_entry.in.size(), /*url_safe=*/false, /*pad=*/true);
EXPECT_EQ(enc_str, vec_entry.b64);
const auto dec_vec = Decode(vec_entry.b64);
EXPECT_EQ(std::string(dec_vec.begin(), dec_vec.end()), vec_entry.in);
}
}
TEST(utils_base64, url_safe_padded_and_unpadded_match_transformed_standard) {
const std::string payload =
std::string("This+/needs/URL-safe mapping and padding checks.") +
std::string("\x00\x01\xFE\xFF", 4);
const auto std_padded =
Encode(reinterpret_cast<const unsigned char *>(payload.data()),
payload.size(), /*url_safe=*/false, /*pad=*/true);
const auto url_padded =
Encode(reinterpret_cast<const unsigned char *>(payload.data()),
payload.size(), /*url_safe=*/true, /*pad=*/true);
const auto url_unpadded =
Encode(reinterpret_cast<const unsigned char *>(payload.data()),
payload.size(), /*url_safe=*/true, /*pad=*/false);
const auto url_from_std_padded =
standard_to_url_safe(std_padded, /*keep_padding=*/true);
const auto url_from_std_unpadded =
standard_to_url_safe(std_padded, /*keep_padding=*/false);
EXPECT_EQ(url_padded, url_from_std_padded);
EXPECT_EQ(url_unpadded, url_from_std_unpadded);
const auto dec_one = Decode(url_padded);
const auto dec_two = Decode(url_unpadded);
EXPECT_EQ(std::string(dec_one.begin(), dec_one.end()), payload);
EXPECT_EQ(std::string(dec_two.begin(), dec_two.end()), payload);
}
TEST(utils_base64, empty_input) {
const std::string empty_str;
const auto enc_empty_std =
Encode(reinterpret_cast<const unsigned char *>(empty_str.data()),
empty_str.size(), /*url_safe=*/false, /*pad=*/true);
const auto enc_empty_url =
Encode(reinterpret_cast<const unsigned char *>(empty_str.data()),
empty_str.size(), /*url_safe=*/true, /*pad=*/false);
EXPECT_TRUE(enc_empty_std.empty());
EXPECT_TRUE(enc_empty_url.empty());
const auto dec_empty = Decode("");
EXPECT_TRUE(dec_empty.empty());
}
TEST(utils_base64, remainder_boundaries_round_trip) {
const std::string str_one = "A"; // rem 1
const std::string str_two = "AB"; // rem 2
const std::string str_thr = "ABC"; // rem 0
const std::string str_fou = "ABCD"; // rem 1 after blocks
const std::string str_fiv = "ABCDE"; // rem 2 after blocks
for (const auto *str_ptr :
{&str_one, &str_two, &str_thr, &str_fou, &str_fiv}) {
const auto enc_std =
Encode(reinterpret_cast<const unsigned char *>(str_ptr->data()),
str_ptr->size(), false, true);
const auto dec_std = Decode(enc_std);
EXPECT_EQ(std::string(dec_std.begin(), dec_std.end()), *str_ptr);
const auto enc_url_pad =
Encode(reinterpret_cast<const unsigned char *>(str_ptr->data()),
str_ptr->size(), true, true);
const auto dec_url_pad = Decode(enc_url_pad);
EXPECT_EQ(std::string(dec_url_pad.begin(), dec_url_pad.end()), *str_ptr);
const auto enc_url_nopad =
Encode(reinterpret_cast<const unsigned char *>(str_ptr->data()),
str_ptr->size(), true, false);
const auto dec_url_nopad = Decode(enc_url_nopad);
EXPECT_EQ(std::string(dec_url_nopad.begin(), dec_url_nopad.end()),
*str_ptr);
}
}
TEST(utils_base64, decode_accepts_standard_and_url_safe_forms) {
const std::string input_str = "Man is distinguished, not only by his reason.";
const auto std_padded =
Encode(reinterpret_cast<const unsigned char *>(input_str.data()),
input_str.size(), false, true);
const auto url_padded =
Encode(reinterpret_cast<const unsigned char *>(input_str.data()),
input_str.size(), true, true);
const auto url_unpadded =
Encode(reinterpret_cast<const unsigned char *>(input_str.data()),
input_str.size(), true, false);
const auto dec_std = Decode(std_padded);
const auto dec_url_pad = Decode(url_padded);
const auto dec_url_nopad = Decode(url_unpadded);
EXPECT_EQ(std::string(dec_std.begin(), dec_std.end()), input_str);
EXPECT_EQ(std::string(dec_url_pad.begin(), dec_url_pad.end()), input_str);
EXPECT_EQ(std::string(dec_url_nopad.begin(), dec_url_nopad.end()), input_str);
}
TEST(utils_base64, all_byte_values_round_trip) {
std::vector<unsigned char> byte_vec(256);
for (size_t idx = 0; idx < byte_vec.size(); ++idx) {
byte_vec[idx] = static_cast<unsigned char>(idx);
}
const auto enc_std = Encode(byte_vec.data(), byte_vec.size(), false, true);
const auto dec_std = Decode(enc_std);
ASSERT_EQ(dec_std.size(), byte_vec.size());
EXPECT_TRUE(std::equal(dec_std.begin(), dec_std.end(), byte_vec.begin()));
const auto enc_url = Encode(byte_vec.data(), byte_vec.size(), true, false);
const auto dec_url = Decode(enc_url);
ASSERT_EQ(dec_url.size(), byte_vec.size());
EXPECT_TRUE(std::equal(dec_url.begin(), dec_url.end(), byte_vec.begin()));
}
TEST(utils_base64, wrapper_encode_url_safe_equals_flagged_encode) {
const std::string data_str = "wrap me!";
const auto enc_wrap_a =
EncodeUrlSafe(reinterpret_cast<const unsigned char *>(data_str.data()),
data_str.size(), /*pad=*/false);
const auto enc_wrap_b =
Encode(reinterpret_cast<const unsigned char *>(data_str.data()),
data_str.size(), /*url_safe=*/true, /*pad=*/false);
EXPECT_EQ(enc_wrap_a, enc_wrap_b);
const auto enc_wrap_a2 = EncodeUrlSafe(data_str, /*pad=*/true);
const auto enc_wrap_b2 = Encode(data_str, /*url_safe=*/true, /*pad=*/true);
EXPECT_EQ(enc_wrap_a2, enc_wrap_b2);
}
TEST(utils_base64, unpadded_length_rules) {
const auto enc_one = Encode("f", /*url_safe=*/true, /*pad=*/false);
const auto enc_two = Encode("fo", /*url_safe=*/true, /*pad=*/false);
const auto enc_thr = Encode("foo", /*url_safe=*/true, /*pad=*/false);
EXPECT_EQ(enc_one.size(), 2U);
EXPECT_EQ(enc_two.size(), 3U);
EXPECT_EQ(enc_thr.size(), 4U);
EXPECT_EQ(Decode(enc_one), std::vector<unsigned char>({'f'}));
EXPECT_EQ(Decode(enc_two), std::vector<unsigned char>({'f', 'o'}));
EXPECT_EQ(Decode(enc_thr), std::vector<unsigned char>({'f', 'o', 'o'}));
}
TEST(utils_base64, errors_length_mod4_eq_1) {
EXPECT_THROW(Decode("A"), std::runtime_error);
EXPECT_THROW(Decode("AAAAA"), std::runtime_error);
}
TEST(utils_base64, errors_invalid_characters) {
EXPECT_THROW(Decode("Zm9v YmFy"), std::runtime_error);
EXPECT_THROW(Decode("Zm9v*YmFy"), std::runtime_error);
EXPECT_THROW(Decode("Z=g="), std::runtime_error);
}
TEST(utils_base64, reject_whitespace_and_controls) {
// newline, tab, and space should be rejected (decoder does not skip
// whitespace)
EXPECT_THROW(Decode("Zg==\n"), std::runtime_error);
EXPECT_THROW(Decode("Zg==\t"), std::runtime_error);
EXPECT_THROW(Decode("Z g=="), std::runtime_error);
}
TEST(utils_base64, reject_padding_in_nonfinal_quartet) {
// '=' cannot appear before the final quartet
EXPECT_THROW(Decode("AAA=AAAA"), std::runtime_error);
EXPECT_THROW(Decode("Zg==Zg=="), std::runtime_error);
}
TEST(utils_base64, reject_padding_in_first_two_slots_of_final_quartet) {
// '=' only allowed in slots 3 and/or 4 of the final quartet
EXPECT_THROW(Decode("=AAA"), std::runtime_error);
EXPECT_THROW(Decode("A=AA"), std::runtime_error);
EXPECT_THROW(
Decode("Z=g="),
std::runtime_error); // already in your suite, kept for completeness
}
TEST(utils_base64, reject_incorrect_padding_count_for_length) {
// "f" must be "Zg==" (two '='). One '=' is invalid.
EXPECT_THROW(Decode("Zg="), std::runtime_error);
// "foo" must be unpadded ("Zm9v"). Extra '=' is invalid.
EXPECT_THROW(Decode("Zm9v="), std::runtime_error);
// "fo" must have exactly one '=' -> "Zm8="
// Correct cases:
EXPECT_NO_THROW(Decode("Zm8="));
EXPECT_NO_THROW(Decode("Zm9v"));
}
TEST(utils_base64, accept_unpadded_equivalents_when_legal) {
EXPECT_EQ(decode_to_string("Zg"), "f");
EXPECT_EQ(decode_to_string("Zm8"), "fo");
EXPECT_EQ(decode_to_string("Zm9v"), "foo");
EXPECT_EQ(decode_to_string("Zm9vYmE"), "fooba");
}
TEST(utils_base64, mixed_alphabet_is_accepted) {
const std::string input_str = "any+/mix_/of+chars/";
const auto std_padded =
Encode(reinterpret_cast<const unsigned char *>(input_str.data()),
input_str.size(), /*url_safe=*/false, /*pad=*/true);
std::string mixed = std_padded;
for (char &cur_ch : mixed) {
if (cur_ch == '+') {
cur_ch = '-';
} else if (cur_ch == '/') {
cur_ch = '_';
}
}
EXPECT_EQ(decode_to_string(mixed), input_str);
}
TEST(utils_base64, invalid_non_ascii_octets_in_input) {
// Extended bytes like 0xFF are not valid Base64 characters
std::string bad = "Zg==";
bad[1] = static_cast<char>(0xFF);
EXPECT_THROW(Decode(bad), std::runtime_error);
}
TEST(utils_base64, large_buffer_round_trip_and_sizes) {
// Deterministic pseudo-random buffer
const std::size_t byte_len = 1 << 20; // 1 MiB
std::vector<unsigned char> data_vec(byte_len);
unsigned int val = 0x12345678U;
for (unsigned char &idx : data_vec) {
val ^= val << 13;
val ^= val >> 17;
val ^= val << 5; // xorshift32
idx = static_cast<unsigned char>(val & 0xFFU);
}
// Padded encode length should be 4 * ceil(N/3)
const auto enc_pad = Encode(data_vec.data(), data_vec.size(),
/*url_safe=*/false, /*pad=*/true);
const std::size_t expected_padded = 4U * ((byte_len + 2U) / 3U);
EXPECT_EQ(enc_pad.size(), expected_padded);
// Unpadded encode length rule (RFC 4648 §5)
const auto enc_nopad = Encode(data_vec.data(), data_vec.size(),
/*url_safe=*/true, /*pad=*/false);
const std::size_t rem = byte_len % 3U;
const std::size_t expected_unpadded =
4U * (byte_len / 3U) + (rem == 0U ? 0U : (rem == 1U ? 2U : 3U));
EXPECT_EQ(enc_nopad.size(), expected_unpadded);
// Round-trips
const auto dec_pad = Decode(enc_pad);
const auto dec_nopad = Decode(enc_nopad);
ASSERT_EQ(dec_pad.size(), data_vec.size());
ASSERT_EQ(dec_nopad.size(), data_vec.size());
EXPECT_TRUE(std::equal(dec_pad.begin(), dec_pad.end(), data_vec.begin()));
EXPECT_TRUE(std::equal(dec_nopad.begin(), dec_nopad.end(), data_vec.begin()));
}
TEST(utils_base64, url_safe_round_trip_various_lengths) {
for (std::size_t len : {0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 32U, 33U, 64U, 65U}) {
std::vector<unsigned char> buf(len);
for (std::size_t i = 0; i < len; ++i) {
buf[i] = static_cast<unsigned char>(i * 13U + 7U);
}
const auto enc_unpadded =
Encode(buf.data(), buf.size(), /*url_safe=*/true, /*pad=*/false);
const auto enc_padded =
Encode(buf.data(), buf.size(), /*url_safe=*/true, /*pad=*/true);
const auto dec_unpadded = Decode(enc_unpadded);
const auto dec_padded = Decode(enc_padded);
ASSERT_EQ(dec_unpadded.size(), buf.size());
ASSERT_EQ(dec_padded.size(), buf.size());
EXPECT_TRUE(
std::equal(dec_unpadded.begin(), dec_unpadded.end(), buf.begin()));
EXPECT_TRUE(std::equal(dec_padded.begin(), dec_padded.end(), buf.begin()));
}
}
TEST(utils_base64, reject_trailing_garbage_after_padding) {
// Anything after final '=' padding is invalid
EXPECT_THROW(Decode("Zg==A"), std::runtime_error);
EXPECT_THROW(Decode("Zm8=A"), std::runtime_error);
}
TEST(utils_base64, reject_three_padding_chars_total) {
// Any string with total length %4==1 is invalid (e.g., "Zg===")
EXPECT_THROW(Decode("Zg==="), std::runtime_error);
}
TEST(utils_base64, standard_vs_url_safe_encoding_equivalence) {
const std::string msg = "base64 / url-safe + cross-check";
const auto std_enc =
Encode(reinterpret_cast<const unsigned char *>(msg.data()), msg.size(),
/*url_safe=*/false, /*pad=*/true);
const auto url_enc =
Encode(reinterpret_cast<const unsigned char *>(msg.data()), msg.size(),
/*url_safe=*/true, /*pad=*/true);
std::string transformed = std_enc;
for (char &cur_ch : transformed) {
if (cur_ch == '+') {
cur_ch = '-';
} else if (cur_ch == '/') {
cur_ch = '_';
}
}
EXPECT_EQ(url_enc, transformed);
// decode once, then construct
EXPECT_EQ(decode_to_string(url_enc), msg);
}

View File

@@ -0,0 +1,250 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
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,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "test.hpp"
namespace fifthgrid {
TEST(utils_collection, excludes) {
auto data = {"cow", "moose", "dog", "chicken"};
EXPECT_FALSE(utils::collection::excludes(data, "chicken"));
EXPECT_FALSE(utils::collection::excludes(data, "cow"));
EXPECT_FALSE(utils::collection::excludes(data, "dog"));
EXPECT_FALSE(utils::collection::excludes(data, "moose"));
EXPECT_TRUE(utils::collection::excludes(data, "mouse"));
}
TEST(utils_collection, includes) {
auto data = {"cow", "moose", "dog", "chicken"};
EXPECT_FALSE(utils::collection::includes(data, "mice"));
EXPECT_TRUE(utils::collection::includes(data, "chicken"));
EXPECT_TRUE(utils::collection::includes(data, "cow"));
EXPECT_TRUE(utils::collection::includes(data, "dog"));
EXPECT_TRUE(utils::collection::includes(data, "moose"));
}
TEST(utils_collection, from_hex_string) {
{
auto data = "0xABCDEF10";
std::vector<std::uint8_t> val{};
EXPECT_TRUE(utils::collection::from_hex_string(data, val));
EXPECT_EQ(4U, val.size());
}
{
auto data = " 0xABCDEF10 ";
std::vector<std::uint8_t> val{};
EXPECT_TRUE(utils::collection::from_hex_string(data, val));
EXPECT_EQ(4U, val.size());
}
{
auto data = "ABCDEF10";
std::vector<std::uint8_t> val{};
EXPECT_TRUE(utils::collection::from_hex_string(data, val));
EXPECT_EQ(4U, val.size());
}
{
auto data = "ACDEF";
std::vector<std::uint8_t> val{};
EXPECT_TRUE(utils::collection::from_hex_string(data, val));
EXPECT_EQ(3U, val.size());
}
{
auto data = " ACDEF ";
std::vector<std::uint8_t> val{};
EXPECT_TRUE(utils::collection::from_hex_string(data, val));
EXPECT_EQ(3U, val.size());
}
{
auto data = "";
std::vector<std::uint8_t> val{};
EXPECT_TRUE(utils::collection::from_hex_string(data, val));
EXPECT_TRUE(val.empty());
}
{
auto data = L"0xABCDEF10";
std::vector<std::uint8_t> val{};
EXPECT_TRUE(utils::collection::from_hex_string(data, val));
EXPECT_EQ(4U, val.size());
}
{
auto data = L" 0xABCDEF10 ";
std::vector<std::uint8_t> val{};
EXPECT_TRUE(utils::collection::from_hex_string(data, val));
EXPECT_EQ(4U, val.size());
}
{
auto data = L"ABCDEF10";
std::vector<std::uint8_t> val{};
EXPECT_TRUE(utils::collection::from_hex_string(data, val));
EXPECT_EQ(4U, val.size());
}
{
auto data = L"ACDEF";
std::vector<std::uint8_t> val{};
EXPECT_TRUE(utils::collection::from_hex_string(data, val));
EXPECT_EQ(3U, val.size());
}
{
auto data = L" ACDEF ";
std::vector<std::uint8_t> val{};
EXPECT_TRUE(utils::collection::from_hex_string(data, val));
EXPECT_EQ(3U, val.size());
}
{
auto data = L"";
std::vector<std::uint8_t> val{};
EXPECT_TRUE(utils::collection::from_hex_string(data, val));
EXPECT_TRUE(val.empty());
}
}
TEST(utils_collection, from_hex_string_fails) {
{
auto data = "ABCDEF1Z";
std::vector<std::uint8_t> val{};
EXPECT_FALSE(utils::collection::from_hex_string(data, val));
EXPECT_TRUE(val.empty());
}
{
auto data = "ABC DEF1";
std::vector<std::uint8_t> val{};
EXPECT_FALSE(utils::collection::from_hex_string(data, val));
EXPECT_TRUE(val.empty());
}
{
auto data = "0x";
std::vector<std::uint8_t> val{};
EXPECT_FALSE(utils::collection::from_hex_string(data, val));
EXPECT_TRUE(val.empty());
}
{
auto data = " 0x ";
std::vector<std::uint8_t> val{};
EXPECT_FALSE(utils::collection::from_hex_string(data, val));
EXPECT_TRUE(val.empty());
}
{
auto data = L"ABCDEF1Z";
std::vector<std::uint8_t> val{};
EXPECT_FALSE(utils::collection::from_hex_string(data, val));
EXPECT_TRUE(val.empty());
}
{
auto data = L"ABC DEF1";
std::vector<std::uint8_t> val{};
EXPECT_FALSE(utils::collection::from_hex_string(data, val));
EXPECT_TRUE(val.empty());
}
{
auto data = L"0x";
std::vector<std::uint8_t> val{};
EXPECT_FALSE(utils::collection::from_hex_string(data, val));
EXPECT_TRUE(val.empty());
}
{
auto data = L" 0x";
std::vector<std::uint8_t> val{};
EXPECT_FALSE(utils::collection::from_hex_string(data, val));
EXPECT_TRUE(val.empty());
}
}
TEST(utils_collection, to_hex_string) {
{
std::array<std::int8_t, 2U> col{
static_cast<std::int8_t>(0xFF),
static_cast<std::int8_t>(0xEE),
};
auto str = utils::collection::to_hex_string(col);
EXPECT_STREQ("ffee", str.c_str());
auto w_str = utils::collection::to_hex_wstring(col);
EXPECT_STREQ(L"ffee", w_str.c_str());
}
{
std::array<std::uint8_t, 2U> col{
static_cast<std::uint8_t>(0xFF),
static_cast<std::uint8_t>(0xEE),
};
auto str = utils::collection::to_hex_string(col);
EXPECT_STREQ("ffee", str.c_str());
auto w_str = utils::collection::to_hex_wstring(col);
EXPECT_STREQ(L"ffee", w_str.c_str());
}
}
TEST(utils_collection, remove_element) {
{
std::vector<std::uint8_t> col{
static_cast<std::uint8_t>(0xFF),
static_cast<std::uint8_t>(0xEE),
};
utils::collection::remove_element(col, 0xFF);
EXPECT_EQ(1U, col.size());
EXPECT_EQ(static_cast<std::uint8_t>(0xEE), col.at(0U));
}
{
std::vector<std::uint8_t> col{
static_cast<std::uint8_t>(0xFF),
static_cast<std::uint8_t>(0xEE),
};
utils::collection::remove_element(col, 0xEE);
EXPECT_EQ(1U, col.size());
EXPECT_EQ(static_cast<std::uint8_t>(0xFF), col.at(0U));
}
{
std::vector<std::uint8_t> col{
static_cast<std::uint8_t>(0xFF),
static_cast<std::uint8_t>(0xEE),
};
utils::collection::remove_element(col, 0xEF);
EXPECT_EQ(2U, col.size());
EXPECT_EQ(static_cast<std::uint8_t>(0xFF), col.at(0U));
EXPECT_EQ(static_cast<std::uint8_t>(0xEE), col.at(1U));
}
}
} // namespace fifthgrid

View File

@@ -0,0 +1,306 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
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,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "test.hpp"
namespace fifthgrid {
TEST(utils_common, calculate_read_size) {
auto read_size = utils::calculate_read_size(0U, 0U, 0U);
EXPECT_EQ(0U, read_size);
read_size = utils::calculate_read_size(5U, 0U, 0U);
EXPECT_EQ(0U, read_size);
read_size = utils::calculate_read_size(0U, 6U, 7U);
EXPECT_EQ(0U, read_size);
read_size = utils::calculate_read_size(7U, 1U, 7U);
EXPECT_EQ(0U, read_size);
read_size = utils::calculate_read_size(5U, 5U, 0U);
EXPECT_EQ(5U, read_size);
read_size = utils::calculate_read_size(5U, 5U, 1U);
EXPECT_EQ(4U, read_size);
}
TEST(utils_common, version_equal) {
EXPECT_EQ(0, utils::compare_version_strings("", ""));
EXPECT_EQ(0, utils::compare_version_strings("1.0", "1.0"));
EXPECT_EQ(0, utils::compare_version_strings("1.0.0", "1.0"));
EXPECT_EQ(0, utils::compare_version_strings("1.0.0.0", "1.0"));
EXPECT_EQ(0, utils::compare_version_strings("1.0.0.0", "1.0.0"));
EXPECT_EQ(0, utils::compare_version_strings(L"", L""));
EXPECT_EQ(0, utils::compare_version_strings(L"1.0", L"1.0"));
EXPECT_EQ(0, utils::compare_version_strings(L"1.0.0", L"1.0"));
EXPECT_EQ(0, utils::compare_version_strings(L"1.0.0.0", L"1.0"));
EXPECT_EQ(0, utils::compare_version_strings(L"1.0.0.0", L"1.0.0"));
}
TEST(utils_common, version_greater) {
EXPECT_EQ(1, utils::compare_version_strings("1.0.1", ""));
EXPECT_EQ(1, utils::compare_version_strings("1.0.1", "1.0"));
EXPECT_EQ(1, utils::compare_version_strings("1.0.1", "1.0.0"));
EXPECT_EQ(1, utils::compare_version_strings("1.0.1", "1.0.0.0"));
EXPECT_EQ(1, utils::compare_version_strings("1.0.1.0", "1.0"));
EXPECT_EQ(1, utils::compare_version_strings("1.0.1.0", "1.0.0"));
EXPECT_EQ(1, utils::compare_version_strings("1.0.1.0", "1.0.0.0"));
EXPECT_EQ(1, utils::compare_version_strings("1.0", "0.9.9"));
EXPECT_EQ(1, utils::compare_version_strings("1.0.1", "0.9.9"));
EXPECT_EQ(1, utils::compare_version_strings("1.0.1.0", "0.9.9"));
EXPECT_EQ(1, utils::compare_version_strings("1.0.1", ""));
EXPECT_EQ(1, utils::compare_version_strings(L"1.0.1", L"1.0"));
EXPECT_EQ(1, utils::compare_version_strings(L"1.0.1", L"1.0.0"));
EXPECT_EQ(1, utils::compare_version_strings(L"1.0.1", L"1.0.0.0"));
EXPECT_EQ(1, utils::compare_version_strings(L"1.0.1.0", L"1.0"));
EXPECT_EQ(1, utils::compare_version_strings(L"1.0.1.0", L"1.0.0"));
EXPECT_EQ(1, utils::compare_version_strings(L"1.0.1.0", L"1.0.0.0"));
EXPECT_EQ(1, utils::compare_version_strings(L"1.0", L"0.9.9"));
EXPECT_EQ(1, utils::compare_version_strings(L"1.0.1", L"0.9.9"));
EXPECT_EQ(1, utils::compare_version_strings(L"1.0.1.0", L"0.9.9"));
}
TEST(utils_common, version_less) {
EXPECT_EQ(-1, utils::compare_version_strings("", "1.0"));
EXPECT_EQ(-1, utils::compare_version_strings("0.9.9", "1.0"));
EXPECT_EQ(-1, utils::compare_version_strings("0.9.9", "1.0.1"));
EXPECT_EQ(-1, utils::compare_version_strings("0.9.9", "1.0.1.0"));
EXPECT_EQ(-1, utils::compare_version_strings("1.0", "1.0.1"));
EXPECT_EQ(-1, utils::compare_version_strings("1.0", "1.0.1.0"));
EXPECT_EQ(-1, utils::compare_version_strings("1.0.0", "1.0.1"));
EXPECT_EQ(-1, utils::compare_version_strings("1.0.0", "1.0.1.0"));
EXPECT_EQ(-1, utils::compare_version_strings("1.0.0.0", "1.0.1"));
EXPECT_EQ(-1, utils::compare_version_strings("1.0.0.0", "1.0.1.0"));
EXPECT_EQ(-1, utils::compare_version_strings(L"", L"1.0"));
EXPECT_EQ(-1, utils::compare_version_strings(L"0.9.9", L"1.0"));
EXPECT_EQ(-1, utils::compare_version_strings(L"0.9.9", L"1.0.1"));
EXPECT_EQ(-1, utils::compare_version_strings(L"0.9.9", L"1.0.1.0"));
EXPECT_EQ(-1, utils::compare_version_strings(L"1.0", L"1.0.1"));
EXPECT_EQ(-1, utils::compare_version_strings(L"1.0", L"1.0.1.0"));
EXPECT_EQ(-1, utils::compare_version_strings(L"1.0.0", L"1.0.1"));
EXPECT_EQ(-1, utils::compare_version_strings(L"1.0.0", L"1.0.1.0"));
EXPECT_EQ(-1, utils::compare_version_strings(L"1.0.0.0", L"1.0.1"));
EXPECT_EQ(-1, utils::compare_version_strings(L"1.0.0.0", L"1.0.1.0"));
}
#if defined(PROJECT_ENABLE_STDUUID)
TEST(utils_common, create_uuid_string) {
{
const auto uuid1 = utils::create_uuid_string();
const auto uuid2 = utils::create_uuid_string();
ASSERT_EQ(36U, uuid1.size());
ASSERT_EQ(36U, uuid2.size());
ASSERT_STRNE(uuid1.c_str(), uuid2.c_str());
}
{
const auto uuid1 = utils::create_uuid_wstring();
const auto uuid2 = utils::create_uuid_wstring();
ASSERT_EQ(36U, uuid1.size());
ASSERT_EQ(36U, uuid2.size());
ASSERT_STRNE(uuid1.c_str(), uuid2.c_str());
}
}
#endif // defined(PROJECT_ENABLE_STDUUID)
#if defined(PROJECT_ENABLE_LIBSODIUM)
TEST(utils_common, generate_secure_random) {
{
auto r1 = utils::generate_secure_random<std::size_t>();
auto r2 = utils::generate_secure_random<std::size_t>();
EXPECT_NE(r1, r2);
}
{
auto r1 = utils::generate_secure_random<std::vector<std::uint8_t>>(6U);
auto r2 = utils::generate_secure_random<std::vector<std::uint8_t>>(6U);
EXPECT_EQ(6U, r1.size());
EXPECT_EQ(r1.size(), r2.size());
EXPECT_NE(r1, r2);
}
{
auto r1 = utils::generate_secure_random<std::array<std::uint8_t, 4U>>();
auto r2 = utils::generate_secure_random<std::array<std::uint8_t, 4U>>();
EXPECT_EQ(4U, r1.size());
EXPECT_EQ(r1.size(), r2.size());
EXPECT_NE(0, std::memcmp(r1.data(), r2.data(), r1.size()));
}
{
auto r1 = utils::generate_secure_random<std::string>(6U);
auto r2 = utils::generate_secure_random<std::string>(6U);
EXPECT_EQ(6U, r1.size());
EXPECT_EQ(r1.size(), r2.size());
EXPECT_NE(0, std::memcmp(r1.data(), r2.data(), r1.size()));
}
{
auto r1 = utils::generate_secure_random<std::wstring>(6U);
auto r2 = utils::generate_secure_random<std::wstring>(6U);
EXPECT_EQ(6U, r1.size());
EXPECT_EQ(r1.size(), r2.size());
EXPECT_NE(0, std::memcmp(r1.data(), r2.data(), r1.size()));
}
}
#endif // defined(PROJECT_ENABLE_LIBSODIUM)
TEST(utils_common, divide_with_ceiling) {
auto r = utils::divide_with_ceiling(12, 5);
EXPECT_EQ(3, r);
r = utils::divide_with_ceiling(12, 4);
EXPECT_EQ(3, r);
r = utils::divide_with_ceiling(1, 2);
EXPECT_EQ(1, r);
r = utils::divide_with_ceiling(2, 2);
EXPECT_EQ(1, r);
r = utils::divide_with_ceiling(0, 2);
EXPECT_EQ(0, r);
}
TEST(utils_common, generate_random_between_for_signed_integers) {
static constexpr auto max_iterations{1000000UL};
for (std::size_t idx = 0U; idx < max_iterations; ++idx) {
auto res = utils::generate_random_between(5, 12);
EXPECT_GE(res, 5);
EXPECT_LE(res, 12);
}
for (std::size_t idx = 0U; idx < max_iterations; ++idx) {
auto res = utils::generate_random_between(-5, 12);
EXPECT_GE(res, -5);
EXPECT_LE(res, 12);
}
for (std::size_t idx = 0U; idx < max_iterations; ++idx) {
auto res = utils::generate_random_between(-5, -1);
EXPECT_GE(res, -5);
EXPECT_LE(res, -1);
}
}
TEST(utils_common, generate_random_between_for_unsigned_integers) {
static constexpr auto max_iterations{1000000UL};
for (std::size_t idx = 0U; idx < max_iterations; ++idx) {
auto res = utils::generate_random_between(5U, 12U);
EXPECT_GE(res, 5);
EXPECT_LE(res, 12);
}
}
TEST(utils_common, generate_random_between_throws_error_on_invalid_range) {
EXPECT_THROW(
{
try {
[[maybe_unused]] auto res = utils::generate_random_between(12, 5);
} catch (const std::range_error &e) {
EXPECT_STREQ("end must be greater than begin", e.what());
throw;
}
},
std::range_error);
EXPECT_THROW(
{
try {
[[maybe_unused]] auto res = utils::generate_random_between(12, 12);
} catch (const std::range_error &e) {
EXPECT_STREQ("end must be greater than begin", e.what());
throw;
}
},
std::range_error);
}
TEST(utils_common, generate_random_string) {
static constexpr auto max_iterations{10000L};
const auto test_string = [](auto str) {
static std::vector<decltype(str)> list{};
EXPECT_FALSE(utils::collection::includes(list, str));
list.push_back(str);
EXPECT_EQ(16U, str.size());
for (auto &&ch : str) {
auto ch_int = static_cast<std::uint32_t>(ch);
EXPECT_GE(ch_int, 48U);
EXPECT_LE(ch_int, 73U + 48U);
}
};
for (std::size_t idx = 0U; idx < max_iterations; ++idx) {
test_string(utils::generate_random_string(16U));
test_string(utils::generate_random_wstring(16U));
}
}
TEST(utils_common, generate_random_string_for_zero_length) {
EXPECT_TRUE(utils::generate_random_string(0U).empty());
EXPECT_TRUE(utils::generate_random_wstring(0U).empty());
}
TEST(utils_common, get_environment_variable) {
static constexpr std::string path_env{"PATH"};
std::string path;
#if defined(_WIN32)
path.resize(fifthgrid::max_path_length + 1U);
auto size = ::GetEnvironmentVariableA(path_env.c_str(), path.data(), 0U);
path.resize(size);
::GetEnvironmentVariableA(path_env.c_str(), path.data(),
static_cast<DWORD>(path.size()));
#else // !defined(_WIN32)
path = std::getenv(path_env.c_str());
#endif // defined(_WIN32)
EXPECT_STREQ(path.c_str(), utils::get_environment_variable(path_env).c_str());
EXPECT_STREQ(
utils::string::from_utf8(path).c_str(),
utils::get_environment_variable(utils::string::from_utf8(path_env))
.c_str());
}
#if defined(PROJECT_ENABLE_BOOST)
TEST(utils_common, get_next_available_port) {
std::uint16_t available_port{};
for (std::uint16_t port = 1025U; port < 1030U; ++port) {
EXPECT_TRUE(utils::get_next_available_port(port, available_port));
EXPECT_GE(available_port, port);
}
}
TEST(utils_common, get_next_available_port_fails_if_starting_point_is_zero) {
std::uint16_t available_port{};
EXPECT_FALSE(utils::get_next_available_port(0U, available_port));
EXPECT_EQ(0U, available_port);
}
#endif // defined(PROJECT_ENABLE_BOOST)
} // namespace fifthgrid

View File

@@ -0,0 +1,300 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
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,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "test.hpp"
#if defined(PROJECT_ENABLE_SQLITE)
namespace fifthgrid {
class utils_db_sqlite : public ::testing::Test {
public:
utils::db::sqlite::db3_t db3;
void SetUp() override {
{
sqlite3 *db3_ptr{nullptr};
auto res = sqlite3_open(":memory:", &db3_ptr);
ASSERT_EQ(SQLITE_OK, res);
ASSERT_TRUE(db3_ptr != nullptr);
db3 = utils::db::sqlite::db3_t{
db3_ptr,
utils::db::sqlite::sqlite3_deleter(),
};
}
utils::db::sqlite::db3_stmt_t db3_stmt;
{
std::string sql{
"CREATE TABLE [table] (column1 TEXT PRIMARY KEY UNIQUE "
"NOT NULL, column2 TEXT NOT NULL);",
};
sqlite3_stmt *stmt_ptr{nullptr};
auto res =
sqlite3_prepare_v2(db3.get(), sql.c_str(), -1, &stmt_ptr, nullptr);
db3_stmt = utils::db::sqlite::db3_stmt_t{
stmt_ptr,
utils::db::sqlite::sqlite3_statement_deleter(),
};
ASSERT_EQ(SQLITE_OK, res);
}
auto res = sqlite3_step(db3_stmt.get());
ASSERT_EQ(SQLITE_DONE, res);
}
void TearDown() override { db3.reset(); }
};
static void common_insert(sqlite3 &db3, bool dump = false) {
auto query = utils::db::sqlite::db_insert{db3, "table"}
.column_value("column1", "test0")
.column_value("column2", "test1");
if (dump) {
std::cout << query.dump() << std::endl;
}
auto res = query.go();
EXPECT_TRUE(res.ok());
}
static void common_select(sqlite3 &db3, std::string value1, std::string value2,
bool dump = false) {
auto query = utils::db::sqlite::db_select{db3, "table"};
if (dump) {
std::cout << query.dump() << std::endl;
}
auto res = query.go();
EXPECT_TRUE(res.ok());
EXPECT_TRUE(res.has_row());
std::size_t row_count{};
while (res.has_row()) {
std::optional<utils::db::sqlite::db_result::row> row;
EXPECT_TRUE(res.get_row(row));
EXPECT_TRUE(row.has_value());
if (row.has_value()) {
auto columns = row.value().get_columns();
EXPECT_EQ(std::size_t(2U), columns.size());
EXPECT_STREQ("column1", columns[0U].get_name().c_str());
EXPECT_STREQ(value1.c_str(),
columns[0U].get_value<std::string>().c_str());
EXPECT_STREQ("column2", columns[1U].get_name().c_str());
EXPECT_STREQ(value2.c_str(),
columns[1U].get_value<std::string>().c_str());
for (auto &&column : columns) {
std::cout << column.get_index() << ':';
std::cout << column.get_name() << ':';
std::cout << column.get_value<std::string>() << std::endl;
}
}
++row_count;
}
EXPECT_EQ(std::size_t(1U), row_count);
}
static void common_delete(sqlite3 &db3, bool dump = false) {
{
auto query = utils::db::sqlite::db_delete{db3, "table"};
if (dump) {
std::cout << query.dump() << std::endl;
}
auto res = query.go();
EXPECT_TRUE(res.ok());
}
{
auto query = utils::db::sqlite::db_select{db3, "table"};
auto res = query.go();
EXPECT_TRUE(res.ok());
std::size_t row_count{};
while (res.has_row()) {
++row_count;
}
EXPECT_EQ(std::size_t(0U), row_count);
}
}
TEST_F(utils_db_sqlite, db_delete_query) {
auto query = utils::db::sqlite::db_delete{*db3.get(), "table"};
auto query_str = query.dump();
std::cout << query_str << std::endl;
EXPECT_STREQ(R"(DELETE FROM "table";)", query_str.c_str());
}
TEST_F(utils_db_sqlite, db_delete_where_query) {
auto query = utils::db::sqlite::db_delete{*db3.get(), "table"}
.where("column1")
.equals("test1")
.and_()
.where("column2")
.equals("test2");
auto query_str = query.dump();
std::cout << query_str << std::endl;
EXPECT_STREQ(R"(DELETE FROM "table" WHERE "column1"=?1 AND "column2"=?2;)",
query_str.c_str());
}
TEST_F(utils_db_sqlite, db_insert_query) {
auto query = utils::db::sqlite::db_insert{*db3.get(), "table"}
.column_value("column1", "test9")
.column_value("column2", "test9");
auto query_str = query.dump();
std::cout << query_str << std::endl;
EXPECT_STREQ(R"(INSERT INTO "table" ("column1", "column2") VALUES (?1, ?2);)",
query_str.c_str());
}
TEST_F(utils_db_sqlite, db_insert_or_replace_query) {
auto query = utils::db::sqlite::db_insert{*db3.get(), "table"}
.or_replace()
.column_value("column1", "test1")
.column_value("column2", "test2");
auto query_str = query.dump();
std::cout << query_str << std::endl;
EXPECT_STREQ(
R"(INSERT OR REPLACE INTO "table" ("column1", "column2") VALUES (?1, ?2);)",
query_str.c_str());
}
TEST_F(utils_db_sqlite, db_select_query) {
auto query = utils::db::sqlite::db_select{*db3.get(), "table"};
auto query_str = query.dump();
std::cout << query_str << std::endl;
EXPECT_STREQ(R"(SELECT * FROM "table";)", query_str.c_str());
}
TEST_F(utils_db_sqlite, db_select_where_query) {
auto query = utils::db::sqlite::db_select{*db3.get(), "table"}
.where("column1")
.equals("test1")
.and_()
.where("column2")
.equals("test2");
auto query_str = query.dump();
std::cout << query_str << std::endl;
EXPECT_STREQ(R"(SELECT * FROM "table" WHERE "column1"=?1 AND "column2"=?2;)",
query_str.c_str());
}
TEST_F(utils_db_sqlite, db_select_where_with_group_query) {
auto query =
utils::db::sqlite::db_select{*db3.get(), "table"}
.group([](auto &grp) {
grp.where("column1").equals("a").or_().where("column1").equals("b");
})
.and_()
.group([](auto &grp) {
grp.where("column2").equals("c").or_().where("column2").equals("d");
})
.or_()
.group([](auto &grp) {
grp.where("column1").equals("e").or_().where("column2").equals("f");
});
auto query_str = query.dump();
std::cout << query_str << std::endl;
EXPECT_STREQ(
R"(SELECT * FROM "table" WHERE ("column1"=?1 OR "column1"=?2) AND ("column2"=?3 OR "column2"=?4) OR ("column1"=?5 OR "column2"=?6);)",
query_str.c_str());
}
TEST_F(utils_db_sqlite, db_select_columns_query) {
auto query = utils::db::sqlite::db_select{*db3.get(), "table"}
.column("column1")
.column("column2")
.where("column1")
.equals("test1")
.and_()
.where("column2")
.equals("test2");
auto query_str = query.dump();
std::cout << query_str << std::endl;
EXPECT_STREQ(
R"(SELECT column1, column2 FROM "table" WHERE "column1"=?1 AND "column2"=?2;)",
query_str.c_str());
}
TEST_F(utils_db_sqlite, db_update_query) {
auto query = utils::db::sqlite::db_update{*db3.get(), "table"}
.column_value("column1", "moose")
.where("column1")
.equals("test1")
.and_()
.where("column2")
.equals("test2");
auto query_str = query.dump();
std::cout << query_str << std::endl;
EXPECT_STREQ(
R"(UPDATE "table" SET "column1"=?1 WHERE "column1"=?2 AND "column2"=?3;)",
query_str.c_str());
}
TEST_F(utils_db_sqlite, insert_select_delete) {
common_insert(*db3.get(), true);
common_select(*db3.get(), "test0", "test1", true);
common_delete(*db3.get(), true);
}
TEST_F(utils_db_sqlite, insert_update_delete) {
common_insert(*db3.get());
{
auto query = utils::db::sqlite::db_update{*db3.get(), "table"}
.column_value("column1", "moose")
.where("column1")
.equals("test0");
std::cout << query.dump() << std::endl;
auto res = query.go();
EXPECT_TRUE(res.ok());
}
common_select(*db3.get(), "moose", "test1");
common_delete(*db3.get());
}
TEST_F(utils_db_sqlite, insert_or_replace_and_delete) {
common_insert(*db3.get());
{
auto query = utils::db::sqlite::db_insert{*db3.get(), "table"}
.or_replace()
.column_value("column1", "test0")
.column_value("column2", "moose");
std::cout << query.dump() << std::endl;
auto res = query.go();
EXPECT_TRUE(res.ok());
}
common_select(*db3.get(), "test0", "moose");
common_delete(*db3.get());
}
} // namespace fifthgrid
#endif // defined(PROJECT_ENABLE_SQLITE)

View File

@@ -0,0 +1,713 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
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,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "test.hpp"
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
namespace {
const auto get_stop_requested = []() -> bool { return false; };
} // namespace
namespace fifthgrid {
TEST(utils_encrypting_reader, read_file_data) {
const auto token = std::string("moose");
auto &source_file = test::create_random_file(
8U * utils::encryption::encrypting_reader::get_data_chunk_size());
EXPECT_TRUE(source_file);
if (source_file) {
utils::encryption::encrypting_reader reader(
"test.dat", source_file.get_path(), get_stop_requested, token,
std::nullopt);
for (std::uint8_t i = 0U; i < 8U; i++) {
data_buffer buffer(
utils::encryption::encrypting_reader::get_encrypted_chunk_size());
for (std::uint8_t j = 0U; j < 2U; j++) {
ASSERT_EQ(
buffer.size() / 2U,
utils::encryption::encrypting_reader::reader_function(
reinterpret_cast<char *>(&buffer[(buffer.size() / 2U) * j]),
buffer.size() / 2U, 1U, &reader));
}
data_buffer decrypted_data;
EXPECT_TRUE(
utils::encryption::decrypt_data(token, buffer, decrypted_data));
EXPECT_EQ(utils::encryption::encrypting_reader::get_data_chunk_size(),
decrypted_data.size());
std::size_t bytes_read{};
data_buffer file_data(decrypted_data.size());
EXPECT_TRUE(source_file.read(
file_data,
utils::encryption::encrypting_reader::get_data_chunk_size() * i,
&bytes_read));
EXPECT_EQ(0, std::memcmp(file_data.data(), decrypted_data.data(),
file_data.size()));
}
}
}
TEST(utils_encrypting_reader, read_file_data_using_argon2id) {
const auto token = std::string("moose");
utils::encryption::kdf_config cfg;
auto &source_file = test::create_random_file(
8U * utils::encryption::encrypting_reader::get_data_chunk_size());
EXPECT_TRUE(source_file);
if (source_file) {
utils::encryption::encrypting_reader reader(
"test.dat", source_file.get_path(), get_stop_requested, token, cfg,
std::nullopt);
std::array<std::uint8_t, utils::encryption::kdf_config::size()> hdr;
EXPECT_EQ(hdr.size(), utils::encryption::encrypting_reader::reader_function(
reinterpret_cast<char *>(hdr.data()), hdr.size(),
1U, &reader));
EXPECT_TRUE(utils::encryption::kdf_config::from_header(hdr, cfg));
for (std::uint8_t i = 0U; i < 8U; i++) {
data_buffer buffer(
utils::encryption::encrypting_reader::get_encrypted_chunk_size());
for (std::uint8_t j = 0U; j < 2U; j++) {
ASSERT_EQ(
buffer.size() / 2U,
utils::encryption::encrypting_reader::reader_function(
reinterpret_cast<char *>(&buffer[(buffer.size() / 2U) * j]),
buffer.size() / 2U, 1U, &reader));
}
data_buffer decrypted_data;
EXPECT_TRUE(utils::encryption::decrypt_data(
token, *reader.get_kdf_config_for_data(), buffer, decrypted_data));
EXPECT_EQ(utils::encryption::encrypting_reader::get_data_chunk_size(),
decrypted_data.size());
std::size_t bytes_read{};
data_buffer file_data(decrypted_data.size());
EXPECT_TRUE(source_file.read(
file_data,
utils::encryption::encrypting_reader::get_data_chunk_size() * i,
&bytes_read));
EXPECT_EQ(0, std::memcmp(file_data.data(), decrypted_data.data(),
file_data.size()));
}
}
}
TEST(utils_encrypting_reader, read_file_data_using_argon2id_master_key) {
const auto token = std::string("moose");
utils::encryption::kdf_config cfg;
auto master_key =
utils::encryption::generate_key<utils::hash::hash_256_t>(token, cfg);
auto &source_file = test::create_random_file(
8U * utils::encryption::encrypting_reader::get_data_chunk_size());
EXPECT_TRUE(source_file);
if (source_file) {
utils::encryption::encrypting_reader reader(
"test.dat", source_file.get_path(), get_stop_requested, master_key, cfg,
std::nullopt);
std::array<std::uint8_t, utils::encryption::kdf_config::size()> hdr;
EXPECT_EQ(hdr.size(), utils::encryption::encrypting_reader::reader_function(
reinterpret_cast<char *>(hdr.data()), hdr.size(),
1U, &reader));
EXPECT_TRUE(utils::encryption::kdf_config::from_header(hdr, cfg));
for (std::uint8_t i = 0U; i < 8U; i++) {
data_buffer buffer(
utils::encryption::encrypting_reader::get_encrypted_chunk_size());
for (std::uint8_t j = 0U; j < 2U; j++) {
ASSERT_EQ(
buffer.size() / 2U,
utils::encryption::encrypting_reader::reader_function(
reinterpret_cast<char *>(&buffer[(buffer.size() / 2U) * j]),
buffer.size() / 2U, 1U, &reader));
}
auto data_cfg = *reader.get_kdf_config_for_data();
utils::hash::hash_256_t data_key;
std::tie(data_key, std::ignore) = cfg.create_subkey(
utils::encryption::kdf_context::data, data_cfg.unique_id, master_key);
data_buffer decrypted_data;
EXPECT_TRUE(
utils::encryption::decrypt_data(data_key, buffer, decrypted_data));
EXPECT_EQ(utils::encryption::encrypting_reader::get_data_chunk_size(),
decrypted_data.size());
std::size_t bytes_read{};
data_buffer file_data(decrypted_data.size());
EXPECT_TRUE(source_file.read(
file_data,
utils::encryption::encrypting_reader::get_data_chunk_size() * i,
&bytes_read));
EXPECT_EQ(0, std::memcmp(file_data.data(), decrypted_data.data(),
file_data.size()));
}
}
}
TEST(utils_encrypting_reader, read_file_data_in_multiple_chunks) {
const auto token = std::string("moose");
auto &source_file = test::create_random_file(
8U * utils::encryption::encrypting_reader::get_data_chunk_size());
EXPECT_TRUE(source_file);
if (source_file) {
utils::encryption::encrypting_reader reader(
"test.dat", source_file.get_path(), get_stop_requested, token,
std::nullopt);
for (std::uint8_t i = 0U; i < 8U; i += 2U) {
data_buffer buffer(
utils::encryption::encrypting_reader::get_encrypted_chunk_size() *
2U);
EXPECT_EQ(buffer.size(),
utils::encryption::encrypting_reader::reader_function(
reinterpret_cast<char *>(buffer.data()), buffer.size(), 1U,
&reader));
for (std::uint8_t j = 0U; j < 2U; j++) {
data_buffer decrypted_data;
const auto offset = (j * (buffer.size() / 2U));
EXPECT_TRUE(utils::encryption::decrypt_data(
token,
data_buffer(
std::next(buffer.begin(), static_cast<std::int64_t>(offset)),
std::next(buffer.begin(), static_cast<std::int64_t>(
offset + (buffer.size() / 2U)))),
decrypted_data));
EXPECT_EQ(utils::encryption::encrypting_reader::get_data_chunk_size(),
decrypted_data.size());
std::size_t bytes_read{};
data_buffer file_data(decrypted_data.size());
EXPECT_TRUE(source_file.read(
file_data,
(utils::encryption::encrypting_reader::get_data_chunk_size() * i) +
(j *
utils::encryption::encrypting_reader::get_data_chunk_size()),
&bytes_read));
EXPECT_EQ(0, std::memcmp(file_data.data(), decrypted_data.data(),
file_data.size()));
}
}
}
}
TEST(utils_encrypting_reader,
read_file_data_in_multiple_chunks_using_argon2id) {
utils::encryption::kdf_config cfg;
const auto token = std::string("moose");
auto &source_file = test::create_random_file(
8U * utils::encryption::encrypting_reader::get_data_chunk_size());
EXPECT_TRUE(source_file);
if (source_file) {
utils::encryption::encrypting_reader reader(
"test.dat", source_file.get_path(), get_stop_requested, token, cfg,
std::nullopt);
std::array<std::uint8_t, utils::encryption::kdf_config::size()> hdr;
EXPECT_EQ(hdr.size(), utils::encryption::encrypting_reader::reader_function(
reinterpret_cast<char *>(hdr.data()), hdr.size(),
1U, &reader));
EXPECT_TRUE(utils::encryption::kdf_config::from_header(hdr, cfg));
for (std::uint8_t i = 0U; i < 8U; i += 2U) {
data_buffer buffer(
utils::encryption::encrypting_reader::get_encrypted_chunk_size() *
2U);
EXPECT_EQ(buffer.size(),
utils::encryption::encrypting_reader::reader_function(
reinterpret_cast<char *>(buffer.data()), buffer.size(), 1U,
&reader));
for (std::uint8_t j = 0U; j < 2U; j++) {
data_buffer decrypted_data;
const auto offset = (j * (buffer.size() / 2U));
EXPECT_TRUE(utils::encryption::decrypt_data(
token, *reader.get_kdf_config_for_data(),
data_buffer(
std::next(buffer.begin(), static_cast<std::int64_t>(offset)),
std::next(buffer.begin(), static_cast<std::int64_t>(
offset + (buffer.size() / 2U)))),
decrypted_data));
EXPECT_EQ(utils::encryption::encrypting_reader::get_data_chunk_size(),
decrypted_data.size());
std::size_t bytes_read{};
data_buffer file_data(decrypted_data.size());
EXPECT_TRUE(source_file.read(
file_data,
(utils::encryption::encrypting_reader::get_data_chunk_size() * i) +
(j *
utils::encryption::encrypting_reader::get_data_chunk_size()),
&bytes_read));
EXPECT_EQ(0, std::memcmp(file_data.data(), decrypted_data.data(),
file_data.size()));
}
}
}
}
TEST(utils_encrypting_reader,
read_file_data_in_multiple_chunks_using_argon2id_master_key) {
const auto token = std::string("moose");
utils::encryption::kdf_config cfg;
auto master_key =
utils::encryption::generate_key<utils::hash::hash_256_t>(token, cfg);
auto &source_file = test::create_random_file(
8U * utils::encryption::encrypting_reader::get_data_chunk_size());
EXPECT_TRUE(source_file);
if (source_file) {
utils::encryption::encrypting_reader reader(
"test.dat", source_file.get_path(), get_stop_requested, master_key, cfg,
std::nullopt);
std::array<std::uint8_t, utils::encryption::kdf_config::size()> hdr;
EXPECT_EQ(hdr.size(), utils::encryption::encrypting_reader::reader_function(
reinterpret_cast<char *>(hdr.data()), hdr.size(),
1U, &reader));
EXPECT_TRUE(utils::encryption::kdf_config::from_header(hdr, cfg));
for (std::uint8_t i = 0U; i < 8U; i += 2U) {
data_buffer buffer(
utils::encryption::encrypting_reader::get_encrypted_chunk_size() *
2U);
EXPECT_EQ(buffer.size(),
utils::encryption::encrypting_reader::reader_function(
reinterpret_cast<char *>(buffer.data()), buffer.size(), 1U,
&reader));
auto data_cfg = *reader.get_kdf_config_for_data();
utils::hash::hash_256_t data_key;
std::tie(data_key, std::ignore) = cfg.create_subkey(
utils::encryption::kdf_context::data, data_cfg.unique_id, master_key);
for (std::uint8_t j = 0U; j < 2U; j++) {
data_buffer decrypted_data;
const auto offset = (j * (buffer.size() / 2U));
EXPECT_TRUE(utils::encryption::decrypt_data(
data_key,
data_buffer(
std::next(buffer.begin(), static_cast<std::int64_t>(offset)),
std::next(buffer.begin(), static_cast<std::int64_t>(
offset + (buffer.size() / 2U)))),
decrypted_data));
EXPECT_EQ(utils::encryption::encrypting_reader::get_data_chunk_size(),
decrypted_data.size());
std::size_t bytes_read{};
data_buffer file_data(decrypted_data.size());
EXPECT_TRUE(source_file.read(
file_data,
(utils::encryption::encrypting_reader::get_data_chunk_size() * i) +
(j *
utils::encryption::encrypting_reader::get_data_chunk_size()),
&bytes_read));
EXPECT_EQ(0, std::memcmp(file_data.data(), decrypted_data.data(),
file_data.size()));
}
}
}
}
TEST(utils_encrypting_reader, read_file_data_as_stream) {
const auto token = std::string("moose");
auto &source_file = test::create_random_file(
8U * utils::encryption::encrypting_reader::get_data_chunk_size());
EXPECT_TRUE(source_file);
if (source_file) {
utils::encryption::encrypting_reader reader(
"test.dat", source_file.get_path(), get_stop_requested, token,
std::nullopt);
auto io_stream = reader.create_iostream();
EXPECT_FALSE(io_stream->seekg(0, std::ios_base::end).fail());
EXPECT_TRUE(io_stream->good());
EXPECT_EQ(reader.get_total_size(),
static_cast<std::uint64_t>(io_stream->tellg()));
EXPECT_FALSE(io_stream->seekg(0, std::ios_base::beg).fail());
EXPECT_TRUE(io_stream->good());
for (std::uint8_t i = 0U; i < 8U; i++) {
data_buffer buffer(
utils::encryption::encrypting_reader::get_encrypted_chunk_size());
EXPECT_FALSE(
io_stream->seekg(static_cast<std::streamoff>(i * buffer.size()))
.fail());
EXPECT_TRUE(io_stream->good());
for (std::uint8_t j = 0U; j < 2U; j++) {
EXPECT_FALSE(
io_stream
->read(
reinterpret_cast<char *>(&buffer[(buffer.size() / 2U) * j]),
static_cast<std::streamsize>(buffer.size()) / 2U)
.fail());
EXPECT_TRUE(io_stream->good());
}
data_buffer decrypted_data;
EXPECT_TRUE(
utils::encryption::decrypt_data(token, buffer, decrypted_data));
EXPECT_EQ(utils::encryption::encrypting_reader::get_data_chunk_size(),
decrypted_data.size());
std::size_t bytes_read{};
data_buffer file_data(decrypted_data.size());
EXPECT_TRUE(source_file.read(
file_data,
utils::encryption::encrypting_reader::get_data_chunk_size() * i,
&bytes_read));
EXPECT_EQ(0, std::memcmp(file_data.data(), decrypted_data.data(),
file_data.size()));
}
}
}
TEST(utils_encrypting_reader, read_file_data_as_stream_using_argon2id) {
utils::encryption::kdf_config cfg;
const auto token = std::string("moose");
auto &source_file = test::create_random_file(
8U * utils::encryption::encrypting_reader::get_data_chunk_size());
EXPECT_TRUE(source_file);
if (source_file) {
utils::encryption::encrypting_reader reader(
"test.dat", source_file.get_path(), get_stop_requested, token, cfg,
std::nullopt);
auto io_stream = reader.create_iostream();
EXPECT_FALSE(io_stream->seekg(0, std::ios_base::end).fail());
EXPECT_TRUE(io_stream->good());
EXPECT_EQ(reader.get_total_size(),
static_cast<std::uint64_t>(io_stream->tellg()));
EXPECT_FALSE(io_stream->seekg(0, std::ios_base::beg).fail());
EXPECT_TRUE(io_stream->good());
for (std::uint8_t i = 0U; i < 8U; i++) {
data_buffer buffer(
utils::encryption::encrypting_reader::get_encrypted_chunk_size());
EXPECT_FALSE(
io_stream
->seekg(static_cast<std::streamoff>(
i * buffer.size() + utils::encryption::kdf_config::size()))
.fail());
EXPECT_TRUE(io_stream->good());
for (std::uint8_t j = 0U; j < 2U; j++) {
EXPECT_FALSE(
io_stream
->read(
reinterpret_cast<char *>(&buffer[(buffer.size() / 2U) * j]),
static_cast<std::streamsize>(buffer.size()) / 2U)
.fail());
EXPECT_TRUE(io_stream->good());
}
data_buffer decrypted_data;
EXPECT_TRUE(utils::encryption::decrypt_data(
token, *reader.get_kdf_config_for_data(), buffer, decrypted_data));
EXPECT_EQ(utils::encryption::encrypting_reader::get_data_chunk_size(),
decrypted_data.size());
std::size_t bytes_read{};
data_buffer file_data(decrypted_data.size());
EXPECT_TRUE(source_file.read(
file_data,
utils::encryption::encrypting_reader::get_data_chunk_size() * i,
&bytes_read));
EXPECT_EQ(0, std::memcmp(file_data.data(), decrypted_data.data(),
file_data.size()));
}
}
}
TEST(utils_encrypting_reader,
read_file_data_as_stream_using_argon2id_master_key) {
const auto token = std::string("moose");
utils::encryption::kdf_config cfg;
auto master_key =
utils::encryption::generate_key<utils::hash::hash_256_t>(token, cfg);
auto &source_file = test::create_random_file(
8U * utils::encryption::encrypting_reader::get_data_chunk_size());
EXPECT_TRUE(source_file);
if (source_file) {
utils::encryption::encrypting_reader reader(
"test.dat", source_file.get_path(), get_stop_requested, master_key, cfg,
std::nullopt);
auto io_stream = reader.create_iostream();
EXPECT_FALSE(io_stream->seekg(0, std::ios_base::end).fail());
EXPECT_TRUE(io_stream->good());
EXPECT_EQ(reader.get_total_size(),
static_cast<std::uint64_t>(io_stream->tellg()));
EXPECT_FALSE(io_stream->seekg(0, std::ios_base::beg).fail());
EXPECT_TRUE(io_stream->good());
for (std::uint8_t i = 0U; i < 8U; i++) {
data_buffer buffer(
utils::encryption::encrypting_reader::get_encrypted_chunk_size());
EXPECT_FALSE(
io_stream
->seekg(static_cast<std::streamoff>(
i * buffer.size() + utils::encryption::kdf_config::size()))
.fail());
EXPECT_TRUE(io_stream->good());
for (std::uint8_t j = 0U; j < 2U; j++) {
EXPECT_FALSE(
io_stream
->read(
reinterpret_cast<char *>(&buffer[(buffer.size() / 2U) * j]),
static_cast<std::streamsize>(buffer.size()) / 2U)
.fail());
EXPECT_TRUE(io_stream->good());
}
auto data_cfg = *reader.get_kdf_config_for_data();
utils::hash::hash_256_t data_key;
std::tie(data_key, std::ignore) = cfg.create_subkey(
utils::encryption::kdf_context::data, data_cfg.unique_id, master_key);
data_buffer decrypted_data;
EXPECT_TRUE(
utils::encryption::decrypt_data(data_key, buffer, decrypted_data));
EXPECT_EQ(utils::encryption::encrypting_reader::get_data_chunk_size(),
decrypted_data.size());
std::size_t bytes_read{};
data_buffer file_data(decrypted_data.size());
EXPECT_TRUE(source_file.read(
file_data,
utils::encryption::encrypting_reader::get_data_chunk_size() * i,
&bytes_read));
EXPECT_EQ(0, std::memcmp(file_data.data(), decrypted_data.data(),
file_data.size()));
}
}
}
TEST(utils_encrypting_reader, read_file_data_in_multiple_chunks_as_stream) {
const auto token = std::string("moose");
auto &source_file = test::create_random_file(
8u * utils::encryption::encrypting_reader::get_data_chunk_size());
EXPECT_TRUE(source_file);
if (source_file) {
utils::encryption::encrypting_reader reader(
"test.dat", source_file.get_path(), get_stop_requested, token,
std::nullopt);
auto io_stream = reader.create_iostream();
EXPECT_FALSE(io_stream->seekg(0, std::ios_base::end).fail());
EXPECT_TRUE(io_stream->good());
EXPECT_EQ(reader.get_total_size(),
static_cast<std::uint64_t>(io_stream->tellg()));
EXPECT_FALSE(io_stream->seekg(0, std::ios_base::beg).fail());
EXPECT_TRUE(io_stream->good());
for (std::uint8_t i = 0U; i < 8U; i += 2U) {
data_buffer buffer(
utils::encryption::encrypting_reader::get_encrypted_chunk_size() *
2U);
EXPECT_FALSE(io_stream
->read(reinterpret_cast<char *>(buffer.data()),
static_cast<std::streamsize>(buffer.size()))
.fail());
EXPECT_TRUE(io_stream->good());
for (std::uint8_t j = 0U; j < 2U; j++) {
data_buffer decrypted_data;
const auto offset = (j * (buffer.size() / 2U));
EXPECT_TRUE(utils::encryption::decrypt_data(
token,
data_buffer(
std::next(buffer.begin(), static_cast<std::int64_t>(offset)),
std::next(buffer.begin(), static_cast<std::int64_t>(
offset + (buffer.size() / 2U)))),
decrypted_data));
EXPECT_EQ(utils::encryption::encrypting_reader::get_data_chunk_size(),
decrypted_data.size());
std::size_t bytes_read{};
data_buffer file_data(decrypted_data.size());
EXPECT_TRUE(source_file.read(
file_data,
(utils::encryption::encrypting_reader::get_data_chunk_size() * i) +
(j *
utils::encryption::encrypting_reader::get_data_chunk_size()),
&bytes_read));
EXPECT_EQ(0, std::memcmp(file_data.data(), decrypted_data.data(),
file_data.size()));
}
}
}
}
TEST(utils_encrypting_reader,
read_file_data_in_multiple_chunks_as_stream_using_argon2id) {
utils::encryption::kdf_config cfg;
const auto token = std::string("moose");
auto &source_file = test::create_random_file(
8u * utils::encryption::encrypting_reader::get_data_chunk_size());
EXPECT_TRUE(source_file);
if (source_file) {
utils::encryption::encrypting_reader reader(
"test.dat", source_file.get_path(), get_stop_requested, token, cfg,
std::nullopt);
auto io_stream = reader.create_iostream();
EXPECT_FALSE(io_stream->seekg(0, std::ios_base::end).fail());
EXPECT_TRUE(io_stream->good());
EXPECT_EQ(reader.get_total_size(),
static_cast<std::uint64_t>(io_stream->tellg()));
EXPECT_FALSE(io_stream->seekg(0, std::ios_base::beg).fail());
EXPECT_TRUE(io_stream->good());
EXPECT_FALSE(io_stream
->seekg(static_cast<std::streamoff>(
utils::encryption::kdf_config::size()))
.fail());
for (std::uint8_t i = 0U; i < 8U; i += 2U) {
data_buffer buffer(
utils::encryption::encrypting_reader::get_encrypted_chunk_size() *
2U);
EXPECT_FALSE(io_stream
->read(reinterpret_cast<char *>(buffer.data()),
static_cast<std::streamsize>(buffer.size()))
.fail());
EXPECT_TRUE(io_stream->good());
for (std::uint8_t j = 0U; j < 2U; j++) {
data_buffer decrypted_data;
const auto offset = (j * (buffer.size() / 2U));
EXPECT_TRUE(utils::encryption::decrypt_data(
token, *reader.get_kdf_config_for_data(),
data_buffer(
std::next(buffer.begin(), static_cast<std::int64_t>(offset)),
std::next(buffer.begin(), static_cast<std::int64_t>(
offset + (buffer.size() / 2U)))),
decrypted_data));
EXPECT_EQ(utils::encryption::encrypting_reader::get_data_chunk_size(),
decrypted_data.size());
std::size_t bytes_read{};
data_buffer file_data(decrypted_data.size());
EXPECT_TRUE(source_file.read(
file_data,
(utils::encryption::encrypting_reader::get_data_chunk_size() * i) +
(j *
utils::encryption::encrypting_reader::get_data_chunk_size()),
&bytes_read));
EXPECT_EQ(0, std::memcmp(file_data.data(), decrypted_data.data(),
file_data.size()));
}
}
}
}
TEST(utils_encrypting_reader,
read_file_data_in_multiple_chunks_as_stream_using_argon2id_master_key) {
const auto token = std::string("moose");
utils::encryption::kdf_config cfg;
auto master_key =
utils::encryption::generate_key<utils::hash::hash_256_t>(token, cfg);
auto &source_file = test::create_random_file(
8u * utils::encryption::encrypting_reader::get_data_chunk_size());
EXPECT_TRUE(source_file);
if (source_file) {
utils::encryption::encrypting_reader reader(
"test.dat", source_file.get_path(), get_stop_requested, master_key, cfg,
std::nullopt);
auto io_stream = reader.create_iostream();
EXPECT_FALSE(io_stream->seekg(0, std::ios_base::end).fail());
EXPECT_TRUE(io_stream->good());
EXPECT_EQ(reader.get_total_size(),
static_cast<std::uint64_t>(io_stream->tellg()));
EXPECT_FALSE(io_stream->seekg(0, std::ios_base::beg).fail());
EXPECT_TRUE(io_stream->good());
EXPECT_FALSE(io_stream
->seekg(static_cast<std::streamoff>(
utils::encryption::kdf_config::size()))
.fail());
for (std::uint8_t i = 0U; i < 8U; i += 2U) {
data_buffer buffer(
utils::encryption::encrypting_reader::get_encrypted_chunk_size() *
2U);
EXPECT_FALSE(io_stream
->read(reinterpret_cast<char *>(buffer.data()),
static_cast<std::streamsize>(buffer.size()))
.fail());
EXPECT_TRUE(io_stream->good());
auto data_cfg = *reader.get_kdf_config_for_data();
utils::hash::hash_256_t data_key;
std::tie(data_key, std::ignore) = cfg.create_subkey(
utils::encryption::kdf_context::data, data_cfg.unique_id, master_key);
for (std::uint8_t j = 0U; j < 2U; j++) {
data_buffer decrypted_data;
const auto offset = (j * (buffer.size() / 2U));
EXPECT_TRUE(utils::encryption::decrypt_data(
data_key,
data_buffer(
std::next(buffer.begin(), static_cast<std::int64_t>(offset)),
std::next(buffer.begin(), static_cast<std::int64_t>(
offset + (buffer.size() / 2U)))),
decrypted_data));
EXPECT_EQ(utils::encryption::encrypting_reader::get_data_chunk_size(),
decrypted_data.size());
std::size_t bytes_read{};
data_buffer file_data(decrypted_data.size());
EXPECT_TRUE(source_file.read(
file_data,
(utils::encryption::encrypting_reader::get_data_chunk_size() * i) +
(j *
utils::encryption::encrypting_reader::get_data_chunk_size()),
&bytes_read));
EXPECT_EQ(0, std::memcmp(file_data.data(), decrypted_data.data(),
file_data.size()));
}
}
}
}
} // namespace fifthgrid
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)

View File

@@ -0,0 +1,402 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
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,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "test.hpp"
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
namespace fifthgrid {
TEST(utils_encryption_kdf_config, can_construct_using_default_constructor) {
utils::encryption::kdf_config cfg;
EXPECT_EQ(utils::encryption::kdf_version::v1, cfg.version);
EXPECT_EQ(utils::encryption::kdf_type::argon2id, cfg.kdf);
EXPECT_EQ(utils::encryption::memlimit_level::level3, cfg.memlimit);
EXPECT_EQ(utils::encryption::opslimit_level::level2, cfg.opslimit);
EXPECT_EQ(utils::encryption::kdf_config::salt_t{}, cfg.salt);
EXPECT_EQ(0U, cfg.unique_id);
EXPECT_EQ(0U, cfg.checksum);
}
TEST(utils_encryption_kdf_config, can_seal) {
utils::encryption::kdf_config cfg;
cfg.seal();
EXPECT_NE(utils::encryption::kdf_config::salt_t{}, cfg.salt);
auto orig_salt = cfg.salt;
cfg.seal();
EXPECT_NE(orig_salt, cfg.salt);
}
TEST(utils_encryption_kdf_config, can_generate_checksum) {
utils::encryption::kdf_config cfg;
EXPECT_EQ(13087047540462255120ULL, cfg.generate_checksum());
}
TEST(utils_encryption_kdf_config, seal_calculates_checksum) {
utils::encryption::kdf_config cfg;
cfg.seal();
EXPECT_NE(0U, cfg.checksum);
}
TEST(utils_encryption_kdf_config, can_create_header_and_restore) {
utils::encryption::kdf_config cfg;
cfg.unique_id = 2U;
cfg.seal();
auto hdr = cfg.to_header();
EXPECT_EQ(utils::encryption::kdf_config::size(), hdr.size());
utils::encryption::kdf_config restored_cfg;
EXPECT_TRUE(utils::encryption::kdf_config::from_header(hdr, restored_cfg));
auto restored_hdr = restored_cfg.to_header();
EXPECT_EQ(hdr, restored_hdr);
EXPECT_EQ(cfg.version, restored_cfg.version);
EXPECT_EQ(cfg.kdf, restored_cfg.kdf);
EXPECT_EQ(cfg.memlimit, restored_cfg.memlimit);
EXPECT_EQ(cfg.opslimit, restored_cfg.opslimit);
EXPECT_EQ(cfg.salt, restored_cfg.salt);
EXPECT_EQ(cfg.checksum, restored_cfg.checksum);
EXPECT_EQ(cfg.unique_id, restored_cfg.unique_id);
EXPECT_EQ(cfg, restored_cfg);
}
TEST(utils_encryption_kdf_config, header_restore_fails_if_version_is_invalid) {
utils::encryption::kdf_config cfg;
cfg.version = static_cast<utils::encryption::kdf_version>(0x11);
cfg.seal();
auto hdr = cfg.to_header();
utils::encryption::kdf_config restored_cfg;
EXPECT_FALSE(utils::encryption::kdf_config::from_header(hdr, restored_cfg));
}
TEST(utils_encryption_kdf_config, header_restore_fails_if_kdf_is_invalid) {
utils::encryption::kdf_config cfg;
cfg.kdf = static_cast<utils::encryption::kdf_type>(0x11);
cfg.seal();
auto hdr = cfg.to_header();
utils::encryption::kdf_config restored_cfg;
EXPECT_FALSE(utils::encryption::kdf_config::from_header(hdr, restored_cfg));
}
TEST(utils_encryption_kdf_config, header_restore_fails_if_memlimit_is_invalid) {
utils::encryption::kdf_config cfg;
cfg.memlimit = static_cast<utils::encryption::memlimit_level>(0x11);
cfg.seal();
auto hdr = cfg.to_header();
utils::encryption::kdf_config restored_cfg;
EXPECT_FALSE(utils::encryption::kdf_config::from_header(hdr, restored_cfg));
}
TEST(utils_encryption_kdf_config, header_restore_fails_if_opslimit_is_invalid) {
utils::encryption::kdf_config cfg;
cfg.opslimit = static_cast<utils::encryption::opslimit_level>(0x11);
cfg.seal();
auto hdr = cfg.to_header();
utils::encryption::kdf_config restored_cfg;
EXPECT_FALSE(utils::encryption::kdf_config::from_header(hdr, restored_cfg));
}
TEST(utils_encryption_kdf_config, header_restore_fails_if_salt_is_invalid) {
utils::encryption::kdf_config cfg;
cfg.seal();
cfg.salt = utils::encryption::kdf_config::salt_t{};
auto hdr = cfg.to_header();
utils::encryption::kdf_config restored_cfg;
EXPECT_FALSE(utils::encryption::kdf_config::from_header(hdr, restored_cfg));
}
TEST(utils_encryption_kdf_config, header_restore_fails_if_id_is_invalid) {
utils::encryption::kdf_config cfg;
cfg.seal();
cfg.unique_id = 22U;
auto hdr = cfg.to_header();
utils::encryption::kdf_config restored_cfg;
EXPECT_FALSE(utils::encryption::kdf_config::from_header(hdr, restored_cfg));
}
TEST(utils_encryption_kdf_config, create_subkey_sets_id_and_updates_checksum) {
using hash_t = utils::hash::hash_256_t;
utils::encryption::kdf_config cfg;
cfg.seal();
hash_t master_key =
utils::encryption::generate_key<hash_t>("root-master-key");
constexpr std::size_t sub_id = 42;
auto [subkey, out_cfg] = cfg.create_subkey<hash_t>(
utils::encryption::kdf_context::path, sub_id, master_key);
EXPECT_NE(subkey, hash_t{});
EXPECT_NE(subkey, master_key);
EXPECT_EQ(out_cfg.unique_id, static_cast<std::uint64_t>(sub_id));
EXPECT_EQ(out_cfg.checksum, out_cfg.generate_checksum());
EXPECT_EQ(out_cfg.version, cfg.version);
EXPECT_EQ(out_cfg.kdf, cfg.kdf);
EXPECT_EQ(out_cfg.memlimit, cfg.memlimit);
EXPECT_EQ(out_cfg.opslimit, cfg.opslimit);
EXPECT_EQ(out_cfg.salt, cfg.salt);
}
TEST(utils_encryption_kdf_config,
create_subkey_is_deterministic_for_same_inputs) {
using hash_t = utils::hash::hash_256_t;
utils::encryption::kdf_config cfg;
cfg.seal();
hash_t master_key =
utils::encryption::generate_key<hash_t>("root-master-key");
constexpr auto ctx = utils::encryption::kdf_context::data;
constexpr std::size_t sub_id = 7;
auto [k1, c1] = cfg.create_subkey<hash_t>(ctx, sub_id, master_key);
auto [k2, c2] = cfg.create_subkey<hash_t>(ctx, sub_id, master_key);
EXPECT_EQ(k1, k2);
EXPECT_EQ(c1.unique_id, c2.unique_id);
EXPECT_EQ(c1.checksum, c2.checksum);
EXPECT_EQ(c1, c2);
}
TEST(utils_encryption_kdf_config, create_subkey_varies_with_different_id) {
using hash_t = utils::hash::hash_256_t;
utils::encryption::kdf_config cfg;
cfg.seal();
hash_t master_key =
utils::encryption::generate_key<hash_t>("root-master-key");
constexpr auto ctx = utils::encryption::kdf_context::data;
auto [k1, c1] = cfg.create_subkey<hash_t>(ctx, 1, master_key);
auto [k2, c2] = cfg.create_subkey<hash_t>(ctx, 2, master_key);
EXPECT_NE(k1, k2);
EXPECT_NE(c1.unique_id, c2.unique_id);
EXPECT_NE(c1.checksum, c2.checksum);
EXPECT_EQ(c1.version, c2.version);
EXPECT_EQ(c1.kdf, c2.kdf);
EXPECT_EQ(c1.memlimit, c2.memlimit);
EXPECT_EQ(c1.opslimit, c2.opslimit);
EXPECT_EQ(c1.salt, c2.salt);
}
TEST(utils_encryption_kdf_config, create_subkey_varies_with_different_context) {
using hash_t = utils::hash::hash_256_t;
utils::encryption::kdf_config cfg;
cfg.seal();
hash_t master_key =
utils::encryption::generate_key<hash_t>("root-master-key");
constexpr std::size_t sub_id = 123;
auto [ka, ca] = cfg.create_subkey<hash_t>(
utils::encryption::kdf_context::data, sub_id, master_key);
auto [kb, cb] = cfg.create_subkey<hash_t>(
utils::encryption::kdf_context::path, sub_id, master_key);
EXPECT_NE(ka, kb);
EXPECT_EQ(ca.unique_id, cb.unique_id);
EXPECT_EQ(ca.checksum, cb.checksum);
EXPECT_EQ(ca, cb);
}
TEST(utils_encryption_kdf_config,
create_subkey_with_undefined_context_uses_fallback) {
using hash_t = utils::hash::hash_256_t;
utils::encryption::kdf_config cfg;
cfg.seal();
hash_t master_key =
utils::encryption::generate_key<hash_t>("root-master-key");
constexpr std::size_t sub_id = 55;
auto [k_def, c_def] = cfg.create_subkey<hash_t>(
utils::encryption::kdf_context::undefined, sub_id, master_key);
auto [k_dat, c_dat] = cfg.create_subkey<hash_t>(
utils::encryption::kdf_context::data, sub_id, master_key);
EXPECT_NE(k_def, hash_t{});
EXPECT_NE(k_dat, hash_t{});
EXPECT_NE(k_def, k_dat);
EXPECT_EQ(c_def, c_dat);
}
#if defined(PROJECT_ENABLE_JSON)
TEST(utils_encryption_kdf_config, can_convert_kdf_config_to_and_from_json) {
utils::encryption::kdf_config cfg;
cfg.unique_id = 2U;
cfg.seal();
nlohmann::json json_kdf(cfg);
auto cfg2 = json_kdf.get<utils::encryption::kdf_config>();
EXPECT_EQ(cfg, cfg2);
}
#endif // defined(PROJECT_ENABLE_JSON)
TEST(utils_encryption_kdf_config, equality) {
{
utils::encryption::kdf_config cfg;
utils::encryption::kdf_config cfg2;
EXPECT_EQ(cfg, cfg2);
}
{
utils::encryption::kdf_config cfg;
utils::encryption::kdf_config cfg2{cfg};
EXPECT_EQ(cfg, cfg2);
}
{
utils::encryption::kdf_config cfg;
cfg.seal();
utils::encryption::kdf_config cfg2{cfg};
EXPECT_EQ(cfg, cfg2);
}
{
utils::encryption::kdf_config cfg;
utils::encryption::kdf_config cfg2;
cfg2 = cfg;
EXPECT_EQ(cfg, cfg2);
}
{
utils::encryption::kdf_config cfg;
cfg.seal();
utils::encryption::kdf_config cfg2;
cfg2 = cfg;
EXPECT_EQ(cfg, cfg2);
}
}
TEST(utils_encryption_kdf_config, sealed_is_not_equal_to_unsealed) {
utils::encryption::kdf_config cfg;
cfg.seal();
utils::encryption::kdf_config cfg2;
EXPECT_NE(cfg, cfg2);
}
TEST(utils_encryption_kdf_config, sealed_is_not_equal_to_sealed) {
utils::encryption::kdf_config cfg;
cfg.seal();
utils::encryption::kdf_config cfg2;
cfg2.seal();
EXPECT_NE(cfg, cfg2);
}
TEST(utils_encryption_kdf_config, is_not_equal_to_different_id) {
utils::encryption::kdf_config cfg;
cfg.unique_id = 2UL;
utils::encryption::kdf_config cfg2;
EXPECT_NE(cfg, cfg2);
}
TEST(utils_encryption_kdf_config, is_not_equal_to_different_version) {
utils::encryption::kdf_config cfg;
cfg.version = static_cast<utils::encryption::kdf_version>(0x11);
utils::encryption::kdf_config cfg2;
EXPECT_NE(cfg, cfg2);
}
TEST(utils_encryption_kdf_config, is_not_equal_to_different_kdf) {
utils::encryption::kdf_config cfg;
cfg.kdf = static_cast<utils::encryption::kdf_type>(0x11);
utils::encryption::kdf_config cfg2;
EXPECT_NE(cfg, cfg2);
}
TEST(utils_encryption_kdf_config, is_not_equal_to_different_memlimit) {
utils::encryption::kdf_config cfg;
cfg.memlimit = static_cast<utils::encryption::memlimit_level>(0x11);
utils::encryption::kdf_config cfg2;
EXPECT_NE(cfg, cfg2);
}
TEST(utils_encryption_kdf_config, is_not_equal_to_different_opslimit) {
utils::encryption::kdf_config cfg;
cfg.opslimit = static_cast<utils::encryption::opslimit_level>(0x11);
utils::encryption::kdf_config cfg2;
EXPECT_NE(cfg, cfg2);
}
TEST(utils_encryption_kdf_config, is_not_equal_to_different_salt) {
utils::encryption::kdf_config cfg;
cfg.salt[0U] = 1U;
utils::encryption::kdf_config cfg2;
EXPECT_NE(cfg, cfg2);
}
TEST(utils_encryption_kdf_config, is_not_equal_to_different_checksum) {
utils::encryption::kdf_config cfg;
cfg.checksum = 2U;
utils::encryption::kdf_config cfg2;
EXPECT_NE(cfg, cfg2);
}
} // namespace fifthgrid
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)

View File

@@ -0,0 +1,450 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
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,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "test.hpp"
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
namespace {
[[nodiscard]] auto make_random_plain(std::size_t size)
-> std::vector<unsigned char> {
std::vector<unsigned char> ret;
ret.resize(size);
constexpr std::size_t chunk_size = 4096U;
using buf_t = std::array<unsigned char, chunk_size>;
std::size_t written = 0U;
while (written < size) {
auto block = fifthgrid::utils::generate_secure_random<buf_t>();
auto to_copy = std::min<std::size_t>(chunk_size, size - written);
std::memcpy(ret.data() + written, block.data(), to_copy);
written += to_copy;
}
return ret;
}
[[nodiscard]] auto
build_encrypted_blob(const std::vector<unsigned char> &plain,
const fifthgrid::utils::hash::hash_256_t &key,
bool with_kdf,
fifthgrid::utils::encryption::kdf_config &kdf)
-> std::pair<fifthgrid::data_buffer, std::uint64_t> {
fifthgrid::data_buffer blob;
if (with_kdf) {
auto hdr = kdf.to_header();
blob.insert(blob.end(), hdr.begin(), hdr.end());
}
auto data_chunk =
fifthgrid::utils::encryption::encrypting_reader::get_data_chunk_size();
std::size_t offset = 0U;
while (offset < plain.size()) {
auto take = std::min<std::size_t>(data_chunk, plain.size() - offset);
fifthgrid::data_buffer buffer;
fifthgrid::utils::encryption::encrypt_data(key, plain.data() + offset, take,
buffer);
blob.insert(blob.end(), buffer.begin(), buffer.end());
offset += take;
}
return {std::move(blob), static_cast<std::uint64_t>(plain.size())};
}
[[nodiscard]] auto make_reader(const fifthgrid::data_buffer &cipher_blob)
-> fifthgrid::utils::encryption::reader_func_t {
return [&cipher_blob](fifthgrid::data_buffer &out, std::uint64_t start,
std::uint64_t end) -> bool {
if (end < start) {
return false;
}
if (end >= static_cast<std::uint64_t>(cipher_blob.size())) {
return false;
}
auto len = static_cast<std::size_t>(end - start + 1U);
out.assign(
std::next(cipher_blob.begin(), static_cast<std::ptrdiff_t>(start)),
std::next(cipher_blob.begin(),
static_cast<std::ptrdiff_t>(start + len)));
return true;
};
}
} // namespace
namespace fifthgrid {
class utils_encryption_read_encrypted_range_fixture
: public ::testing::Test,
public ::testing::WithParamInterface<bool> {
protected:
bool uses_kdf{};
utils::hash::hash_256_t key{};
utils::encryption::kdf_config kdf{};
std::size_t chunk{};
std::size_t plain_sz{};
std::vector<unsigned char> plain;
data_buffer cipher_blob;
std::uint64_t total_size{};
utils::encryption::reader_func_t reader;
void SetUp() override {
uses_kdf = GetParam();
key =
uses_kdf
? utils::encryption::generate_key<utils::hash::hash_256_t>("moose",
kdf)
: utils::encryption::generate_key<utils::hash::hash_256_t>("moose");
chunk = utils::encryption::encrypting_reader::get_data_chunk_size();
plain_sz = (2U * chunk) + (chunk / 2U);
plain = make_random_plain(plain_sz);
std::tie(cipher_blob, total_size) =
build_encrypted_blob(plain, key, uses_kdf, kdf);
reader = make_reader(cipher_blob);
}
};
TEST_P(utils_encryption_read_encrypted_range_fixture,
within_chunk_data_buffer) {
std::uint64_t end_cap = chunk ? static_cast<std::uint64_t>(chunk) - 1U : 0U;
std::uint64_t begin = 123U;
std::uint64_t end = 4567U;
if (end > end_cap) {
end = end_cap;
}
if (end < begin) {
begin = end;
}
ASSERT_GE(end, begin);
ASSERT_LT(end, plain_sz);
http_range range{begin, end};
data_buffer out;
ASSERT_TRUE(utils::encryption::read_encrypted_range(range, key, uses_kdf,
reader, total_size, out));
std::vector<unsigned char> want(
plain.begin() + static_cast<std::ptrdiff_t>(begin),
plain.begin() + static_cast<std::ptrdiff_t>(end) + 1U);
EXPECT_EQ(out, want);
}
TEST_P(utils_encryption_read_encrypted_range_fixture,
cross_chunk_boundary_data_buffer) {
std::uint64_t begin = static_cast<std::uint64_t>(chunk) - 512U;
std::uint64_t end = begin + 1024U - 1U;
ASSERT_GE(end, begin);
ASSERT_LT(end, plain_sz);
http_range range{begin, end};
data_buffer out;
ASSERT_TRUE(utils::encryption::read_encrypted_range(range, key, uses_kdf,
reader, total_size, out));
std::vector<unsigned char> want(
plain.begin() + static_cast<std::ptrdiff_t>(begin),
plain.begin() + static_cast<std::ptrdiff_t>(end) + 1U);
EXPECT_EQ(out, want);
}
TEST_P(utils_encryption_read_encrypted_range_fixture,
multi_chunk_span_data_buffer) {
std::uint64_t begin = static_cast<std::uint64_t>(chunk) - 10U;
std::uint64_t end = static_cast<std::uint64_t>(2U * chunk) + 19U;
ASSERT_GE(end, begin);
ASSERT_LT(end, plain_sz);
http_range range{begin, end};
data_buffer out;
ASSERT_TRUE(utils::encryption::read_encrypted_range(range, key, uses_kdf,
reader, total_size, out));
std::vector<unsigned char> want(
plain.begin() + static_cast<std::ptrdiff_t>(begin),
plain.begin() + static_cast<std::ptrdiff_t>(end) + 1U);
EXPECT_EQ(out, want);
}
TEST_P(utils_encryption_read_encrypted_range_fixture,
tail_of_file_data_buffer) {
std::uint64_t begin = static_cast<std::uint64_t>(plain_sz) - 200U;
std::uint64_t end = static_cast<std::uint64_t>(plain_sz) - 1U;
ASSERT_GE(end, begin);
http_range range{begin, end};
data_buffer out;
ASSERT_TRUE(utils::encryption::read_encrypted_range(range, key, uses_kdf,
reader, total_size, out));
std::vector<unsigned char> want(
plain.begin() + static_cast<std::ptrdiff_t>(begin),
plain.begin() + static_cast<std::ptrdiff_t>(end) + 1U);
EXPECT_EQ(out, want);
}
TEST_P(utils_encryption_read_encrypted_range_fixture, whole_file_data_buffer) {
std::uint64_t begin = 0U;
std::uint64_t end = static_cast<std::uint64_t>(plain_sz - 1U);
ASSERT_GE(end, begin);
http_range range{begin, end};
data_buffer out;
ASSERT_TRUE(utils::encryption::read_encrypted_range(range, key, uses_kdf,
reader, total_size, out));
EXPECT_EQ(out, plain);
}
TEST_P(utils_encryption_read_encrypted_range_fixture,
pointer_sink_cross_chunk_with_array) {
std::uint64_t begin = static_cast<std::uint64_t>(chunk) - 256U;
constexpr std::size_t data_len = 2048U;
std::uint64_t end = begin + data_len - 1U;
ASSERT_GE(end, begin);
ASSERT_LT(end, plain_sz);
http_range range{begin, end};
std::array<unsigned char, data_len> sink{};
std::size_t bytes_read = 0U;
ASSERT_TRUE(utils::encryption::read_encrypted_range(
range, key, uses_kdf, reader, total_size, sink.data(), sink.size(),
bytes_read));
EXPECT_EQ(bytes_read, sink.size());
std::vector<unsigned char> want(
plain.begin() + static_cast<std::ptrdiff_t>(begin),
plain.begin() + static_cast<std::ptrdiff_t>(end) + 1U);
EXPECT_TRUE(std::equal(sink.begin(), sink.end(), want.begin(), want.end()));
}
TEST_P(utils_encryption_read_encrypted_range_fixture,
reader_failure_for_both_overloads) {
std::size_t call_count = 0U;
auto flaky_reader = [this, &call_count](data_buffer &out, std::uint64_t start,
std::uint64_t end) -> bool {
if (++call_count == 1U) {
return false;
}
auto len = static_cast<std::size_t>(end - start + 1U);
out.assign(
std::next(cipher_blob.begin(), static_cast<std::ptrdiff_t>(start)),
std::next(cipher_blob.begin(),
static_cast<std::ptrdiff_t>(start + len)));
return true;
};
std::uint64_t begin = 0U;
constexpr std::size_t data_len = 1024U;
std::uint64_t end = begin + data_len - 1U;
http_range range{begin, end};
{
data_buffer out;
EXPECT_FALSE(utils::encryption::read_encrypted_range(
range, key, uses_kdf, flaky_reader, total_size, out));
EXPECT_TRUE(out.empty());
}
call_count = 0U;
{
std::array<unsigned char, data_len> buf{};
std::size_t bytes_read = 0U;
EXPECT_FALSE(utils::encryption::read_encrypted_range(
range, key, uses_kdf, flaky_reader, total_size, buf.data(), buf.size(),
bytes_read));
EXPECT_EQ(bytes_read, 0U);
}
}
TEST_P(utils_encryption_read_encrypted_range_fixture,
invalid_range_end_before_begin) {
std::uint64_t begin = 100U;
std::uint64_t end = 99U;
http_range range{begin, end};
{
data_buffer out;
EXPECT_TRUE(utils::encryption::read_encrypted_range(
range, key, uses_kdf, reader, total_size, out));
EXPECT_TRUE(out.empty());
}
{
std::array<unsigned char, 16U> buf{};
std::size_t bytes_read = 0U;
EXPECT_TRUE(utils::encryption::read_encrypted_range(
range, key, uses_kdf, reader, total_size, buf.data(), buf.size(),
bytes_read));
EXPECT_EQ(bytes_read, 0U);
}
}
TEST_P(utils_encryption_read_encrypted_range_fixture, single_byte_read) {
std::uint64_t pos = 777U;
if (pos >= plain_sz) {
pos = plain_sz ? static_cast<std::uint64_t>(plain_sz) - 1U : 0U;
}
http_range range{pos, pos};
data_buffer out;
ASSERT_TRUE(utils::encryption::read_encrypted_range(range, key, uses_kdf,
reader, total_size, out));
ASSERT_EQ(out.size(), 1U);
EXPECT_EQ(out[0], plain[pos]);
std::array<unsigned char, 1U> buf{};
std::size_t bytes_read = 0U;
ASSERT_TRUE(utils::encryption::read_encrypted_range(
range, key, uses_kdf, reader, total_size, buf.data(), buf.size(),
bytes_read));
EXPECT_EQ(bytes_read, 1U);
EXPECT_EQ(buf[0], plain[pos]);
}
TEST_P(utils_encryption_read_encrypted_range_fixture,
begin_at_exact_chunk_boundary) {
auto begin = static_cast<std::uint64_t>(chunk);
std::uint64_t end = begin + 1024U - 1U;
if (end >= plain_sz)
end = static_cast<std::uint64_t>(plain_sz) - 1U;
ASSERT_GE(end, begin);
http_range range{begin, end};
data_buffer out;
ASSERT_TRUE(utils::encryption::read_encrypted_range(range, key, uses_kdf,
reader, total_size, out));
std::vector<unsigned char> want(
plain.begin() + static_cast<std::ptrdiff_t>(begin),
plain.begin() + static_cast<std::ptrdiff_t>(end) + 1U);
EXPECT_EQ(out, want);
}
TEST_P(utils_encryption_read_encrypted_range_fixture, last_byte_only) {
std::uint64_t pos = plain_sz ? static_cast<std::uint64_t>(plain_sz) - 1U : 0U;
http_range range{pos, pos};
data_buffer out;
ASSERT_TRUE(utils::encryption::read_encrypted_range(range, key, uses_kdf,
reader, total_size, out));
ASSERT_EQ(out.size(), 1U);
EXPECT_EQ(out[0], plain[pos]);
}
TEST_P(utils_encryption_read_encrypted_range_fixture, tiny_file_whole_read) {
plain = make_random_plain(37U);
std::tie(cipher_blob, total_size) =
build_encrypted_blob(plain, key, uses_kdf, kdf);
reader = make_reader(cipher_blob);
http_range range{0U, static_cast<std::uint64_t>(plain.size() - 1U)};
data_buffer out;
ASSERT_TRUE(utils::encryption::read_encrypted_range(range, key, uses_kdf,
reader, total_size, out));
EXPECT_EQ(out, plain);
}
TEST_P(utils_encryption_read_encrypted_range_fixture,
pointer_sink_exact_small_window) {
std::uint64_t begin = 5U;
std::uint64_t end = begin + 7U;
ASSERT_GE(end, begin);
ASSERT_LT(end, plain_sz);
http_range range{begin, end};
std::array<unsigned char, 8U> sink{};
std::size_t bytes_read = 0U;
ASSERT_TRUE(utils::encryption::read_encrypted_range(
range, key, uses_kdf, reader, total_size, sink.data(), sink.size(),
bytes_read));
EXPECT_EQ(bytes_read, sink.size());
EXPECT_TRUE(std::equal(sink.begin(), sink.end(),
plain.begin() + static_cast<std::ptrdiff_t>(begin)));
}
TEST_P(utils_encryption_read_encrypted_range_fixture,
range_past_eof_truncates) {
std::uint64_t begin = static_cast<std::uint64_t>(plain_sz) - 10U;
std::uint64_t end = static_cast<std::uint64_t>(plain_sz);
http_range range{begin, end};
data_buffer out;
ASSERT_TRUE(utils::encryption::read_encrypted_range(range, key, uses_kdf,
reader, total_size, out));
std::size_t expected_len =
static_cast<std::size_t>(static_cast<std::uint64_t>(plain_sz) - begin);
std::vector<unsigned char> want(
plain.begin() + static_cast<std::ptrdiff_t>(begin),
plain.begin() + static_cast<std::ptrdiff_t>(plain_sz));
ASSERT_EQ(out.size(), expected_len);
EXPECT_EQ(out, want);
std::array<unsigned char, 32U> buf{};
std::size_t bytes_read = 0U;
ASSERT_TRUE(utils::encryption::read_encrypted_range(
range, key, uses_kdf, reader, total_size, buf.data(), buf.size(),
bytes_read));
EXPECT_EQ(bytes_read, std::min<std::size_t>(buf.size(), expected_len));
EXPECT_TRUE(std::equal(buf.begin(), buf.begin() + bytes_read,
plain.begin() + static_cast<std::ptrdiff_t>(begin)));
}
TEST_P(utils_encryption_read_encrypted_range_fixture,
pointer_sink_larger_buffer) {
std::uint64_t begin = 42U;
std::uint64_t end = begin + 63U;
ASSERT_GE(end, begin);
ASSERT_LT(end, plain_sz);
http_range range{begin, end};
std::array<unsigned char, 128U> buf{};
std::size_t bytes_read = 0U;
ASSERT_TRUE(utils::encryption::read_encrypted_range(
range, key, uses_kdf, reader, total_size, buf.data(), buf.size(),
bytes_read));
EXPECT_EQ(bytes_read, 64U);
EXPECT_TRUE(std::equal(buf.begin(), buf.begin() + 64U,
plain.begin() + static_cast<std::ptrdiff_t>(begin)));
}
INSTANTIATE_TEST_SUITE_P(no_kdf_and_kdf,
utils_encryption_read_encrypted_range_fixture,
::testing::Values(false, true));
} // namespace fifthgrid
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)

View File

@@ -0,0 +1,385 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
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,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "test.hpp"
#if defined(PROJECT_ENABLE_LIBSODIUM)
namespace {
#if defined(PROJECT_ENABLE_BOOST)
const std::string buffer = "cow moose dog chicken";
#endif // defined(PROJECT_ENABLE_BOOST)
const auto get_stop_requested = []() -> bool { return false; };
} // namespace
namespace fifthgrid {
static constexpr std::string_view token{"moose"};
static constexpr std::wstring_view token_w{L"moose"};
TEST(utils_encryption, generate_key) {
auto key1 = utils::encryption::generate_key<utils::hash::hash_256_t>(token);
EXPECT_STREQ(
"ab4a0b004e824962913f7c0f79582b6ec7a3b8726426ca61d1a0a28ce5049e96",
utils::collection::to_hex_string(key1).c_str());
auto key2 = utils::encryption::generate_key<utils::hash::hash_256_t>("moose");
auto key3 = utils::encryption::generate_key<utils::hash::hash_256_t>("moose");
EXPECT_EQ(key2, key3);
auto key4 =
utils::encryption::generate_key<utils::hash::hash_256_t>("moose2");
EXPECT_NE(key2, key4);
auto key1_w =
utils::encryption::generate_key<utils::hash::hash_256_t>(token_w);
EXPECT_NE(key1, key1_w);
#if defined(_WIN32)
EXPECT_STREQ(
L"4f5eb2a2ab34e3777b230465283923080b9ba59311e74058ccd74185131d11fe",
utils::collection::to_hex_wstring(key1_w).c_str());
#else // !defined(_WIN32)
EXPECT_STREQ(
L"0392d95ed3eee9772fbb9af68fedf829a8eb0adbe8575d9691cc9a752196766a",
utils::collection::to_hex_wstring(key1_w).c_str());
#endif
auto key2_w =
utils::encryption::generate_key<utils::hash::hash_256_t>(L"moose");
auto key3_w =
utils::encryption::generate_key<utils::hash::hash_256_t>(L"moose");
EXPECT_EQ(key2_w, key3_w);
EXPECT_NE(key2_w, key2);
EXPECT_NE(key3_w, key3);
auto key4_w =
utils::encryption::generate_key<utils::hash::hash_256_t>(L"moose2");
EXPECT_NE(key2_w, key4_w);
EXPECT_NE(key4_w, key4);
}
TEST(utils_encryption, generate_key_default_hasher_is_blake2b_256) {
auto key1 = utils::encryption::generate_key<utils::hash::hash_256_t>(token);
auto key2 = utils::encryption::generate_key<utils::hash::hash_256_t>(
token, [](auto &&data, auto &&size) -> auto {
return utils::hash::create_hash_blake2b_256(
std::string_view(reinterpret_cast<const char *>(data), size));
});
EXPECT_EQ(key1, key2);
auto key1_w =
utils::encryption::generate_key<utils::hash::hash_256_t>(token_w);
auto key2_w = utils::encryption::generate_key<utils::hash::hash_256_t>(
token_w, [](auto &&data, auto &&size) -> auto {
return utils::hash::create_hash_blake2b_256(std::wstring_view(
reinterpret_cast<const wchar_t *>(data), size / sizeof(wchar_t)));
});
EXPECT_EQ(key1_w, key2_w);
EXPECT_NE(key1_w, key1);
EXPECT_NE(key2_w, key2);
}
TEST(utils_encryption, generate_key_with_hasher) {
auto key1 = utils::encryption::generate_key<utils::hash::hash_256_t>(
token, utils::hash::blake2b_256_hasher);
EXPECT_STREQ(
"ab4a0b004e824962913f7c0f79582b6ec7a3b8726426ca61d1a0a28ce5049e96",
utils::collection::to_hex_string(key1).c_str());
auto key2 = utils::encryption::generate_key<utils::hash::hash_256_t>(
token, utils::hash::sha256_hasher);
EXPECT_NE(key1, key2);
EXPECT_STREQ(
"182072537ada59e4d6b18034a80302ebae935f66adbdf0f271d3d36309c2d481",
utils::collection::to_hex_string(key2).c_str());
auto key1_w = utils::encryption::generate_key<utils::hash::hash_256_t>(
token_w, utils::hash::blake2b_256_hasher);
#if defined(_WIN32)
EXPECT_STREQ(
L"4f5eb2a2ab34e3777b230465283923080b9ba59311e74058ccd74185131d11fe",
utils::collection::to_hex_wstring(key1_w).c_str());
#else // !defined(_WIN32)
EXPECT_STREQ(
L"0392d95ed3eee9772fbb9af68fedf829a8eb0adbe8575d9691cc9a752196766a",
utils::collection::to_hex_wstring(key1_w).c_str());
#endif
auto key2_w = utils::encryption::generate_key<utils::hash::hash_256_t>(
token_w, utils::hash::sha256_hasher);
EXPECT_NE(key1_w, key2_w);
#if defined(_WIN32)
EXPECT_STREQ(
L"918e4c6d39bb373f139b5fac8ec0548a9770da399b2835608974ffeac7fab6c4",
utils::collection::to_hex_wstring(key2_w).c_str());
#else // !defined(_WIN32)
EXPECT_STREQ(
L"590ac70125bec4501172937f6a2cbdeb22a87b5e40d5595eccd06b2b20548d8f",
utils::collection::to_hex_wstring(key2_w).c_str());
#endif
EXPECT_NE(key1_w, key1);
EXPECT_NE(key2_w, key2);
}
#if defined(PROJECT_ENABLE_BOOST)
TEST(utils_encryption, generate_argon2id_key) {
utils::encryption::kdf_config cfg;
{
auto key1 =
utils::encryption::generate_key<utils::hash::hash_256_t>(token, cfg);
auto key2 =
utils::encryption::generate_key<utils::hash::hash_256_t>(token, cfg);
EXPECT_NE(key1, key2);
auto key3 =
utils::encryption::generate_key<utils::hash::hash_256_t>(token, cfg);
EXPECT_NE(key3, key1);
auto key4 =
utils::encryption::generate_key<utils::hash::hash_256_t>(token, cfg);
EXPECT_NE(key4, key2);
EXPECT_NE(key3, key4);
}
{
auto key1 =
utils::encryption::generate_key<utils::hash::hash_256_t>(token_w, cfg);
auto key2 =
utils::encryption::generate_key<utils::hash::hash_256_t>(token_w, cfg);
EXPECT_NE(key1, key2);
auto key3 =
utils::encryption::generate_key<utils::hash::hash_256_t>(token_w, cfg);
EXPECT_NE(key3, key1);
auto key4 =
utils::encryption::generate_key<utils::hash::hash_256_t>(token_w, cfg);
EXPECT_NE(key4, key2);
EXPECT_NE(key3, key4);
}
}
TEST(utils_encryption, recreate_argon2id_key) {
utils::encryption::kdf_config cfg;
{
auto key1 =
utils::encryption::generate_key<utils::hash::hash_256_t>(token, cfg);
auto key2 =
utils::encryption::recreate_key<utils::hash::hash_256_t>(token, cfg);
EXPECT_EQ(key1, key2);
}
{
auto key1 =
utils::encryption::generate_key<utils::hash::hash_256_t>(token_w, cfg);
auto key2 =
utils::encryption::recreate_key<utils::hash::hash_256_t>(token_w, cfg);
EXPECT_EQ(key1, key2);
}
}
static void test_encrypted_result(const data_buffer &result) {
EXPECT_EQ(buffer.size() + utils::encryption::encryption_header_size,
result.size());
std::string data;
EXPECT_TRUE(utils::encryption::decrypt_data(token, result, data));
EXPECT_EQ(buffer.size(), data.size());
EXPECT_STREQ(buffer.c_str(), data.c_str());
}
static void
test_encrypted_result_using_argon2id(const data_buffer &result,
const utils::encryption::kdf_config &cfg) {
EXPECT_EQ(buffer.size() + utils::encryption::encryption_header_size,
result.size());
std::string data;
EXPECT_TRUE(utils::encryption::decrypt_data(token, cfg, result, data));
EXPECT_EQ(buffer.size(), data.size());
EXPECT_STREQ(buffer.c_str(), data.c_str());
}
TEST(utils_encryption, encrypt_data_buffer) {
data_buffer result;
utils::encryption::encrypt_data(token, buffer, result);
test_encrypted_result(result);
}
TEST(utils_encryption, encrypt_data_buffer_with_key) {
const auto key =
utils::encryption::generate_key<utils::hash::hash_256_t>(token);
data_buffer result;
utils::encryption::encrypt_data(key, buffer, result);
test_encrypted_result(result);
}
TEST(utils_encryption, encrypt_data_pointer) {
data_buffer result;
utils::encryption::encrypt_data(
token, reinterpret_cast<const unsigned char *>(buffer.data()),
buffer.size(), result);
test_encrypted_result(result);
}
TEST(utils_encryption, encrypt_data_pointer_with_key) {
const auto key =
utils::encryption::generate_key<utils::hash::hash_256_t>(token);
data_buffer result;
utils::encryption::encrypt_data(
key, reinterpret_cast<const unsigned char *>(buffer.data()),
buffer.size(), result);
test_encrypted_result(result);
}
TEST(utils_encryption, decrypt_data_pointer) {
const auto key =
utils::encryption::generate_key<utils::hash::hash_256_t>(token);
data_buffer result;
utils::encryption::encrypt_data(
key, reinterpret_cast<const unsigned char *>(buffer.data()),
buffer.size(), result);
std::string data;
EXPECT_TRUE(utils::encryption::decrypt_data(token, result.data(),
result.size(), data));
EXPECT_EQ(buffer.size(), data.size());
EXPECT_STREQ(buffer.c_str(), data.c_str());
}
TEST(utils_encryption, decrypt_data_buffer_with_key) {
const auto key =
utils::encryption::generate_key<utils::hash::hash_256_t>(token);
data_buffer result;
utils::encryption::encrypt_data(
key, reinterpret_cast<const unsigned char *>(buffer.data()),
buffer.size(), result);
std::string data;
EXPECT_TRUE(utils::encryption::decrypt_data(key, result, data));
EXPECT_EQ(buffer.size(), data.size());
EXPECT_STREQ(buffer.c_str(), data.c_str());
}
TEST(utils_encryption, decrypt_data_pointer_with_key) {
const auto key =
utils::encryption::generate_key<utils::hash::hash_256_t>(token);
data_buffer result;
utils::encryption::encrypt_data(
key, reinterpret_cast<const unsigned char *>(buffer.data()),
buffer.size(), result);
std::string data;
EXPECT_TRUE(
utils::encryption::decrypt_data(key, result.data(), result.size(), data));
EXPECT_EQ(buffer.size(), data.size());
EXPECT_STREQ(buffer.c_str(), data.c_str());
}
TEST(utils_encryption, decryption_failure) {
const auto key =
utils::encryption::generate_key<utils::hash::hash_256_t>(token);
data_buffer result;
utils::encryption::encrypt_data(
key, reinterpret_cast<const unsigned char *>(buffer.data()),
buffer.size(), result);
result[0U] = 0U;
result[1U] = 1U;
result[2U] = 2U;
std::string data;
EXPECT_FALSE(utils::encryption::decrypt_data(key, result, data));
}
TEST(utils_encryption, decrypt_file_name) {
auto &source_file = test::create_random_file(
8U * utils::encryption::encrypting_reader::get_data_chunk_size());
EXPECT_TRUE(source_file);
if (source_file) {
utils::encryption::encrypting_reader reader(
"test.dat", source_file.get_path(), get_stop_requested, token,
std::nullopt);
auto file_name = reader.get_encrypted_file_name();
EXPECT_EQ(true, utils::encryption::decrypt_file_name(token, file_name));
EXPECT_STREQ("test.dat", file_name.c_str());
}
}
TEST(utils_encryption, decrypt_file_path) {
auto &source_file = test::create_random_file(
8U * utils::encryption::encrypting_reader::get_data_chunk_size());
EXPECT_TRUE(source_file);
if (source_file) {
utils::encryption::encrypting_reader reader(
"test.dat", source_file.get_path(), get_stop_requested, token,
"moose/cow");
auto file_path = reader.get_encrypted_file_path();
EXPECT_EQ(true, utils::encryption::decrypt_file_path(token, file_path));
EXPECT_STREQ("/moose/cow/test.dat", file_path.c_str());
}
}
TEST(utils_encryption, encrypt_data_buffer_using_argon2id) {
utils::encryption::kdf_config cfg;
data_buffer result;
utils::encryption::encrypt_data(token, cfg, buffer, result);
test_encrypted_result_using_argon2id(result, cfg);
}
TEST(utils_encryption, encrypt_data_pointer_using_argon2id) {
utils::encryption::kdf_config cfg;
data_buffer result;
utils::encryption::encrypt_data(
token, cfg, reinterpret_cast<const unsigned char *>(buffer.data()),
buffer.size(), result);
test_encrypted_result_using_argon2id(result, cfg);
}
// TEST(utils_encryption, decrypt_file_name_using_argon2id) {}
// TEST(utils_encryption, decrypt_file_path_using_argon2id) {}
//
// TEST(utils_encryption, decrypt_file_name_using_argon2id_master_key) {}
// TEST(utils_encryption, decrypt_file_path_using_argon2id_master_key) {}
#endif // defined(PROJECT_ENABLE_BOOST)
} // namespace fifthgrid
#endif // defined(PROJECT_ENABLE_LIBSODIUM)

View File

@@ -0,0 +1,116 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
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,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "test.hpp"
namespace fifthgrid {
template <typename T, typename U>
constexpr bool is_decay_equ = std::is_same_v<std::decay_t<T>, U>;
TEST(utils_error, check_default_exception_handler) {
EXPECT_TRUE(utils::error::get_exception_handler() != nullptr);
if (&utils::error::default_exception_handler ==
utils::error::get_exception_handler()) {
#if defined(PROJECT_ENABLE_SPDLOG) && defined(PROJECT_ENABLE_V2_ERRORS)
auto default_handler_is_spdlog =
is_decay_equ<decltype(utils::error::default_exception_handler),
utils::error::spdlog_exception_handler>;
EXPECT_TRUE(default_handler_is_spdlog);
#else // !defined(PROJECT_ENABLE_SPDLOG) || !defined(PROJECT_ENABLE_V2_ERRORS)
auto default_handler_is_iostream =
is_decay_equ<decltype(utils::error::default_exception_handler),
utils::error::iostream_exception_handler>;
EXPECT_TRUE(default_handler_is_iostream);
#endif
}
}
TEST(utils_error, can_override_exception_handler) {
struct my_exc_handler final : public utils::error::i_exception_handler {
#if defined(PROJECT_ENABLE_V2_ERRORS)
MOCK_METHOD(void, handle_debug,
(std::string_view function_name, std::string_view msg),
(const, override));
#endif // defined(PROJECT_ENABLE_V2_ERRORS)
MOCK_METHOD(void, handle_error,
(std::string_view function_name, std::string_view msg),
(const, override));
MOCK_METHOD(void, handle_exception, (std::string_view function_name),
(const, override));
MOCK_METHOD(void, handle_exception,
(std::string_view function_name, const std::exception &ex),
(const, override));
#if defined(PROJECT_ENABLE_V2_ERRORS)
MOCK_METHOD(void, handle_info,
(std::string_view function_name, std::string_view msg),
(const, override));
MOCK_METHOD(void, handle_trace,
(std::string_view function_name, std::string_view msg),
(const, override));
MOCK_METHOD(void, handle_warn,
(std::string_view function_name, std::string_view msg),
(const, override));
#endif // defined(PROJECT_ENABLE_V2_ERRORS)
};
my_exc_handler handler{};
utils::error::set_exception_handler(&handler);
#if defined(PROJECT_ENABLE_V2_ERRORS)
EXPECT_CALL(handler, handle_debug("test_func", "debug")).WillOnce(Return());
utils::error::handle_debug("test_func", "debug");
#endif // defined(PROJECT_ENABLE_V2_ERRORS)
EXPECT_CALL(handler, handle_error("test_func", "error")).WillOnce(Return());
utils::error::handle_error("test_func", "error");
EXPECT_CALL(handler, handle_exception("test_func")).WillOnce(Return());
utils::error::handle_exception("test_func");
auto ex = std::runtime_error("moose");
EXPECT_CALL(handler, handle_exception(_, _))
.WillOnce(
[&ex](std::string_view function_name, const std::exception &ex2) {
EXPECT_EQ("test_func_ex", function_name);
EXPECT_STREQ(ex.what(), ex2.what());
});
utils::error::handle_exception("test_func_ex", ex);
#if defined(PROJECT_ENABLE_V2_ERRORS)
EXPECT_CALL(handler, handle_info("test_func", "info")).WillOnce(Return());
utils::error::handle_info("test_func", "info");
EXPECT_CALL(handler, handle_trace("test_func", "trace")).WillOnce(Return());
utils::error::handle_trace("test_func", "trace");
EXPECT_CALL(handler, handle_warn("test_func", "warn")).WillOnce(Return());
utils::error::handle_warn("test_func", "warn");
#endif // defined(PROJECT_ENABLE_V2_ERRORS)
utils::error::set_exception_handler(&utils::error::default_exception_handler);
}
} // namespace fifthgrid

View File

@@ -0,0 +1,603 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
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,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "test.hpp"
namespace {
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
#include "utils/file_enc_file.hpp"
constexpr auto file_type_count{3U};
#else
constexpr auto file_type_count{2U};
#endif
[[nodiscard]] auto create_file(auto idx, auto path, bool read_only = false)
-> auto {
switch (idx) {
case 0U:
return fifthgrid::utils::file::file::open_or_create_file(path, read_only);
case 1U:
return fifthgrid::utils::file::thread_file::open_or_create_file(path,
read_only);
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
case 2U:
return fifthgrid::utils::file::enc_file::attach_file(
fifthgrid::utils::file::file::open_or_create_file(path, read_only));
#endif
default:
throw std::runtime_error("not supported");
}
}
[[nodiscard]] auto open_file(auto idx, auto path, bool read_only = false)
-> auto {
switch (idx) {
case 0U:
return fifthgrid::utils::file::file::open_file(path, read_only);
case 1U:
return fifthgrid::utils::file::thread_file::open_file(path, read_only);
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
case 2U:
return fifthgrid::utils::file::enc_file::attach_file(
fifthgrid::utils::file::file::open_file(path, read_only));
#endif
default:
throw std::runtime_error("not supported");
}
}
} // namespace
namespace fifthgrid {
TEST(utils_file, can_create_and_remove_file) {
for (auto idx = 0U; idx < file_type_count; ++idx) {
auto path = test::generate_test_file_name("utils_file");
EXPECT_FALSE(utils::file::file(path).exists() ||
utils::file::directory(path).exists());
auto file{create_file(idx, path)};
EXPECT_TRUE(*file);
EXPECT_TRUE(utils::file::file(path).exists());
EXPECT_TRUE(file->exists());
EXPECT_TRUE(file->remove());
EXPECT_FALSE(utils::file::file(path).exists());
EXPECT_FALSE(file->exists());
}
}
TEST(utils_file, can_open_file) {
for (auto idx = 0U; idx < file_type_count; ++idx) {
auto path = test::generate_test_file_name("utils_file");
{
auto file{create_file(idx, path)};
EXPECT_TRUE(*file);
}
{
auto file{create_file(idx, path)};
EXPECT_TRUE(*file);
}
}
}
TEST(utils_file, open_file_fails_if_not_found) {
for (auto idx = 0U; idx < file_type_count; ++idx) {
auto path = test::generate_test_file_name("utils_file");
auto file{open_file(idx, path)};
EXPECT_FALSE(*file);
}
}
TEST(utils_file, write_fails_for_read_only_file) {
for (auto idx = 0U; idx < file_type_count; ++idx) {
auto path = test::generate_test_file_name("utils_file");
auto file{create_file(idx, path, true)};
EXPECT_TRUE(utils::file::file(path).exists());
EXPECT_TRUE(*file);
std::size_t bytes_written{};
EXPECT_FALSE(file->write(reinterpret_cast<const unsigned char *>("0"), 1U,
0U, &bytes_written));
EXPECT_EQ(0U, bytes_written);
}
}
// TEST(utils_file, can_attach_file) {
// for (auto idx = 0U; idx < file_type_count; ++idx) {
// auto path = test::generate_test_file_name("utils_file");
// auto file = idx == 0U ? utils::file::file::open_or_create_file(path)
// :
// utils::file::thread_file::open_or_create_file(path);
// auto file2 =
// idx == 0U ? utils::file::file::attach_file(file->get_handle())
// :
// utils::file::thread_file::attach_file(file->get_handle());
// EXPECT_TRUE(*file);
// EXPECT_TRUE(*file2);
// EXPECT_EQ(file->get_path(), file2->get_path());
// }
// }
#if defined(PROJECT_ENABLE_JSON)
TEST(utils_file, read_and_write_json_file) {
auto path = test::generate_test_file_name("utils_file");
auto json_data = nlohmann::json({{"moose", "cow"}});
EXPECT_TRUE(utils::file::write_json_file(path, json_data));
{
nlohmann::json result_data{};
EXPECT_TRUE(utils::file::read_json_file(path, result_data));
EXPECT_STREQ(json_data.dump().c_str(), result_data.dump().c_str());
}
{
nlohmann::json result_data{};
EXPECT_TRUE(utils::file::read_json_file(utils::string::from_utf8(path),
result_data));
EXPECT_STREQ(json_data.dump().c_str(), result_data.dump().c_str());
}
}
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
TEST(utils_file, read_and_write_json_file_encrypted) {
{
auto path = test::generate_test_file_name("utils_file");
auto json_data = nlohmann::json({{"moose", "cow"}});
EXPECT_TRUE(utils::file::write_json_file(path, json_data, "moose"));
nlohmann::json result_data{};
EXPECT_TRUE(utils::file::read_json_file(path, result_data, "moose"));
EXPECT_STREQ(json_data.dump().c_str(), result_data.dump().c_str());
{
auto file = utils::file::file::open_file(path);
data_buffer encrypted_data{};
EXPECT_TRUE(file->read_all(encrypted_data, 0U));
data_buffer decrypted_data{};
EXPECT_TRUE(utils::encryption::decrypt_data("moose", encrypted_data,
decrypted_data));
EXPECT_STREQ(json_data.dump().c_str(),
nlohmann::json::parse(std::string(decrypted_data.begin(),
decrypted_data.end()))
.dump()
.c_str());
}
}
{
auto path =
utils::string::from_utf8(test::generate_test_file_name("utils_file"));
auto json_data = nlohmann::json({{"moose", "cow"}});
EXPECT_TRUE(utils::file::write_json_file(path, json_data, L"moose"));
nlohmann::json result_data{};
EXPECT_TRUE(utils::file::read_json_file(path, result_data, L"moose"));
EXPECT_STREQ(json_data.dump().c_str(), result_data.dump().c_str());
{
auto file = utils::file::file::open_file(path);
data_buffer encrypted_data{};
EXPECT_TRUE(file->read_all(encrypted_data, 0U));
data_buffer decrypted_data{};
EXPECT_TRUE(utils::encryption::decrypt_data("moose", encrypted_data,
decrypted_data));
EXPECT_STREQ(json_data.dump().c_str(),
nlohmann::json::parse(std::string(decrypted_data.begin(),
decrypted_data.end()))
.dump()
.c_str());
}
}
}
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
#endif // defined(PROJECT_ENABLE_JSON)
#if defined(PROJECT_ENABLE_LIBDSM)
TEST(utils_file, smb_create_smb_path) {
const auto *path = "//server/share";
const auto *rel_path = "test/test.txt";
auto smb_path = utils::file::smb_create_smb_path(path, rel_path);
EXPECT_STREQ("//server/share/test/test.txt", smb_path.c_str());
rel_path = "/test/test.txt";
smb_path = utils::file::smb_create_smb_path(path, rel_path);
EXPECT_STREQ("//server/share/test/test.txt", smb_path.c_str());
rel_path = "test\\test.txt";
smb_path = utils::file::smb_create_smb_path(path, rel_path);
EXPECT_STREQ("//server/share/test/test.txt", smb_path.c_str());
rel_path = "\\test\\test.txt";
smb_path = utils::file::smb_create_smb_path(path, rel_path);
EXPECT_STREQ("//server/share/test/test.txt", smb_path.c_str());
}
TEST(utils_file, smb_create_relative_path) {
const auto *path = "//server/share/test.txt";
auto rel_path = utils::file::smb_create_relative_path(path);
EXPECT_STREQ("\\test.txt", rel_path.c_str());
path = "//server/share/test";
rel_path = utils::file::smb_create_relative_path(path);
EXPECT_STREQ("\\test", rel_path.c_str());
path = "//server/share/test/";
rel_path = utils::file::smb_create_relative_path(path);
EXPECT_STREQ("\\test", rel_path.c_str());
path = "//server/share/test/";
rel_path = utils::file::smb_create_relative_path(path);
EXPECT_STREQ("\\test", rel_path.c_str());
}
TEST(utils_file, smb_create_search_path) {
const auto *path = "//server/share";
auto search_path = utils::file::smb_create_search_path(path);
EXPECT_STREQ("\\*", search_path.c_str());
path = "//server/share/";
search_path = utils::file::smb_create_search_path(path);
EXPECT_STREQ("\\*", search_path.c_str());
path = "//server/share/folder";
search_path = utils::file::smb_create_search_path(path);
EXPECT_STREQ("\\folder\\*", search_path.c_str());
path = "//server/share/folder/";
search_path = utils::file::smb_create_search_path(path);
EXPECT_STREQ("\\folder\\*", search_path.c_str());
path = "//server/share/folder/next";
search_path = utils::file::smb_create_search_path(path);
EXPECT_STREQ("\\folder\\next\\*", search_path.c_str());
path = "//server/share/folder/next/";
search_path = utils::file::smb_create_search_path(path);
EXPECT_STREQ("\\folder\\next\\*", search_path.c_str());
}
TEST(utils_file, smb_parent_is_same) {
const auto *path1 = "//server/share";
const auto *path2 = "//server/share";
EXPECT_TRUE(utils::file::smb_parent_is_same(path1, path2));
path1 = "//server/share/";
path2 = "//server/share/";
EXPECT_TRUE(utils::file::smb_parent_is_same(path1, path2));
path1 = "//server/share/one";
path2 = "//server/share/two";
EXPECT_TRUE(utils::file::smb_parent_is_same(path1, path2));
path1 = "// server/cow";
path2 = "// server/cow";
EXPECT_TRUE(utils::file::smb_parent_is_same(path1, path2));
}
TEST(utils_file, smb_parent_is_not_same) {
const auto *path1 = "server/share";
const auto *path2 = "//server/share";
EXPECT_FALSE(utils::file::smb_parent_is_same(path1, path2));
path1 = "server/share/";
path2 = "server/share/";
EXPECT_FALSE(utils::file::smb_parent_is_same(path1, path2));
path1 = "//server1/share/one";
path2 = "//server/share/two";
EXPECT_FALSE(utils::file::smb_parent_is_same(path1, path2));
path1 = "//server/share";
path2 = "//server/share2";
EXPECT_FALSE(utils::file::smb_parent_is_same(path1, path2));
path1 = "//server/share/";
path2 = "//server/share2/";
EXPECT_FALSE(utils::file::smb_parent_is_same(path1, path2));
path1 = "//server/share/one";
path2 = "//server/share2/two";
EXPECT_FALSE(utils::file::smb_parent_is_same(path1, path2));
path1 = "//server";
path2 = "//server/share/two";
EXPECT_FALSE(utils::file::smb_parent_is_same(path1, path2));
path1 = "//server/";
path2 = "//server/";
EXPECT_FALSE(utils::file::smb_parent_is_same(path1, path2));
path1 = "//server";
path2 = "//server";
EXPECT_FALSE(utils::file::smb_parent_is_same(path1, path2));
}
#endif // defined(PROJECT_ENABLE_LIBDSM)
TEST(utils_file, directory_exists_in_path) {
auto &test_dir = test::generate_test_directory();
EXPECT_FALSE(
utils::file::directory_exists_in_path(test_dir.get_path(), "moose"));
EXPECT_FALSE(utils::file::directory_exists_in_path(
utils::string::from_utf8(test_dir.get_path()), L"moose"));
EXPECT_FALSE(utils::file::file_exists_in_path(test_dir.get_path(), "moose"));
EXPECT_FALSE(utils::file::file_exists_in_path(
utils::string::from_utf8(test_dir.get_path()), L"moose"));
auto sub_dir = test_dir.create_directory("moose");
EXPECT_TRUE(sub_dir != nullptr);
if (sub_dir) {
EXPECT_TRUE(
utils::file::directory_exists_in_path(test_dir.get_path(), "moose"));
EXPECT_FALSE(
utils::file::file_exists_in_path(test_dir.get_path(), "moose"));
EXPECT_TRUE(utils::file::directory_exists_in_path(
utils::string::from_utf8(test_dir.get_path()), L"moose"));
EXPECT_FALSE(utils::file::file_exists_in_path(
utils::string::from_utf8(test_dir.get_path()), L"moose"));
}
}
TEST(utils_file, directory_can_get_empty_directory_count) {
auto &test_dir = test::generate_test_directory();
EXPECT_EQ(0U, test_dir.count());
EXPECT_EQ(0U, test_dir.count(false));
}
TEST(utils_file, directory_can_get_empty_directory_count_recursively) {
auto &test_dir = test::generate_test_directory();
EXPECT_EQ(0U, test_dir.count(true));
}
TEST(utils_file, directory_can_get_non_empty_directory_count) {
auto &test_dir = test::generate_test_directory();
auto sub_dir = test_dir.create_directory("sub_dir");
EXPECT_TRUE(sub_dir != nullptr);
if (sub_dir) {
sub_dir->create_directory("sub_dir");
EXPECT_EQ(1U, test_dir.count());
EXPECT_EQ(1U, test_dir.count(false));
}
}
TEST(utils_file, directory_can_get_non_empty_directory_count_recursively) {
auto &test_dir = test::generate_test_directory();
auto sub_dir = test_dir.create_directory("sub_dir");
EXPECT_TRUE(sub_dir != nullptr);
if (sub_dir) {
sub_dir = sub_dir->create_directory("sub_dir");
EXPECT_TRUE(sub_dir != nullptr);
}
EXPECT_EQ(2U, test_dir.count(true));
}
TEST(utils_file, file_exists_in_path) {
auto &test_dir = test::generate_test_directory();
EXPECT_FALSE(
utils::file::file_exists_in_path(test_dir.get_path(), "moose.txt"));
EXPECT_FALSE(utils::file::file_exists_in_path(
utils::string::from_utf8(test_dir.get_path()), L"moose.txt"));
EXPECT_FALSE(
utils::file::directory_exists_in_path(test_dir.get_path(), "moose.txt"));
EXPECT_FALSE(utils::file::directory_exists_in_path(
utils::string::from_utf8(test_dir.get_path()), L"moose.txt"));
auto sub_file = test_dir.create_file("moose.txt", false);
EXPECT_TRUE(sub_file != nullptr);
if (sub_file) {
EXPECT_TRUE(
utils::file::file_exists_in_path(test_dir.get_path(), "moose.txt"));
EXPECT_FALSE(utils::file::directory_exists_in_path(test_dir.get_path(),
"moose.txt"));
EXPECT_TRUE(utils::file::file_exists_in_path(
utils::string::from_utf8(test_dir.get_path()), L"moose.txt"));
EXPECT_FALSE(utils::file::directory_exists_in_path(
utils::string::from_utf8(test_dir.get_path()), L"moose.txt"));
}
}
TEST(utils_file, get_free_drive_space) {
#if defined(_WIN32)
auto space = utils::file::get_free_drive_space("C:");
auto space2 = utils::file::get_free_drive_space(L"C:");
#else // defined(_WIN32)
auto space = utils::file::get_free_drive_space("/");
auto space2 = utils::file::get_free_drive_space(L"/");
#endif // !defined(_WIN32)
EXPECT_TRUE(space.has_value());
EXPECT_LT(0U, space.value());
EXPECT_TRUE(space2.has_value());
EXPECT_EQ(space.value(), space2.value());
}
TEST(utils_file, get_free_drive_space_fails_for_bad_path) {
std::string name{"free_drive_space_test_XXXXXX"};
auto temp = utils::file::create_temp_name("free_drive_space_test");
auto space = utils::file::get_free_drive_space(temp);
EXPECT_FALSE(space.has_value());
}
TEST(utils_file, get_total_drive_space) {
#if defined(_WIN32)
auto space = utils::file::get_total_drive_space("C:");
auto space2 = utils::file::get_total_drive_space(L"C:");
#else // defined(_WIN32)
auto space = utils::file::get_total_drive_space("/");
auto space2 = utils::file::get_total_drive_space(L"/");
#endif // !defined(_WIN32)
EXPECT_TRUE(space.has_value());
EXPECT_LT(0U, space.value());
EXPECT_TRUE(space2.has_value());
EXPECT_EQ(space.value(), space2.value());
}
TEST(utils_file, create_temp_name) {
{
auto temp = utils::file::create_temp_name("test_temp");
EXPECT_EQ(18U, temp.size());
auto temp2 = utils::file::create_temp_name("test_temp");
EXPECT_STRNE(temp.c_str(), temp2.c_str());
EXPECT_TRUE(utils::string::begins_with(temp, "test_temp_"));
}
{
auto temp = utils::file::create_temp_name(L"test_temp");
EXPECT_EQ(18U, temp.size());
auto temp2 = utils::file::create_temp_name(L"test_temp");
EXPECT_STRNE(temp.c_str(), temp2.c_str());
EXPECT_TRUE(utils::string::begins_with(temp, L"test_temp_"));
}
}
TEST(utils_file, get_total_drive_space_fails_for_bad_path) {
auto temp = utils::file::create_temp_name("total_drive_space_test");
auto space = utils::file::get_total_drive_space(temp);
EXPECT_FALSE(space.has_value());
}
TEST(utils_file, get_times) {
{
auto times =
utils::file::get_times(test::create_random_file(1U).get_path());
EXPECT_TRUE(times.has_value());
EXPECT_LT(0U, times->get(utils::file::time_type::accessed));
EXPECT_LT(0U, times->get(utils::file::time_type::changed));
EXPECT_LT(0U, times->get(utils::file::time_type::created));
EXPECT_LT(0U, times->get(utils::file::time_type::modified));
EXPECT_LT(0U, times->get(utils::file::time_type::written));
}
{
auto times = utils::file::get_times(
utils::string::from_utf8(test::create_random_file(1U).get_path()));
EXPECT_TRUE(times.has_value());
EXPECT_LT(0U, times->get(utils::file::time_type::accessed));
EXPECT_LT(0U, times->get(utils::file::time_type::changed));
EXPECT_LT(0U, times->get(utils::file::time_type::created));
EXPECT_LT(0U, times->get(utils::file::time_type::modified));
EXPECT_LT(0U, times->get(utils::file::time_type::written));
}
}
TEST(utils_file, get_times_fails_if_not_found) {
auto temp = utils::path::combine(".", {"get_times_test"});
auto times = utils::file::get_times(temp);
EXPECT_FALSE(times.has_value());
}
TEST(utils_file, get_time) {
{
auto file_path = test::create_random_file(1U).get_path();
auto file_time =
utils::file::get_time(file_path, utils::file::time_type::accessed);
EXPECT_TRUE(file_time.has_value());
EXPECT_LT(0U, file_time.value());
file_time =
utils::file::get_time(file_path, utils::file::time_type::changed);
EXPECT_TRUE(file_time.has_value());
EXPECT_LT(0U, file_time.value());
file_time =
utils::file::get_time(file_path, utils::file::time_type::created);
EXPECT_TRUE(file_time.has_value());
EXPECT_LT(0U, file_time.value());
file_time =
utils::file::get_time(file_path, utils::file::time_type::modified);
EXPECT_TRUE(file_time.has_value());
EXPECT_LT(0U, file_time.value());
file_time =
utils::file::get_time(file_path, utils::file::time_type::written);
EXPECT_TRUE(file_time.has_value());
EXPECT_LT(0U, file_time.value());
}
{
auto file_path =
utils::string::from_utf8(test::create_random_file(1U).get_path());
auto file_time =
utils::file::get_time(file_path, utils::file::time_type::accessed);
EXPECT_TRUE(file_time.has_value());
EXPECT_LT(0U, file_time.value());
file_time =
utils::file::get_time(file_path, utils::file::time_type::changed);
EXPECT_TRUE(file_time.has_value());
EXPECT_LT(0U, file_time.value());
file_time =
utils::file::get_time(file_path, utils::file::time_type::created);
EXPECT_TRUE(file_time.has_value());
EXPECT_LT(0U, file_time.value());
file_time =
utils::file::get_time(file_path, utils::file::time_type::modified);
EXPECT_TRUE(file_time.has_value());
EXPECT_LT(0U, file_time.value());
file_time =
utils::file::get_time(file_path, utils::file::time_type::written);
EXPECT_TRUE(file_time.has_value());
EXPECT_LT(0U, file_time.value());
}
}
TEST(utils_file, get_time_fails_if_not_found) {
auto temp = utils::path::combine(".", {"get_times_test"});
auto file_time =
utils::file::get_time(temp, utils::file::time_type::accessed);
EXPECT_FALSE(file_time.has_value());
}
} // namespace fifthgrid

View File

@@ -0,0 +1,249 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
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,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "test.hpp"
#if defined(PROJECT_ENABLE_LIBSODIUM)
namespace fifthgrid {
TEST(utils_hash, hash_type_sizes) {
EXPECT_EQ(4U, utils::hash::hash_32_t{}.size());
EXPECT_EQ(8U, utils::hash::hash_64_t{}.size());
EXPECT_EQ(16U, utils::hash::hash_128_t{}.size());
EXPECT_EQ(32U, utils::hash::hash_256_t{}.size());
EXPECT_EQ(48U, utils::hash::hash_384_t{}.size());
EXPECT_EQ(64U, utils::hash::hash_512_t{}.size());
}
TEST(utils_hash, default_hasher_is_blake2b) {
EXPECT_EQ(&utils::hash::blake2b_32_hasher,
&utils::hash::default_create_hash<utils::hash::hash_32_t>());
EXPECT_EQ(&utils::hash::blake2b_64_hasher,
&utils::hash::default_create_hash<utils::hash::hash_64_t>());
EXPECT_EQ(&utils::hash::blake2b_128_hasher,
&utils::hash::default_create_hash<utils::hash::hash_128_t>());
EXPECT_EQ(&utils::hash::blake2b_256_hasher,
&utils::hash::default_create_hash<utils::hash::hash_256_t>());
EXPECT_EQ(&utils::hash::blake2b_384_hasher,
&utils::hash::default_create_hash<utils::hash::hash_384_t>());
EXPECT_EQ(&utils::hash::blake2b_512_hasher,
&utils::hash::default_create_hash<utils::hash::hash_512_t>());
}
TEST(utils_hash, blake2b_32) {
auto hash = utils::collection::to_hex_string(
utils::hash::create_hash_blake2b_32("a"));
EXPECT_STREQ("ca234c55", hash.c_str());
hash = utils::collection::to_hex_string(
utils::hash::create_hash_blake2b_32(L"a"));
#if defined(_WIN32)
EXPECT_STREQ("4c368117", hash.c_str());
#else // !defined(_WIN32)
EXPECT_STREQ("02a631b8", hash.c_str());
#endif
hash = utils::collection::to_hex_string(
utils::hash::create_hash_blake2b_32({1U}));
EXPECT_STREQ("593bda73", hash.c_str());
}
TEST(utils_hash, blake2b_64) {
auto hash = utils::collection::to_hex_string(
utils::hash::create_hash_blake2b_64("a"));
EXPECT_STREQ("40f89e395b66422f", hash.c_str());
hash = utils::collection::to_hex_string(
utils::hash::create_hash_blake2b_64(L"a"));
#if defined(_WIN32)
EXPECT_STREQ("4dd0bb1c45b748c1", hash.c_str());
#else // !defined(_WIN32)
EXPECT_STREQ("85ff8cc55b79d38a", hash.c_str());
#endif
hash = utils::collection::to_hex_string(
utils::hash::create_hash_blake2b_64({1U}));
EXPECT_STREQ("00e83d0a3f7519ad", hash.c_str());
}
TEST(utils_hash, blake2b_128) {
auto hash = utils::collection::to_hex_string(
utils::hash::create_hash_blake2b_128("a"));
EXPECT_STREQ("27c35e6e9373877f29e562464e46497e", hash.c_str());
hash = utils::collection::to_hex_string(
utils::hash::create_hash_blake2b_128(L"a"));
#if defined(_WIN32)
EXPECT_STREQ("396660e76c84bb7786f979f10b58fa79", hash.c_str());
#else // !defined(_WIN32)
EXPECT_STREQ("dae64afb310a3426ad84f0739fde5cef", hash.c_str());
#endif
hash = utils::collection::to_hex_string(
utils::hash::create_hash_blake2b_128({1U}));
EXPECT_STREQ("4a9e6f9b8d43f6ad008f8c291929dee2", hash.c_str());
}
TEST(utils_hash, blake2b_256) {
auto hash = utils::collection::to_hex_string(
utils::hash::create_hash_blake2b_256("a"));
EXPECT_STREQ(
"8928aae63c84d87ea098564d1e03ad813f107add474e56aedd286349c0c03ea4",
hash.c_str());
hash = utils::collection::to_hex_string(
utils::hash::create_hash_blake2b_256(L"a"));
#if defined(_WIN32)
EXPECT_STREQ(
"d2373b17cd8a8e19e39f52fa4905a274f93805fbb8bb4c7f3cb4b2cd6708ec8a",
hash.c_str());
#else // !defined(_WIN32)
EXPECT_STREQ(
"9fdf5757d7eea386f0d34d2c0e202527986febf1ebb4315fcf7fff40776fa41d",
hash.c_str());
#endif
hash = utils::collection::to_hex_string(
utils::hash::create_hash_blake2b_256({1U}));
EXPECT_STREQ(
"ee155ace9c40292074cb6aff8c9ccdd273c81648ff1149ef36bcea6ebb8a3e25",
hash.c_str());
}
TEST(utils_hash, blake2b_384) {
auto hash = utils::collection::to_hex_string(
utils::hash::create_hash_blake2b_384("a"));
EXPECT_STREQ("7d40de16ff771d4595bf70cbda0c4ea0a066a6046fa73d34471cd4d93d827d7"
"c94c29399c50de86983af1ec61d5dcef0",
hash.c_str());
hash = utils::collection::to_hex_string(
utils::hash::create_hash_blake2b_384(L"a"));
#if defined(_WIN32)
EXPECT_STREQ("637fe31d1e955760ef31043d525d9321826a778ddbe82fcde45a98394241380"
"96675e2f87e36b53ab223a7fd254198fd",
hash.c_str());
#else // !defined(_WIN32)
EXPECT_STREQ("9d469bd9dab9d4b48b8688de7c22704a8de1b081294f9be294100dfa9f05c92"
"e8d3616476e46cd14f9e613fed80fd157",
hash.c_str());
#endif
hash = utils::collection::to_hex_string(
utils::hash::create_hash_blake2b_384({1U}));
EXPECT_STREQ("42cfe875d08d816538103b906bb0b05202e0b09c4e981680c1110684fc7845b"
"c91c178fa167afcc445490644b2bf5f5b",
hash.c_str());
}
TEST(utils_hash, blake2b_512) {
auto hash = utils::collection::to_hex_string(
utils::hash::create_hash_blake2b_512("a"));
EXPECT_STREQ(
"333fcb4ee1aa7c115355ec66ceac917c8bfd815bf7587d325aec1864edd24e34d5abe2c6"
"b1b5ee3face62fed78dbef802f2a85cb91d455a8f5249d330853cb3c",
hash.c_str());
hash = utils::collection::to_hex_string(
utils::hash::create_hash_blake2b_512(L"a"));
#if defined(_WIN32)
EXPECT_STREQ(
"05970b95468b0b1941066ff189091493e73859ce41cde5ad08118e93ea1d81a57a144296"
"a26a9fe7781481bde97b886725e36e30b305d8bd5cce1ae36bf1564a",
hash.c_str());
#else // !defined(_WIN32)
EXPECT_STREQ(
"bbc187c6e4d8525655d0ada62d16eed59f3db3ab07e04fb0483fd4ae21d88b984774add9"
"b3fbcff56f9638091013994f8e2d4646fdbbcb4879e2b5160bbb755d",
hash.c_str());
#endif
hash = utils::collection::to_hex_string(
utils::hash::create_hash_blake2b_512({1U}));
EXPECT_STREQ(
"9545ba37b230d8a2e716c4707586542780815b7c4088edcb9af6a9452d50f32474d5ba9a"
"ab52a67aca864ef2696981c2eadf49020416136afd838fb048d21653",
hash.c_str());
}
TEST(utils_hash, sha256) {
auto hash =
utils::collection::to_hex_string(utils::hash::create_hash_sha256("a"));
EXPECT_STREQ(
"ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb",
hash.c_str());
hash =
utils::collection::to_hex_string(utils::hash::create_hash_sha256(L"a"));
#if defined(_WIN32)
EXPECT_STREQ(
"ffe9aaeaa2a2d5048174df0b80599ef0197ec024c4b051bc9860cff58ef7f9f3",
hash.c_str());
#else // !defined(_WIN32)
EXPECT_STREQ(
"a2d398922901344d08180dc41d3e9d73d8c148c7f6e092835bbb28e02dbcf184",
hash.c_str());
#endif
hash =
utils::collection::to_hex_string(utils::hash::create_hash_sha256({1U}));
EXPECT_STREQ(
"4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a",
hash.c_str());
}
TEST(utils_hash, sha512) {
auto hash =
utils::collection::to_hex_string(utils::hash::create_hash_sha512("a"));
EXPECT_STREQ(
"1f40fc92da241694750979ee6cf582f2d5d7d28e18335de05abc54d0560e0f5302860c65"
"2bf08d560252aa5e74210546f369fbbbce8c12cfc7957b2652fe9a75",
hash.c_str());
hash =
utils::collection::to_hex_string(utils::hash::create_hash_sha512(L"a"));
#if defined(_WIN32)
EXPECT_STREQ(
"5c2ca3d50f46ece6066c53bd1a490cbe5f72d2738ae9417332e91e5c3f75205c639d71a9"
"a41d67d965fa137dddf439e0ab9443a6ea44915e90d8b5b566d1c076",
hash.c_str());
#else // !defined(_WIN32)
EXPECT_STREQ(
"a93498d992e81915075144cb304d2bdf040b336283f888252244882d8366dd3a6e2d9749"
"077114dda1a9aa1a7b69d33f7a781f003ccd12e599a6341014f29aaf",
hash.c_str());
#endif
hash =
utils::collection::to_hex_string(utils::hash::create_hash_sha512({1U}));
EXPECT_STREQ(
"7b54b66836c1fbdd13d2441d9e1434dc62ca677fb68f5fe66a464baadecdbd00576f8d6b"
"5ac3bcc80844b7d50b1cc6603444bbe7cfcf8fc0aa1ee3c636d9e339",
hash.c_str());
}
} // namespace fifthgrid
#endif // defined(PROJECT_ENABLE_LIBSODIUM)

View File

@@ -0,0 +1,552 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
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,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "test.hpp"
#if defined(_WIN32)
namespace {
static const auto test_path = [](std::string str) -> std::string {
#if defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES)
if (fifthgrid::utils::string::begins_with(str, "\\")) {
str = fifthgrid::utils::string::to_lower(
std::filesystem::current_path().string().substr(0U, 2U)) +
str;
}
str = std::string{fifthgrid::utils::path::long_notation} + str;
#else // !defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES)
if (fifthgrid::utils::string::begins_with(str, "\\")) {
str = fifthgrid::utils::string::to_lower(
std::filesystem::current_path().string().substr(0U, 2U)) +
str;
}
#endif // defined(PROJECT_ENABLE_WIN32_LONG_PATH_NAMES)
return fifthgrid::utils::string::right_trim(str, '\\');
};
} // namespace
#endif // defined(_WIN32)
namespace fifthgrid {
TEST(utils_path, constants) {
EXPECT_EQ(std::string_view{"\\"}, utils::path::backslash);
EXPECT_EQ(std::wstring_view{L"\\"}, utils::path::backslash_w);
EXPECT_EQ(std::string_view{"."}, utils::path::dot);
EXPECT_EQ(std::wstring_view{L"."}, utils::path::dot_w);
EXPECT_EQ(std::string_view{".\\"}, utils::path::dot_backslash);
EXPECT_EQ(std::wstring_view{L".\\"}, utils::path::dot_backslash_w);
EXPECT_EQ(std::string_view{"./"}, utils::path::dot_slash);
EXPECT_EQ(std::wstring_view{L"./"}, utils::path::dot_slash_w);
EXPECT_EQ(std::string_view{"/"}, utils::path::slash);
EXPECT_EQ(std::wstring_view{L"/"}, utils::path::slash_w);
#if defined(_WIN32)
EXPECT_EQ(std::string_view{"\\\\"}, utils::path::unc_notation);
EXPECT_EQ(std::wstring_view{L"\\\\"}, utils::path::unc_notation_w);
#endif // defined(_WIN32)
}
TEST(utils_path, directory_seperator) {
#if defined(_WIN32)
EXPECT_EQ(utils::path::backslash, utils::path::directory_seperator);
EXPECT_EQ(utils::path::backslash_w, utils::path::directory_seperator_w);
EXPECT_EQ(utils::path::slash, utils::path::not_directory_seperator);
EXPECT_EQ(utils::path::slash_w, utils::path::not_directory_seperator_w);
#else // !defined(_WIN32)
EXPECT_EQ(utils::path::slash, utils::path::directory_seperator);
EXPECT_EQ(utils::path::slash_w, utils::path::directory_seperator_w);
EXPECT_EQ(utils::path::backslash, utils::path::not_directory_seperator);
EXPECT_EQ(utils::path::backslash_w, utils::path::not_directory_seperator_w);
#endif // defined(_WIN32)
}
TEST(utils_path, get_directory_seperator) {
#if defined(_WIN32)
EXPECT_EQ(utils::path::backslash,
utils::path::get_directory_seperator<char>());
EXPECT_EQ(utils::path::backslash_w,
utils::path::get_directory_seperator<wchar_t>());
EXPECT_EQ(utils::path::slash,
utils::path::get_not_directory_seperator<char>());
EXPECT_EQ(utils::path::slash_w,
utils::path::get_not_directory_seperator<wchar_t>());
#else // !defined(_WIN32)
EXPECT_EQ(utils::path::slash, utils::path::get_directory_seperator<char>());
EXPECT_EQ(utils::path::slash_w,
utils::path::get_directory_seperator<wchar_t>());
EXPECT_EQ(utils::path::backslash,
utils::path::get_not_directory_seperator<char>());
EXPECT_EQ(utils::path::backslash_w,
utils::path::get_not_directory_seperator<wchar_t>());
#endif // defined(_WIN32)
}
TEST(utils_path, get_backslash) {
EXPECT_EQ(utils::path::backslash, utils::path::get_backslash<char>());
EXPECT_EQ(utils::path::backslash_w, utils::path::get_backslash<wchar_t>());
}
TEST(utils_path, get_dot) {
EXPECT_EQ(utils::path::dot, utils::path::get_dot<char>());
EXPECT_EQ(utils::path::dot_w, utils::path::get_dot<wchar_t>());
}
TEST(utils_path, get_dot_backslash) {
EXPECT_EQ(utils::path::dot_backslash, utils::path::get_dot_backslash<char>());
EXPECT_EQ(utils::path::dot_backslash_w,
utils::path::get_dot_backslash<wchar_t>());
}
TEST(utils_path, get_dot_slash) {
EXPECT_EQ(utils::path::dot_slash, utils::path::get_dot_slash<char>());
EXPECT_EQ(utils::path::dot_slash_w, utils::path::get_dot_slash<wchar_t>());
}
TEST(utils_path, get_slash) {
EXPECT_EQ(utils::path::slash, utils::path::get_slash<char>());
EXPECT_EQ(utils::path::slash_w, utils::path::get_slash<wchar_t>());
}
TEST(utils_path, get_long_notation) {
EXPECT_EQ(utils::path::long_notation, utils::path::get_long_notation<char>());
EXPECT_EQ(utils::path::long_notation_w,
utils::path::get_long_notation<wchar_t>());
}
TEST(utils_path, combine) {
auto s = utils::path::combine(R"(\test\path)", {});
#if defined(_WIN32)
EXPECT_STREQ(test_path(R"(\test\path)").c_str(), s.c_str());
#else
EXPECT_STREQ("/test/path", s.c_str());
#endif
s = utils::path::combine(R"(\test)", {R"(\path)"});
#if defined(_WIN32)
EXPECT_STREQ(test_path(R"(\test\path)").c_str(), s.c_str());
#else
EXPECT_STREQ("/test/path", s.c_str());
#endif
s = utils::path::combine(R"(\test)", {R"(\path)", R"(\again\)"});
#if defined(_WIN32)
EXPECT_STREQ(test_path(R"(\test\path\again)").c_str(), s.c_str());
#else
EXPECT_STREQ("/test/path/again", s.c_str());
#endif
s = utils::path::combine("/home/test/.dest", {".state"});
#if defined(_WIN32)
EXPECT_STREQ(test_path(R"(\home\test\.dest\.state)").c_str(), s.c_str());
#else
EXPECT_STREQ("/home/test/.dest/.state", s.c_str());
#endif
#if defined(_WIN32)
s = utils::path::combine(R"(R:\test)", {R"(\path)", R"(\again\)"});
EXPECT_STREQ(test_path(R"(r:\test\path\again)").c_str(), s.c_str());
s = utils::path::combine("R:", {R"(\path)", R"(\again\)"});
EXPECT_STREQ(test_path(R"(r:\path\again)").c_str(), s.c_str());
s = utils::path::combine("R:", {});
EXPECT_STREQ(test_path("r:").c_str(), s.c_str());
s = utils::path::combine("R:", {"\\"});
EXPECT_STREQ(test_path("r:").c_str(), s.c_str());
s = utils::path::combine("\\\\moose", {"cow"});
EXPECT_STREQ("\\\\moose\\cow", s.c_str());
#endif
}
TEST(utils_path, format_path) {
std::string path{"./"};
utils::path::format_path(path, utils::path::slash, utils::path::backslash);
EXPECT_STREQ(".", path.c_str());
path = "~/.test";
utils::path::format_path(path, utils::path::slash, utils::path::backslash);
EXPECT_STREQ("~/.test", path.c_str());
path = "\\";
utils::path::format_path(path, utils::path::slash, utils::path::backslash);
EXPECT_STREQ("/", path.c_str());
path = "\\\\";
utils::path::format_path(path, utils::path::slash, utils::path::backslash);
EXPECT_STREQ("/", path.c_str());
path = "\\\\\\";
utils::path::format_path(path, utils::path::slash, utils::path::backslash);
EXPECT_STREQ("/", path.c_str());
path = "\\\\\\\\";
utils::path::format_path(path, utils::path::slash, utils::path::backslash);
EXPECT_STREQ("/", path.c_str());
path = "/";
utils::path::format_path(path, utils::path::slash, utils::path::backslash);
EXPECT_STREQ("/", path.c_str());
path = "//";
utils::path::format_path(path, utils::path::slash, utils::path::backslash);
EXPECT_STREQ("/", path.c_str());
path = "///";
utils::path::format_path(path, utils::path::slash, utils::path::backslash);
EXPECT_STREQ("/", path.c_str());
path = "////";
utils::path::format_path(path, utils::path::slash, utils::path::backslash);
EXPECT_STREQ("/", path.c_str());
}
TEST(utils_path, create_api_path) {
auto s = utils::path::create_api_path("");
EXPECT_STREQ("/", s.c_str());
s = utils::path::create_api_path(R"(\)");
EXPECT_STREQ("/", s.c_str());
s = utils::path::create_api_path("/");
EXPECT_STREQ("/", s.c_str());
s = utils::path::create_api_path(".");
EXPECT_STREQ("/", s.c_str());
s = utils::path::create_api_path("./");
EXPECT_STREQ("/", s.c_str());
s = utils::path::create_api_path(R"(\\)");
EXPECT_STREQ("/", s.c_str());
s = utils::path::create_api_path("//");
EXPECT_STREQ("/", s.c_str());
s = utils::path::create_api_path("/cow///moose/////dog/chicken");
EXPECT_STREQ("/cow/moose/dog/chicken", s.c_str());
s = utils::path::create_api_path("\\cow\\\\\\moose\\\\\\\\dog\\chicken/");
EXPECT_STREQ("/cow/moose/dog/chicken", s.c_str());
s = utils::path::create_api_path("/cow\\\\/moose\\\\/\\dog\\chicken\\");
EXPECT_STREQ("/cow/moose/dog/chicken", s.c_str());
s = utils::path::create_api_path(".state");
EXPECT_STREQ("/.state", s.c_str());
s = utils::path::create_api_path("/.state/.local");
EXPECT_STREQ("/.state/.local", s.c_str());
s = utils::path::create_api_path("./.state/.local");
EXPECT_STREQ("/.state/.local", s.c_str());
}
TEST(utils_path, get_parent_api_path) {
auto s = utils::path::get_parent_api_path("");
EXPECT_STREQ("/", s.c_str());
s = utils::path::get_parent_api_path("/");
EXPECT_STREQ("/", s.c_str());
s = utils::path::get_parent_api_path("/moose");
EXPECT_STREQ("/", s.c_str());
s = utils::path::get_parent_api_path("/moose/cow");
EXPECT_STREQ("/moose", s.c_str());
s = utils::path::get_parent_api_path("/moose/cow/");
EXPECT_STREQ("/moose", s.c_str());
}
TEST(utils_path, finalize) {
auto s = utils::path::finalize("");
EXPECT_STREQ("", s.c_str());
s = utils::path::finalize(R"(\)");
#if defined(_WIN32)
EXPECT_STREQ(test_path(R"(\)").c_str(), s.c_str());
#else
EXPECT_STREQ("/", s.c_str());
#endif
s = utils::path::finalize("/");
#if defined(_WIN32)
EXPECT_STREQ(test_path(R"(\)").c_str(), s.c_str());
#else
EXPECT_STREQ("/", s.c_str());
#endif
s = utils::path::finalize(R"(\\)");
#if defined(_WIN32)
EXPECT_STREQ("\\\\", s.c_str());
#else
EXPECT_STREQ("/", s.c_str());
#endif
s = utils::path::finalize("//");
#if defined(_WIN32)
EXPECT_STREQ("\\\\", s.c_str());
#else
EXPECT_STREQ("/", s.c_str());
#endif
s = utils::path::finalize("/cow///moose/////dog/chicken");
#if defined(_WIN32)
EXPECT_STREQ(test_path(R"(\cow\moose\dog\chicken)").c_str(), s.c_str());
#else
EXPECT_STREQ("/cow/moose/dog/chicken", s.c_str());
#endif
s = utils::path::finalize("\\cow\\\\\\moose\\\\\\\\dog\\chicken/");
#if defined(_WIN32)
EXPECT_STREQ(test_path(R"(\cow\moose\dog\chicken)").c_str(), s.c_str());
#else
EXPECT_STREQ("/cow/moose/dog/chicken", s.c_str());
#endif
s = utils::path::finalize("/cow\\\\/moose\\\\/\\dog\\chicken\\");
#if defined(_WIN32)
EXPECT_STREQ(test_path(R"(\cow\moose\dog\chicken)").c_str(), s.c_str());
#else
EXPECT_STREQ("/cow/moose/dog/chicken", s.c_str());
#endif
#if defined(_WIN32)
s = utils::path::finalize("D:");
EXPECT_STREQ(test_path("d:").c_str(), s.c_str());
s = utils::path::finalize("D:\\");
EXPECT_STREQ(test_path("d:").c_str(), s.c_str());
s = utils::path::finalize("D:\\moose");
EXPECT_STREQ(test_path("d:\\moose").c_str(), s.c_str());
s = utils::path::finalize("D:\\moose\\");
EXPECT_STREQ(test_path("d:\\moose").c_str(), s.c_str());
s = utils::path::finalize("D:/");
EXPECT_STREQ(test_path("d:").c_str(), s.c_str());
s = utils::path::finalize("D:/moose");
EXPECT_STREQ(test_path("d:\\moose").c_str(), s.c_str());
s = utils::path::finalize("D:/moose/");
EXPECT_STREQ(test_path("d:\\moose").c_str(), s.c_str());
s = utils::path::finalize("\\\\moose\\cow");
EXPECT_STREQ("\\\\moose\\cow", s.c_str());
s = utils::path::finalize("//moose/cow");
EXPECT_STREQ("\\\\moose\\cow", s.c_str());
#else // !defined(_WIN32)
s = utils::path::finalize("\\\\moose\\cow");
EXPECT_STREQ("/moose/cow", s.c_str());
s = utils::path::finalize("//moose/cow");
EXPECT_STREQ("/moose/cow", s.c_str());
#endif // defined(_WIN32)
}
TEST(utils_path, absolute) {
auto dir = utils::path::get_current_path<std::string>();
auto path = utils::path::absolute(".");
EXPECT_STREQ(dir.c_str(), path.c_str());
path = utils::path::absolute("./");
EXPECT_STREQ(dir.c_str(), path.c_str());
path = utils::path::absolute(R"(.\)");
EXPECT_STREQ(dir.c_str(), path.c_str());
#if defined(_WIN32)
path = utils::path::absolute(R"(.\moose)");
EXPECT_STREQ((dir + R"(\moose)").c_str(), path.c_str());
path = utils::path::absolute(R"(./moose)");
EXPECT_STREQ((dir + R"(\moose)").c_str(), path.c_str());
path = utils::path::absolute(R"(\\server\share)");
EXPECT_STREQ(R"(\\server\share)", path.c_str());
path = utils::path::absolute(R"(//server/share)");
EXPECT_STREQ(R"(\\server\share)", path.c_str());
auto home_env = utils::get_environment_variable("USERPROFILE");
#else // !defined(_WIN32)
path = utils::path::absolute(R"(.\moose)");
EXPECT_STREQ((dir + R"(/moose)").c_str(), path.c_str());
path = utils::path::absolute(R"(./moose)");
EXPECT_STREQ((dir + R"(/moose)").c_str(), path.c_str());
path = utils::path::absolute(R"(\\server\share)");
EXPECT_STREQ(R"(/server/share)", path.c_str());
#endif // defined(_WIN32)
}
TEST(utils_path, absolute_can_resolve_path_variables) {
#if defined(_WIN32)
auto home =
utils::path::absolute(utils::get_environment_variable("USERPROFILE"));
EXPECT_STREQ(home.c_str(), utils::path::absolute("%USERPROFILE%").c_str());
#else // !defined(_WIN32)
auto home = utils::path::absolute(utils::get_environment_variable("HOME"));
#endif // defined(_WIN32)
auto expanded_str = utils::path::absolute("~\\");
EXPECT_STREQ(home.c_str(), expanded_str.c_str());
expanded_str = utils::path::absolute("~/");
EXPECT_STREQ(home.c_str(), expanded_str.c_str());
expanded_str = utils::path::absolute("~");
EXPECT_STREQ("~", expanded_str.c_str());
}
TEST(utils_path, get_parent_path) {
#if defined(_WIN32)
{
auto dir = R"(c:\test)";
auto parent = utils::path::get_parent_path(dir);
EXPECT_STREQ("c:", parent.c_str());
dir = R"(c:\test\file.txt)";
parent = utils::path::get_parent_path(dir);
EXPECT_STREQ(R"(c:\test)", parent.c_str());
dir = "c:";
parent = utils::path::get_parent_path(dir);
EXPECT_STREQ("c:", parent.c_str());
}
{
auto dir = LR"(c:\test)";
auto parent = utils::path::get_parent_path(dir);
EXPECT_STREQ(L"c:", parent.c_str());
dir = LR"(c:\test\file.txt)";
parent = utils::path::get_parent_path(dir);
EXPECT_STREQ(LR"(c:\test)", parent.c_str());
dir = L"c:";
parent = utils::path::get_parent_path(dir);
EXPECT_STREQ(L"c:", parent.c_str());
}
#else // !defined(_WIN32)
{
auto dir = "/test";
auto parent = utils::path::get_parent_path(dir);
EXPECT_STREQ("/", parent.c_str());
dir = "/test/test";
parent = utils::path::get_parent_path(dir);
EXPECT_STREQ("/test", parent.c_str());
}
{
auto dir = L"/test";
auto parent = utils::path::get_parent_path(dir);
EXPECT_STREQ(L"/", parent.c_str());
dir = L"/test/test";
parent = utils::path::get_parent_path(dir);
EXPECT_STREQ(L"/test", parent.c_str());
}
#endif // defined(_WIN32)
}
TEST(utils_path, contains_trash_directory) {
#if defined(_WIN32)
{
auto dir = R"(c:\$recycle.bin)";
EXPECT_TRUE(utils::path::contains_trash_directory(dir));
dir = R"(c:\$recycle.bin\moose.txt)";
EXPECT_TRUE(utils::path::contains_trash_directory(dir));
}
{
auto dir = LR"(c:\$recycle.bin)";
EXPECT_TRUE(utils::path::contains_trash_directory(dir));
dir = LR"(c:\$recycle.bin\moose.txt)";
EXPECT_TRUE(utils::path::contains_trash_directory(dir));
}
#else // !defined(_WIN32)
{
auto dir = "/$recycle.bin";
EXPECT_TRUE(utils::path::contains_trash_directory(dir));
dir = "/$recycle.bin/moose.txt";
EXPECT_TRUE(utils::path::contains_trash_directory(dir));
}
{
auto dir = L"/$recycle.bin";
EXPECT_TRUE(utils::path::contains_trash_directory(dir));
dir = L"/$recycle.bin/moose.txt";
EXPECT_TRUE(utils::path::contains_trash_directory(dir));
}
#endif // defined(_WIN32)
}
TEST(utils_path, does_not_contain_trash_directory) {
#if defined(_WIN32)
{
auto dir = R"(c:\recycle.bin)";
EXPECT_FALSE(utils::path::contains_trash_directory(dir));
dir = R"(c:\recycle.bin\moose.txt)";
EXPECT_FALSE(utils::path::contains_trash_directory(dir));
}
{
auto dir = LR"(c:\recycle.bin)";
EXPECT_FALSE(utils::path::contains_trash_directory(dir));
dir = LR"(c:\recycle.bin\moose.txt)";
EXPECT_FALSE(utils::path::contains_trash_directory(dir));
}
#else // !defined(_WIN32)
{
auto dir = "/recycle.bin";
EXPECT_FALSE(utils::path::contains_trash_directory(dir));
dir = "/recycle.bin/moose.txt)";
EXPECT_FALSE(utils::path::contains_trash_directory(dir));
}
{
auto dir = L"/recycle.bin";
EXPECT_FALSE(utils::path::contains_trash_directory(dir));
dir = L"/recycle.bin/moose.txt)";
EXPECT_FALSE(utils::path::contains_trash_directory(dir));
}
#endif // defined(_WIN32)
}
} // namespace fifthgrid

View File

@@ -0,0 +1,143 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
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,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "test.hpp"
namespace fifthgrid {
TEST(utils_string, begins_with) {
std::string str{"moose"};
EXPECT_TRUE(utils::string::begins_with(str, "m"));
EXPECT_TRUE(utils::string::begins_with(str, "mo"));
EXPECT_TRUE(utils::string::begins_with(str, "moo"));
EXPECT_TRUE(utils::string::begins_with(str, "moos"));
EXPECT_TRUE(utils::string::begins_with(str, "moose"));
EXPECT_FALSE(utils::string::begins_with(str, "a"));
EXPECT_FALSE(utils::string::begins_with(str, "ma"));
EXPECT_FALSE(utils::string::begins_with(str, "moose1"));
std::wstring str_w{L"moose"};
EXPECT_TRUE(utils::string::begins_with(str_w, L"m"));
EXPECT_TRUE(utils::string::begins_with(str_w, L"mo"));
EXPECT_TRUE(utils::string::begins_with(str_w, L"moo"));
EXPECT_TRUE(utils::string::begins_with(str_w, L"moos"));
EXPECT_TRUE(utils::string::begins_with(str_w, L"moose"));
EXPECT_FALSE(utils::string::begins_with(str_w, L"a"));
EXPECT_FALSE(utils::string::begins_with(str_w, L"ma"));
EXPECT_FALSE(utils::string::begins_with(str_w, L"moose1"));
}
TEST(utils_string, contains) {
std::string str{R"(\\)"};
EXPECT_TRUE(utils::string::contains(str, "\\"));
EXPECT_FALSE(utils::string::contains(str, "/"));
std::wstring str_w{LR"(\\)"};
EXPECT_TRUE(utils::string::contains(str_w, L"\\"));
EXPECT_FALSE(utils::string::contains(str_w, L"/"));
}
TEST(utils_string, replace) {
std::string str{"moose"};
utils::string::replace(str, 'o', '0');
EXPECT_STREQ("m00se", str.c_str());
std::wstring str_w{L"moose"};
utils::string::replace(str_w, 'o', '0');
EXPECT_STREQ(L"m00se", str_w.c_str());
std::string str2{"\\\\\\"};
utils::string::replace(str2, '\\', '/');
EXPECT_STREQ("///", str2.c_str());
std::wstring str_w2{L"\\\\\\"};
utils::string::replace(str_w2, '\\', '/');
EXPECT_STREQ(L"///", str_w2.c_str());
std::string str3{"///"};
utils::string::replace(str3, '/', '\\');
EXPECT_STREQ("\\\\\\", str3.c_str());
std::wstring str_w3{L"///"};
utils::string::replace(str_w3, '/', '\\');
EXPECT_STREQ(L"\\\\\\", str_w3.c_str());
str.clear();
utils::string::replace(str, '/', '\\');
EXPECT_STREQ("", str.c_str());
str_w.clear();
utils::string::replace(str_w, '/', '\\');
EXPECT_STREQ(L"", str_w.c_str());
}
TEST(utils_string, replace_string) {
std::string str{"moose"};
utils::string::replace(str, "o", "0");
EXPECT_STREQ("m00se", str.c_str());
std::wstring str_w{L"moose"};
utils::string::replace(str_w, L"o", L"0");
EXPECT_STREQ(L"m00se", str_w.c_str());
}
TEST(utils_string, is_numeric) {
EXPECT_TRUE(utils::string::is_numeric("100"));
EXPECT_TRUE(utils::string::is_numeric("+100"));
EXPECT_TRUE(utils::string::is_numeric("-100"));
EXPECT_TRUE(utils::string::is_numeric("100.00"));
EXPECT_TRUE(utils::string::is_numeric("+100.00"));
EXPECT_TRUE(utils::string::is_numeric("-100.00"));
EXPECT_FALSE(utils::string::is_numeric("1.00.00"));
EXPECT_FALSE(utils::string::is_numeric("+1.00.00"));
EXPECT_FALSE(utils::string::is_numeric("-1.00.00"));
EXPECT_FALSE(utils::string::is_numeric("a1"));
EXPECT_FALSE(utils::string::is_numeric("1a"));
EXPECT_FALSE(utils::string::is_numeric("+"));
EXPECT_FALSE(utils::string::is_numeric("-"));
EXPECT_FALSE(utils::string::is_numeric(""));
}
TEST(utils_string, to_bool) {
EXPECT_TRUE(utils::string::to_bool("1"));
EXPECT_TRUE(utils::string::to_bool("-1"));
EXPECT_TRUE(utils::string::to_bool("0.1"));
EXPECT_TRUE(utils::string::to_bool("-0.1"));
EXPECT_TRUE(utils::string::to_bool("00000.1000000"));
EXPECT_TRUE(utils::string::to_bool("true"));
EXPECT_FALSE(utils::string::to_bool("false"));
EXPECT_FALSE(utils::string::to_bool("0"));
EXPECT_FALSE(utils::string::to_bool("00000.00000"));
}
TEST(utils_string, utf8_string_conversion) {
std::wstring ws = L"Hello 🌍 — 𝄞 漢字";
std::wstring ws2 = utils::string::from_utf8(utils::string::to_utf8(ws));
EXPECT_STREQ(ws.c_str(), ws2.c_str());
}
} // namespace fifthgrid

View File

@@ -0,0 +1,253 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
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,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "test.hpp"
namespace fifthgrid {
TEST(utils_ttl_cache, can_construct_cache) {
utils::ttl_cache<std::uint8_t> cache;
EXPECT_EQ(utils::ttl_cache<std::uint8_t>::default_expiration,
cache.get_ttl());
}
TEST(utils_ttl_cache, can_construct_cache_with_ttl) {
utils::ttl_cache<std::uint8_t> cache(std::chrono::milliseconds(1000U));
EXPECT_EQ(std::chrono::milliseconds(1000U), cache.get_ttl());
}
TEST(utils_ttl_cache, can_change_ttl) {
utils::ttl_cache<std::uint8_t> cache;
cache.set_ttl(std::chrono::milliseconds(1000U));
EXPECT_EQ(std::chrono::milliseconds(1000U), cache.get_ttl());
}
TEST(utils_ttl_cache, can_set_and_get) {
utils::ttl_cache<std::uint8_t> cache;
cache.set("/test", 21U);
auto data = cache.get("/test");
ASSERT_NE(nullptr, data.get());
EXPECT_EQ(21U, data->load());
}
TEST(utils_ttl_cache, get_returns_nullptr_for_api_path_not_in_cache) {
utils::ttl_cache<std::uint8_t> cache;
auto data = cache.get("/test");
ASSERT_EQ(nullptr, data.get());
}
TEST(utils_ttl_cache, set_and_get_returns_value_and_refreshes_ttl) {
utils::ttl_cache<std::uint8_t> cache(std::chrono::milliseconds(1000U));
cache.set("/test", 7U);
auto data = cache.get("/test");
{
EXPECT_TRUE(cache.contains("/test"));
ASSERT_NE(data, nullptr);
EXPECT_EQ(7U, data->load());
}
std::this_thread::sleep_for(std::chrono::milliseconds(200U));
{
EXPECT_TRUE(cache.contains("/test"));
auto data2 = cache.get("/test");
ASSERT_NE(data2, nullptr);
ASSERT_EQ(data.get(), data2.get());
EXPECT_EQ(7U, data2->load());
}
{
std::this_thread::sleep_for(std::chrono::milliseconds(800U));
cache.purge_expired();
auto data3 = cache.get("/test");
EXPECT_TRUE(cache.contains("/test"));
ASSERT_NE(data3, nullptr);
ASSERT_EQ(data.get(), data3.get());
EXPECT_EQ(7U, data3->load());
}
}
TEST(utils_ttl_cache, entry_expires_without_refresh) {
utils::ttl_cache<std::uint8_t> cache(std::chrono::milliseconds(50U));
cache.set("/test", 42U);
std::this_thread::sleep_for(std::chrono::milliseconds(51U));
cache.purge_expired();
EXPECT_FALSE(cache.contains("/test"));
auto data = cache.get("/test");
EXPECT_EQ(nullptr, data.get());
}
TEST(utils_ttl_cache, can_erase) {
utils::ttl_cache<std::uint8_t> cache(std::chrono::milliseconds(50U));
cache.set("/test", 42U);
cache.erase("/test");
EXPECT_FALSE(cache.contains("/test"));
auto data = cache.get("/test");
EXPECT_EQ(nullptr, data.get());
}
TEST(utils_ttl_cache, can_clear) {
utils::ttl_cache<std::uint8_t> cache(std::chrono::milliseconds(50U));
cache.set("/test", 42U);
cache.set("/test2", 42U);
EXPECT_TRUE(cache.contains("/test"));
EXPECT_TRUE(cache.contains("/test2"));
cache.clear();
{
EXPECT_FALSE(cache.contains("/test"));
auto data = cache.get("/test");
EXPECT_EQ(nullptr, data.get());
}
{
EXPECT_FALSE(cache.contains("/test2"));
auto data = cache.get("/test2");
EXPECT_EQ(nullptr, data.get());
}
}
TEST(utils_ttl_cache, can_handle_concurrent_access) {
utils::ttl_cache<std::uint8_t> cache(std::chrono::milliseconds(5000U));
std::atomic<bool> start{false};
std::thread writer([&] {
while (not start.load()) {
}
for (std::uint8_t ttl = 0U; ttl < 100U; ++ttl) {
cache.set("/key", ttl);
std::this_thread::yield();
}
});
std::thread reader([&] {
while (not start.load()) {
}
for (std::uint8_t ttl = 0U; ttl < 100U; ++ttl) {
auto data = cache.get("/key");
if (data) {
[[maybe_unused]] auto res = data->load();
}
std::this_thread::yield();
}
});
start = true;
writer.join();
reader.join();
auto data = cache.get("/key");
ASSERT_NE(data, nullptr);
[[maybe_unused]] auto res = data->load();
}
TEST(utils_ttl_cache, can_handle_custom_atomic) {
utils::ttl_cache<std::string, utils::atomic> cache(
std::chrono::milliseconds(5000U));
cache.set("/test", "test");
auto data = cache.get("/test");
ASSERT_NE(nullptr, data.get());
EXPECT_STREQ("test", data->load().c_str());
}
TEST(utils_ttl_cache, get_renews_after_ttl_if_purge_expired_is_not_called) {
utils::ttl_cache<std::uint8_t> cache(std::chrono::milliseconds(50U));
cache.set("/test", 9U);
std::this_thread::sleep_for(std::chrono::milliseconds(75U));
auto data = cache.get("/test");
ASSERT_NE(nullptr, data.get());
EXPECT_EQ(9U, data->load());
cache.purge_expired();
EXPECT_TRUE(cache.contains("/test"));
}
TEST(utils_ttl_cache, can_update_data) {
utils::ttl_cache<std::uint8_t> cache;
cache.set("/test", 1U);
auto data = cache.get("/test");
ASSERT_NE(nullptr, data.get());
EXPECT_EQ(1U, data->load());
cache.set("/test", 2U);
auto data2 = cache.get("/test");
ASSERT_NE(nullptr, data2.get());
EXPECT_EQ(data.get(), data2.get());
EXPECT_EQ(2U, data2->load());
}
TEST(utils_ttl_cache, purge_expired_removes_only_expired_entries) {
utils::ttl_cache<std::uint8_t> cache(std::chrono::milliseconds(1000U));
cache.set("/test1", 1U);
cache.set("/test2", 2U);
std::this_thread::sleep_for(std::chrono::milliseconds(500U));
auto data = cache.get("/test2");
ASSERT_NE(data, nullptr);
std::this_thread::sleep_for(std::chrono::milliseconds(501U));
cache.purge_expired();
EXPECT_FALSE(cache.contains("/test1"));
EXPECT_TRUE(cache.contains("/test2"));
}
TEST(utils_ttl_cache, can_handle_non_existing_items_without_failure) {
utils::ttl_cache<std::uint8_t> cache;
cache.set("/exists", 5U);
EXPECT_TRUE(cache.contains("/exists"));
cache.erase("/not_found");
EXPECT_TRUE(cache.contains("/exists"));
auto data = cache.get("/exists");
ASSERT_NE(nullptr, data.get());
EXPECT_EQ(5U, data->load());
}
TEST(utils_ttl_cache, changing_ttl_affects_only_future_expirations) {
utils::ttl_cache<std::uint8_t> cache(std::chrono::milliseconds(1000U));
cache.set("/test", 11U);
cache.set_ttl(std::chrono::milliseconds(100U));
std::this_thread::sleep_for(std::chrono::milliseconds(200U));
cache.purge_expired();
EXPECT_TRUE(cache.contains("/test"));
auto data = cache.get("/test");
ASSERT_NE(nullptr, data.get());
EXPECT_EQ(11U, data->load());
std::this_thread::sleep_for(std::chrono::milliseconds(200U));
cache.purge_expired();
EXPECT_FALSE(cache.contains("/test"));
}
} // namespace fifthgrid