mirror of
https://github.com/usatiuk/remotefs.git
synced 2025-10-28 23:47:47 +01:00
dump
This commit is contained in:
22
utils/CMakeLists.txt
Normal file
22
utils/CMakeLists.txt
Normal file
@@ -0,0 +1,22 @@
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
|
||||
add_library(utils
|
||||
include/Serialize.hpp
|
||||
src/Exception.cpp
|
||||
include/Exception.h
|
||||
include/stuff.hpp
|
||||
src/stuff.cpp
|
||||
include/Logger.h
|
||||
src/Logger.cpp
|
||||
src/Options.cpp
|
||||
include/Options.h
|
||||
include/SHA.h
|
||||
src/SHA.cpp
|
||||
)
|
||||
|
||||
find_package(OpenSSL REQUIRED)
|
||||
target_link_libraries(utils PRIVATE OpenSSL::SSL OpenSSL::Crypto)
|
||||
|
||||
target_include_directories(utils PUBLIC include)
|
||||
|
||||
add_subdirectory(tests)
|
||||
37
utils/include/Exception.h
Normal file
37
utils/include/Exception.h
Normal 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
65
utils/include/Logger.h
Normal 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
66
utils/include/Options.h
Normal 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
49
utils/include/SHA.h
Normal 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
|
||||
50
utils/include/SerializableStruct.hpp
Normal file
50
utils/include/SerializableStruct.hpp
Normal 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
286
utils/include/Serialize.hpp
Normal 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
37
utils/include/stuff.hpp
Normal 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
|
||||
51
utils/src/Exception.cpp
Normal file
51
utils/src/Exception.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// Created by Stepan Usatiuk on 01.05.2023.
|
||||
//
|
||||
|
||||
#include "Exception.h"
|
||||
|
||||
#include "stuff.hpp"
|
||||
|
||||
#include <execinfo.h>
|
||||
#include <sstream>
|
||||
|
||||
#include <openssl/err.h>
|
||||
|
||||
Exception::Exception(const std::string& text) : runtime_error(text + "\n" + getStacktrace()) {}
|
||||
|
||||
Exception::Exception(const char* text) : runtime_error(std::string(text) + "\n" + getStacktrace()) {}
|
||||
|
||||
// Based on: https://www.gnu.org/software/libc/manual/html_node/Backtraces.html
|
||||
std::string Exception::getStacktrace() {
|
||||
std::vector<void*> functions(50);
|
||||
char** strings;
|
||||
int n;
|
||||
|
||||
n = backtrace(functions.data(), 50);
|
||||
strings = backtrace_symbols(functions.data(), n);
|
||||
|
||||
std::stringstream out;
|
||||
|
||||
if (strings != nullptr) {
|
||||
out << "Stacktrace:" << std::endl;
|
||||
for (int i = 0; i < n; i++)
|
||||
out << strings[i] << std::endl;
|
||||
}
|
||||
|
||||
free(strings);
|
||||
return out.str();
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/42106339/how-to-get-the-error-string-in-openssl
|
||||
static std::string OpenSSLErrorToString() {
|
||||
BIO* bio = BIO_new(BIO_s_mem());
|
||||
ERR_print_errors(bio);
|
||||
char* buf;
|
||||
size_t len = checked_cast<size_t>(BIO_get_mem_data(bio, &buf));
|
||||
std::string ret(buf, len);
|
||||
BIO_free(bio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
OpenSSLException::OpenSSLException(const std::string& text) : Exception(text + "\n" + OpenSSLErrorToString()) {}
|
||||
OpenSSLException::OpenSSLException(const char* text) : Exception(std::string(text) + "\n" + OpenSSLErrorToString()) {}
|
||||
60
utils/src/Logger.cpp
Normal file
60
utils/src/Logger.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// Created by stepus53 on 3.1.24.
|
||||
//
|
||||
|
||||
#include "Logger.h"
|
||||
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
#include "Options.h"
|
||||
|
||||
Logger& Logger::get() {
|
||||
static Logger logger;
|
||||
return logger;
|
||||
}
|
||||
|
||||
void Logger::log(LogTag tag, const std::string& what, int level) {
|
||||
if (!en_level(tag, level))
|
||||
return;
|
||||
{
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
std::stringstream out;
|
||||
out << std::setprecision(3) << std::fixed << "["
|
||||
<< static_cast<double>(
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(now - get()._start_time).count()) /
|
||||
1000.0
|
||||
<< "s]"
|
||||
<< "[" << tag_to_str(tag) << "][" << get()._level_names.at(level) << "] " << what << '\n';
|
||||
|
||||
if (level == 1)
|
||||
get()._out_err.get() << out.str();
|
||||
else
|
||||
get()._out.get() << out.str();
|
||||
}
|
||||
}
|
||||
|
||||
void Logger::log(LogTag tag, const std::function<void(std::ostream&)>& fn, int level) {
|
||||
if (!en_level(tag, level))
|
||||
return;
|
||||
|
||||
std::stringstream out;
|
||||
fn(out);
|
||||
log(tag, out.str(), level);
|
||||
}
|
||||
|
||||
void Logger::set_level(LogTag tag, int level) { get()._levels[tag] = static_cast<LogLevel>(level); }
|
||||
void Logger::set_out(std::ostream& out) { get()._out = out; }
|
||||
void Logger::set_out_err(std::ostream& out_err) { get()._out_err = out_err; }
|
||||
void Logger::reset() { get()._levels.fill(static_cast<LogLevel>(Options::get<size_t>("default_log_level"))); }
|
||||
|
||||
int Logger::get_level(LogTag tag) { return get()._levels.at(tag); }
|
||||
|
||||
bool Logger::en_level(LogTag tag, int level) {
|
||||
int en_level = get_level(tag);
|
||||
if (en_level < level)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
Logger::Logger() { _levels.fill(static_cast<LogLevel>(Options::get<size_t>("default_log_level"))); }
|
||||
71
utils/src/Options.cpp
Normal file
71
utils/src/Options.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// Created by stepus53 on 3.1.24.
|
||||
//
|
||||
|
||||
#include "Options.h"
|
||||
|
||||
#include "Exception.h"
|
||||
|
||||
#include "Logger.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
Options& Options::get() {
|
||||
static Options opts;
|
||||
return opts;
|
||||
}
|
||||
|
||||
void Options::reset() { get()._current = _defaults; }
|
||||
|
||||
void Options::reset(int argc, char* argv[]) {
|
||||
Options::reset();
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
std::string arg = argv[i];
|
||||
|
||||
if (arg.length() < 2 || arg.substr(0, 2) != "--") {
|
||||
throw Exception("Can't parse argument " + arg);
|
||||
}
|
||||
std::string rest = arg.substr(2);
|
||||
|
||||
std::vector<std::string> split;
|
||||
{
|
||||
std::istringstream ins(rest);
|
||||
std::string cur;
|
||||
while (std::getline(ins, cur, ':')) {
|
||||
split.emplace_back(cur);
|
||||
}
|
||||
}
|
||||
|
||||
if (split.empty())
|
||||
throw Exception("Can't parse argument " + arg);
|
||||
|
||||
if (split.at(0) == "log") {
|
||||
if (split.size() != 3)
|
||||
throw Exception("Log options must be in format --log:TAG:LEVEL");
|
||||
try {
|
||||
Logger::set_level(Logger::str_to_tag(split.at(1)), std::stoi(split.at(2)));
|
||||
} catch (...) {
|
||||
throw Exception("Log options must be in format --log:TAG:LEVEL");
|
||||
}
|
||||
} else if (split.size() == 1) {
|
||||
std::string str = split.at(0);
|
||||
if (str.back() != '+' && str.back() != '-') {
|
||||
throw Exception("Bool options must be in format --option[+/-], instead have" + arg);
|
||||
}
|
||||
Options::set<bool>(str.substr(0, str.length() - 1), str.back() == '+' ? true : false);
|
||||
} else if (split.size() == 2) {
|
||||
try {
|
||||
Options::set<size_t>(split.at(0), std::stoul(split.at(1)));
|
||||
} catch (...) {
|
||||
Options::set<std::string>(split.at(0), split.at(1));
|
||||
}
|
||||
} else {
|
||||
throw Exception("Can't parse argument " + arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
43
utils/src/SHA.cpp
Normal file
43
utils/src/SHA.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// Created by Stepan Usatiuk on 15.04.2023.
|
||||
//
|
||||
|
||||
#include "SHA.h"
|
||||
|
||||
#include "Exception.h"
|
||||
|
||||
std::string SHA::calculate(const std::vector<char> &in) {
|
||||
SHA hasher;
|
||||
hasher.feedData(in);
|
||||
return hasher.getHash();
|
||||
}
|
||||
|
||||
SHA::SHA() {
|
||||
if (!mdctx) throw Exception("Can't create hashing context!");
|
||||
|
||||
if (!EVP_DigestInit_ex(mdctx.get(), EVP_sha256(), nullptr)) throw Exception("Can't create hashing context!");
|
||||
}
|
||||
|
||||
void SHA::feedData(const std::vector<char> &in) {
|
||||
if (in.empty()) return;
|
||||
if (!EVP_DigestUpdate(mdctx.get(), in.data(), in.size())) throw Exception("Error hashing!");
|
||||
}
|
||||
|
||||
std::string SHA::getHash() {
|
||||
std::array<char, 32> out;
|
||||
unsigned int s = 0;
|
||||
|
||||
if (!EVP_DigestFinal_ex(mdctx.get(), reinterpret_cast<unsigned char *>(out.data()), &s))
|
||||
throw Exception("Error hashing!");
|
||||
|
||||
if (s != out.size()) throw Exception("Error hashing!");
|
||||
|
||||
if (!EVP_MD_CTX_reset(mdctx.get())) throw Exception("Error hashing!");
|
||||
|
||||
return {out.begin(), out.end()};
|
||||
}
|
||||
|
||||
std::string SHA::calculate(const std::string &in) {
|
||||
std::vector<char> tmp(in.begin(), in.end());
|
||||
return SHA::calculate(tmp);
|
||||
}
|
||||
19
utils/src/stuff.cpp
Normal file
19
utils/src/stuff.cpp
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// Created by Stepan Usatiuk on 04.01.2025.
|
||||
//
|
||||
|
||||
|
||||
#include "stuff.hpp"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
std::vector<std::string> split(const std::string& s, char delim) {
|
||||
std::vector<std::string> elems;
|
||||
std::string token;
|
||||
|
||||
std::istringstream tokenStream(s);
|
||||
while (std::getline(tokenStream, token, delim))
|
||||
elems.push_back(token);
|
||||
|
||||
return elems;
|
||||
}
|
||||
13
utils/tests/CMakeLists.txt
Normal file
13
utils/tests/CMakeLists.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
include(gTest)
|
||||
|
||||
add_executable(
|
||||
SerializableHelperTest
|
||||
src/SerializableHelperTest.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
SerializableHelperTest PRIVATE
|
||||
GTest::gtest_main utils
|
||||
)
|
||||
|
||||
gtest_discover_tests(SerializableHelperTest DISCOVERY_TIMEOUT 600)
|
||||
44
utils/tests/src/SerializableHelperTest.cpp
Normal file
44
utils/tests/src/SerializableHelperTest.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// Created by Stepan Usatiuk on 08.12.2024.
|
||||
//
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "Serialize.hpp"
|
||||
#include "SerializableStruct.hpp"
|
||||
|
||||
#define TEST_STRUCT(FIELD) \
|
||||
FIELD(long, _test2) \
|
||||
FIELD(std::string, _test_str) \
|
||||
FIELD(std::vector<uint8_t>, _test_vec)
|
||||
|
||||
DECLARE_SERIALIZABLE(TestStruct, TEST_STRUCT)
|
||||
DECLARE_SERIALIZABLE_END
|
||||
|
||||
TEST(SerializableHelperTestStruct, Works) {
|
||||
TestStruct test(123, "hello", {});
|
||||
|
||||
std::vector<uint8_t> out;
|
||||
test.serialize(out);
|
||||
|
||||
TestStruct deserialized = Serialize::deserialize<TestStruct>(out);
|
||||
|
||||
ASSERT_EQ(test, deserialized);
|
||||
}
|
||||
|
||||
TEST(SerializableHelperTestStruct, Variant) {
|
||||
using VarT = std::variant<int, std::string>;
|
||||
|
||||
static_assert(Serialize::is_template_of<VarT, std::variant>::value);
|
||||
|
||||
VarT intV{1};
|
||||
VarT strV{"hello"};
|
||||
|
||||
auto serializedInt = Serialize::serialize(intV);
|
||||
auto deserializedInt = Serialize::deserialize<VarT>(serializedInt);
|
||||
ASSERT_EQ(intV, deserializedInt);
|
||||
|
||||
auto serializedString = Serialize::serialize(strV);
|
||||
auto deserializedString = Serialize::deserialize<VarT>(serializedString);
|
||||
ASSERT_EQ(strV, deserializedString);
|
||||
}
|
||||
Reference in New Issue
Block a user