code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2025 Ivan Baidakou

#pragma once

#include <cstdint>

namespace syncspirit::presentation {

struct entity_stats_t {
    std::int64_t size = 0;
    std::int32_t entities = 0;

    bool operator==(const entity_stats_t &) const noexcept = default;

    inline entity_stats_t &operator+=(const entity_stats_t &o) noexcept {
        size += o.size;
        entities += o.entities;
        return *this;
    }

    inline entity_stats_t &operator-=(const entity_stats_t &o) noexcept {
        size -= o.size;
        entities -= o.entities;
        return *this;
    }

    inline entity_stats_t operator-(const entity_stats_t &o) const noexcept {
        return {
            size - o.size,
            entities - o.entities,
        };
    }
    inline entity_stats_t operator-() const noexcept { return {-size, -entities}; }
};

struct presence_stats_t : entity_stats_t {
    std::int32_t cluster_entries = 0;
    std::int32_t local_entries = 0;

    bool operator==(const presence_stats_t &) const noexcept = default;

    inline presence_stats_t &operator+=(const presence_stats_t &o) noexcept {
        static_cast<entity_stats_t &>(*this) += o;
        cluster_entries += o.cluster_entries;
        local_entries += o.local_entries;
        return *this;
    }

    inline presence_stats_t &operator-=(const presence_stats_t &o) noexcept {
        static_cast<entity_stats_t &>(*this) += o;
        cluster_entries -= o.cluster_entries;
        local_entries -= o.local_entries;
        return *this;
    }

    inline presence_stats_t operator-(const presence_stats_t &o) const noexcept {
        auto &self = static_cast<const entity_stats_t &>(*this);
        return {
            {self - o},
            cluster_entries - o.cluster_entries,
            local_entries - o.local_entries,
        };
    }
    inline presence_stats_t operator-() const noexcept {
        auto &self = static_cast<const entity_stats_t &>(*this);
        return {-self, -cluster_entries, -local_entries};
    }
};

}; // namespace syncspirit::presentation


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#include "bep_support.h"
#include "constants.h"
#include "utils/error_code.h"
#include "proto/proto-helpers-bep.h"
#include "syncspirit-config.h"
#include <boost/endian/arithmetic.hpp>
#include <boost/endian/conversion.hpp>
#include <algorithm>
#include <lz4.h>
#include <type_traits>

using namespace syncspirit;
namespace be = boost::endian;

namespace syncspirit::proto {

static const constexpr std::size_t compression_threshold = 128;

utils::bytes_t make_hello_message(std::string_view device_name) noexcept {
    proto::Hello msg;
    proto::set_device_name(msg, device_name);
    proto::set_client_name(msg, constants::client_name);
    proto::set_client_version(msg, SYNCSPIRIT_VERSION);
    auto bytes = proto::encode(msg, 4 + 2);

    auto magic = be::native_to_big(constants::bep_magic);
    auto dst = (unsigned char *)bytes.data();
    auto src = (unsigned char *)&magic;
    *dst++ = *src++;
    *dst++ = *src++;
    *dst++ = *src++;
    *dst++ = *src++;

    auto sz = be::native_to_big(uint16_t(bytes.size() - 6));
    src = (unsigned char *)&sz;
    *dst++ = *src++;
    *dst++ = *src++;

    return bytes;
}

template <typename... Ts> auto wrap(Ts &&...data) noexcept {
    return message::wrapped_message_t{std::forward<Ts>(data)...};
}

static outcome::result<message::wrapped_message_t> parse_hello(utils::bytes_view_t buff) noexcept {
    auto sz = buff.size();
    std::uint16_t msg_sz;
    auto src = buff.data();
    auto dst = (unsigned char *)&msg_sz;
    *dst++ = *src++;
    *dst++ = *src++;
    be::big_to_native_inplace(msg_sz);
    if (msg_sz < sz - 2) {
        return wrap(Hello(), static_cast<size_t>(0));
    }

    auto bytes = utils::bytes_view_t(src, msg_sz);
    auto msg = proto::Hello();
    if (auto left = proto::decode(bytes, msg); left != 0) {
        return make_error_code(utils::bep_error_code_t::protobuf_err);
    }
    return wrap(std::move(msg), static_cast<size_t>(msg_sz + 6));
}

using MT = proto::MessageType;
template <MT> struct T2M;

template <> struct T2M<MT::CLUSTER_CONFIG> {
    using type = proto::ClusterConfig;
};
template <> struct T2M<MT::INDEX> {
    using type = proto::Index;
};
template <> struct T2M<MT::INDEX_UPDATE> {
    using type = proto::IndexUpdate;
};
template <> struct T2M<MT::REQUEST> {
    using type = proto::Request;
};
template <> struct T2M<MT::RESPONSE> {
    using type = proto::Response;
};
template <> struct T2M<MT::DOWNLOAD_PROGRESS> {
    using type = proto::DownloadProgress;
};
template <> struct T2M<MT::PING> {
    using type = proto::Ping;
};
template <> struct T2M<MT::CLOSE> {
    using type = proto::Close;
};

template <typename M> struct M2T;
template <> struct M2T<typename proto::ClusterConfig> {
    using type = std::integral_constant<MT, MT::CLUSTER_CONFIG>;
};
template <> struct M2T<typename proto::Index> {
    using type = std::integral_constant<MT, MT::INDEX>;
};
template <> struct M2T<typename proto::IndexUpdate> {
    using type = std::integral_constant<MT, MT::INDEX_UPDATE>;
};
template <> struct M2T<typename proto::Request> {
    using type = std::integral_constant<MT, MT::REQUEST>;
};
template <> struct M2T<typename proto::Response> {
    using type = std::integral_constant<MT, MT::RESPONSE>;
};
template <> struct M2T<typename proto::DownloadProgress> {
    using type = std::integral_constant<MT, MT::DOWNLOAD_PROGRESS>;
};
template <> struct M2T<typename proto::Ping> {
    using type = std::integral_constant<MT, MT::PING>;
};
template <> struct M2T<typename proto::Close> {
    using type = std::integral_constant<MT, MT::CLOSE>;
};

template <MessageType T>
outcome::result<message::wrapped_message_t> parse(utils::bytes_view_t buff, std::size_t consumed) noexcept {
    using ProtoType = typename T2M<T>::type;

    auto item = ProtoType();
    if (auto left = proto::decode(buff, item); left) {
        return make_error_code(utils::bep_error_code_t::protobuf_err);
    }
    return wrap(std::move(item), static_cast<size_t>(consumed + buff.size()));
}

static auto bep_magic_big = be::native_to_big(constants::bep_magic);
static auto bep_magic_bytes = utils::bytes_view_t((unsigned char *)&bep_magic_big, sizeof(bep_magic_big));

outcome::result<message::wrapped_message_t> parse_bep(utils::bytes_view_t buff) noexcept {
    auto sz = buff.size();
    if (sz < 4) {
        return wrap(message::message_t(), 0u);
    }

    auto head = buff.subspan(0, 4);
    if (head == bep_magic_bytes) {
        return parse_hello(buff.subspan(4));
    } else {
        auto ptr = head.data();
        auto header_sz = std::uint16_t();
        auto dst = (unsigned char *)(&header_sz);
        *dst++ = *ptr++;
        *dst++ = *ptr++;
        be::big_to_native_inplace(header_sz);
        if (2 + header_sz > (int)sz) {
            return wrap(message::message_t(), 0u);
        }

        auto header = proto::Header();
        auto header_buff = head.subspan(2, header_sz);
        if (auto left = proto::decode(header_buff, header); left) {
            return make_error_code(utils::bep_error_code_t::protobuf_err);
        }
        auto type = proto::get_type(header);
        ptr += header_sz;
        std::uint32_t message_sz;
        dst = (unsigned char *)(&message_sz);
        *dst++ = *ptr++;
        *dst++ = *ptr++;
        *dst++ = *ptr++;
        *dst++ = *ptr++;
        be::big_to_native_inplace(message_sz);

        // auto tail = ptr + sz;
        if (ptr + message_sz > buff.data() + sz) {
            return wrap(message::message_t(), 0u);
        }

        auto parse_msg = [type](utils::bytes_view_t buff, std::size_t consumed) noexcept {
            switch (type) {
            case MT::CLUSTER_CONFIG:
                return parse<MT::CLUSTER_CONFIG>(buff, consumed);
            case MT::INDEX:
                return parse<MT::INDEX>(buff, consumed);
            case MT::INDEX_UPDATE:
                return parse<MT::INDEX_UPDATE>(buff, consumed);
            case MT::REQUEST:
                return parse<MT::REQUEST>(buff, consumed);
            case MT::RESPONSE:
                return parse<MT::RESPONSE>(buff, consumed);
            case MT::DOWNLOAD_PROGRESS:
                return parse<MT::DOWNLOAD_PROGRESS>(buff, consumed);
            case MT::PING:
                return parse<MT::PING>(buff, consumed);
            case MT::CLOSE:
                return parse<MT::CLOSE>(buff, consumed);
            default:
                std::abort();
            }
        };

        auto rest = utils::bytes_view_t(ptr, message_sz);
        std::size_t consumed = 2 + header_sz + 4;
        auto compression = proto::get_compression(header);
        if (compression == proto::MessageCompression::NONE) {
            return parse_msg(rest, consumed);
        } else {
            std::uint32_t uncompr_sz;
            dst = (unsigned char *)(&uncompr_sz);
            *dst++ = *ptr++;
            *dst++ = *ptr++;
            *dst++ = *ptr++;
            *dst++ = *ptr++;
            be::big_to_native_inplace(uncompr_sz);

            auto msg_buff = utils::bytes_view_t();

            auto block_sz = message_sz - sizeof(std::uint32_t);
            std::vector<char> uncompressed;
            uncompressed.resize(uncompr_sz);
            auto data_ptr = reinterpret_cast<const char *>(ptr);
            auto dec = LZ4_decompress_safe(data_ptr, uncompressed.data(), block_sz, uncompr_sz);
            if (dec < 0) {
                return make_error_code(utils::bep_error_code_t::lz4_decoding);
            }
            auto msg_bytes = utils::bytes_view_t((unsigned char *)uncompressed.data(), uncompr_sz);
            auto &&r = parse_msg(msg_bytes, 0); // consumed is ignored anyway
            if (r) {
                auto &parsed = r.value().message;
                return wrap(std::move(parsed), static_cast<size_t>(consumed + message_sz));
            } else {
                return std::move(r);
            }
        }
    }
}

std::size_t make_announce_message(utils::bytes_view_t buff, utils::bytes_view_t device_id, const payload::URIs &uris,
                                  std::int64_t instance) noexcept {

    proto::Announce msg;
    proto::set_id(msg, device_id);
    proto::set_instance_id(msg, instance);
    for (auto &uri : uris) {
        proto::add_addresses(msg, uri->buffer());
    }

    auto msg_sz = proto::estimate(msg);
    auto total_sz = msg_sz + 4;
    assert(buff.size() >= msg_sz);

    auto magic = be::native_to_big(constants::bep_magic);
    auto dst = const_cast<unsigned char *>(buff.data());
    auto src = reinterpret_cast<unsigned char *>(&magic);
    *dst++ = *src++;
    *dst++ = *src++;
    *dst++ = *src++;
    *dst++ = *src++;

    proto::encode(msg, utils::bytes_view_t(dst, msg_sz));
    return total_sz;
}

outcome::result<Announce> parse_announce(utils::bytes_view_t buff) noexcept {
    auto sz = buff.size();
    if (sz <= 4) {
        return utils::make_error_code(utils::error_code_t::wrong_magic);
    }

    const std::uint32_t *ptr_32 = reinterpret_cast<const std::uint32_t *>(buff.data());
    if (be::big_to_native(*ptr_32++) != constants::bep_magic) {
        return utils::make_error_code(utils::error_code_t::wrong_magic);
    }

    auto ptr = (unsigned char *)(ptr_32);
    proto::Announce msg;
    if (auto ok = proto::decode({ptr, sz - 4}, msg); ok) {
        return make_error_code(utils::bep_error_code_t::protobuf_err);
    }
    return msg;
}

template <typename Message>
SYNCSPIRIT_API utils::bytes_t serialize(const Message &message, Compression compression) noexcept {
    using MC = MessageCompression;
    using type = typename M2T<Message>::type;
    auto message_sz = static_cast<int>(proto::estimate(message));
    auto applied_compression = MC::NONE;
    if (message_sz > compression_threshold && compression != Compression::NEVER) {
        if constexpr (std::is_same_v<Message, Response>) {
            if (compression == Compression::ALWAYS) {
                applied_compression = MC::LZ4;
            }
        } else {
            applied_compression = MC::LZ4;
        }
    }
    proto::Header header(type::value, applied_compression);
    auto header_sz = proto::estimate(header);
    auto header_sz_16 = be::native_to_big(static_cast<std::uint16_t>(header_sz));

    auto bytes = utils::bytes_t();
    if (applied_compression == MC::NONE) {
        auto raw_offset = (2 + 4 + header_sz);
        auto storage_sz = raw_offset + message_sz;
        bytes = utils::bytes_t(storage_sz);
        auto ptr = bytes.data() + raw_offset;
        proto::encode(message, {ptr, static_cast<size_t>(message_sz)});
    } else {
        auto bound_sz = LZ4_COMPRESSBOUND(message_sz);
        auto raw_offset = (2 + 4 + 4 + header_sz) + bound_sz;
        auto storage_sz = raw_offset + message_sz;
        bytes = utils::bytes_t(storage_sz);
        auto ptr = reinterpret_cast<unsigned char *>(bytes.data() + raw_offset);
        proto::encode(message, {ptr, static_cast<size_t>(message_sz)});
        auto dst = (unsigned char *)(bytes.data() + 2 + 4 + 4 + header_sz);
        auto uncompressed_sz = be::native_to_big(static_cast<std::uint32_t>(message_sz));
        auto result_sz = LZ4_compress_destSize((char *)ptr, (char *)dst, &message_sz, bound_sz);
        assert(result_sz);
        message_sz = result_sz + sizeof(std::uint32_t);
        ptr = dst - 4;
        auto src = (decltype(ptr))&uncompressed_sz;
        *ptr++ = *src++;
        *ptr++ = *src++;
        *ptr++ = *src++;
        *ptr++ = *src++;
        bytes.resize(2 + 4 + header_sz + message_sz);
    }

    auto *ptr = reinterpret_cast<std::uint8_t *>(bytes.data());
    auto src = reinterpret_cast<const std::uint8_t *>(&header_sz_16);
    *ptr++ = *src++;
    *ptr++ = *src++;
    proto::encode(header, {ptr, header_sz});
    ptr += header_sz;

    auto message_sz_32 = be::native_to_big(static_cast<std::uint32_t>(message_sz));
    src = reinterpret_cast<const std::uint8_t *>(&message_sz_32);
    *ptr++ = *src++;
    *ptr++ = *src++;
    *ptr++ = *src++;
    *ptr++ = *src++;
    return bytes;
}

template utils::bytes_t SYNCSPIRIT_API serialize(const proto::ClusterConfig &message,
                                                 proto::Compression compression) noexcept;
template utils::bytes_t SYNCSPIRIT_API serialize(const proto::Index &message, proto::Compression compression) noexcept;
template utils::bytes_t SYNCSPIRIT_API serialize(const proto::IndexUpdate &message,
                                                 proto::Compression compression) noexcept;
template utils::bytes_t SYNCSPIRIT_API serialize(const proto::Request &message,
                                                 proto::Compression compression) noexcept;
template utils::bytes_t SYNCSPIRIT_API serialize(const proto::Response &message,
                                                 proto::Compression compression) noexcept;
template utils::bytes_t SYNCSPIRIT_API serialize(const proto::DownloadProgress &message,
                                                 proto::Compression compression) noexcept;
template utils::bytes_t SYNCSPIRIT_API serialize(const proto::Ping &message, proto::Compression compression) noexcept;
template utils::bytes_t SYNCSPIRIT_API serialize(const proto::Close &message, proto::Compression compression) noexcept;

} // namespace syncspirit::proto

repo_name: basiliscos/syncspirit

license: mit


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#pragma once

#include <boost/outcome.hpp>
#include <variant>
#include "syncspirit-export.h"
#include "proto/proto-fwd.hpp"
#include "utils/uri.h"
#include "utils/bytes.h"

namespace syncspirit::proto {

namespace outcome = boost::outcome_v2;

namespace payload {
using URIs = utils::uri_container_t;
}

namespace message {

using message_t =
    std::variant<Hello, ClusterConfig, Index, IndexUpdate, Request, Response, DownloadProgress, Ping, Close>;

struct wrapped_message_t {
    message_t message;
    std::size_t consumed = 0;
};

template <typename T> consteval MessageType get_bep_type() {
    if constexpr (std::is_same_v<T, ClusterConfig>) {
        return MessageType::CLUSTER_CONFIG;
    } else if constexpr (std::is_same_v<T, Index>) {
        return MessageType::INDEX;
    } else if constexpr (std::is_same_v<T, IndexUpdate>) {
        return MessageType::INDEX_UPDATE;
    } else if constexpr (std::is_same_v<T, Request>) {
        return MessageType::REQUEST;
    } else if constexpr (std::is_same_v<T, Response>) {
        return MessageType::RESPONSE;
    } else if constexpr (std::is_same_v<T, DownloadProgress>) {
        return MessageType::DOWNLOAD_PROGRESS;
    } else if constexpr (std::is_same_v<T, Ping>) {
        return MessageType::PING;
    } else if constexpr (std::is_same_v<T, Close>) {
        return MessageType::CLOSE;
    } else if constexpr (std::is_same_v<T, Hello>) {
        return MessageType::HELLO;
    }
}

} // namespace message

SYNCSPIRIT_API utils::bytes_t make_hello_message(std::string_view device_name) noexcept;

SYNCSPIRIT_API std::size_t make_announce_message(utils::bytes_view_t storage, utils::bytes_view_t device_id,
                                                 const payload::URIs &uris, std::int64_t instance) noexcept;

template <typename Message>
utils::bytes_t serialize(const Message &message, proto::Compression compression = proto::Compression::NEVER) noexcept;

SYNCSPIRIT_API outcome::result<message::wrapped_message_t> parse_bep(utils::bytes_view_t) noexcept;

SYNCSPIRIT_API outcome::result<Announce> parse_announce(utils::bytes_view_t) noexcept;
} // namespace syncspirit::proto


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#include "discovery_support.h"
#include "utils/beast_support.h"
#include "utils/error_code.h"
#include <boost/beast/http.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <nlohmann/json.hpp>
#include <fmt/format.h>
#include <charconv>
#include <cctype>

using namespace syncspirit::utils;

namespace syncspirit::proto {

namespace http = boost::beast::http;
using json = nlohmann::json;

outcome::result<uri_ptr_t> make_announce_request(utils::bytes_t &buff, const uri_ptr_t &announce_uri,
                                                 const utils::uri_container_t &listening_uris) noexcept {
    json payload = json::object();
    json addresses = json::array();
    for (auto &uri : listening_uris) {
        addresses.push_back(uri->buffer());
    }
    payload["addresses"] = addresses;

    auto uri = announce_uri->clone();
    uri->set_path("/v2/");
    http::request<http::string_body> req;
    req.method(http::verb::post);
    req.version(11);
    req.keep_alive(true);
    req.target(uri->encoded_target());
    req.set(http::field::host, uri->host());
    req.set(http::field::content_type, "application/json");

    req.body() = payload.dump();
    req.prepare_payload();

    auto ok = serialize(req, buff);
    if (!ok) {
        return ok.error();
    }
    return uri;
}

outcome::result<uri_ptr_t> make_discovery_request(utils::bytes_t &buff, const uri_ptr_t &announce_uri,
                                                  const model::device_id_t device_id) noexcept {
    auto target = fmt::format("device={}", device_id.get_value());
    auto uri = announce_uri->clone();
    uri->set_query(target);

    http::request<http::empty_body> req;
    req.method(http::verb::get);
    req.version(11);
    req.keep_alive(true);
    req.target(uri->encoded_target());
    req.set(http::field::host, uri->host());

    auto ok = serialize(req, buff);
    if (!ok) {
        return ok.error();
    }
    return uri;
}

outcome::result<std::uint32_t> parse_announce(http::response<http::string_body> &res) noexcept {
    auto code = res.result_int();
    if (code != 204 && code != 429) {
        auto ec = make_error_code(error_code_t::unexpected_response_code);
        return ec;
    }

    auto convert = [&](const auto &it) -> int {
        if (it != res.end()) {
            auto str = it->value();
            int value;
            auto result = std::from_chars(str.begin(), str.end(), value);
            if (result.ec == std::errc()) {
                return value;
            }
        }
        return 0;
    };

    auto reannounce = convert(res.find("Reannounce-After"));
    if (reannounce < 1) {
        reannounce = convert(res.find("Retry-After"));
    }
    if (reannounce < 1) {
        auto ec = make_error_code(error_code_t::negative_reannounce_interval);
        return ec;
    }
    return static_cast<std::uint32_t>(reannounce);
}

outcome::result<utils::uri_container_t> parse_contact(http::response<http::string_body> &res) noexcept {
    auto code = res.result_int();
    if (code == 404) {
        return utils::uri_container_t{};
    };
    if (code != 200) {
        return make_error_code(error_code_t::unexpected_response_code);
    };

    auto &body = res.body();
    auto ptr = body.data();
    auto data = json::parse(ptr, ptr + body.size(), nullptr, false);
    if (data.is_discarded()) {
        return make_error_code(error_code_t::malformed_json);
    }
    if (!data.is_object()) {
        return make_error_code(error_code_t::incorrect_json);
    }

    auto &addresses = data["addresses"];
    if (!addresses.is_array()) {
        return make_error_code(error_code_t::incorrect_json);
    }

    uri_container_t urls;
    for (auto &it : addresses) {
        if (!it.is_string()) {
            return make_error_code(error_code_t::incorrect_json);
        }
        auto uri_str = it.get<std::string>();
        auto uri = utils::parse(uri_str.c_str());
        if (!uri) {
            continue;
        }
        urls.emplace_back(std::move(uri));
    }
    if (urls.empty()) {
        return make_error_code(error_code_t::malformed_url);
    }

    auto &seen = data["seen"];
    if (!seen.is_string()) {
        return make_error_code(error_code_t::incorrect_json);
    }
    try {
        auto date_str = seen.get<std::string>();
        auto valid_count = date_str.size();
        while (valid_count > 0 && !isdigit(date_str[valid_count - 1])) {
            --valid_count;
        }
        auto date_substr = std::string(date_str.data(), valid_count);
        auto date = boost::posix_time::from_iso_extended_string(date_substr);
        (void)date;
        return outcome::success(std::move(urls));
    } catch (...) {
        return make_error_code(error_code_t::malformed_date);
    }
}

} // namespace syncspirit::proto


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#pragma once
#include "utils/uri.h"
#include "model/device_id.h"
#include "syncspirit-export.h"
#include "utils/bytes.h"
#include <boost/outcome.hpp>
#include <boost/asio.hpp>
#include <boost/beast/http.hpp>

namespace syncspirit::proto {

namespace outcome = boost::outcome_v2;
namespace asio = boost::asio;
namespace http = boost::beast::http;

SYNCSPIRIT_API outcome::result<utils::uri_ptr_t>
make_announce_request(utils::bytes_t &buff, const utils::uri_ptr_t &announce_uri,
                      const utils::uri_container_t &listening_uris) noexcept;

SYNCSPIRIT_API outcome::result<utils::uri_ptr_t> make_discovery_request(utils::bytes_t &buff,
                                                                        const utils::uri_ptr_t &announce_uri,
                                                                        const model::device_id_t device_id) noexcept;

SYNCSPIRIT_API outcome::result<std::uint32_t> parse_announce(http::response<http::string_body> &res) noexcept;

SYNCSPIRIT_API outcome::result<utils::uri_container_t> parse_contact(http::response<http::string_body> &res) noexcept;

} // namespace syncspirit::proto


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2022 Ivan Baidakou

#include "luhn32.h"
#include "../utils/base32.h"
#include <cassert>
#include <string_view>
#include <boost/outcome.hpp>

using namespace syncspirit::proto;
using namespace syncspirit::utils;
namespace outcome = boost::outcome_v2;

static outcome::result<char> calc(std::string_view in) {
    int factor = 1;
    int sum = 0;
    constexpr const int n = 32;

    for (auto it = in.begin(); it != in.end(); ++it) {
        std::size_t index = static_cast<unsigned char>(*it);
        int code_point = base32::out_alphabet[index];
        assert(code_point >= 0 && "wrong codepoint (bad input string)");

        int addend = factor * code_point;

        factor = (factor == 2) ? 1 : 2;
        addend = (addend / n) + (addend % n);
        sum += addend;
    }

    int remainder = sum % n;
    int check_char_index = (n - remainder) % n;

    return base32::in_alphabet[check_char_index];
}

char luhn32::calculate(std::string_view in) noexcept {
    auto result = calc(in);
    assert(result && "wrong codepoint (bad input string)");
    return result.value();
}

bool luhn32::validate(std::string_view in) noexcept {
    auto length = in.length();
    if (!length) {
        return false; // not sure
    }

    auto view = std::string_view(in.data(), length - 1);
    auto result = calc(view);
    if (!result) {
        return false;
    }
    return in[length - 1] == result.value();
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2022 Ivan Baidakou

#pragma once

#include <string_view>
#include "syncspirit-export.h"

namespace syncspirit::proto {

struct SYNCSPIRIT_API luhn32 {
    static char calculate(std::string_view in) noexcept;
    static bool validate(std::string_view in) noexcept;
};

} // namespace syncspirit::proto


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2025 Ivan Baidakou

#pragma once

#include <protopuf/message.h>

namespace syncspirit {

namespace proto {

using bytes_backend_t = std::vector<std::vector<unsigned char>>;

// clang-format off

enum class MessageType {
    CLUSTER_CONFIG    = 0,
    INDEX             = 1,
    INDEX_UPDATE      = 2,
    REQUEST           = 3,
    RESPONSE          = 4,
    DOWNLOAD_PROGRESS = 5,
    PING              = 6,
    CLOSE             = 7,
    HELLO             = 8,
    UNKNOWN           = 9,
};

enum class MessageCompression {
    NONE = 0,
    LZ4  = 1,
};

enum class Compression {
    METADATA = 0,
    NEVER    = 1,
    ALWAYS   = 2,
};

enum class FileInfoType {
    FILE              = 0,
    DIRECTORY         = 1,
    SYMLINK_FILE      = 2,
    SYMLINK_DIRECTORY = 3,
    SYMLINK           = 4,
};

enum class ErrorCode {
    NO_BEP_ERROR = 0,
    GENERIC      = 1,
    NO_SUCH_FILE = 2,
    INVALID_FILE = 3,
};

enum class FileDownloadProgressUpdateType {
    APPEND = 0,
    FORGET = 1,
};

using Announce = pp::message<
    pp::bytes_field  <"id",          1, pp::singular, proto::bytes_backend_t>,
    pp::string_field <"addresses",   2, pp::repeated>,
    pp::uint64_field <"instance_id", 3              >
>;

using Hello = pp::message<
    pp::string_field <"device_name",    1>,
    pp::string_field <"client_name",    2>,
    pp::string_field <"client_version", 3>
>;

using Header = pp::message<
    pp::enum_field <"type",        1, MessageType>,
    pp::enum_field <"compression", 2, MessageCompression>
>;

using Device = pp::message<
    pp::bytes_field  <"id",                         1, pp::singular, proto::bytes_backend_t>,
    pp::string_field <"name",                       2                                      >,
    pp::string_field <"addresses",                  3, pp::repeated                        >,
    pp::enum_field   <"compression",                4, Compression                         >,
    pp::string_field <"cert_name",                  5                                      >,
    pp::int64_field  <"max_sequence",               6                                      >,
    pp::bool_field   <"introducer",                 7                                      >,
    pp::uint64_field <"index_id",                   8                                      >,
    pp::bool_field   <"skip_introduction_removals", 9                                      >
>;

using Folder = pp::message<
    pp::string_field    <"id",                    1                      >,
    pp::string_field    <"label",                 2                      >,
    pp::bool_field      <"read_only",             3                      >,
    pp::bool_field      <"ignore_permissions",    4                      >,
    pp::bool_field      <"ignore_delete",         5                      >,
    pp::bool_field      <"disable_temp_indexes",  6                      >,
    pp::bool_field      <"paused",                7                      >,
    pp::message_field   <"devices",              16, Device, pp::repeated>
>;

using ClusterConfig = pp::message<
    pp::message_field   <"folders", 1, Folder, pp::repeated>
>;

using Counter = pp::message<
    pp::uint64_field    <"id",    1>,
    pp::uint64_field    <"value", 2>
>;

using Vector = pp::message<
    pp::message_field   <"counters", 1, Counter, pp::repeated>
>;

using BlockInfo = pp::message<
    pp::int64_field  <"offset",    1                                      >,
    pp::int32_field  <"size",      2                                      >,
    pp::bytes_field  <"hash",      3, pp::singular, proto::bytes_backend_t>,
    pp::uint32_field <"weak_hash", 4                                      >
>;

using FileInfo = pp::message<
    pp::string_field  <"name",            1                           >,
    pp::enum_field    <"type",            2, FileInfoType             >,
    pp::int64_field   <"size",            3                           >,
    pp::uint32_field  <"permissions",     4                           >,
    pp::int64_field   <"modified_s",      5                           >,
    pp::int32_field   <"modified_ns",     11                          >,
    pp::uint64_field  <"modified_by",     12                          >,
    pp::bool_field    <"deleted",         6                           >,
    pp::bool_field    <"invalid",         7                           >,
    pp::bool_field    <"no_permissions",  8                           >,
    pp::message_field <"version",         9, Vector                   >,
    pp::int64_field   <"sequence",        10                          >,
    pp::int32_field   <"block_size",      13                          >,
    pp::message_field <"blocks",          16, BlockInfo, pp::repeated >,
    pp::string_field  <"symlink_target",  17                          >
>;

using IndexBase = pp::message<
    pp::string_field    <"folder",  1                        >,
    pp::message_field   <"files",   2, FileInfo, pp::repeated>
>;
struct Index: IndexBase {};
struct IndexUpdate: IndexBase {};

using Request = pp::message<
    pp::int32_field  <"id",              1                                      >,
    pp::string_field <"folder",          2                                      >,
    pp::string_field <"name",            3                                      >,
    pp::int64_field  <"offset",          4                                      >,
    pp::int32_field  <"size",            5                                      >,
    pp::bytes_field  <"hash",            6, pp::singular, proto::bytes_backend_t>,
    pp::bool_field   <"from_temporary",  7                                      >,
    pp::uint32_field <"weak_hash",       8                                      >
>;

using Response = pp::message<
    pp::int32_field <"id",      1                                      >,
    pp::bytes_field <"data",    2, pp::singular, proto::bytes_backend_t>,
    pp::enum_field  <"code",    3, ErrorCode                           >
>;

using FileDownloadProgressUpdate = pp::message<
    pp::enum_field      <"update_type",     1, FileDownloadProgressUpdateType>,
    pp::string_field    <"name",            2                                >,
    pp::message_field   <"version",         3, Vector                        >,
    pp::int32_field     <"block_indexes",   4, pp::repeated                  >
>;

using DownloadProgress = pp::message<
    pp::string_field    <"folder",                     1                                          >,
    pp::message_field   <"FileDownloadProgressUpdate", 2, FileDownloadProgressUpdate, pp::repeated>
>;

// using Ping = pp::message<>;
struct Ping {};

using Close = pp::message<
    pp::string_field <"reason", 1>
>;

}

namespace db {

// clang-format off

enum class FolderType {
    send             = 0,
    receive          = 1,
    send_and_receive = 2,
};

enum class PullOrder {
    random      = 0,
    alphabetic  = 1,
    smallest    = 2,
    largest     = 3,
    oldest      = 4,
    newest      = 5,
};

using FileInfoType = proto::FileInfoType;
using Vector = proto::Vector;
using Compression = proto::Compression;

using IgnoredFolder = pp::message<
    pp::string_field    <"label",   1>
>;

using Device = pp::message<
    pp::string_field    <"name",                       1              >,
    pp::string_field    <"addresses",                  2, pp::repeated>,
    pp::enum_field      <"compression",                3, Compression >,
    pp::string_field    <"cert_name",                  4              >,
    pp::bool_field      <"introducer",                 5              >,
    pp::bool_field      <"skip_introduction_removals", 6              >,
    pp::bool_field      <"auto_accept",                7              >,
    pp::bool_field      <"paused",                     8              >,
    pp::int64_field     <"last_seen",                  9              >
>;

using Folder = pp::message<
    pp::string_field    <"id",                    1             >,
    pp::string_field    <"label",                 2             >,
    pp::bool_field      <"ignore_permissions",    4             >,
    pp::bool_field      <"ignore_delete",         5             >,
    pp::bool_field      <"disable_temp_indexes",  6             >,
    pp::bool_field      <"paused",                7             >,
    pp::bool_field      <"scheduled",             8             >,
    pp::string_field    <"path",                  9             >,
    pp::enum_field      <"folder_type",          10, FolderType >,
    pp::enum_field      <"pull_order",           11, PullOrder  >,
    pp::uint32_field    <"rescan_interval",      12             >
>;

using FolderInfo = pp::message<
    pp::uint64_field  <"index_id",              1                                      >,
    pp::int64_field   <"max_sequence",          2                                      >,
    pp::bytes_field   <"introducer_device_key", 3, pp::singular, proto::bytes_backend_t>
>;

using PendingFolder = pp::message<
    pp::message_field   <"folder",      1, Folder    >,
    pp::message_field   <"folder_info", 2, FolderInfo>
>;

using FileInfo = pp::message<
    pp::string_field    <"name",            1                                            >,
    pp::enum_field      <"type",            2, FileInfoType                              >,
    pp::int64_field     <"size",            3                                            >,
    pp::uint32_field    <"permissions",     4                                            >,
    pp::int64_field     <"modified_s",      5                                            >,
    pp::int32_field     <"modified_ns",     11                                           >,
    pp::uint64_field    <"modified_by",     12                                           >,
    pp::bool_field      <"deleted",         6                                            >,
    pp::bool_field      <"invalid",         7                                            >,
    pp::bool_field      <"no_permissions",  8                                            >,
    pp::message_field   <"version",         9, proto::Vector                             >,
    pp::int64_field     <"sequence",        10                                           >,
    pp::int32_field     <"block_size",      13                                           >,
    pp::string_field    <"symlink_target",  16                                           >,
    pp::bytes_field     <"blocks",          17, pp::repeated, proto::bytes_backend_t     >
>;

using BlockInfo = pp::message<
    pp::uint32_field   <"weak_hash", 1>,
    pp::int32_field    <"size",      2>
>;

using SomeDevice = pp::message<
    pp::string_field <"name",           1>,
    pp::string_field <"client_name",    2>,
    pp::string_field <"client_version", 3>,
    pp::string_field <"address",        4>,
    pp::int64_field  <"last_seen",      5>
>;

// clang-format on

} // namespace db

} // namespace syncspirit


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2025 Ivan Baidakou

#include "proto-helpers-bep.h"
#include "proto-helpers-impl.hpp"

namespace syncspirit::proto {

using namespace syncspirit::details;

template <typename T> size_t generic_estimate(const T &object) {
    using coder_t = pp::message_coder<T>;
    using skipper_t = pp::skipper<coder_t>;
    return skipper_t::encode_skip(object);
}

template <typename T> void encode_unsafe(const T &object, utils::bytes_view_t storage) {
    using coder_t = pp::message_coder<T>;
    using skipper_t = pp::skipper<coder_t>;
    auto bytes = pp::bytes((std::byte *)storage.data(), storage.size());
    coder_t::template encode<pp::unsafe_mode>(object, bytes);
}

int decode(utils::bytes_view_t bytes, Announce &dest) { return generic_decode(bytes, dest); }

std::size_t estimate(const Announce &object) { return generic_estimate(object); }

void encode(const Announce &object, utils::bytes_view_t dest) { encode_unsafe(object, dest); }

int decode(utils::bytes_view_t bytes, BlockInfo &dest) { return generic_decode(bytes, dest); }

std::size_t estimate(const BlockInfo &object) { return generic_estimate(object); }

void encode(const BlockInfo &object, utils::bytes_view_t dest) { encode_unsafe(object, dest); }

int decode(utils::bytes_view_t bytes, Close &dest) { return generic_decode(bytes, dest); }

std::size_t estimate(const Close &object) { return generic_estimate(object); }

void encode(const Close &object, utils::bytes_view_t dest) { encode_unsafe(object, dest); }

int decode(utils::bytes_view_t bytes, ClusterConfig &dest) { return generic_decode(bytes, dest); }

std::size_t estimate(const ClusterConfig &object) { return generic_estimate(object); }

void encode(const ClusterConfig &object, utils::bytes_view_t dest) { encode_unsafe(object, dest); }

int decode(utils::bytes_view_t bytes, Device &dest) { return generic_decode(bytes, dest); }

std::size_t estimate(const Device &object) { return generic_estimate(object); }

void encode(const Device &object, utils::bytes_view_t dest) { encode_unsafe(object, dest); }

int decode(utils::bytes_view_t bytes, DownloadProgress &dest) { return generic_decode(bytes, dest); }

std::size_t estimate(const DownloadProgress &object) { return generic_estimate(object); }

void encode(const DownloadProgress &object, utils::bytes_view_t dest) { encode_unsafe(object, dest); }

int decode(utils::bytes_view_t bytes, FileDownloadProgressUpdate &dest) { return generic_decode(bytes, dest); }

std::size_t estimate(const FileDownloadProgressUpdate &object) { return generic_estimate(object); }

void encode(const FileDownloadProgressUpdate &object, utils::bytes_view_t dest) { encode_unsafe(object, dest); }

int decode(utils::bytes_view_t bytes, FileInfo &dest) { return generic_decode(bytes, dest); }

std::size_t estimate(const FileInfo &object) { return generic_estimate(object); }

void encode(const FileInfo &object, utils::bytes_view_t dest) { encode_unsafe(object, dest); }

int decode(utils::bytes_view_t bytes, Folder &dest) { return generic_decode(bytes, dest); }

std::size_t estimate(const Folder &object) { return generic_estimate(object); }

void encode(const Folder &object, utils::bytes_view_t dest) { encode_unsafe(object, dest); }

int decode(utils::bytes_view_t bytes, Header &dest) { return generic_decode(bytes, dest); }

std::size_t estimate(const Header &object) { return generic_estimate(object); }

void encode(const Header &object, utils::bytes_view_t dest) { encode_unsafe(object, dest); }

int decode(utils::bytes_view_t bytes, Hello &dest) { return generic_decode(bytes, dest); }

int decode(utils::bytes_view_t bytes, IndexBase &dest) { return generic_decode(bytes, dest); }

std::size_t estimate(const IndexBase &object) { return generic_estimate(object); }

void encode(const IndexBase &object, utils::bytes_view_t dest) { encode_unsafe(object, dest); }

int decode(utils::bytes_view_t bytes, Ping &dest) { return 0; }

std::size_t estimate(const Ping &object) { return 0; }

void encode(const Ping &, utils::bytes_view_t) {}

int decode(utils::bytes_view_t bytes, Request &dest) { return generic_decode(bytes, dest); }

std::size_t estimate(const Request &object) { return generic_estimate(object); }

void encode(const Request &object, utils::bytes_view_t dest) { encode_unsafe(object, dest); }

int decode(utils::bytes_view_t bytes, Response &dest) { return generic_decode(bytes, dest); }

std::size_t estimate(const Response &object) { return generic_estimate(object); }

void encode(const Response &object, utils::bytes_view_t dest) { encode_unsafe(object, dest); }

utils::bytes_t encode(const Hello &object, std::size_t prefix) {
    using coder_t = pp::message_coder<Hello>;
    using skipper_t = pp::skipper<coder_t>;
    auto size = skipper_t::encode_skip(object);
    auto storage = utils::bytes_t(size + prefix);
    auto bytes = pp::bytes((std::byte *)storage.data() + prefix, size);
    coder_t::template encode<pp::unsafe_mode>(object, bytes);
    return storage;
}

} // namespace syncspirit::proto


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2025 Ivan Baidakou

#pragma once

#include "proto-fwd.hpp"
#include "utils/bytes.h"

namespace syncspirit::proto {

std::size_t estimate(const Announce &);
std::size_t estimate(const BlockInfo &);
std::size_t estimate(const Close &);
std::size_t estimate(const ClusterConfig &);
std::size_t estimate(const Device &);
std::size_t estimate(const DownloadProgress &);
std::size_t estimate(const FileDownloadProgressUpdate &);
std::size_t estimate(const FileInfo &);
std::size_t estimate(const Folder &);
std::size_t estimate(const Header &);
std::size_t estimate(const Header &);
std::size_t estimate(const IndexBase &);
std::size_t estimate(const Ping &);
std::size_t estimate(const Request &);
std::size_t estimate(const Response &);

void encode(const Announce &, utils::bytes_view_t);
void encode(const BlockInfo &, utils::bytes_view_t);
void encode(const Close &, utils::bytes_view_t);
void encode(const ClusterConfig &, utils::bytes_view_t);
void encode(const Device &, utils::bytes_view_t);
void encode(const DownloadProgress &, utils::bytes_view_t);
void encode(const FileDownloadProgressUpdate &, utils::bytes_view_t);
void encode(const FileInfo &, utils::bytes_view_t);
void encode(const Folder &, utils::bytes_view_t);
void encode(const Header &, utils::bytes_view_t);
void encode(const IndexBase &, utils::bytes_view_t);
void encode(const Ping &, utils::bytes_view_t);
void encode(const Request &, utils::bytes_view_t);
void encode(const Response &, utils::bytes_view_t);

utils::bytes_t encode(const Hello &, std::size_t prefix);

int decode(utils::bytes_view_t, Announce &);
int decode(utils::bytes_view_t, BlockInfo &);
int decode(utils::bytes_view_t, Close &);
int decode(utils::bytes_view_t, ClusterConfig &);
int decode(utils::bytes_view_t, Device &);
int decode(utils::bytes_view_t, DownloadProgress &);
int decode(utils::bytes_view_t, FileDownloadProgressUpdate &);
int decode(utils::bytes_view_t, FileInfo &);
int decode(utils::bytes_view_t, Folder &);
int decode(utils::bytes_view_t, Header &);
int decode(utils::bytes_view_t, Hello &);
int decode(utils::bytes_view_t, IndexBase &);
int decode(utils::bytes_view_t, Ping &);
int decode(utils::bytes_view_t, Request &);
int decode(utils::bytes_view_t, Response &);

/****************/
/*** Announce ***/
/****************/

inline utils::bytes_view_t get_id(const Announce &msg) {
    using namespace pp;
    auto &opt = msg["id"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_id(Announce &msg, utils::bytes_view_t value) {
    using namespace pp;
    msg["id"_f] = utils::bytes_t{value.begin(), value.end()};
}
inline std::size_t get_addresses_size(const Announce &msg) {
    using namespace pp;
    return msg["addresses"_f].size();
}
inline std::string_view get_addresses(const Announce &msg, std::size_t i) {
    using namespace pp;
    return msg["addresses"_f][i];
}
inline void set_addresses(Announce &msg, std::size_t i, std::string_view value) {
    using namespace pp;
    msg["addresses"_f][i] = value;
}
inline void add_addresses(Announce &msg, std::string_view value) {
    using namespace pp;
    msg["addresses"_f].emplace_back(value);
}
inline std::uint64_t get_instance_id(const Announce &msg) {
    using namespace pp;
    return msg["instance_id"_f].value_or(0);
}
inline void set_instance_id(Announce &msg, std::uint64_t value) {
    using namespace pp;
    msg["instance_id"_f] = value;
}

/*****************/
/*** BlockInfo ***/
/*****************/

inline std::int64_t get_offset(BlockInfo &msg) {
    using namespace pp;
    return msg["offset"_f].value_or(0);
}

inline void set_offset(BlockInfo &msg, std::int64_t value) {
    using namespace pp;
    msg["offset"_f] = value;
}
inline std::int32_t get_size(const BlockInfo &msg) {
    using namespace pp;
    return msg["size"_f].value_or(0);
}
inline void set_size(BlockInfo &msg, std::int32_t value) {
    using namespace pp;
    msg["size"_f] = value;
}
inline utils::bytes_view_t get_hash(const BlockInfo &msg) {
    using namespace pp;
    auto &opt = msg["hash"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_hash(BlockInfo &msg, utils::bytes_view_t value) {
    using namespace pp;
    msg["hash"_f] = utils::bytes_t{value.begin(), value.end()};
}
inline std::uint32_t get_weak_hash(const BlockInfo &msg) {
    using namespace pp;
    return msg["weak_hash"_f].value_or(0);
}
inline void set_weak_hash(BlockInfo &msg, std::uint32_t value) {
    using namespace pp;
    msg["weak_hash"_f] = value;
}

/*************/
/*** Close ***/
/*************/

inline std::string_view get_reason(const Close &msg) {
    using namespace pp;
    auto &opt = msg["reason"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_reason(Close &msg, std::string value) {
    using namespace pp;
    msg["reason"_f] = std::move(value);
}

/*********************/
/*** ClusterConfig ***/
/*********************/

inline std::size_t get_folders_size(const ClusterConfig &msg) {
    using namespace pp;
    return msg["folders"_f].size();
}
inline const Folder &get_folders(const ClusterConfig &msg, std::size_t i) {
    using namespace pp;
    return msg["folders"_f][i];
}
inline void set_folders(ClusterConfig &msg, std::size_t i, Folder value) {
    using namespace pp;
    msg["folders"_f][i] = std::move(value);
}
inline void add_folders(ClusterConfig &msg, Folder value) {
    using namespace pp;
    msg["folders"_f].emplace_back(std::move(value));
}
inline Folder &add_folders(ClusterConfig &msg) {
    using namespace pp;
    auto &opt = msg["folders"_f];
    opt.emplace_back(Folder());
    return opt.back();
}

/***************/
/*** Counter ***/
/***************/

inline std::uint64_t get_id(const Counter &msg) {
    using namespace pp;
    return msg["id"_f].value_or(0);
}
inline void set_id(Counter &msg, std::uint64_t value) {
    using namespace pp;
    msg["id"_f] = value;
}
inline std::uint64_t get_value(const Counter &msg) {
    using namespace pp;
    return msg["value"_f].value_or(0);
}
inline void set_value(Counter &msg, std::uint64_t value) {
    using namespace pp;
    msg["value"_f] = value;
}

/**************/
/*** Device ***/
/**************/

inline utils::bytes_view_t get_id(const Device &msg) {
    using namespace pp;
    auto &opt = msg["id"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_id(Device &msg, utils::bytes_view_t value) {
    using namespace pp;
    msg["id"_f] = utils::bytes_t{value.begin(), value.end()};
}
inline std::string_view get_name(const Device &msg) {
    using namespace pp;
    auto &opt = msg["name"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_name(Device &msg, std::string_view value) {
    using namespace pp;
    msg["name"_f] = std::string(value);
}
inline std::size_t get_addresses_size(const Device &msg) {
    using namespace pp;
    return msg["addresses"_f].size();
}
inline std::string_view get_addresses(const Device &msg, std::size_t i) {
    using namespace pp;
    return msg["addresses"_f][i];
}
inline void set_addresses(Device &msg, std::size_t i, std::string_view value) {
    using namespace pp;
    msg["addresses"_f][i] = std::string(value);
}
inline void add_addresses(Device &msg, std::string_view value) {
    using namespace pp;
    msg["addresses"_f].emplace_back(std::string(value));
}
inline Compression get_compression(const Device &msg) {
    using namespace pp;
    return msg["compression"_f].value_or(Compression{});
}
inline void set_compression(Device &msg, Compression value) {
    using namespace pp;
    msg["compression"_f] = value;
}
inline std::string_view get_cert_name(const Device &msg) {
    using namespace pp;
    auto &opt = msg["cert_name"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_cert_name(Device &msg, std::string_view value) {
    using namespace pp;
    msg["cert_name"_f] = std::string(value);
}
inline std::int64_t get_max_sequence(const Device &msg) {
    using namespace pp;
    return msg["max_sequence"_f].value_or(0);
}
inline void set_max_sequence(Device &msg, std::int64_t value) {
    using namespace pp;
    msg["max_sequence"_f] = value;
}
inline bool get_introducer(const Device &msg) {
    using namespace pp;
    return msg["introducer"_f].value_or(false);
}
inline void set_introducer(Device &msg, bool value) {
    using namespace pp;
    msg["introducer"_f] = value;
}
inline std::uint64_t get_index_id(const Device &msg) {
    using namespace pp;
    return msg["index_id"_f].value_or(0);
}
inline void set_index_id(Device &msg, std::uint64_t value) {
    using namespace pp;
    msg["index_id"_f] = value;
}
inline bool get_skip_introduction_removals(const Device &msg) {
    using namespace pp;
    return msg["skip_introduction_removals"_f].value_or(false);
}
inline void set_skip_introduction_removals(Device &msg, bool value) {
    using namespace pp;
    msg["skip_introduction_removals"_f] = value;
}

/****************/
/*** FileInfo ***/
/****************/

inline std::string_view get_name(const FileInfo &msg) {
    using namespace pp;
    auto &opt = msg["name"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_name(FileInfo &msg, std::string_view value) {
    using namespace pp;
    msg["name"_f] = std::string(value);
}
inline FileInfoType get_type(const FileInfo &msg) {
    using namespace pp;
    return msg["type"_f].value_or(FileInfoType{});
}
inline void set_type(FileInfo &msg, FileInfoType value) {
    using namespace pp;
    msg["type"_f] = value;
}
inline std::int64_t get_size(const FileInfo &msg) {
    using namespace pp;
    return msg["size"_f].value_or(0);
}
inline void set_size(FileInfo &msg, std::int64_t value) {
    using namespace pp;
    msg["size"_f] = value;
}
inline std::uint32_t get_permissions(const FileInfo &msg) {
    using namespace pp;
    return msg["permissions"_f].value_or(0);
}
inline void set_permissions(FileInfo &msg, std::uint32_t value) {
    using namespace pp;
    msg["permissions"_f] = value;
}
inline std::int64_t get_modified_s(const FileInfo &msg) {
    using namespace pp;
    return msg["modified_s"_f].value_or(0);
}
inline void set_modified_s(FileInfo &msg, std::int64_t value) {
    using namespace pp;
    msg["modified_s"_f] = value;
}
inline std::int32_t get_modified_ns(const FileInfo &msg) {
    using namespace pp;
    return msg["modified_ns"_f].value_or(0);
}
inline void set_modified_ns(FileInfo &msg, std::int32_t value) {
    using namespace pp;
    msg["modified_ns"_f] = value;
}
inline std::uint64_t get_modified_by(const FileInfo &msg) {
    using namespace pp;
    return msg["modified_by"_f].value_or(0);
}
inline void set_modified_by(FileInfo &msg, std::uint64_t value) {
    using namespace pp;
    msg["modified_by"_f] = value;
}
inline bool get_deleted(const FileInfo &msg) {
    using namespace pp;
    return msg["deleted"_f].value_or(false);
}
inline void set_deleted(FileInfo &msg, bool value) {
    using namespace pp;
    msg["deleted"_f] = value;
}
inline bool get_invalid(const FileInfo &msg) {
    using namespace pp;
    return msg["invalid"_f].value_or(false);
}
inline void set_invalid(FileInfo &msg, bool value) {
    using namespace pp;
    msg["invalid"_f] = value;
}
inline bool get_no_permissions(const FileInfo &msg) {
    using namespace pp;
    return msg["no_permissions"_f].value_or(false);
}
inline void set_no_permissions(FileInfo &msg, bool value) {
    using namespace pp;
    msg["no_permissions"_f] = value;
}
inline const Vector &get_version(const FileInfo &msg) {
    using namespace pp;
    auto &opt = msg["version"_f];
    if (!opt) {
        using Opt = std::remove_cv_t<std::remove_reference_t<decltype(opt)>>;
        auto &mutable_opt = const_cast<Opt &>(opt);
        mutable_opt = Vector();
    }
    return opt.value();
}
inline Vector &get_version(FileInfo &msg) {
    using namespace pp;
    auto &opt = msg["version"_f];
    if (!opt) {
        opt = Vector();
    }
    return opt.value();
}
inline void set_version(FileInfo &msg, Vector value) {
    using namespace pp;
    msg["version"_f] = std::move(value);
}
inline std::int64_t get_sequence(const FileInfo &msg) {
    using namespace pp;
    return msg["sequence"_f].value_or(0);
}
inline void set_sequence(FileInfo &msg, std::int64_t value) {
    using namespace pp;
    msg["sequence"_f] = value;
}
inline std::int32_t get_block_size(const FileInfo &msg) {
    using namespace pp;
    return msg["block_size"_f].value_or(0);
}
inline void set_block_size(FileInfo &msg, std::int32_t value) {
    using namespace pp;
    msg["block_size"_f] = value;
}
inline std::size_t get_blocks_size(const FileInfo &msg) {
    using namespace pp;
    return msg["blocks"_f].size();
}
inline const BlockInfo &get_blocks(const FileInfo &msg, std::size_t i) {
    using namespace pp;
    return msg["blocks"_f][i];
}
inline void set_blocks(FileInfo &msg, std::size_t i, BlockInfo block) {
    using namespace pp;
    msg["blocks"_f][i] = std::move(block);
}
inline void add_blocks(FileInfo &msg, BlockInfo block) {
    using namespace pp;
    msg["blocks"_f].emplace_back(std::move(block));
}
inline BlockInfo &add_blocks(FileInfo &msg) {
    using namespace pp;
    auto &opt = msg["blocks"_f];
    opt.emplace_back(BlockInfo());
    return opt.back();
}
inline void clear_blocks(FileInfo &msg) {
    using namespace pp;
    msg["blocks"_f].clear();
}
inline std::string_view get_symlink_target(const FileInfo &msg) {
    using namespace pp;
    auto &opt = msg["symlink_target"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_symlink_target(FileInfo &msg, std::string_view value) {
    using namespace pp;
    msg["symlink_target"_f] = std::string(value);
}
template <typename T = void> inline void set_symlink_target(FileInfo &msg, std::string value) {
    using namespace pp;
    msg["symlink_target"_f] = std::move(value);
}

/**************/
/*** Folder ***/
/**************/

inline std::string_view get_id(const Folder &msg) {
    using namespace pp;
    auto &opt = msg["id"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_id(Folder &msg, std::string_view value) {
    using namespace pp;
    msg["id"_f] = std::string(value);
}
inline std::string_view get_label(const Folder &msg) {
    using namespace pp;
    auto &opt = msg["label"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_label(Folder &msg, std::string_view value) {
    using namespace pp;
    msg["label"_f] = std::string(value);
}
inline bool get_read_only(const Folder &msg) {
    using namespace pp;
    return msg["read_only"_f].value_or(false);
}
inline void set_read_only(Folder &msg, bool value) {
    using namespace pp;
    msg["read_only"_f] = value;
}
inline bool get_ignore_permissions(const Folder &msg) {
    using namespace pp;
    return msg["ignore_permissions"_f].value_or(false);
}
inline void set_ignore_permissions(Folder &msg, bool value) {
    using namespace pp;
    msg["ignore_permissions"_f] = value;
}
inline bool get_ignore_delete(const Folder &msg) {
    using namespace pp;
    return msg["ignore_delete"_f].value_or(false);
}
inline void set_ignore_delete(Folder &msg, bool value) {
    using namespace pp;
    msg["ignore_delete"_f] = value;
}
inline bool get_disable_temp_indexes(const Folder &msg) {
    using namespace pp;
    return msg["disable_temp_indexes"_f].value_or(false);
}
inline void set_disable_temp_indexes(Folder &msg, bool value) {
    using namespace pp;
    msg["disable_temp_indexes"_f] = value;
}
inline bool get_paused(const Folder &msg) {
    using namespace pp;
    return msg["paused"_f].value_or(false);
}
inline void set_paused(Folder &msg, bool value) {
    using namespace pp;
    msg["paused"_f] = value;
}
inline std::size_t get_devices_size(const Folder &msg) {
    using namespace pp;
    return msg["devices"_f].size();
}
inline const Device &get_devices(const Folder &msg, std::size_t i) {
    using namespace pp;
    return msg["devices"_f][i];
}
inline Device &get_devices(Folder &msg, std::size_t i) {
    using namespace pp;
    return msg["devices"_f][i];
}
inline void set_devices(Folder &msg, std::size_t i, Device value) {
    using namespace pp;
    msg["devices"_f][i] = std::move(value);
}
inline void add_devices(Folder &msg, Device value) {
    using namespace pp;
    msg["devices"_f].emplace_back(std::move(value));
}
inline Device &add_devices(Folder &msg) {
    using namespace pp;
    auto &opt = msg["devices"_f];
    opt.emplace_back(Device());
    return opt.back();
}

/**************/
/*** Header ***/
/**************/

inline MessageType get_type(const Header &msg) {
    using namespace pp;
    return msg["type"_f].value_or(MessageType{});
}
inline void set_type(Header &msg, MessageType value) {
    using namespace pp;
    msg["type"_f] = value;
}
inline MessageCompression get_compression(const Header &msg) {
    using namespace pp;
    return msg["compression"_f].value_or(MessageCompression{});
}
inline void set_compression(Header &msg, MessageCompression value) {
    using namespace pp;
    msg["compression"_f] = value;
}

/*************/
/*** Hello ***/
/*************/

inline std::string_view get_device_name(const Hello &msg) {
    using namespace pp;
    auto &opt = msg["device_name"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_device_name(Hello &msg, std::string_view value) {
    using namespace pp;
    msg["device_name"_f] = std::string(value);
}
inline std::string_view get_client_name(const Hello &msg) {
    using namespace pp;
    auto &opt = msg["client_name"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_client_name(Hello &msg, std::string_view value) {
    using namespace pp;
    msg["client_name"_f] = std::string(value);
}
inline std::string_view get_client_version(const Hello &msg) {
    using namespace pp;
    auto &opt = msg["client_version"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_client_version(Hello &msg, std::string_view value) {
    using namespace pp;
    msg["client_version"_f] = std::string(value);
}

/*****************/
/*** IndexBase ***/
/*****************/

inline std::string_view get_folder(const IndexBase &msg) {
    using namespace pp;
    auto &opt = msg["folder"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_folder(IndexBase &msg, std::string_view value) {
    using namespace pp;
    msg["folder"_f] = std::string(value);
}
inline std::size_t get_files_size(const IndexBase &msg) {
    using namespace pp;
    return msg["files"_f].size();
}
inline const FileInfo &get_files(const IndexBase &msg, std::size_t i) {
    using namespace pp;
    return msg["files"_f][i];
}
inline FileInfo &get_files(IndexBase &msg, std::size_t i) {
    using namespace pp;
    return msg["files"_f][i];
}
inline void add_files(IndexBase &msg, FileInfo value) {
    using namespace pp;
    msg["files"_f].emplace_back(std::move(value));
}
inline FileInfo &add_files(IndexBase &msg) {
    using namespace pp;
    auto &opt = msg["files"_f];
    opt.emplace_back(FileInfo());
    return opt.back();
}

/***************/
/*** Request ***/
/***************/

inline std::int32_t get_id(const Request &msg) {
    using namespace pp;
    return msg["id"_f].value_or(0);
}
inline void set_id(Request &msg, std::int32_t value) {
    using namespace pp;
    msg["id"_f] = value;
}
inline std::string_view get_folder(const Request &msg) {
    using namespace pp;
    auto &opt = msg["folder"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_folder(Request &msg, std::string_view value) {
    using namespace pp;
    msg["folder"_f] = std::string(value);
}
inline std::string_view get_name(const Request &msg) {
    using namespace pp;
    auto &opt = msg["name"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_name(Request &msg, std::string_view value) {
    using namespace pp;
    msg["name"_f] = std::string(value);
}
inline std::int32_t get_offset(const Request &msg) {
    using namespace pp;
    return msg["offset"_f].value_or(0);
}
inline void set_offset(Request &msg, std::int32_t value) {
    using namespace pp;
    msg["offset"_f] = value;
}
inline std::int32_t get_size(const Request &msg) {
    using namespace pp;
    return msg["size"_f].value_or(0);
}
inline void set_size(Request &msg, std::int32_t value) {
    using namespace pp;
    msg["size"_f] = value;
}
inline utils::bytes_view_t get_hash(const Request &msg) {
    using namespace pp;
    auto &opt = msg["hash"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_hash(Request &msg, utils::bytes_view_t value) {
    using namespace pp;
    msg["hash"_f] = utils::bytes_t{value.begin(), value.end()};
}
inline bool get_from_temporary(const Request &msg) {
    using namespace pp;
    return msg["from_temporary"_f].value_or(false);
}
inline void set_from_temporary(Request &msg, bool value) {
    using namespace pp;
    msg["from_temporary"_f] = value;
}
inline std::uint32_t get_weak_hash(const Request &msg) {
    using namespace pp;
    return msg["weak_hash"_f].value_or(0);
}
inline void set_weak_hash(Request &msg, std::uint32_t value) {
    using namespace pp;
    msg["weak_hash"_f] = value;
}

/****************/
/*** Response ***/
/****************/

inline std::int32_t get_id(const Response &msg) {
    using namespace pp;
    return msg["id"_f].value_or(0);
}
inline void set_id(Response &msg, std::int32_t value) {
    using namespace pp;
    msg["id"_f] = value;
}
inline utils::bytes_view_t get_data(const Response &msg) {
    using namespace pp;
    auto &opt = msg["data"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline std::vector<unsigned char> extract_data(Response &msg) {
    using namespace pp;
    auto &opt = msg["data"_f];
    return std::move(opt.value());
}
inline void set_data(Response &msg, utils::bytes_view_t value) {
    using namespace pp;
    msg["data"_f] = utils::bytes_t{value.begin(), value.end()};
}
inline void set_data(Response &msg, utils::bytes_t value) {
    using namespace pp;
    msg["data"_f] = std::move(value);
}
inline ErrorCode get_code(const Response &msg) {
    using namespace pp;
    return msg["code"_f].value_or(ErrorCode{});
}
inline void set_code(Response &msg, ErrorCode value) {
    using namespace pp;
    msg["code"_f] = value;
}

/**************/
/*** Vector ***/
/**************/

inline std::size_t get_counters_size(const Vector &msg) {
    using namespace pp;
    return msg["counters"_f].size();
}
inline const Counter &get_counters(const Vector &msg, std::size_t i) {
    using namespace pp;
    return msg["counters"_f][i];
}
inline Counter &get_counters(Vector &msg, std::size_t i) {
    using namespace pp;
    return msg["counters"_f][i];
}
inline void clear_counters(Vector &msg) {
    using namespace pp;
    msg["counters"_f].clear();
}
inline void set_counters(Vector &msg, std::size_t i, Counter value) {
    using namespace pp;
    msg["counters"_f][i] = std::move(value);
}
inline void add_counters(Vector &msg, Counter value) {
    using namespace pp;
    msg["counters"_f].emplace_back(std::move(value));
}
inline Counter &add_counters(Vector &msg) {
    using namespace pp;
    auto &opt = msg["counters"_f];
    opt.emplace_back(Counter());
    return opt.back();
}

} // namespace syncspirit::proto


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2025 Ivan Baidakou

#include "proto-helpers-db.h"
#include "proto-helpers-impl.hpp"
#include <protopuf/message.h>
#include <protopuf/coder.h>

namespace syncspirit::db {

using namespace syncspirit::details;

int decode(utils::bytes_view_t data, BlockInfo &object) { return generic_decode(data, object); }

utils::bytes_t encode(const BlockInfo &object) { return generic_encode(object); }

int decode(utils::bytes_view_t data, Device &object) { return generic_decode(data, object); }

utils::bytes_t encode(const Device &object) { return generic_encode(object); }

int decode(utils::bytes_view_t data, FileInfo &object) { return generic_decode(data, object); }

utils::bytes_t encode(const FileInfo &object) { return generic_encode(object); }

int decode(utils::bytes_view_t data, Folder &object) { return generic_decode(data, object); }

utils::bytes_t encode(const Folder &object) { return generic_encode(object); }

int decode(utils::bytes_view_t data, FolderInfo &object) { return generic_decode(data, object); }

utils::bytes_t encode(const FolderInfo &object) { return generic_encode(object); }

int decode(utils::bytes_view_t data, IgnoredFolder &object) { return generic_decode(data, object); }

utils::bytes_t encode(const IgnoredFolder &object) { return generic_encode(object); }

int decode(utils::bytes_view_t data, PendingFolder &object) { return generic_decode(data, object); }

utils::bytes_t encode(const PendingFolder &object) { return generic_encode(object); }

int decode(utils::bytes_view_t data, SomeDevice &object) { return generic_decode(data, object); }

utils::bytes_t encode(const SomeDevice &object) { return generic_encode(object); }

} // namespace syncspirit::db


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2025 Ivan Baidakou

#pragma once

#include "proto-fwd.hpp"
#include "utils/bytes.h"
#include "syncspirit-export.h"

namespace syncspirit::db {

utils::bytes_t SYNCSPIRIT_API encode(const BlockInfo &);
utils::bytes_t SYNCSPIRIT_API encode(const Device &);
utils::bytes_t SYNCSPIRIT_API encode(const FileInfo &);
utils::bytes_t SYNCSPIRIT_API encode(const Folder &);
utils::bytes_t SYNCSPIRIT_API encode(const FolderInfo &);
utils::bytes_t SYNCSPIRIT_API encode(const IgnoredFolder &);
utils::bytes_t SYNCSPIRIT_API encode(const PendingFolder &);
utils::bytes_t SYNCSPIRIT_API encode(const SomeDevice &);

int SYNCSPIRIT_API decode(utils::bytes_view_t, BlockInfo &);
int SYNCSPIRIT_API decode(utils::bytes_view_t, Device &);
int SYNCSPIRIT_API decode(utils::bytes_view_t, FileInfo &);
int SYNCSPIRIT_API decode(utils::bytes_view_t, Folder &);
int SYNCSPIRIT_API decode(utils::bytes_view_t, FolderInfo &);
int SYNCSPIRIT_API decode(utils::bytes_view_t, IgnoredFolder &);
int SYNCSPIRIT_API decode(utils::bytes_view_t, PendingFolder &);
int SYNCSPIRIT_API decode(utils::bytes_view_t, SomeDevice &);

/*****************/
/*** BlockInfo ***/
/*****************/

inline std::uint32_t get_weak_hash(const BlockInfo &msg) {
    using namespace pp;
    return msg["weak_hash"_f].value_or(0);
}
inline void set_weak_hash(BlockInfo &msg, std::uint32_t value) {
    using namespace pp;
    msg["weak_hash"_f] = value;
}
inline std::int32_t get_size(const BlockInfo &msg) {
    using namespace pp;
    return msg["size"_f].value_or(0);
}
inline void set_size(BlockInfo &msg, std::int32_t value) {
    using namespace pp;
    msg["size"_f] = value;
}

/**************/
/*** Device ***/
/**************/

inline std::string_view get_name(const Device &msg) {
    using namespace pp;
    auto &opt = msg["name"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_name(Device &msg, std::string_view value) {
    using namespace pp;
    msg["name"_f] = std::string(value);
}
inline std::size_t get_addresses_size(const Device &msg) {
    using namespace pp;
    return msg["addresses"_f].size();
}
inline std::string_view get_addresses(const Device &msg, std::size_t i) {
    using namespace pp;
    return msg["addresses"_f][i];
}
inline void set_addresses(Device &msg, std::size_t i, std::string_view value) {
    using namespace pp;
    msg["addresses"_f][i] = std::string(value);
}
inline void clear_addresses(Device &msg) {
    using namespace pp;
    msg["addresses"_f].clear();
}
inline void add_addresses(Device &msg, std::string_view value) {
    using namespace pp;
    msg["addresses"_f].emplace_back(std::string(value));
}
inline Compression get_compression(const Device &msg) {
    using namespace pp;
    return msg["compression"_f].value_or(Compression{});
}
inline void set_compression(Device &msg, Compression value) {
    using namespace pp;
    msg["compression"_f] = value;
}
inline std::string_view get_cert_name(const Device &msg) {
    using namespace pp;
    auto &opt = msg["cert_name"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_cert_name(Device &msg, std::string_view value) {
    using namespace pp;
    msg["cert_name"_f] = std::string(value);
}
inline bool get_introducer(const Device &msg) {
    using namespace pp;
    return msg["introducer"_f].value_or(false);
}
inline void set_introducer(Device &msg, bool value) {
    using namespace pp;
    msg["introducer"_f] = value;
}
inline bool get_skip_introduction_removals(const Device &msg) {
    using namespace pp;
    return msg["skip_introduction_removals"_f].value_or(false);
}
inline void set_skip_introduction_removals(Device &msg, bool value) {
    using namespace pp;
    msg["skip_introduction_removals"_f] = value;
}
inline bool get_auto_accept(const Device &msg) {
    using namespace pp;
    return msg["auto_accept"_f].value_or(false);
}
inline void set_auto_accept(Device &msg, bool value) {
    using namespace pp;
    msg["auto_accept"_f] = value;
}
inline bool get_paused(const Device &msg) {
    using namespace pp;
    return msg["paused"_f].value_or(false);
}
inline void set_paused(Device &msg, bool value) {
    using namespace pp;
    msg["paused"_f] = value;
}
inline std::int64_t get_last_seen(const Device &msg) {
    using namespace pp;
    return msg["last_seen"_f].value_or(0);
}
inline void set_last_seen(Device &msg, std::int64_t value) {
    using namespace pp;
    msg["last_seen"_f] = value;
}

/****************/
/*** FileInfo ***/
/****************/

inline std::string_view get_name(const FileInfo &msg) {
    using namespace pp;
    auto &opt = msg["name"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_name(FileInfo &msg, std::string_view value) {
    using namespace pp;
    msg["name"_f] = std::string(value);
}
inline FileInfoType get_type(const FileInfo &msg) {
    using namespace pp;
    return msg["type"_f].value_or(FileInfoType{});
}
inline void set_type(FileInfo &msg, FileInfoType value) {
    using namespace pp;
    msg["type"_f] = value;
}
inline std::int64_t get_size(const FileInfo &msg) {
    using namespace pp;
    return msg["size"_f].value_or(0);
}
inline void set_size(FileInfo &msg, std::int64_t value) {
    using namespace pp;
    msg["size"_f] = value;
}
inline std::uint32_t get_permissions(const FileInfo &msg) {
    using namespace pp;
    return msg["permissions"_f].value_or(0);
}
inline void set_permissions(FileInfo &msg, std::uint32_t value) {
    using namespace pp;
    msg["permissions"_f] = value;
}
inline std::int64_t get_modified_s(const FileInfo &msg) {
    using namespace pp;
    return msg["modified_s"_f].value_or(0);
}
inline void set_modified_s(FileInfo &msg, std::int64_t value) {
    using namespace pp;
    msg["modified_s"_f] = value;
}
inline std::int32_t get_modified_ns(const FileInfo &msg) {
    using namespace pp;
    return msg["modified_ns"_f].value_or(0);
}
inline void set_modified_ns(FileInfo &msg, std::int32_t value) {
    using namespace pp;
    msg["modified_ns"_f] = value;
}
inline std::uint64_t get_modified_by(const FileInfo &msg) {
    using namespace pp;
    return msg["modified_by"_f].value_or(0);
}
inline void set_modified_by(FileInfo &msg, std::uint64_t value) {
    using namespace pp;
    msg["modified_by"_f] = value;
}
inline bool get_deleted(const FileInfo &msg) {
    using namespace pp;
    return msg["deleted"_f].value_or(false);
}
inline void set_deleted(FileInfo &msg, bool value) {
    using namespace pp;
    msg["deleted"_f] = value;
}
inline bool get_invalid(const FileInfo &msg) {
    using namespace pp;
    return msg["invalid"_f].value_or(false);
}
inline void set_invalid(FileInfo &msg, bool value) {
    using namespace pp;
    msg["invalid"_f] = value;
}
inline bool get_no_permissions(const FileInfo &msg) {
    using namespace pp;
    return msg["no_permissions"_f].value_or(false);
}
inline void set_no_permissions(FileInfo &msg, bool value) {
    using namespace pp;
    msg["no_permissions"_f] = value;
}
inline const Vector &get_version(const FileInfo &msg) {
    using namespace pp;
    auto &opt = msg["version"_f];
    if (!opt) {
        using Opt = std::remove_cv_t<std::remove_reference_t<decltype(opt)>>;
        auto &mutable_opt = const_cast<Opt &>(opt);
        mutable_opt = Vector();
    }
    return opt.value();
}
inline Vector &get_version(FileInfo &msg) {
    using namespace pp;
    auto &opt = msg["version"_f];
    if (!opt) {
        opt = Vector();
    }
    return opt.value();
}
inline void set_version(FileInfo &msg, Vector value) {
    using namespace pp;
    msg["version"_f] = std::move(value);
}
inline std::int64_t get_sequence(const FileInfo &msg) {
    using namespace pp;
    return msg["sequence"_f].value_or(0);
}
inline void set_sequence(FileInfo &msg, std::int64_t value) {
    using namespace pp;
    msg["sequence"_f] = value;
}
inline std::int32_t get_block_size(const FileInfo &msg) {
    using namespace pp;
    return msg["block_size"_f].value_or(0);
}
inline void set_block_size(FileInfo &msg, std::int32_t value) {
    using namespace pp;
    msg["block_size"_f] = value;
}
inline std::size_t get_blocks_size(const FileInfo &msg) {
    using namespace pp;
    return msg["blocks"_f].size();
}
inline utils::bytes_view_t get_blocks(const FileInfo &msg, std::size_t i) {
    using namespace pp;
    return msg["blocks"_f][i];
}
inline void set_blocks(FileInfo &msg, std::size_t i, utils::bytes_view_t value) {
    using namespace pp;
    msg["blocks"_f][i] = utils::bytes_t(value.begin(), value.end());
}
inline void add_blocks(FileInfo &msg, utils::bytes_view_t value) {
    using namespace pp;
    msg["blocks"_f].emplace_back(utils::bytes_t(value.begin(), value.end()));
}
inline std::string_view get_symlink_target(const FileInfo &msg) {
    using namespace pp;
    auto &opt = msg["symlink_target"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_symlink_target(FileInfo &msg, std::string_view value) {
    using namespace pp;
    msg["symlink_target"_f] = std::string(value);
}
inline void set_symlink_target(FileInfo &msg, std::string value) {
    using namespace pp;
    msg["symlink_target"_f] = std::move(value);
}

/**************/
/*** Folder ***/
/**************/

inline std::string_view get_id(const Folder &msg) {
    using namespace pp;
    auto &opt = msg["id"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_id(Folder &msg, std::string_view value) {
    using namespace pp;
    msg["id"_f] = std::string(value);
}
inline std::string_view get_label(const Folder &msg) {
    using namespace pp;
    auto &opt = msg["label"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_label(Folder &msg, std::string_view value) {
    using namespace pp;
    msg["label"_f] = std::string(value);
}
inline bool get_ignore_permissions(const Folder &msg) {
    using namespace pp;
    return msg["ignore_permissions"_f].value_or(false);
}
inline void set_ignore_permissions(Folder &msg, bool value) {
    using namespace pp;
    msg["ignore_permissions"_f] = value;
}
inline bool get_ignore_delete(const Folder &msg) {
    using namespace pp;
    return msg["ignore_delete"_f].value_or(false);
}
inline void set_ignore_delete(Folder &msg, bool value) {
    using namespace pp;
    msg["ignore_delete"_f] = value;
}
inline bool get_disable_temp_indexes(const Folder &msg) {
    using namespace pp;
    return msg["disable_temp_indexes"_f].value_or(false);
}
inline void set_disable_temp_indexes(Folder &msg, bool value) {
    using namespace pp;
    msg["disable_temp_indexes"_f] = value;
}
inline bool get_paused(const Folder &msg) {
    using namespace pp;
    return msg["paused"_f].value_or(false);
}
inline void set_paused(Folder &msg, bool value) {
    using namespace pp;
    msg["paused"_f] = value;
}
inline bool get_scheduled(const Folder &msg) {
    using namespace pp;
    return msg["scheduled"_f].value_or(false);
}
inline void set_scheduled(Folder &msg, bool value) {
    using namespace pp;
    msg["scheduled"_f] = value;
}
inline std::string_view get_path(const Folder &msg) {
    using namespace pp;
    auto &opt = msg["path"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_path(Folder &msg, std::string_view value) {
    using namespace pp;
    msg["path"_f] = std::string(value);
}
template <typename T = void> inline void set_path(Folder &msg, std::string value) {
    using namespace pp;
    msg["path"_f] = std::move(value);
}
inline FolderType get_folder_type(const Folder &msg) {
    using namespace pp;
    return msg["folder_type"_f].value_or(FolderType{});
}
inline void set_folder_type(Folder &msg, FolderType value) {
    using namespace pp;
    msg["folder_type"_f] = value;
}
inline PullOrder get_pull_order(const Folder &msg) {
    using namespace pp;
    return msg["pull_order"_f].value_or(PullOrder{});
}
inline void set_pull_order(Folder &msg, PullOrder value) {
    using namespace pp;
    msg["pull_order"_f] = value;
}
inline std::uint32_t get_rescan_interval(const Folder &msg) {
    using namespace pp;
    return msg["rescan_interval"_f].value_or(0);
}
inline void set_rescan_interval(Folder &msg, std::uint32_t value) {
    using namespace pp;
    msg["rescan_interval"_f] = value;
}

/******************/
/*** FolderInfo ***/
/******************/

inline std::uint64_t get_index_id(const FolderInfo &msg) {
    using namespace pp;
    return msg["index_id"_f].value_or(0);
}
inline void set_index_id(FolderInfo &msg, std::uint64_t value) {
    using namespace pp;
    msg["index_id"_f] = value;
}
inline std::int64_t get_max_sequence(const FolderInfo &msg) {
    using namespace pp;
    return msg["max_sequence"_f].value_or(0);
}
inline void set_max_sequence(FolderInfo &msg, std::int64_t value) {
    using namespace pp;
    msg["max_sequence"_f] = value;
}
inline utils::bytes_view_t get_introducer_device_key(const FolderInfo &msg) {
    using namespace pp;
    auto &opt = msg["introducer_device_key"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_introducer_device_key(FolderInfo &msg, utils::bytes_view_t value) {
    using namespace pp;
    msg["introducer_device_key"_f] = utils::bytes_t{value.begin(), value.end()};
}

/*********************/
/*** IgnoredFolder ***/
/*********************/

inline std::string_view get_label(const IgnoredFolder &msg) {
    using namespace pp;
    auto &opt = msg["label"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_label(IgnoredFolder &msg, std::string_view value) {
    using namespace pp;
    msg["label"_f] = std::string(value);
}

/*********************/
/*** PendingFolder ***/
/*********************/

inline const Folder &get_folder(const PendingFolder &msg) {
    using namespace pp;
    auto &opt = msg["folder"_f];
    if (!opt) {
        using Opt = std::remove_cv_t<std::remove_reference_t<decltype(opt)>>;
        auto &mutable_opt = const_cast<Opt &>(opt);
        mutable_opt = Folder();
    }
    return opt.value();
}
inline Folder &get_folder(PendingFolder &msg) {
    using namespace pp;
    auto &opt = msg["folder"_f];
    if (!opt) {
        opt = Folder();
    }
    return opt.value();
}
inline void set_folder(PendingFolder &msg, Folder value) {
    using namespace pp;
    msg["folder"_f] = std::move(value);
}
inline const FolderInfo &get_folder_info(const PendingFolder &msg) {
    using namespace pp;
    auto &opt = msg["folder_info"_f];
    if (!opt) {
        using Opt = std::remove_cv_t<std::remove_reference_t<decltype(opt)>>;
        auto &mutable_opt = const_cast<Opt &>(opt);
        mutable_opt = FolderInfo();
    }
    return opt.value();
}
inline FolderInfo &get_folder_info(PendingFolder &msg) {
    using namespace pp;
    auto &opt = msg["folder_info"_f];
    if (!opt) {
        opt = FolderInfo();
    }
    return opt.value();
}
inline void set_folder_info(PendingFolder &msg, FolderInfo value) {
    using namespace pp;
    msg["folder_info"_f] = std::move(value);
}

/******************/
/*** SomeDevice ***/
/******************/

inline std::string_view get_name(const SomeDevice &msg) {
    using namespace pp;
    auto &opt = msg["name"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_name(SomeDevice &msg, std::string_view value) {
    using namespace pp;
    msg["name"_f] = std::string(value);
}
inline std::string_view get_client_name(const SomeDevice &msg) {
    using namespace pp;
    auto &opt = msg["client_name"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_client_name(SomeDevice &msg, std::string_view value) {
    using namespace pp;
    msg["client_name"_f] = std::string(value);
}
inline std::string_view get_client_version(const SomeDevice &msg) {
    using namespace pp;
    auto &opt = msg["client_version"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_client_version(SomeDevice &msg, std::string_view value) {
    using namespace pp;
    msg["client_version"_f] = std::string(value);
}
inline std::string_view get_address(const SomeDevice &msg) {
    using namespace pp;
    auto &opt = msg["address"_f];
    if (opt) {
        return opt.value();
    }
    return {};
}
inline void set_address(SomeDevice &msg, std::string_view value) {
    using namespace pp;
    msg["address"_f] = std::string(value);
}
template <typename T = void> inline void set_address(SomeDevice &msg, std::string value) {
    using namespace pp;
    msg["address"_f] = std::move(value);
}
inline std::int64_t get_last_seen(const SomeDevice &msg) {
    using namespace pp;
    return msg["last_seen"_f].value_or(0);
}
inline void set_last_seen(SomeDevice &msg, std::int64_t value) {
    using namespace pp;
    msg["last_seen"_f] = value;
}

} // namespace syncspirit::db


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2025 Ivan Baidakou

#include "utils/bytes.h"
#include <protopuf/message.h>
#include <protopuf/coder.h>

namespace syncspirit::details {

template <typename T> size_t generic_decode(utils::bytes_view_t bytes, T &storage) noexcept {
    using coder_t = pp::message_coder<T>;
    auto ptr = (std::byte *)bytes.data();
    auto span = std::span<std::byte>(ptr, bytes.size());
    auto pair = coder_t::template decode<pp::unsafe_mode>(span);
    storage = std::move(pair.first);
    return pair.second.size();
}

template <typename T> utils::bytes_t generic_encode(const T &object) noexcept {
    using coder_t = pp::message_coder<T>;
    using skipper_t = pp::skipper<coder_t>;
    auto size = skipper_t::encode_skip(object);
    auto storage = utils::bytes_t(size);
    auto bytes = pp::bytes((std::byte *)storage.data(), size);
    coder_t::template encode<pp::unsafe_mode>(object, bytes);
    return storage;
}

} // namespace syncspirit::details


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2025 Ivan Baidakou

#pragma once

#include "proto-helpers-bep.h"
#include "proto-helpers-db.h"