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

#include "test-utils.h"
#include "access.h"
#include "diff-builder.h"
#include "model/misc/updates_streamer.h"

using namespace syncspirit;
using namespace syncspirit::test;
using namespace syncspirit::utils;
using namespace syncspirit::model;

TEST_CASE("updates_streamer", "[model]") {
    test::init_logging();

    auto my_id = device_id_t::from_string("KHQNO2S-5QSILRK-YX4JZZ4-7L77APM-QNVGZJT-EKU7IFI-PNEPBMY-4MXFMQD").value();
    auto my_device = device_t::create(my_id, "my-device").value();
    auto peer_id = device_id_t::from_string("VUV42CZ-IQD5A37-RPEBPM4-VVQK6E4-6WSKC7B-PVJQHHD-4PZD44V-ENC6WAZ").value();
    auto peer_device = device_t::create(peer_id, "peer-device").value();

    auto cluster = cluster_ptr_t(new cluster_t(my_device, 1));
    auto sequencer = make_sequencer(4);
    cluster->get_devices().put(my_device);
    cluster->get_devices().put(peer_device);
    auto builder = diff_builder_t(*cluster);

    auto &folders = cluster->get_folders();
    REQUIRE(builder.upsert_folder("1234-5678", "/my/path").apply());
    REQUIRE(builder.share_folder(peer_id.get_sha256(), "1234-5678").apply());
    auto folder = folders.by_id("1234-5678");

    auto add_remote = [&](std::uint64_t index, std::int64_t sequence) {
        auto remote_folder = remote_folder_info_t::create(index, sequence, *peer_device, *folder).value();
        peer_device->get_remote_folder_infos().put(remote_folder);
    };

    SECTION("trivial") {
        SECTION("no files") {
            auto streamer = model::updates_streamer_t(*cluster, *peer_device);
            REQUIRE(!streamer.next());
        }

        add_remote(0, 0);

        SECTION("no files (2)") {
            auto streamer = model::updates_streamer_t(*cluster, *peer_device);
            REQUIRE(!streamer.next());
        }
    }

    auto my_folder = folder->get_folder_infos().by_device(*my_device);

    int seq = 1;
    auto add_file = [&](const char *name) {
        auto pr_file = proto::FileInfo();
        proto::set_name(pr_file, name);
        proto::set_sequence(pr_file, seq++);

        auto &v = proto::get_version(pr_file);
        proto::add_counters(v, proto::Counter(my_device->device_id().get_uint(), 0));

        auto f = file_info_t::create(sequencer->next_uuid(), pr_file, my_folder).value();
        my_folder->add_strict(f);
        return f;
    };

    SECTION("2 files, index mismatch") {
        SECTION("zero sequence") { add_remote(0, 0); }
        SECTION("non-zero sequence") { add_remote(0, seq + 100); }

        auto f1 = add_file("a.txt");
        auto f2 = add_file("b.txt");

        auto streamer = model::updates_streamer_t(*cluster, *peer_device);
        CHECK(streamer.next() == f1);
        CHECK(streamer.next() == f2);
        CHECK(!streamer.next());
    }

    SECTION("2 files, index matches, sequence greater") {
        auto f1 = add_file("a.txt");
        auto f2 = add_file("b.txt");

        add_remote(my_folder->get_index(), f2->get_sequence());

        auto streamer = model::updates_streamer_t(*cluster, *peer_device);
        CHECK(!streamer.next());
    }

    SECTION("2 files, index matches, sequence greater") {
        auto f1 = add_file("a.txt");
        auto f2 = add_file("b.txt");

        add_remote(my_folder->get_index(), f1->get_sequence());

        auto streamer = model::updates_streamer_t(*cluster, *peer_device);
        CHECK(streamer.next() == f2);
        CHECK(!streamer.next());
    }

    SECTION("1 file, streamer is updated lazily") {
        add_remote(0, seq);

        auto streamer = model::updates_streamer_t(*cluster, *peer_device);
        REQUIRE(!streamer.next());

        auto f1 = add_file("a.txt");
        streamer.on_update(*f1);
        CHECK(streamer.next() == f1);
        CHECK(!streamer.next());
    }

    SECTION("empty streamer ignores updates") {
        add_remote(0, seq);
        auto peer_folder = folder->get_folder_infos().by_device(*peer_device);
        REQUIRE(builder.unshare_folder(*peer_folder).apply());

        auto streamer = model::updates_streamer_t(*cluster, *peer_device);
        REQUIRE(!streamer.next());

        auto f1 = add_file("a.txt");
        streamer.on_update(*f1);
        CHECK(!streamer.next());
    }

    SECTION("2 files, streamer is updated") {
        add_remote(0, seq + 100);

        auto f1 = add_file("a.txt");
        auto f2 = add_file("b.txt");

        auto streamer = model::updates_streamer_t(*cluster, *peer_device);

        f1->set_sequence(++seq);
        my_folder->add_strict(f1);

        streamer.on_update(*f1);

        REQUIRE(streamer.next() == f2);
        REQUIRE(streamer.next() == f1);
        REQUIRE(!streamer.next());
    }

    SECTION("no files streaming for receive-only folder") {
        SECTION("lazy streamer update") {
            add_remote(0, seq);
            auto &folder_type = ((model::folder_data_t *)folder.get())->access<test::to::folder_type>();
            folder_type = db::FolderType::receive;

            auto streamer = model::updates_streamer_t(*cluster, *peer_device);
            REQUIRE(!streamer.next());

            auto f1 = add_file("a.txt");
            streamer.on_update(*f1);
            CHECK(!streamer.next());
        }
        SECTION("non-lazy streamer update") {
            add_remote(0, seq);
            auto f1 = add_file("a.txt");
            auto &folder_type = ((model::folder_data_t *)folder.get())->access<test::to::folder_type>();
            folder_type = db::FolderType::receive;

            auto streamer = model::updates_streamer_t(*cluster, *peer_device);
            CHECK(!streamer.next());
        }
    }
}


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

#include "test-utils.h"
#include "diff-builder.h"
#include "access.h"
#include "model/cluster.h"
#include "model/misc/resolver.h"

using namespace syncspirit;
using namespace syncspirit::test;
using namespace syncspirit::utils;
using namespace syncspirit::model;

using A = advance_action_t;

TEST_CASE("resolver", "[model]") {
    test::init_logging();
    auto my_id = device_id_t::from_string("KHQNO2S-5QSILRK-YX4JZZ4-7L77APM-QNVGZJT-EKU7IFI-PNEPBMY-4MXFMQD").value();
    auto peer_id = device_id_t::from_string("VUV42CZ-IQD5A37-RPEBPM4-VVQK6E4-6WSKC7B-PVJQHHD-4PZD44V-ENC6WAZ").value();
    auto peer_2_id =
        model::device_id_t::from_string("LYXKCHX-VI3NYZR-ALCJBHF-WMZYSPK-QG6QJA3-MPFYMSO-U56GTUK-NA2MIAW").value();
    auto my_device = device_t::create(my_id, "my-device").value();
    auto peer_device = device_t::create(peer_id, "peer-device").value();
    auto peer_2_device = device_t::create(peer_2_id, "peer-device-2").value();

    auto cluster = cluster_ptr_t(new cluster_t(my_device, 1));
    auto sequencer = make_sequencer(4);
    cluster->get_devices().put(my_device);
    cluster->get_devices().put(peer_device);
    cluster->get_devices().put(peer_2_device);

    auto &folders = cluster->get_folders();
    auto builder = diff_builder_t(*cluster);
    REQUIRE(builder.upsert_folder("1234-5678", "some/path", "my-label").apply());
    REQUIRE(builder.share_folder(peer_id.get_sha256(), "1234-5678").apply());
    REQUIRE(builder.share_folder(peer_2_id.get_sha256(), "1234-5678").apply());

    auto folder = folders.by_id("1234-5678");
    auto &folder_infos = folder->get_folder_infos();
    auto folder_my = folder_infos.by_device(*my_device);
    auto folder_peer = folder_infos.by_device(*peer_device);
    auto folder_peer_2 = folder_infos.by_device(*peer_2_device);

    auto pr_remote = proto::FileInfo();
    proto::set_name(pr_remote, "a.txt");
    proto::set_sequence(pr_remote, 2);

    auto &peer_v = proto::get_version(pr_remote);
    auto &peer_c1 = proto::add_counters(peer_v);
    proto::set_id(peer_c1, 1);
    proto::set_value(peer_c1, 2);

    SECTION("no local file -> copy remote") {
        auto file_remote = file_info_t::create(sequencer->next_uuid(), pr_remote, folder_peer).value();
        folder_peer->add_strict(file_remote);
        auto action = resolve(*file_remote);
        CHECK(action == A::remote_copy);
    }

    SECTION("unreacheable -> ignore") {
        auto file_remote = file_info_t::create(sequencer->next_uuid(), pr_remote, folder_peer).value();
        file_remote->mark_unreachable(true);
        folder_peer->add_strict(file_remote);
        auto action = resolve(*file_remote);
        CHECK(action == A::ignore);
    }

    SECTION("invalid -> ignore") {
        proto::set_invalid(pr_remote, true);
        auto file_remote = file_info_t::create(sequencer->next_uuid(), pr_remote, folder_peer).value();
        folder_peer->add_strict(file_remote);
        auto action = resolve(*file_remote);
        CHECK(action == A::ignore);
    }

    SECTION("3rd party has global version -> ignore") {
        auto pr_remote_2 = pr_remote;
        auto &c2 = proto::add_counters(proto::get_version(pr_remote_2));
        proto::set_id(c2, 2);
        proto::set_value(c2, 3);

        auto file_remote = file_info_t::create(sequencer->next_uuid(), pr_remote, folder_peer).value();
        folder_peer->add_strict(file_remote);

        auto file_remote_2 = file_info_t::create(sequencer->next_uuid(), pr_remote_2, folder_peer_2).value();
        folder_peer_2->add_strict(file_remote_2);

        auto action = resolve(*file_remote);
        CHECK(action == A::ignore);
    }

    SECTION("has outdated local file, which is not scanned yet -> ignore") {
        auto pr_local = pr_remote;
        auto &c_local = proto::get_counters(proto::get_version(pr_local), 0);
        proto::set_value(c_local, 1);

        auto file_remote = file_info_t::create(sequencer->next_uuid(), pr_remote, folder_peer).value();
        auto file_local = file_info_t::create(sequencer->next_uuid(), pr_local, folder_my).value();
        folder_peer->add_strict(file_remote);
        folder_my->add_strict(file_local);
        auto action = resolve(*file_remote);
        CHECK(action == A::ignore);
    }

    SECTION("has outdated local file, scanned -> copy remote (1)") {
        auto pr_local = pr_remote;
        auto &c_local = proto::get_counters(proto::get_version(pr_local), 0);
        proto::set_value(c_local, 1);
        auto file_remote = file_info_t::create(sequencer->next_uuid(), pr_remote, folder_peer).value();
        auto file_local = file_info_t::create(sequencer->next_uuid(), pr_local, folder_my).value();
        file_local->mark_local();
        folder_peer->add_strict(file_remote);
        folder_my->add_strict(file_local);
        auto action = resolve(*file_remote);
        CHECK(action == A::remote_copy);
    }

    SECTION("has outdated local file, scanned -> copy remote (2)") {
        auto pr_local = pr_remote;
        auto &c2 = proto::add_counters(proto::get_version(pr_remote));
        proto::set_id(c2, 2);
        proto::set_value(c2, 3);
        auto file_remote = file_info_t::create(sequencer->next_uuid(), pr_remote, folder_peer).value();
        auto file_local = file_info_t::create(sequencer->next_uuid(), pr_local, folder_my).value();
        file_local->mark_local();
        folder_peer->add_strict(file_remote);
        folder_my->add_strict(file_local);
        auto action = resolve(*file_remote);
        CHECK(action == A::remote_copy);
    }

    SECTION("has outdated local file, scanned & remote deleted -> copy remote (3)") {
        auto pr_local = pr_remote;
        auto &c2 = proto::add_counters(proto::get_version(pr_remote));
        proto::set_id(c2, 2);
        proto::set_value(c2, 3);
        proto::set_deleted(pr_remote, true);
        auto file_remote = file_info_t::create(sequencer->next_uuid(), pr_remote, folder_peer).value();
        auto file_local = file_info_t::create(sequencer->next_uuid(), pr_local, folder_my).value();
        file_local->mark_local();
        folder_peer->add_strict(file_remote);
        folder_my->add_strict(file_local);
        auto action = resolve(*file_remote);
        CHECK(action == A::remote_copy);
    }

    SECTION("has newer local file, scanned -> ignore (1)") {
        auto pr_local = pr_remote;
        auto &c2 = proto::get_counters(proto::get_version(pr_local), 0);
        proto::set_value(c2, 3);
        auto file_remote = file_info_t::create(sequencer->next_uuid(), pr_remote, folder_peer).value();
        auto file_local = file_info_t::create(sequencer->next_uuid(), pr_local, folder_my).value();
        file_local->mark_local();
        folder_peer->add_strict(file_remote);
        folder_my->add_strict(file_local);
        auto action = resolve(*file_remote);
        CHECK(action == A::ignore);
    }

    SECTION("has newer local file, scanned -> ignore (2)") {
        auto pr_local = pr_remote;
        auto &c2 = proto::add_counters(proto::get_version(pr_local));
        proto::set_id(c2, 2);
        proto::set_value(c2, 3);
        auto file_remote = file_info_t::create(sequencer->next_uuid(), pr_remote, folder_peer).value();
        auto file_local = file_info_t::create(sequencer->next_uuid(), pr_local, folder_my).value();
        file_local->mark_local();
        folder_peer->add_strict(file_remote);
        folder_my->add_strict(file_local);
        auto action = resolve(*file_remote);
        CHECK(action == A::ignore);
    }

    SECTION("local & remote are both deleted-> ignore") {
        proto::set_deleted(pr_remote, true);
        auto pr_local = pr_remote;
        auto file_remote = file_info_t::create(sequencer->next_uuid(), pr_remote, folder_peer).value();
        auto file_local = file_info_t::create(sequencer->next_uuid(), pr_local, folder_my).value();
        file_local->mark_local();
        folder_peer->add_strict(file_remote);
        folder_my->add_strict(file_local);
        auto action = resolve(*file_remote);
        CHECK(action == A::ignore);
    }

    SECTION("remote is deleted, local is outdated, folder is marked as ignored deletes -> ignore") {
        ((model::folder_data_t *)folder.get())->access<test::to::ignore_delete>() = true;
        auto pr_local = pr_remote;
        auto &c_local = proto::get_counters(proto::get_version(pr_local), 0);
        proto::set_value(c_local, 1);
        proto::set_deleted(pr_remote, true);
        auto file_remote = file_info_t::create(sequencer->next_uuid(), pr_remote, folder_peer).value();
        auto file_local = file_info_t::create(sequencer->next_uuid(), pr_local, folder_my).value();
        file_local->mark_local();
        folder_peer->add_strict(file_remote);
        folder_my->add_strict(file_local);
        auto action = resolve(*file_remote);
        CHECK(action == A::ignore);
    }

    SECTION("conflicts") {
        auto pr_local = pr_remote;

        SECTION("remote deleted, locally modified -> ignore") {
            auto &c2_remote = proto::add_counters(proto::get_version(pr_remote));
            proto::set_id(c2_remote, 2);
            proto::set_value(c2_remote, 3);

            auto &c2_local = proto::add_counters(proto::get_version(pr_local));
            proto::set_id(c2_local, 3);
            proto::set_value(c2_local, 3);

            proto::set_deleted(pr_remote, true);
            auto file_remote = file_info_t::create(sequencer->next_uuid(), pr_remote, folder_peer).value();
            auto file_local = file_info_t::create(sequencer->next_uuid(), pr_local, folder_my).value();
            file_local->mark_local();
            folder_peer->add_strict(file_remote);
            folder_my->add_strict(file_local);
            auto action = resolve(*file_remote);
            CHECK(action == A::ignore);
        }
        SECTION("remote deleted, locally modified -> ignore (2)") {
            auto &v_remote = proto::get_version(pr_remote);
            auto &c2_remote = proto::get_counters(v_remote, 0);
            auto v = proto::get_value(c2_remote) + 1;
            proto::set_value(c2_remote, v);

            auto &c2_local = proto::add_counters(proto::get_version(pr_local));
            proto::set_id(c2_local, 3);
            proto::set_value(c2_local, v);

            proto::set_deleted(pr_remote, true);
            auto file_remote = file_info_t::create(sequencer->next_uuid(), pr_remote, folder_peer).value();
            auto file_local = file_info_t::create(sequencer->next_uuid(), pr_local, folder_my).value();
            file_local->mark_local();
            folder_peer->add_strict(file_remote);
            folder_my->add_strict(file_local);
            auto action = resolve(*file_remote);
            CHECK(action == A::ignore);
        }
        SECTION("locally deleted, remotely modified -> resurrect remote") {
            auto &c2_remote = proto::add_counters(proto::get_version(pr_remote));
            proto::set_id(c2_remote, 2);
            proto::set_value(c2_remote, 3);

            auto &c2_local = proto::add_counters(proto::get_version(pr_local));
            proto::set_id(c2_local, 3);
            proto::set_value(c2_local, 1);

            proto::set_deleted(pr_remote, true);
            auto file_remote = file_info_t::create(sequencer->next_uuid(), pr_remote, folder_peer).value();
            auto file_local = file_info_t::create(sequencer->next_uuid(), pr_local, folder_my).value();
            file_local->mark_local();
            folder_peer->add_strict(file_remote);
            folder_my->add_strict(file_local);
            auto action = resolve(*file_remote);
            CHECK(action == A::remote_copy);
        }
        SECTION("locally mod > remote mod -> ignore(local_win)") {
            auto &c2_remote = proto::add_counters(proto::get_version(pr_remote));
            proto::set_id(c2_remote, 2);
            proto::set_value(c2_remote, 3);
            proto::set_modified_s(pr_remote, 1734680000);

            auto &c2_local = proto::add_counters(proto::get_version(pr_local));
            proto::set_id(c2_local, 3);
            proto::set_value(c2_local, 4);
            proto::set_modified_s(pr_local, 1734690000);

            auto file_remote = file_info_t::create(sequencer->next_uuid(), pr_remote, folder_peer).value();
            auto file_local = file_info_t::create(sequencer->next_uuid(), pr_local, folder_my).value();
            file_local->mark_local();
            folder_peer->add_strict(file_remote);
            folder_my->add_strict(file_local);
            auto action = resolve(*file_remote);
            CHECK(action == A::ignore);
        }
        SECTION("locally mod < remote mod -> remote_win") {
            auto &c2_remote = proto::add_counters(proto::get_version(pr_remote));
            proto::set_id(c2_remote, 2);
            proto::set_value(c2_remote, 4);
            proto::set_modified_s(pr_remote, 1734680000);

            auto &c2_local = proto::add_counters(proto::get_version(pr_local));
            proto::set_id(c2_local, 3);
            proto::set_value(c2_local, 3);
            proto::set_modified_s(pr_local, 1734670000);

            auto file_remote = file_info_t::create(sequencer->next_uuid(), pr_remote, folder_peer).value();
            auto file_local = file_info_t::create(sequencer->next_uuid(), pr_local, folder_my).value();
            file_local->mark_local();
            folder_peer->add_strict(file_remote);
            folder_my->add_strict(file_local);
            auto action = resolve(*file_remote);
            CHECK(action == A::resolve_remote_win);
        }
        SECTION("locally version == remote version -> local_win(ignore), by device") {
            auto &c2_remote = proto::add_counters(proto::get_version(pr_remote));
            proto::set_id(c2_remote, 2);
            proto::set_value(c2_remote, 4);
            proto::set_modified_s(pr_remote, 1734680000);

            auto &c2_local = proto::add_counters(proto::get_version(pr_local));
            proto::set_id(c2_local, 3);
            proto::set_value(c2_local, 4);
            proto::set_modified_s(pr_local, 1734680000);

            auto file_remote = file_info_t::create(sequencer->next_uuid(), pr_remote, folder_peer).value();
            auto file_local = file_info_t::create(sequencer->next_uuid(), pr_local, folder_my).value();
            file_local->mark_local();
            folder_peer->add_strict(file_remote);
            folder_my->add_strict(file_local);
            auto action = resolve(*file_remote);
            CHECK(action == A::ignore);
        }
        SECTION("no recursive conflicts are allowed -> ignore") {
            auto name = std::string("a.txt..sync-conflict-20060102-150405-XBOWT");
            proto::set_name(pr_remote, name);
            proto::set_name(pr_local, name);

            auto &c2_remote = proto::add_counters(proto::get_version(pr_remote));
            proto::set_id(c2_remote, 2);
            proto::set_value(c2_remote, 4);

            auto &c2_local = proto::add_counters(proto::get_version(pr_local));
            proto::set_id(c2_local, 3);
            proto::set_value(c2_local, 4);

            auto file_remote = file_info_t::create(sequencer->next_uuid(), pr_remote, folder_peer).value();
            auto file_local = file_info_t::create(sequencer->next_uuid(), pr_local, folder_my).value();
            file_local->mark_local();
            folder_peer->add_strict(file_remote);
            folder_my->add_strict(file_local);
            auto action = resolve(*file_remote);
            CHECK(action == A::ignore);
        }
        SECTION("locally version < remote version, already resolved -> ignore") {
            auto &c2_remote = proto::add_counters(proto::get_version(pr_remote));
            proto::set_id(c2_remote, 2);
            proto::set_value(c2_remote, 4);

            auto &v_local = proto::get_version(pr_local);
            auto &c2_local = proto::add_counters(v_local);
            proto::set_id(c2_local, 3);
            proto::set_value(c2_local, 3);

            auto file_remote = file_info_t::create(sequencer->next_uuid(), pr_remote, folder_peer).value();
            auto file_local = file_info_t::create(sequencer->next_uuid(), pr_local, folder_my).value();
            file_local->mark_local();
            folder_my->add_strict(file_local);
            folder_peer->add_strict(file_remote);

            proto::clear_counters(v_local);
            auto &c3_local = proto::add_counters(v_local);
            proto::set_id(c3_local, 3);
            proto::set_value(c3_local, 1);

            proto::set_name(pr_local, file_local->make_conflicting_name());
            proto::set_sequence(pr_local, folder_my->get_max_sequence() + 1);
            auto file_resolved = file_info_t::create(sequencer->next_uuid(), pr_local, folder_my).value();
            file_resolved->mark_local();
            REQUIRE(folder_my->add_strict(file_resolved));

            auto action = resolve(*file_remote);
            CHECK(action == A::ignore);
        }
        SECTION("locally version > remote version, already resolved -> ignore") {
            auto &c2_remote = proto::add_counters(proto::get_version(pr_remote));
            proto::set_id(c2_remote, 2);
            proto::set_value(c2_remote, 3);

            auto &v_local = proto::get_version(pr_local);
            auto &c2_local = proto::add_counters(v_local);
            proto::set_id(c2_local, 3);
            proto::set_value(c2_local, 4);

            auto file_remote = file_info_t::create(sequencer->next_uuid(), pr_remote, folder_peer).value();
            auto file_local = file_info_t::create(sequencer->next_uuid(), pr_local, folder_my).value();
            file_local->mark_local();
            folder_my->add_strict(file_local);
            folder_peer->add_strict(file_remote);

            proto::clear_counters(v_local);
            auto &c3_local = proto::add_counters(v_local);
            proto::set_id(c3_local, 3);
            proto::set_value(c3_local, 1);

            proto::set_name(pr_local, file_remote->make_conflicting_name());
            proto::set_sequence(pr_local, folder_my->get_max_sequence() + 1);
            auto file_resolved = file_info_t::create(sequencer->next_uuid(), pr_local, folder_my).value();
            file_resolved->mark_local();
            REQUIRE(folder_my->add_strict(file_resolved));

            auto action = resolve(*file_remote);
            CHECK(action == A::ignore);
        }
    }
}

repo_name: basiliscos/syncspirit

license: mit


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

#include "test-utils.h"
#include "proto/bep_support.h"
#include "model/device_id.h"
#include "utils/error_code.h"
#include "utils/uri.h"
#include "syncspirit-config.h"

using namespace syncspirit;
using namespace syncspirit::test;
using namespace syncspirit::utils;
using namespace syncspirit::proto;
using namespace syncspirit::model;

TEST_CASE("announce", "[bep]") {
    unsigned char buff_raw[] = {
        0x2e, 0xa7, 0xd9, 0x0b, 0x0a, 0x20, 0x51, 0xe0, 0xd7, 0x6a, 0x5d, 0x84, 0x90, 0xb8, 0xe2, 0xfc, 0x4e,
        0x73, 0xcf, 0xaf, 0xff, 0x03, 0xe0, 0xda, 0x9b, 0x29, 0x99, 0x15, 0x4f, 0xa0, 0xaf, 0x69, 0x1e, 0x16,
        0x63, 0x8c, 0xb9, 0x59, 0x12, 0x13, 0x74, 0x63, 0x70, 0x3a, 0x2f, 0x2f, 0x30, 0x2e, 0x30, 0x2e, 0x30,
        0x2e, 0x30, 0x3a, 0x32, 0x32, 0x30, 0x30, 0x30, 0x12, 0x19, 0x74, 0x63, 0x70, 0x3a, 0x2f, 0x2f, 0x31,
        0x39, 0x32, 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x31, 0x30, 0x30, 0x2e, 0x36, 0x3a, 0x32, 0x32, 0x30, 0x30,
        0x30, 0x12, 0x0f, 0x74, 0x63, 0x70, 0x3a, 0x2f, 0x2f, 0x30, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x30, 0x3a,
        0x30, 0x12, 0x14, 0x71, 0x75, 0x69, 0x63, 0x3a, 0x2f, 0x2f, 0x30, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x30,
        0x3a, 0x32, 0x32, 0x30, 0x30, 0x30, 0x12, 0x1a, 0x71, 0x75, 0x69, 0x63, 0x3a, 0x2f, 0x2f, 0x31, 0x39,
        0x32, 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x31, 0x30, 0x30, 0x2e, 0x36, 0x3a, 0x32, 0x32, 0x30, 0x30, 0x30,
        0x18, 0xaa, 0x97, 0xfb, 0xd6, 0xb7, 0x99, 0xde, 0xd8, 0x40};

    constexpr auto buff_sz = sizeof(buff_raw);
    auto buff = utils::bytes_view_t(buff_raw, buff_sz);
    auto r = parse_announce(buff);
    REQUIRE((bool)r);
    auto &v = r.value();
    auto device_id = device_id_t::from_sha256(proto::get_id(v));
    REQUIRE((bool)device_id);
    CHECK(device_id.value().get_value() == "KHQNO2S-5QSILRK-YX4JZZ4-7L77APM-QNVGZJT-EKU7IFI-PNEPBMY-4MXFMQD");
    REQUIRE(proto::get_addresses_size(v) == 5);
    CHECK(proto::get_addresses(v, 1) == "tcp://192.168.100.6:22000");

    SECTION("encode & decode") {
        auto bytes = utils::bytes_t(1500);
        auto out = utils::bytes_view_t(bytes);
        payload::URIs uris{utils::parse("tcp://192.168.100.6:22000")};
        auto device_id_bytes = device_id.value().get_sha256();
        auto sz = make_announce_message(out, device_id_bytes, uris, 1234);
        REQUIRE(sz > 50);

        auto in = utils::bytes_view_t(bytes.data(), sz);
        auto r_ = parse_announce(in);
        REQUIRE((bool)r_);
        auto &v_ = r_.value();
        auto device_id_ = device_id_t::from_sha256(proto::get_id(v_));
        REQUIRE(proto::get_addresses_size(v_) == 1);
        CHECK(proto::get_addresses(v_, 0) == "tcp://192.168.100.6:22000");
        CHECK(proto::get_instance_id(v_) == 1234);
    }
}

TEST_CASE("hello", "[bep]") {
    unsigned char buff_raw[] = {0x2e, 0xa7, 0xd9, 0x0b, 0x00, 0x1f, 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c,
                                0x68, 0x6f, 0x73, 0x74, 0x12, 0x09, 0x73, 0x79, 0x6e, 0x63, 0x74, 0x68, 0x69,
                                0x6e, 0x67, 0x1a, 0x07, 0x76, 0x31, 0x2e, 0x31, 0x31, 0x2e, 0x31};

    constexpr auto buff_sz = sizeof(buff_raw);
    auto buff = utils::bytes_view_t(buff_raw, buff_sz);
    auto r = parse_bep(buff);
    REQUIRE((bool)r);
    auto &v = r.value();
    CHECK(v.consumed == buff_sz);
    auto &msg = std::get<proto::Hello>(v.message);
    CHECK(proto::get_device_name(msg) == "localhost");
    CHECK(proto::get_client_name(msg) == "syncthing");
    CHECK(proto::get_client_version(msg) == "v1.11.1");

    SECTION("make") {
        auto out = make_hello_message("test-device");
        auto buff = utils::bytes_view_t(out.data(), out.size());
        auto r = parse_bep(buff);

        REQUIRE((bool)r);
        auto &v = r.value();
        CHECK(v.consumed == out.size());
        auto &msg = std::get<proto::Hello>(v.message);
        CHECK(proto::get_device_name(msg) == "test-device");
        CHECK(proto::get_client_name(msg) == "syncspirit");
        CHECK(proto::get_client_version(msg) == SYNCSPIRIT_VERSION);
    }
}

TEST_CASE("cluster config", "[bep]") {
    unsigned char buff_raw[] = {
        0x00, 0x02, 0x10, 0x01, 0x00, 0x00, 0x00, 0xb5, 0x00, 0x00, 0x00, 0xc2, 0xa1, 0x0a, 0xbf, 0x01, 0x0a, 0x04,
        0x64, 0x61, 0x74, 0x61, 0x12, 0x06, 0x00, 0xf5, 0x58, 0x82, 0x01, 0x34, 0x0a, 0x20, 0x45, 0x72, 0x75, 0x36,
        0xab, 0xf5, 0xe5, 0xb7, 0xf9, 0xe7, 0x8d, 0xbd, 0xbf, 0xeb, 0x54, 0x72, 0xdd, 0x65, 0x06, 0x27, 0x18, 0xc6,
        0x0b, 0x75, 0x04, 0xbe, 0xdc, 0x1f, 0x14, 0xa4, 0x01, 0xbf, 0x12, 0x07, 0x68, 0x70, 0x2d, 0x6e, 0x6f, 0x74,
        0x65, 0x1a, 0x07, 0x64, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x82, 0x01, 0x42, 0x0a, 0x20, 0x51, 0xe0, 0xd7,
        0x6a, 0x5d, 0x84, 0x90, 0xb8, 0xe2, 0xfc, 0x4e, 0x73, 0xcf, 0xaf, 0xff, 0x03, 0xe0, 0xda, 0x9b, 0x29, 0x99,
        0x15, 0x4f, 0xa0, 0xaf, 0x69, 0x1e, 0x16, 0x63, 0x8c, 0xb9, 0x59, 0x12, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c,
        0x68, 0x6f, 0x73, 0x74, 0x39, 0x00, 0xc1, 0x30, 0x05, 0x40, 0xc3, 0xe4, 0xdd, 0xa2, 0xbe, 0x8a, 0xb2, 0xed,
        0x3d, 0x7c, 0x00, 0xf9, 0x11, 0x89, 0x7f, 0x93, 0xb0, 0x95, 0x15, 0x3f, 0xd3, 0x52, 0xe4, 0xf5, 0xfa, 0xbd,
        0x8c, 0xc5, 0x2d, 0x7a, 0x2d, 0x21, 0x60, 0xf3, 0x76, 0x6e, 0xd7, 0xc5, 0xce, 0x27, 0x5f, 0xed, 0xe5, 0x76,
        0x12, 0x7c, 0x00, 0x50, 0x6e, 0x61, 0x6d, 0x69, 0x63};

    constexpr auto buff_sz = sizeof(buff_raw);
    auto buff = utils::bytes_view_t(buff_raw, buff_sz);
    auto r = parse_bep(buff);
    REQUIRE((bool)r);
    auto &v = r.value();
    CHECK(v.consumed == buff_sz);
    auto &msg = std::get<proto::ClusterConfig>(v.message);
    REQUIRE(proto::get_folders_size(msg) == 1);
    auto &folder = proto::get_folders(msg, 0);
    CHECK(proto::get_label(folder) == "data");
    CHECK(!proto::get_read_only(folder));
    REQUIRE(proto::get_devices_size(folder) == 3);
    auto &d1 = proto::get_devices(folder, 0);
    auto &d2 = proto::get_devices(folder, 1);
    auto &d3 = proto::get_devices(folder, 2);

    auto d1_id = device_id_t::from_sha256(proto::get_id(d1)).value().get_value();
    auto d2_id = device_id_t::from_sha256(proto::get_id(d2)).value().get_value();
    auto d3_id = device_id_t::from_sha256(proto::get_id(d3)).value().get_value();
    CHECK(d1_id == "IVZHKNV-L6XS3PS-6PHRW63-722UOLD-OWKBRHD-DDAW5IV-EX3OB6F-FEAG7QQ");
    CHECK(d2_id == "KHQNO2S-5QSILRK-YX4JZZ4-7L77APM-QNVGZJT-EKU7IFI-PNEPBMY-4MXFMQD");
    CHECK(d3_id == "RF7ZHME-VCU75G2-UXE6X5L-3DGFFVV-5C2ILA6-N3G5V6F-FZYTV73-PFOYJAR");

    CHECK(!proto::get_introducer(d1));
    CHECK(!proto::get_introducer(d2));
    CHECK(!proto::get_introducer(d3));

    CHECK(proto::get_name(d1) == "hp-note");
    CHECK(proto::get_name(d2) == "localhost");
    CHECK(proto::get_name(d3) == "hp-note");

    CHECK(proto::get_addresses_size(d1) == 1);
    CHECK(proto::get_addresses(d1, 0) == "dynamic");

    CHECK(proto::get_addresses_size(d2) == 1);
    CHECK(proto::get_addresses(d2, 0) == "dynamic");

    SECTION("corrupt data a little bit") {
        buff_raw[11] = 0xC0;
        auto buff = utils::bytes_view_t(buff_raw, buff_sz);
        auto r = parse_bep(buff);
        REQUIRE(!r);
        CHECK(r.error() == utils::make_error_code(utils::bep_error_code_t::lz4_decoding));
    }

    SECTION("serialize, round-trip") {
        auto buff = serialize(msg);
        auto r2 = parse_bep(buff);
        REQUIRE(r2);
        auto &v2 = r.value();
        auto &msg2 = std::get<proto::ClusterConfig>(v.message);
        CHECK(v2.consumed == buff_sz);
        auto f_1 = proto::get_folders(msg2, 0);
        auto d_1 = proto::get_devices(f_1, 0);
        auto d_1_id = proto::get_id(d_1);
        CHECK(d_1_id == proto::get_id(d1));
    }

    SECTION("serialize compressed, round-trip") {
        auto buff = serialize(msg, proto::Compression::ALWAYS);
        REQUIRE(buff.size() <= buff_sz + 10);
        auto r2 = parse_bep(buff);
        REQUIRE(r2);
        auto &v2 = r2.value();
        auto &msg2 = std::get<proto::ClusterConfig>(v2.message);
        CHECK(v2.consumed == buff.size());
        auto f_1 = proto::get_folders(msg2, 0);
        auto d_1 = proto::get_devices(f_1, 0);
        auto d_1_id = proto::get_id(d_1);
        CHECK(d_1_id == proto::get_id(d1));
    }
}