BIN
support/3rd_party/boost_1_88_0.tar.gz
(Stored with Git LFS)
vendored
BIN
support/3rd_party/boost_1_88_0.tar.gz
(Stored with Git LFS)
vendored
Binary file not shown.
1
support/3rd_party/boost_1_88_0.tar.gz.sha256
vendored
1
support/3rd_party/boost_1_88_0.tar.gz.sha256
vendored
@@ -1 +0,0 @@
|
||||
3621533e820dcab1e8012afd583c0c73cf0f77694952b81352bf38c1488f9cb4 boost_1_88_0.tar.gz
|
||||
BIN
support/3rd_party/boost_1_89_0.tar.gz
(Stored with Git LFS)
vendored
Normal file
BIN
support/3rd_party/boost_1_89_0.tar.gz
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
1
support/3rd_party/boost_1_89_0.tar.gz.sha256
vendored
Normal file
1
support/3rd_party/boost_1_89_0.tar.gz.sha256
vendored
Normal file
@@ -0,0 +1 @@
|
||||
9de758db755e8330a01d995b0a24d09798048400ac25c03fc5ea9be364b13c93 boost_1_89_0.tar.gz
|
||||
BIN
support/3rd_party/cpp-httplib-0.23.1.tar.gz
(Stored with Git LFS)
vendored
BIN
support/3rd_party/cpp-httplib-0.23.1.tar.gz
(Stored with Git LFS)
vendored
Binary file not shown.
@@ -1 +0,0 @@
|
||||
410a1347ed6bcbcc4a19af8ed8ad3873fe9fa97731d52db845c4c78f3f9c31e6 cpp-httplib-0.23.1.tar.gz
|
||||
BIN
support/3rd_party/cpp-httplib-0.26.0.tar.gz
(Stored with Git LFS)
vendored
Normal file
BIN
support/3rd_party/cpp-httplib-0.26.0.tar.gz
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
1
support/3rd_party/cpp-httplib-0.26.0.tar.gz.sha256
vendored
Normal file
1
support/3rd_party/cpp-httplib-0.26.0.tar.gz.sha256
vendored
Normal file
@@ -0,0 +1 @@
|
||||
a66f908f50ccb119769adce44fe1eac75f81b6ffab7c4ac0211bb663ffeb2688 cpp-httplib-0.26.0.tar.gz
|
||||
BIN
support/3rd_party/curl-8.15.0.tar.gz
(Stored with Git LFS)
vendored
BIN
support/3rd_party/curl-8.15.0.tar.gz
(Stored with Git LFS)
vendored
Binary file not shown.
1
support/3rd_party/curl-8.15.0.tar.gz.sha256
vendored
1
support/3rd_party/curl-8.15.0.tar.gz.sha256
vendored
@@ -1 +0,0 @@
|
||||
2937cadde007aa3a52a17c21ac9153ea054700f37926d1d96602bf07e888c847 curl-8.15.0.tar.gz
|
||||
BIN
support/3rd_party/curl-8.16.0.tar.gz
(Stored with Git LFS)
vendored
Normal file
BIN
support/3rd_party/curl-8.16.0.tar.gz
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
1
support/3rd_party/curl-8.16.0.tar.gz.sha256
vendored
Normal file
1
support/3rd_party/curl-8.16.0.tar.gz.sha256
vendored
Normal file
@@ -0,0 +1 @@
|
||||
d4d9a5001b491f5726efe9b50bc4aad03029506e73c9261272e809c64b05e814 curl-8.16.0.tar.gz
|
||||
16
support/3rd_party/icu_configure.sh
vendored
Executable file
16
support/3rd_party/icu_configure.sh
vendored
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [ "$(uname -m)" == "arm64" ] &&
|
||||
[ "${PROJECT_IS_ARM64}" == "0" ]; then
|
||||
HOST_CFG="--host=x86_64-apple-darwin"
|
||||
export CC="clang -arch x86_64"
|
||||
export CXX="clang++ -arch x86_64"
|
||||
fi
|
||||
|
||||
CXXFLAGS="-std=gnu++17 -march=$1 -mtune=generic" ./configure \
|
||||
--disable-samples \
|
||||
--disable-tests \
|
||||
--enable-shared=$3 \
|
||||
--enable-static=yes \
|
||||
--prefix="$2" \
|
||||
${HOST_CFG}
|
||||
2
support/3rd_party/json-3.12.0.tar.gz.sha256
vendored
2
support/3rd_party/json-3.12.0.tar.gz.sha256
vendored
@@ -1 +1 @@
|
||||
4b92eb0c06d10683f7447ce9406cb97cd4b453be18d7279320f7b2f025c10187 json-3.12.0.tar.gz
|
||||
4b92eb0c06d10683f7447ce9406cb97cd4b453be18d7279320f7b2f025c10187 *json-3.12.0.tar.gz
|
||||
|
||||
7
support/3rd_party/libsodium_configure.sh
vendored
7
support/3rd_party/libsodium_configure.sh
vendored
@@ -1,7 +1,12 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [ "$1" == "mingw64" ]; then
|
||||
HOST_CFG=--host=x86_64-w64-mingw32
|
||||
elif [ "$(uname -s)" == "Darwin" ] && [ "$(uname -m)" == "arm64" ] &&
|
||||
[ "${PROJECT_IS_ARM64}" == "0" ]; then
|
||||
HOST_CFG="--host=x86_64-apple-darwin"
|
||||
export CC="clang -arch x86_64"
|
||||
export CXX="clang++ -arch x86_64"
|
||||
fi
|
||||
|
||||
CFLAGS="-O3 -fomit-frame-pointer -march=$2 -mtune=generic" ./configure \
|
||||
|
||||
BIN
support/3rd_party/mingw64/gcc-15.1.0.tar.gz
(Stored with Git LFS)
vendored
BIN
support/3rd_party/mingw64/gcc-15.1.0.tar.gz
(Stored with Git LFS)
vendored
Binary file not shown.
@@ -1 +0,0 @@
|
||||
51b9919ea69c980d7a381db95d4be27edf73b21254eb13d752a08003b4d013b1 *gcc-15.1.0.tar.gz
|
||||
BIN
support/3rd_party/mingw64/gcc-15.2.0.tar.gz
(Stored with Git LFS)
vendored
Normal file
BIN
support/3rd_party/mingw64/gcc-15.2.0.tar.gz
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
1
support/3rd_party/mingw64/gcc-15.2.0.tar.gz.sha256
vendored
Normal file
1
support/3rd_party/mingw64/gcc-15.2.0.tar.gz.sha256
vendored
Normal file
@@ -0,0 +1 @@
|
||||
7294d65cc1a0558cb815af0ca8c7763d86f7a31199794ede3f630c0d1b0a5723 gcc-15.2.0.tar.gz
|
||||
BIN
support/3rd_party/mingw64/innosetup-6.5.4.exe
vendored
Normal file
BIN
support/3rd_party/mingw64/innosetup-6.5.4.exe
vendored
Normal file
Binary file not shown.
1
support/3rd_party/mingw64/innosetup-6.5.4.exe.sha256
vendored
Normal file
1
support/3rd_party/mingw64/innosetup-6.5.4.exe.sha256
vendored
Normal file
@@ -0,0 +1 @@
|
||||
fa73bf47a4da250d185d07561c2bfda387e5e20db77e4570004cf6a133cc10b1 innosetup-6.5.4.exe
|
||||
BIN
support/3rd_party/openssl-3.5.1.tar.gz
(Stored with Git LFS)
vendored
BIN
support/3rd_party/openssl-3.5.1.tar.gz
(Stored with Git LFS)
vendored
Binary file not shown.
@@ -1 +0,0 @@
|
||||
529043b15cffa5f36077a4d0af83f3de399807181d607441d734196d889b641f openssl-3.5.1.tar.gz
|
||||
BIN
support/3rd_party/openssl-3.6.0.tar.gz
(Stored with Git LFS)
vendored
Normal file
BIN
support/3rd_party/openssl-3.6.0.tar.gz
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
1
support/3rd_party/openssl-3.6.0.tar.gz.sha256
vendored
Normal file
1
support/3rd_party/openssl-3.6.0.tar.gz.sha256
vendored
Normal file
@@ -0,0 +1 @@
|
||||
b6a5f44b7eb69e3fa35dbf15524405b44837a481d43d81daddde3ff21fcbb8e9 openssl-3.6.0.tar.gz
|
||||
BIN
support/3rd_party/rocksdb-10.4.2.tar.gz
(Stored with Git LFS)
vendored
BIN
support/3rd_party/rocksdb-10.4.2.tar.gz
(Stored with Git LFS)
vendored
Binary file not shown.
@@ -1 +0,0 @@
|
||||
afccfab496556904900afacf7d99887f1d50cb893e5d2288bd502db233adacac rocksdb-10.4.2.tar.gz
|
||||
BIN
support/3rd_party/rocksdb-10.5.1.tar.gz
(Stored with Git LFS)
vendored
Normal file
BIN
support/3rd_party/rocksdb-10.5.1.tar.gz
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
1
support/3rd_party/rocksdb-10.5.1.tar.gz.sha256
vendored
Normal file
1
support/3rd_party/rocksdb-10.5.1.tar.gz.sha256
vendored
Normal file
@@ -0,0 +1 @@
|
||||
7ec942baab802b2845188d02bc5d4e42c29236e61bcbc08f5b3a6bdd92290c22 rocksdb-10.5.1.tar.gz
|
||||
BIN
support/3rd_party/sqlite-amalgamation-3500300.zip
(Stored with Git LFS)
vendored
BIN
support/3rd_party/sqlite-amalgamation-3500300.zip
(Stored with Git LFS)
vendored
Binary file not shown.
@@ -1 +0,0 @@
|
||||
9ad6d16cbc1df7cd55c8b55127c82a9bca5e9f287818de6dc87e04e73599d754 sqlite-amalgamation-3500300.zip
|
||||
BIN
support/3rd_party/sqlite-amalgamation-3500400.zip
(Stored with Git LFS)
vendored
Normal file
BIN
support/3rd_party/sqlite-amalgamation-3500400.zip
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
1
support/3rd_party/sqlite-amalgamation-3500400.zip.sha256
vendored
Normal file
1
support/3rd_party/sqlite-amalgamation-3500400.zip.sha256
vendored
Normal file
@@ -0,0 +1 @@
|
||||
1d3049dd0f830a025a53105fc79fd2ab9431aea99e137809d064d8ee8356b032 sqlite-amalgamation-3500400.zip
|
||||
BIN
support/3rd_party/winfsp-2.1.25156.msi
(Stored with Git LFS)
vendored
Normal file
BIN
support/3rd_party/winfsp-2.1.25156.msi
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
1
support/3rd_party/winfsp-2.1.25156.msi.sha256
vendored
Normal file
1
support/3rd_party/winfsp-2.1.25156.msi.sha256
vendored
Normal file
@@ -0,0 +1 @@
|
||||
073a70e00f77423e34bed98b86e600def93393ba5822204fac57a29324db9f7a *winfsp-2.1.25156.msi
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
#include "utils/config.hpp"
|
||||
|
||||
#include "utils/atomic.hpp"
|
||||
#include "utils/base64.hpp"
|
||||
#include "utils/collection.hpp"
|
||||
#if defined(_WIN32)
|
||||
@@ -49,6 +50,7 @@
|
||||
#include "utils/path.hpp"
|
||||
#include "utils/string.hpp"
|
||||
#include "utils/time.hpp"
|
||||
#include "utils/ttl_cache.hpp"
|
||||
#if !defined(_WIN32)
|
||||
#include "utils/unix.hpp"
|
||||
#endif // !defined(_WIN32)
|
||||
|
||||
118
support/include/utils/atomic.hpp
Normal file
118
support/include/utils/atomic.hpp
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
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 REPERTORY_INCLUDE_UTILS_ATOMIC_HPP_
|
||||
#define REPERTORY_INCLUDE_UTILS_ATOMIC_HPP_
|
||||
|
||||
#include "utils/config.hpp"
|
||||
|
||||
namespace repertory::utils {
|
||||
template <typename data_t> class atomic final {
|
||||
public:
|
||||
atomic() : mtx_(std::make_shared<std::mutex>()) {}
|
||||
|
||||
atomic(const atomic &at_data)
|
||||
: data_(at_data.load()), mtx_(std::make_shared<std::mutex>()) {}
|
||||
|
||||
atomic(data_t data)
|
||||
: data_(std::move(data)), mtx_(std::make_shared<std::mutex>()) {}
|
||||
|
||||
atomic(atomic &&) = default;
|
||||
|
||||
~atomic() = default;
|
||||
|
||||
private:
|
||||
data_t data_;
|
||||
std::shared_ptr<std::mutex> mtx_;
|
||||
|
||||
public:
|
||||
[[nodiscard]] auto load() const -> data_t {
|
||||
mutex_lock lock(*mtx_);
|
||||
return data_;
|
||||
}
|
||||
|
||||
auto store(data_t data) -> data_t {
|
||||
mutex_lock lock(*mtx_);
|
||||
data_ = std::move(data);
|
||||
return data_;
|
||||
}
|
||||
|
||||
auto operator=(const atomic &at_data) -> atomic & {
|
||||
if (&at_data == this) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
store(at_data.load());
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator=(atomic &&) -> atomic & = default;
|
||||
|
||||
auto operator=(data_t data) -> atomic & {
|
||||
if (&data == &data_) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
store(std::move(data));
|
||||
return *this;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto operator==(const atomic &at_data) const -> bool {
|
||||
if (&at_data == this) {
|
||||
return true;
|
||||
}
|
||||
|
||||
mutex_lock lock(*mtx_);
|
||||
return at_data.load() == data_;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto operator==(const data_t &data) const -> bool {
|
||||
if (&data == &data_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
mutex_lock lock(*mtx_);
|
||||
return data == data_;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto operator!=(const atomic &at_data) const -> bool {
|
||||
if (&at_data == this) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mutex_lock lock(*mtx_);
|
||||
return at_data.load() != data_;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto operator!=(const data_t &data) const -> bool {
|
||||
if (&data == &data_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mutex_lock lock(*mtx_);
|
||||
return data != data_;
|
||||
}
|
||||
|
||||
[[nodiscard]] operator data_t() const { return load(); }
|
||||
};
|
||||
} // namespace repertory::utils
|
||||
|
||||
#endif // REPERTORY_INCLUDE_UTILS_ATOMIC_HPP_
|
||||
@@ -1,10 +1,11 @@
|
||||
// NOLINTBEGIN
|
||||
#ifndef _MACARON_BASE64_H_
|
||||
#define _MACARON_BASE64_H_
|
||||
#ifndef MACARON_BASE64_H_
|
||||
#define MACARON_BASE64_H_
|
||||
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2016 tomykaira
|
||||
* Copyright (c) 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
|
||||
@@ -39,121 +40,272 @@
|
||||
#endif
|
||||
|
||||
#include <array>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace macaron::Base64 {
|
||||
static std::string Encode(const unsigned char *data, std::size_t len) {
|
||||
static constexpr std::array<unsigned char, 64U> sEncodingTable{
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
||||
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
||||
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/',
|
||||
};
|
||||
|
||||
auto in_len{len};
|
||||
std::string ret;
|
||||
if (in_len > 0) {
|
||||
std::size_t out_len{4U * ((in_len + 2U) / 3U)};
|
||||
ret = std::string(out_len, '\0');
|
||||
std::size_t i;
|
||||
auto *p = reinterpret_cast<unsigned char *>(ret.data());
|
||||
// --- Alphabets --------------------------------------------------------------
|
||||
|
||||
for (i = 0U; i < in_len - 2U; i += 3U) {
|
||||
*p++ = sEncodingTable[(data[i] >> 2U) & 0x3F];
|
||||
*p++ = sEncodingTable[((data[i] & 0x3) << 4U) |
|
||||
((int)(data[i + 1U] & 0xF0) >> 4U)];
|
||||
*p++ = sEncodingTable[((data[i + 1] & 0xF) << 2) |
|
||||
((int)(data[i + 2U] & 0xC0) >> 6U)];
|
||||
*p++ = sEncodingTable[data[i + 2U] & 0x3F];
|
||||
static constexpr std::array<unsigned char, 64U> kStdAlphabet{
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
||||
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
||||
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/',
|
||||
};
|
||||
|
||||
static constexpr std::array<unsigned char, 64U> kUrlAlphabet{
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
||||
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
||||
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_',
|
||||
};
|
||||
|
||||
// Decoding table that accepts BOTH standard and URL-safe alphabets.
|
||||
static constexpr std::array<unsigned char, 256U> kDecodingTable = [] {
|
||||
std::array<unsigned char, 256U> t{};
|
||||
t.fill(64U);
|
||||
// 'A'-'Z'
|
||||
for (unsigned char c = 'A'; c <= 'Z'; ++c)
|
||||
t[c] = static_cast<unsigned char>(c - 'A');
|
||||
// 'a'-'z'
|
||||
for (unsigned char c = 'a'; c <= 'z'; ++c)
|
||||
t[c] = static_cast<unsigned char>(26 + c - 'a');
|
||||
// '0'-'9'
|
||||
for (unsigned char c = '0'; c <= '9'; ++c)
|
||||
t[c] = static_cast<unsigned char>(52 + c - '0');
|
||||
// Standard extras
|
||||
t[static_cast<unsigned char>('+')] = 62U;
|
||||
t[static_cast<unsigned char>('/')] = 63U;
|
||||
// URL-safe extras
|
||||
t[static_cast<unsigned char>('-')] = 62U;
|
||||
t[static_cast<unsigned char>('_')] = 63U;
|
||||
return t;
|
||||
}();
|
||||
|
||||
// --- Encoding ---------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Encode to Base64.
|
||||
* @param data pointer to bytes
|
||||
* @param len number of bytes
|
||||
* @param url_safe if true, use URL-safe alphabet ("-","_") instead of ("+","/")
|
||||
* @param pad if true, add '=' padding; if false, omit padding (RFC 4648
|
||||
* §5)
|
||||
*/
|
||||
static std::string Encode(const unsigned char *data, std::size_t len,
|
||||
bool url_safe = false, bool pad = true) {
|
||||
const auto &alpha = url_safe ? kUrlAlphabet : kStdAlphabet;
|
||||
|
||||
std::string out;
|
||||
if (len == 0U) {
|
||||
return out;
|
||||
}
|
||||
|
||||
const std::size_t full_blocks = len / 3U;
|
||||
const std::size_t rem = len % 3U;
|
||||
|
||||
std::size_t out_len{};
|
||||
if (pad) {
|
||||
out_len = 4U * ((len + 2U) / 3U);
|
||||
} else {
|
||||
// Unpadded length per RFC 4648 §5
|
||||
out_len = 4U * full_blocks + (rem == 0U ? 0U : (rem == 1U ? 2U : 3U));
|
||||
}
|
||||
out.assign(out_len, '\0');
|
||||
|
||||
auto *p = reinterpret_cast<unsigned char *>(out.data());
|
||||
std::size_t i = 0;
|
||||
|
||||
// Full 3-byte blocks -> 4 chars
|
||||
for (; i + 2U < len; i += 3U) {
|
||||
const unsigned char b0 = data[i + 0U];
|
||||
const unsigned char b1 = data[i + 1U];
|
||||
const unsigned char b2 = data[i + 2U];
|
||||
|
||||
*p++ = alpha[(b0 >> 2U) & 0x3F];
|
||||
*p++ = alpha[((b0 & 0x03U) << 4U) | ((b1 >> 4U) & 0x0FU)];
|
||||
*p++ = alpha[((b1 & 0x0FU) << 2U) | ((b2 >> 6U) & 0x03U)];
|
||||
*p++ = alpha[b2 & 0x3FU];
|
||||
}
|
||||
|
||||
// Remainder
|
||||
if (rem == 1U) {
|
||||
const unsigned char b0 = data[i];
|
||||
*p++ = alpha[(b0 >> 2U) & 0x3F];
|
||||
*p++ = alpha[(b0 & 0x03U) << 4U];
|
||||
if (pad) {
|
||||
*p++ = '=';
|
||||
*p++ = '=';
|
||||
}
|
||||
if (i < in_len) {
|
||||
*p++ = sEncodingTable[(data[i] >> 2U) & 0x3F];
|
||||
if (i == (in_len - 1U)) {
|
||||
*p++ = sEncodingTable[((data[i] & 0x3) << 4U)];
|
||||
*p++ = '=';
|
||||
} else {
|
||||
*p++ = sEncodingTable[((data[i] & 0x3) << 4U) |
|
||||
((int)(data[i + 1U] & 0xF0) >> 4U)];
|
||||
*p++ = sEncodingTable[((data[i + 1U] & 0xF) << 2U)];
|
||||
}
|
||||
} else if (rem == 2U) {
|
||||
const unsigned char b0 = data[i + 0U];
|
||||
const unsigned char b1 = data[i + 1U];
|
||||
*p++ = alpha[(b0 >> 2U) & 0x3F];
|
||||
*p++ = alpha[((b0 & 0x03U) << 4U) | ((b1 >> 4U) & 0x0FU)];
|
||||
*p++ = alpha[(b1 & 0x0FU) << 2U];
|
||||
if (pad) {
|
||||
*p++ = '=';
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
return out;
|
||||
}
|
||||
|
||||
[[maybe_unused]] static std::string Encode(std::string_view data) {
|
||||
[[maybe_unused]] static std::string
|
||||
Encode(std::string_view data, bool url_safe = false, bool pad = true) {
|
||||
return Encode(reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size());
|
||||
data.size(), url_safe, pad);
|
||||
}
|
||||
|
||||
[[maybe_unused]] static std::string
|
||||
EncodeUrlSafe(const unsigned char *data, std::size_t len, bool pad = false) {
|
||||
return Encode(data, len, /*url_safe=*/true, /*pad=*/pad);
|
||||
}
|
||||
|
||||
[[maybe_unused]] static std::string EncodeUrlSafe(std::string_view data,
|
||||
bool pad = false) {
|
||||
return Encode(reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size(), /*url_safe=*/true, /*pad=*/pad);
|
||||
}
|
||||
|
||||
// --- Decoding ---------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Decode standard OR URL-safe Base64.
|
||||
* Accepts inputs with or without '=' padding.
|
||||
* Throws std::runtime_error on malformed input.
|
||||
*/
|
||||
[[maybe_unused]] static std::vector<unsigned char>
|
||||
Decode(std::string_view input) {
|
||||
static constexpr std::array<unsigned char, 256> kDecodingTable{
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, 52, 53, 54, 55, 56, 57,
|
||||
58, 59, 60, 61, 64, 64, 64, 64, 64, 64, 64, 0, 1, 2, 3, 4, 5, 6,
|
||||
7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
|
||||
25, 64, 64, 64, 64, 64, 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
|
||||
37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64,
|
||||
std::vector<unsigned char> out;
|
||||
if (input.empty()) {
|
||||
return out;
|
||||
}
|
||||
|
||||
std::size_t inLen = input.size();
|
||||
std::size_t rem = inLen % 4U;
|
||||
|
||||
// padded if multiple of 4 and last char is '='
|
||||
bool hasPadding = (rem == 0U) && (inLen >= 4U) && (input[inLen - 1U] == '=');
|
||||
|
||||
// compute output length
|
||||
std::size_t outLen{};
|
||||
if (hasPadding) {
|
||||
outLen = (inLen / 4U) * 3U;
|
||||
if (input[inLen - 1U] == '=')
|
||||
outLen--;
|
||||
if (input[inLen - 2U] == '=')
|
||||
outLen--;
|
||||
} else {
|
||||
if (rem == 1U) {
|
||||
throw std::runtime_error("Invalid Base64 length (mod 4 == 1)");
|
||||
}
|
||||
outLen = (inLen / 4U) * 3U + (rem == 0U ? 0U : (rem == 2U ? 1U : 2U));
|
||||
}
|
||||
|
||||
out.resize(outLen);
|
||||
|
||||
auto readVal = [](unsigned char c) -> unsigned char {
|
||||
unsigned char v = kDecodingTable[c];
|
||||
if (v == 64U) {
|
||||
throw std::runtime_error("Invalid Base64 character");
|
||||
}
|
||||
return v;
|
||||
};
|
||||
|
||||
std::vector<unsigned char> out;
|
||||
if (not input.empty()) {
|
||||
auto in_len{input.size()};
|
||||
if (in_len % 4U != 0U) {
|
||||
throw std::runtime_error("Input data size is not a multiple of 4");
|
||||
}
|
||||
std::size_t i = 0U;
|
||||
std::size_t j = 0U;
|
||||
|
||||
std::size_t out_len{in_len / 4U * 3U};
|
||||
if (input[in_len - 1U] == '=') {
|
||||
out_len--;
|
||||
}
|
||||
if (input[in_len - 2U] == '=') {
|
||||
out_len--;
|
||||
}
|
||||
// process all full unpadded quartets
|
||||
std::size_t lastFull =
|
||||
hasPadding ? (inLen - 4U) : (rem == 0U ? inLen : (inLen - rem));
|
||||
|
||||
out.resize(out_len);
|
||||
while (i + 4U <= lastFull) {
|
||||
unsigned char a = readVal(static_cast<unsigned char>(input[i + 0U]));
|
||||
unsigned char b = readVal(static_cast<unsigned char>(input[i + 1U]));
|
||||
unsigned char c = readVal(static_cast<unsigned char>(input[i + 2U]));
|
||||
unsigned char d = readVal(static_cast<unsigned char>(input[i + 3U]));
|
||||
i += 4U;
|
||||
|
||||
for (std::size_t i = 0U, j = 0U; i < in_len;) {
|
||||
std::uint32_t a =
|
||||
input.at(i) == '='
|
||||
? 0U & i++
|
||||
: kDecodingTable[static_cast<unsigned char>(input.at(i++))];
|
||||
std::uint32_t b =
|
||||
input.at(i) == '='
|
||||
? 0U & i++
|
||||
: kDecodingTable[static_cast<unsigned char>(input.at(i++))];
|
||||
std::uint32_t c =
|
||||
input.at(i) == '='
|
||||
? 0U & i++
|
||||
: kDecodingTable[static_cast<unsigned char>(input.at(i++))];
|
||||
std::uint32_t d =
|
||||
input.at(i) == '='
|
||||
? 0U & i++
|
||||
: kDecodingTable[static_cast<unsigned char>(input.at(i++))];
|
||||
std::uint32_t triple = (static_cast<std::uint32_t>(a) << 18U) |
|
||||
(static_cast<std::uint32_t>(b) << 12U) |
|
||||
(static_cast<std::uint32_t>(c) << 6U) |
|
||||
(static_cast<std::uint32_t>(d));
|
||||
|
||||
std::uint32_t triple =
|
||||
(a << 3U * 6U) + (b << 2U * 6U) + (c << 1U * 6U) + (d << 0U * 6U);
|
||||
if (j < outLen)
|
||||
out[j++] = static_cast<unsigned char>((triple >> 16U) & 0xFFU);
|
||||
if (j < outLen)
|
||||
out[j++] = static_cast<unsigned char>((triple >> 8U) & 0xFFU);
|
||||
if (j < outLen)
|
||||
out[j++] = static_cast<unsigned char>(triple & 0xFFU);
|
||||
}
|
||||
|
||||
if (j < out_len)
|
||||
out[j++] = (triple >> 2U * 8U) & 0xFF;
|
||||
if (j < out_len)
|
||||
out[j++] = (triple >> 1U * 8U) & 0xFF;
|
||||
if (j < out_len)
|
||||
out[j++] = (triple >> 0U * 8U) & 0xFF;
|
||||
// tail: padded quartet or unpadded remainder
|
||||
if (i < inLen) {
|
||||
std::size_t left = inLen - i;
|
||||
|
||||
if (left == 4U) {
|
||||
bool thirdIsPad = (input[i + 2U] == '=');
|
||||
bool fourthIsPad = (input[i + 3U] == '=');
|
||||
|
||||
// '=' is never allowed in positions 1 or 2 of any quartet
|
||||
if (input[i + 0U] == '=' || input[i + 1U] == '=') {
|
||||
throw std::runtime_error("Invalid Base64 padding placement");
|
||||
}
|
||||
|
||||
unsigned char a = readVal(static_cast<unsigned char>(input[i + 0U]));
|
||||
unsigned char b = readVal(static_cast<unsigned char>(input[i + 1U]));
|
||||
unsigned char c = 0U;
|
||||
unsigned char d = 0U;
|
||||
|
||||
if (!thirdIsPad) {
|
||||
c = readVal(static_cast<unsigned char>(input[i + 2U]));
|
||||
if (!fourthIsPad) {
|
||||
d = readVal(static_cast<unsigned char>(input[i + 3U]));
|
||||
}
|
||||
} else {
|
||||
// if the 3rd is '=', the 4th must also be '='
|
||||
if (!fourthIsPad) {
|
||||
throw std::runtime_error("Invalid Base64 padding placement");
|
||||
}
|
||||
}
|
||||
i += 4U;
|
||||
|
||||
std::uint32_t triple = (static_cast<std::uint32_t>(a) << 18U) |
|
||||
(static_cast<std::uint32_t>(b) << 12U) |
|
||||
(static_cast<std::uint32_t>(c) << 6U) |
|
||||
(static_cast<std::uint32_t>(d));
|
||||
|
||||
if (j < outLen)
|
||||
out[j++] = static_cast<unsigned char>((triple >> 16U) & 0xFFU);
|
||||
if (!thirdIsPad && j < outLen)
|
||||
out[j++] = static_cast<unsigned char>((triple >> 8U) & 0xFFU);
|
||||
if (!fourthIsPad && !thirdIsPad && j < outLen)
|
||||
out[j++] = static_cast<unsigned char>(triple & 0xFFU);
|
||||
|
||||
} else if (left == 2U || left == 3U) {
|
||||
unsigned char a = readVal(static_cast<unsigned char>(input[i + 0U]));
|
||||
unsigned char b = readVal(static_cast<unsigned char>(input[i + 1U]));
|
||||
unsigned char c = (left == 3U)
|
||||
? readVal(static_cast<unsigned char>(input[i + 2U]))
|
||||
: 0U;
|
||||
i += left;
|
||||
|
||||
std::uint32_t triple = (static_cast<std::uint32_t>(a) << 18U) |
|
||||
(static_cast<std::uint32_t>(b) << 12U) |
|
||||
(static_cast<std::uint32_t>(c) << 6U);
|
||||
|
||||
if (j < outLen)
|
||||
out[j++] = static_cast<unsigned char>((triple >> 16U) & 0xFFU);
|
||||
if (left == 3U && j < outLen)
|
||||
out[j++] = static_cast<unsigned char>((triple >> 8U) & 0xFFU);
|
||||
} else {
|
||||
throw std::runtime_error("Invalid Base64 length (mod 4 == 1)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,6 +321,5 @@ Decode(std::string_view input) {
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
#endif /* _MACARON_BASE64_H_ */
|
||||
|
||||
// NOLINTEND
|
||||
#endif /* MACARON_BASE64_H_ */
|
||||
// NOLINTEND
|
||||
|
||||
@@ -46,7 +46,7 @@ struct com_init_wrapper final {
|
||||
[[nodiscard]] auto is_initialized() const -> bool { return initialized_; }
|
||||
|
||||
private:
|
||||
BOOL initialized_;
|
||||
BOOL initialized_{};
|
||||
};
|
||||
} // namespace repertory::utils
|
||||
|
||||
|
||||
@@ -35,9 +35,10 @@ struct result final {
|
||||
|
||||
using retryable_action_t = std::function<bool()>;
|
||||
|
||||
[[nodiscard]] inline constexpr auto
|
||||
calculate_read_size(std::uint64_t total_size, std::size_t read_size,
|
||||
std::uint64_t offset) -> std::size_t {
|
||||
[[nodiscard]] constexpr auto calculate_read_size(std::uint64_t total_size,
|
||||
std::size_t read_size,
|
||||
std::uint64_t offset)
|
||||
-> std::size_t {
|
||||
return static_cast<std::size_t>(
|
||||
((offset + read_size) > total_size)
|
||||
? ((offset < total_size) ? total_size - offset : 0U)
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#define NOMINMAX
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define WINVER 0x0602
|
||||
#define WINVER 0x0A00
|
||||
#define _WIN32_WINNT WINVER
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
|
||||
@@ -107,7 +107,6 @@
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <climits>
|
||||
#include <codecvt>
|
||||
#include <condition_variable>
|
||||
#include <csignal>
|
||||
#include <cstdint>
|
||||
@@ -149,6 +148,9 @@
|
||||
#include <version>
|
||||
#endif // defined(__cplusplus)
|
||||
|
||||
#include <unicode/uchar.h>
|
||||
#include <unicode/utf.h>
|
||||
|
||||
#if defined(PROJECT_ENABLE_CURL)
|
||||
#include "curl/curl.h"
|
||||
#include "curl/multi.h"
|
||||
@@ -423,6 +425,8 @@ using vlc_string_t = std::unique_ptr<char, vlc_string_deleter>;
|
||||
|
||||
namespace repertory {
|
||||
using data_buffer = std::vector<unsigned char>;
|
||||
using data_span = std::span<unsigned char>;
|
||||
using data_cspan = std::span<const unsigned char>;
|
||||
using mutex_lock = std::lock_guard<std::mutex>;
|
||||
using recur_mutex_lock = std::lock_guard<std::recursive_mutex>;
|
||||
using stop_type = std::atomic_bool;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include "utils/config.hpp"
|
||||
|
||||
#include "utils/encryption.hpp"
|
||||
#include "utils/hash.hpp"
|
||||
#include "utils/types/file/i_file.hpp"
|
||||
|
||||
@@ -37,14 +38,74 @@ public:
|
||||
std::optional<std::string> relative_parent_path,
|
||||
std::size_t error_return = 0U);
|
||||
|
||||
encrypting_reader(std::string_view encrypted_file_path,
|
||||
std::string_view source_path,
|
||||
stop_type_callback stop_requested_cb,
|
||||
std::string_view token, std::size_t error_return = 0U);
|
||||
encrypting_reader(stop_type_callback stop_requested_cb,
|
||||
std::string_view encrypted_file_path,
|
||||
std::string_view source_path, std::string_view token,
|
||||
std::size_t error_return = 0U);
|
||||
|
||||
encrypting_reader(
|
||||
stop_type_callback stop_requested_cb,
|
||||
std::string_view encrypted_file_path, std::string_view source_path,
|
||||
stop_type_callback stop_requested_cb, std::string_view token,
|
||||
std::string_view token,
|
||||
std::vector<std::array<unsigned char,
|
||||
crypto_aead_xchacha20poly1305_IETF_NPUBBYTES>>
|
||||
iv_list,
|
||||
std::size_t error_return = 0U);
|
||||
|
||||
encrypting_reader(std::string_view file_name, std::string_view source_path,
|
||||
stop_type_callback stop_requested_cb,
|
||||
std::string_view token, kdf_config cfg,
|
||||
std::optional<std::string> relative_parent_path,
|
||||
std::size_t error_return = 0U);
|
||||
|
||||
encrypting_reader(stop_type_callback stop_requested_cb,
|
||||
std::string_view encrypted_file_path,
|
||||
std::string_view source_path, std::string_view token,
|
||||
kdf_config cfg, std::size_t error_return = 0U);
|
||||
|
||||
encrypting_reader(
|
||||
stop_type_callback stop_requested_cb,
|
||||
std::string_view encrypted_file_path, std::string_view source_path,
|
||||
std::string_view token, kdf_config cfg,
|
||||
std::vector<std::array<unsigned char,
|
||||
crypto_aead_xchacha20poly1305_IETF_NPUBBYTES>>
|
||||
iv_list,
|
||||
std::size_t error_return = 0U);
|
||||
|
||||
encrypting_reader(std::string_view file_name, std::string_view source_path,
|
||||
stop_type_callback stop_requested_cb,
|
||||
const utils::hash::hash_256_t &master_key,
|
||||
const kdf_config &cfg,
|
||||
std::optional<std::string> relative_parent_path,
|
||||
std::size_t error_return = 0U);
|
||||
|
||||
encrypting_reader(std::string_view file_name, std::string_view source_path,
|
||||
stop_type_callback stop_requested_cb,
|
||||
const utils::hash::hash_256_t &master_key,
|
||||
const std::pair<kdf_config, kdf_config> &configs,
|
||||
std::optional<std::string> relative_parent_path,
|
||||
std::size_t error_return = 0U);
|
||||
|
||||
encrypting_reader(stop_type_callback stop_requested_cb,
|
||||
std::string_view encrypted_file_path,
|
||||
std::string_view source_path,
|
||||
const utils::hash::hash_256_t &master_key,
|
||||
const kdf_config &cfg, std::size_t error_return = 0U);
|
||||
|
||||
encrypting_reader(
|
||||
stop_type_callback stop_requested_cb,
|
||||
std::string_view encrypted_file_path, std::string_view source_path,
|
||||
const utils::hash::hash_256_t &master_key, const kdf_config &cfg,
|
||||
std::vector<std::array<unsigned char,
|
||||
crypto_aead_xchacha20poly1305_IETF_NPUBBYTES>>
|
||||
iv_list,
|
||||
std::size_t error_return = 0U);
|
||||
|
||||
encrypting_reader(
|
||||
stop_type_callback stop_requested_cb,
|
||||
std::string_view encrypted_file_path, std::string_view source_path,
|
||||
const utils::hash::hash_256_t &master_key,
|
||||
const std::pair<kdf_config, kdf_config> &configs,
|
||||
std::vector<std::array<unsigned char,
|
||||
crypto_aead_xchacha20poly1305_IETF_NPUBBYTES>>
|
||||
iv_list,
|
||||
@@ -60,21 +121,25 @@ public:
|
||||
|
||||
public:
|
||||
using iostream = std::basic_iostream<char, std::char_traits<char>>;
|
||||
using kdf_pair_t = std::pair<data_buffer, data_buffer>;
|
||||
using key_pair_t =
|
||||
std::pair<utils::hash::hash_256_t, utils::hash::hash_256_t>;
|
||||
using streambuf = std::basic_streambuf<char, std::char_traits<char>>;
|
||||
|
||||
private:
|
||||
utils::encryption::hash_256_t key_;
|
||||
key_pair_t keys_;
|
||||
stop_type_callback stop_requested_cb_;
|
||||
size_t error_return_;
|
||||
std::unique_ptr<utils::file::i_file> source_file_;
|
||||
|
||||
private:
|
||||
std::unordered_map<std::size_t, data_buffer> chunk_buffers_;
|
||||
std::string encrypted_file_name_;
|
||||
std::string encrypted_file_path_;
|
||||
std::vector<
|
||||
std::array<unsigned char, crypto_aead_xchacha20poly1305_IETF_NPUBBYTES>>
|
||||
iv_list_;
|
||||
|
||||
private:
|
||||
std::unordered_map<std::size_t, data_buffer> chunk_buffers_;
|
||||
std::optional<kdf_pair_t> kdf_headers_;
|
||||
std::size_t last_data_chunk_{};
|
||||
std::size_t last_data_chunk_size_{};
|
||||
std::uint64_t read_offset_{};
|
||||
@@ -88,12 +153,30 @@ private:
|
||||
private:
|
||||
auto reader_function(char *buffer, size_t size, size_t nitems) -> size_t;
|
||||
|
||||
void common_initialize(bool procces_iv_list);
|
||||
|
||||
void common_initialize_kdf_data(const kdf_config &cfg,
|
||||
const utils::hash::hash_256_t &master_key);
|
||||
|
||||
void common_initialize_kdf_keys(std::string_view token, kdf_config &cfg);
|
||||
|
||||
void common_initialize_kdf_path(const utils::hash::hash_256_t &master_key);
|
||||
|
||||
void create_encrypted_paths(std::string_view file_name,
|
||||
std::optional<std::string> relative_parent_path);
|
||||
|
||||
public:
|
||||
[[nodiscard]] static auto calculate_decrypted_size(std::uint64_t total_size)
|
||||
[[nodiscard]] static auto calculate_decrypted_size(std::uint64_t total_size,
|
||||
bool uses_kdf)
|
||||
-> std::uint64_t;
|
||||
|
||||
[[nodiscard]] static auto
|
||||
calculate_encrypted_size(std::string_view source_path) -> std::uint64_t;
|
||||
calculate_encrypted_size(std::string_view source_path, bool uses_kdf)
|
||||
-> std::uint64_t;
|
||||
|
||||
[[nodiscard]] static auto calculate_encrypted_size(std::uint64_t size,
|
||||
bool uses_kdf)
|
||||
-> std::uint64_t;
|
||||
|
||||
[[nodiscard]] auto create_iostream() const -> std::shared_ptr<iostream>;
|
||||
|
||||
@@ -127,6 +210,12 @@ public:
|
||||
return iv_list_;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_kdf_config_for_data() const
|
||||
-> std::optional<kdf_config>;
|
||||
|
||||
[[nodiscard]] auto get_kdf_config_for_path() const
|
||||
-> std::optional<kdf_config>;
|
||||
|
||||
[[nodiscard]] auto get_stop_requested() const -> bool {
|
||||
return stop_requested_cb_();
|
||||
}
|
||||
|
||||
@@ -25,6 +25,9 @@
|
||||
|
||||
#include "utils/config.hpp"
|
||||
|
||||
#if defined(PROJECT_ENABLE_BOOST) && defined(PROJECT_ENABLE_JSON)
|
||||
#include "utils/collection.hpp"
|
||||
#endif // defined(PROJECT_ENABLE_BOOST) && defined(PROJECT_ENABLE_JSON)
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/hash.hpp"
|
||||
|
||||
@@ -34,32 +37,236 @@ inline constexpr std::uint32_t encryption_header_size{
|
||||
crypto_aead_xchacha20poly1305_IETF_ABYTES,
|
||||
};
|
||||
|
||||
#if defined(PROJECT_ENABLE_BOOST)
|
||||
enum class kdf_version : std::uint8_t { v1 };
|
||||
|
||||
enum class kdf_type : std::uint8_t { argon2id };
|
||||
|
||||
enum class memlimit_level : std::uint8_t {
|
||||
level1, // 64MiB
|
||||
level2, // 256MiB
|
||||
level3, // 512MiB
|
||||
level4, // 1GiB
|
||||
};
|
||||
|
||||
enum class opslimit_level : std::uint8_t {
|
||||
level1, // interactive
|
||||
level2, // moderate
|
||||
level3, // sensitive
|
||||
};
|
||||
|
||||
[[nodiscard]] inline auto get_memlimit(memlimit_level memlimit) -> size_t {
|
||||
constexpr auto mib512{512ULL * 1024ULL * 1024ULL};
|
||||
|
||||
switch (memlimit) {
|
||||
case memlimit_level::level1:
|
||||
return crypto_pwhash_MEMLIMIT_INTERACTIVE;
|
||||
|
||||
case memlimit_level::level2:
|
||||
return crypto_pwhash_MEMLIMIT_MODERATE;
|
||||
|
||||
case memlimit_level::level3:
|
||||
return mib512;
|
||||
|
||||
case memlimit_level::level4:
|
||||
return crypto_pwhash_MEMLIMIT_SENSITIVE;
|
||||
}
|
||||
|
||||
return mib512;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline auto get_opslimit(opslimit_level opslimit)
|
||||
-> unsigned long long {
|
||||
switch (opslimit) {
|
||||
case opslimit_level::level1:
|
||||
return crypto_pwhash_OPSLIMIT_INTERACTIVE;
|
||||
|
||||
case opslimit_level::level2:
|
||||
return crypto_pwhash_OPSLIMIT_MODERATE;
|
||||
|
||||
case opslimit_level::level3:
|
||||
return crypto_pwhash_OPSLIMIT_SENSITIVE;
|
||||
}
|
||||
|
||||
return crypto_pwhash_OPSLIMIT_MODERATE;
|
||||
}
|
||||
|
||||
enum class kdf_context : std::uint8_t {
|
||||
data,
|
||||
path,
|
||||
undefined,
|
||||
};
|
||||
using kdf_ctx_t = std::array<char, crypto_kdf_CONTEXTBYTES>;
|
||||
|
||||
namespace kdf {
|
||||
constexpr inline std::array<
|
||||
kdf_ctx_t, static_cast<std::size_t>(kdf_context::undefined) + 1U>
|
||||
KDF_CTXS{
|
||||
{
|
||||
{'D', 'A', 'T', 'A', '_', 'C', 'T', 'X'},
|
||||
{'F', 'I', 'L', 'E', '_', 'C', 'T', 'X'},
|
||||
{'D', 'E', 'F', 'L', '_', 'C', 'T', 'X'},
|
||||
},
|
||||
};
|
||||
} // namespace kdf
|
||||
|
||||
[[nodiscard]] constexpr inline auto get_kdf_context_name(kdf_context ctx)
|
||||
-> kdf_ctx_t {
|
||||
const auto idx = static_cast<std::size_t>(ctx);
|
||||
return idx < kdf::KDF_CTXS.size() ? kdf::KDF_CTXS.at(idx)
|
||||
: kdf::KDF_CTXS.back();
|
||||
}
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct kdf_config final {
|
||||
using salt_t = std::array<std::uint8_t, crypto_pwhash_SALTBYTES>;
|
||||
|
||||
kdf_version version{kdf_version::v1};
|
||||
kdf_type kdf{kdf_type::argon2id};
|
||||
memlimit_level memlimit{memlimit_level::level3};
|
||||
opslimit_level opslimit{opslimit_level::level2};
|
||||
std::uint64_t unique_id{};
|
||||
salt_t salt{};
|
||||
std::uint64_t checksum{};
|
||||
|
||||
template <typename hash_t>
|
||||
[[nodiscard]] auto create_subkey(kdf_context ctx, std::size_t unique_id_,
|
||||
const hash_t &master_key) const
|
||||
-> std::pair<hash_t, kdf_config> {
|
||||
auto sub_key = derive_subkey<hash_t>(ctx, unique_id_, master_key);
|
||||
|
||||
auto cfg = *this;
|
||||
cfg.unique_id = unique_id_;
|
||||
cfg.checksum = cfg.generate_checksum();
|
||||
return {sub_key, cfg};
|
||||
}
|
||||
|
||||
template <typename hash_t>
|
||||
[[nodiscard]] static auto derive_subkey(kdf_context ctx,
|
||||
std::size_t unique_id_,
|
||||
const hash_t &master_key) -> hash_t {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
hash_t sub_key{};
|
||||
auto res = crypto_kdf_derive_from_key(
|
||||
sub_key.data(), sub_key.size(), unique_id_,
|
||||
get_kdf_context_name(ctx).data(), master_key.data());
|
||||
if (res != 0) {
|
||||
throw repertory::utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to derive sub-key",
|
||||
std::to_string(res),
|
||||
});
|
||||
}
|
||||
|
||||
return sub_key;
|
||||
}
|
||||
|
||||
template <typename hash_t>
|
||||
[[nodiscard]] auto recreate_subkey(kdf_context ctx,
|
||||
const hash_t &master_key) const -> hash_t {
|
||||
return derive_subkey<hash_t>(ctx, unique_id, master_key);
|
||||
}
|
||||
|
||||
[[nodiscard]] static auto from_header(data_cspan data, kdf_config &cfg,
|
||||
bool ignore_checksum = false) -> bool;
|
||||
|
||||
[[nodiscard]] auto generate_checksum() const -> std::uint64_t;
|
||||
|
||||
void seal();
|
||||
|
||||
[[nodiscard]] static constexpr auto size() -> std::size_t {
|
||||
return sizeof(kdf_config);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto to_header() const -> data_buffer;
|
||||
|
||||
[[nodiscard]] auto operator==(const kdf_config &) const -> bool = default;
|
||||
[[nodiscard]] auto operator!=(const kdf_config &) const -> bool = default;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
#endif // defined(PROJECT_ENABLE_BOOST)
|
||||
|
||||
template <typename hash_t>
|
||||
inline auto generate_key(
|
||||
[[nodiscard]] inline auto generate_key(
|
||||
std::string_view password,
|
||||
std::function<hash_t(const unsigned char *data, std::size_t size)> hasher =
|
||||
default_create_hash<hash_t>()) -> hash_t;
|
||||
utils::hash::default_create_hash<hash_t>()) -> hash_t;
|
||||
|
||||
template <typename hash_t>
|
||||
inline auto generate_key(
|
||||
[[nodiscard]] inline auto generate_key(
|
||||
std::wstring_view password,
|
||||
std::function<hash_t(const unsigned char *data, std::size_t size)> hasher =
|
||||
default_create_hash<hash_t>()) -> hash_t;
|
||||
utils::hash::default_create_hash<hash_t>()) -> hash_t;
|
||||
|
||||
#if defined(PROJECT_ENABLE_BOOST)
|
||||
template <typename hash_t>
|
||||
[[nodiscard]] inline auto generate_key(std::string_view password,
|
||||
kdf_config &cfg) -> hash_t;
|
||||
|
||||
template <typename hash_t>
|
||||
[[nodiscard]] inline auto generate_key(std::wstring_view password,
|
||||
kdf_config &cfg) -> hash_t;
|
||||
|
||||
template <typename hash_t>
|
||||
[[nodiscard]] inline auto recreate_key(std::string_view password,
|
||||
const kdf_config &cfg) -> hash_t;
|
||||
|
||||
template <typename hash_t>
|
||||
[[nodiscard]] inline auto recreate_key(std::wstring_view password,
|
||||
const kdf_config &cfg) -> hash_t;
|
||||
|
||||
template <typename string_t>
|
||||
[[nodiscard]] auto create_key_argon2id(string_t password, kdf_config &cfg,
|
||||
utils::hash::hash_256_t &key) -> bool;
|
||||
|
||||
template <typename string_t>
|
||||
[[nodiscard]] auto recreate_key_argon2id(string_t password,
|
||||
const kdf_config &cfg,
|
||||
utils::hash::hash_256_t &key) -> bool;
|
||||
|
||||
template <typename hash_t, typename string_t>
|
||||
[[nodiscard]] inline auto
|
||||
detect_and_recreate_key(string_t password, data_cspan header, hash_t &key,
|
||||
std::optional<kdf_config> &cfg) -> bool;
|
||||
|
||||
template <typename hash_t>
|
||||
[[nodiscard]] inline auto
|
||||
detect_and_recreate_key(std::string_view password, data_cspan header,
|
||||
hash_t &key, std::optional<kdf_config> &cfg) -> bool;
|
||||
|
||||
template <typename hash_t>
|
||||
[[nodiscard]] inline auto
|
||||
detect_and_recreate_key(std::wstring_view password, data_cspan header,
|
||||
hash_t &key, std::optional<kdf_config> &cfg) -> bool;
|
||||
|
||||
[[nodiscard]] auto decrypt_file_name(std::string_view encryption_token,
|
||||
std::string &file_name) -> bool;
|
||||
|
||||
[[nodiscard]] auto decrypt_file_path(std::string_view encryption_token,
|
||||
std::string &file_path) -> bool;
|
||||
|
||||
[[nodiscard]] auto decrypt_file_name(std::string_view encryption_token,
|
||||
const kdf_config &cfg,
|
||||
std::string &file_name) -> bool;
|
||||
|
||||
[[nodiscard]] auto decrypt_file_path(std::string_view encryption_token,
|
||||
const kdf_config &cfg,
|
||||
std::string &file_path) -> bool;
|
||||
|
||||
[[nodiscard]] auto decrypt_file_name(const utils::hash::hash_256_t &master_key,
|
||||
std::string &file_name) -> bool;
|
||||
|
||||
[[nodiscard]] auto decrypt_file_path(const utils::hash::hash_256_t &master_key,
|
||||
std::string &file_path) -> bool;
|
||||
|
||||
template <typename result_t, typename arr_t, std::size_t arr_size>
|
||||
[[nodiscard]] inline auto decrypt_data(const std::array<arr_t, arr_size> &key,
|
||||
const unsigned char *buffer,
|
||||
std::size_t buffer_size,
|
||||
result_t &res) -> bool {
|
||||
std::size_t buffer_size, result_t &res)
|
||||
-> bool {
|
||||
if (buffer_size > encryption_header_size) {
|
||||
const std::uint32_t size =
|
||||
std::uint32_t size =
|
||||
boost::endian::native_to_big(static_cast<std::uint32_t>(buffer_size));
|
||||
res.resize(buffer_size - encryption_header_size);
|
||||
return crypto_aead_xchacha20poly1305_ietf_decrypt_detached(
|
||||
@@ -76,32 +283,53 @@ template <typename result_t, typename arr_t, std::size_t arr_size>
|
||||
template <typename buffer_t, typename result_t, typename arr_t,
|
||||
std::size_t arr_size>
|
||||
[[nodiscard]] inline auto decrypt_data(const std::array<arr_t, arr_size> &key,
|
||||
const buffer_t &buf,
|
||||
result_t &res) -> bool {
|
||||
const buffer_t &buf, result_t &res)
|
||||
-> bool {
|
||||
return decrypt_data<result_t>(
|
||||
key, reinterpret_cast<const unsigned char *>(buf.data()), buf.size(),
|
||||
res);
|
||||
}
|
||||
|
||||
template <typename buffer_t, typename result_t, typename hash_t = hash_256_t>
|
||||
template <typename buffer_t, typename result_t,
|
||||
typename hash_t = utils::hash::hash_256_t>
|
||||
[[nodiscard]] inline auto decrypt_data(
|
||||
std::string_view password, const buffer_t &buf, result_t &res,
|
||||
std::function<hash_t(const unsigned char *data, std::size_t size)> hasher =
|
||||
default_create_hash<hash_t>()) -> bool {
|
||||
utils::hash::default_create_hash<hash_t>()) -> bool {
|
||||
return decrypt_data<buffer_t, result_t>(generate_key(password, hasher), buf,
|
||||
res);
|
||||
}
|
||||
|
||||
template <typename result_t, typename hash_t = hash_256_t>
|
||||
template <typename buffer_t, typename result_t,
|
||||
typename hash_t = utils::hash::hash_256_t>
|
||||
[[nodiscard]] inline auto decrypt_data(std::string_view password,
|
||||
const kdf_config &cfg,
|
||||
const buffer_t &buf, result_t &res)
|
||||
-> bool {
|
||||
return decrypt_data<buffer_t, result_t>(recreate_key<hash_t>(password, cfg),
|
||||
buf, res);
|
||||
}
|
||||
|
||||
template <typename result_t, typename hash_t = utils::hash::hash_256_t>
|
||||
[[nodiscard]] inline auto decrypt_data(
|
||||
std::string_view password, const unsigned char *buffer,
|
||||
std::size_t buffer_size, result_t &res,
|
||||
std::function<hash_t(const unsigned char *data, std::size_t size)> hasher =
|
||||
default_create_hash<hash_t>()) -> bool {
|
||||
utils::hash::default_create_hash<hash_t>()) -> bool {
|
||||
return decrypt_data<result_t>(generate_key(password, hasher), buffer,
|
||||
buffer_size, res);
|
||||
}
|
||||
|
||||
template <typename result_t, typename hash_t = utils::hash::hash_256_t>
|
||||
[[nodiscard]] inline auto decrypt_data(std::string_view password,
|
||||
const kdf_config &cfg,
|
||||
const unsigned char *buffer,
|
||||
std::size_t buffer_size, result_t &res)
|
||||
-> bool {
|
||||
return decrypt_data<result_t>(recreate_key<hash_t>(password, cfg), buffer,
|
||||
buffer_size, res);
|
||||
}
|
||||
|
||||
template <typename result_t, typename arr_t, std::size_t arr_size>
|
||||
inline void
|
||||
encrypt_data(const std::array<unsigned char,
|
||||
@@ -144,26 +372,44 @@ inline void encrypt_data(const std::array<arr_t, arr_size> &key,
|
||||
encrypt_data<result_t>(iv, key, buffer, buffer_size, res);
|
||||
}
|
||||
|
||||
template <typename result_t, typename hash_t = hash_256_t>
|
||||
template <typename result_t, typename hash_t = utils::hash::hash_256_t>
|
||||
inline void encrypt_data(
|
||||
std::string_view password, const unsigned char *buffer,
|
||||
std::size_t buffer_size, result_t &res,
|
||||
std::function<hash_t(const unsigned char *data, std::size_t size)> hasher =
|
||||
default_create_hash<hash_t>()) {
|
||||
utils::hash::default_create_hash<hash_t>()) {
|
||||
encrypt_data<result_t>(generate_key(password, hasher), buffer, buffer_size,
|
||||
res);
|
||||
}
|
||||
|
||||
template <typename buffer_t, typename result_t, typename hash_t = hash_256_t>
|
||||
template <typename result_t, typename hash_t = utils::hash::hash_256_t>
|
||||
inline void encrypt_data(std::string_view password, kdf_config &cfg,
|
||||
const unsigned char *buffer, std::size_t buffer_size,
|
||||
result_t &res) {
|
||||
encrypt_data<result_t>(generate_key<hash_t>(password, cfg), buffer,
|
||||
buffer_size, res);
|
||||
}
|
||||
|
||||
template <typename buffer_t, typename result_t,
|
||||
typename hash_t = utils::hash::hash_256_t>
|
||||
inline void encrypt_data(
|
||||
std::string_view password, const buffer_t &buf, result_t &res,
|
||||
std::function<hash_t(const unsigned char *data, std::size_t size)> hasher =
|
||||
default_create_hash<hash_t>()) {
|
||||
utils::hash::default_create_hash<hash_t>()) {
|
||||
encrypt_data<result_t>(generate_key(password, hasher),
|
||||
reinterpret_cast<const unsigned char *>(buf.data()),
|
||||
buf.size(), res);
|
||||
}
|
||||
|
||||
template <typename buffer_t, typename result_t,
|
||||
typename hash_t = utils::hash::hash_256_t>
|
||||
inline void encrypt_data(std::string_view password, kdf_config &cfg,
|
||||
const buffer_t &buf, result_t &res) {
|
||||
encrypt_data<result_t>(generate_key<hash_t>(password, cfg),
|
||||
reinterpret_cast<const unsigned char *>(buf.data()),
|
||||
buf.size(), res);
|
||||
}
|
||||
|
||||
template <typename buffer_t, typename result_t, typename arr_t,
|
||||
std::size_t arr_size>
|
||||
inline void encrypt_data(const std::array<arr_t, arr_size> &key,
|
||||
@@ -189,16 +435,52 @@ using reader_func_t =
|
||||
std::function<bool(data_buffer &cypher_text, std::uint64_t start_offset,
|
||||
std::uint64_t end_offset)>;
|
||||
|
||||
[[nodiscard]] auto
|
||||
read_encrypted_range(const http_range &range,
|
||||
const utils::encryption::hash_256_t &key,
|
||||
reader_func_t reader_func, std::uint64_t total_size,
|
||||
data_buffer &data) -> bool;
|
||||
[[nodiscard]] auto read_encrypted_range(const http_range &range,
|
||||
const utils::hash::hash_256_t &key,
|
||||
bool uses_kdf,
|
||||
reader_func_t reader_func,
|
||||
std::uint64_t total_size,
|
||||
data_buffer &data) -> bool;
|
||||
|
||||
[[nodiscard]] auto read_encrypted_range(
|
||||
const http_range &range, const utils::encryption::hash_256_t &key,
|
||||
const http_range &range, const utils::hash::hash_256_t &key, bool uses_kdf,
|
||||
reader_func_t reader_func, std::uint64_t total_size, unsigned char *data,
|
||||
std::size_t size, std::size_t &bytes_read) -> bool;
|
||||
|
||||
[[nodiscard]] inline auto
|
||||
read_encrypted_range(const http_range &range,
|
||||
const utils::hash::hash_256_t &key,
|
||||
reader_func_t reader_func, std::uint64_t total_size,
|
||||
data_buffer &data) -> bool {
|
||||
return read_encrypted_range(range, key, false, reader_func, total_size, data);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline auto read_encrypted_range(
|
||||
const http_range &range, const utils::hash::hash_256_t &key,
|
||||
reader_func_t reader_func, std::uint64_t total_size, unsigned char *data,
|
||||
std::size_t size, std::size_t &bytes_read) -> bool {
|
||||
return read_encrypted_range(range, key, false, reader_func, total_size, data,
|
||||
size, bytes_read);
|
||||
}
|
||||
|
||||
template <typename string_t>
|
||||
auto create_key_argon2id(string_t password, kdf_config &cfg,
|
||||
utils::hash::hash_256_t &key) -> bool {
|
||||
cfg.seal();
|
||||
|
||||
return recreate_key_argon2id(password, cfg, key);
|
||||
}
|
||||
|
||||
template <typename string_t>
|
||||
auto recreate_key_argon2id(string_t password, const kdf_config &cfg,
|
||||
utils::hash::hash_256_t &key) -> bool {
|
||||
return crypto_pwhash(
|
||||
reinterpret_cast<unsigned char *>(key.data()), key.size(),
|
||||
reinterpret_cast<const char *>(password.data()),
|
||||
password.size() * sizeof(typename string_t::value_type),
|
||||
cfg.salt.data(), get_opslimit(cfg.opslimit),
|
||||
get_memlimit(cfg.memlimit), crypto_pwhash_ALG_ARGON2ID13) == 0;
|
||||
}
|
||||
#endif // defined(PROJECT_ENABLE_BOOST)
|
||||
|
||||
template <typename hash_t>
|
||||
@@ -218,7 +500,223 @@ inline auto generate_key(
|
||||
return hasher(reinterpret_cast<const unsigned char *>(password.data()),
|
||||
password.size() * sizeof(wchar_t));
|
||||
}
|
||||
|
||||
#if defined(PROJECT_ENABLE_BOOST)
|
||||
template <typename hash_t, typename string_t>
|
||||
inline auto generate_key_impl(string_t password, kdf_config &cfg) -> hash_t {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
switch (cfg.version) {
|
||||
case kdf_version::v1:
|
||||
switch (cfg.kdf) {
|
||||
case kdf_type::argon2id: {
|
||||
hash_t key{};
|
||||
if (not create_key_argon2id(password, cfg, key)) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to generate argon2id key",
|
||||
});
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
default:
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"unsupported kdf type",
|
||||
std::to_string(static_cast<std::uint8_t>(cfg.kdf)),
|
||||
});
|
||||
}
|
||||
|
||||
default:
|
||||
throw utils::error::create_exception(
|
||||
function_name,
|
||||
{
|
||||
"unsupported kdf version",
|
||||
std::to_string(static_cast<std::uint8_t>(cfg.version)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
template <typename hash_t, typename string_t>
|
||||
inline auto recreate_key_impl(string_t password, const kdf_config &cfg)
|
||||
-> hash_t {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
switch (cfg.version) {
|
||||
case kdf_version::v1:
|
||||
switch (cfg.kdf) {
|
||||
case kdf_type::argon2id: {
|
||||
hash_t key{};
|
||||
if (not recreate_key_argon2id(password, cfg, key)) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to generate argon2id key",
|
||||
});
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
default:
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"unsupported kdf type",
|
||||
std::to_string(static_cast<std::uint8_t>(cfg.kdf)),
|
||||
});
|
||||
}
|
||||
|
||||
default:
|
||||
throw utils::error::create_exception(
|
||||
function_name,
|
||||
{
|
||||
"unsupported kdf version",
|
||||
std::to_string(static_cast<std::uint8_t>(cfg.version)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
template <typename hash_t>
|
||||
inline auto generate_key(std::string_view password, kdf_config &cfg) -> hash_t {
|
||||
return generate_key_impl<hash_t, std::string_view>(password, cfg);
|
||||
}
|
||||
|
||||
template <typename hash_t>
|
||||
inline auto generate_key(std::wstring_view password, kdf_config &cfg)
|
||||
-> hash_t {
|
||||
return generate_key_impl<hash_t, std::wstring_view>(password, cfg);
|
||||
}
|
||||
|
||||
template <typename hash_t>
|
||||
inline auto recreate_key(std::string_view password, const kdf_config &cfg)
|
||||
-> hash_t {
|
||||
return recreate_key_impl<hash_t, std::string_view>(password, cfg);
|
||||
}
|
||||
|
||||
template <typename hash_t>
|
||||
inline auto recreate_key(std::wstring_view password, const kdf_config &cfg)
|
||||
-> hash_t {
|
||||
return recreate_key_impl<hash_t, std::wstring_view>(password, cfg);
|
||||
}
|
||||
|
||||
template <typename hash_t, typename string_t>
|
||||
inline auto detect_and_recreate_key(string_t password, data_cspan header,
|
||||
hash_t &key, std::optional<kdf_config> &cfg)
|
||||
-> bool {
|
||||
if (header.size() >= kdf_config::size()) {
|
||||
kdf_config tmp{};
|
||||
if (kdf_config::from_header(header.first(kdf_config::size()), tmp)) {
|
||||
cfg = tmp;
|
||||
key = recreate_key<hash_t>(password, *cfg);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
key = generate_key<hash_t>(password);
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename hash_t>
|
||||
inline auto detect_and_recreate_key(std::string_view password,
|
||||
data_cspan header, hash_t &key,
|
||||
std::optional<kdf_config> &cfg) -> bool {
|
||||
return detect_and_recreate_key<hash_t, std::string_view>(password, header,
|
||||
key, cfg);
|
||||
}
|
||||
|
||||
template <typename hash_t>
|
||||
inline auto detect_and_recreate_key(std::wstring_view password,
|
||||
data_cspan header, hash_t &key,
|
||||
std::optional<kdf_config> &cfg) -> bool {
|
||||
return detect_and_recreate_key<hash_t, std::wstring_view>(password, header,
|
||||
key, cfg);
|
||||
}
|
||||
|
||||
#endif // defined(PROJECT_ENABLE_BOOST)
|
||||
} // namespace repertory::utils::encryption
|
||||
|
||||
#if defined(PROJECT_ENABLE_BOOST) && defined(PROJECT_ENABLE_JSON)
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
|
||||
namespace kdf {
|
||||
inline constexpr std::string_view JSON_CHECKSUM{"checksum"};
|
||||
inline constexpr std::string_view JSON_KDF{"kdf"};
|
||||
inline constexpr std::string_view JSON_MEMLIMIT{"memlimit"};
|
||||
inline constexpr std::string_view JSON_OPSLIMIT{"opslimit"};
|
||||
inline constexpr std::string_view JSON_SALT{"salt"};
|
||||
inline constexpr std::string_view JSON_UNIQUE_ID{"unique_id"};
|
||||
inline constexpr std::string_view JSON_VERSION{"version"};
|
||||
} // namespace kdf
|
||||
|
||||
template <>
|
||||
struct adl_serializer<repertory::utils::encryption::kdf_config::salt_t> {
|
||||
static void
|
||||
to_json(json &data,
|
||||
const repertory::utils::encryption::kdf_config::salt_t &value) {
|
||||
data = repertory::utils::collection::to_hex_string(value);
|
||||
}
|
||||
|
||||
static void
|
||||
from_json(const json &data,
|
||||
repertory::utils::encryption::kdf_config::salt_t &value) {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
repertory::data_buffer buffer{};
|
||||
if (not repertory::utils::collection::from_hex_string(
|
||||
data.get<std::string>(), buffer)) {
|
||||
throw repertory::utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to convert hex string to salt",
|
||||
data.get<std::string>(),
|
||||
});
|
||||
}
|
||||
|
||||
if (buffer.size() != value.size()) {
|
||||
throw repertory::utils::error::create_exception(
|
||||
function_name, {
|
||||
"unexpected length for salt after hex conversion",
|
||||
"expected",
|
||||
std::to_string(value.size()),
|
||||
"actual",
|
||||
std::to_string(buffer.size()),
|
||||
});
|
||||
}
|
||||
|
||||
std::copy_n(buffer.begin(), value.size(), value.begin());
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct adl_serializer<repertory::utils::encryption::kdf_config> {
|
||||
static void to_json(json &data,
|
||||
const repertory::utils::encryption::kdf_config &value) {
|
||||
data[kdf::JSON_CHECKSUM] = value.checksum;
|
||||
data[kdf::JSON_KDF] = value.kdf;
|
||||
data[kdf::JSON_MEMLIMIT] = value.memlimit;
|
||||
data[kdf::JSON_OPSLIMIT] = value.opslimit;
|
||||
data[kdf::JSON_SALT] = value.salt;
|
||||
data[kdf::JSON_UNIQUE_ID] = value.unique_id;
|
||||
data[kdf::JSON_VERSION] = value.version;
|
||||
}
|
||||
|
||||
static void from_json(const json &data,
|
||||
repertory::utils::encryption::kdf_config &value) {
|
||||
data.at(kdf::JSON_CHECKSUM).get_to<std::uint64_t>(value.checksum);
|
||||
data.at(kdf::JSON_KDF)
|
||||
.get_to<repertory::utils::encryption::kdf_type>(value.kdf);
|
||||
data.at(kdf::JSON_MEMLIMIT)
|
||||
.get_to<repertory::utils::encryption::memlimit_level>(value.memlimit);
|
||||
data.at(kdf::JSON_OPSLIMIT)
|
||||
.get_to<repertory::utils::encryption::opslimit_level>(value.opslimit);
|
||||
data.at(kdf::JSON_SALT)
|
||||
.get_to<repertory::utils::encryption::kdf_config::salt_t>(value.salt);
|
||||
data.at(kdf::JSON_UNIQUE_ID).get_to<std::uint64_t>(value.unique_id);
|
||||
data.at(kdf::JSON_VERSION)
|
||||
.get_to<repertory::utils::encryption::kdf_version>(value.version);
|
||||
}
|
||||
};
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
#endif // defined(PROJECT_ENABLE_BOOST) && defined(PROJECT_ENABLE_JSON)
|
||||
|
||||
#endif // defined(PROJECT_ENABLE_LIBSODIUM)
|
||||
#endif // REPERTORY_INCLUDE_UTILS_ENCRYPTION_HPP_
|
||||
|
||||
@@ -41,13 +41,13 @@ namespace repertory::utils::file {
|
||||
[[nodiscard]] auto create_temp_name(std::string_view file_part) -> std::string;
|
||||
|
||||
// INFO: has test
|
||||
[[nodiscard]] auto
|
||||
create_temp_name(std::wstring_view file_part) -> std::wstring;
|
||||
[[nodiscard]] auto create_temp_name(std::wstring_view file_part)
|
||||
-> std::wstring;
|
||||
|
||||
// INFO: has test
|
||||
[[nodiscard]] inline auto
|
||||
directory_exists_in_path(std::string_view path,
|
||||
std::string_view sub_directory) -> bool;
|
||||
directory_exists_in_path(std::string_view path, std::string_view sub_directory)
|
||||
-> bool;
|
||||
|
||||
// INFO: has test
|
||||
[[nodiscard]] inline auto
|
||||
@@ -55,45 +55,46 @@ directory_exists_in_path(std::wstring_view path,
|
||||
std::wstring_view sub_directory) -> bool;
|
||||
|
||||
// INFO: has test
|
||||
[[nodiscard]] inline auto
|
||||
file_exists_in_path(std::string_view path, std::string_view file_name) -> bool;
|
||||
[[nodiscard]] inline auto file_exists_in_path(std::string_view path,
|
||||
std::string_view file_name)
|
||||
-> bool;
|
||||
|
||||
// INFO: has test
|
||||
[[nodiscard]] inline auto
|
||||
file_exists_in_path(std::wstring_view path,
|
||||
std::wstring_view file_name) -> bool;
|
||||
[[nodiscard]] inline auto file_exists_in_path(std::wstring_view path,
|
||||
std::wstring_view file_name)
|
||||
-> bool;
|
||||
|
||||
// INFO: has test
|
||||
[[nodiscard]] auto
|
||||
get_free_drive_space(std::string_view path) -> std::optional<std::uint64_t>;
|
||||
[[nodiscard]] auto get_free_drive_space(std::string_view path)
|
||||
-> std::optional<std::uint64_t>;
|
||||
|
||||
// INFO: has test
|
||||
[[nodiscard]] auto
|
||||
get_free_drive_space(std::wstring_view path) -> std::optional<std::uint64_t>;
|
||||
[[nodiscard]] auto get_free_drive_space(std::wstring_view path)
|
||||
-> std::optional<std::uint64_t>;
|
||||
|
||||
// INFO: has test
|
||||
[[nodiscard]] auto get_time(std::string_view path,
|
||||
time_type type) -> std::optional<std::uint64_t>;
|
||||
[[nodiscard]] auto get_time(std::string_view path, time_type type)
|
||||
-> std::optional<std::uint64_t>;
|
||||
|
||||
// INFO: has test
|
||||
[[nodiscard]] auto get_time(std::wstring_view path,
|
||||
time_type type) -> std::optional<std::uint64_t>;
|
||||
[[nodiscard]] auto get_time(std::wstring_view path, time_type type)
|
||||
-> std::optional<std::uint64_t>;
|
||||
|
||||
// INFO: has test
|
||||
[[nodiscard]] auto
|
||||
get_times(std::string_view path) -> std::optional<file_times>;
|
||||
[[nodiscard]] auto get_times(std::string_view path)
|
||||
-> std::optional<file_times>;
|
||||
|
||||
// INFO: has test
|
||||
[[nodiscard]] auto
|
||||
get_times(std::wstring_view path) -> std::optional<file_times>;
|
||||
[[nodiscard]] auto get_times(std::wstring_view path)
|
||||
-> std::optional<file_times>;
|
||||
|
||||
// INFO: has test
|
||||
[[nodiscard]] auto
|
||||
get_total_drive_space(std::string_view path) -> std::optional<std::uint64_t>;
|
||||
[[nodiscard]] auto get_total_drive_space(std::string_view path)
|
||||
-> std::optional<std::uint64_t>;
|
||||
|
||||
// INFO: has test
|
||||
[[nodiscard]] auto
|
||||
get_total_drive_space(std::wstring_view path) -> std::optional<std::uint64_t>;
|
||||
[[nodiscard]] auto get_total_drive_space(std::wstring_view path)
|
||||
-> std::optional<std::uint64_t>;
|
||||
|
||||
#if defined(PROJECT_ENABLE_LIBDSM)
|
||||
[[nodiscard]] auto
|
||||
@@ -101,20 +102,20 @@ smb_create_and_validate_relative_path(std::string_view smb_path,
|
||||
std::string_view rel_path) -> std::string;
|
||||
|
||||
// INFO: has test
|
||||
[[nodiscard]] auto
|
||||
smb_create_relative_path(std::string_view smb_path) -> std::string;
|
||||
[[nodiscard]] auto smb_create_relative_path(std::string_view smb_path)
|
||||
-> std::string;
|
||||
|
||||
// INFO: has test
|
||||
[[nodiscard]] auto
|
||||
smb_create_search_path(std::string_view smb_path) -> std::string;
|
||||
[[nodiscard]] auto smb_create_search_path(std::string_view smb_path)
|
||||
-> std::string;
|
||||
|
||||
// INFO: has test
|
||||
[[nodiscard]] auto
|
||||
smb_create_smb_path(std::string_view smb_path,
|
||||
std::string_view rel_path) -> std::string;
|
||||
[[nodiscard]] auto smb_create_smb_path(std::string_view smb_path,
|
||||
std::string_view rel_path)
|
||||
-> std::string;
|
||||
|
||||
[[nodiscard]] auto
|
||||
smb_get_parent_path(std::string_view smb_path) -> std::string;
|
||||
[[nodiscard]] auto smb_get_parent_path(std::string_view smb_path)
|
||||
-> std::string;
|
||||
|
||||
[[nodiscard]] auto smb_get_root_path(std::string_view smb_path) -> std::string;
|
||||
|
||||
@@ -143,27 +144,30 @@ read_json_file(std::string_view path, nlohmann::json &data,
|
||||
std::optional<std::string_view> password = std::nullopt) -> bool;
|
||||
|
||||
// INFO: has test
|
||||
[[nodiscard]] auto read_json_file(
|
||||
std::wstring_view path, nlohmann::json &data,
|
||||
std::optional<std::wstring_view> password = std::nullopt) -> bool;
|
||||
[[nodiscard]] auto
|
||||
read_json_file(std::wstring_view path, nlohmann::json &data,
|
||||
std::optional<std::wstring_view> password = std::nullopt)
|
||||
-> bool;
|
||||
|
||||
// INFO: has test
|
||||
[[nodiscard]] auto write_json_file(
|
||||
std::string_view path, const nlohmann::json &data,
|
||||
std::optional<std::string_view> password = std::nullopt) -> bool;
|
||||
[[nodiscard]] auto
|
||||
write_json_file(std::string_view path, const nlohmann::json &data,
|
||||
std::optional<std::string_view> password = std::nullopt)
|
||||
-> bool;
|
||||
|
||||
// INFO: has test
|
||||
[[nodiscard]] auto write_json_file(
|
||||
std::wstring_view path, const nlohmann::json &data,
|
||||
std::optional<std::wstring_view> password = std::nullopt) -> bool;
|
||||
[[nodiscard]] auto
|
||||
write_json_file(std::wstring_view path, const nlohmann::json &data,
|
||||
std::optional<std::wstring_view> password = std::nullopt)
|
||||
-> bool;
|
||||
#else // !defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
|
||||
// INFO: has test
|
||||
[[nodiscard]] auto read_json_file(std::string_view path,
|
||||
nlohmann::json &data) -> bool;
|
||||
[[nodiscard]] auto read_json_file(std::string_view path, nlohmann::json &data)
|
||||
-> bool;
|
||||
|
||||
// INFO: has test
|
||||
[[nodiscard]] auto read_json_file(std::wstring_view path,
|
||||
nlohmann::json &data) -> bool;
|
||||
[[nodiscard]] auto read_json_file(std::wstring_view path, nlohmann::json &data)
|
||||
-> bool;
|
||||
|
||||
// INFO: has test
|
||||
[[nodiscard]] auto write_json_file(std::string_view path,
|
||||
|
||||
@@ -49,28 +49,28 @@ private:
|
||||
stop_type *stop_requested_{nullptr};
|
||||
|
||||
public:
|
||||
[[nodiscard]] auto copy_to(std::string_view new_path,
|
||||
bool overwrite) const -> bool override;
|
||||
[[nodiscard]] auto copy_to(std::string_view new_path, bool overwrite) const
|
||||
-> bool override;
|
||||
|
||||
[[nodiscard]] auto
|
||||
count(bool recursive = false) const -> std::uint64_t override;
|
||||
[[nodiscard]] auto count(bool recursive = false) const
|
||||
-> std::uint64_t override;
|
||||
|
||||
[[nodiscard]] auto
|
||||
create_directory(std::string_view path = "") const -> fs_directory_t override;
|
||||
[[nodiscard]] auto create_directory(std::string_view path = "") const
|
||||
-> fs_directory_t override;
|
||||
|
||||
[[nodiscard]] auto create_file(std::string_view file_name,
|
||||
bool read_only) const -> fs_file_t override;
|
||||
|
||||
[[nodiscard]] auto exists() const -> bool override;
|
||||
|
||||
[[nodiscard]] auto
|
||||
get_directory(std::string_view path) const -> fs_directory_t override;
|
||||
[[nodiscard]] auto get_directory(std::string_view path) const
|
||||
-> fs_directory_t override;
|
||||
|
||||
[[nodiscard]] auto
|
||||
get_directories() const -> std::vector<fs_directory_t> override;
|
||||
[[nodiscard]] auto get_directories() const
|
||||
-> std::vector<fs_directory_t> override;
|
||||
|
||||
[[nodiscard]] auto
|
||||
get_file(std::string_view path) const -> fs_file_t override;
|
||||
[[nodiscard]] auto get_file(std::string_view path) const
|
||||
-> fs_file_t override;
|
||||
|
||||
[[nodiscard]] auto get_files() const -> std::vector<fs_file_t> override;
|
||||
|
||||
@@ -88,8 +88,8 @@ public:
|
||||
|
||||
[[nodiscard]] auto remove_recursively() -> bool override;
|
||||
|
||||
[[nodiscard]] auto
|
||||
size(bool recursive = false) const -> std::uint64_t override;
|
||||
[[nodiscard]] auto size(bool recursive = false) const
|
||||
-> std::uint64_t override;
|
||||
|
||||
public:
|
||||
auto operator=(const directory &) noexcept -> directory & = delete;
|
||||
|
||||
@@ -27,32 +27,55 @@
|
||||
|
||||
#include "utils/error.hpp"
|
||||
|
||||
namespace repertory::utils::encryption {
|
||||
namespace repertory::utils::hash {
|
||||
using hash_32_t = std::array<unsigned char, 4U>;
|
||||
using hash_64_t = std::array<unsigned char, 8U>;
|
||||
using hash_128_t = std::array<unsigned char, 16U>;
|
||||
using hash_256_t = std::array<unsigned char, 32U>;
|
||||
using hash_384_t = std::array<unsigned char, 48U>;
|
||||
using hash_512_t = std::array<unsigned char, 64U>;
|
||||
|
||||
[[nodiscard]] auto create_hash_blake2b_32(std::string_view data) -> hash_32_t;
|
||||
|
||||
[[nodiscard]] auto create_hash_blake2b_32(std::wstring_view data) -> hash_32_t;
|
||||
|
||||
[[nodiscard]] auto create_hash_blake2b_32(const data_buffer &data) -> hash_32_t;
|
||||
|
||||
[[nodiscard]] auto create_hash_blake2b_64(std::string_view data) -> hash_64_t;
|
||||
|
||||
[[nodiscard]] auto create_hash_blake2b_64(std::wstring_view data) -> hash_64_t;
|
||||
|
||||
[[nodiscard]] auto create_hash_blake2b_64(const data_buffer &data) -> hash_64_t;
|
||||
|
||||
[[nodiscard]] auto create_hash_blake2b_128(std::string_view data) -> hash_128_t;
|
||||
|
||||
[[nodiscard]] auto create_hash_blake2b_128(std::wstring_view data)
|
||||
-> hash_128_t;
|
||||
|
||||
[[nodiscard]] auto create_hash_blake2b_128(const data_buffer &data)
|
||||
-> hash_128_t;
|
||||
|
||||
[[nodiscard]] auto create_hash_blake2b_256(std::string_view data) -> hash_256_t;
|
||||
|
||||
[[nodiscard]] auto
|
||||
create_hash_blake2b_256(std::wstring_view data) -> hash_256_t;
|
||||
[[nodiscard]] auto create_hash_blake2b_256(std::wstring_view data)
|
||||
-> hash_256_t;
|
||||
|
||||
[[nodiscard]] auto
|
||||
create_hash_blake2b_256(const data_buffer &data) -> hash_256_t;
|
||||
[[nodiscard]] auto create_hash_blake2b_256(const data_buffer &data)
|
||||
-> hash_256_t;
|
||||
|
||||
[[nodiscard]] auto create_hash_blake2b_384(std::string_view data) -> hash_384_t;
|
||||
|
||||
[[nodiscard]] auto
|
||||
create_hash_blake2b_384(std::wstring_view data) -> hash_384_t;
|
||||
[[nodiscard]] auto create_hash_blake2b_384(std::wstring_view data)
|
||||
-> hash_384_t;
|
||||
|
||||
[[nodiscard]] auto
|
||||
create_hash_blake2b_384(const data_buffer &data) -> hash_384_t;
|
||||
[[nodiscard]] auto create_hash_blake2b_384(const data_buffer &data)
|
||||
-> hash_384_t;
|
||||
|
||||
[[nodiscard]] auto
|
||||
create_hash_blake2b_512(std::wstring_view data) -> hash_512_t;
|
||||
[[nodiscard]] auto create_hash_blake2b_512(std::wstring_view data)
|
||||
-> hash_512_t;
|
||||
|
||||
[[nodiscard]] auto
|
||||
create_hash_blake2b_512(const data_buffer &data) -> hash_512_t;
|
||||
[[nodiscard]] auto create_hash_blake2b_512(const data_buffer &data)
|
||||
-> hash_512_t;
|
||||
|
||||
[[nodiscard]] auto create_hash_blake2b_512(std::string_view data) -> hash_512_t;
|
||||
|
||||
@@ -83,8 +106,8 @@ template <typename hash_t>
|
||||
std::function<hash_t(const unsigned char *data, std::size_t size)> &;
|
||||
|
||||
template <typename hash_t>
|
||||
auto create_hash_blake2b_t(const unsigned char *data,
|
||||
std::size_t data_size) -> hash_t {
|
||||
auto create_hash_blake2b_t(const unsigned char *data, std::size_t data_size)
|
||||
-> hash_t {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
hash_t hash{};
|
||||
@@ -123,6 +146,27 @@ auto create_hash_blake2b_t(const unsigned char *data,
|
||||
return hash;
|
||||
}
|
||||
|
||||
inline const std::function<hash_32_t(const unsigned char *data,
|
||||
std::size_t size)>
|
||||
blake2b_32_hasher =
|
||||
[](const unsigned char *data, std::size_t data_size) -> hash_32_t {
|
||||
return create_hash_blake2b_t<hash_32_t>(data, data_size);
|
||||
};
|
||||
|
||||
inline const std::function<hash_64_t(const unsigned char *data,
|
||||
std::size_t size)>
|
||||
blake2b_64_hasher =
|
||||
[](const unsigned char *data, std::size_t data_size) -> hash_64_t {
|
||||
return create_hash_blake2b_t<hash_64_t>(data, data_size);
|
||||
};
|
||||
|
||||
inline const std::function<hash_128_t(const unsigned char *data,
|
||||
std::size_t size)>
|
||||
blake2b_128_hasher =
|
||||
[](const unsigned char *data, std::size_t data_size) -> hash_128_t {
|
||||
return create_hash_blake2b_t<hash_128_t>(data, data_size);
|
||||
};
|
||||
|
||||
inline const std::function<hash_256_t(const unsigned char *data,
|
||||
std::size_t size)>
|
||||
blake2b_256_hasher =
|
||||
@@ -158,6 +202,24 @@ inline const std::function<hash_512_t(const unsigned char *data,
|
||||
return create_hash_sha512(data, data_size);
|
||||
};
|
||||
|
||||
template <>
|
||||
[[nodiscard]] inline auto default_create_hash<hash_32_t>() -> const
|
||||
std::function<hash_32_t(const unsigned char *data, std::size_t size)> & {
|
||||
return blake2b_32_hasher;
|
||||
}
|
||||
|
||||
template <>
|
||||
[[nodiscard]] inline auto default_create_hash<hash_64_t>() -> const
|
||||
std::function<hash_64_t(const unsigned char *data, std::size_t size)> & {
|
||||
return blake2b_64_hasher;
|
||||
}
|
||||
|
||||
template <>
|
||||
[[nodiscard]] inline auto default_create_hash<hash_128_t>() -> const
|
||||
std::function<hash_128_t(const unsigned char *data, std::size_t size)> & {
|
||||
return blake2b_128_hasher;
|
||||
}
|
||||
|
||||
template <>
|
||||
[[nodiscard]] inline auto default_create_hash<hash_256_t>() -> const
|
||||
std::function<hash_256_t(const unsigned char *data, std::size_t size)> & {
|
||||
@@ -175,7 +237,7 @@ template <>
|
||||
std::function<hash_512_t(const unsigned char *data, std::size_t size)> & {
|
||||
return blake2b_512_hasher;
|
||||
}
|
||||
} // namespace repertory::utils::encryption
|
||||
} // namespace repertory::utils::hash
|
||||
|
||||
#endif // defined(PROJECT_ENABLE_LIBSODIUM)
|
||||
#endif // REPERTORY_INCLUDE_UTILS_HASH_HPP_
|
||||
|
||||
59
support/include/utils/timeout.hpp
Normal file
59
support/include/utils/timeout.hpp
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
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 REPERTORY_INCLUDE_UTILS_TIMEOUT_HPP_
|
||||
#define REPERTORY_INCLUDE_UTILS_TIMEOUT_HPP_
|
||||
|
||||
#include "utils/config.hpp"
|
||||
|
||||
namespace repertory::utils {
|
||||
class timeout final {
|
||||
public:
|
||||
using callback_t = std::function<void()>;
|
||||
|
||||
public:
|
||||
timeout(const timeout &) noexcept = delete;
|
||||
timeout(timeout &&) noexcept = delete;
|
||||
auto operator=(const timeout &) noexcept -> timeout & = delete;
|
||||
auto operator=(timeout &&) noexcept -> timeout & = delete;
|
||||
|
||||
public:
|
||||
timeout(callback_t timeout_callback,
|
||||
std::chrono::system_clock::duration duration);
|
||||
|
||||
~timeout();
|
||||
|
||||
private:
|
||||
std::chrono::system_clock::duration duration_;
|
||||
callback_t timeout_callback_;
|
||||
std::atomic<bool> timeout_killed_{false};
|
||||
std::unique_ptr<std::thread> timeout_thread_{nullptr};
|
||||
std::mutex timeout_mutex_;
|
||||
std::condition_variable timeout_notify_;
|
||||
|
||||
public:
|
||||
void disable();
|
||||
|
||||
void reset();
|
||||
};
|
||||
} // namespace repertory::utils
|
||||
|
||||
#endif // REPERTORY_INCLUDE_UTILS_TIMEOUT_HPP_
|
||||
100
support/include/utils/ttl_cache.hpp
Normal file
100
support/include/utils/ttl_cache.hpp
Normal file
@@ -0,0 +1,100 @@
|
||||
#ifndef REPERTORY_INCLUDE_UTILS_TTL_CACHE_HPP_
|
||||
#define REPERTORY_INCLUDE_UTILS_TTL_CACHE_HPP_
|
||||
|
||||
#include "utils/config.hpp"
|
||||
|
||||
namespace repertory::utils {
|
||||
template <typename data_t, template <typename> class atomic_t = std::atomic>
|
||||
class ttl_cache final {
|
||||
public:
|
||||
using clock = std::chrono::steady_clock;
|
||||
using duration = std::chrono::milliseconds;
|
||||
using entry_t = atomic_t<data_t>;
|
||||
using entry_ptr_t = std::shared_ptr<entry_t>;
|
||||
|
||||
static constexpr auto default_expiration{duration(60000U)};
|
||||
|
||||
private:
|
||||
struct entry final {
|
||||
entry_ptr_t data;
|
||||
clock::time_point expires_at;
|
||||
};
|
||||
|
||||
public:
|
||||
ttl_cache(duration ttl = default_expiration) : ttl_{ttl} {}
|
||||
|
||||
private:
|
||||
duration ttl_;
|
||||
|
||||
private:
|
||||
mutable std::mutex mutex_;
|
||||
std::unordered_map<std::string, entry> entries_;
|
||||
|
||||
public:
|
||||
void clear() {
|
||||
mutex_lock lock(mutex_);
|
||||
entries_.clear();
|
||||
}
|
||||
|
||||
void erase(const std::string &api_path) {
|
||||
mutex_lock lock(mutex_);
|
||||
entries_.erase(api_path);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto contains(const std::string &api_path) -> bool {
|
||||
mutex_lock lock(mutex_);
|
||||
return entries_.contains(api_path);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get(const std::string &api_path) -> entry_ptr_t {
|
||||
mutex_lock lock(mutex_);
|
||||
auto iter = entries_.find(api_path);
|
||||
if (iter == entries_.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
iter->second.expires_at = clock::now() + ttl_;
|
||||
return iter->second.data;
|
||||
}
|
||||
|
||||
void purge_expired() {
|
||||
mutex_lock lock(mutex_);
|
||||
auto now = clock::now();
|
||||
for (auto iter = entries_.begin(); iter != entries_.end();) {
|
||||
if (iter->second.expires_at <= now) {
|
||||
iter = entries_.erase(iter);
|
||||
continue;
|
||||
}
|
||||
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_ttl() const -> duration {
|
||||
mutex_lock lock(mutex_);
|
||||
return ttl_;
|
||||
}
|
||||
|
||||
void set(const std::string &api_path, const data_t &data) {
|
||||
mutex_lock lock(mutex_);
|
||||
if (entries_.contains(api_path)) {
|
||||
auto &entry = entries_.at(api_path);
|
||||
entry.data->store(data);
|
||||
entry.expires_at = clock::now() + ttl_;
|
||||
return;
|
||||
}
|
||||
|
||||
entries_.emplace(api_path, entry{
|
||||
.data = std::make_shared<entry_t>(data),
|
||||
.expires_at = clock::now() + ttl_,
|
||||
});
|
||||
}
|
||||
|
||||
void set_ttl(duration ttl) {
|
||||
mutex_lock lock(mutex_);
|
||||
ttl_ = ttl;
|
||||
}
|
||||
};
|
||||
} // namespace repertory::utils
|
||||
|
||||
#endif // REPERTORY_INCLUDE_UTILS_TTL_CACHE_HPP_
|
||||
@@ -30,6 +30,7 @@
|
||||
namespace repertory::utils::file {
|
||||
enum class time_type {
|
||||
accessed,
|
||||
changed,
|
||||
created,
|
||||
modified,
|
||||
written,
|
||||
@@ -37,6 +38,7 @@ enum class time_type {
|
||||
|
||||
struct file_times final {
|
||||
std::uint64_t accessed{};
|
||||
std::uint64_t changed{};
|
||||
std::uint64_t created{};
|
||||
std::uint64_t modified{};
|
||||
std::uint64_t written{};
|
||||
@@ -47,6 +49,8 @@ struct file_times final {
|
||||
switch (type) {
|
||||
case time_type::accessed:
|
||||
return accessed;
|
||||
case time_type::changed:
|
||||
return changed;
|
||||
case time_type::created:
|
||||
return created;
|
||||
case time_type::modified:
|
||||
@@ -70,8 +74,8 @@ struct i_fs_item {
|
||||
[[nodiscard]] virtual auto copy_to(std::string_view to_path,
|
||||
bool overwrite) const -> bool = 0;
|
||||
|
||||
[[nodiscard]] virtual auto copy_to(std::wstring_view new_path,
|
||||
bool overwrite) -> bool {
|
||||
[[nodiscard]] virtual auto copy_to(std::wstring_view new_path, bool overwrite)
|
||||
-> bool {
|
||||
return copy_to(utils::string::to_utf8(new_path), overwrite);
|
||||
}
|
||||
|
||||
@@ -79,8 +83,8 @@ struct i_fs_item {
|
||||
|
||||
[[nodiscard]] virtual auto get_path() const -> std::string = 0;
|
||||
|
||||
[[nodiscard]] virtual auto
|
||||
get_time(time_type type) const -> std::optional<std::uint64_t>;
|
||||
[[nodiscard]] virtual auto get_time(time_type type) const
|
||||
-> std::optional<std::uint64_t>;
|
||||
|
||||
[[nodiscard]] virtual auto is_directory_item() const -> bool = 0;
|
||||
|
||||
|
||||
@@ -27,6 +27,40 @@
|
||||
#include "utils/config.hpp"
|
||||
|
||||
namespace repertory::utils {
|
||||
#if defined(__linux__)
|
||||
struct autostart_cfg final {
|
||||
std::string app_name;
|
||||
std::optional<std::string> comment;
|
||||
bool enabled{true};
|
||||
std::vector<std::string> exec_args;
|
||||
std::string exec_path;
|
||||
std::optional<std::string> icon_path;
|
||||
std::vector<std::string> only_show_in;
|
||||
bool terminal{false};
|
||||
};
|
||||
#endif // defined(__linux__)
|
||||
|
||||
#if defined(__APPLE__)
|
||||
enum class launchctl_type : std::uint8_t {
|
||||
bootout,
|
||||
bootstrap,
|
||||
kickstart,
|
||||
};
|
||||
|
||||
#if defined(PROJECT_ENABLE_PUGIXML)
|
||||
struct plist_cfg final {
|
||||
std::vector<std::string> args;
|
||||
bool keep_alive{false};
|
||||
std::string label;
|
||||
std::string plist_path;
|
||||
bool run_at_load{false};
|
||||
std::string stderr_log{"/tmp/stderr.log"};
|
||||
std::string stdout_log{"/tmp/stdout.log"};
|
||||
std::string working_dir{"/tmp"};
|
||||
};
|
||||
#endif // defined(PROJECT_ENABLE_PUGIXML)
|
||||
#endif // defined(__APPLE__)
|
||||
|
||||
using passwd_callback_t = std::function<void(struct passwd *pass)>;
|
||||
|
||||
#if defined(__APPLE__)
|
||||
@@ -37,6 +71,12 @@ template <typename thread_t>
|
||||
[[nodiscard]] auto convert_to_uint64(const pthread_t &thread) -> std::uint64_t;
|
||||
#endif // defined(__APPLE__)
|
||||
|
||||
#if defined(__linux__)
|
||||
[[nodiscard]] auto create_autostart_entry(const autostart_cfg &cfg,
|
||||
bool overwrite_existing = true)
|
||||
-> bool;
|
||||
#endif // defined(__linux__)
|
||||
|
||||
[[nodiscard]] auto get_last_error_code() -> int;
|
||||
|
||||
[[nodiscard]] auto get_thread_id() -> std::uint64_t;
|
||||
@@ -48,9 +88,30 @@ void set_last_error_code(int error_code);
|
||||
[[nodiscard]] auto use_getpwuid(uid_t uid, passwd_callback_t callback)
|
||||
-> utils::result;
|
||||
|
||||
#if defined(__linux__)
|
||||
[[nodiscard]] auto remove_autostart_entry(std::string_view name) -> bool;
|
||||
#endif // defined(__linux__)
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#if defined(PROJECT_ENABLE_PUGIXML)
|
||||
[[nodiscard]] auto generate_launchd_plist(const plist_cfg &cfg,
|
||||
bool overwrite_existing = true)
|
||||
-> bool;
|
||||
#endif // defined(PROJECT_ENABLE_PUGIXML)
|
||||
|
||||
#if defined(PROJECT_ENABLE_SPDLOG) || defined(PROJECT_ENABLE_FMT)
|
||||
[[nodiscard]] auto launchctl_command(std::string_view label,
|
||||
launchctl_type type) -> int;
|
||||
|
||||
[[nodiscard]] auto remove_launchd_plist(std::string_view plist_path,
|
||||
std::string_view label,
|
||||
bool should_bootout) -> bool;
|
||||
#endif // defined(PROJECT_ENABLE_SPDLOG) || defined(PROJECT_ENABLE_FMT)
|
||||
#endif // defined(__APPLE__)
|
||||
|
||||
// template implementations
|
||||
#if defined(__APPLE__)
|
||||
template <typename t>
|
||||
template <typename thread_t>
|
||||
[[nodiscard]] auto convert_to_uint64(const thread_t *thread_ptr)
|
||||
-> std::uint64_t {
|
||||
return static_cast<std::uint64_t>(
|
||||
|
||||
@@ -30,17 +30,42 @@ void create_console();
|
||||
|
||||
void free_console();
|
||||
|
||||
[[nodiscard]] auto get_available_drive_letter(char first = 'a')
|
||||
-> std::optional<std::string_view>;
|
||||
|
||||
[[nodiscard]] auto get_available_drive_letters(char first = 'a')
|
||||
-> std::vector<std::string_view>;
|
||||
|
||||
[[nodiscard]] auto get_local_app_data_directory() -> const std::string &;
|
||||
|
||||
[[nodiscard]] auto get_last_error_code() -> DWORD;
|
||||
|
||||
[[nodiscard]] auto get_startup_folder() -> std::wstring;
|
||||
|
||||
[[nodiscard]] auto get_thread_id() -> std::uint64_t;
|
||||
|
||||
[[nodiscard]] auto is_process_elevated() -> bool;
|
||||
|
||||
[[nodiscard]] auto run_process_elevated(std::vector<const char *> args) -> int;
|
||||
|
||||
void set_last_error_code(DWORD errorCode);
|
||||
struct shortcut_cfg final {
|
||||
std::wstring arguments;
|
||||
std::wstring exe_path;
|
||||
std::wstring icon_path;
|
||||
std::wstring location{get_startup_folder()};
|
||||
std::wstring shortcut_name;
|
||||
std::wstring working_directory;
|
||||
};
|
||||
|
||||
[[nodiscard]]
|
||||
auto create_shortcut(const shortcut_cfg &cfg, bool overwrite_existing = true)
|
||||
-> bool;
|
||||
|
||||
[[nodiscard]] auto
|
||||
remove_shortcut(std::wstring shortcut_name,
|
||||
const std::wstring &location = get_startup_folder()) -> bool;
|
||||
|
||||
void set_last_error_code(DWORD error_code);
|
||||
} // namespace repertory::utils
|
||||
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
@@ -23,8 +23,10 @@
|
||||
|
||||
#include "utils/encrypting_reader.hpp"
|
||||
|
||||
#include "utils/base64.hpp"
|
||||
#include "utils/collection.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/config.hpp"
|
||||
#include "utils/encryption.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/file.hpp"
|
||||
@@ -119,7 +121,7 @@ protected:
|
||||
reader_.set_read_position(reinterpret_cast<std::uintptr_t>(gptr()));
|
||||
|
||||
char c{};
|
||||
const auto res = encrypting_reader::reader_function(&c, 1U, 1U, &reader_);
|
||||
auto res = encrypting_reader::reader_function(&c, 1U, 1U, &reader_);
|
||||
if (res != 1) {
|
||||
return traits_type::eof();
|
||||
}
|
||||
@@ -180,171 +182,218 @@ encrypting_reader::encrypting_reader(
|
||||
std::string_view file_name, std::string_view source_path,
|
||||
stop_type_callback stop_requested_cb, std::string_view token,
|
||||
std::optional<std::string> relative_parent_path, std::size_t error_return)
|
||||
: key_(utils::encryption::generate_key<utils::encryption::hash_256_t>(
|
||||
token)),
|
||||
: keys_(utils::encryption::generate_key<utils::hash::hash_256_t>(token),
|
||||
utils::encryption::generate_key<utils::hash::hash_256_t>(token)),
|
||||
stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)) {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
if (not*source_file_) {
|
||||
throw utils::error::create_exception(function_name, {
|
||||
"file open failed",
|
||||
source_path,
|
||||
});
|
||||
}
|
||||
|
||||
data_buffer result;
|
||||
utils::encryption::encrypt_data(
|
||||
key_, reinterpret_cast<const unsigned char *>(file_name.data()),
|
||||
file_name.size(), result);
|
||||
encrypted_file_name_ = utils::collection::to_hex_string(result);
|
||||
|
||||
if (relative_parent_path.has_value()) {
|
||||
for (auto &&part :
|
||||
utils::string::split(relative_parent_path.value(),
|
||||
utils::path::directory_seperator, false)) {
|
||||
utils::encryption::encrypt_data(
|
||||
key_, reinterpret_cast<const unsigned char *>(part.c_str()),
|
||||
strnlen(part.c_str(), part.size()), result);
|
||||
encrypted_file_path_ += '/' + utils::collection::to_hex_string(result);
|
||||
}
|
||||
encrypted_file_path_ += '/' + encrypted_file_name_;
|
||||
}
|
||||
|
||||
auto opt_size = source_file_->size();
|
||||
if (not opt_size.has_value()) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to get file size",
|
||||
source_file_->get_path(),
|
||||
});
|
||||
}
|
||||
auto file_size = opt_size.value();
|
||||
|
||||
const auto total_chunks = utils::divide_with_ceiling(
|
||||
file_size, static_cast<std::uint64_t>(data_chunk_size_));
|
||||
total_size_ = file_size + (total_chunks * encryption_header_size);
|
||||
last_data_chunk_ = total_chunks - 1U;
|
||||
last_data_chunk_size_ = (file_size <= data_chunk_size_) ? file_size
|
||||
: (file_size % data_chunk_size_) == 0U
|
||||
? data_chunk_size_
|
||||
: file_size % data_chunk_size_;
|
||||
iv_list_.resize(total_chunks);
|
||||
for (auto &iv : iv_list_) {
|
||||
randombytes_buf(iv.data(), iv.size());
|
||||
}
|
||||
common_initialize(true);
|
||||
create_encrypted_paths(file_name, relative_parent_path);
|
||||
}
|
||||
|
||||
encrypting_reader::encrypting_reader(std::string_view encrypted_file_path,
|
||||
encrypting_reader::encrypting_reader(stop_type_callback stop_requested_cb,
|
||||
std::string_view encrypted_file_path,
|
||||
std::string_view source_path,
|
||||
stop_type_callback stop_requested_cb,
|
||||
std::string_view token,
|
||||
std::size_t error_return)
|
||||
: key_(utils::encryption::generate_key<utils::encryption::hash_256_t>(
|
||||
token)),
|
||||
: keys_(utils::encryption::generate_key<utils::hash::hash_256_t>(token),
|
||||
utils::encryption::generate_key<utils::hash::hash_256_t>(token)),
|
||||
stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)) {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
if (not*source_file_) {
|
||||
throw utils::error::create_exception(function_name, {
|
||||
"file open failed",
|
||||
source_path,
|
||||
});
|
||||
}
|
||||
|
||||
encrypted_file_path_ = encrypted_file_path;
|
||||
encrypted_file_name_ = utils::path::strip_to_file_name(encrypted_file_path_);
|
||||
|
||||
auto opt_size = source_file_->size();
|
||||
if (not opt_size.has_value()) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to get file size",
|
||||
source_file_->get_path(),
|
||||
});
|
||||
}
|
||||
auto file_size = opt_size.value();
|
||||
|
||||
const auto total_chunks = utils::divide_with_ceiling(
|
||||
file_size, static_cast<std::uint64_t>(data_chunk_size_));
|
||||
total_size_ = file_size + (total_chunks * encryption_header_size);
|
||||
last_data_chunk_ = total_chunks - 1U;
|
||||
last_data_chunk_size_ = (file_size <= data_chunk_size_) ? file_size
|
||||
: (file_size % data_chunk_size_) == 0U
|
||||
? data_chunk_size_
|
||||
: file_size % data_chunk_size_;
|
||||
iv_list_.resize(total_chunks);
|
||||
for (auto &iv : iv_list_) {
|
||||
randombytes_buf(iv.data(), iv.size());
|
||||
}
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)),
|
||||
encrypted_file_name_(
|
||||
utils::path::strip_to_file_name(std::string{encrypted_file_path})),
|
||||
encrypted_file_path_(encrypted_file_path) {
|
||||
common_initialize(true);
|
||||
}
|
||||
|
||||
encrypting_reader::encrypting_reader(
|
||||
std::string_view encrypted_file_path, std::string_view source_path,
|
||||
stop_type_callback stop_requested_cb, std::string_view token,
|
||||
stop_type_callback stop_requested_cb, std::string_view encrypted_file_path,
|
||||
std::string_view source_path, std::string_view token,
|
||||
std::vector<
|
||||
std::array<unsigned char, crypto_aead_xchacha20poly1305_IETF_NPUBBYTES>>
|
||||
iv_list,
|
||||
std::size_t error_return)
|
||||
: key_(utils::encryption::generate_key<utils::encryption::hash_256_t>(
|
||||
token)),
|
||||
: keys_(utils::encryption::generate_key<utils::hash::hash_256_t>(token),
|
||||
utils::encryption::generate_key<utils::hash::hash_256_t>(token)),
|
||||
stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)),
|
||||
encrypted_file_name_(
|
||||
utils::path::strip_to_file_name(std::string{encrypted_file_path})),
|
||||
encrypted_file_path_(encrypted_file_path),
|
||||
iv_list_(std::move(iv_list)) {
|
||||
common_initialize(false);
|
||||
}
|
||||
|
||||
encrypting_reader::encrypting_reader(
|
||||
std::string_view file_name, std::string_view source_path,
|
||||
stop_type_callback stop_requested_cb, std::string_view token,
|
||||
kdf_config cfg, std::optional<std::string> relative_parent_path,
|
||||
std::size_t error_return)
|
||||
: stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)) {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
common_initialize_kdf_keys(token, cfg);
|
||||
common_initialize(true);
|
||||
create_encrypted_paths(file_name, relative_parent_path);
|
||||
}
|
||||
|
||||
if (not*source_file_) {
|
||||
throw utils::error::create_exception(function_name, {
|
||||
"file open failed",
|
||||
source_path,
|
||||
});
|
||||
}
|
||||
encrypting_reader::encrypting_reader(stop_type_callback stop_requested_cb,
|
||||
std::string_view encrypted_file_path,
|
||||
std::string_view source_path,
|
||||
std::string_view token, kdf_config cfg,
|
||||
std::size_t error_return)
|
||||
: stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)),
|
||||
encrypted_file_name_(
|
||||
utils::path::strip_to_file_name(std::string{encrypted_file_path})),
|
||||
encrypted_file_path_(encrypted_file_path) {
|
||||
common_initialize_kdf_keys(token, cfg);
|
||||
common_initialize(true);
|
||||
}
|
||||
|
||||
encrypted_file_path_ = encrypted_file_path;
|
||||
encrypted_file_name_ = utils::path::strip_to_file_name(encrypted_file_path_);
|
||||
encrypting_reader::encrypting_reader(
|
||||
stop_type_callback stop_requested_cb, std::string_view encrypted_file_path,
|
||||
std::string_view source_path, std::string_view token, kdf_config cfg,
|
||||
std::vector<
|
||||
std::array<unsigned char, crypto_aead_xchacha20poly1305_IETF_NPUBBYTES>>
|
||||
iv_list,
|
||||
std::size_t error_return)
|
||||
: stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)),
|
||||
encrypted_file_name_(
|
||||
utils::path::strip_to_file_name(std::string{encrypted_file_path})),
|
||||
encrypted_file_path_(encrypted_file_path),
|
||||
iv_list_(std::move(iv_list)) {
|
||||
common_initialize_kdf_keys(token, cfg);
|
||||
common_initialize(false);
|
||||
}
|
||||
|
||||
auto opt_size = source_file_->size();
|
||||
if (not opt_size.has_value()) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"get file size failed",
|
||||
std::to_string(utils::get_last_error_code()),
|
||||
source_file_->get_path(),
|
||||
});
|
||||
}
|
||||
auto file_size{opt_size.value()};
|
||||
encrypting_reader::encrypting_reader(
|
||||
std::string_view file_name, std::string_view source_path,
|
||||
stop_type_callback stop_requested_cb,
|
||||
const utils::hash::hash_256_t &master_key, const kdf_config &cfg,
|
||||
std::optional<std::string> relative_parent_path, std::size_t error_return)
|
||||
: stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)) {
|
||||
common_initialize_kdf_data(cfg, master_key);
|
||||
auto [path_key, path_cfg] = cfg.create_subkey(
|
||||
kdf_context::path, utils::generate_secure_random<std::uint64_t>(),
|
||||
master_key);
|
||||
keys_.second = std::move(path_key);
|
||||
kdf_headers_->second = path_cfg.to_header();
|
||||
common_initialize(true);
|
||||
create_encrypted_paths(file_name, relative_parent_path);
|
||||
}
|
||||
|
||||
const auto total_chunks = utils::divide_with_ceiling(
|
||||
file_size, static_cast<std::uint64_t>(data_chunk_size_));
|
||||
total_size_ = file_size + (total_chunks * encryption_header_size);
|
||||
last_data_chunk_ = total_chunks - 1U;
|
||||
last_data_chunk_size_ = (file_size <= data_chunk_size_) ? file_size
|
||||
: (file_size % data_chunk_size_) == 0U
|
||||
? data_chunk_size_
|
||||
: file_size % data_chunk_size_;
|
||||
iv_list_ = std::move(iv_list);
|
||||
encrypting_reader::encrypting_reader(
|
||||
std::string_view file_name, std::string_view source_path,
|
||||
stop_type_callback stop_requested_cb,
|
||||
const utils::hash::hash_256_t &master_key,
|
||||
const std::pair<kdf_config, kdf_config> &configs,
|
||||
std::optional<std::string> relative_parent_path, std::size_t error_return)
|
||||
: stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)) {
|
||||
keys_ = {
|
||||
configs.first.recreate_subkey(utils::encryption::kdf_context::data,
|
||||
master_key),
|
||||
configs.second.recreate_subkey(utils::encryption::kdf_context::path,
|
||||
master_key),
|
||||
};
|
||||
kdf_headers_ = {
|
||||
configs.first.to_header(),
|
||||
configs.second.to_header(),
|
||||
};
|
||||
common_initialize(true);
|
||||
create_encrypted_paths(file_name, relative_parent_path);
|
||||
}
|
||||
|
||||
encrypting_reader::encrypting_reader(stop_type_callback stop_requested_cb,
|
||||
std::string_view encrypted_file_path,
|
||||
std::string_view source_path,
|
||||
const utils::hash::hash_256_t &master_key,
|
||||
const kdf_config &cfg,
|
||||
std::size_t error_return)
|
||||
: stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)),
|
||||
encrypted_file_name_(
|
||||
utils::path::strip_to_file_name(std::string{encrypted_file_path})),
|
||||
encrypted_file_path_(encrypted_file_path) {
|
||||
common_initialize_kdf_data(cfg, master_key);
|
||||
common_initialize_kdf_path(master_key);
|
||||
common_initialize(true);
|
||||
}
|
||||
|
||||
encrypting_reader::encrypting_reader(
|
||||
stop_type_callback stop_requested_cb, std::string_view encrypted_file_path,
|
||||
std::string_view source_path, const utils::hash::hash_256_t &master_key,
|
||||
const kdf_config &cfg,
|
||||
std::vector<
|
||||
std::array<unsigned char, crypto_aead_xchacha20poly1305_IETF_NPUBBYTES>>
|
||||
iv_list,
|
||||
std::size_t error_return)
|
||||
: stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)),
|
||||
encrypted_file_name_(
|
||||
utils::path::strip_to_file_name(std::string{encrypted_file_path})),
|
||||
encrypted_file_path_(encrypted_file_path),
|
||||
iv_list_(std::move(iv_list)) {
|
||||
common_initialize_kdf_data(cfg, master_key);
|
||||
common_initialize_kdf_path(master_key);
|
||||
common_initialize(false);
|
||||
}
|
||||
|
||||
encrypting_reader::encrypting_reader(
|
||||
stop_type_callback stop_requested_cb, std::string_view encrypted_file_path,
|
||||
std::string_view source_path, const utils::hash::hash_256_t &master_key,
|
||||
const std::pair<kdf_config, kdf_config> &configs,
|
||||
std::vector<
|
||||
std::array<unsigned char, crypto_aead_xchacha20poly1305_IETF_NPUBBYTES>>
|
||||
iv_list,
|
||||
std::size_t error_return)
|
||||
: stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)),
|
||||
encrypted_file_name_(
|
||||
utils::path::strip_to_file_name(std::string{encrypted_file_path})),
|
||||
encrypted_file_path_(encrypted_file_path),
|
||||
iv_list_(std::move(iv_list)) {
|
||||
keys_.first = configs.first.recreate_subkey(
|
||||
utils::encryption::kdf_context::data, master_key);
|
||||
keys_.second = configs.second.recreate_subkey(
|
||||
utils::encryption::kdf_context::path, master_key);
|
||||
kdf_headers_ = {
|
||||
configs.first.to_header(),
|
||||
configs.second.to_header(),
|
||||
};
|
||||
common_initialize(false);
|
||||
}
|
||||
|
||||
encrypting_reader::encrypting_reader(const encrypting_reader &reader)
|
||||
: key_(reader.key_),
|
||||
: keys_(reader.keys_),
|
||||
stop_requested_cb_(reader.stop_requested_cb_),
|
||||
error_return_(reader.error_return_),
|
||||
source_file_(
|
||||
utils::file::file::open_file(reader.source_file_->get_path(), true)),
|
||||
chunk_buffers_(reader.chunk_buffers_),
|
||||
encrypted_file_name_(reader.encrypted_file_name_),
|
||||
encrypted_file_path_(reader.encrypted_file_path_),
|
||||
iv_list_(reader.iv_list_),
|
||||
chunk_buffers_(reader.chunk_buffers_),
|
||||
kdf_headers_(reader.kdf_headers_),
|
||||
last_data_chunk_(reader.last_data_chunk_),
|
||||
last_data_chunk_size_(reader.last_data_chunk_size_),
|
||||
read_offset_(reader.read_offset_),
|
||||
total_size_(reader.total_size_) {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
if (not*source_file_) {
|
||||
if (not *source_file_) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"file open failed",
|
||||
@@ -354,15 +403,21 @@ encrypting_reader::encrypting_reader(const encrypting_reader &reader)
|
||||
}
|
||||
}
|
||||
|
||||
auto encrypting_reader::calculate_decrypted_size(std::uint64_t total_size)
|
||||
auto encrypting_reader::calculate_decrypted_size(std::uint64_t total_size,
|
||||
bool uses_kdf)
|
||||
-> std::uint64_t {
|
||||
if (uses_kdf) {
|
||||
total_size -= kdf_config::size();
|
||||
}
|
||||
|
||||
return total_size - (utils::divide_with_ceiling(
|
||||
total_size, static_cast<std::uint64_t>(
|
||||
get_encrypted_chunk_size())) *
|
||||
encryption_header_size);
|
||||
}
|
||||
|
||||
auto encrypting_reader::calculate_encrypted_size(std::string_view source_path)
|
||||
auto encrypting_reader::calculate_encrypted_size(std::string_view source_path,
|
||||
bool uses_kdf)
|
||||
-> std::uint64_t {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
@@ -375,11 +430,135 @@ auto encrypting_reader::calculate_encrypted_size(std::string_view source_path)
|
||||
source_path,
|
||||
});
|
||||
}
|
||||
auto file_size{opt_size.value()};
|
||||
|
||||
const auto total_chunks = utils::divide_with_ceiling(
|
||||
return calculate_encrypted_size(opt_size.value(), uses_kdf);
|
||||
}
|
||||
|
||||
auto encrypting_reader::calculate_encrypted_size(std::uint64_t size,
|
||||
bool uses_kdf)
|
||||
-> std::uint64_t {
|
||||
auto total_chunks = utils::divide_with_ceiling(
|
||||
size, static_cast<std::uint64_t>(data_chunk_size_));
|
||||
return size + (total_chunks * encryption_header_size) +
|
||||
(uses_kdf ? kdf_config::size() : 0U);
|
||||
}
|
||||
|
||||
void encrypting_reader::common_initialize(bool procces_iv_list) {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
if (not *source_file_) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"file open failed",
|
||||
source_file_->get_path(),
|
||||
});
|
||||
}
|
||||
|
||||
auto opt_size = source_file_->size();
|
||||
if (not opt_size.has_value()) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to get file size",
|
||||
source_file_->get_path(),
|
||||
});
|
||||
}
|
||||
auto file_size = opt_size.value();
|
||||
|
||||
auto total_chunks = utils::divide_with_ceiling(
|
||||
file_size, static_cast<std::uint64_t>(data_chunk_size_));
|
||||
return file_size + (total_chunks * encryption_header_size);
|
||||
total_size_ = file_size + (total_chunks * encryption_header_size) +
|
||||
(kdf_headers_.has_value() ? kdf_headers_->first.size() : 0U);
|
||||
last_data_chunk_ = total_chunks - 1U;
|
||||
last_data_chunk_size_ = (file_size <= data_chunk_size_) ? file_size
|
||||
: (file_size % data_chunk_size_) == 0U
|
||||
? data_chunk_size_
|
||||
: file_size % data_chunk_size_;
|
||||
if (not procces_iv_list) {
|
||||
return;
|
||||
}
|
||||
|
||||
iv_list_.resize(total_chunks);
|
||||
for (auto &data : iv_list_) {
|
||||
randombytes_buf(data.data(), data.size());
|
||||
}
|
||||
}
|
||||
|
||||
void encrypting_reader::common_initialize_kdf_data(
|
||||
const kdf_config &cfg, const utils::hash::hash_256_t &master_key) {
|
||||
auto [data_key, data_cfg] = cfg.create_subkey(
|
||||
kdf_context::data, utils::generate_secure_random<std::uint64_t>(),
|
||||
master_key);
|
||||
keys_.first = std::move(data_key);
|
||||
kdf_headers_ = {data_cfg.to_header(), {}};
|
||||
}
|
||||
|
||||
void encrypting_reader::common_initialize_kdf_keys(std::string_view token,
|
||||
kdf_config &cfg) {
|
||||
auto key =
|
||||
utils::encryption::generate_key<utils::hash::hash_256_t>(token, cfg);
|
||||
keys_ = {key, key};
|
||||
kdf_headers_ = {cfg.to_header(), cfg.to_header()};
|
||||
}
|
||||
|
||||
void encrypting_reader::common_initialize_kdf_path(
|
||||
const utils::hash::hash_256_t &master_key) {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
auto buffer = macaron::Base64::Decode(encrypted_file_path_);
|
||||
|
||||
kdf_config path_cfg;
|
||||
if (not kdf_config::from_header(buffer, path_cfg)) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {"failed to create path kdf config from header"});
|
||||
}
|
||||
|
||||
utils::hash::hash_256_t path_key;
|
||||
std::tie(path_key, std::ignore) =
|
||||
path_cfg.create_subkey(kdf_context::path, path_cfg.unique_id, master_key);
|
||||
|
||||
kdf_headers_->second = path_cfg.to_header();
|
||||
}
|
||||
|
||||
void encrypting_reader::create_encrypted_paths(
|
||||
std::string_view file_name,
|
||||
std::optional<std::string> relative_parent_path) {
|
||||
data_buffer result;
|
||||
utils::encryption::encrypt_data(
|
||||
keys_.second, reinterpret_cast<const unsigned char *>(file_name.data()),
|
||||
file_name.size(), result);
|
||||
if (kdf_headers_.has_value()) {
|
||||
result.insert(result.begin(), kdf_headers_->second.begin(),
|
||||
kdf_headers_->second.end());
|
||||
}
|
||||
|
||||
encrypted_file_name_ =
|
||||
kdf_headers_.has_value()
|
||||
? macaron::Base64::EncodeUrlSafe(result.data(), result.size())
|
||||
: utils::collection::to_hex_string(result);
|
||||
|
||||
if (not relative_parent_path.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &part :
|
||||
utils::string::split(relative_parent_path.value(),
|
||||
utils::path::directory_seperator, false)) {
|
||||
utils::encryption::encrypt_data(
|
||||
keys_.second, reinterpret_cast<const unsigned char *>(part.c_str()),
|
||||
strnlen(part.c_str(), part.size()), result);
|
||||
if (kdf_headers_.has_value()) {
|
||||
result.insert(result.begin(), kdf_headers_->second.begin(),
|
||||
kdf_headers_->second.end());
|
||||
}
|
||||
|
||||
encrypted_file_path_ +=
|
||||
'/' +
|
||||
(kdf_headers_.has_value()
|
||||
? macaron::Base64::EncodeUrlSafe(result.data(), result.size())
|
||||
: utils::collection::to_hex_string(result));
|
||||
}
|
||||
|
||||
encrypted_file_path_ += '/' + encrypted_file_name_;
|
||||
}
|
||||
|
||||
auto encrypting_reader::create_iostream() const
|
||||
@@ -388,24 +567,93 @@ auto encrypting_reader::create_iostream() const
|
||||
std::make_unique<encrypting_streambuf>(*this));
|
||||
}
|
||||
|
||||
auto encrypting_reader::get_kdf_config_for_data() const
|
||||
-> std::optional<kdf_config> {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
if (not kdf_headers_.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
kdf_config cfg;
|
||||
if (not kdf_config::from_header(kdf_headers_->first, cfg)) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"invalid kdf header",
|
||||
});
|
||||
}
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
auto encrypting_reader::get_kdf_config_for_path() const
|
||||
-> std::optional<kdf_config> {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
if (not kdf_headers_.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
kdf_config cfg;
|
||||
if (not kdf_config::from_header(kdf_headers_->second, cfg)) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"invalid kdf header",
|
||||
});
|
||||
}
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
auto encrypting_reader::reader_function(char *buffer, size_t size,
|
||||
size_t nitems) -> size_t {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
const auto read_size = static_cast<std::size_t>(std::min(
|
||||
static_cast<std::uint64_t>(size * nitems), total_size_ - read_offset_));
|
||||
auto read_size =
|
||||
static_cast<std::uint64_t>(size) * static_cast<std::uint64_t>(nitems);
|
||||
if (read_size == 0U) {
|
||||
return 0U;
|
||||
}
|
||||
|
||||
auto chunk = read_offset_ / encrypted_chunk_size_;
|
||||
auto chunk_offset = read_offset_ % encrypted_chunk_size_;
|
||||
std::span<char> dest(buffer, read_size);
|
||||
auto read_offset{read_offset_};
|
||||
std::size_t total_read{};
|
||||
auto total_size{total_size_};
|
||||
|
||||
auto ret = false;
|
||||
if (read_offset_ < total_size_) {
|
||||
if (kdf_headers_.has_value()) {
|
||||
auto &hdr = kdf_headers_->first;
|
||||
total_size -= hdr.size();
|
||||
|
||||
if (read_offset < hdr.size()) {
|
||||
auto to_read{
|
||||
utils::calculate_read_size(hdr.size(), read_size, read_offset),
|
||||
};
|
||||
read_offset_ += to_read;
|
||||
|
||||
std::memcpy(&dest[total_read], &hdr.at(read_offset), to_read);
|
||||
if (read_size - to_read == 0) {
|
||||
return to_read;
|
||||
}
|
||||
|
||||
read_offset = 0U;
|
||||
read_size -= to_read;
|
||||
total_read += to_read;
|
||||
} else {
|
||||
read_offset -= hdr.size();
|
||||
}
|
||||
}
|
||||
|
||||
auto chunk = static_cast<std::size_t>(read_offset / encrypted_chunk_size_);
|
||||
auto chunk_offset =
|
||||
static_cast<std::size_t>(read_offset % encrypted_chunk_size_);
|
||||
auto remain = utils::calculate_read_size(total_size, read_size, read_offset);
|
||||
|
||||
auto ret{false};
|
||||
if (read_offset < total_size) {
|
||||
try {
|
||||
ret = true;
|
||||
auto remain = read_size;
|
||||
while (not get_stop_requested() && ret && (remain != 0U)) {
|
||||
if (chunk_buffers_.find(chunk) == chunk_buffers_.end()) {
|
||||
if (not chunk_buffers_.contains(chunk)) {
|
||||
auto &chunk_buffer = chunk_buffers_[chunk];
|
||||
data_buffer file_data(chunk == last_data_chunk_
|
||||
? last_data_chunk_size_
|
||||
@@ -413,24 +661,26 @@ auto encrypting_reader::reader_function(char *buffer, size_t size,
|
||||
chunk_buffer.resize(file_data.size() + encryption_header_size);
|
||||
|
||||
std::size_t bytes_read{};
|
||||
if ((ret = source_file_->read(file_data, chunk * data_chunk_size_,
|
||||
&bytes_read))) {
|
||||
utils::encryption::encrypt_data(iv_list_.at(chunk), key_, file_data,
|
||||
chunk_buffer);
|
||||
ret = source_file_->read(
|
||||
file_data,
|
||||
static_cast<std::uint64_t>(chunk) *
|
||||
static_cast<std::uint64_t>(data_chunk_size_),
|
||||
&bytes_read);
|
||||
if (ret) {
|
||||
utils::encryption::encrypt_data(iv_list_.at(chunk), keys_.first,
|
||||
file_data, chunk_buffer);
|
||||
}
|
||||
} else if (chunk) {
|
||||
chunk_buffers_.erase(chunk - 1u);
|
||||
} else if (chunk != 0U) {
|
||||
chunk_buffers_.erase(chunk - 1U);
|
||||
}
|
||||
|
||||
auto &chunk_buffer = chunk_buffers_[chunk];
|
||||
const auto to_read = std::min(
|
||||
static_cast<std::size_t>(chunk_buffer.size() - chunk_offset),
|
||||
remain);
|
||||
std::memcpy(buffer + total_read, &chunk_buffer[chunk_offset], to_read);
|
||||
auto to_read = std::min(chunk_buffer.size() - chunk_offset, remain);
|
||||
std::memcpy(&dest[total_read], &chunk_buffer[chunk_offset], to_read);
|
||||
total_read += to_read;
|
||||
remain -= to_read;
|
||||
chunk_offset = 0u;
|
||||
chunk++;
|
||||
chunk_offset = 0U;
|
||||
++chunk;
|
||||
read_offset_ += to_read;
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
|
||||
@@ -23,11 +23,130 @@
|
||||
|
||||
#include "utils/encryption.hpp"
|
||||
|
||||
#include "utils/base64.hpp"
|
||||
#include "utils/collection.hpp"
|
||||
#include "utils/config.hpp"
|
||||
#include "utils/encrypting_reader.hpp"
|
||||
#include "utils/hash.hpp"
|
||||
#include "utils/path.hpp"
|
||||
|
||||
namespace {
|
||||
constexpr auto resize_by(repertory::data_span &data, std::size_t /* size */)
|
||||
-> repertory::data_span & {
|
||||
return data;
|
||||
}
|
||||
|
||||
auto resize_by(repertory::data_buffer &data, std::size_t size)
|
||||
-> repertory::data_buffer & {
|
||||
data.resize(data.size() + size);
|
||||
return data;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace repertory::utils::encryption {
|
||||
auto kdf_config::to_header() const -> data_buffer {
|
||||
kdf_config tmp{*this};
|
||||
tmp.checksum = boost::endian::native_to_big(tmp.checksum);
|
||||
tmp.unique_id = boost::endian::native_to_big(tmp.unique_id);
|
||||
|
||||
data_buffer ret(size());
|
||||
std::memcpy(ret.data(), &tmp, ret.size());
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto kdf_config::generate_checksum() const -> std::uint64_t {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
kdf_config tmp = *this;
|
||||
tmp.checksum = 0;
|
||||
|
||||
auto hash = utils::hash::create_hash_blake2b_64(tmp.to_header());
|
||||
std::uint64_t ret{};
|
||||
std::memcpy(&ret, hash.data(), hash.size());
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto kdf_config::from_header(data_cspan data, kdf_config &cfg,
|
||||
bool ignore_checksum) -> bool {
|
||||
if (data.size() < kdf_config::size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::memcpy(&cfg, data.data(), kdf_config::size());
|
||||
|
||||
cfg.checksum = boost::endian::big_to_native(cfg.checksum);
|
||||
cfg.unique_id = boost::endian::big_to_native(cfg.unique_id);
|
||||
return cfg.version == kdf_version::v1 && cfg.kdf == kdf_type::argon2id &&
|
||||
cfg.memlimit >= memlimit_level::level1 &&
|
||||
cfg.memlimit <= memlimit_level::level4 &&
|
||||
cfg.opslimit >= opslimit_level::level1 &&
|
||||
cfg.opslimit <= opslimit_level::level3 &&
|
||||
(ignore_checksum || cfg.checksum == cfg.generate_checksum());
|
||||
}
|
||||
|
||||
void kdf_config::seal() {
|
||||
randombytes_buf(salt.data(), salt.size());
|
||||
checksum = generate_checksum();
|
||||
}
|
||||
|
||||
auto decrypt_file_name(std::string_view encryption_token,
|
||||
std::string &file_name) -> bool {
|
||||
data_buffer buffer;
|
||||
if (not utils::collection::from_hex_string(file_name, buffer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file_name.clear();
|
||||
return utils::encryption::decrypt_data(encryption_token, buffer, file_name);
|
||||
}
|
||||
|
||||
auto decrypt_file_name(std::string_view encryption_token, const kdf_config &cfg,
|
||||
std::string &file_name) -> bool {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
auto buffer = macaron::Base64::Decode(file_name);
|
||||
|
||||
file_name.clear();
|
||||
return utils::encryption::decrypt_data(encryption_token, cfg, buffer,
|
||||
file_name);
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto decrypt_file_name(const utils::hash::hash_256_t &master_key,
|
||||
std::string &file_name) -> bool {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
auto buffer = macaron::Base64::Decode(file_name);
|
||||
|
||||
utils::encryption::kdf_config path_cfg;
|
||||
if (not utils::encryption::kdf_config::from_header(buffer, path_cfg)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto path_key = path_cfg.recreate_subkey(
|
||||
utils::encryption::kdf_context::path, master_key);
|
||||
|
||||
file_name.clear();
|
||||
return utils::encryption::decrypt_data(
|
||||
path_key, &buffer[utils::encryption::kdf_config::size()],
|
||||
buffer.size() - utils::encryption::kdf_config::size(), file_name);
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto decrypt_file_path(std::string_view encryption_token,
|
||||
std::string &file_path) -> bool {
|
||||
std::vector<std::string> decrypted_parts;
|
||||
@@ -49,21 +168,83 @@ auto decrypt_file_path(std::string_view encryption_token,
|
||||
return true;
|
||||
}
|
||||
|
||||
auto decrypt_file_name(std::string_view encryption_token,
|
||||
std::string &file_name) -> bool {
|
||||
data_buffer buffer;
|
||||
if (not utils::collection::from_hex_string(file_name, buffer)) {
|
||||
return false;
|
||||
auto decrypt_file_path(std::string_view encryption_token, const kdf_config &cfg,
|
||||
std::string &file_path) -> bool {
|
||||
std::vector<std::string> decrypted_parts;
|
||||
for (const auto &part : std::filesystem::path(file_path)) {
|
||||
auto file_name = part.string();
|
||||
if (file_name == "/") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (not decrypt_file_name(encryption_token, cfg, file_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
decrypted_parts.push_back(file_name);
|
||||
}
|
||||
|
||||
file_name.clear();
|
||||
return utils::encryption::decrypt_data(encryption_token, buffer, file_name);
|
||||
file_path =
|
||||
utils::path::create_api_path(utils::string::join(decrypted_parts, '/'));
|
||||
return true;
|
||||
}
|
||||
|
||||
auto read_encrypted_range(const http_range &range,
|
||||
const utils::encryption::hash_256_t &key,
|
||||
reader_func_t reader_func, std::uint64_t total_size,
|
||||
data_buffer &data) -> bool {
|
||||
auto decrypt_file_path(const utils::hash::hash_256_t &master_key,
|
||||
std::string &file_path) -> bool {
|
||||
std::vector<std::string> decrypted_parts;
|
||||
for (const auto &part : std::filesystem::path(file_path)) {
|
||||
auto file_name = part.string();
|
||||
if (file_name == "/") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (not decrypt_file_name(master_key, file_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
decrypted_parts.push_back(file_name);
|
||||
}
|
||||
|
||||
file_path =
|
||||
utils::path::create_api_path(utils::string::join(decrypted_parts, '/'));
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename data_t>
|
||||
[[nodiscard]] auto
|
||||
read_encrypted_range(http_range range, const utils::hash::hash_256_t &key,
|
||||
reader_func_t reader_func, std::uint64_t total_size,
|
||||
data_t &data, std::uint8_t file_header_size,
|
||||
std::size_t &bytes_read) -> bool {
|
||||
bytes_read = 0U;
|
||||
|
||||
{
|
||||
if (total_size == 0U) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::uint64_t begin = range.begin;
|
||||
std::uint64_t end = range.end;
|
||||
|
||||
if (begin >= total_size) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::uint64_t last = total_size - 1U;
|
||||
if (end > last) {
|
||||
end = last;
|
||||
}
|
||||
|
||||
if (end < begin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
range = http_range{
|
||||
.begin = begin,
|
||||
.end = end,
|
||||
};
|
||||
}
|
||||
|
||||
auto encrypted_chunk_size =
|
||||
utils::encryption::encrypting_reader::get_encrypted_chunk_size();
|
||||
auto data_chunk_size =
|
||||
@@ -75,22 +256,22 @@ auto read_encrypted_range(const http_range &range,
|
||||
auto source_offset = static_cast<std::size_t>(range.begin % data_chunk_size);
|
||||
|
||||
for (std::size_t chunk = start_chunk; chunk <= end_chunk; chunk++) {
|
||||
data_buffer cypher;
|
||||
auto start_offset = chunk * encrypted_chunk_size;
|
||||
data_buffer cipher;
|
||||
auto start_offset = (chunk * encrypted_chunk_size) + file_header_size;
|
||||
auto end_offset = std::min(
|
||||
start_offset + (total_size - (chunk * data_chunk_size)) +
|
||||
encryption_header_size - 1U,
|
||||
static_cast<std::uint64_t>(start_offset + encrypted_chunk_size - 1U));
|
||||
|
||||
if (not reader_func(cypher, start_offset, end_offset)) {
|
||||
if (not reader_func(cipher, start_offset, end_offset)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
data_buffer source_buffer;
|
||||
if (not utils::encryption::decrypt_data(key, cypher, source_buffer)) {
|
||||
if (not utils::encryption::decrypt_data(key, cipher, source_buffer)) {
|
||||
return false;
|
||||
}
|
||||
cypher.clear();
|
||||
cipher.clear();
|
||||
|
||||
auto data_size = static_cast<std::size_t>(std::min(
|
||||
remain, static_cast<std::uint64_t>(data_chunk_size - source_offset)));
|
||||
@@ -98,58 +279,8 @@ auto read_encrypted_range(const http_range &range,
|
||||
static_cast<std::int64_t>(source_offset)),
|
||||
std::next(source_buffer.begin(),
|
||||
static_cast<std::int64_t>(source_offset + data_size)),
|
||||
std::back_inserter(data));
|
||||
remain -= data_size;
|
||||
source_offset = 0U;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto read_encrypted_range(const http_range &range,
|
||||
const utils::encryption::hash_256_t &key,
|
||||
reader_func_t reader_func, std::uint64_t total_size,
|
||||
unsigned char *data, std::size_t size,
|
||||
std::size_t &bytes_read) -> bool {
|
||||
bytes_read = 0U;
|
||||
|
||||
auto encrypted_chunk_size =
|
||||
utils::encryption::encrypting_reader::get_encrypted_chunk_size();
|
||||
auto data_chunk_size =
|
||||
utils::encryption::encrypting_reader::get_data_chunk_size();
|
||||
|
||||
auto start_chunk = static_cast<std::size_t>(range.begin / data_chunk_size);
|
||||
auto end_chunk = static_cast<std::size_t>(range.end / data_chunk_size);
|
||||
auto remain = range.end - range.begin + 1U;
|
||||
auto source_offset = static_cast<std::size_t>(range.begin % data_chunk_size);
|
||||
|
||||
std::span dest_buffer(data, size);
|
||||
for (std::size_t chunk = start_chunk; chunk <= end_chunk; chunk++) {
|
||||
data_buffer cypher;
|
||||
auto start_offset = chunk * encrypted_chunk_size;
|
||||
auto end_offset = std::min(
|
||||
start_offset + (total_size - (chunk * data_chunk_size)) +
|
||||
encryption_header_size - 1U,
|
||||
static_cast<std::uint64_t>(start_offset + encrypted_chunk_size - 1U));
|
||||
|
||||
if (not reader_func(cypher, start_offset, end_offset)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
data_buffer source_buffer;
|
||||
if (not utils::encryption::decrypt_data(key, cypher, source_buffer)) {
|
||||
return false;
|
||||
}
|
||||
cypher.clear();
|
||||
|
||||
auto data_size = static_cast<std::size_t>(std::min(
|
||||
remain, static_cast<std::uint64_t>(data_chunk_size - source_offset)));
|
||||
std::copy(
|
||||
std::next(source_buffer.begin(),
|
||||
static_cast<std::int64_t>(source_offset)),
|
||||
std::next(source_buffer.begin(),
|
||||
static_cast<std::int64_t>(source_offset + data_size)),
|
||||
std::next(dest_buffer.begin(), static_cast<std::int64_t>(bytes_read)));
|
||||
std::next(resize_by(data, data_size).begin(),
|
||||
static_cast<std::int64_t>(bytes_read)));
|
||||
remain -= data_size;
|
||||
bytes_read += data_size;
|
||||
source_offset = 0U;
|
||||
@@ -157,6 +288,26 @@ auto read_encrypted_range(const http_range &range,
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto read_encrypted_range(const http_range &range,
|
||||
const utils::hash::hash_256_t &key, bool uses_kdf,
|
||||
reader_func_t reader_func, std::uint64_t total_size,
|
||||
data_buffer &data) -> bool {
|
||||
std::size_t bytes_read{};
|
||||
return read_encrypted_range<data_buffer>(
|
||||
range, key, reader_func, total_size, data,
|
||||
uses_kdf ? kdf_config::size() : 0U, bytes_read);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto read_encrypted_range(
|
||||
const http_range &range, const utils::hash::hash_256_t &key, bool uses_kdf,
|
||||
reader_func_t reader_func, std::uint64_t total_size, unsigned char *data,
|
||||
std::size_t size, std::size_t &bytes_read) -> bool {
|
||||
data_span dest_buffer(data, size);
|
||||
return read_encrypted_range<data_span>(
|
||||
range, key, reader_func, total_size, dest_buffer,
|
||||
uses_kdf ? kdf_config::size() : 0U, bytes_read);
|
||||
}
|
||||
} // namespace repertory::utils::encryption
|
||||
|
||||
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined (PROJECT_ENABLE_BOOST)
|
||||
|
||||
@@ -53,9 +53,11 @@ auto change_to_process_directory() -> bool {
|
||||
}
|
||||
#else // !defined(_WIN32)
|
||||
std::string path;
|
||||
path.resize(PATH_MAX + 1);
|
||||
path.resize(repertory::max_path_length + 1);
|
||||
#if defined(__APPLE__)
|
||||
proc_pidpath(getpid(), path.c_str(), path.size());
|
||||
auto res = proc_pidpath(getpid(), reinterpret_cast<void *>(path.data()),
|
||||
static_cast<uint32_t>(path.size()));
|
||||
path = path.c_str();
|
||||
#else // !defined(__APPLE__)
|
||||
auto res = readlink("/proc/self/exe", path.data(), path.size());
|
||||
if (res == -1) {
|
||||
@@ -133,8 +135,8 @@ auto get_free_drive_space(std::string_view path)
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
#if defined(__linux__)
|
||||
struct statfs64 st{};
|
||||
if (statfs64(std::string{path}.c_str(), &st) != 0) {
|
||||
struct statfs64 u_stat{};
|
||||
if (statfs64(std::string{path}.c_str(), &u_stat) != 0) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to get free disk space",
|
||||
@@ -143,12 +145,12 @@ auto get_free_drive_space(std::string_view path)
|
||||
});
|
||||
}
|
||||
|
||||
return st.f_bfree * static_cast<std::uint64_t>(st.f_bsize);
|
||||
return u_stat.f_bfree * static_cast<std::uint64_t>(u_stat.f_bsize);
|
||||
#endif // defined(__linux__)
|
||||
|
||||
#if defined(__APPLE__)
|
||||
struct statvfs st{};
|
||||
if (statvfs(path.c_str(), &st) != 0) {
|
||||
struct statvfs u_stat{};
|
||||
if (statvfs(std::string{path}.c_str(), &u_stat) != 0) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to get free disk space",
|
||||
@@ -157,7 +159,7 @@ auto get_free_drive_space(std::string_view path)
|
||||
});
|
||||
}
|
||||
|
||||
return st.f_bfree * static_cast<std::uint64_t>(st.f_frsize);
|
||||
return u_stat.f_bfree * static_cast<std::uint64_t>(u_stat.f_frsize);
|
||||
#endif // defined(__APPLE__)
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
@@ -207,6 +209,7 @@ auto get_times(std::string_view path) -> std::optional<file_times> {
|
||||
if (res) {
|
||||
ret.accessed =
|
||||
utils::time::windows_file_time_to_unix_time(times.at(1U));
|
||||
ret.changed = utils::time::windows_file_time_to_unix_time(times.at(2U));
|
||||
ret.created = utils::time::windows_file_time_to_unix_time(times.at(0U));
|
||||
ret.modified =
|
||||
utils::time::windows_file_time_to_unix_time(times.at(2U));
|
||||
@@ -215,8 +218,8 @@ auto get_times(std::string_view path) -> std::optional<file_times> {
|
||||
}
|
||||
}
|
||||
|
||||
struct _stat64 st{};
|
||||
if (_stat64(std::string{path}.c_str(), &st) != 0) {
|
||||
struct _stat64 u_stat{};
|
||||
if (_stat64(std::string{path}.c_str(), &u_stat) != 0) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to get file times",
|
||||
@@ -225,13 +228,14 @@ auto get_times(std::string_view path) -> std::optional<file_times> {
|
||||
});
|
||||
}
|
||||
|
||||
ret.accessed = utils::time::windows_time_t_to_unix_time(st.st_atime);
|
||||
ret.created = utils::time::windows_time_t_to_unix_time(st.st_ctime);
|
||||
ret.modified = utils::time::windows_time_t_to_unix_time(st.st_mtime);
|
||||
ret.written = utils::time::windows_time_t_to_unix_time(st.st_mtime);
|
||||
ret.accessed = utils::time::windows_time_t_to_unix_time(u_stat.st_atime);
|
||||
ret.changed = utils::time::windows_time_t_to_unix_time(u_stat.st_ctime);
|
||||
ret.created = utils::time::windows_time_t_to_unix_time(u_stat.st_ctime);
|
||||
ret.modified = utils::time::windows_time_t_to_unix_time(u_stat.st_mtime);
|
||||
ret.written = utils::time::windows_time_t_to_unix_time(u_stat.st_mtime);
|
||||
#else // !defined(_WIN32)
|
||||
struct stat64 st{};
|
||||
if (stat64(std::string{path}.c_str(), &st) != 0) {
|
||||
struct stat64 u_stat{};
|
||||
if (stat64(std::string{path}.c_str(), &u_stat) != 0) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to get file times",
|
||||
@@ -240,19 +244,39 @@ auto get_times(std::string_view path) -> std::optional<file_times> {
|
||||
});
|
||||
}
|
||||
|
||||
ret.accessed = static_cast<std::uint64_t>(st.st_atim.tv_nsec) +
|
||||
static_cast<std::uint64_t>(st.st_atim.tv_sec) *
|
||||
#if defined(__APPLE__)
|
||||
ret.accessed = static_cast<std::uint64_t>(u_stat.st_atimespec.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_atimespec.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
ret.created = static_cast<std::uint64_t>(st.st_ctim.tv_nsec) +
|
||||
static_cast<std::uint64_t>(st.st_ctim.tv_sec) *
|
||||
ret.created = static_cast<std::uint64_t>(u_stat.st_birthtimespec.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_birthtimespec.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
ret.modified = static_cast<std::uint64_t>(st.st_mtim.tv_nsec) +
|
||||
static_cast<std::uint64_t>(st.st_mtim.tv_sec) *
|
||||
ret.changed = static_cast<std::uint64_t>(u_stat.st_ctimespec.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_ctimespec.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
ret.modified = static_cast<std::uint64_t>(u_stat.st_mtimespec.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_mtimespec.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
ret.written = static_cast<std::uint64_t>(st.st_mtim.tv_nsec) +
|
||||
static_cast<std::uint64_t>(st.st_mtim.tv_sec) *
|
||||
ret.written = static_cast<std::uint64_t>(u_stat.st_mtimespec.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_mtimespec.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
|
||||
#else // !defined(__APPLE__)
|
||||
ret.accessed = static_cast<std::uint64_t>(u_stat.st_atim.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_atim.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
ret.changed = static_cast<std::uint64_t>(u_stat.st_ctim.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_ctim.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
ret.created = static_cast<std::uint64_t>(u_stat.st_ctim.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_ctim.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
ret.modified = static_cast<std::uint64_t>(u_stat.st_mtim.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_mtim.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
ret.written = static_cast<std::uint64_t>(u_stat.st_mtim.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_mtim.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
#endif // defined(__APPLE__)
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
return ret;
|
||||
@@ -290,8 +314,8 @@ auto get_total_drive_space(std::string_view path)
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
#if defined(__linux__)
|
||||
struct statfs64 st{};
|
||||
if (statfs64(std::string{path}.c_str(), &st) != 0) {
|
||||
struct statfs64 u_stat{};
|
||||
if (statfs64(std::string{path}.c_str(), &u_stat) != 0) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to get total disk space",
|
||||
@@ -300,12 +324,12 @@ auto get_total_drive_space(std::string_view path)
|
||||
});
|
||||
}
|
||||
|
||||
return st.f_blocks * static_cast<std::uint64_t>(st.f_bsize);
|
||||
return u_stat.f_blocks * static_cast<std::uint64_t>(u_stat.f_bsize);
|
||||
#endif // defined(__linux__)
|
||||
|
||||
#if defined(__APPLE__)
|
||||
struct statvfs st{};
|
||||
if (statvfs(path.c_str(), &st) != 0) {
|
||||
struct statvfs u_stat{};
|
||||
if (statvfs(std::string{path}.c_str(), &u_stat) != 0) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to get total disk space",
|
||||
@@ -314,7 +338,7 @@ auto get_total_drive_space(std::string_view path)
|
||||
});
|
||||
}
|
||||
|
||||
return st.f_blocks * static_cast<std::uint64_t>(st.f_frsize);
|
||||
return u_stat.f_blocks * static_cast<std::uint64_t>(u_stat.f_frsize);
|
||||
#endif // defined(__APPLE__)
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
@@ -498,8 +522,7 @@ auto write_json_file(std::wstring_view path, const nlohmann::json &data)
|
||||
#endif // defined(PROJECT_ENABLE_JSON)
|
||||
|
||||
#if defined(PROJECT_ENABLE_LIBDSM)
|
||||
static constexpr auto validate_smb_path =
|
||||
[](std::string_view path) -> bool {
|
||||
static constexpr auto validate_smb_path = [](std::string_view path) -> bool {
|
||||
return (not utils::string::begins_with(path, "///") &&
|
||||
utils::string::begins_with(path, "//") &&
|
||||
// not utils::string::contains(path, " ") &&
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
*/
|
||||
#include "utils/file_directory.hpp"
|
||||
|
||||
#include "utils/com_init_wrapper.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/string.hpp"
|
||||
@@ -83,19 +84,19 @@ auto traverse_directory(
|
||||
});
|
||||
}
|
||||
|
||||
struct dirent *de{nullptr};
|
||||
while (res && (de = readdir(root)) && !is_stop_requested()) {
|
||||
if (de->d_type == DT_DIR) {
|
||||
if ((std::string_view(de->d_name) == ".") ||
|
||||
(std::string_view(de->d_name) == "..")) {
|
||||
struct dirent *entry{nullptr};
|
||||
while (res && (entry = ::readdir(root)) && !is_stop_requested()) {
|
||||
if (entry->d_type == DT_DIR) {
|
||||
if ((std::string_view(entry->d_name) == ".") ||
|
||||
(std::string_view(entry->d_name) == "..")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
res = directory_action(repertory::utils::file::directory(
|
||||
repertory::utils::path::combine(path, {de->d_name})));
|
||||
repertory::utils::path::combine(path, {entry->d_name})));
|
||||
} else {
|
||||
res = file_action(repertory::utils::file::file(
|
||||
repertory::utils::path::combine(path, {de->d_name})));
|
||||
repertory::utils::path::combine(path, {entry->d_name})));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,16 +113,67 @@ auto directory::copy_to(std::string_view new_path, bool overwrite) const
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to copy directory",
|
||||
"not implemented",
|
||||
utils::string::from_bool(overwrite),
|
||||
new_path,
|
||||
path_,
|
||||
});
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
if (not exists()) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to copy directory",
|
||||
"source does not exist",
|
||||
path_,
|
||||
std::string{new_path},
|
||||
});
|
||||
}
|
||||
|
||||
auto src_root = utils::path::finalize(path_);
|
||||
auto dst_root = utils::path::finalize(new_path);
|
||||
|
||||
if (directory{dst_root}.exists()) {
|
||||
auto src_base = utils::path::strip_to_file_name(src_root);
|
||||
dst_root = utils::path::combine(dst_root, {src_base});
|
||||
} else {
|
||||
auto dst_parent = utils::path::get_parent_path(dst_root);
|
||||
if (not dst_parent.empty() && not directory{dst_parent}.exists()) {
|
||||
auto parent_parent = utils::path::get_parent_path(dst_parent);
|
||||
auto last_piece = utils::path::strip_to_file_name(dst_parent);
|
||||
[[maybe_unused]] auto sub_dir =
|
||||
directory{parent_parent}.create_directory(last_piece);
|
||||
}
|
||||
if (not directory{dst_root}.exists()) {
|
||||
auto root_parent = utils::path::get_parent_path(dst_root);
|
||||
auto root_name = utils::path::strip_to_file_name(dst_root);
|
||||
[[maybe_unused]] auto sub_dir =
|
||||
directory{root_parent}.create_directory(root_name);
|
||||
}
|
||||
}
|
||||
|
||||
auto success = traverse_directory(
|
||||
src_root,
|
||||
[this, &dst_root, &src_root](auto &&dir_item) -> bool {
|
||||
auto child_src = dir_item.get_path();
|
||||
auto rel_path = utils::path::get_relative_path(child_src, src_root);
|
||||
auto child_dst = utils::path::combine(dst_root, {rel_path});
|
||||
|
||||
auto child_parent = utils::path::get_parent_path(child_dst);
|
||||
auto child_name = utils::path::strip_to_file_name(child_dst);
|
||||
[[maybe_unused]] auto sub_dir =
|
||||
directory{child_parent}.create_directory(child_name);
|
||||
return not is_stop_requested();
|
||||
},
|
||||
[this, &dst_root, overwrite, &src_root](auto &&file_item) -> bool {
|
||||
auto child_src = file_item.get_path();
|
||||
auto rel_path = utils::path::get_relative_path(child_src, src_root);
|
||||
auto child_dst = utils::path::combine(dst_root, {rel_path});
|
||||
|
||||
if (not file{child_src}.copy_to(child_dst, overwrite)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return not is_stop_requested();
|
||||
},
|
||||
stop_requested_);
|
||||
|
||||
return success && not is_stop_requested();
|
||||
} catch (const std::exception &ex) {
|
||||
utils::error::handle_exception(function_name, ex);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
@@ -137,7 +189,7 @@ auto directory::count(bool recursive) const -> std::uint64_t {
|
||||
|
||||
traverse_directory(
|
||||
path_,
|
||||
[&ret, &recursive](auto dir_item) -> bool {
|
||||
[&ret, &recursive](auto &&dir_item) -> bool {
|
||||
if (recursive) {
|
||||
ret += dir_item.count(true);
|
||||
}
|
||||
@@ -145,7 +197,7 @@ auto directory::count(bool recursive) const -> std::uint64_t {
|
||||
++ret;
|
||||
return true;
|
||||
},
|
||||
[&ret](auto /* file_item */) -> bool {
|
||||
[&ret](auto && /* file_item */) -> bool {
|
||||
++ret;
|
||||
return true;
|
||||
},
|
||||
@@ -172,6 +224,8 @@ auto directory::create_directory(std::string_view path) const
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
[[maybe_unused]] thread_local const utils::com_init_wrapper wrapper;
|
||||
|
||||
auto res = ::SHCreateDirectory(nullptr,
|
||||
utils::string::from_utf8(abs_path).c_str());
|
||||
if (res != ERROR_SUCCESS) {
|
||||
@@ -215,8 +269,8 @@ auto directory::exists() const -> bool {
|
||||
#if defined(_WIN32)
|
||||
return ::PathIsDirectoryA(path_.c_str()) != 0;
|
||||
#else // !defined(_WIN32)
|
||||
struct stat64 st{};
|
||||
return (stat64(path_.c_str(), &st) == 0 && S_ISDIR(st.st_mode));
|
||||
struct stat64 u_stat{};
|
||||
return (stat64(path_.c_str(), &u_stat) == 0 && S_ISDIR(u_stat.st_mode));
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
return false;
|
||||
@@ -249,14 +303,14 @@ auto directory::get_directories() const -> std::vector<fs_directory_t> {
|
||||
|
||||
traverse_directory(
|
||||
path_,
|
||||
[this, &ret](auto dir_item) -> bool {
|
||||
[this, &ret](auto &&dir_item) -> bool {
|
||||
ret.emplace_back(fs_directory_t{
|
||||
new directory(dir_item.get_path(), stop_requested_),
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
[](auto /* file_item */) -> bool { return true; }, stop_requested_);
|
||||
[](auto && /* file_item */) -> bool { return true; }, stop_requested_);
|
||||
|
||||
return ret;
|
||||
} catch (const std::exception &e) {
|
||||
@@ -308,8 +362,8 @@ auto directory::get_files() const -> std::vector<fs_file_t> {
|
||||
std::vector<fs_file_t> ret{};
|
||||
|
||||
traverse_directory(
|
||||
path_, [](auto /* dir_item */) -> bool { return true; },
|
||||
[&ret](auto file_item) -> bool {
|
||||
path_, [](auto && /* dir_item */) -> bool { return true; },
|
||||
[&ret](auto &&file_item) -> bool {
|
||||
ret.emplace_back(fs_file_t{
|
||||
new file(file_item.get_path()),
|
||||
});
|
||||
@@ -335,13 +389,13 @@ auto directory::get_items() const -> std::vector<fs_item_t> {
|
||||
|
||||
traverse_directory(
|
||||
path_,
|
||||
[this, &ret](auto dir_item) -> bool {
|
||||
[this, &ret](auto &&dir_item) -> bool {
|
||||
ret.emplace_back(fs_item_t{
|
||||
new directory(dir_item.get_path(), stop_requested_),
|
||||
});
|
||||
return true;
|
||||
},
|
||||
[&ret](auto file_item) -> bool {
|
||||
[&ret](auto &&file_item) -> bool {
|
||||
ret.emplace_back(fs_item_t{
|
||||
new file(file_item.get_path()),
|
||||
});
|
||||
@@ -381,15 +435,68 @@ auto directory::move_to(std::string_view new_path) -> bool {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to move directory",
|
||||
"not implemented",
|
||||
new_path,
|
||||
path_,
|
||||
});
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
if (not exists()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto src_root = utils::path::finalize(path_);
|
||||
auto dst_root = utils::path::finalize(new_path);
|
||||
if (src_root == dst_root) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (directory{dst_root}.exists()) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to move directory",
|
||||
"destination exists",
|
||||
src_root,
|
||||
dst_root,
|
||||
});
|
||||
}
|
||||
|
||||
auto dst_parent = utils::path::get_parent_path(dst_root);
|
||||
if (not dst_parent.empty() && not directory{dst_parent}.exists()) {
|
||||
auto parent_parent = utils::path::get_parent_path(dst_parent);
|
||||
auto last_piece = utils::path::strip_to_file_name(dst_parent);
|
||||
[[maybe_unused]] auto sub_dir =
|
||||
directory{parent_parent}.create_directory(last_piece);
|
||||
}
|
||||
|
||||
if (not directory{dst_root}.exists()) {
|
||||
auto root_parent = utils::path::get_parent_path(dst_root);
|
||||
auto root_name = utils::path::strip_to_file_name(dst_root);
|
||||
[[maybe_unused]] auto sub_dir =
|
||||
directory{root_parent}.create_directory(root_name);
|
||||
}
|
||||
|
||||
if (not copy_to(dst_root, true)) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to move directory",
|
||||
"copy failed",
|
||||
src_root,
|
||||
dst_root,
|
||||
});
|
||||
}
|
||||
|
||||
if (is_stop_requested()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (not remove_recursively()) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to move directory",
|
||||
"remove source failed",
|
||||
src_root,
|
||||
});
|
||||
}
|
||||
|
||||
path_ = dst_root;
|
||||
return true;
|
||||
} catch (const std::exception &ex) {
|
||||
utils::error::handle_exception(function_name, ex);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
@@ -436,8 +543,10 @@ auto directory::remove_recursively() -> bool {
|
||||
|
||||
if (not traverse_directory(
|
||||
path_,
|
||||
[](auto dir_item) -> bool { return dir_item.remove_recursively(); },
|
||||
[](auto file_item) -> bool { return file_item.remove(); },
|
||||
[](auto &&dir_item) -> bool {
|
||||
return dir_item.remove_recursively();
|
||||
},
|
||||
[](auto &&file_item) -> bool { return file_item.remove(); },
|
||||
stop_requested_)) {
|
||||
return false;
|
||||
}
|
||||
@@ -460,14 +569,14 @@ auto directory::size(bool recursive) const -> std::uint64_t {
|
||||
|
||||
traverse_directory(
|
||||
path_,
|
||||
[&ret, &recursive](auto dir_item) -> bool {
|
||||
[&ret, &recursive](auto &&dir_item) -> bool {
|
||||
if (recursive) {
|
||||
ret += dir_item.size(true);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
[&ret](auto file_item) -> bool {
|
||||
[&ret](auto &&file_item) -> bool {
|
||||
auto cur_size = file_item.size();
|
||||
if (cur_size.has_value()) {
|
||||
ret += cur_size.value();
|
||||
|
||||
@@ -43,7 +43,7 @@ auto enc_file::copy_to(std::string_view new_path, bool overwrite) const
|
||||
return file_->copy_to(new_path, overwrite);
|
||||
}
|
||||
|
||||
void enc_file::flush() const { return file_->flush(); }
|
||||
void enc_file::flush() const { file_->flush(); }
|
||||
|
||||
auto enc_file::move_to(std::string_view path) -> bool {
|
||||
return file_->move_to(path);
|
||||
@@ -68,8 +68,8 @@ auto enc_file::read(unsigned char *data, std::size_t to_read,
|
||||
std::size_t bytes_read{};
|
||||
auto ret{
|
||||
utils::encryption::read_encrypted_range(
|
||||
{offset, offset + to_read - 1U},
|
||||
utils::encryption::generate_key<utils::encryption::hash_256_t>(
|
||||
{.begin = offset, .end = offset + to_read - 1U},
|
||||
utils::encryption::generate_key<utils::hash::hash_256_t>(
|
||||
encryption_token_),
|
||||
[&](auto &&ct_buffer, auto &&start_offset,
|
||||
auto &&end_offset) -> bool {
|
||||
@@ -145,7 +145,7 @@ auto enc_file::truncate(std::size_t size) -> bool {
|
||||
return i_file::write(data, offset);
|
||||
}
|
||||
|
||||
auto begin_chunk{
|
||||
/* auto begin_chunk{
|
||||
file_size.value() /
|
||||
utils::encryption::encrypting_reader::get_data_chunk_size(),
|
||||
};
|
||||
@@ -153,13 +153,14 @@ auto enc_file::truncate(std::size_t size) -> bool {
|
||||
utils::divide_with_ceiling(
|
||||
file_size.value(),
|
||||
utils::encryption::encrypting_reader::get_data_chunk_size()),
|
||||
};
|
||||
}; */
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto enc_file::write(const unsigned char *data, std::size_t to_write,
|
||||
std::size_t offset, std::size_t *total_written) -> bool {
|
||||
auto enc_file::write(const unsigned char * /* data */, std::size_t to_write,
|
||||
std::size_t offset, std::size_t * /* total_written */)
|
||||
-> bool {
|
||||
auto file_size{size()};
|
||||
if (not file_size.has_value()) {
|
||||
return false;
|
||||
@@ -181,7 +182,7 @@ auto enc_file::size() const -> std::optional<std::uint64_t> {
|
||||
}
|
||||
|
||||
return utils::encryption::encrypting_reader::calculate_decrypted_size(
|
||||
file_size.value());
|
||||
file_size.value(), false);
|
||||
}
|
||||
} // namespace repertory::utils::file
|
||||
|
||||
|
||||
@@ -33,13 +33,13 @@ namespace {
|
||||
file_size = 0U;
|
||||
|
||||
#if defined(_WIN32)
|
||||
struct _stat64 st{};
|
||||
auto res = _stat64(std::string{path}.c_str(), &st);
|
||||
struct _stat64 u_stat{};
|
||||
auto res = _stat64(std::string{path}.c_str(), &u_stat);
|
||||
if (res != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file_size = static_cast<std::uint64_t>(st.st_size);
|
||||
file_size = static_cast<std::uint64_t>(u_stat.st_size);
|
||||
return true;
|
||||
#else // !defined(_WIN32)
|
||||
std::error_code ec{};
|
||||
@@ -55,8 +55,9 @@ namespace {
|
||||
return ((::PathFileExistsA(abs_path.c_str()) != 0) &&
|
||||
(::PathIsDirectoryA(abs_path.c_str()) == 0));
|
||||
#else // !defined(_WIN32)
|
||||
struct stat64 st{};
|
||||
return (stat64(abs_path.c_str(), &st) == 0 && not S_ISDIR(st.st_mode));
|
||||
struct stat64 u_stat{};
|
||||
return (stat64(abs_path.c_str(), &u_stat) == 0 &&
|
||||
not S_ISDIR(u_stat.st_mode));
|
||||
#endif // defined(_WIN32)
|
||||
}
|
||||
} // namespace
|
||||
@@ -265,7 +266,7 @@ auto file::move_to(std::string_view path) -> bool {
|
||||
#if defined(_WIN32)
|
||||
success = ::MoveFileExA(path_.c_str(), abs_path.c_str(),
|
||||
MOVEFILE_REPLACE_EXISTING) != 0;
|
||||
#else // !// defined(_WIN32)
|
||||
#else // !defined(_WIN32)
|
||||
std::error_code ec{};
|
||||
std::filesystem::rename(path_, abs_path, ec);
|
||||
success = ec.value() == 0;
|
||||
|
||||
@@ -25,7 +25,58 @@
|
||||
|
||||
#include "utils/error.hpp"
|
||||
|
||||
namespace repertory::utils::encryption {
|
||||
namespace repertory::utils::hash {
|
||||
auto create_hash_blake2b_32(std::string_view data) -> hash_32_t {
|
||||
return create_hash_blake2b_t<hash_32_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()), data.size());
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_32(std::wstring_view data) -> hash_32_t {
|
||||
return create_hash_blake2b_t<hash_32_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size() * sizeof(wchar_t));
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_32(const data_buffer &data) -> hash_32_t {
|
||||
return create_hash_blake2b_t<hash_32_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size() * sizeof(data_buffer::value_type));
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_64(std::string_view data) -> hash_64_t {
|
||||
return create_hash_blake2b_t<hash_64_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()), data.size());
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_64(std::wstring_view data) -> hash_64_t {
|
||||
return create_hash_blake2b_t<hash_64_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size() * sizeof(wchar_t));
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_64(const data_buffer &data) -> hash_64_t {
|
||||
return create_hash_blake2b_t<hash_64_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size() * sizeof(data_buffer::value_type));
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_128(std::string_view data) -> hash_128_t {
|
||||
return create_hash_blake2b_t<hash_128_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()), data.size());
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_128(std::wstring_view data) -> hash_128_t {
|
||||
return create_hash_blake2b_t<hash_128_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size() * sizeof(wchar_t));
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_128(const data_buffer &data) -> hash_128_t {
|
||||
return create_hash_blake2b_t<hash_128_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size() * sizeof(data_buffer::value_type));
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_256(std::string_view data) -> hash_256_t {
|
||||
return create_hash_blake2b_t<hash_256_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()), data.size());
|
||||
@@ -111,8 +162,8 @@ auto create_hash_sha512(const data_buffer &data) -> hash_512_t {
|
||||
data.size() * sizeof(data_buffer::value_type));
|
||||
}
|
||||
|
||||
auto create_hash_sha512(const unsigned char *data,
|
||||
std::size_t data_size) -> hash_512_t {
|
||||
auto create_hash_sha512(const unsigned char *data, std::size_t data_size)
|
||||
-> hash_512_t {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
hash_512_t hash{};
|
||||
@@ -148,8 +199,8 @@ auto create_hash_sha512(const unsigned char *data,
|
||||
return hash;
|
||||
}
|
||||
|
||||
auto create_hash_sha256(const unsigned char *data,
|
||||
std::size_t data_size) -> hash_256_t {
|
||||
auto create_hash_sha256(const unsigned char *data, std::size_t data_size)
|
||||
-> hash_256_t {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
hash_256_t hash{};
|
||||
@@ -184,6 +235,6 @@ auto create_hash_sha256(const unsigned char *data,
|
||||
|
||||
return hash;
|
||||
}
|
||||
} // namespace repertory::utils::encryption
|
||||
} // namespace repertory::utils::hash
|
||||
|
||||
#endif // defined(PROJECT_ENABLE_LIBSODIUM)
|
||||
|
||||
@@ -36,10 +36,49 @@ auto from_dynamic_bitset(const boost::dynamic_bitset<> &bitset) -> std::string {
|
||||
#endif // defined(PROJECT_ENABLE_BOOST)
|
||||
|
||||
auto from_utf8(std::string_view str) -> std::wstring {
|
||||
return str.empty()
|
||||
? L""
|
||||
: std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>()
|
||||
.from_bytes(std::string{str});
|
||||
if (str.empty()) {
|
||||
return L"";
|
||||
}
|
||||
|
||||
std::wstring out;
|
||||
|
||||
const auto *str_ptr = reinterpret_cast<const std::uint8_t *>(str.data());
|
||||
std::int32_t idx{};
|
||||
auto len{static_cast<std::int32_t>(str.size())};
|
||||
|
||||
#if WCHAR_MAX <= 0xFFFF
|
||||
out.reserve((str.size() + 1U) / 2U);
|
||||
while (idx < len) {
|
||||
UChar32 uni_ch{};
|
||||
U8_NEXT(str_ptr, idx, len, uni_ch);
|
||||
if (uni_ch < 0 || not U_IS_UNICODE_CHAR(uni_ch)) {
|
||||
throw std::runtime_error("from_utf8: invalid UTF-8 sequence");
|
||||
}
|
||||
std::array<UChar, 2U> units{};
|
||||
std::int32_t off{};
|
||||
auto err{false};
|
||||
U16_APPEND(units.data(), off, 2, uni_ch, err);
|
||||
if (err || off <= 0) {
|
||||
throw std::runtime_error("from_utf8: U16_APPEND failed");
|
||||
}
|
||||
out.push_back(static_cast<wchar_t>(units[0U]));
|
||||
if (off == 2) {
|
||||
out.push_back(static_cast<wchar_t>(units[1U]));
|
||||
}
|
||||
}
|
||||
#else // WCHAR_MAX > 0xFFFF
|
||||
out.reserve(str.size());
|
||||
while (idx < len) {
|
||||
UChar32 uni_ch{};
|
||||
U8_NEXT(str_ptr, idx, len, uni_ch);
|
||||
if (uni_ch < 0 || not U_IS_UNICODE_CHAR(uni_ch)) {
|
||||
throw std::runtime_error("from_utf8: invalid UTF-8 sequence");
|
||||
}
|
||||
out.push_back(static_cast<wchar_t>(uni_ch));
|
||||
}
|
||||
#endif // WCHAR_MAX <= 0xFFFF
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
#if defined(PROJECT_ENABLE_SFML)
|
||||
@@ -55,8 +94,8 @@ auto replace_sf(sf::String &src, const sf::String &find, const sf::String &with,
|
||||
return src;
|
||||
}
|
||||
|
||||
auto split_sf(sf::String str, wchar_t delim,
|
||||
bool should_trim) -> std::vector<sf::String> {
|
||||
auto split_sf(sf::String str, wchar_t delim, bool should_trim)
|
||||
-> std::vector<sf::String> {
|
||||
auto result = std::views::split(str.toWideString(), delim);
|
||||
|
||||
std::vector<sf::String> ret{};
|
||||
@@ -130,9 +169,51 @@ auto to_uint64(const std::string &val) -> std::uint64_t {
|
||||
auto to_utf8(std::string_view str) -> std::string { return std::string{str}; }
|
||||
|
||||
auto to_utf8(std::wstring_view str) -> std::string {
|
||||
return str.empty()
|
||||
? ""
|
||||
: std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>()
|
||||
.to_bytes(std::wstring{str});
|
||||
if (str.empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string out;
|
||||
out.reserve(static_cast<size_t>(str.size()) * 4);
|
||||
|
||||
#if WCHAR_MAX <= 0xFFFF
|
||||
const auto *u16 = reinterpret_cast<const UChar *>(str.data());
|
||||
std::int32_t idx{};
|
||||
auto len{static_cast<int32_t>(str.size())};
|
||||
while (idx < len) {
|
||||
UChar32 uni_ch{};
|
||||
U16_NEXT(u16, idx, len, uni_ch);
|
||||
if (uni_ch < 0 || not U_IS_UNICODE_CHAR(uni_ch)) {
|
||||
throw std::runtime_error("to_utf8: invalid UTF-16 sequence");
|
||||
}
|
||||
std::array<std::uint8_t, U8_MAX_LENGTH> buf{};
|
||||
std::int32_t off{0};
|
||||
auto err{false};
|
||||
U8_APPEND(buf, off, U8_MAX_LENGTH, uni_ch, err);
|
||||
if (err || off <= 0) {
|
||||
throw std::runtime_error("to_utf8: U8_APPEND failed");
|
||||
}
|
||||
out.append(reinterpret_cast<const char *>(buf.data()),
|
||||
static_cast<std::size_t>(off));
|
||||
}
|
||||
#else // WCHAR_MAX > 0xFFFF
|
||||
for (const auto &cur_ch : str) {
|
||||
auto uni_char{static_cast<UChar32>(cur_ch)};
|
||||
if (not U_IS_UNICODE_CHAR(uni_char)) {
|
||||
throw std::runtime_error("to_utf8: invalid Unicode scalar value");
|
||||
}
|
||||
std::array<std::uint8_t, U8_MAX_LENGTH> buf{};
|
||||
std::int32_t off{0};
|
||||
auto err{false};
|
||||
U8_APPEND(buf, off, U8_MAX_LENGTH, uni_char, err);
|
||||
if (err || off <= 0) {
|
||||
throw std::runtime_error("to_utf8: U8_APPEND failed");
|
||||
}
|
||||
out.append(reinterpret_cast<const char *>(buf.data()),
|
||||
static_cast<std::size_t>(off));
|
||||
}
|
||||
#endif // WCHAR_MAX <= 0xFFFF
|
||||
|
||||
return out;
|
||||
}
|
||||
} // namespace repertory::utils::string
|
||||
|
||||
82
support/src/utils/timeout.cpp
Normal file
82
support/src/utils/timeout.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
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 "utils/timeout.hpp"
|
||||
|
||||
namespace repertory::utils {
|
||||
timeout::timeout(callback_t timeout_callback,
|
||||
std::chrono::system_clock::duration duration)
|
||||
: duration_(duration),
|
||||
timeout_callback_(std::move(timeout_callback)),
|
||||
timeout_killed_(duration <= std::chrono::system_clock::duration::zero()) {
|
||||
if (timeout_killed_) {
|
||||
return;
|
||||
}
|
||||
|
||||
timeout_thread_ = std::make_unique<std::thread>([this]() {
|
||||
std::unique_lock<std::mutex> loc_lock(timeout_mutex_);
|
||||
|
||||
while (not timeout_killed_) {
|
||||
auto res = timeout_notify_.wait_for(loc_lock, duration_);
|
||||
if (res != std::cv_status::timeout) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (timeout_killed_) {
|
||||
return;
|
||||
}
|
||||
|
||||
timeout_killed_ = true;
|
||||
loc_lock.unlock();
|
||||
|
||||
try {
|
||||
timeout_callback_();
|
||||
} catch (...) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
timeout::~timeout() { disable(); }
|
||||
|
||||
void timeout::disable() {
|
||||
unique_mutex_lock lock(timeout_mutex_);
|
||||
std::unique_ptr<std::thread> timeout_thread{nullptr};
|
||||
std::swap(timeout_thread, timeout_thread_);
|
||||
|
||||
if (not timeout_thread) {
|
||||
timeout_notify_.notify_all();
|
||||
return;
|
||||
}
|
||||
|
||||
timeout_killed_ = true;
|
||||
timeout_notify_.notify_all();
|
||||
lock.unlock();
|
||||
|
||||
timeout_thread->join();
|
||||
}
|
||||
|
||||
void timeout::reset() {
|
||||
mutex_lock lock(timeout_mutex_);
|
||||
timeout_notify_.notify_all();
|
||||
}
|
||||
} // namespace repertory::utils
|
||||
@@ -22,7 +22,155 @@
|
||||
#if !defined(_WIN32)
|
||||
|
||||
#include "utils/unix.hpp"
|
||||
|
||||
#include "utils/collection.hpp"
|
||||
#include "utils/config.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/file.hpp"
|
||||
#include "utils/path.hpp"
|
||||
|
||||
namespace {
|
||||
[[nodiscard]] auto get_group_list(auto *pass) -> std::vector<gid_t> {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
std::vector<gid_t> groups{};
|
||||
#if defined(__APPLE__)
|
||||
constexpr int buffer_count{8};
|
||||
constexpr int max_group_count{1024};
|
||||
groups.resize(buffer_count);
|
||||
|
||||
std::size_t orig_count{0U};
|
||||
while (true) {
|
||||
auto group_count{static_cast<int>(groups.size())};
|
||||
if (group_count > max_group_count) {
|
||||
repertory::utils::error::handle_error(function_name,
|
||||
"group list has too many groups");
|
||||
break;
|
||||
}
|
||||
|
||||
auto res{
|
||||
getgrouplist(pass->pw_name, static_cast<int>(pass->pw_gid),
|
||||
reinterpret_cast<int *>(groups.data()), &group_count),
|
||||
};
|
||||
if (res < 0) {
|
||||
if (orig_count == 0U) {
|
||||
repertory::utils::error::handle_error(
|
||||
function_name, std::string{"failed to get group list|error|"} +
|
||||
std::to_string(errno));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
groups.resize(static_cast<std::size_t>(group_count));
|
||||
if (groups.size() == orig_count) {
|
||||
break;
|
||||
}
|
||||
|
||||
orig_count = groups.size();
|
||||
}
|
||||
#else // !defined(__APPLE__)
|
||||
int group_count{};
|
||||
auto res = getgrouplist(pass->pw_name, pass->pw_gid, nullptr, &group_count);
|
||||
if (res >= 0) {
|
||||
repertory::utils::error::handle_error(
|
||||
function_name, std::string{"failed to get group list count|error|"} +
|
||||
std::to_string(errno));
|
||||
}
|
||||
|
||||
groups.resize(static_cast<std::size_t>(group_count));
|
||||
res = getgrouplist(pass->pw_name, pass->pw_gid, groups.data(), &group_count);
|
||||
if (res >= 0) {
|
||||
repertory::utils::error::handle_error(
|
||||
function_name,
|
||||
std::string{"failed to get group list|error|"} + std::to_string(errno));
|
||||
}
|
||||
#endif // !defined(__APPLE__)
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
#if defined(__linux__)
|
||||
[[nodiscard]] auto sanitize_basename(std::string_view app_name) -> std::string {
|
||||
std::string out;
|
||||
out.reserve(app_name.size());
|
||||
for (const auto &cur_ch : app_name) {
|
||||
if ((cur_ch >= 'a' && cur_ch <= 'z') || (cur_ch >= '0' && cur_ch <= '9') ||
|
||||
(cur_ch == '-' || cur_ch == '_')) {
|
||||
out.push_back(cur_ch);
|
||||
} else if (cur_ch >= 'A' && cur_ch <= 'Z') {
|
||||
out.push_back(static_cast<char>(cur_ch - 'A' + 'a'));
|
||||
} else {
|
||||
out.push_back('-'); // replace spaces/symbols
|
||||
}
|
||||
}
|
||||
|
||||
std::string collapsed;
|
||||
collapsed.reserve(out.size());
|
||||
bool prev_dash = false;
|
||||
for (const auto &cur_ch : out) {
|
||||
if (cur_ch == '-') {
|
||||
if (not prev_dash) {
|
||||
collapsed.push_back(cur_ch);
|
||||
}
|
||||
prev_dash = true;
|
||||
} else {
|
||||
collapsed.push_back(cur_ch);
|
||||
prev_dash = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (collapsed.empty()) {
|
||||
collapsed = "app";
|
||||
}
|
||||
return collapsed;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_autostart_dir() -> std::string {
|
||||
auto config = repertory::utils::get_environment_variable("XDG_CONFIG_HOME");
|
||||
if (config.empty()) {
|
||||
config = repertory::utils::path::combine(
|
||||
repertory::utils::get_environment_variable("HOME"), {".config"});
|
||||
}
|
||||
|
||||
return repertory::utils::path::combine(config, {"autostart"});
|
||||
}
|
||||
|
||||
[[nodiscard]] auto desktop_file_path_for(std::string_view app_name)
|
||||
-> std::string {
|
||||
return repertory::utils::path::combine(
|
||||
get_autostart_dir(), {
|
||||
sanitize_basename(app_name) + ".desktop",
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] auto join_args_for_exec(const std::vector<std::string> &args)
|
||||
-> std::string {
|
||||
std::string str;
|
||||
for (const auto &arg : args) {
|
||||
if (not str.empty()) {
|
||||
str += ' ';
|
||||
}
|
||||
|
||||
auto needs_quotes = arg.find_first_of(" \t\"'\\$`") != std::string::npos;
|
||||
if (needs_quotes) {
|
||||
str += '"';
|
||||
for (const auto &cur_ch : arg) {
|
||||
if (cur_ch == '"' || cur_ch == '\\') {
|
||||
str += '\\';
|
||||
}
|
||||
str += cur_ch;
|
||||
}
|
||||
str += '"';
|
||||
} else {
|
||||
str += arg;
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
#endif // defined(__linux__)
|
||||
} // namespace
|
||||
|
||||
namespace repertory::utils {
|
||||
#if !defined(__APPLE__)
|
||||
@@ -31,6 +179,80 @@ auto convert_to_uint64(const pthread_t &thread) -> std::uint64_t {
|
||||
}
|
||||
#endif // !defined(__APPLE__)
|
||||
|
||||
#if defined(__linux__)
|
||||
auto create_autostart_entry(const autostart_cfg &cfg, bool overwrite_existing)
|
||||
-> bool {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
auto file = desktop_file_path_for(cfg.app_name);
|
||||
if (utils::file::file{file}.exists() && not overwrite_existing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto dir = get_autostart_dir();
|
||||
if (dir.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (not utils::file::directory{dir}.create_directory()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto exec_line = cfg.exec_path;
|
||||
if (not cfg.exec_args.empty()) {
|
||||
exec_line += ' ';
|
||||
exec_line += join_args_for_exec(cfg.exec_args);
|
||||
}
|
||||
|
||||
std::ofstream out(file, std::ios::binary | std::ios::trunc);
|
||||
if (not out) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out << "[Desktop Entry]\n";
|
||||
out << "Type=Application\n";
|
||||
out << "Version=1.0\n";
|
||||
out << "Name=" << cfg.app_name << "\n";
|
||||
out << "Exec=" << exec_line << "\n";
|
||||
out << "Terminal=" << (cfg.terminal ? "true" : "false") << "\n";
|
||||
|
||||
if (cfg.comment && not cfg.comment->empty()) {
|
||||
out << "Comment=" << *cfg.comment << "\n";
|
||||
}
|
||||
|
||||
if (cfg.icon_path && not cfg.icon_path->empty()) {
|
||||
out << "Icon=" << *cfg.icon_path << "\n";
|
||||
}
|
||||
|
||||
if (not cfg.only_show_in.empty()) {
|
||||
out << "OnlyShowIn=";
|
||||
for (std::size_t idx = 0U; idx < cfg.only_show_in.size(); ++idx) {
|
||||
if (idx != 0U) {
|
||||
out << ';';
|
||||
}
|
||||
out << cfg.only_show_in[idx];
|
||||
}
|
||||
out << ";\n";
|
||||
}
|
||||
|
||||
if (not cfg.enabled) {
|
||||
out << "X-GNOME-Autostart-enabled=false\n";
|
||||
out << "Hidden=true\n";
|
||||
}
|
||||
|
||||
out.flush();
|
||||
if (not out) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
chmod(file.c_str(), 0644);
|
||||
#endif // defined(__linux__) || defined(__APPLE__)
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif // defined(__linux__)
|
||||
|
||||
auto get_last_error_code() -> int { return errno; }
|
||||
|
||||
auto get_thread_id() -> std::uint64_t {
|
||||
@@ -38,20 +260,11 @@ auto get_thread_id() -> std::uint64_t {
|
||||
}
|
||||
|
||||
auto is_uid_member_of_group(uid_t uid, gid_t gid) -> bool {
|
||||
std::vector<gid_t> groups{};
|
||||
auto res = use_getpwuid(uid, [&groups](struct passwd *pass) {
|
||||
int group_count{};
|
||||
if (getgrouplist(pass->pw_name, pass->pw_gid, nullptr, &group_count) < 0) {
|
||||
groups.resize(static_cast<std::size_t>(group_count));
|
||||
#if defined(__APPLE__)
|
||||
getgrouplist(pass->pw_name, pass->pw_gid,
|
||||
reinterpret_cast<int *>(groups.data()), &group_count);
|
||||
#else // !defined(__APPLE__)
|
||||
getgrouplist(pass->pw_name, pass->pw_gid, groups.data(), &group_count);
|
||||
#endif // defined(__APPLE__)
|
||||
}
|
||||
});
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
std::vector<gid_t> groups{};
|
||||
auto res = use_getpwuid(
|
||||
uid, [&groups](struct passwd *pass) { groups = get_group_list(pass); });
|
||||
if (not res) {
|
||||
throw utils::error::create_exception(res.function_name,
|
||||
{"use_getpwuid failed", res.reason});
|
||||
@@ -60,6 +273,19 @@ auto is_uid_member_of_group(uid_t uid, gid_t gid) -> bool {
|
||||
return collection::includes(groups, gid);
|
||||
}
|
||||
|
||||
#if defined(__linux__)
|
||||
auto remove_autostart_entry(std::string_view name) -> bool {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
auto file = desktop_file_path_for(name);
|
||||
if (not utils::file::file{file}.exists()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return utils::file::file{file}.remove();
|
||||
}
|
||||
#endif // defined(__linux__)
|
||||
|
||||
auto use_getpwuid(uid_t uid, passwd_callback_t callback) -> result {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
@@ -69,15 +295,130 @@ auto use_getpwuid(uid_t uid, passwd_callback_t callback) -> result {
|
||||
auto *temp_pw = getpwuid(uid);
|
||||
if (temp_pw == nullptr) {
|
||||
return {
|
||||
std::string{function_name},
|
||||
false,
|
||||
"'getpwuid' returned nullptr",
|
||||
.function_name = std::string{function_name},
|
||||
.ok = false,
|
||||
.reason = "'getpwuid' returned nullptr",
|
||||
};
|
||||
}
|
||||
|
||||
callback(temp_pw);
|
||||
return {std::string{function_name}};
|
||||
return {
|
||||
.function_name = std::string{function_name},
|
||||
};
|
||||
}
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#if defined(PROJECT_ENABLE_PUGIXML)
|
||||
auto generate_launchd_plist(const plist_cfg &cfg, bool overwrite_existing)
|
||||
-> bool {
|
||||
auto file = utils::path::combine(cfg.plist_path, {cfg.label + ".plist"});
|
||||
if (utils::file::file{file}.exists() && not overwrite_existing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
pugi::xml_document doc;
|
||||
auto decl = doc.append_child(pugi::node_declaration);
|
||||
decl.append_attribute("version") = "1.0";
|
||||
decl.append_attribute("encoding") = "UTF-8";
|
||||
|
||||
auto plist = doc.append_child("plist");
|
||||
plist.append_attribute("version") = "1.0";
|
||||
|
||||
auto dict = plist.append_child("dict");
|
||||
|
||||
dict.append_child("key").text().set("Label");
|
||||
dict.append_child("string").text().set(cfg.label.c_str());
|
||||
|
||||
dict.append_child("key").text().set("ProgramArguments");
|
||||
auto array = dict.append_child("array");
|
||||
for (const auto &arg : cfg.args) {
|
||||
array.append_child("string").text().set(arg.c_str());
|
||||
}
|
||||
|
||||
dict.append_child("key").text().set("EnvironmentVariables");
|
||||
pugi::xml_node env_dict = dict.append_child("dict");
|
||||
if (not utils::get_environment_variable("PROJECT_TEST_CONFIG_DIR").empty()) {
|
||||
env_dict.append_child("key").text().set("PROJECT_TEST_CONFIG_DIR");
|
||||
env_dict.append_child("string").text().set(
|
||||
utils::get_environment_variable("PROJECT_TEST_CONFIG_DIR"));
|
||||
}
|
||||
if (not utils::get_environment_variable("PROJECT_TEST_INPUT_DIR").empty()) {
|
||||
env_dict.append_child("key").text().set("PROJECT_TEST_INPUT_DIR");
|
||||
env_dict.append_child("string").text().set(
|
||||
utils::get_environment_variable("PROJECT_TEST_INPUT_DIR"));
|
||||
}
|
||||
|
||||
dict.append_child("key").text().set("WorkingDirectory");
|
||||
dict.append_child("string").text().set(cfg.working_dir.c_str());
|
||||
|
||||
dict.append_child("key").text().set("KeepAlive");
|
||||
dict.append_child(cfg.keep_alive ? "true" : "false");
|
||||
|
||||
dict.append_child("key").text().set("RunAtLoad");
|
||||
dict.append_child(cfg.run_at_load ? "true" : "false");
|
||||
|
||||
dict.append_child("key").text().set("StandardOutPath");
|
||||
dict.append_child("string").text().set(cfg.stdout_log.c_str());
|
||||
|
||||
dict.append_child("key").text().set("StandardErrorPath");
|
||||
dict.append_child("string").text().set(cfg.stderr_log.c_str());
|
||||
|
||||
return doc.save_file(file.c_str(), " ",
|
||||
pugi::format_indent | pugi::format_write_bom);
|
||||
}
|
||||
#endif // defined(PROJECT_ENABLE_PUGIXML)
|
||||
|
||||
#if defined(PROJECT_ENABLE_SPDLOG) || defined(PROJECT_ENABLE_FMT)
|
||||
auto launchctl_command(std::string_view label, launchctl_type type) -> int {
|
||||
switch (type) {
|
||||
case launchctl_type::bootout:
|
||||
return system(
|
||||
fmt::format("launchctl bootout gui/{} '{}' 1>/dev/null 2>&1", getuid(),
|
||||
utils::path::combine("~",
|
||||
{
|
||||
"/Library/LaunchAgents",
|
||||
fmt::format("{}.plist", label),
|
||||
}))
|
||||
.c_str());
|
||||
|
||||
case launchctl_type::bootstrap:
|
||||
return system(
|
||||
fmt::format("launchctl bootstrap gui/{} '{}' 1>/dev/null 2>&1",
|
||||
getuid(),
|
||||
utils::path::combine("~",
|
||||
{
|
||||
"/Library/LaunchAgents",
|
||||
fmt::format("{}.plist", label),
|
||||
}))
|
||||
.c_str());
|
||||
|
||||
case launchctl_type::kickstart:
|
||||
return system(
|
||||
fmt::format("launchctl kickstart gui/{}/{}", getuid(), label).c_str());
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto remove_launchd_plist(std::string_view plist_path, std::string_view label,
|
||||
bool should_bootout) -> bool {
|
||||
auto file = utils::file::file{
|
||||
utils::path::combine(plist_path, {std::string{label} + ".plist"}),
|
||||
};
|
||||
if (not file.exists()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto res =
|
||||
should_bootout ? launchctl_command(label, launchctl_type::bootout) : 0;
|
||||
if (not file.remove()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return res == 0;
|
||||
}
|
||||
#endif // defined(PROJECT_ENABLE_SPDLOG) || defined(PROJECT_ENABLE_FMT)
|
||||
#endif // defined(__APPLE__)
|
||||
} // namespace repertory::utils
|
||||
|
||||
#endif // !defined(_WIN32)
|
||||
|
||||
@@ -25,8 +25,18 @@
|
||||
|
||||
#include "utils/com_init_wrapper.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/file.hpp"
|
||||
#include "utils/path.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
namespace {
|
||||
constexpr std::array<std::string_view, 26U> drive_letters{
|
||||
"a:", "b:", "c:", "d:", "e:", "f:", "g:", "h:", "i:",
|
||||
"j:", "k:", "l:", "m:", "n:", "o:", "p:", "q:", "r:",
|
||||
"s:", "t:", "u:", "v:", "w:", "x:", "y:", "z:",
|
||||
};
|
||||
}
|
||||
|
||||
namespace repertory::utils {
|
||||
void create_console() {
|
||||
if (::AllocConsole() == 0) {
|
||||
@@ -59,13 +69,55 @@ void create_console() {
|
||||
|
||||
void free_console() { ::FreeConsole(); }
|
||||
|
||||
auto get_available_drive_letter(char first) -> std::optional<std::string_view> {
|
||||
const auto *begin = std::ranges::find_if(
|
||||
drive_letters, [first](auto &&val) { return val.at(0U) == first; });
|
||||
if (begin == drive_letters.end()) {
|
||||
begin = drive_letters.begin();
|
||||
}
|
||||
|
||||
auto available =
|
||||
std::ranges::find_if(begin, drive_letters.end(), [](auto &&val) -> bool {
|
||||
return not utils::file::directory{utils::path::combine(val, {"\\"})}
|
||||
.exists();
|
||||
});
|
||||
if (available == drive_letters.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return *available;
|
||||
}
|
||||
|
||||
auto get_available_drive_letters(char first) -> std::vector<std::string_view> {
|
||||
const auto *begin =
|
||||
std::ranges::find_if(drive_letters, [first](auto &&val) -> bool {
|
||||
return val.at(0U) == first;
|
||||
});
|
||||
if (begin == drive_letters.end()) {
|
||||
begin = drive_letters.begin();
|
||||
}
|
||||
|
||||
return std::accumulate(
|
||||
begin, drive_letters.end(), std::vector<std::string_view>(),
|
||||
[](auto &&vec, auto &&letter) -> auto {
|
||||
if (utils::file::directory{utils::path::combine(letter, {"\\"})}
|
||||
.exists()) {
|
||||
return vec;
|
||||
}
|
||||
|
||||
vec.emplace_back(letter);
|
||||
return vec;
|
||||
});
|
||||
}
|
||||
|
||||
auto get_last_error_code() -> DWORD { return ::GetLastError(); }
|
||||
|
||||
auto get_local_app_data_directory() -> const std::string & {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
[[maybe_unused]] thread_local const utils::com_init_wrapper wrapper;
|
||||
|
||||
static std::string app_data = ([]() -> std::string {
|
||||
com_init_wrapper cw;
|
||||
PWSTR local_app_data{};
|
||||
if (SUCCEEDED(::SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr,
|
||||
&local_app_data))) {
|
||||
@@ -91,10 +143,11 @@ auto is_process_elevated() -> bool {
|
||||
auto ret{false};
|
||||
HANDLE token{INVALID_HANDLE_VALUE};
|
||||
if (::OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
|
||||
TOKEN_ELEVATION te{};
|
||||
DWORD sz = sizeof(te);
|
||||
if (::GetTokenInformation(token, TokenElevation, &te, sizeof(te), &sz)) {
|
||||
ret = (te.TokenIsElevated != 0);
|
||||
TOKEN_ELEVATION token_elevation{};
|
||||
DWORD size = sizeof(token_elevation);
|
||||
if (::GetTokenInformation(token, TokenElevation, &token_elevation,
|
||||
sizeof(token_elevation), &size)) {
|
||||
ret = (token_elevation.TokenIsElevated != 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,6 +192,170 @@ auto run_process_elevated(std::vector<const char *> args) -> int {
|
||||
}
|
||||
|
||||
void set_last_error_code(DWORD error_code) { ::SetLastError(error_code); }
|
||||
|
||||
auto get_startup_folder() -> std::wstring {
|
||||
[[maybe_unused]] thread_local const utils::com_init_wrapper wrapper;
|
||||
|
||||
PWSTR raw{nullptr};
|
||||
auto result = ::SHGetKnownFolderPath(FOLDERID_Startup, 0, nullptr, &raw);
|
||||
if (FAILED(result)) {
|
||||
if (raw != nullptr) {
|
||||
::CoTaskMemFree(raw);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::wstring str{raw};
|
||||
::CoTaskMemFree(raw);
|
||||
return str;
|
||||
}
|
||||
|
||||
auto create_shortcut(const shortcut_cfg &cfg, bool overwrite_existing) -> bool {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
[[maybe_unused]] thread_local const utils::com_init_wrapper wrapper;
|
||||
|
||||
const auto hr_hex = [](HRESULT result) -> std::string {
|
||||
std::ostringstream oss;
|
||||
oss << "0x" << std::uppercase << std::hex << std::setw(8)
|
||||
<< std::setfill('0') << static_cast<std::uint32_t>(result);
|
||||
return oss.str();
|
||||
};
|
||||
|
||||
if (cfg.location.empty()) {
|
||||
utils::error::handle_error(function_name, "shortcut location was empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (not utils::file::directory{cfg.location}.create_directory()) {
|
||||
utils::error::handle_error(function_name,
|
||||
"failed to create shortcut directory|path|" +
|
||||
utils::string::to_utf8(cfg.location));
|
||||
return false;
|
||||
}
|
||||
|
||||
auto final_name = cfg.shortcut_name.empty()
|
||||
? utils::path::strip_to_file_name(cfg.exe_path)
|
||||
: cfg.shortcut_name;
|
||||
if (not final_name.ends_with(L".lnk")) {
|
||||
final_name += L".lnk";
|
||||
}
|
||||
|
||||
auto lnk_path = utils::path::combine(cfg.location, {final_name});
|
||||
if (utils::file::file{lnk_path}.exists() && not overwrite_existing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
IShellLinkW *psl{nullptr};
|
||||
auto result = ::CoCreateInstance(CLSID_ShellLink, nullptr,
|
||||
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&psl));
|
||||
if (FAILED(result)) {
|
||||
utils::error::handle_error(
|
||||
function_name,
|
||||
std::string("CoCreateInstance(CLSID_ShellLink) failed: ") +
|
||||
hr_hex(result));
|
||||
return false;
|
||||
}
|
||||
|
||||
result = psl->SetPath(cfg.exe_path.c_str());
|
||||
if (FAILED(result)) {
|
||||
utils::error::handle_error(function_name,
|
||||
std::string("IShellLink::SetPath failed: ") +
|
||||
hr_hex(result));
|
||||
psl->Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (not cfg.arguments.empty()) {
|
||||
result = psl->SetArguments(cfg.arguments.c_str());
|
||||
if (FAILED(result)) {
|
||||
utils::error::handle_error(
|
||||
function_name,
|
||||
std::string("IShellLink::SetArguments failed: ") + hr_hex(result));
|
||||
psl->Release();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (not cfg.working_directory.empty()) {
|
||||
result = psl->SetWorkingDirectory(cfg.working_directory.c_str());
|
||||
if (FAILED(result)) {
|
||||
utils::error::handle_error(
|
||||
function_name,
|
||||
std::string("IShellLink::SetWorkingDirectory failed: ") +
|
||||
hr_hex(result));
|
||||
psl->Release();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
result = psl->SetShowCmd(SW_SHOWNORMAL);
|
||||
if (FAILED(result)) {
|
||||
utils::error::handle_error(function_name,
|
||||
std::string("IShellLink::SetShowCmd failed: ") +
|
||||
hr_hex(result));
|
||||
psl->Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (not cfg.icon_path.empty()) {
|
||||
result = psl->SetIconLocation(cfg.icon_path.c_str(), 0);
|
||||
if (FAILED(result)) {
|
||||
utils::error::handle_error(
|
||||
function_name,
|
||||
std::string("IShellLink::SetIconLocation failed: ") + hr_hex(result));
|
||||
psl->Release();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (not utils::file::file{lnk_path}.remove()) {
|
||||
utils::error::handle_error(function_name,
|
||||
"failed to remove existing shortcut|path|" +
|
||||
utils::string::to_utf8(lnk_path));
|
||||
return false;
|
||||
}
|
||||
|
||||
IPersistFile *ppf{nullptr};
|
||||
result = psl->QueryInterface(IID_PPV_ARGS(&ppf));
|
||||
if (FAILED(result)) {
|
||||
utils::error::handle_error(
|
||||
function_name,
|
||||
std::string("QueryInterface(IPersistFile) failed: ") + hr_hex(result));
|
||||
psl->Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
result = ppf->Save(lnk_path.c_str(), TRUE);
|
||||
ppf->SaveCompleted(lnk_path.c_str());
|
||||
|
||||
ppf->Release();
|
||||
psl->Release();
|
||||
|
||||
if (FAILED(result)) {
|
||||
utils::error::handle_error(function_name,
|
||||
std::string("IPersistFile::Save failed: ") +
|
||||
hr_hex(result));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto remove_shortcut(std::wstring shortcut_name, const std::wstring &location)
|
||||
-> bool {
|
||||
if (not shortcut_name.ends_with(L".lnk")) {
|
||||
shortcut_name += L".lnk";
|
||||
}
|
||||
|
||||
auto file = utils::path::combine(location, {shortcut_name});
|
||||
if (not utils::file::file{file}.exists()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return utils::file::file{file}.remove();
|
||||
}
|
||||
} // namespace repertory::utils
|
||||
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
73
support/test/src/utils/atomic_test.cpp
Normal file
73
support/test/src/utils/atomic_test.cpp
Normal 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
|
||||
389
support/test/src/utils/base64_test.cpp
Normal file
389
support/test/src/utils/base64_test.cpp
Normal 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);
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
402
support/test/src/utils/encryption_kdf_config_test.cpp
Normal file
402
support/test/src/utils/encryption_kdf_config_test.cpp
Normal 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)
|
||||
450
support/test/src/utils/encryption_read_encrypted_range_test.cpp
Normal file
450
support/test/src/utils/encryption_read_encrypted_range_test.cpp
Normal 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)
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
253
support/test/src/utils/ttl_cache_test.cpp
Normal file
253
support/test/src/utils/ttl_cache_test.cpp
Normal 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
|
||||
Reference in New Issue
Block a user