This commit is contained in:
2025-02-15 09:53:11 +01:00
commit 0922b77fad
44 changed files with 3384 additions and 0 deletions

37
utils/include/Exception.h Normal file
View File

@@ -0,0 +1,37 @@
//
// Created by Stepan Usatiuk on 01.05.2023.
//
#ifndef SEMBACKUP_EXCEPTION_H
#define SEMBACKUP_EXCEPTION_H
#include <cstring>
#include <stdexcept>
#include <string>
#include <vector>
/// Custom exception class that uses execinfo to append a stacktrace to the exception message
class Exception : public std::runtime_error {
public:
Exception(const std::string& text);
Exception(const char* text);
private:
/// Static function to get the current stacktrace
static std::string getStacktrace();
};
class ErrnoException : public Exception {
public:
ErrnoException(const std::string& text) : Exception(text + " " + std::strerror(errno)) {}
ErrnoException(const char* text) : Exception(std::string(text) + " " + std::strerror(errno)) {}
};
class OpenSSLException : public Exception {
public:
OpenSSLException(const std::string& text);
OpenSSLException(const char* text);
};
#endif // SEMBACKUP_EXCEPTION_H

65
utils/include/Logger.h Normal file
View File

@@ -0,0 +1,65 @@
//
// Created by stepus53 on 3.1.24.
//
#ifndef PSIL_LOGGER_H
#define PSIL_LOGGER_H
#include <chrono>
#include <functional>
#include <iostream>
#include <mutex>
#include <ostream>
#include <shared_mutex>
#include <string>
#include <unordered_map>
class Logger {
public:
Logger();
enum LogLevel { ALWAYS = 0, ERROR = 1, INFO = 2, DEBUG = 3, TRACE = 4 };
enum LogTag { RemoteFs, LogTagMax };
static void log(LogTag tag, const std::string& what, int level = INFO);
static void log(LogTag tag, const std::function<void(std::ostream&)>& fn, int level = INFO);
// 0 - disabled
// 1 - error
// 2 - info
// 3 - debug
// 4 - trace
static void set_level(LogTag tag, int level);
static int get_level(LogTag tag);
static bool en_level(LogTag tag, int level);
static void set_out(std::ostream& out);
static void set_out_err(std::ostream& out_err);
static void reset();
static Logger& get();
static LogTag str_to_tag(const std::string& str) { return _str_to_tag.at(str); }
static const std::string& tag_to_str(LogTag tag) { return _tag_to_str.at(tag); }
private:
std::array<LogLevel, LogTag::LogTagMax> _levels{};
static inline std::unordered_map<int, std::string> _level_names{
{ALWAYS, "ALWAYS"}, {ERROR, "ERROR"}, {INFO, "INFO"}, {DEBUG, "DEBUG"}, {TRACE, "TRACE"},
};
static inline std::unordered_map<std::string, LogTag> _str_to_tag{
{"Server", RemoteFs},
};
static inline std::unordered_map<LogTag, std::string> _tag_to_str{
{RemoteFs, "Server"},
};
std::chrono::time_point<std::chrono::high_resolution_clock> _start_time = std::chrono::high_resolution_clock::now();
std::reference_wrapper<std::ostream> _out = std::cout;
std::reference_wrapper<std::ostream> _out_err = std::cerr;
};
#endif // PSIL_LOGGER_H

66
utils/include/Options.h Normal file
View File

@@ -0,0 +1,66 @@
//
// Created by stepus53 on 3.1.24.
//
#ifndef PSIL_OPTIONS_H
#define PSIL_OPTIONS_H
#include <cstddef>
#include <cstdint>
#include <mutex>
#include <shared_mutex>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <variant>
#include "Exception.h"
class Options {
public:
template<typename T>
static T get(const std::string& opt) {
Options& o = get();
if (_defaults.find(opt) == _defaults.end())
throw Exception("Unknown option " + opt);
if (!std::holds_alternative<T>(_defaults.at(opt)))
throw Exception("Bad option type " + opt);
return std::get<T>(o._current.at(opt));
}
template<typename T>
static void set(const std::string& opt, const T& val) {
Options& o = get();
if (_defaults.find(opt) == _defaults.end())
throw Exception("Unknown option " + opt);
if (!std::holds_alternative<T>(_defaults.at(opt)))
throw Exception("Bad option type " + opt);
o._current[opt] = val;
}
static void reset();
static void reset(int argc, char* argv[]);
static Options& get();
private:
using OptionType = std::variant<size_t, std::string, bool>;
const static inline std::unordered_map<std::string, OptionType> _defaults{{"ip", "127.0.0.1"},
{"port", 42069U},
{"default_log_level", 2U},
{"timeout", 30U},
{"ca_path", "cert.pem"},
{"pk_path", "key.pem"},
{"mode", "server"},
{"path", ""},
{"acl_path", ""},
{"users_path", ""},
{"username", ""},
{"password", ""}};
std::unordered_map<std::string, OptionType> _current = _defaults;
};
#endif // PSIL_OPTIONS_H

49
utils/include/SHA.h Normal file
View File

@@ -0,0 +1,49 @@
//
// Created by Stepan Usatiuk on 15.04.2023.
//
#ifndef SEMBACKUP_SHA_H
#define SEMBACKUP_SHA_H
#include <array>
#include <memory>
#include <vector>
#include <openssl/evp.h>
/// Class to handle SHA hashing
/**
* Based on: https://wiki.openssl.org/index.php/EVP_Message_Digests
*/
class SHA {
public:
/// Constructs an empty SHA hasher instance
/// \throws Exception on initialization error
SHA();
/// Calculates the hash for a given \p in char vector
/// \param in Constant reference to an input vector
/// \return SHA hash of \p in
static std::string calculate(const std::vector<char> &in);
/// Calculates the hash for a given \p in string
/// \param in Constant reference to an input string
/// \return SHA hash of \p in
static std::string calculate(const std::string &in);
/// Append a vector of chars to the current hash
/// \param in Constant reference to an input vector
/// \throws Exception on any error
void feedData(const std::vector<char> &in);
/// Returns the hash, resets the hashing context
/// \throws Exception on any error
std::string getHash();
private:
const std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)> mdctx{EVP_MD_CTX_new(),
&EVP_MD_CTX_free};///< Current hashing context
};
#endif//SEMBACKUP_SHA_H

View File

@@ -0,0 +1,50 @@
//
// Created by Stepan Usatiuk on 08.12.2024.
//
#ifndef SERIALIZABLESTRUCT_HPP
#define SERIALIZABLESTRUCT_HPP
#include <variant>
#include <vector>
#include "Serialize.hpp"
#define MAKE_FIELD(type, name) type name;
#define READ_FIELD(type, name) name = Serialize::deserialize<type>(in, end);
#define SERIALIZE_FIELD(type, name) Serialize::serialize(name, out);
#define COMPARE_FIELD(type, name) name == rhs.name&&
#define ARG_FIELD(type, name) type _##name,
#define CONSTRUCT_FIELD(type, name) name(std::move(_##name)),
#define DECLARE_SERIALIZABLE(name, FIELDS) \
class name { \
public: \
using serializable = std::true_type; \
FIELDS(MAKE_FIELD) \
\
std::monostate _dummy; \
\
explicit name(FIELDS(ARG_FIELD) std::monostate dummy = std::monostate{}) : \
FIELDS(CONSTRUCT_FIELD) _dummy(dummy) {} \
\
explicit name(std::vector<uint8_t>::const_iterator& in, const std::vector<uint8_t>::const_iterator& end) { \
FIELDS(READ_FIELD) \
} \
void serialize(std::vector<uint8_t>& out) const { FIELDS(SERIALIZE_FIELD) } \
\
bool operator==(name const& rhs) const { return FIELDS(COMPARE_FIELD) true; } \
\
private:
#define DECLARE_SERIALIZABLE_END \
} \
;
#endif // SERIALIZABLESTRUCT_HPP

286
utils/include/Serialize.hpp Normal file
View File

@@ -0,0 +1,286 @@
//
// Created by Stepan Usatiuk on 15.04.2023.
//
#ifndef SEMBACKUP_SERIALIZE_H
#define SEMBACKUP_SERIALIZE_H
#include <cstddef>
#include <memory>
#include <optional>
#include <type_traits>
#include <variant>
#include "Exception.h"
#include "stuff.hpp"
/// Serialization library
/**
* To serialize the objects in Repository, we have to handle a couple of cases:
* 1. Serializing integers (object ids, etc...)
* 2. Serializing enums (object types)
* 3. Serializing char vectors and strings
* 4. Serializing other STL containers (which also requires serializing pairs)
* 5. Serializing custom structs (including the objects themselves)
*
* With this library it is possible to do all of that.
*
*/
namespace Serialize {
template<typename, typename = void, typename = void>
struct is_pair : std::false_type {};
template<typename P>
struct is_pair<P, std::void_t<decltype(std::declval<P>().first)>, std::void_t<decltype(std::declval<P>().second)>>
: std::true_type {};
template<typename Args, template<typename...> class T>
struct is_template_of : std::false_type {};
template<typename... Args, template<typename...> class T>
struct is_template_of<T<Args...>, T> : std::true_type {};
template<typename, typename, typename = void>
struct has_emplace_back : std::false_type {};
template<typename T, typename V>
struct has_emplace_back<T, V, std::void_t<decltype(T().emplace_back(std::declval<V>()))>> : std::true_type {};
template<typename, typename = void, typename = void>
struct serializable : std::false_type {};
/// Checks if the object has the `serializable` type
/// In that case, its serialization will be delegated to its .serialize() parameter,
/// and deserialization to its T(char vector iterator in, const char vector iterator end) constructor,
/// similar to Serialize::deserialize
template<typename T>
struct serializable<T, std::void_t<decltype(T::serializable::value)>> : std::true_type {};
/// Deserializes object of type \p T starting from fist byte \p in, advances the iterator past the end of object
/// \tparam T Type to deserialize
/// \param in Iterator to the first byte of the object
/// \param end End iterator of source container
/// \return Deserialized value
template<typename T, typename C = std::vector<uint8_t>>
static std::optional<T> deserializeOpt(typename C::const_iterator& in, const typename C::const_iterator& end);
/// Deserializes object of type \p T starting from fist byte \p in, advances the iterator past the end of object
/// \tparam T Type to deserialize
/// \param in Iterator to the first byte of the object
/// \param end End iterator of source container
/// \return Deserialized value
template<typename T, typename C = std::vector<uint8_t>>
static T deserialize(typename C::const_iterator& in, const typename C::const_iterator& end);
/// Serializes object of type \p T into vector \p out
/// \tparam T Type to serialize
/// \param what Constant reference to the serialized object
/// \param out Reference to output vector
template<typename T, typename C = std::vector<uint8_t>>
static void serialize(const T& what, C& out);
/// Serializes the object of type \p T and returns the resulting vector
/// \tparam T Type to serialize
/// \param o Constant reference to the serialized object
/// \return Serialized data
template<typename T, typename C = std::vector<uint8_t>>
static C serialize(const T& o);
/// Deserializes object of type \p T from input vector \p from
/// \tparam T Type to deserialize
/// \param from Constant reference to the serialized object
/// \return Deserialized value
template<typename T, typename C = std::vector<uint8_t>>
static std::optional<T> deserializeOpt(const C& from);
/// Deserializes object of type \p T from input vector \p from
/// \tparam T Type to deserialize
/// \param from Constant reference to the serialized object
/// \return Deserialized value
template<typename T, typename C = std::vector<uint8_t>>
static T deserialize(const C& from);
// Deserializes an std::variant instance, with readIdx serving as an index of the alternative on the wire
template<std::size_t I = 0, typename C, typename V>
std::optional<V> deserializeVar(size_t readIdx, typename C::const_iterator& in, const typename C::const_iterator& end)
requires is_template_of<V, std::variant>::value
{
if constexpr (I < std::variant_size_v<V>) {
if (readIdx == I) {
auto res = deserializeOpt<std::variant_alternative_t<I, V>>(in, end);
if (res)
return std::make_optional(V{std::move(*res)});
return std::nullopt;
}
return deserializeVar<I + 1, C, V>(readIdx, in, end);
} else {
return std::nullopt;
}
}
template<typename T, typename C>
std::optional<T> deserializeOpt(typename C::const_iterator& in, const typename C::const_iterator& end) {
if constexpr (serializable<T>::value) {
// If the object declares itself as serializable, call its constructor with in and end
return T(in, end);
} else if constexpr (std::is_same_v<T, std::monostate>) {
// No-op
return std::make_optional(std::monostate{});
} else {
if (in >= end)
return std::nullopt;
if constexpr (is_pair<T>::value) {
// If the object is pair, deserialize the first and second element and return the pair
using KT = typename std::remove_const<decltype(T::first)>::type;
using VT = typename std::remove_const<decltype(T::second)>::type;
auto K = deserialize<KT>(in, end);
auto V = deserialize<VT>(in, end);
return T(std::move(K), std::move(V));
} else if constexpr (std::is_enum<T>::value) {
// If the object is an enum, deserialize an int and cast it to the enum
auto tmp = deserialize<uint32_t>(in, end);
if (tmp >= 0 && tmp < static_cast<uint32_t>(T::END))
return static_cast<T>(tmp);
else
return std::nullopt;
} else if constexpr (sizeof(T) == 1) {
// If it's a single byte, just copy it
if (std::distance(in, end) < checked_cast<ssize_t>(sizeof(T)))
return std::nullopt;
return *(in++);
} else if constexpr (std::is_integral<T>::value) {
static_assert(sizeof(T) <= sizeof(uint64_t));
uint64_t tmp;
// If the object is a number, copy it byte-by-byte
if (std::distance(in, end) < checked_cast<ssize_t>(sizeof(tmp)))
return std::nullopt;
std::copy(in, in + sizeof(tmp), reinterpret_cast<char*>(&tmp));
in += sizeof(tmp);
return static_cast<T>(be64toh(tmp));
} else if constexpr (is_template_of<T, std::variant>::value) {
auto index = deserializeOpt<uint64_t>(in, end);
if (!index.has_value())
return std::nullopt;
return deserializeVar<0, C, T>(*index - 1, in, end);
} else {
// Otherwise we treat it as a container, in format of <number of elements>b<elements>e
auto size = deserializeOpt<size_t>(in, end);
if (!size)
return std::nullopt;
auto b = deserialize<char>(in, end);
if (!b || b != 'b')
return std::nullopt;
T out;
if constexpr (sizeof(typename T::value_type) == 1) {
// Optimization for char vectors
if (std::distance(in, end) < checked_cast<ssize_t>(*size))
return std::nullopt;
out.insert(out.end(), in, in + checked_cast<ssize_t>(*size));
in += checked_cast<ssize_t>(*size);
} else
for (size_t i = 0; i < *size; i++) {
using V = typename T::value_type;
V v = deserialize<V>(in, end);
// Try either emplace_back or emplace if it doesn't exist
if constexpr (has_emplace_back<T, V>::value)
out.emplace_back(std::move(v));
else
out.emplace(std::move(v));
}
b = deserialize<char>(in, end);
if (!b || b != 'e')
return std::nullopt;
return out;
}
}
}
template<typename T, typename C>
static T deserialize(typename C::const_iterator& in, const typename C::const_iterator& end) {
std::optional<T> out = deserializeOpt<T>(in, end);
if (!out)
throw Exception("deserialize failed");
return out.value();
}
template<typename T, typename C>
static T deserialize(const C& from) {
std::optional<T> out = deserializeOpt<T>(from);
if (!out)
throw Exception("deserialize failed");
return out.value();
}
template<typename... T, typename C>
void serialize(const std::variant<T...>& what, C& out) {
serialize<uint64_t>(what.index() + 1, out);
std::visit(
[&out](auto&& arg) -> void {
using I = std::decay_t<decltype(arg)>;
serialize<I>(arg, out);
},
what);
}
template<typename T, typename C>
void serialize(const T& what, C& out) {
if constexpr (serializable<T>::value) {
// If the object declares itself as serializable, call its serialize method
what.serialize(out);
} else if constexpr (std::is_same_v<T, std::monostate>) {
// No-op
} else if constexpr (is_pair<T>::value) {
// If the object is pair, serialize the first and second element
serialize(what.first, out);
serialize(what.second, out);
} else if constexpr (std::is_enum<T>::value) {
// If the object is an enum, cast it to an int and serialize that
serialize(static_cast<uint32_t>(what), out);
} else if constexpr (sizeof(T) == 1) {
// If it's a single byte, just copy it
out.push_back(static_cast<typename C::value_type>(what));
} else if constexpr (std::is_integral<T>::value) {
static_assert(sizeof(T) <= sizeof(uint64_t));
// If the object is a number, copy it byte-by-byte
uint64_t tmp = htobe64(static_cast<uint64_t>(what));
static_assert(sizeof(tmp) == 8);
out.insert(out.end(), (reinterpret_cast<const char*>(&tmp)),
(reinterpret_cast<const char*>(&tmp) + sizeof(tmp)));
} else {
// Otherwise we treat it as a container, in format of <number of elements>b<elements>e
serialize(what.size(), out);
serialize('b', out);
if constexpr (sizeof(typename T::value_type) == 1) {
// Optimization for char vectors
out.insert(out.end(), what.begin(), what.end());
} else
for (auto const& i: what) {
serialize(i, out);
}
serialize('e', out);
}
}
template<typename T, typename C>
C serialize(const T& o) {
C out;
serialize(o, out);
return out;
}
template<typename T, typename C>
std::optional<T> deserializeOpt(const C& from) {
auto bgwr = from.begin();
return deserialize<T>(bgwr, from.end());
}
} // namespace Serialize
#endif // SEMBACKUP_SERIALIZE_H

37
utils/include/stuff.hpp Normal file
View File

@@ -0,0 +1,37 @@
//
// Created by stepus53 on 24.8.24.
//
#ifndef STUFF_H
#define STUFF_H
#include <cassert>
#include "Exception.h"
#ifdef __APPLE__
#include <machine/endian.h>
#define htobe64(x) htonll(x)
#define be64toh(x) ntohll(x)
#else
#include <endian.h>
#endif
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsign-conversion"
#pragma GCC diagnostic ignored "-Wsign-compare"
template<typename To, typename From>
constexpr To checked_cast(const From& f) {
To result = static_cast<To>(f);
if (f != result) {
throw Exception("Value out of bounds");
}
return result;
}
#pragma GCC diagnostic pop
std::vector<std::string> split(const std::string& s, char delim);
#endif // STUFF_H