v2.1.0-rc (#63)

Reviewed-on: #63
This commit is contained in:
2025-10-16 17:23:36 -05:00
parent 5ab7301cbe
commit f198cd49ee
471 changed files with 24173 additions and 9459 deletions

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 repertory {
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 repertory

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

@@ -35,7 +35,8 @@ TEST(utils_encrypting_reader, read_file_data) {
EXPECT_TRUE(source_file);
if (source_file) {
utils::encryption::encrypting_reader reader(
"test.dat", source_file.get_path(), get_stop_requested, token);
"test.dat", source_file.get_path(), get_stop_requested, token,
std::nullopt);
for (std::uint8_t i = 0U; i < 8U; i++) {
data_buffer buffer(
@@ -67,6 +68,109 @@ TEST(utils_encrypting_reader, read_file_data) {
}
}
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(
@@ -74,7 +178,8 @@ TEST(utils_encrypting_reader, read_file_data_in_multiple_chunks) {
EXPECT_TRUE(source_file);
if (source_file) {
utils::encryption::encrypting_reader reader(
"test.dat", source_file.get_path(), get_stop_requested, token);
"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(
@@ -114,6 +219,128 @@ TEST(utils_encrypting_reader, read_file_data_in_multiple_chunks) {
}
}
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(
@@ -121,7 +348,8 @@ TEST(utils_encrypting_reader, read_file_data_as_stream) {
EXPECT_TRUE(source_file);
if (source_file) {
utils::encryption::encrypting_reader reader(
"test.dat", source_file.get_path(), get_stop_requested, token);
"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());
@@ -166,6 +394,129 @@ TEST(utils_encrypting_reader, read_file_data_as_stream) {
}
}
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(
@@ -173,7 +524,8 @@ TEST(utils_encrypting_reader, read_file_data_in_multiple_chunks_as_stream) {
EXPECT_TRUE(source_file);
if (source_file) {
utils::encryption::encrypting_reader reader(
"test.dat", source_file.get_path(), get_stop_requested, token);
"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());
@@ -220,6 +572,142 @@ TEST(utils_encrypting_reader, read_file_data_in_multiple_chunks_as_stream) {
}
}
}
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 repertory
#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 repertory {
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 repertory
#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 = repertory::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 repertory::utils::hash::hash_256_t &key,
bool with_kdf,
repertory::utils::encryption::kdf_config &kdf)
-> std::pair<repertory::data_buffer, std::uint64_t> {
repertory::data_buffer blob;
if (with_kdf) {
auto hdr = kdf.to_header();
blob.insert(blob.end(), hdr.begin(), hdr.end());
}
auto data_chunk =
repertory::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);
repertory::data_buffer buffer;
repertory::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 repertory::data_buffer &cipher_blob)
-> repertory::utils::encryption::reader_func_t {
return [&cipher_blob](repertory::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 repertory {
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 repertory
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)

View File

@@ -24,6 +24,10 @@
#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
@@ -32,24 +36,21 @@ 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::encryption::hash_256_t>(token);
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::encryption::hash_256_t>("moose");
auto key3 =
utils::encryption::generate_key<utils::encryption::hash_256_t>("moose");
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::encryption::hash_256_t>("moose2");
utils::encryption::generate_key<utils::hash::hash_256_t>("moose2");
EXPECT_NE(key2, key4);
auto key1_w =
utils::encryption::generate_key<utils::encryption::hash_256_t>(token_w);
utils::encryption::generate_key<utils::hash::hash_256_t>(token_w);
EXPECT_NE(key1, key1_w);
#if defined(_WIN32)
EXPECT_STREQ(
@@ -62,34 +63,33 @@ TEST(utils_encryption, generate_key) {
#endif
auto key2_w =
utils::encryption::generate_key<utils::encryption::hash_256_t>(L"moose");
utils::encryption::generate_key<utils::hash::hash_256_t>(L"moose");
auto key3_w =
utils::encryption::generate_key<utils::encryption::hash_256_t>(L"moose");
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::encryption::hash_256_t>(L"moose2");
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::encryption::hash_256_t>(token);
auto key2 = utils::encryption::generate_key<utils::encryption::hash_256_t>(
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::encryption::create_hash_blake2b_256(
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::encryption::hash_256_t>(token_w);
auto key2_w = utils::encryption::generate_key<utils::encryption::hash_256_t>(
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::encryption::create_hash_blake2b_256(std::wstring_view(
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);
@@ -99,22 +99,22 @@ TEST(utils_encryption, generate_key_default_hasher_is_blake2b_256) {
}
TEST(utils_encryption, generate_key_with_hasher) {
auto key1 = utils::encryption::generate_key<utils::encryption::hash_256_t>(
token, utils::encryption::blake2b_256_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::encryption::hash_256_t>(
token, utils::encryption::sha256_hasher);
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::encryption::hash_256_t>(
token_w, utils::encryption::blake2b_256_hasher);
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",
@@ -125,8 +125,8 @@ TEST(utils_encryption, generate_key_with_hasher) {
utils::collection::to_hex_wstring(key1_w).c_str());
#endif
auto key2_w = utils::encryption::generate_key<utils::encryption::hash_256_t>(
token_w, utils::encryption::sha256_hasher);
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)
@@ -144,7 +144,69 @@ TEST(utils_encryption, generate_key_with_hasher) {
}
#if defined(PROJECT_ENABLE_BOOST)
static const std::string buffer = "cow moose dog chicken";
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,
@@ -155,6 +217,17 @@ static void test_encrypted_result(const data_buffer &result) {
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);
@@ -163,7 +236,7 @@ TEST(utils_encryption, encrypt_data_buffer) {
TEST(utils_encryption, encrypt_data_buffer_with_key) {
const auto key =
utils::encryption::generate_key<utils::encryption::hash_256_t>(token);
utils::encryption::generate_key<utils::hash::hash_256_t>(token);
data_buffer result;
utils::encryption::encrypt_data(key, buffer, result);
test_encrypted_result(result);
@@ -179,7 +252,7 @@ TEST(utils_encryption, encrypt_data_pointer) {
TEST(utils_encryption, encrypt_data_pointer_with_key) {
const auto key =
utils::encryption::generate_key<utils::encryption::hash_256_t>(token);
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()),
@@ -189,7 +262,7 @@ TEST(utils_encryption, encrypt_data_pointer_with_key) {
TEST(utils_encryption, decrypt_data_pointer) {
const auto key =
utils::encryption::generate_key<utils::encryption::hash_256_t>(token);
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()),
@@ -205,7 +278,7 @@ TEST(utils_encryption, decrypt_data_pointer) {
TEST(utils_encryption, decrypt_data_buffer_with_key) {
const auto key =
utils::encryption::generate_key<utils::encryption::hash_256_t>(token);
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()),
@@ -220,7 +293,7 @@ TEST(utils_encryption, decrypt_data_buffer_with_key) {
TEST(utils_encryption, decrypt_data_pointer_with_key) {
const auto key =
utils::encryption::generate_key<utils::encryption::hash_256_t>(token);
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()),
@@ -236,7 +309,7 @@ TEST(utils_encryption, decrypt_data_pointer_with_key) {
TEST(utils_encryption, decryption_failure) {
const auto key =
utils::encryption::generate_key<utils::encryption::hash_256_t>(token);
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()),
@@ -280,6 +353,32 @@ TEST(utils_encryption, decrypt_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 repertory

View File

@@ -510,6 +510,7 @@ TEST(utils_file, get_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));
@@ -520,6 +521,7 @@ TEST(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));
@@ -540,6 +542,11 @@ TEST(utils_file, get_time) {
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());
@@ -565,6 +572,11 @@ TEST(utils_file, get_time) {
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());

View File

@@ -25,34 +25,97 @@
namespace repertory {
TEST(utils_hash, hash_type_sizes) {
EXPECT_EQ(32U, utils::encryption::hash_256_t{}.size());
EXPECT_EQ(48U, utils::encryption::hash_384_t{}.size());
EXPECT_EQ(64U, utils::encryption::hash_512_t{}.size());
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::encryption::blake2b_256_hasher,
&utils::encryption::default_create_hash<utils::encryption::hash_256_t>());
EXPECT_EQ(&utils::hash::blake2b_32_hasher,
&utils::hash::default_create_hash<utils::hash::hash_32_t>());
EXPECT_EQ(
&utils::encryption::blake2b_384_hasher,
&utils::encryption::default_create_hash<utils::encryption::hash_384_t>());
EXPECT_EQ(&utils::hash::blake2b_64_hasher,
&utils::hash::default_create_hash<utils::hash::hash_64_t>());
EXPECT_EQ(
&utils::encryption::blake2b_512_hasher,
&utils::encryption::default_create_hash<utils::encryption::hash_512_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::encryption::create_hash_blake2b_256("a"));
utils::hash::create_hash_blake2b_256("a"));
EXPECT_STREQ(
"8928aae63c84d87ea098564d1e03ad813f107add474e56aedd286349c0c03ea4",
hash.c_str());
hash = utils::collection::to_hex_string(
utils::encryption::create_hash_blake2b_256(L"a"));
utils::hash::create_hash_blake2b_256(L"a"));
#if defined(_WIN32)
EXPECT_STREQ(
"d2373b17cd8a8e19e39f52fa4905a274f93805fbb8bb4c7f3cb4b2cd6708ec8a",
@@ -64,7 +127,7 @@ TEST(utils_hash, blake2b_256) {
#endif
hash = utils::collection::to_hex_string(
utils::encryption::create_hash_blake2b_256({1U}));
utils::hash::create_hash_blake2b_256({1U}));
EXPECT_STREQ(
"ee155ace9c40292074cb6aff8c9ccdd273c81648ff1149ef36bcea6ebb8a3e25",
hash.c_str());
@@ -72,13 +135,13 @@ TEST(utils_hash, blake2b_256) {
TEST(utils_hash, blake2b_384) {
auto hash = utils::collection::to_hex_string(
utils::encryption::create_hash_blake2b_384("a"));
utils::hash::create_hash_blake2b_384("a"));
EXPECT_STREQ("7d40de16ff771d4595bf70cbda0c4ea0a066a6046fa73d34471cd4d93d827d7"
"c94c29399c50de86983af1ec61d5dcef0",
hash.c_str());
hash = utils::collection::to_hex_string(
utils::encryption::create_hash_blake2b_384(L"a"));
utils::hash::create_hash_blake2b_384(L"a"));
#if defined(_WIN32)
EXPECT_STREQ("637fe31d1e955760ef31043d525d9321826a778ddbe82fcde45a98394241380"
"96675e2f87e36b53ab223a7fd254198fd",
@@ -90,7 +153,7 @@ TEST(utils_hash, blake2b_384) {
#endif
hash = utils::collection::to_hex_string(
utils::encryption::create_hash_blake2b_384({1U}));
utils::hash::create_hash_blake2b_384({1U}));
EXPECT_STREQ("42cfe875d08d816538103b906bb0b05202e0b09c4e981680c1110684fc7845b"
"c91c178fa167afcc445490644b2bf5f5b",
hash.c_str());
@@ -98,14 +161,14 @@ TEST(utils_hash, blake2b_384) {
TEST(utils_hash, blake2b_512) {
auto hash = utils::collection::to_hex_string(
utils::encryption::create_hash_blake2b_512("a"));
utils::hash::create_hash_blake2b_512("a"));
EXPECT_STREQ(
"333fcb4ee1aa7c115355ec66ceac917c8bfd815bf7587d325aec1864edd24e34d5abe2c6"
"b1b5ee3face62fed78dbef802f2a85cb91d455a8f5249d330853cb3c",
hash.c_str());
hash = utils::collection::to_hex_string(
utils::encryption::create_hash_blake2b_512(L"a"));
utils::hash::create_hash_blake2b_512(L"a"));
#if defined(_WIN32)
EXPECT_STREQ(
"05970b95468b0b1941066ff189091493e73859ce41cde5ad08118e93ea1d81a57a144296"
@@ -119,7 +182,7 @@ TEST(utils_hash, blake2b_512) {
#endif
hash = utils::collection::to_hex_string(
utils::encryption::create_hash_blake2b_512({1U}));
utils::hash::create_hash_blake2b_512({1U}));
EXPECT_STREQ(
"9545ba37b230d8a2e716c4707586542780815b7c4088edcb9af6a9452d50f32474d5ba9a"
"ab52a67aca864ef2696981c2eadf49020416136afd838fb048d21653",
@@ -127,14 +190,14 @@ TEST(utils_hash, blake2b_512) {
}
TEST(utils_hash, sha256) {
auto hash = utils::collection::to_hex_string(
utils::encryption::create_hash_sha256("a"));
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::encryption::create_hash_sha256(L"a"));
hash =
utils::collection::to_hex_string(utils::hash::create_hash_sha256(L"a"));
#if defined(_WIN32)
EXPECT_STREQ(
"ffe9aaeaa2a2d5048174df0b80599ef0197ec024c4b051bc9860cff58ef7f9f3",
@@ -145,23 +208,23 @@ TEST(utils_hash, sha256) {
hash.c_str());
#endif
hash = utils::collection::to_hex_string(
utils::encryption::create_hash_sha256({1U}));
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::encryption::create_hash_sha512("a"));
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::encryption::create_hash_sha512(L"a"));
hash =
utils::collection::to_hex_string(utils::hash::create_hash_sha512(L"a"));
#if defined(_WIN32)
EXPECT_STREQ(
"5c2ca3d50f46ece6066c53bd1a490cbe5f72d2738ae9417332e91e5c3f75205c639d71a9"
@@ -174,8 +237,8 @@ TEST(utils_hash, sha512) {
hash.c_str());
#endif
hash = utils::collection::to_hex_string(
utils::encryption::create_hash_sha512({1U}));
hash =
utils::collection::to_hex_string(utils::hash::create_hash_sha512({1U}));
EXPECT_STREQ(
"7b54b66836c1fbdd13d2441d9e1434dc62ca677fb68f5fe66a464baadecdbd00576f8d6b"
"5ac3bcc80844b7d50b1cc6603444bbe7cfcf8fc0aa1ee3c636d9e339",

View File

@@ -134,4 +134,10 @@ TEST(utils_string, to_bool) {
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 repertory

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 repertory {
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 repertory