diff --git a/CMakeLists.txt b/CMakeLists.txt index e8a68a0d..ebea72f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,7 @@ find_package(OpenSSL REQUIRED) find_package(Protobuf REQUIRED) find_package(ZLIB REQUIRED) -set(SYNCSPIRIT_VERSION "v0.1.0") +set(SYNCSPIRIT_VERSION "v0.2.0") configure_file(misc/syncspirit-config.h.in include/syncspirit-config.h @ONLY) set(Protobuf_IMPORT_DIRS ${syncspirit_SOURCE_DIR}/src) @@ -97,6 +97,7 @@ add_library(syncspirit_lib src/model/diff/modify/local_update.cpp src/model/diff/modify/lock_file.cpp src/model/diff/modify/new_file.cpp + src/model/diff/modify/relay_connect_request.cpp src/model/diff/modify/share_folder.cpp src/model/diff/modify/update_contact.cpp src/model/diff/modify/update_peer.cpp @@ -128,11 +129,13 @@ add_library(syncspirit_lib src/net/dialer_actor.cpp src/net/global_discovery_actor.cpp src/net/http_actor.cpp + src/net/initiator_actor.cpp src/net/local_discovery_actor.cpp src/net/messages.cpp src/net/net_supervisor.cpp src/net/peer_actor.cpp src/net/peer_supervisor.cpp + src/net/relay_actor.cpp src/net/resolver_actor.cpp src/net/names.cpp src/net/ssdp_actor.cpp @@ -140,6 +143,7 @@ add_library(syncspirit_lib src/proto/bep_support.cpp src/proto/discovery_support.cpp src/proto/luhn32.cpp + src/proto/relay_support.cpp src/proto/upnp_support.cpp src/transport/stream.cpp src/transport/http.cpp @@ -164,6 +168,8 @@ set(BUILD_SHARED_LIBS false CACHE BOOL "BUILD_SHARED_LIBS") set(LZ4_BUILD_CLI false CACHE BOOL "LZ4_BUILD_CLI") set(LZ4_BUILD_LEGACY_LZ4C false CACHE BOOL "LZ4_BUILD_LEGACY_LZ4C") set(MDBX_BUILD_TOOLS false CACHE BOOL "MDBX_BUILD_TOOLS") +set(MDBX_ENABLE_TESTS false CACHE BOOL "MDBX_ENABLE_TESTS") +set(MDBX_BUILD_CXX false CACHE BOOL "MDBX_BUILD_CXX") cmake_policy(SET CMP0077 NEW) set(PUGIXML_NO_EXCEPTIONS on) diff --git a/README.md b/README.md index 9a012a0f..d38d3677 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ # syncspirit -sites: [github](https://github.com/basiliscos/syncspirit), [abf](https://github.com/basiliscos/syncspirit) +sites: [github](https://github.com/basiliscos/syncspirit), [abf](https://github.com/basiliscos/syncspirit), +[gitflic](https://gitflic.ru/project/basiliscos/syncspirit) `syncspirit` is a continuous file synchronization program, which synchronizes files between devices. It is build using C++ [rotor](github.com/basiliscos/cpp-rotor) actor framework. It implements [BEP-protocol](https://docs.syncthing.net/specs/bep-v1.html) for files syncrhonization, or, simplistically speaking, it is [syncthing](https://syncthing.net)-compatible syncrhonization -program. +program, which uses [syncthing](https://syncthing.net) infrastructure (for global discovery +and relaying) Despite of being functional `syncspirit` is much less feature-rich then [syncthing](https://syncthing.net) and still is in heavy development. @@ -14,38 +16,37 @@ and still is in heavy development. # status - [x] downloading files from peer devices (aka all folders are receive only) +- [x] downloading files from peer devices (aka all folders are receive only) - [x] [global peer discovery](https://docs.syncthing.net/specs/globaldisco-v3.html) +- [x] [global peer discovery](https://docs.syncthing.net/specs/globaldisco-v3.html) - [x] [local (LAN) peer discovery](https://docs.syncthing.net/specs/localdisco-v4.html) +- [x] [local (LAN) peer discovery](https://docs.syncthing.net/specs/localdisco-v4.html) - [x] upnp & nat passthough +- [x] upnp & nat passthough - [x] certificates generation +- [x] certificates generation +- [x] relay transport # missing features This list is probably incomplete, here are the most important changes - [ ] relay transport +- [ ] full-powered files synchronization (aka send and receive) - [ ] full-powered files synchronization (aka send and receive) +- [ ] conflict resolution - [ ] conflict resolution +- [ ] ignoring files - [ ] ingoring files +- [ ] [QUIC transport](https://en.wikipedia.org/wiki/QUIC) - [ ] [QUIC transport](https://en.wikipedia.org/wiki/QUIC) +- [ ] introducer support - [ ] introducer support +- [ ] outgoing messages compression - [ ] outgoing messages compression +- [ ] [untrusted devices encryption](https://docs.syncthing.net/specs/untrusted.html) - [ ] [untrusted devices encryption](https://docs.syncthing.net/specs/untrusted.html) - - [ ] ... +- [ ] ... # run @@ -63,7 +64,7 @@ the output should be like [![asciicast](https://asciinema.org/a/474217.svg)](https://asciinema.org/a/474217) i.e. it records some peer, adds a folder, then shares the folder with the peer device, connects to -the peer and downloads all files into `/tmp/my_dir/data` . The peer device Currently can be +the peer and downloads all files into `/tmp/my_dir/data` . The peer device currently can be only [syncthing](https://syncthing.net). Then `syncspirit` either exits after 2 minutes of inactivity or when you press `ctrl+c`. The output is successful, because I previousy authorized this device with [syncthing](https://syncthing.net) web interface, and shared the folder with this device @@ -92,7 +93,7 @@ classical desktop and client-server application models. I think, "core" into multiple different user interfaces (GUIs). Currently, there syncspirit has only "daemon-ui", i.e. a simple non-interactive application, -which shows synchronization log, and the only possible just stop it. However, as soon +which shows synchronization log, and the only possibility is just to stop it. However, as soon as the "core" will be complete, there are plans to develop multiple `syncspirit` UIs: [wx-widgets](https://www.wxwidgets.org/), qt, gtk, may be native, may be even native mobile UIs... @@ -117,6 +118,14 @@ after the core completion. # changes +## 0.2.0 (22-May-2022) +- [feature] implement [relay transport](https://docs.syncthing.net/specs/relay-v1.html), +the relay is randombly chosen from the public relays [pool](https://relays.syncthing.net/endpoint) +- [feature] output binary is compressed via [upx](https://upx.github.io) +- [feature] small optimization, use thread less in overall program +- [bugfix] sometimes fs::scan_actor request timeout ocurrs, which is fatal +- [bugfix] global discovery sometimes skipped announcements + ## 0.1.0 (18-Arp-2022) - initial release diff --git a/docs/config.md b/docs/config.md index e3ec157e..3f304c16 100644 --- a/docs/config.md +++ b/docs/config.md @@ -70,6 +70,12 @@ timeout = 5000 # the amount of hasher threads hasher_threads = 3 +[relay] +enabled = true +# where pick the list of relay servers pool +discovery_url = 'https://relays.syncthing.net/endpoint' +rx_buff_size = 1048576 + [upnp] enabled = true # do output of upnp requests diff --git a/lib/rotor b/lib/rotor index 757d207e..0396d3e8 160000 --- a/lib/rotor +++ b/lib/rotor @@ -1 +1 @@ -Subproject commit 757d207ea737024144e171d04d4e59353b58f1d3 +Subproject commit 0396d3e857f1dcf21d82abcedeaffdf3ec6e3b0c diff --git a/misc/ubuntu14.04.toolchain b/misc/ubuntu14.04.toolchain new file mode 100644 index 00000000..d18c676b --- /dev/null +++ b/misc/ubuntu14.04.toolchain @@ -0,0 +1,8 @@ +set(TOOLCHAIN_HOME "/home/b/x-tools/x86_64-ubuntu14.04-linux-gnu") +set(CMAKE_C_COMPILER ${TOOLCHAIN_HOME}/bin/x86_64-ubuntu14.04-linux-gnu-gcc) +set(CMAKE_CXX_COMPILER ${TOOLCHAIN_HOME}/bin/x86_64-ubuntu14.04-linux-gnu-g++) +set(CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++") +set(CMAKE_FIND_ROOT_PATH + ${TOOLCHAIN_HOME}/x86_64-ubuntu14.04-linux-gnu/sysroot/ + /home/b/development/cpp/syncspirit-cross/sysroot +) diff --git a/src/config/main.h b/src/config/main.h index 80d2312e..b096dbbb 100644 --- a/src/config/main.h +++ b/src/config/main.h @@ -11,6 +11,7 @@ #include "global_announce.h" #include "local_announce.h" #include "log.h" +#include "relay.h" #include "upnp.h" #include @@ -31,6 +32,7 @@ struct main_t { dialer_config_t dialer_config; fs_config_t fs_config; db_config_t db_config; + relay_config_t relay_config; std::uint32_t timeout; std::string device_name; diff --git a/src/config/relay.h b/src/config/relay.h new file mode 100644 index 00000000..356065ae --- /dev/null +++ b/src/config/relay.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2022 Ivan Baidakou + +#pragma once + +#include + +namespace syncspirit::config { + +struct relay_config_t { + bool enabled; + std::string discovery_url; + std::uint32_t rx_buff_size; +}; + +} // namespace syncspirit::config diff --git a/src/config/utils.cpp b/src/config/utils.cpp index 94fd3c61..1d334ce4 100644 --- a/src/config/utils.cpp +++ b/src/config/utils.cpp @@ -22,51 +22,6 @@ static const std::string home_path = "~/.config/syncspirit"; namespace syncspirit::config { -bool operator==(const bep_config_t &lhs, const bep_config_t &rhs) noexcept { - return lhs.rx_buff_size == rhs.rx_buff_size && lhs.connect_timeout == rhs.connect_timeout && - lhs.request_timeout == rhs.request_timeout && lhs.tx_timeout == rhs.tx_timeout && - lhs.rx_timeout == rhs.rx_timeout && lhs.blocks_max_requested == rhs.blocks_max_requested; -} - -bool operator==(const dialer_config_t &lhs, const dialer_config_t &rhs) noexcept { - return lhs.enabled == rhs.enabled && lhs.redial_timeout == rhs.redial_timeout; -} - -bool operator==(const fs_config_t &lhs, const fs_config_t &rhs) noexcept { - return lhs.temporally_timeout == rhs.temporally_timeout && lhs.mru_size == rhs.mru_size; -} - -bool operator==(const db_config_t &lhs, const db_config_t &rhs) noexcept { - return lhs.upper_limit == rhs.upper_limit && lhs.uncommited_threshold == rhs.uncommited_threshold; -} - -bool operator==(const global_announce_config_t &lhs, const global_announce_config_t &rhs) noexcept { - return lhs.enabled == rhs.enabled && lhs.announce_url == rhs.announce_url && lhs.device_id == rhs.device_id && - lhs.cert_file == rhs.cert_file && lhs.key_file == rhs.key_file && lhs.rx_buff_size == rhs.rx_buff_size && - lhs.timeout == rhs.timeout && lhs.reannounce_after == rhs.reannounce_after; -} - -bool operator==(const local_announce_config_t &lhs, const local_announce_config_t &rhs) noexcept { - return lhs.enabled == rhs.enabled && lhs.port == rhs.port && lhs.frequency == rhs.frequency; -} - -bool operator==(const log_config_t &lhs, const log_config_t &rhs) noexcept { - return lhs.name == rhs.name && lhs.level == rhs.level && lhs.sinks == rhs.sinks; -} - -bool operator==(const main_t &lhs, const main_t &rhs) noexcept { - return lhs.local_announce_config == rhs.local_announce_config && lhs.upnp_config == rhs.upnp_config && - lhs.global_announce_config == rhs.global_announce_config && lhs.bep_config == rhs.bep_config && - lhs.db_config == rhs.db_config && lhs.timeout == rhs.timeout && lhs.device_name == rhs.device_name && - lhs.config_path == rhs.config_path && lhs.log_configs == rhs.log_configs && - lhs.hasher_threads == rhs.hasher_threads; -} - -bool operator==(const upnp_config_t &lhs, const upnp_config_t &rhs) noexcept { - return lhs.enabled == rhs.enabled && lhs.max_wait == rhs.max_wait && lhs.external_port == rhs.external_port && - lhs.rx_buff_size == rhs.rx_buff_size && lhs.debug == rhs.debug; -} - using device_name_t = outcome::result; static std::string expand_home(const std::string &path, const char *home) { @@ -282,6 +237,30 @@ config_result_t get_config(std::istream &config, const boost::filesystem::path & c.debug = debug.value(); }; + // relay + { + auto t = root_tbl["relay"]; + auto &c = cfg.relay_config; + + auto enabled = t["enabled"].value(); + if (!enabled) { + return "relay/enabled is incorrect or missing"; + } + c.enabled = enabled.value(); + + auto discovery_url = t["discovery_url"].value(); + if (!discovery_url) { + return "upnp/discovery_url is incorrect or missing"; + } + c.discovery_url = discovery_url.value(); + + auto rx_buff_size = t["rx_buff_size"].value(); + if (!rx_buff_size) { + return "relay/rx_buff_size is incorrect or missing"; + } + c.rx_buff_size = rx_buff_size.value(); + }; + // bep { auto t = root_tbl["bep"]; @@ -467,6 +446,11 @@ outcome::result serialize(const main_t cfg, std::ostream &out) noexcept { {"upper_limit", cfg.db_config.upper_limit}, {"uncommited_threshold", cfg.db_config.uncommited_threshold}, }}}, + {"relay", toml::table{{ + {"enabled", cfg.relay_config.enabled}, + {"discovery_url", cfg.relay_config.discovery_url}, + {"rx_buff_size", cfg.relay_config.rx_buff_size}, + }}}, }}; // clang-format on out << tbl; @@ -559,8 +543,12 @@ outcome::result generate_config(const boost::filesystem::path &config_pa 0x400000000, /* upper_limit, 16Gb */ 150, /* uncommited_threshold */ }; + cfg.relay_config = relay_config_t { + true, /* enabled */ + "https://relays.syncthing.net/endpoint", /* discovery url */ + 1024 * 1024, /* rx buff size */ + }; return cfg; } - } diff --git a/src/config/utils.h b/src/config/utils.h index 296b160f..56863da9 100644 --- a/src/config/utils.h +++ b/src/config/utils.h @@ -13,17 +13,6 @@ namespace outcome = boost::outcome_v2; // comparators -SYNCSPIRIT_API bool operator==(const bep_config_t &lhs, const bep_config_t &rhs) noexcept; -SYNCSPIRIT_API bool operator==(const dialer_config_t &lhs, const dialer_config_t &rhs) noexcept; -SYNCSPIRIT_API bool operator==(const fs_config_t &lhs, const fs_config_t &rhs) noexcept; -SYNCSPIRIT_API bool operator==(const db_config_t &lhs, const db_config_t &rhs) noexcept; -SYNCSPIRIT_API bool operator==(const global_announce_config_t &lhs, const global_announce_config_t &rhs) noexcept; -SYNCSPIRIT_API bool operator==(const local_announce_config_t &lhs, const local_announce_config_t &rhs) noexcept; -SYNCSPIRIT_API bool operator==(const main_t &lhs, const main_t &rhs) noexcept; -SYNCSPIRIT_API bool operator==(const upnp_config_t &lhs, const upnp_config_t &rhs) noexcept; - -// misc - using config_result_t = outcome::outcome; SYNCSPIRIT_API config_result_t get_config(std::istream &config, const boost::filesystem::path &config_path); diff --git a/src/constants.cpp b/src/constants.cpp index cc3b0f33..aeaa6f76 100644 --- a/src/constants.cpp +++ b/src/constants.cpp @@ -7,5 +7,6 @@ namespace syncspirit::constants { const char *client_name = "syncspirit"; const char *issuer_name = "syncthing"; const char *protocol_name = "bep/1.0"; +const char *relay_protocol_name = "bep-relay"; const char *client_version = "v0.01"; } // namespace syncspirit::constants diff --git a/src/constants.h b/src/constants.h index 1492555b..33b9a4c5 100644 --- a/src/constants.h +++ b/src/constants.h @@ -11,5 +11,6 @@ static const constexpr std::uint32_t rescan_interval = 3600; extern const char *client_name; extern const char *issuer_name; extern const char *protocol_name; +extern const char *relay_protocol_name; } // namespace syncspirit::constants diff --git a/src/hasher/hasher_actor.cpp b/src/hasher/hasher_actor.cpp index abf9a4c7..19ea914c 100644 --- a/src/hasher/hasher_actor.cpp +++ b/src/hasher/hasher_actor.cpp @@ -2,7 +2,6 @@ // SPDX-FileCopyrightText: 2019-2022 Ivan Baidakou #include "hasher_actor.h" -#include "../net/names.h" #include "../utils/tls.h" #include #include diff --git a/src/hasher/hasher_proxy_actor.cpp b/src/hasher/hasher_proxy_actor.cpp index ea1f2dbf..089f4109 100644 --- a/src/hasher/hasher_proxy_actor.cpp +++ b/src/hasher/hasher_proxy_actor.cpp @@ -2,7 +2,6 @@ // SPDX-FileCopyrightText: 2019-2022 Ivan Baidakou #include "hasher_proxy_actor.h" -#include "../net/names.h" #include "../utils/error_code.h" #include #include diff --git a/src/model/device.cpp b/src/model/device.cpp index 96df44ed..8be342f6 100644 --- a/src/model/device.cpp +++ b/src/model/device.cpp @@ -50,7 +50,8 @@ outcome::result device_t::create(const device_id_t &device_id, std device_t::device_t(const device_id_t &device_id_, std::string_view name_, std::string_view cert_name_) noexcept : id(std::move(device_id_)), name{name_}, compression{proto::Compression::METADATA}, cert_name{cert_name_}, - introducer{false}, auto_accept{false}, paused{false}, skip_introduction_removals{false}, online{false} {} + introducer{false}, auto_accept{false}, paused{false}, + skip_introduction_removals{false}, state{device_state_t::offline} {} void device_t::update(const db::Device &source) noexcept { assign(source); } @@ -82,14 +83,16 @@ std::string device_t::serialize() noexcept { return r.SerializeAsString(); } -void device_t::mark_online(bool value) noexcept { online = value; } +void device_t::update_state(device_state_t new_state) { state = new_state; } std::string_view device_t::get_key() const noexcept { return id.get_key(); } void device_t::assing_uris(const uris_t &uris_) noexcept { uris = uris_; } local_device_t::local_device_t(const device_id_t &device_id, std::string_view name, std::string_view cert_name) noexcept - : device_t(device_id, name, cert_name) {} + : device_t(device_id, name, cert_name) { + state = device_state_t::online; +} std::string_view local_device_t::get_key() const noexcept { return local_device_id.get_key(); } diff --git a/src/model/device.h b/src/model/device.h index c1cbfa83..a1d4e65c 100644 --- a/src/model/device.h +++ b/src/model/device.h @@ -20,6 +20,8 @@ namespace outcome = boost::outcome_v2; struct device_t; using device_ptr_t = intrusive_ptr_t; +enum class device_state_t { offline, dialing, online }; + struct SYNCSPIRIT_API device_t : arc_base_t { using uris_t = std::vector; using name_option_t = std::optional; @@ -35,8 +37,8 @@ struct SYNCSPIRIT_API device_t : arc_base_t { std::string serialize() noexcept; inline bool is_dynamic() const noexcept { return static_uris.empty(); } - void mark_online(bool value) noexcept; - inline bool is_online() const noexcept { return online; } + inline device_state_t get_state() const noexcept { return state; } + void update_state(device_state_t new_state); inline device_id_t &device_id() noexcept { return id; } inline const device_id_t &device_id() const noexcept { return id; } inline std::string_view get_name() const noexcept { return name; } @@ -66,7 +68,7 @@ struct SYNCSPIRIT_API device_t : arc_base_t { bool auto_accept; bool paused; bool skip_introduction_removals; - bool online = false; + device_state_t state = device_state_t::offline; }; struct local_device_t final : device_t { diff --git a/src/model/device_id.cpp b/src/model/device_id.cpp index 0a5a802a..19c26945 100644 --- a/src/model/device_id.cpp +++ b/src/model/device_id.cpp @@ -21,23 +21,20 @@ device_id_t::device_id_t(std::string_view value_, std::string_view sha256_) noex std::copy(sha256_.begin(), sha256_.end(), hash + 1); } -device_id_t::device_id_t(device_id_t &&other) noexcept { - *this = std::move(other); -} +device_id_t::device_id_t(device_id_t &&other) noexcept { *this = std::move(other); } -device_id_t& device_id_t::operator=(device_id_t &&other) noexcept { +device_id_t &device_id_t::operator=(device_id_t &&other) noexcept { value = std::move(other.value); std::copy(other.hash, other.hash + data_length, hash); return *this; } -device_id_t& device_id_t::operator=(const device_id_t &other) noexcept { +device_id_t &device_id_t::operator=(const device_id_t &other) noexcept { value = other.value; std::copy(other.hash, other.hash + data_length, hash); return *this; } - std::string_view device_id_t::get_short() const noexcept { return std::string_view(value.data(), DASH_INT); } std::optional device_id_t::from_string(std::string_view value) noexcept { diff --git a/src/model/device_id.h b/src/model/device_id.h index 1b22557d..84faca25 100644 --- a/src/model/device_id.h +++ b/src/model/device_id.h @@ -33,8 +33,8 @@ struct SYNCSPIRIT_API device_id_t { device_id_t(device_id_t &&other) noexcept; device_id_t(const device_id_t &other) = default; - device_id_t& operator=(device_id_t &&other) noexcept; - device_id_t& operator=(const device_id_t &other) noexcept; + device_id_t &operator=(device_id_t &&other) noexcept; + device_id_t &operator=(const device_id_t &other) noexcept; bool operator==(const device_id_t &other) const noexcept { return get_sha256() == other.get_sha256(); } bool operator!=(const device_id_t &other) const noexcept { return !(*this == other); } diff --git a/src/model/diff/contact_visitor.cpp b/src/model/diff/contact_visitor.cpp index 9d91ba8c..03eede1e 100644 --- a/src/model/diff/contact_visitor.cpp +++ b/src/model/diff/contact_visitor.cpp @@ -12,3 +12,7 @@ auto contact_visitor_t::operator()(const modify::connect_request_t &) noexcept - auto contact_visitor_t::operator()(const modify::update_contact_t &) noexcept -> outcome::result { return outcome::success(); } + +auto contact_visitor_t::operator()(const modify::relay_connect_request_t &) noexcept -> outcome::result { + return outcome::success(); +} diff --git a/src/model/diff/contact_visitor.h b/src/model/diff/contact_visitor.h index 1325cbc8..bb32932b 100644 --- a/src/model/diff/contact_visitor.h +++ b/src/model/diff/contact_visitor.h @@ -11,6 +11,7 @@ namespace syncspirit::model::diff { namespace modify { struct update_contact_t; struct connect_request_t; +struct relay_connect_request_t; } // namespace modify template <> struct SYNCSPIRIT_API generic_visitor_t { @@ -18,6 +19,7 @@ template <> struct SYNCSPIRIT_API generic_visitor_t { virtual outcome::result operator()(const modify::update_contact_t &) noexcept; virtual outcome::result operator()(const modify::connect_request_t &) noexcept; + virtual outcome::result operator()(const modify::relay_connect_request_t &) noexcept; }; using contact_visitor_t = generic_visitor_t; diff --git a/src/model/diff/modify/connect_request.cpp b/src/model/diff/modify/connect_request.cpp index cee80b18..89a9fb5b 100644 --- a/src/model/diff/modify/connect_request.cpp +++ b/src/model/diff/modify/connect_request.cpp @@ -3,7 +3,6 @@ #include "connect_request.h" #include "../contact_visitor.h" -#include "../../cluster.h" using namespace syncspirit::model::diff::modify; diff --git a/src/model/diff/modify/connect_request.h b/src/model/diff/modify/connect_request.h index 65b43680..10993d7f 100644 --- a/src/model/diff/modify/connect_request.h +++ b/src/model/diff/modify/connect_request.h @@ -3,7 +3,7 @@ #pragma once -#include +#include #include #include "../contact_diff.h" #include "model/cluster.h" diff --git a/src/model/diff/modify/relay_connect_request.cpp b/src/model/diff/modify/relay_connect_request.cpp new file mode 100644 index 00000000..03385658 --- /dev/null +++ b/src/model/diff/modify/relay_connect_request.cpp @@ -0,0 +1,17 @@ +#include "relay_connect_request.h" +#include "../contact_visitor.h" + +using namespace syncspirit::model::diff::modify; + +relay_connect_request_t::relay_connect_request_t(model::device_id_t peer_, std::string session_key_, + tcp::endpoint relay_) noexcept + : peer{std::move(peer_)}, session_key{std::move(session_key_)}, relay{std::move(relay_)} {} + +auto relay_connect_request_t::apply_impl(cluster_t &) const noexcept -> outcome::result { + return outcome::success(); +} + +auto relay_connect_request_t::visit(contact_visitor_t &visitor) const noexcept -> outcome::result { + LOG_TRACE(log, "visiting relay_connect_request_t"); + return visitor(*this); +} diff --git a/src/model/diff/modify/relay_connect_request.h b/src/model/diff/modify/relay_connect_request.h new file mode 100644 index 00000000..313c21b6 --- /dev/null +++ b/src/model/diff/modify/relay_connect_request.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2019-2022 Ivan Baidakou + +#pragma once + +#include +#include +#include "../contact_diff.h" +#include "model/cluster.h" +#include "model/device_id.h" + +namespace syncspirit::model::diff::modify { + +namespace asio = boost::asio; +using tcp = asio::ip::tcp; + +struct SYNCSPIRIT_API relay_connect_request_t final : contact_diff_t { + relay_connect_request_t(model::device_id_t peer, std::string session_key, tcp::endpoint relay) noexcept; + + outcome::result apply_impl(cluster_t &) const noexcept override; + outcome::result visit(contact_visitor_t &) const noexcept override; + + model::device_id_t peer; + std::string session_key; + tcp::endpoint relay; +}; + +} // namespace syncspirit::model::diff::modify diff --git a/src/model/diff/modify/update_contact.cpp b/src/model/diff/modify/update_contact.cpp index d35f3d38..f5d95c83 100644 --- a/src/model/diff/modify/update_contact.cpp +++ b/src/model/diff/modify/update_contact.cpp @@ -8,7 +8,7 @@ using namespace syncspirit::model::diff::modify; -update_contact_t::update_contact_t(const model::cluster_t &cluster, const model::device_id_t device_, +update_contact_t::update_contact_t(const model::cluster_t &cluster, const model::device_id_t &device_, const utils::uri_container_t &uris_) noexcept : device{device_}, uris{uris_} { auto &devices = cluster.get_devices(); diff --git a/src/model/diff/modify/update_contact.h b/src/model/diff/modify/update_contact.h index 88cc0750..2a159536 100644 --- a/src/model/diff/modify/update_contact.h +++ b/src/model/diff/modify/update_contact.h @@ -11,7 +11,7 @@ namespace syncspirit::model::diff::modify { struct SYNCSPIRIT_API update_contact_t final : contact_diff_t { using ip_addresses_t = std::vector; - update_contact_t(const model::cluster_t &cluster, const model::device_id_t device, + update_contact_t(const model::cluster_t &cluster, const model::device_id_t &device, const utils::uri_container_t &uris) noexcept; update_contact_t(const model::cluster_t &cluster, const ip_addresses_t &addresses) noexcept; diff --git a/src/model/diff/peer/peer_state.cpp b/src/model/diff/peer/peer_state.cpp index ca20a77b..fc2c5cbc 100644 --- a/src/model/diff/peer/peer_state.cpp +++ b/src/model/diff/peer/peer_state.cpp @@ -8,17 +8,18 @@ using namespace syncspirit::model::diff::peer; peer_state_t::peer_state_t(cluster_t &cluster, std::string_view peer_id_, const r::address_ptr_t &peer_addr_, - bool online_, std::string cert_name_, tcp::endpoint endpoint_, + model::device_state_t state_, std::string cert_name_, tcp::endpoint endpoint_, std::string_view client_name_) noexcept : peer_id{peer_id_}, peer_addr{peer_addr_}, cert_name{cert_name_}, endpoint{endpoint_}, - client_name{client_name_}, online{online_} { + client_name{client_name_}, state{state_} { known = (bool)cluster.get_devices().by_sha256(peer_id); } auto peer_state_t::apply_impl(cluster_t &cluster) const noexcept -> outcome::result { + using State = model::device_state_t; if (known) { auto peer = cluster.get_devices().by_sha256(peer_id); - peer->mark_online(online); + peer->update_state(state); } return outcome::success(); } diff --git a/src/model/diff/peer/peer_state.h b/src/model/diff/peer/peer_state.h index def58553..7a609200 100644 --- a/src/model/diff/peer/peer_state.h +++ b/src/model/diff/peer/peer_state.h @@ -5,6 +5,7 @@ #include #include "../cluster_diff.h" +#include "model/device.h" #include namespace syncspirit::model::diff::peer { @@ -14,8 +15,8 @@ using tcp = boost::asio::ip::tcp; struct SYNCSPIRIT_API peer_state_t final : cluster_diff_t { - peer_state_t(cluster_t &cluster, std::string_view peer_id_, const r::address_ptr_t &peer_addr_, bool online_, - std::string cert_name_ = {}, tcp::endpoint endpoint_ = {}, + peer_state_t(cluster_t &cluster, std::string_view peer_id_, const r::address_ptr_t &peer_addr_, + model::device_state_t state, std::string cert_name_ = {}, tcp::endpoint endpoint_ = {}, std::string_view client_name_ = {}) noexcept; outcome::result apply_impl(cluster_t &) const noexcept override; @@ -26,7 +27,7 @@ struct SYNCSPIRIT_API peer_state_t final : cluster_diff_t { std::string cert_name; tcp::endpoint endpoint; std::string client_name; - bool online; + model::device_state_t state; bool known; }; diff --git a/src/net/cluster_supervisor.cpp b/src/net/cluster_supervisor.cpp index 3f77e96b..3043ef85 100644 --- a/src/net/cluster_supervisor.cpp +++ b/src/net/cluster_supervisor.cpp @@ -61,9 +61,8 @@ void cluster_supervisor_t::on_model_update(model::message::model_update_t &messa auto cluster_supervisor_t::operator()(const model::diff::peer::peer_state_t &diff) noexcept -> outcome::result { if (!cluster->is_tainted() && diff.known) { auto peer = cluster->get_devices().by_sha256(diff.peer_id); - LOG_TRACE(log, "{}, visiting peer_state_t, {} is online: {}", identity, peer->device_id(), - (diff.online ? "yes" : "no")); - if (diff.online) { + LOG_TRACE(log, "{}, visiting peer_state_t, {}, state: {}", identity, peer->device_id(), (int)diff.state); + if (diff.state == model::device_state_t::online) { /* auto addr = */ create_actor() .request_pool(bep_config.rx_buff_size) diff --git a/src/net/db_actor.cpp b/src/net/db_actor.cpp index d918de66..29f9d4b4 100644 --- a/src/net/db_actor.cpp +++ b/src/net/db_actor.cpp @@ -171,6 +171,7 @@ void db_actor_t::shutdown_finish() noexcept { if (r != MDBX_SUCCESS) { LOG_ERROR(log, "{}, open, mbdx close error ({}): {}", identity, r, mdbx_strerror(r)); } + env = nullptr; r::actor_base_t::shutdown_finish(); } diff --git a/src/net/dialer_actor.cpp b/src/net/dialer_actor.cpp index 2f26db92..1b0e5ce5 100644 --- a/src/net/dialer_actor.cpp +++ b/src/net/dialer_actor.cpp @@ -123,13 +123,13 @@ void dialer_actor_t::on_model_update(model::message::model_update_t &msg) noexce } } -auto dialer_actor_t::operator()(const model::diff::peer::peer_state_t &state) noexcept -> outcome::result { - if (state.known) { +auto dialer_actor_t::operator()(const model::diff::peer::peer_state_t &diff) noexcept -> outcome::result { + if (diff.known) { auto &devices = cluster->get_devices(); - auto peer = devices.by_sha256(state.peer_id); + auto peer = devices.by_sha256(diff.peer_id); if (peer) { - if (!state.online) { + if (diff.state == model::device_state_t::offline) { schedule_redial(peer); } else { auto it = redial_map.find(peer); diff --git a/src/net/global_discovery_actor.cpp b/src/net/global_discovery_actor.cpp index 649aeef0..9fe5695d 100644 --- a/src/net/global_discovery_actor.cpp +++ b/src/net/global_discovery_actor.cpp @@ -241,9 +241,8 @@ auto global_discovery_actor_t::operator()(const model::diff::modify::update_cont auto &uris = diff.uris; if (resources->has(resource::timer)) { cancel_timer(*timer_request); - } else { - announce(); } + announce(); } return outcome::success(); } diff --git a/src/net/http_actor.cpp b/src/net/http_actor.cpp index 745ddfcb..d950b6c9 100644 --- a/src/net/http_actor.cpp +++ b/src/net/http_actor.cpp @@ -149,7 +149,7 @@ void http_actor_t::on_resolve(message::resolve_response_t &res) noexcept { auto &payload = queue.front()->payload.request_payload; auto &ssl_ctx = payload->ssl_context; auto sup = static_cast(supervisor); - transport::transport_config_t cfg{std::move(ssl_ctx), payload->url, *sup, {}}; + transport::transport_config_t cfg{std::move(ssl_ctx), payload->url, *sup, {}, true}; transport = transport::initiate_http(cfg); if (!transport) { auto ec = utils::make_error_code(utils::error_code_t::transport_not_available); diff --git a/src/net/http_actor.h b/src/net/http_actor.h index 6b0ad548..27b9e23a 100644 --- a/src/net/http_actor.h +++ b/src/net/http_actor.h @@ -3,8 +3,8 @@ #pragma once -#include "../transport/http.h" -#include "../utils/log.h" +#include "transport/http.h" +#include "utils/log.h" #include "messages.h" #include #include diff --git a/src/net/initiator_actor.cpp b/src/net/initiator_actor.cpp new file mode 100644 index 00000000..786f025e --- /dev/null +++ b/src/net/initiator_actor.cpp @@ -0,0 +1,439 @@ +#include "initiator_actor.h" +#include "constants.h" +#include "names.h" +#include "model/messages.h" +#include "proto/relay_support.h" +#include "utils/error_code.h" +#include "model/diff/peer/peer_state.h" +#include +#include +#include +#include + +using namespace syncspirit::net; + +namespace { +namespace resource { +r::plugin::resource_id_t initializing = 0; +r::plugin::resource_id_t resolving = 1; +r::plugin::resource_id_t connect = 2; +r::plugin::resource_id_t handshake = 3; +r::plugin::resource_id_t read = 4; +r::plugin::resource_id_t write = 5; +} // namespace resource +} // namespace + +static constexpr size_t BUFF_SZ = 256; + +initiator_actor_t::initiator_actor_t(config_t &cfg) + : r::actor_base_t{cfg}, peer_device_id{cfg.peer_device_id}, + relay_key(std::move(cfg.relay_session)), ssl_pair{*cfg.ssl_pair}, + sock(std::move(cfg.sock)), cluster{std::move(cfg.cluster)}, sink(std::move(cfg.sink)), + custom(std::move(cfg.custom)), router{*cfg.router}, alpn(cfg.alpn) { + log = utils::get_logger("net.initator"); + for (auto &uri : cfg.uris) { + if (uri.proto != "tcp" && uri.proto != "relay") { + LOG_DEBUG(log, "{}, unsupported proto '{}' for the url '{}'", identity, uri.proto, uri.full); + } else { + uris.emplace_back(std::move(uri)); + } + } + auto comparator = [](const utils::URI &a, const utils::URI &b) noexcept -> bool { + if (a.proto == b.proto) { + return std::lexicographical_compare(a.full.begin(), a.full.end(), b.full.begin(), b.full.end()); + } + if (a.proto == "relay") { + return true; + } + return false; + }; + std::sort(uris.begin(), uris.end(), comparator); + + if (sock.has_value()) { + role = role_t::passive; + } else { + if (!relay_key.empty()) { + role = role_t::relay_passive; + } else { + role = role_t::active; + } + } +} + +void initiator_actor_t::configure(r::plugin::plugin_base_t &plugin) noexcept { + r::actor_base_t::configure(plugin); + + plugin.with_casted([&](auto &p) { + std::string value; + switch (role) { + case role_t::active: + value = fmt::format("init/active:{}", peer_device_id.get_short()); + break; + case role_t::passive: { + auto ep = sock.value().remote_endpoint(); + value = fmt::format("init/passive:{}", ep); + break; + } + case role_t::relay_passive: { + value = fmt::format("init/relay-passive:{}", peer_device_id.get_short()); + break; + } + } + p.set_identity(value, false); + }); + plugin.with_casted([&](auto &p) { + p.subscribe_actor(&initiator_actor_t::on_resolve); + resources->acquire(resource::initializing); + if (role == role_t::active) { + if (cluster) { + auto diff = model::diff::cluster_diff_ptr_t(); + auto state = model::device_state_t::dialing; + auto sha256 = peer_device_id.get_sha256(); + diff = new model::diff::peer::peer_state_t(*cluster, sha256, nullptr, state); + send(coordinator, std::move(diff)); + } + initiate_active(); + } else if (role == role_t::passive) { + initiate_passive(); + } else if (role == role_t::relay_passive) { + initiate_relay_passive(); + } + }); + plugin.with_casted([&](auto &p) { + p.discover_name(names::resolver, resolver).link(false); + p.discover_name(names::coordinator, coordinator).link(false); + }); +} + +void initiator_actor_t::initiate_active() noexcept { + if (state > r::state_t::OPERATIONAL) { + return; + } + + while (uri_idx < uris.size()) { + auto &uri = uris[uri_idx++]; + if (uri.proto == "tcp") { + initiate_active_tls(uri); + } else if (uri.proto == "relay") { + initiate_active_relay(uri); + } else { + continue; + } + return; + } + + LOG_TRACE(log, "{}, try_next_uri, no way to connect found, shut down", identity); + auto ec = utils::make_error_code(utils::error_code_t::connection_impossible); + do_shutdown(make_error(ec)); +} + +void initiator_actor_t::initiate_passive() noexcept { + if (state > r::state_t::OPERATIONAL) { + return; + } + + auto sup = static_cast(&router); + transport = transport::initiate_tls_passive(*sup, ssl_pair, std::move(sock.value())); + initiate_handshake(); +} + +void initiator_actor_t::initiate_relay_passive() noexcept { + if (state > r::state_t::OPERATIONAL) { + return; + } + + auto sup = static_cast(&router); + auto &uri = uris.at(0); + transport::transport_config_t cfg{{}, uri, *sup, {}, true}; + transport = transport::initiate_stream(cfg); + assert(transport); + resolve(uri); +} + +void initiator_actor_t::on_start() noexcept { + r::actor_base_t::on_start(); + LOG_TRACE(log, "{}, on_start", identity); + std::string proto; + if (active_uri) { + proto = active_uri->proto; + } else { + if (role == role_t::relay_passive) { + proto = "relay"; + } else if (role == role_t::passive) { + proto = "tcp"; + } + } + send(sink, std::move(transport), peer_device_id, remote_endpoint, std::move(proto), + std::move(custom)); + success = true; + do_shutdown(); +} + +void initiator_actor_t::shutdown_start() noexcept { + LOG_TRACE(log, "{}, shutdown_start", identity); + if (resources->has(resource::initializing)) { + resources->release(resource::initializing); + } + bool cancel_transport = resources->has(resource::connect) || resources->has(resource::handshake) || + resources->has(resource::read) || resources->has(resource::write); + if (cancel_transport) { + transport->cancel(); + } + r::actor_base_t::shutdown_start(); +} + +void initiator_actor_t::shutdown_finish() noexcept { + LOG_TRACE(log, "{}, shutdown_finish", identity); + bool notify_offline = role == role_t::active && !success && cluster; + if (notify_offline) { + auto diff = model::diff::cluster_diff_ptr_t(); + auto state = model::device_state_t::offline; + auto sha256 = peer_device_id.get_sha256(); + diff = new model::diff::peer::peer_state_t(*cluster, sha256, nullptr, state); + send(coordinator, std::move(diff)); + } + r::actor_base_t::shutdown_finish(); +} + +void initiator_actor_t::resolve(const utils::URI &uri) noexcept { + LOG_DEBUG(log, "{}, resolving {} (transport = {})", identity, uri.full, (void *)transport.get()); + pt::time_duration resolve_timeout = init_timeout / 2; + auto port = std::to_string(uri.port); + request(resolver, uri.host, port).send(resolve_timeout); + resources->acquire(resource::resolving); +} + +void initiator_actor_t::initiate_active_tls(const utils::URI &uri) noexcept { + LOG_DEBUG(log, "{}, trying '{}' as active tls, alpn = {}", identity, uri.full, alpn); + auto sup = static_cast(&router); + transport = transport::initiate_tls_active(*sup, ssl_pair, peer_device_id, uri, false, alpn); + active_uri = &uri; + relaying = false; + resolve(uri); +} + +void initiator_actor_t::initiate_active_relay(const utils::URI &uri) noexcept { + LOG_TRACE(log, "{}, trying '{}' as active relay", identity, uri.full); + auto relay_device = proto::relay::parse_device(uri); + if (!relay_device) { + LOG_WARN(log, "{}, relay url '{}' does not contains valid device_id", identity, uri.full); + return initiate_active(); + } + active_uri = &uri; + relaying = true; + auto sup = static_cast(&router); + transport = transport::initiate_tls_active(*sup, ssl_pair, relay_device.value(), *active_uri); + resolve(uri); +} + +void initiator_actor_t::on_resolve(message::resolve_response_t &res) noexcept { + LOG_TRACE(log, "{}, on_resolve", identity); + resources->release(resource::resolving); + if (state > r::state_t::OPERATIONAL) { + return; + } + + auto &ee = res.payload.ee; + if (ee) { + LOG_WARN(log, "{}, on_resolve error : {}", identity, ee->message()); + if (role == role_t::active) { + return initiate_active(); + } else { + return do_shutdown(ee); + } + } + + auto &addresses = res.payload.res->results; + transport::connect_fn_t on_connect = [&](auto arg) { this->on_connect(arg); }; + transport::error_fn_t on_error = [&](auto arg) { this->on_io_error(arg, resource::connect); }; + transport->async_connect(addresses, on_connect, on_error); + resources->acquire(resource::connect); +} + +void initiator_actor_t::on_io_error(const sys::error_code &ec, r::plugin::resource_id_t resource) noexcept { + LOG_TRACE(log, "{}, on_io_error: {}", identity, ec.message()); + resources->release(resource); + if (ec != asio::error::operation_aborted) { + LOG_WARN(log, "{}, on_io_error: {}", identity, ec.message()); + } + if (state < r::state_t::SHUTTING_DOWN) { + if (!connected && role == role_t::active) { + initiate_active(); + } else { + connected = false; + LOG_DEBUG(log, "{}, on_io_error, initiating shutdown...", identity); + do_shutdown(make_error(ec)); + } + } +} + +void initiator_actor_t::on_connect(resolve_it_t) noexcept { + LOG_TRACE(log, "{}, on_connect, device_id = {}, transport = {}", identity, peer_device_id.get_short(), + (void *)transport.get()); + resources->release(resource::connect); + // auto do_handshake = role == role_t::active; + auto do_handshake = (role == role_t::active) && + (active_uri && (active_uri->proto == "relay" && relaying) || active_uri->proto == "tcp"); + if (do_handshake) { + initiate_handshake(); + } else { + join_session(); + } +} + +void initiator_actor_t::initiate_handshake() noexcept { + if (state > r::state_t::OPERATIONAL) { + return; + } + + LOG_TRACE(log, "{}, initializing handshake", identity); + connected = true; + transport::handshake_fn_t handshake_fn([&](auto &&...args) { on_handshake(args...); }); + transport::error_fn_t error_fn([&](auto arg) { on_io_error(arg, resource::handshake); }); + resources->acquire(resource::handshake); + transport->async_handshake(handshake_fn, error_fn); +} + +void initiator_actor_t::join_session() noexcept { + if (state > r::state_t::OPERATIONAL) { + return; + } + + transport::error_fn_t read_err_fn([&](auto arg) { on_io_error(arg, resource::read); }); + transport::io_fn_t read_fn = [this](size_t bytes) { on_read_relay(bytes); }; + rx_buff.resize(BUFF_SZ); + transport->async_recv(asio::buffer(rx_buff), read_fn, read_err_fn); + resources->acquire(resource::read); + + LOG_TRACE(log, "{}, join_session", identity); + auto msg = proto::relay::join_session_request_t{std::move(relay_key)}; + proto::relay::serialize(msg, relay_tx); + transport::error_fn_t write_err_fn([&](auto arg) { on_io_error(arg, resource::write); }); + transport::io_fn_t write_fn = [this](size_t bytes) { on_write(bytes); }; + transport->async_send(asio::buffer(relay_tx), write_fn, write_err_fn); + resources->acquire(resource::write); +} + +void initiator_actor_t::on_handshake(bool valid_peer, utils::x509_t &cert, const tcp::endpoint &peer_endpoint, + const model::device_id_t *peer_device) noexcept { + resources->release(resource::handshake); + if (!peer_device) { + LOG_WARN(log, "{}, on_handshake, missing peer device id", identity); + auto ec = utils::make_error_code(utils::error_code_t::missing_device_id); + return do_shutdown(make_error(ec)); + } + + auto cert_name = utils::get_common_name(cert); + if (!cert_name) { + LOG_WARN(log, "{}, on_handshake, can't get certificate name: {}", identity, cert_name.error().message()); + auto ec = utils::make_error_code(utils::error_code_t::missing_cn); + return do_shutdown(make_error(ec)); + } + LOG_TRACE(log, "{}, on_handshake, valid = {}, issued by {}", identity, valid_peer, cert_name.value()); + if (relaying) { + request_relay_connection(); + } else { + peer_device_id = *peer_device; + remote_endpoint = peer_endpoint; + resources->release(resource::initializing); + } +} + +void initiator_actor_t::on_write(size_t bytes) noexcept { + LOG_TRACE(log, "{}, on_write, {} bytes", identity, bytes); + resources->release(resource::write); +} + +void initiator_actor_t::on_read_relay(size_t bytes) noexcept { + LOG_TRACE(log, "{}, on_read_relay, {} bytes", identity, bytes); + resources->release(resource::read); + if (state > r::state_t::OPERATIONAL) { + return; + } + + auto buff = std::string_view(rx_buff.data(), bytes); + auto r = proto::relay::parse(buff); + auto wrapped = std::get_if(&r); + if (!wrapped) { + LOG_WARN(log, "{}, unexpected incoming relay data: {}", identity, spdlog::to_hex(buff.begin(), buff.end())); + auto ec = utils::make_error_code(utils::error_code_t::relay_failure); + return do_shutdown(make_error(ec)); + } + auto reply = std::get_if(&wrapped->message); + if (!reply) { + LOG_WARN(log, "{}, unexpected relay message: {}", identity, spdlog::to_hex(buff.begin(), buff.end())); + auto ec = utils::make_error_code(utils::error_code_t::relay_failure); + return do_shutdown(make_error(ec)); + } + if (reply->code) { + LOG_WARN(log, "{}, relay join failure({}): {}", identity, reply->code, reply->details); + auto ec = utils::make_error_code(utils::error_code_t::relay_failure); + return do_shutdown(make_error(ec)); + } + auto &upgradeable = dynamic_cast(*transport.get()); + auto ssl = transport::ssl_junction_t{peer_device_id, &ssl_pair, true, constants::protocol_name}; + auto active = role == role_t::active; + transport = upgradeable.upgrade(ssl, active); + initiate_handshake(); +} + +void initiator_actor_t::request_relay_connection() noexcept { + if (state > r::state_t::OPERATIONAL) { + return; + } + + transport::error_fn_t read_err_fn([&](auto arg) { on_io_error(arg, resource::read); }); + transport::io_fn_t read_fn = [this](size_t bytes) { on_read_relay_active(bytes); }; + rx_buff.resize(BUFF_SZ); + transport->async_recv(asio::buffer(rx_buff), read_fn, read_err_fn); + resources->acquire(resource::read); + + auto msg = proto::relay::connect_request_t{std::string(peer_device_id.get_sha256())}; + proto::relay::serialize(msg, relay_tx); + transport::error_fn_t write_err_fn([&](auto arg) { on_io_error(arg, resource::write); }); + transport::io_fn_t write_fn = [this](size_t bytes) { on_write(bytes); }; + transport->async_send(asio::buffer(relay_tx), write_fn, write_err_fn); + resources->acquire(resource::write); +} + +void initiator_actor_t::on_read_relay_active(size_t bytes) noexcept { + LOG_TRACE(log, "{}, on_read_relay_active, {} bytes", identity, bytes); + resources->release(resource::read); + auto buff = std::string_view(rx_buff.data(), bytes); + auto r = proto::relay::parse(buff); + auto wrapped = std::get_if(&r); + if (!wrapped) { + LOG_WARN(log, "{}, unexpected incoming relay data: {}", identity, spdlog::to_hex(buff.begin(), buff.end())); + auto ec = utils::make_error_code(utils::error_code_t::relay_failure); + return do_shutdown(make_error(ec)); + } + auto inv = std::get_if(&wrapped->message); + if (!inv) { + auto reply = std::get_if(&wrapped->message); + if (reply) { + LOG_DEBUG(log, "{}, reply (expected session invitation) ({}) : {}", identity, reply->code, reply->details); + } else { + LOG_WARN(log, "{}, unexpected relay message: {}", identity, spdlog::to_hex(buff.begin(), buff.end())); + } + return initiate_active(); + } + auto &peer = inv->from; + if (peer != peer_device_id.get_sha256()) { + LOG_WARN(log, "{}, unexpected peer device: {}", identity, spdlog::to_hex(peer.begin(), peer.end())); + return initiate_active(); + } + auto &addr = inv->address; + relay_key = inv->key; + auto ip = !addr.empty() ? &addr : &active_uri->host; + auto uri_str = fmt::format("tcp://{}:{}", *ip, inv->port); + LOG_DEBUG(log, "{}, going to connect to {}, using key: {}", identity, uri_str, + spdlog::to_hex(relay_key.begin(), relay_key.end())); + auto uri_opt = utils::parse(uri_str); + auto &uri = uri_opt.value(); + relaying = false; + + auto sup = static_cast(&router); + transport::transport_config_t cfg{{}, uri, *sup, {}, true}; + transport = transport::initiate_stream(cfg); + resolve(uri); +} diff --git a/src/net/initiator_actor.h b/src/net/initiator_actor.h new file mode 100644 index 00000000..431f3757 --- /dev/null +++ b/src/net/initiator_actor.h @@ -0,0 +1,163 @@ +#pragma once + +#include +#include "model/cluster.h" +#include "config/bep.h" +#include "messages.h" +#include "utils/log.h" +#include "transport/stream.h" + +namespace syncspirit::net { + +namespace r = rotor; + +struct initiator_actor_config_t : public r::actor_config_t { + model::device_id_t peer_device_id; + utils::uri_container_t uris; + std::string relay_session; + const utils::key_pair_t *ssl_pair; + std::optional sock; + model::cluster_ptr_t cluster; + r::address_ptr_t sink; + r::message_ptr_t custom; + r::supervisor_t *router; + std::string_view alpn; +}; + +template struct initiator_actor_config_builder_t : r::actor_config_builder_t { + using builder_t = typename Actor::template config_builder_t; + using parent_t = r::actor_config_builder_t; + using parent_t::parent_t; + + builder_t &&peer_device_id(const model::device_id_t &value) &&noexcept { + parent_t::config.peer_device_id = value; + return std::move(*static_cast(this)); + } + + builder_t &&uris(const utils::uri_container_t &value) &&noexcept { + parent_t::config.uris = value; + return std::move(*static_cast(this)); + } + + builder_t &&relay_session(const std::string &value) &&noexcept { + parent_t::config.relay_session = value; + return std::move(*static_cast(this)); + } + + builder_t &&ssl_pair(const utils::key_pair_t *value) &&noexcept { + parent_t::config.ssl_pair = value; + return std::move(*static_cast(this)); + } + + builder_t &&sock(tcp_socket_t value) &&noexcept { + parent_t::config.sock = std::move(value); + return std::move(*static_cast(this)); + } + + builder_t &&cluster(const model::cluster_ptr_t &value) &&noexcept { + parent_t::config.cluster = value; + return std::move(*static_cast(this)); + } + + builder_t &&sink(const r::address_ptr_t &value) &&noexcept { + parent_t::config.sink = value; + return std::move(*static_cast(this)); + } + + builder_t &&custom(r::message_ptr_t value) &&noexcept { + parent_t::config.custom = std::move(value); + return std::move(*static_cast(this)); + } + + builder_t &&router(r::supervisor_t &value) &&noexcept { + parent_t::config.router = &value; + return std::move(*static_cast(this)); + } + + builder_t &&alpn(std::string_view value) &&noexcept { + parent_t::config.alpn = value; + return std::move(*static_cast(this)); + } +}; + +struct initiator_actor_t : r::actor_base_t { + using config_t = initiator_actor_config_t; + template using config_builder_t = initiator_actor_config_builder_t; + + initiator_actor_t(config_t &config); + void configure(r::plugin::plugin_base_t &plugin) noexcept override; + void on_start() noexcept override; + void shutdown_start() noexcept override; + void shutdown_finish() noexcept override; + + template auto &access() noexcept; + + private: + using resolve_it_t = payload::address_response_t::resolve_results_t::iterator; + enum class role_t { active, passive, relay_passive }; + + void initiate_passive() noexcept; + void initiate_active() noexcept; + void initiate_relay_passive() noexcept; + void initiate_active_tls(const utils::URI &uri) noexcept; + void initiate_active_relay(const utils::URI &uri) noexcept; + void initiate_handshake() noexcept; + void join_session() noexcept; + void request_relay_connection() noexcept; + void resolve(const utils::URI &uri) noexcept; + + void on_resolve(message::resolve_response_t &res) noexcept; + void on_connect(resolve_it_t) noexcept; + void on_io_error(const sys::error_code &ec, r::plugin::resource_id_t resource) noexcept; + void on_handshake(bool valid_peer, utils::x509_t &peer_cert, const tcp::endpoint &peer_endpoint, + const model::device_id_t *peer_device) noexcept; + void on_read_relay(size_t bytes) noexcept; + void on_read_relay_active(size_t bytes) noexcept; + void on_write(size_t bytes) noexcept; + + model::device_id_t peer_device_id; + utils::uri_container_t uris; + std::string relay_rx; + std::string relay_tx; + std::string relay_key; + const utils::key_pair_t &ssl_pair; + std::optional sock; + model::cluster_ptr_t cluster; + r::address_ptr_t sink; + r::message_ptr_t custom; + r::supervisor_t &router; + std::string_view alpn; + + const utils::URI *active_uri = nullptr; + transport::stream_sp_t transport; + r::address_ptr_t resolver; + r::address_ptr_t coordinator; + size_t uri_idx = 0; + utils::logger_t log; + tcp::endpoint remote_endpoint; + bool connected = false; + role_t role = role_t::passive; + std::string rx_buff; + bool success = false; + bool relaying = false; +}; + +namespace payload { + +struct peer_connected_t { + transport::stream_sp_t transport; + model::device_id_t peer_device_id; + tcp::endpoint remote_endpoint; + std::string proto; + r::message_ptr_t custom; +}; + +} // namespace payload + +namespace message { + +using peer_connected_t = r::message_t; + +} + +} // namespace syncspirit::net diff --git a/src/net/messages.h b/src/net/messages.h index b6fb2612..33cb3140 100644 --- a/src/net/messages.h +++ b/src/net/messages.h @@ -137,6 +137,18 @@ struct SYNCSPIRIT_API block_request_t { ~block_request_t(); }; +struct connect_response_t { + transport::stream_sp_t transport; + tcp::endpoint remote_endpoint; +}; + +struct connect_request_t { + using response_t = connect_response_t; + model::device_id_t device_id; + utils::URI uri; + std::string_view alpn; +}; + } // end of namespace payload namespace message { @@ -164,6 +176,9 @@ using termination_signal_t = r::message_t; using block_request_t = r::request_traits_t::request::message_t; using block_response_t = r::request_traits_t::response::message_t; +using connect_request_t = r::request_traits_t::request::message_t; +using connect_response_t = r::request_traits_t::response::message_t; + } // end of namespace message } // namespace net diff --git a/src/net/names.cpp b/src/net/names.cpp index 079f378e..69df0ce1 100644 --- a/src/net/names.cpp +++ b/src/net/names.cpp @@ -5,8 +5,10 @@ using namespace syncspirit::net; +const char *names::peer_supervisor = "net::peer_supervisor"; const char *names::coordinator = "net::coodinator"; const char *names::resolver = "net::resolver"; const char *names::http10 = "net::http10"; const char *names::http11_gda = "net::http11_gda"; +const char *names::http11_relay = "net::http11_relay"; const char *names::hasher_proxy = "net::hasher_proxy"; diff --git a/src/net/names.h b/src/net/names.h index f62835a8..fb6fd8f5 100644 --- a/src/net/names.h +++ b/src/net/names.h @@ -9,10 +9,12 @@ namespace syncspirit { namespace net { struct SYNCSPIRIT_API names { + static const char *peer_supervisor; static const char *coordinator; static const char *resolver; static const char *http10; static const char *http11_gda; + static const char *http11_relay; static const char *hasher_proxy; }; diff --git a/src/net/net_supervisor.cpp b/src/net/net_supervisor.cpp index c7923de6..5b42131a 100644 --- a/src/net/net_supervisor.cpp +++ b/src/net/net_supervisor.cpp @@ -14,6 +14,7 @@ #include "peer_supervisor.h" #include "dialer_actor.h" #include "db_actor.h" +#include "relay_actor.h" #include "names.h" #include #include @@ -297,6 +298,34 @@ void net_supervisor_t::launch_net() noexcept { .spawn(); } + if (app_config.relay_config.enabled) { + auto timeout = shutdown_timeout * 9 / 10; + auto io_timeout = shutdown_timeout * 8 / 10; + create_actor() + .timeout(timeout) + .request_timeout(io_timeout) + .resolve_timeout(io_timeout) + .registry_name(names::http11_relay) + .keep_alive(true) + .escalate_failure() + .finish(); + + auto factory = [this](r::supervisor_t &sup, const r::address_ptr_t &spawner) -> r::actor_ptr_t { + auto timeout = shutdown_timeout * 9 / 10; + return create_actor() + .timeout(timeout) + .relay_config(app_config.relay_config) + .cluster(cluster) + .spawner_address(spawner) + .finish(); + }; + spawn(factory) + .restart_period(pt::seconds{5}) + .restart_period(r::pt::seconds{10}) + .restart_policy(r::restart_policy_t::fail_only) + .spawn(); + } + auto timeout = shutdown_timeout * 9 / 10; create_actor().cluster(cluster).timeout(timeout).escalate_failure().finish(); create_actor() diff --git a/src/net/peer_actor.cpp b/src/net/peer_actor.cpp index 1c73c1f5..fe9226d4 100644 --- a/src/net/peer_actor.cpp +++ b/src/net/peer_actor.cpp @@ -10,161 +10,56 @@ #include "model/messages.h" #include "model/diff/peer/peer_state.h" #include +#include using namespace syncspirit::net; using namespace syncspirit; namespace { namespace resource { -r::plugin::resource_id_t resolving = 0; -r::plugin::resource_id_t uris = 1; -r::plugin::resource_id_t io_handshake = 2; -r::plugin::resource_id_t io_read = 3; -r::plugin::resource_id_t io_write = 4; -r::plugin::resource_id_t io_connect = 5; -r::plugin::resource_id_t io_timer = 6; -r::plugin::resource_id_t tx_timer = 7; -r::plugin::resource_id_t rx_timer = 8; -r::plugin::resource_id_t finalization = 9; +r::plugin::resource_id_t io_read = 0; +r::plugin::resource_id_t io_write = 1; +r::plugin::resource_id_t io_timer = 2; +r::plugin::resource_id_t tx_timer = 3; +r::plugin::resource_id_t rx_timer = 4; +r::plugin::resource_id_t finalization = 5; } // namespace resource } // namespace peer_actor_t::peer_actor_t(config_t &config) : r::actor_base_t{config}, cluster{config.cluster}, device_name{config.device_name}, bep_config{config.bep_config}, - coordinator{config.coordinator}, peer_device_id{config.peer_device_id}, uris{config.uris}, - sock(std::move(config.sock)), ssl_pair{*config.ssl_pair} { + coordinator{config.coordinator}, peer_device_id{config.peer_device_id}, + transport(std::move(config.transport)), peer_endpoint{config.peer_endpoint}, + peer_proto(std::move(config.peer_proto)) { rx_buff.resize(config.bep_config.rx_buff_size); log = utils::get_logger("net.peer_actor"); } -static std::string generate_id(const model::device_id_t *device_id, const tcp::endpoint *remote) noexcept { - std::string r; - if (device_id) { - r += device_id->get_short(); - } else { - r += "[?]"; - } - r += "/"; - if (remote) { - r += remote->address().to_string(); - r += ":"; - r += std::to_string(remote->port()); - } else { - r += "[?]"; - } - return r; -} - void peer_actor_t::configure(r::plugin::plugin_base_t &plugin) noexcept { r::actor_base_t::configure(plugin); plugin.with_casted([&](auto &p) { - sys::error_code ec; - tcp::endpoint remote; - if (sock) { - remote = sock.value().remote_endpoint(ec); - } - auto value = generate_id((sock ? nullptr : &peer_device_id), (ec ? nullptr : &remote)); - p.set_identity(value, false); + std::stringstream ss; + ss << peer_device_id.get_short() << "/" << peer_proto << "/" << peer_endpoint; + p.set_identity(ss.str(), false); }); plugin.with_casted([&](auto &p) { - p.subscribe_actor(&peer_actor_t::on_resolve); p.subscribe_actor(&peer_actor_t::on_start_reading); p.subscribe_actor(&peer_actor_t::on_termination); p.subscribe_actor(&peer_actor_t::on_block_request); p.subscribe_actor(&peer_actor_t::on_forward); - instantiate_transport(); }); - plugin.with_casted( - [&](auto &p) { p.discover_name(names::resolver, resolver).link(false); }); } -void peer_actor_t::instantiate_transport() noexcept { - if (sock) { - transport::ssl_junction_t ssl{peer_device_id, &ssl_pair, false, ""}; - auto uri = utils::parse("tcp://0.0.0.0/").value(); - auto sup = static_cast(supervisor); - transport::transport_config_t cfg{transport::ssl_option_t(ssl), uri, *sup, std::move(sock)}; - transport = transport::initiate_stream(cfg); - auto timeout = r::pt::milliseconds{bep_config.connect_timeout}; - timer_request = start_timer(timeout, *this, &peer_actor_t::on_timer); - resources->acquire(resource::io_timer); - initiate_handshake(); - } else { - resources->acquire(resource::uris); - try_next_uri(); - } -} +void peer_actor_t::on_start() noexcept { + LOG_TRACE(log, "{}, on_start", identity); -void peer_actor_t::try_next_uri() noexcept { - transport::ssl_junction_t ssl{peer_device_id, &ssl_pair, false, constants::protocol_name}; - while (++uri_idx < (std::int32_t)uris.size()) { - auto &uri = uris[uri_idx]; - auto sup = static_cast(supervisor); - // log->warn("url: {}", uri.full); - transport::transport_config_t cfg{transport::ssl_option_t(ssl), uri, *sup, {}}; - auto result = transport::initiate_stream(cfg); - if (result) { - initiate(std::move(result), uri); - resources->release(resource::uris); - return; - } - } - - LOG_TRACE(log, "{}, try_next_uri, no way to conenct found, shut down", identity); - resources->release(resource::uris); - auto ec = utils::make_error_code(utils::error_code_t::connection_impossible); - do_shutdown(make_error(ec)); -} - -void peer_actor_t::initiate(transport::stream_sp_t tran, const utils::URI &url) noexcept { - transport = std::move(tran); - - LOG_TRACE(log, "{}, try_next_uri, will initate connection with via {} (transport = {})", identity, url.full, - (void *)transport.get()); - pt::time_duration resolve_timeout = init_timeout / 2; - auto port = std::to_string(url.port); - request(resolver, url.host, port).send(resolve_timeout); - resources->acquire(resource::resolving); - return; -} - -void peer_actor_t::on_resolve(message::resolve_response_t &res) noexcept { - resources->release(resource::resolving); - if (state > r::state_t::OPERATIONAL) { - return; - } - - auto &ee = res.payload.ee; - if (ee) { - LOG_WARN(log, "{}, on_resolve error : {}", identity, ee->message()); - resources->acquire(resource::uris); - return try_next_uri(); - } - - auto &addresses = res.payload.res->results; - transport::connect_fn_t on_connect = [&](auto arg) { this->on_connect(arg); }; - transport::error_fn_t on_error = [&](auto arg) { this->on_io_error(arg, resource::io_connect); }; - transport->async_connect(addresses, on_connect, on_error); - resources->acquire(resource::io_connect); - - auto timeout = r::pt::milliseconds{bep_config.connect_timeout}; - timer_request = start_timer(timeout, *this, &peer_actor_t::on_timer); - resources->acquire(resource::io_timer); -} - -void peer_actor_t::on_connect(resolve_it_t) noexcept { - LOG_TRACE(log, "{}, on_connect, device_id = {}", identity, peer_device_id.get_short()); - initiate_handshake(); - resources->release(resource::io_connect); -} + fmt::memory_buffer buff; + proto::make_hello_message(buff, device_name); + push_write(std::move(buff), false); -void peer_actor_t::initiate_handshake() noexcept { - connected = true; - transport::handshake_fn_t handshake_fn([&](auto &&...args) { on_handshake(args...); }); - transport::error_fn_t error_fn([&](auto arg) { on_io_error(arg, resource::io_handshake); }); - transport->async_handshake(handshake_fn, error_fn); - resources->acquire(resource::io_handshake); + read_more(); + read_action = &peer_actor_t::read_hello; } void peer_actor_t::on_io_error(const sys::error_code &ec, rotor::plugin::resource_id_t resource) noexcept { @@ -180,14 +75,7 @@ void peer_actor_t::on_io_error(const sys::error_code &ec, rotor::plugin::resourc } io_error = true; if (state < r::state_t::SHUTTING_DOWN) { - if (!connected) { - resources->acquire(resource::uris); - try_next_uri(); - } else { - connected = false; - LOG_DEBUG(log, "{}, on_io_error, initiating shutdown...", identity); - do_shutdown(make_error(ec)); - } + do_shutdown(make_error(ec)); } } @@ -232,51 +120,6 @@ void peer_actor_t::push_write(fmt::memory_buffer &&buff, bool final) noexcept { } } -void peer_actor_t::on_handshake(bool valid_peer, utils::x509_t &cert, const tcp::endpoint &peer_endpoint, - const model::device_id_t *peer_device) noexcept { - resources->release(resource::io_handshake); - handshaked = true; - if (!peer_device) { - LOG_WARN(log, "{}, on_handshake, missing peer device id", identity); - auto ec = utils::make_error_code(utils::error_code_t::missing_device_id); - return do_shutdown(make_error(ec)); - } - - auto new_id = generate_id(peer_device, &peer_endpoint); - LOG_DEBUG(log, "{} now becomes {}", identity, new_id); - identity = new_id; - - identity = new_id; - auto cert_name = utils::get_common_name(cert); - if (!cert_name) { - LOG_WARN(log, "{}, on_handshake, can't get certificate name: {}", identity, cert_name.error().message()); - auto ec = utils::make_error_code(utils::error_code_t::missing_cn); - return do_shutdown(make_error(ec)); - } - LOG_TRACE(log, "{}, on_handshake, valid = {}, issued by {}", identity, valid_peer, cert_name.value()); - - this->cert_name = cert_name.value(); - this->valid_peer = valid_peer; - this->peer_device_id = *peer_device; - this->peer_endpoint = peer_endpoint; - - fmt::memory_buffer buff; - proto::make_hello_message(buff, device_name); - push_write(std::move(buff), false); - - read_more(); - read_action = &peer_actor_t::read_hello; -} - -#if 0 -void peer_actor_t::on_handshake_error(sys::error_code ec) noexcept { - resources->release(resource::io); - if (ec != asio::error::operation_aborted) { - LOG_WARN(log, "{}, on_handshake_error: {}", identity, ec.message()); - } -} -#endif - void peer_actor_t::read_more() noexcept { if (state > r::state_t::OPERATIONAL) { return; @@ -345,10 +188,8 @@ void peer_actor_t::on_timer(r::request_id_t, bool cancelled) noexcept { LOG_TRACE(log, "{}, on_timer_trigger, cancelled = {}", identity, cancelled); if (!cancelled) { cancel_io(); - if (connected) { - auto ec = r::make_error_code(r::shutdown_code_t::normal); - do_shutdown(make_error(ec)); - } + auto ec = r::make_error_code(r::shutdown_code_t::normal); + do_shutdown(make_error(ec)); } } @@ -367,17 +208,13 @@ void peer_actor_t::shutdown_start() noexcept { send(controller, shutdown_reason); } - if (handshaked) { - fmt::memory_buffer buff; - proto::Close close; - close.set_reason(shutdown_reason->message()); - proto::serialize(buff, close); - tx_queue.clear(); - push_write(std::move(buff), true); - LOG_TRACE(log, "{}, going to send close message", identity); - } else { - cancel_io(); - } + fmt::memory_buffer buff; + proto::Close close; + close.set_reason(shutdown_reason->message()); + proto::serialize(buff, close); + tx_queue.clear(); + push_write(std::move(buff), true); + LOG_TRACE(log, "{}, going to send close message", identity); r::actor_base_t::shutdown_start(); } @@ -393,15 +230,14 @@ void peer_actor_t::shutdown_finish() noexcept { send(controller, shutdown_reason); } r::actor_base_t::shutdown_finish(); - if (handshaked) { - auto sha256 = peer_device_id.get_sha256(); - auto device = cluster->get_devices().by_sha256(sha256); - if (device && device->is_online()) { - auto diff = model::diff::cluster_diff_ptr_t(); - diff = new model::diff::peer::peer_state_t(*cluster, sha256, address, false); - send(coordinator, std::move(diff)); - } - } + auto sha256 = peer_device_id.get_sha256(); + auto device = cluster->get_devices().by_sha256(sha256); + assert(device && device->get_state() == model::device_state_t::online); + + auto diff = model::diff::cluster_diff_ptr_t(); + auto state = model::device_state_t::offline; + diff = new model::diff::peer::peer_state_t(*cluster, sha256, address, state); + send(coordinator, std::move(diff)); } void peer_actor_t::cancel_timer() noexcept { @@ -422,16 +258,6 @@ void peer_actor_t::cancel_io() noexcept { transport->cancel(); return; } - if (resources->has(resource::io_connect)) { - LOG_TRACE(log, "{}, cancelling I/O (connect)", identity); - transport->cancel(); - return; - } - if (resources->has(resource::io_handshake)) { - LOG_TRACE(log, "{}, cancelling I/O (handshake)", identity); - transport->cancel(); - return; - } } void peer_actor_t::on_start_reading(message::start_reading_t &message) noexcept { @@ -493,12 +319,13 @@ void peer_actor_t::read_hello(proto::message::message_t &&msg) noexcept { LOG_TRACE(log, "{}, read_hello, from {} ({} {})", identity, msg->device_name(), msg->client_name(), msg->client_version()); auto peer = cluster->get_devices().by_sha256(peer_device_id.get_sha256()); - if (peer && peer->is_online()) { + if (peer && peer->get_state() == model::device_state_t::online) { auto ec = utils::make_error_code(utils::error_code_t::already_connected); return do_shutdown(make_error(ec)); } auto diff = cluster_diff_ptr_t(); - diff = new peer::peer_state_t(*cluster, peer_device_id.get_sha256(), get_address(), true, cert_name, + auto state = model::device_state_t::online; + diff = new peer::peer_state_t(*cluster, peer_device_id.get_sha256(), get_address(), state, cert_name, peer_endpoint, msg->client_name()); send(coordinator, std::move(diff)); } else { diff --git a/src/net/peer_actor.h b/src/net/peer_actor.h index a4893915..b3404111 100644 --- a/src/net/peer_actor.h +++ b/src/net/peer_actor.h @@ -20,13 +20,12 @@ namespace net { struct peer_actor_config_t : public r::actor_config_t { std::string_view device_name; model::device_id_t peer_device_id; - utils::uri_container_t uris; - std::optional sock; - std::optional peer_identity; - const utils::key_pair_t *ssl_pair; config::bep_config_t bep_config; + transport::stream_sp_t transport; r::address_ptr_t coordinator; model::cluster_ptr_t cluster; + tcp::endpoint peer_endpoint; + std::string peer_proto; }; template struct peer_actor_config_builder_t : r::actor_config_builder_t { @@ -39,16 +38,6 @@ template struct peer_actor_config_builder_t : r::actor_config_b return std::move(*static_cast(this)); } - builder_t &&uris(const utils::uri_container_t &value) &&noexcept { - parent_t::config.uris = value; - return std::move(*static_cast(this)); - } - - builder_t &&ssl_pair(const utils::key_pair_t *value) &&noexcept { - parent_t::config.ssl_pair = value; - return std::move(*static_cast(this)); - } - builder_t &&device_name(std::string_view value) &&noexcept { parent_t::config.device_name = value; return std::move(*static_cast(this)); @@ -64,13 +53,23 @@ template struct peer_actor_config_builder_t : r::actor_config_b return std::move(*static_cast(this)); } - builder_t &&sock(std::optional &&value) &&noexcept { - parent_t::config.sock = std::move(value); + builder_t &&cluster(const model::cluster_ptr_t &value) &&noexcept { + parent_t::config.cluster = value; + return std::move(*static_cast(this)); + } + + builder_t &&transport(transport::stream_sp_t value) &&noexcept { + parent_t::config.transport = std::move(value); return std::move(*static_cast(this)); } - builder_t &&cluster(const model::cluster_ptr_t &value) &&noexcept { - parent_t::config.cluster = value; + builder_t &&peer_endpoint(const tcp::endpoint &value) &&noexcept { + parent_t::config.peer_endpoint = value; + return std::move(*static_cast(this)); + } + + builder_t &&peer_proto(std::string value) &&noexcept { + parent_t::config.peer_proto = value; return std::move(*static_cast(this)); } }; @@ -81,6 +80,7 @@ struct SYNCSPIRIT_API peer_actor_t : public r::actor_base_t { peer_actor_t(config_t &config); void configure(r::plugin::plugin_base_t &plugin) noexcept override; + void on_start() noexcept override; void shutdown_start() noexcept override; void shutdown_finish() noexcept override; @@ -101,7 +101,6 @@ struct SYNCSPIRIT_API peer_actor_t : public r::actor_base_t { }; }; - using resolve_it_t = payload::address_response_t::resolve_results_t::iterator; using tx_item_t = model::intrusive_ptr_t; using tx_message_t = confidential::message::tx_item_t; using tx_queue_t = std::list; @@ -109,29 +108,20 @@ struct SYNCSPIRIT_API peer_actor_t : public r::actor_base_t { using block_request_ptr_t = r::intrusive_ptr_t; using block_requests_t = std::list; - void on_resolve(message::resolve_response_t &res) noexcept; void on_start_reading(message::start_reading_t &) noexcept; void on_termination(message::termination_signal_t &) noexcept; void on_block_request(message::block_request_t &) noexcept; void on_forward(message::forwarded_message_t &message) noexcept; - void on_connect(resolve_it_t) noexcept; void on_io_error(const sys::error_code &ec, r::plugin::resource_id_t resource) noexcept; void on_write(std::size_t bytes) noexcept; void on_read(std::size_t bytes) noexcept; - void try_next_uri() noexcept; - void initiate(transport::stream_sp_t tran, const utils::URI &url) noexcept; - void on_handshake(bool valid_peer, utils::x509_t &peer_cert, const tcp::endpoint &peer_endpoint, - const model::device_id_t *peer_device) noexcept; - void on_handshake_error(sys::error_code ec) noexcept; void on_timer(r::request_id_t, bool cancelled) noexcept; void read_more() noexcept; void push_write(fmt::memory_buffer &&buff, bool final) noexcept; void process_tx_queue() noexcept; void cancel_timer() noexcept; void cancel_io() noexcept; - void instantiate_transport() noexcept; - void initiate_handshake() noexcept; void on_tx_timeout(r::request_id_t, bool cancelled) noexcept; void on_rx_timeout(r::request_id_t, bool cancelled) noexcept; @@ -150,12 +140,7 @@ struct SYNCSPIRIT_API peer_actor_t : public r::actor_base_t { config::bep_config_t bep_config; r::address_ptr_t coordinator; model::device_id_t peer_device_id; - utils::uri_container_t uris; - std::optional sock; - const utils::key_pair_t &ssl_pair; - r::address_ptr_t resolver; transport::stream_sp_t transport; - std::int32_t uri_idx = -1; std::optional timer_request; std::optional tx_timer_request; std::optional rx_timer_request; @@ -163,13 +148,11 @@ struct SYNCSPIRIT_API peer_actor_t : public r::actor_base_t { tx_item_t tx_item; fmt::memory_buffer rx_buff; std::size_t rx_idx = 0; - bool connected = false; - bool handshaked = false; - bool valid_peer = false; bool finished = false; bool io_error = false; std::string cert_name; tcp::endpoint peer_endpoint; + std::string peer_proto; read_action_t read_action; r::address_ptr_t controller; block_requests_t block_requests; diff --git a/src/net/peer_supervisor.cpp b/src/net/peer_supervisor.cpp index 949b6fd8..e6f4d23a 100644 --- a/src/net/peer_supervisor.cpp +++ b/src/net/peer_supervisor.cpp @@ -3,10 +3,12 @@ #include "peer_supervisor.h" #include "peer_actor.h" +#include "initiator_actor.h" #include "names.h" #include "utils/error_code.h" #include "model/diff/peer/peer_state.h" #include "model/diff/modify/connect_request.h" +#include "model/diff/modify/relay_connect_request.h" #include "model/diff/modify/update_contact.h" #include "model/misc/error_code.h" @@ -22,14 +24,21 @@ peer_supervisor_t::peer_supervisor_t(peer_supervisor_config_t &cfg) void peer_supervisor_t::configure(r::plugin::plugin_base_t &plugin) noexcept { r::actor_base_t::configure(plugin); - plugin.with_casted([&](auto &p) { p.set_identity("peer_supervisor", false); }); + plugin.with_casted([&](auto &p) { + p.set_identity("peer_supervisor", false); + addr_unknown = p.create_address(); + }); plugin.with_casted([&](auto &p) { + p.register_name(names::peer_supervisor, get_address()); p.discover_name(names::coordinator, coordinator, true).link(false).callback([&](auto phase, auto &ee) { if (!ee && phase == r::plugin::registry_plugin_t::phase_t::linking) { auto p = get_plugin(r::plugin::starter_plugin_t::class_identity); auto plugin = static_cast(p); plugin->subscribe_actor(&peer_supervisor_t::on_model_update, coordinator); plugin->subscribe_actor(&peer_supervisor_t::on_contact_update, coordinator); + plugin->subscribe_actor(&peer_supervisor_t::on_peer_ready); + plugin->subscribe_actor(&peer_supervisor_t::on_connect); + plugin->subscribe_actor(&peer_supervisor_t::on_connected, addr_unknown); } }); }); @@ -68,9 +77,55 @@ void peer_supervisor_t::on_contact_update(model::message::contact_update_t &msg) } } -auto peer_supervisor_t::operator()(const model::diff::peer::peer_state_t &state) noexcept -> outcome::result { - auto &peer_addr = state.peer_addr; - if (!state.known && state.online) { +void peer_supervisor_t::on_peer_ready(message::peer_connected_t &msg) noexcept { + LOG_TRACE(log, "{}, on_peer_ready", identity); + auto timeout = r::pt::milliseconds{bep_config.connect_timeout}; + auto &p = msg.payload; + auto &d = p.peer_device_id; + auto peer = cluster->get_devices().by_sha256(d.get_sha256()); + if (peer->get_state() == model::device_state_t::online) { + LOG_DEBUG(log, "{}, peer '{}' is already online, ignoring request", identity, d.get_short()); + return; + } + create_actor() + .transport(std::move(p.transport)) + .peer_device_id(d) + .device_name(device_name) + .bep_config(bep_config) + .coordinator(coordinator) + .peer_endpoint(p.remote_endpoint) + .peer_proto(p.proto) + .timeout(timeout) + .cluster(cluster) + .finish(); +} + +void peer_supervisor_t::on_connected(message::peer_connected_t &msg) noexcept { + LOG_TRACE(log, "{}, on_connected", identity); + auto &p = msg.payload; + auto req = static_cast(p.custom.get()); + reply_to(*req, std::move(p.transport), std::move(p.remote_endpoint)); +} + +void peer_supervisor_t::on_connect(message::connect_request_t &msg) noexcept { + auto &p = msg.payload.request_payload; + auto connect_timeout = r::pt::milliseconds{bep_config.connect_timeout}; + create_actor() + .ssl_pair(&ssl_pair) + .peer_device_id(p.device_id) + .uris({p.uri}) + .custom(&msg) + .router(*locality_leader) + .sink(addr_unknown) + .alpn(p.alpn) + .init_timeout(connect_timeout * 2) + .shutdown_timeout(connect_timeout) + .finish(); +} + +auto peer_supervisor_t::operator()(const model::diff::peer::peer_state_t &diff) noexcept -> outcome::result { + auto &peer_addr = diff.peer_addr; + if (!diff.known && diff.state == model::device_state_t::online) { auto ec = model::make_error_code(model::error_code_t::unknown_device); auto ee = make_error(ec); send(address, peer_addr, ee); @@ -86,16 +141,39 @@ auto peer_supervisor_t::operator()(const model::diff::modify::connect_request_t diff.sock.reset(); auto timeout = r::pt::milliseconds{bep_config.connect_timeout}; - auto peer_addr = create_actor() - .ssl_pair(&ssl_pair) - .device_name(device_name) - .bep_config(bep_config) - .coordinator(coordinator) - .timeout(timeout) - .sock(std::optional(std::move(sock))) - .cluster(cluster) - .finish() - ->get_address(); + create_actor() + .cluster(cluster) + .router(*locality_leader) + .sink(address) + .ssl_pair(&ssl_pair) + .sock(std::move(sock)) + .timeout(timeout) + .finish(); + return outcome::success(); +} + +auto peer_supervisor_t::operator()(const model::diff::modify::relay_connect_request_t &diff) noexcept + -> outcome::result { + + auto peer = cluster->get_devices().by_sha256(diff.peer.get_sha256()); + if (peer->get_state() == model::device_state_t::offline) { + LOG_DEBUG(log, "{} initiating relay connection with {}", identity, peer->device_id()); + auto timeout = r::pt::milliseconds{bep_config.connect_timeout}; + auto uri_str = fmt::format("tcp://{}", diff.relay); + auto uri = utils::parse(uri_str); + create_actor() + .cluster(cluster) + .router(*locality_leader) + .sink(address) + .ssl_pair(&ssl_pair) + .peer_device_id(diff.peer) + .uris({std::move(uri.value())}) + .relay_session(diff.session_key) + .timeout(timeout) + .finish(); + } else { + LOG_DEBUG(log, "{}, peer '{}' is not offline, dropping relay connection request", identity, peer->device_id()); + } return outcome::success(); } @@ -104,22 +182,22 @@ auto peer_supervisor_t::operator()(const model::diff::modify::update_contact_t & if (!diff.self && diff.known) { auto &devices = cluster->get_devices(); auto peer = devices.by_sha256(diff.device.get_sha256()); - if (!peer->is_online()) { + if (peer->get_state() == model::device_state_t::offline) { auto &uris = diff.uris; auto connect_timeout = r::pt::milliseconds{bep_config.connect_timeout}; LOG_DEBUG(log, "{} initiating connection with {}", identity, peer->device_id()); - auto peer_addr = create_actor() - .ssl_pair(&ssl_pair) - .device_name(device_name) - .bep_config(bep_config) - .coordinator(coordinator) - .init_timeout(connect_timeout * (uris.size() + 1)) - .shutdown_timeout(connect_timeout) - .peer_device_id(diff.device) - .uris(uris) - .cluster(cluster) - .finish() - ->get_address(); + create_actor() + .router(*locality_leader) + .sink(address) + .ssl_pair(&ssl_pair) + .peer_device_id(diff.device) + .uris(uris) + .cluster(cluster) + .init_timeout(connect_timeout * (uris.size() + 1)) + .shutdown_timeout(connect_timeout) + .finish(); + } else { + LOG_DEBUG(log, "{}, peer '{}' is not offline, dropping connection", identity, peer->device_id()); } } return outcome::success(); diff --git a/src/net/peer_supervisor.h b/src/net/peer_supervisor.h index c3467d23..dc6ec9cc 100644 --- a/src/net/peer_supervisor.h +++ b/src/net/peer_supervisor.h @@ -16,6 +16,14 @@ namespace syncspirit { namespace net { +namespace payload { +struct peer_connected_t; +} + +namespace message { +using peer_connected_t = r::message_t; +} + namespace outcome = boost::outcome_v2; struct peer_supervisor_config_t : ra::supervisor_config_asio_t { @@ -65,16 +73,21 @@ struct SYNCSPIRIT_API peer_supervisor_t : public ra::supervisor_asio_t, void on_start() noexcept override; private: + void on_connect(message::connect_request_t &) noexcept; void on_model_update(model::message::model_update_t &) noexcept; void on_contact_update(model::message::contact_update_t &) noexcept; + void on_peer_ready(message::peer_connected_t &) noexcept; + void on_connected(message::peer_connected_t &) noexcept; outcome::result operator()(const model::diff::peer::peer_state_t &) noexcept override; outcome::result operator()(const model::diff::modify::update_contact_t &) noexcept override; outcome::result operator()(const model::diff::modify::connect_request_t &) noexcept override; + outcome::result operator()(const model::diff::modify::relay_connect_request_t &) noexcept override; model::cluster_ptr_t cluster; utils::logger_t log; r::address_ptr_t coordinator; + r::address_ptr_t addr_unknown; std::string_view device_name; const utils::key_pair_t &ssl_pair; config::bep_config_t bep_config; diff --git a/src/net/relay_actor.cpp b/src/net/relay_actor.cpp new file mode 100644 index 00000000..27c8bc78 --- /dev/null +++ b/src/net/relay_actor.cpp @@ -0,0 +1,456 @@ +#include "relay_actor.h" +#include "names.h" +#include "constants.h" +#include "utils/error_code.h" +#include "utils/beast_support.h" +#include "model/messages.h" +#include "model/diff/modify/update_contact.h" +#include "model/diff/modify/relay_connect_request.h" +#include +#include "messages.h" +#include +#include + +using namespace syncspirit::net; + +static const constexpr size_t BUFF_SZ = 1500; +template inline constexpr bool always_false_v = false; + +namespace { +namespace resource { +r::plugin::resource_id_t init = 0; +r::plugin::resource_id_t http = 1; +r::plugin::resource_id_t io_read = 2; +r::plugin::resource_id_t io_write = 3; +} // namespace resource +} // namespace + +relay_actor_t::relay_actor_t(config_t &config) noexcept + : r::actor_base_t(config), cluster{std::move(config.cluster)}, config{config.config} { + log = utils::get_logger("net.relay"); + http_rx_buff = std::make_shared(); +} + +void relay_actor_t::configure(r::plugin::plugin_base_t &plugin) noexcept { + r::actor_base_t::configure(plugin); + plugin.with_casted([&](auto &p) { p.set_identity("relay", false); }); + plugin.with_casted([&](auto &p) { + p.discover_name(names::http11_relay, http_client, true).link(true); + p.discover_name(names::coordinator, coordinator, false).link(false); + p.discover_name(names::peer_supervisor, peer_supervisor, true).link(false); + }); + plugin.with_casted([&](auto &p) { + p.subscribe_actor(&relay_actor_t::on_list); + p.subscribe_actor(&relay_actor_t::on_connect); + resources->acquire(resource::init); + request_relay_list(); + }); +} + +void relay_actor_t::shutdown_start() noexcept { + LOG_TRACE(log, "{}, shutdown_start", identity); + r::actor_base_t::shutdown_start(); + + if (resources->has(resource::init)) { + resources->release(resource::init); + } + if (resources->has(resource::http)) { + send(http_client, *http_request, get_address()); + } + if (resources->has(resource::io_read) || resources->has(resource::io_write)) { + master->cancel(); + } + if (rx_state) { + auto self = cluster->get_device(); + utils::uri_container_t uris; + for (auto &uri : self->get_uris()) { + if (uri.proto != "relay") { + uris.emplace_back(uri); + } + } + using namespace model::diff; + auto diff = model::diff::contact_diff_ptr_t{}; + diff = new modify::update_contact_t(*cluster, self->device_id(), uris); + send(coordinator, std::move(diff), this); + } +} + +void relay_actor_t::on_start() noexcept { + LOG_TRACE(log, "{}, on_start", identity); + r::actor_base_t::on_start(); + connect_to_relay(); +} + +void relay_actor_t::connect_to_relay() noexcept { + size_t attempts = 0; + while (++attempts < 10) { + relay_index = rand() % relays.size(); + auto relay = relays[relay_index]; + if (!relay) { + continue; + } + auto &l = relay->location; + auto &u = relay->uri; + LOG_INFO(log, "{}, chosen relay({}) {}:{}, city: {}, country: {}, continent: {}", identity, relay_index, + relay->uri.host, relay->uri.port, l.city, l.country, l.continent); + + auto uri = utils::parse(fmt::format("tcp://{}:{}", u.host, u.port)).value(); + request(peer_supervisor, relay->device_id, std::move(uri), + constants::relay_protocol_name) + .send(init_timeout); + return; + } + if (attempts >= 10) { + auto ec = utils::make_error_code(utils::error_code_t::relay_failure); + do_shutdown(make_error(ec)); + } +} + +void relay_actor_t::request_relay_list() noexcept { + auto timeout = init_timeout * 9 / 10; + auto url_opt = utils::parse(config.discovery_url); + if (!url_opt) { + LOG_WARN(log, "{}, malformed discovery url '{}'", identity, config.discovery_url); + auto ec = utils::make_error_code(utils::error_code_t::malformed_url); + return do_shutdown(make_error(ec)); + } + + auto uri = url_opt.value(); + assert(uri.proto == "https"); + http::request req; + req.method(http::verb::get); + req.version(10); + req.target(uri.relative()); + req.set(http::field::host, uri.host); + req.set(http::field::connection, "close"); + + fmt::memory_buffer tx_buff; + auto r = utils::serialize(req, tx_buff); + if (!r) { + auto &ec = r.assume_error(); + LOG_WARN(log, "{}, cannot serialize request: {}'", identity, r.assume_error().message()); + return do_shutdown(make_error(ec)); + } + resources->acquire(resource::http); + transport::ssl_junction_t ssl{ + model::device_id_t{}, + nullptr, + true, + }; + http_request = request(http_client, uri, std::move(tx_buff), http_rx_buff, + config.rx_buff_size, std::move(ssl)) + .send(timeout); +} + +void relay_actor_t::on_list(message::http_response_t &msg) noexcept { + LOG_TRACE(log, "{}, on_list", identity); + resources->release(resource::http); + + auto &ee = msg.payload.ee; + if (ee) { + LOG_WARN(log, "{}, get public relays failed: {}", identity, ee->message()); + auto inner = utils::make_error_code(utils::error_code_t::cannot_get_public_relays); + return do_shutdown(make_error(inner, ee)); + } + if (state > r::state_t::OPERATIONAL) { + return; + } + + auto &body = msg.payload.res->response.body(); + auto result = proto::relay::parse_endpoint(body); + if (!result) { + auto &ec = result.assume_error(); + LOG_WARN(log, "{}, cannot parse relays: {}", identity, ec.message()); + return do_shutdown(make_error(ec)); + } + auto &list = result.assume_value(); + if (list.empty()) { + LOG_WARN(log, "{}, empty list of public relays", identity); + auto ec = utils::make_error_code(utils::error_code_t::cannot_get_public_relays); + return do_shutdown(make_error(ec)); + } + relays = std::move(list); + http_rx_buff.reset(); + resources->release(resource::init); +} + +void relay_actor_t::read_master() noexcept { + if (state > r::state_t::OPERATIONAL) { + return; + } + transport::io_fn_t on_read = [&](auto arg) { this->on_read(arg); }; + transport::error_fn_t on_error = [&](auto arg) { this->on_io_error(arg, resource::io_read); }; + resources->acquire(resource::io_read); + auto buff = asio::buffer(rx_buff.data() + rx_idx, rx_buff.size() - rx_idx); + LOG_TRACE(log, "{}, read_master, sz = {}", identity, buff.size()); + master->async_recv(buff, on_read, on_error); +} + +void relay_actor_t::push_master(std::string data) noexcept { + tx_queue.emplace_back(tx_item_t(new std::string(std::move(data)))); + if (!resources->has(resource::io_write)) { + write_master(); + } +} + +void relay_actor_t::write_master() noexcept { + if (state > r::state_t::OPERATIONAL) { + return; + } + auto &item = tx_queue.front(); + transport::io_fn_t on_write = [&](auto sz) { + LOG_TRACE(log, "{}, write {} bytes", identity, sz); + resources->release(resource::io_write); + tx_queue.pop_front(); + if (tx_timer) { + cancel_timer(*tx_timer); + } + if (!tx_queue.empty()) { + write_master(); + } + }; + transport::error_fn_t on_error = [&](auto arg) { this->on_io_error(arg, resource::io_write); }; + auto buff = asio::buffer(*item); + master->async_send(buff, on_write, on_error); + resources->acquire(resource::io_write); + respawn_tx_timer(); +} + +void relay_actor_t::on_connect(message::connect_response_t &res) noexcept { + if (state > r::state_t::OPERATIONAL) { + return; + } + LOG_TRACE(log, "{}, on_connect", identity); + auto &ee = res.payload.ee; + auto &r = relays[relay_index]; + if (ee) { + LOG_TRACE(log, "{}, failed to connect to relay {}: {}", identity, r->device_id.get_short(), ee->message()); + relays[relay_index].reset(); + return connect_to_relay(); + } + LOG_DEBUG(log, "{}, connected to relay {}", identity, r->device_id.get_short()); + auto &p = res.payload.res; + master = std::move(p.transport); + master_endpoint = std::move(p.remote_endpoint); + rx_buff.resize(BUFF_SZ); + read_master(); + rx_state |= rx_state_t::response; + respawn_rx_timer(); + + auto tx = std::string{}; + proto::relay::serialize(proto::relay::join_relay_request_t{}, tx); + push_master(tx); +} + +void relay_actor_t::on_io_error(const sys::error_code &ec, rotor::plugin::resource_id_t resource) noexcept { + LOG_TRACE(log, "{}, on_io_error: {}", identity, ec.message()); + resources->release(resource); + if (ec != asio::error::operation_aborted) { + LOG_WARN(log, "{}, on_io_error: {}", identity, ec.message()); + if (state < r::state_t::SHUTTING_DOWN) { + do_shutdown(make_error(ec)); + } + } +} + +void relay_actor_t::on_read(std::size_t bytes) noexcept { + enum process_t { stop = 1 << 0, more = 1 << 1, incomplete = 1 << 2 }; + + LOG_TRACE(log, "{}, on_read: {} bytes, data: {}", identity, bytes, + spdlog::to_hex(rx_buff.begin(), rx_buff.begin() + bytes)); + resources->release(resource::io_read); + rx_idx += bytes; + size_t from = 0; + auto process_op = process_t::more; + while (process_op == process_t::more && from < rx_idx) { + auto start = rx_buff.data() + from; + auto sz = rx_idx - from; + auto r = proto::relay::parse({start, sz}); + process_op = std::visit( + [&](auto &it) -> process_t { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return process_t::incomplete; + } else if constexpr (std::is_same_v) { + LOG_ERROR(log, "{}, protocol error (master)", identity); + auto ec = utils::make_error_code(utils::error_code_t::protocol_error); + do_shutdown(make_error(ec)); + return process_t::stop; + } else if constexpr (std::is_same_v) { + from += it.length; + auto ok = on(it.message); + return ok ? process_t::more : process_t::stop; + } else { + static_assert(always_false_v, "non-exhaustive visitor!"); + } + }, + r); + } + rx_idx -= from; + if (process_op != process_t::stop) { + read_master(); + } +} + +bool relay_actor_t::on(proto::relay::message_t &msg) noexcept { + return std::visit( + [&](auto &it) -> bool { + using T = std::decay_t; + bool err = false; + bool cancel_rx_timer = false; + if constexpr (std::is_same_v) { + if (rx_state & rx_state_t::pong) { + rx_state = ~rx_state & rx_state_t::pong; + cancel_rx_timer = true; + } else { + err = true; + } + } else if constexpr (std::is_same_v) { + auto tx = std::string{}; + proto::relay::serialize(proto::relay::pong_t{}, tx); + push_master(tx); + } else if constexpr (std::is_same_v) { + if (rx_state & rx_state_t::response) { + cancel_rx_timer = true; + rx_state = ~rx_state & rx_state_t::response; + err = !on(it); + } else { + err = true; + } + } else if constexpr (std::is_same_v) { + if (rx_state & rx_state_t::invitation) { + return on(it); + } else { + err = true; + } + } else { + err = true; + } + if (err) { + LOG_ERROR(log, "{}, protocol error (master, unexpected message)", identity); + auto ec = utils::make_error_code(utils::error_code_t::protocol_error); + do_shutdown(make_error(ec)); + } + if (cancel_rx_timer && rx_timer) { + cancel_timer(*rx_timer); + rx_timer.reset(); + } + return !err; + }, + msg); +} + +bool relay_actor_t::on(proto::relay::response_t &res) noexcept { + LOG_DEBUG(log, "{}, on response code = {}", identity, res.code); + if (res.code) { + LOG_WARN(log, "{}, response error, details = {}", identity, res.details); + auto ec = utils::make_error_code(utils::error_code_t::relay_failure); + do_shutdown(make_error(ec)); + return false; + } + respawn_ping_timer(); + rx_state |= rx_state_t::invitation; + auto self = cluster->get_device(); + auto uris = self->get_uris(); + uris.emplace_back(relays[relay_index]->uri); + using namespace model::diff; + auto diff = model::diff::contact_diff_ptr_t{}; + diff = new modify::update_contact_t(*cluster, self->device_id(), uris); + send(coordinator, std::move(diff), this); + return true; +} + +bool relay_actor_t::on(proto::relay::session_invitation_t &msg) noexcept { + auto diff = model::diff::contact_diff_ptr_t{}; + auto device_opt = model::device_id_t::from_sha256(msg.from); + if (!device_opt) { + LOG_ERROR(log, "{}, not valid device: {}", identity, spdlog::to_hex(msg.from.begin(), msg.from.end())); + auto ec = utils::make_error_code(utils::error_code_t::invalid_deviceid); + do_shutdown(make_error(ec)); + return false; + } + + asio::ip::tcp::endpoint relay_ep; + if (!msg.address.empty()) { + sys::error_code ec; + auto ip = asio::ip::make_address(msg.address, ec); + if (ec) { + LOG_ERROR(log, "{}, invalid ip address: {}", identity, + spdlog::to_hex(msg.address.begin(), msg.address.end())); + do_shutdown(make_error(ec)); + return false; + } + relay_ep = asio::ip::tcp::endpoint{ip, (uint16_t)msg.port}; + } else { + relay_ep = asio::ip::tcp::endpoint{master_endpoint.address(), (uint16_t)msg.port}; + } + + diff = new model::diff::modify::relay_connect_request_t(std::move(device_opt.value()), std::move(msg.key), + std::move(relay_ep)); + send(coordinator, std::move(diff), this); + return true; +} + +void relay_actor_t::respawn_ping_timer() noexcept { + if (ping_timer) { + cancel_timer(*ping_timer); + } + if (state > r::state_t::OPERATIONAL) { + return; + } + ping_timer = start_timer(relays[relay_index]->ping_interval, *this, &relay_actor_t::on_ping_timer); +} + +void relay_actor_t::respawn_tx_timer() noexcept { + if (tx_timer) { + cancel_timer(*tx_timer); + } + if (state > r::state_t::OPERATIONAL) { + return; + } + tx_timer = start_timer(relays[relay_index]->ping_interval, *this, &relay_actor_t::on_tx_timer); +} + +void relay_actor_t::respawn_rx_timer() noexcept { + if (rx_timer) { + cancel_timer(*rx_timer); + } + if (state > r::state_t::OPERATIONAL) { + return; + } + rx_timer = start_timer(relays[relay_index]->ping_interval, *this, &relay_actor_t::on_rx_timer); +} + +void relay_actor_t::on_ping_timer(r::request_id_t, bool cancelled) noexcept { + ping_timer.reset(); + if (!cancelled) { + send_ping(); + } +} + +void relay_actor_t::on_tx_timer(r::request_id_t, bool cancelled) noexcept { + tx_timer.reset(); + if (!cancelled) { + auto ec = utils::make_error_code(utils::error_code_t::tx_timeout); + do_shutdown(make_error(ec)); + } +} + +void relay_actor_t::on_rx_timer(r::request_id_t, bool cancelled) noexcept { + rx_timer.reset(); + if (!cancelled) { + auto ec = utils::make_error_code(utils::error_code_t::rx_timeout); + do_shutdown(make_error(ec)); + } +} + +void relay_actor_t::send_ping() noexcept { + if (state > r::state_t::OPERATIONAL) { + return; + } + + auto buff = std::string{}; + proto::relay::serialize(proto::relay::ping_t{}, buff); + push_master(std::move(buff)); + rx_state |= rx_state_t::pong; +} diff --git a/src/net/relay_actor.h b/src/net/relay_actor.h new file mode 100644 index 00000000..83592d1f --- /dev/null +++ b/src/net/relay_actor.h @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2022 Ivan Baidakou + +#pragma once + +#include "messages.h" +#include "utils/log.h" +#include "config/relay.h" +#include "proto/relay_support.h" +#include "transport/stream.h" +#include +#include +#include +#include +#include +#include + +namespace syncspirit::net { + +struct relay_actor_config_t : r::actor_config_t { + model::cluster_ptr_t cluster; + config::relay_config_t config; +}; + +template struct relay_actor_config_builder_t : r::actor_config_builder_t { + using builder_t = typename Actor::template config_builder_t; + using parent_t = r::actor_config_builder_t; + using parent_t::parent_t; + + builder_t &&cluster(const model::cluster_ptr_t &value) &&noexcept { + parent_t::config.cluster = value; + return std::move(*static_cast(this)); + } + + builder_t &&relay_config(const config::relay_config_t &value) &&noexcept { + parent_t::config.config = value; + return std::move(*static_cast(this)); + } +}; + +struct SYNCSPIRIT_API relay_actor_t : public r::actor_base_t { + using config_t = relay_actor_config_t; + template using config_builder_t = relay_actor_config_builder_t; + + relay_actor_t(config_t &config) noexcept; + + void configure(r::plugin::plugin_base_t &plugin) noexcept override; + void on_start() noexcept override; + void shutdown_start() noexcept override; + + private: + using http_rx_buff_t = payload::http_request_t::rx_buff_ptr_t; + using request_option_t = std::optional; + using relays_t = proto::relay::relay_infos_t; + using tx_item_t = boost::local_shared_ptr; + using tx_queue_t = std::deque; + + enum rx_state_t : std::uint32_t { + response = 1 << 0, + ping = 1 << 1, + pong = 1 << 2, + invitation = 1 << 3, + }; + + void request_relay_list() noexcept; + void connect_to_relay() noexcept; + void push_master(std::string data) noexcept; + void write_master() noexcept; + void read_master() noexcept; + void respawn_ping_timer() noexcept; + void respawn_tx_timer() noexcept; + void respawn_rx_timer() noexcept; + void send_ping() noexcept; + + void on_list(message::http_response_t &res) noexcept; + void on_connect(message::connect_response_t &res) noexcept; + void on_write(std::size_t sz) noexcept; + void on_read(std::size_t bytes) noexcept; + void on_io_error(const sys::error_code &ec, r::plugin::resource_id_t resource) noexcept; + bool on(proto::relay::message_t &) noexcept; + bool on(proto::relay::response_t &) noexcept; + bool on(proto::relay::session_invitation_t &) noexcept; + void on_ping_timer(r::request_id_t, bool cancelled) noexcept; + void on_tx_timer(r::request_id_t, bool cancelled) noexcept; + void on_rx_timer(r::request_id_t, bool cancelled) noexcept; + + model::cluster_ptr_t cluster; + config::relay_config_t config; + utils::logger_t log; + + r::address_ptr_t http_client; + r::address_ptr_t coordinator; + r::address_ptr_t peer_supervisor; + http_rx_buff_t http_rx_buff; + request_option_t http_request; + relays_t relays; + int relay_index = -1; + transport::stream_sp_t master; + tcp::endpoint master_endpoint; + fmt::memory_buffer rx_buff; + std::size_t rx_idx = 0; + std::uint32_t rx_state = 0; + tx_queue_t tx_queue; + std::optional ping_timer; + std::optional tx_timer; + std::optional rx_timer; +}; + +} // namespace syncspirit::net diff --git a/src/proto/relay_support.cpp b/src/proto/relay_support.cpp new file mode 100644 index 00000000..d37614d4 --- /dev/null +++ b/src/proto/relay_support.cpp @@ -0,0 +1,438 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2022 Ivan Baidakou + +#include "relay_support.h" +#include "utils/error_code.h" +#include +#include +#include + +namespace be = boost::endian; +using json = nlohmann::json; + +template inline constexpr bool always_false_v = false; + +namespace syncspirit::proto::relay { + +struct header_t { + uint32_t magic; + uint32_t type; + uint32_t length; +}; + +enum class type_t { + ping = 0, + pong = 1, + join_relay_request = 2, + join_session_request = 3, + response = 4, + connect_request = 5, + session_invitation = 6, + LAST = session_invitation, +}; + +static constexpr auto header_sz = sizeof(header_t); +static constexpr auto max_packet_sz = size_t{1400}; +static constexpr uint32_t magic = 0x9E79BC40; + +static pt::time_duration parse_interval(const std::string_view in) noexcept { + auto ptr = in.data(); + auto end = in.data() + in.size(); + auto s = ptr; + auto e = ptr; + while (ptr < end && *ptr != 'm') { + ++ptr; + } + if (*ptr == 'm') { + e = ptr++; + }; + int mins = 0; + while (s < e) { + mins *= 10; + mins += *s - '0'; + ++s; + } + + s = e = ptr; + while (ptr < end && *ptr != 's') { + ++ptr; + } + if (*ptr == 's') { + e = ptr; + }; + + int secs = 0; + while (s < e) { + secs *= 10; + secs += *s - '0'; + ++s; + } + + if (mins == 0 && secs == 0) { + mins = 1; + } + + return pt::minutes{mins} + pt::seconds{secs}; +} + +static void serialize_header(char *ptr, type_t type, size_t payload_sz) noexcept { + auto h = header_t{be::native_to_big(magic), be::native_to_big(static_cast(type)), + be::native_to_big(static_cast(payload_sz))}; + auto in = reinterpret_cast(&h); + std::copy(in, in + header_sz, ptr); +} + +size_t serialize(const message_t &msg, std::string &out) noexcept { + return std::visit( + [&](auto &it) -> size_t { + using T = std::decay_t; + if constexpr (std::is_same_v) { + out.resize(header_sz); + serialize_header(out.data(), type_t::ping, 0); + return header_sz; + } else if constexpr (std::is_same_v) { + out.resize(header_sz); + serialize_header(out.data(), type_t::pong, 0); + return header_sz; + } else if constexpr (std::is_same_v) { + out.resize(header_sz); + serialize_header(out.data(), type_t::join_relay_request, 0); + return header_sz; + } else if constexpr (std::is_same_v) { + auto key_sz = it.key.size(); + auto payload_sz = sizeof(uint32_t) + key_sz; + auto sz = header_sz + payload_sz; + out.resize(sz); + auto ptr = out.data(); + serialize_header(ptr, type_t::join_session_request, payload_sz); + ptr += header_sz; + *(reinterpret_cast(ptr)) = be::native_to_big((uint32_t)key_sz); + ptr += sizeof(uint32_t); + std::copy(it.key.begin(), it.key.end(), ptr); + return sz; + } else if constexpr (std::is_same_v) { + auto msg_sz = it.details.size(); + auto payload_sz = sizeof(uint32_t) + sizeof(uint32_t) + msg_sz; + auto sz = header_sz + payload_sz; + out.resize(sz); + auto ptr = out.data(); + serialize_header(ptr, type_t::response, payload_sz); + ptr += header_sz; + *(reinterpret_cast(ptr)) = be::native_to_big(it.code); + ptr += sizeof(int32_t); + *(reinterpret_cast(ptr)) = be::native_to_big((uint32_t)msg_sz); + ptr += sizeof(uint32_t); + std::copy(it.details.begin(), it.details.end(), ptr); + return sz; + } else if constexpr (std::is_same_v) { + auto id_sz = it.device_id.size(); + auto payload_sz = sizeof(uint32_t) + id_sz; + auto sz = header_sz + payload_sz; + out.resize(sz); + auto ptr = out.data(); + serialize_header(ptr, type_t::connect_request, payload_sz); + ptr += header_sz; + *(reinterpret_cast(ptr)) = be::native_to_big((uint32_t)id_sz); + ptr += sizeof(uint32_t); + std::copy(it.device_id.begin(), it.device_id.end(), ptr); + return sz; + } else if constexpr (std::is_same_v) { + auto &from = it.from; + auto &key = it.key; + auto &address = it.address; + auto payload_sz = sizeof(uint32_t) * 6 + from.size() + key.size() + address.size(); + auto sz = header_sz + payload_sz; + out.resize(sz); + auto ptr = out.data(); + serialize_header(ptr, type_t::session_invitation, payload_sz); + ptr += header_sz; + *(reinterpret_cast(ptr)) = be::native_to_big((uint32_t)from.size()); + ptr += sizeof(uint32_t); + std::copy(from.begin(), from.end(), ptr); + ptr += from.size(); + + *(reinterpret_cast(ptr)) = be::native_to_big((uint32_t)key.size()); + ptr += sizeof(uint32_t); + std::copy(key.begin(), key.end(), ptr); + ptr += key.size(); + + *(reinterpret_cast(ptr)) = be::native_to_big((uint32_t)address.size()); + ptr += sizeof(uint32_t); + std::copy(address.begin(), address.end(), ptr); + ptr += address.size(); + + *(reinterpret_cast(ptr)) = be::native_to_big((uint32_t)it.port); + ptr += sizeof(uint32_t); + + *(reinterpret_cast(ptr)) = be::native_to_big((uint32_t)it.server_socket); + return sz; + } else { + static_assert(always_false_v, "non-exhaustive visitor!"); + } + }, + msg); +} + +static parse_result_t parse_ping(std::string_view data) noexcept { return wrapped_message_t{header_sz, ping_t{}}; } + +static parse_result_t parse_pong(std::string_view data) noexcept { return wrapped_message_t{header_sz, pong_t{}}; } + +static parse_result_t parse_join_relay_request(std::string_view data) noexcept { + return wrapped_message_t{header_sz, join_relay_request_t{}}; +} + +static parse_result_t parse_join_session_request(std::string_view data) noexcept { + if (data.size() < sizeof(uint32_t)) { + return protocol_error_t{}; + } + auto ptr = data.data(); + auto sz = be::big_to_native(*reinterpret_cast(ptr)); + if (sz + sizeof(uint32_t) > data.size()) { + return protocol_error_t{}; + } + auto tail = data.substr(sizeof(uint32_t)); + return wrapped_message_t{header_sz + data.size(), join_session_request_t{std::string(tail)}}; +} + +static parse_result_t parse_response(std::string_view data) noexcept { + if (data.size() < sizeof(uint32_t) * 2) { + return protocol_error_t{}; + } + auto ptr = data.data(); + auto code = be::big_to_native(*reinterpret_cast(ptr)); + ptr += sizeof(uint32_t); + auto sz = be::big_to_native(*reinterpret_cast(ptr)); + if (sz + sizeof(uint32_t) * 2 > data.size()) { + return protocol_error_t{}; + } + auto tail = data.substr(sizeof(uint32_t) * 2, sz); + return wrapped_message_t{header_sz + data.size(), response_t{code, std::string(tail)}}; +} + +static parse_result_t parse_connect_request(std::string_view data) noexcept { + if (data.size() < sizeof(uint32_t)) { + return protocol_error_t{}; + } + auto ptr = data.data(); + auto sz = be::big_to_native(*reinterpret_cast(ptr)); + if (sz + sizeof(uint32_t) != data.size()) { + return protocol_error_t{}; + } + auto tail = data.substr(sizeof(uint32_t)); + return wrapped_message_t{header_sz + data.size(), connect_request_t{std::string(tail)}}; +} + +static parse_result_t parse_session_invitation(std::string_view data) noexcept { + if (data.size() < sizeof(uint32_t)) { + return protocol_error_t{}; + } + auto orig = data; + + auto from_sz = be::big_to_native(*reinterpret_cast(data.data())); + data = data.substr(sizeof(uint32_t)); + if (data.size() < from_sz) { + return protocol_error_t{}; + } + auto from = data.substr(0, from_sz); + data = data.substr(from_sz); + + if (data.size() < sizeof(uint32_t)) { + return protocol_error_t{}; + } + auto key_sz = be::big_to_native(*reinterpret_cast(data.data())); + data = data.substr(sizeof(uint32_t)); + if (data.size() < key_sz) { + return protocol_error_t{}; + } + auto key = data.substr(0, key_sz); + data = data.substr(key_sz); + + if (data.size() < sizeof(uint32_t)) { + return protocol_error_t{}; + } + auto addr_sz = be::big_to_native(*reinterpret_cast(data.data())); + data = data.substr(sizeof(uint32_t)); + if (data.size() < addr_sz) { + return protocol_error_t{}; + } + auto addr = data.substr(0, addr_sz); + data = data.substr(addr_sz); + + if (data.size() < sizeof(uint32_t)) { + return protocol_error_t{}; + } + auto port = be::big_to_native(*reinterpret_cast(data.data())); + data = data.substr(sizeof(uint32_t)); + + if (data.size() < sizeof(uint32_t)) { + return protocol_error_t{}; + } + auto server_socket = be::big_to_native(*reinterpret_cast(data.data())); + data = data.substr(sizeof(uint32_t)); + + if (!addr.empty() && !addr[0]) { + addr = ""; + }; + + return wrapped_message_t{ + header_sz + orig.size(), + session_invitation_t{std::string(from), std::string(key), std::string(addr), port, (bool)server_socket}}; +} + +parse_result_t parse(std::string_view data) noexcept { + if (data.size() < header_sz) { + return incomplete_t{}; + } + auto ptr = data.data(); + auto header = reinterpret_cast(ptr); + if (be::big_to_native(header->magic) != magic) { + return protocol_error_t{}; + } + auto type = be::big_to_native(header->type); + if (type > (uint32_t)type_t::LAST) { + return protocol_error_t{}; + } + auto sz = be::big_to_native(header->length); + if (sz > max_packet_sz) { + return protocol_error_t{}; + } + if (data.size() < header_sz + sz) { + return incomplete_t{}; + } + auto tail = data.substr(header_sz); + + switch ((type_t)type) { + case type_t::ping: + return parse_ping(tail); + case type_t::pong: + return parse_pong(tail); + case type_t::join_relay_request: + return parse_join_relay_request(tail); + case type_t::join_session_request: + return parse_join_session_request(tail); + case type_t::response: + return parse_response(tail); + case type_t::connect_request: + return parse_connect_request(tail); + case type_t::session_invitation: + return parse_session_invitation(tail); + default: + return protocol_error_t{}; + } +} + +std::optional parse_device(const utils::URI &uri) noexcept { + if (uri.proto != "relay") { + return {}; + } + auto device_id_str = std::string{}; + auto q = uri.decompose_query(); + for (auto &pair : q) { + if (pair.first == "id") { + device_id_str = std::move(pair.second); + break; + } + } + if (device_id_str.empty()) { + return {}; + } + auto device_opt = model::device_id_t::from_string(device_id_str); + if (!device_opt) { + return {}; + } + return std::move(device_opt.value()); +} + +outcome::result parse_endpoint(std::string_view buff) noexcept { + using namespace syncspirit::utils; + auto data = json::parse(buff.begin(), buff.end(), 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 &relays = data["relays"]; + if (!relays.is_array()) { + return make_error_code(error_code_t::incorrect_json); + } + + auto r = relay_infos_t{}; + for (auto &it : relays) { + if (!it.is_object()) { + continue; + return make_error_code(error_code_t::incorrect_json); + } + auto &url = it["url"]; + if (!url.is_string()) { + continue; + } + auto uri_str = url.get(); + auto uri_option = utils::parse(uri_str.c_str()); + if (!uri_option) { + continue; + } + auto &uri = uri_option.value(); + if (uri.proto != "relay") { + continue; + } + auto device_id_str = std::string{}; + auto ping_interval_str = std::string{}; + auto q = uri.decompose_query(); + for (auto &pair : q) { + if (pair.first == "id") { + device_id_str = std::move(pair.second); + } else if (pair.first == "pingInterval") { + ping_interval_str = std::move(pair.second); + } + } + if (device_id_str.empty()) { + continue; + } + auto device_id = model::device_id_t::from_string(device_id_str); + if (!device_id) { + continue; + } + + auto ping_interval = parse_interval(ping_interval_str); + + auto &location = it["location"]; + if (!location.is_object()) { + continue; + } + auto &latitude = location["latitude"]; + if (!latitude.is_number_float()) { + continue; + } + auto &longitude = location["longitude"]; + if (!longitude.is_number_float()) { + continue; + } + auto &city = location["city"]; + if (!city.is_string()) { + continue; + } + auto &country = location["country"]; + if (!country.is_string()) { + continue; + } + auto &continent = location["continent"]; + if (!continent.is_string()) { + continue; + } + auto relay = relay_info_ptr_t{new relay_info_t{std::move(uri), device_id.value(), + location_t{ + latitude.get(), + longitude.get(), + city.get(), + country.get(), + continent.get(), + }, + ping_interval}}; + r.emplace_back(std::move(relay)); + } + return std::move(r); +} + +} // namespace syncspirit::proto::relay diff --git a/src/proto/relay_support.h b/src/proto/relay_support.h new file mode 100644 index 00000000..eb04b2f5 --- /dev/null +++ b/src/proto/relay_support.h @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2022 Ivan Baidakou + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "utils/uri.h" +#include "syncspirit-export.h" +#include +#include + +namespace syncspirit::proto::relay { + +namespace outcome = boost::outcome_v2; +namespace pt = boost::posix_time; + +struct ping_t {}; + +struct pong_t {}; + +struct join_relay_request_t {}; + +struct join_session_request_t { + std::string key; +}; + +struct response_t { + std::uint32_t code; + std::string details; +}; + +struct connect_request_t { + std::string device_id; +}; + +struct session_invitation_t { + std::string from; + std::string key; + std::string address; + std::uint32_t port; + bool server_socket; +}; + +using message_t = std::variant; + +// support +struct incomplete_t {}; +struct protocol_error_t {}; +struct wrapped_message_t { + size_t length; + message_t message; +}; + +using parse_result_t = std::variant; + +SYNCSPIRIT_API size_t serialize(const relay::message_t &, std::string &out) noexcept; +SYNCSPIRIT_API parse_result_t parse(std::string_view data) noexcept; + +struct location_t { + float latitude; + float longitude; + std::string city; + std::string country; + std::string continent; +}; + +struct relay_info_t : model::arc_base_t { + inline relay_info_t(utils::URI uri_, const model::device_id_t &device_id_, location_t location_, + const pt::time_duration &ping_interval_) noexcept + : uri(std::move(uri_)), device_id{device_id_}, location{std::move(location_)}, ping_interval{ping_interval_} {} + utils::URI uri; + model::device_id_t device_id; + location_t location; + pt::time_duration ping_interval; +}; + +using relay_info_ptr_t = model::intrusive_ptr_t; +using relay_infos_t = std::vector; + +SYNCSPIRIT_API std::optional parse_device(const utils::URI &uri) noexcept; +SYNCSPIRIT_API outcome::result parse_endpoint(std::string_view data) noexcept; + +} // namespace syncspirit::proto::relay diff --git a/src/transport/base.h b/src/transport/base.h index 9f740da2..262d4643 100644 --- a/src/transport/base.h +++ b/src/transport/base.h @@ -18,6 +18,7 @@ namespace syncspirit::transport { namespace asio = boost::asio; namespace sys = boost::system; +namespace ra = rotor::asio; using tcp = asio::ip::tcp; @@ -44,8 +45,13 @@ using ssl_option_t = std::optional; struct transport_config_t { ssl_option_t ssl_junction; utils::URI uri; - rotor::asio::supervisor_asio_t &supervisor; + ra::supervisor_asio_t &supervisor; std::optional sock; + bool active; }; +struct stream_base_t; + +using stream_sp_t = model::intrusive_ptr_t; + } // namespace syncspirit::transport diff --git a/src/transport/http.cpp b/src/transport/http.cpp index 8b84aa7f..a85bbca0 100644 --- a/src/transport/http.cpp +++ b/src/transport/http.cpp @@ -6,8 +6,6 @@ namespace syncspirit::transport { -http_base_t::~http_base_t() {} - template struct http_impl_t : base_impl_t, interface_t, Sock, http_base_t> { using self_t = http_impl_t; using socket_t = Sock; @@ -17,8 +15,6 @@ template struct http_impl_t : base_impl_t, interface_t; - // virtual ~http_impl_t() {}; - void async_read(rx_buff_t &rx_buff, response_t &response, io_fn_t &on_read, error_fn_t &on_error) noexcept override { auto owner = curry_io(*this, on_read, on_error); @@ -40,14 +36,10 @@ template struct http_impl_t : base_impl_t, interface_t(config); - } - } else { - if (proto == "https") { - return new http_impl_t(config); - } + if (proto == "http") { + return new http_impl_t(config); + } else if (proto == "https") { + return new http_impl_t(config); } return http_sp_t(); } diff --git a/src/transport/http.h b/src/transport/http.h index 06db1f24..e319ba5b 100644 --- a/src/transport/http.h +++ b/src/transport/http.h @@ -18,7 +18,7 @@ struct http_interface_t { }; struct SYNCSPIRIT_API http_base_t : model::arc_base_t, http_interface_t, stream_interface_t { - virtual ~http_base_t(); + virtual ~http_base_t() = default; }; using http_sp_t = model::intrusive_ptr_t; diff --git a/src/transport/impl.hpp b/src/transport/impl.hpp index e34638b4..0641fb15 100644 --- a/src/transport/impl.hpp +++ b/src/transport/impl.hpp @@ -66,36 +66,51 @@ template <> struct base_impl_t { strand_t &strand; tcp_socket_t sock; bool cancelling = false; + bool active; + + static tcp_socket_t mk_sock(transport_config_t &config, strand_t &strand) noexcept { + if (config.sock) { + tcp::socket sock(std::move(config.sock.value())); + return {std::move(sock)}; + } else { + tcp::socket sock(strand.context()); + return {std::move(sock)}; + } + } + base_impl_t(transport_config_t &config) noexcept - : supervisor{config.supervisor}, strand{supervisor.get_strand()}, sock(strand.context()) {} + : supervisor{config.supervisor}, strand{supervisor.get_strand()}, + sock(mk_sock(config, strand)), active{config.active} {} tcp_socket_t &get_physical_layer() noexcept { return sock; } - // virtual ~base_impl_t(){} }; template <> struct base_impl_t { using self_t = base_impl_t; - // virtual ~base_impl_t(){} rotor::asio::supervisor_asio_t &supervisor; strand_t &strand; model::device_id_t expected_peer; model::device_id_t actual_peer; - const utils::key_pair_t &me; + const utils::key_pair_t *me = nullptr; ssl::context ctx; ssl::stream_base::handshake_type role; ssl_socket_t sock; bool validation_passed = false; bool cancelling = false; + bool active; static ssl::context get_context(self_t &source, std::string_view alpn) noexcept { ssl::context ctx(ssl::context::tls); ctx.set_options(ssl::context::default_workarounds | ssl::context::no_sslv2); - auto &cert_data = source.me.cert_data.bytes; - auto &key_data = source.me.key_data.bytes; - ctx.use_certificate(asio::const_buffer(cert_data.c_str(), cert_data.size()), ssl::context::asn1); - ctx.use_private_key(asio::const_buffer(key_data.c_str(), key_data.size()), ssl::context::asn1); + auto me = source.me; + if (me) { + auto &cert_data = me->cert_data.bytes; + auto &key_data = me->key_data.bytes; + ctx.use_certificate(asio::const_buffer(cert_data.c_str(), cert_data.size()), ssl::context::asn1); + ctx.use_private_key(asio::const_buffer(key_data.c_str(), key_data.size()), ssl::context::asn1); + } if (alpn.size()) { std::byte wire_alpn[alpn.size() + 1]; @@ -122,8 +137,9 @@ template <> struct base_impl_t { base_impl_t(transport_config_t &config) noexcept : supervisor{config.supervisor}, strand{supervisor.get_strand()}, expected_peer{config.ssl_junction->peer}, - me(*config.ssl_junction->me), ctx(get_context(*this, config.ssl_junction->alpn)), - role(config.sock ? ssl::stream_base::server : ssl::stream_base::client), sock(mk_sock(config, ctx, strand)) { + me(config.ssl_junction->me), ctx(get_context(*this, config.ssl_junction->alpn)), + role(!config.active ? ssl::stream_base::server : ssl::stream_base::client), + sock(mk_sock(config, ctx, strand)) { if (config.ssl_junction->sni_extension) { auto &host = config.uri.host; if (!SSL_set_tlsext_host_name(sock.native_handle(), host.c_str())) { @@ -131,44 +147,47 @@ template <> struct base_impl_t { spdlog::error("http_actor_t:: Set SNI Hostname : {}", ec.message()); } } - auto mode = ssl::verify_peer | ssl::verify_fail_if_no_peer_cert | ssl::verify_client_once; - sock.set_verify_depth(1); - sock.set_verify_mode(mode); - sock.set_verify_callback([&](bool, ssl::verify_context &peer_ctx) -> bool { - auto native = peer_ctx.native_handle(); - auto peer_cert = X509_STORE_CTX_get_current_cert(native); - if (!peer_cert) { - spdlog::warn("no peer certificate"); - return false; - } - auto der_option = utils::as_serialized_der(peer_cert); - if (!der_option) { - spdlog::warn("peer certificate cannot be serialized as der : {}", der_option.error().message()); - return false; - } + if (me) { + auto mode = ssl::verify_peer | ssl::verify_fail_if_no_peer_cert | ssl::verify_client_once; + sock.set_verify_mode(mode); + sock.set_verify_depth(1); + sock.set_verify_callback([&](bool, ssl::verify_context &peer_ctx) -> bool { + auto native = peer_ctx.native_handle(); + auto peer_cert = X509_STORE_CTX_get_current_cert(native); + if (!peer_cert) { + spdlog::warn("no peer certificate"); + return false; + } + auto der_option = utils::as_serialized_der(peer_cert); + if (!der_option) { + spdlog::warn("peer certificate cannot be serialized as der : {}", der_option.error().message()); + return false; + } - utils::cert_data_t cert_data{std::move(der_option.value())}; - auto peer_option = model::device_id_t::from_cert(cert_data); - if (!peer_option) { - spdlog::warn("cannot get device_id from peer"); - return false; - } + utils::cert_data_t cert_data{std::move(der_option.value())}; + auto peer_option = model::device_id_t::from_cert(cert_data); + if (!peer_option) { + spdlog::warn("cannot get device_id from peer"); + return false; + } - auto peer = std::move(peer_option.value()); - if (!actual_peer) { - actual_peer = std::move(peer); - spdlog::trace("tls, peer device_id = {}", actual_peer); - } + auto peer = std::move(peer_option.value()); + if (!actual_peer) { + actual_peer = std::move(peer); + spdlog::trace("tls, peer device_id = {}", actual_peer); + } - if (role == ssl::stream_base::handshake_type::client) { - if (actual_peer != expected_peer) { - spdlog::warn("unexcpected peer device_id. Got: {}, expected: {}", actual_peer, expected_peer); - return false; + if (role == ssl::stream_base::handshake_type::client) { + if (actual_peer != expected_peer) { + spdlog::warn("unexcpected peer device_id. Got: {}, expected: {}", actual_peer.get_value(), + expected_peer.get_value()); + return false; + } } - } - validation_passed = true; - return true; - }); + validation_passed = true; + return true; + }); + } } tcp_socket_t &get_physical_layer() noexcept { return sock.next_layer(); } diff --git a/src/transport/stream.cpp b/src/transport/stream.cpp index 6cf9eb01..e9353b36 100644 --- a/src/transport/stream.cpp +++ b/src/transport/stream.cpp @@ -6,27 +6,53 @@ namespace syncspirit::transport { -stream_base_t::~stream_base_t() {} - -template struct steam_impl_t : base_impl_t, interface_t, Sock, stream_base_t> { - using self_t = steam_impl_t; +template +struct generic_steam_impl_t : base_impl_t, interface_t, Sock, Interface> { + using self_t = generic_steam_impl_t; using socket_t = Sock; using parent_t = base_impl_t; using parent_t::parent_t; }; +using ssl_stream_impl_t = generic_steam_impl_t; + +struct tcp_stream_impl_t final : generic_steam_impl_t { + using parent_t = generic_steam_impl_t; + + tcp_stream_impl_t(transport_config_t &config) noexcept : parent_t(config), uri(config.uri) {} + + using parent_t::parent_t; + + stream_sp_t upgrade(ssl_junction_t &ssl, bool active_role) noexcept { + transport_config_t cfg{ssl_option_t(ssl), uri, supervisor, std::move(sock), active_role}; + return new ssl_stream_impl_t(cfg); + } + + utils::URI uri; +}; + +stream_sp_t initiate_tls_passive(ra::supervisor_asio_t &sup, const utils::key_pair_t &my_keys, + tcp::socket peer_sock) noexcept { + ssl_junction_t ssl{{}, &my_keys, false, ""}; + transport_config_t cfg{ssl_option_t(ssl), {}, sup, std::move(peer_sock), false}; + return new ssl_stream_impl_t(cfg); +} + +stream_sp_t initiate_tls_active(ra::supervisor_asio_t &sup, const utils::key_pair_t &my_keys, + const model::device_id_t &expected_peer, const utils::URI &uri, bool sni, + std::string_view alpn) noexcept { + ssl_junction_t ssl{expected_peer, &my_keys, sni, alpn}; + transport_config_t cfg{ssl_option_t(ssl), uri, sup, {}, true}; + return new ssl_stream_impl_t(cfg); +} + stream_sp_t initiate_stream(transport_config_t &config) noexcept { - auto &proto = config.uri.proto; - if (proto == "tcp") { - if (config.ssl_junction) { - using socket_t = ssl_socket_t; - return new steam_impl_t(config); - } else { - using socket_t = tcp_socket_t; - return new steam_impl_t(config); - } + assert(config.uri.proto == "tcp"); + if (config.ssl_junction) { + return new ssl_stream_impl_t(config); + } else { + return new tcp_stream_impl_t(config); } - return stream_sp_t(); } } // namespace syncspirit::transport diff --git a/src/transport/stream.h b/src/transport/stream.h index 3e6b4ddd..548f7236 100644 --- a/src/transport/stream.h +++ b/src/transport/stream.h @@ -17,11 +17,18 @@ struct stream_interface_t { }; struct stream_base_t : model::arc_base_t, stream_interface_t { - virtual ~stream_base_t(); + virtual ~stream_base_t() = default; }; -using stream_sp_t = model::intrusive_ptr_t; +struct upgradeable_stream_base_t : stream_base_t { + virtual stream_sp_t upgrade(ssl_junction_t &ssl, bool active) noexcept = 0; +}; +stream_sp_t initiate_tls_active(ra::supervisor_asio_t &supervisor, const utils::key_pair_t &my_keys, + const model::device_id_t &expected_peer, const utils::URI &uri, bool sni = false, + std::string_view alpn = "") noexcept; +stream_sp_t initiate_tls_passive(ra::supervisor_asio_t &supervisor, const utils::key_pair_t &my_keys, + tcp::socket sock) noexcept; stream_sp_t initiate_stream(transport_config_t &config) noexcept; } // namespace syncspirit::transport diff --git a/src/ui-daemon/CMakeLists.txt b/src/ui-daemon/CMakeLists.txt index d82bf529..eeaa5c32 100644 --- a/src/ui-daemon/CMakeLists.txt +++ b/src/ui-daemon/CMakeLists.txt @@ -32,20 +32,25 @@ endif() if (CMAKE_BUILD_TYPE MATCHES "^([Rr]elease)|(MinSizeRel)") set_target_properties(syncspirit-daemon PROPERTIES LINK_FLAGS -s) - set(DAEMON_TARGET "${CMAKE_CXX_COMPILER}") - string(REGEX REPLACE ".*/" "" DAEMON_TARGET ${DAEMON_TARGET}) - string(REGEX REPLACE "(.*)-.+" "\\1" DAEMON_TARGET ${DAEMON_TARGET}) - if ("${DAEMON_TARGET}" STREQUAL "") - set(DAEMON_TARGET "unknown") + find_program(UPX upx) + if (NOT ${UPX_FOUND}) + message(WARNING "upx not found") + else() + message(STATUS "upx found") + set(DAEMON_TARGET "${CMAKE_CXX_COMPILER}") + string(REGEX REPLACE ".*/" "" DAEMON_TARGET ${DAEMON_TARGET}) + string(REGEX REPLACE "(.*)-.+" "\\1" DAEMON_TARGET ${DAEMON_TARGET}) + if ("${DAEMON_TARGET}" STREQUAL "") + set(DAEMON_TARGET "unknown") + endif() + string(JOIN "_" DAEMON_TARGET "syncspirit-daemon" ${SYNCSPIRIT_VERSION} ${DAEMON_TARGET}) + set(DAEMON_TARGET "${DAEMON_TARGET}${CMAKE_EXECUTABLE_SUFFIX}") + + set(EXE "syncspirit-daemon${CMAKE_EXECUTABLE_SUFFIX}") + add_custom_target(compress_exec ALL + COMMAND upx "--force" "-9" "-q" "-o" "${syncspirit_BINARY_DIR}/${DAEMON_TARGET}" ${EXE} + DEPENDS ${EXE} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "compressing ${DAEMON_TARGET}") endif() - string(JOIN "_" DAEMON_TARGET "syncspirit-daemon" ${SYNCSPIRIT_VERSION} ${DAEMON_TARGET}) - set(DAEMON_TARGET "${DAEMON_TARGET}.zip") - set(ACHIVE_NAME "${syncspirit_BINARY_DIR}/${DAEMON_TARGET}") - message(STATUS "going to make an ${ACHIVE_NAME}") - add_custom_target(make_archive ALL - COMMAND zip "-q9" "${ACHIVE_NAME}" "syncspirit-daemon${CMAKE_EXECUTABLE_SUFFIX}" - DEPENDS "syncspirit-daemon${CMAKE_EXECUTABLE_SUFFIX}" - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMENT "making release archive ${DAEMON_TARGET}" - VERBATIM) endif() diff --git a/src/ui-daemon/main.cpp b/src/ui-daemon/main.cpp index ede9db17..c3b5e4e2 100644 --- a/src/ui-daemon/main.cpp +++ b/src/ui-daemon/main.cpp @@ -203,8 +203,11 @@ int main(int argc, char **argv) { .create_registry() .guard_context(true) .cluster_copies(cluster_copies) + .shutdown_flag(shutdown_flag, r::pt::millisec{50}) .finish(); sup_net->start(); + // pre-startup + sup_net->do_process(); rth::system_context_thread_t fs_context; auto fs_sup = fs_context.create_supervisor() @@ -234,15 +237,6 @@ int main(int argc, char **argv) { } /* launch actors */ - auto net_thread = std::thread([&]() { -#if defined(__linux__) - pthread_setname_np(pthread_self(), "ss/net"); -#endif - io_context.run(); - shutdown_flag = true; - spdlog::trace("net thread has been terminated"); - }); - auto fs_thread = std::thread([&]() { #if defined(__linux__) pthread_setname_np(pthread_self(), "ss/fs"); @@ -270,18 +264,12 @@ int main(int argc, char **argv) { } // main loop; - while (!shutdown_flag) { - using namespace std::chrono_literals; - std::this_thread::sleep_for(10ms); - } - - // initiate shutdown - spdlog::trace("sending shutdown signal..."); - auto ec = r::make_error_code(r::shutdown_code_t::normal); - auto coordinator = sup_net->get_address(); - auto ee = r::make_error("syncspirit-daemon is terminating", ec); - auto msg = r::make_message(coordinator, coordinator, ee); - sup_net->enqueue(msg); +#if defined(__linux__) + pthread_setname_np(pthread_self(), "ss/net"); +#endif + io_context.run(); + shutdown_flag = true; + spdlog::trace("net thread has been terminated"); spdlog::trace("waiting fs thread termination"); fs_thread.join(); @@ -290,10 +278,6 @@ int main(int argc, char **argv) { for (auto &thread : hasher_threads) { thread.join(); } - - spdlog::trace("waiting net thread termination"); - net_thread.join(); - spdlog::trace("everything has been terminated"); } catch (...) { spdlog::critical("unknown exception"); diff --git a/src/utils/error_code.cpp b/src/utils/error_code.cpp index 0234d7dd..310010b9 100644 --- a/src/utils/error_code.cpp +++ b/src/utils/error_code.cpp @@ -74,6 +74,9 @@ std::string error_code_category::message(int c) const { case error_code_t::rx_timeout: r = "rx timeout"; break; + case error_code_t::tx_timeout: + r = "tx timeout"; + break; case error_code_t::announce_failed: r = "announce failed"; break; @@ -101,6 +104,18 @@ std::string error_code_category::message(int c) const { case error_code_t::already_connected: r = "peer is already connected"; break; + case error_code_t::cannot_get_public_relays: + r = "cannot get public relays"; + break; + case error_code_t::protocol_error: + r = "protocol error"; + break; + case error_code_t::relay_failure: + r = "relay failure"; + break; + case error_code_t::invalid_deviceid: + r = "invalid device id"; + break; default: r = "unknown"; } diff --git a/src/utils/error_code.h b/src/utils/error_code.h index 41225d4b..ce4a9b1f 100644 --- a/src/utils/error_code.h +++ b/src/utils/error_code.h @@ -62,12 +62,17 @@ enum class error_code_t { unparseable_control_url, external_ip_failed, rx_timeout, + tx_timeout, fs_error, scan_aborted, already_shared, unknown_sink, misconfigured_default_logger, already_connected, + cannot_get_public_relays, + protocol_error, + relay_failure, + invalid_deviceid, }; enum class bep_error_code_t { diff --git a/src/utils/uri.cpp b/src/utils/uri.cpp index 560c7112..83ac84a6 100644 --- a/src/utils/uri.cpp +++ b/src/utils/uri.cpp @@ -77,4 +77,22 @@ void URI::set_query(const std::string &value) noexcept { std::string URI::relative() const noexcept { return path + query; } +auto URI::decompose_query() const noexcept -> StringPairs { + StringPairs r; + UriQueryListA *queryList; + int itemCount; + auto q = query.data(); + if (uriDissectQueryMallocA(&queryList, &itemCount, q, q + query.size()) != URI_SUCCESS) { + return {}; + } + using guard_t = std::unique_ptr>; + guard_t guard(queryList, [](auto ptr) { uriFreeQueryListA(ptr); }); + auto p = queryList; + while (p) { + r.emplace_back(StringPair(p->key, p->value)); + p = p->next; + } + return r; +} + }; // namespace syncspirit::utils diff --git a/src/utils/uri.h b/src/utils/uri.h index dad88f8b..3db01ebe 100644 --- a/src/utils/uri.h +++ b/src/utils/uri.h @@ -8,10 +8,14 @@ #include #include #include +#include namespace syncspirit::utils { struct SYNCSPIRIT_API URI { + using StringPair = std::pair; + using StringPairs = std::vector; + std::string full; std::string host; std::uint16_t port; @@ -25,6 +29,8 @@ struct SYNCSPIRIT_API URI { void set_query(const std::string &value) noexcept; std::string relative() const noexcept; + StringPairs decompose_query() const noexcept; + inline bool operator==(const URI &other) const noexcept { return full == other.full; } inline operator bool() const noexcept { return !full.empty(); } diff --git a/syncspirit.toml b/syncspirit.toml index 1d4ebf13..049fe1de 100644 --- a/syncspirit.toml +++ b/syncspirit.toml @@ -4,7 +4,7 @@ connect_timeout = 5000 request_timeout = 60000 rx_timeout = 300000 tx_timeout = 10000 -blocks_max_requested = 16 +blocks_max_requested = 8 [dialer] enabled = true @@ -16,7 +16,7 @@ temporally_timeout = 86400000 mru_size = 10 [db] -upper_limit = 0x400000000 +upper_limit = 0x40000000 uncommited_threshold = 150 [global_discovery] @@ -31,7 +31,7 @@ timeout = 4000 [local_discovery] enabled = true frequency = 10000 -port = 21026 +port = 21027 [[log]] name = 'default' @@ -40,7 +40,15 @@ sinks = ['stdout', 'file:/tmp/log.txt'] [[log]] name = 'net.db' -level = 'debug' +level = 'info' + +[[log]] +name = 'net.peer_actor' +level = 'trace' + +[[log]] +name = 'net.hasher.actor' +level = 'info' [main] default_location = '/tmp/syncspirit' @@ -54,5 +62,5 @@ discovery_attempts = 2 external_port = 22001 max_wait = 1 rx_buff_size = 65536 -debug = true +debug = false diff --git a/tests/014-configuration.cpp b/tests/014-configuration.cpp index f45fb9e3..6e9dc2b7 100644 --- a/tests/014-configuration.cpp +++ b/tests/014-configuration.cpp @@ -8,6 +8,59 @@ #include #include +namespace syncspirit::config { + +bool operator==(const bep_config_t &lhs, const bep_config_t &rhs) noexcept { + return lhs.rx_buff_size == rhs.rx_buff_size && lhs.connect_timeout == rhs.connect_timeout && + lhs.request_timeout == rhs.request_timeout && lhs.tx_timeout == rhs.tx_timeout && + lhs.rx_timeout == rhs.rx_timeout && lhs.blocks_max_requested == rhs.blocks_max_requested; +} + +bool operator==(const dialer_config_t &lhs, const dialer_config_t &rhs) noexcept { + return lhs.enabled == rhs.enabled && lhs.redial_timeout == rhs.redial_timeout; +} + +bool operator==(const fs_config_t &lhs, const fs_config_t &rhs) noexcept { + return lhs.temporally_timeout == rhs.temporally_timeout && lhs.mru_size == rhs.mru_size; +} + +bool operator==(const db_config_t &lhs, const db_config_t &rhs) noexcept { + return lhs.upper_limit == rhs.upper_limit && lhs.uncommited_threshold == rhs.uncommited_threshold; +} + +bool operator==(const global_announce_config_t &lhs, const global_announce_config_t &rhs) noexcept { + return lhs.enabled == rhs.enabled && lhs.announce_url == rhs.announce_url && lhs.device_id == rhs.device_id && + lhs.cert_file == rhs.cert_file && lhs.key_file == rhs.key_file && lhs.rx_buff_size == rhs.rx_buff_size && + lhs.timeout == rhs.timeout && lhs.reannounce_after == rhs.reannounce_after; +} + +bool operator==(const local_announce_config_t &lhs, const local_announce_config_t &rhs) noexcept { + return lhs.enabled == rhs.enabled && lhs.port == rhs.port && lhs.frequency == rhs.frequency; +} + +bool operator==(const log_config_t &lhs, const log_config_t &rhs) noexcept { + return lhs.name == rhs.name && lhs.level == rhs.level && lhs.sinks == rhs.sinks; +} + +bool operator==(const upnp_config_t &lhs, const upnp_config_t &rhs) noexcept { + return lhs.enabled == rhs.enabled && lhs.max_wait == rhs.max_wait && lhs.external_port == rhs.external_port && + lhs.rx_buff_size == rhs.rx_buff_size && lhs.debug == rhs.debug; +} + +bool operator==(const relay_config_t &lhs, const relay_config_t &rhs) noexcept { + return lhs.enabled == rhs.enabled && lhs.discovery_url == rhs.discovery_url && lhs.rx_buff_size == rhs.rx_buff_size; +} + +bool operator==(const main_t &lhs, const main_t &rhs) noexcept { + return lhs.local_announce_config == rhs.local_announce_config && lhs.upnp_config == rhs.upnp_config && + lhs.global_announce_config == rhs.global_announce_config && lhs.bep_config == rhs.bep_config && + lhs.db_config == rhs.db_config && lhs.timeout == rhs.timeout && lhs.device_name == rhs.device_name && + lhs.config_path == rhs.config_path && lhs.log_configs == rhs.log_configs && + lhs.hasher_threads == rhs.hasher_threads; +} + +} // namespace syncspirit::config + namespace sys = boost::system; namespace fs = boost::filesystem; namespace st = syncspirit::test; diff --git a/tests/016-relay-support.cpp b/tests/016-relay-support.cpp new file mode 100644 index 00000000..4d2323ee --- /dev/null +++ b/tests/016-relay-support.cpp @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2022 Ivan Baidakou + +#include "catch.hpp" +#include "proto/relay_support.h" + +using namespace syncspirit::proto::relay; + +TEST_CASE("relay proto", "[relay]") { + std::string buff; + + SECTION("successful cases") { + SECTION("ping") { + auto sz = serialize(ping_t{}, buff); + REQUIRE(sz); + auto r = parse(buff); + auto msg = std::get_if(&r); + REQUIRE(msg); + CHECK(msg->length == sz); + auto target = std::get_if(&msg->message); + REQUIRE(target); + } + + SECTION("pong") { + auto sz = serialize(pong_t{}, buff); + REQUIRE(sz); + auto r = parse(buff); + auto msg = std::get_if(&r); + REQUIRE(msg); + CHECK(msg->length == sz); + auto target = std::get_if(&msg->message); + REQUIRE(target); + } + + SECTION("join_relay_request") { + auto sz = serialize(join_relay_request_t{}, buff); + REQUIRE(sz); + auto r = parse(buff); + auto msg = std::get_if(&r); + REQUIRE(msg); + CHECK(msg->length == sz); + auto target = std::get_if(&msg->message); + REQUIRE(target); + } + + SECTION("join_session_request") { + auto source = join_session_request_t{"lorem impsum dolor"}; + auto sz = serialize(source, buff); + REQUIRE(sz); + auto r = parse(buff); + auto msg = std::get_if(&r); + REQUIRE(msg); + CHECK(msg->length == sz); + auto target = std::get_if(&msg->message); + REQUIRE(target); + CHECK(target->key == source.key); + } + + SECTION("response") { + auto source = response_t{404, "not found"}; + auto sz = serialize(source, buff); + REQUIRE(sz); + auto r = parse(buff); + auto msg = std::get_if(&r); + REQUIRE(msg); + CHECK(msg->length == sz); + auto target = std::get_if(&msg->message); + REQUIRE(target); + CHECK(target->code == source.code); + CHECK(target->details == source.details); + } + + SECTION("connect_request") { + auto source = connect_request_t{"lorem impsum dolor"}; + auto sz = serialize(source, buff); + REQUIRE(sz); + auto r = parse(buff); + auto msg = std::get_if(&r); + REQUIRE(msg); + CHECK(msg->length == sz); + auto target = std::get_if(&msg->message); + REQUIRE(target); + CHECK(target->device_id == source.device_id); + } + + SECTION("session_invitation") { + auto source = session_invitation_t{"lorem", "impsum", "dolor", 1234, true}; + auto sz = serialize(source, buff); + REQUIRE(sz); + auto r = parse(buff); + auto msg = std::get_if(&r); + REQUIRE(msg); + CHECK(msg->length == sz); + auto target = std::get_if(&msg->message); + REQUIRE(target); + CHECK(target->from == source.from); + CHECK(target->key == source.key); + CHECK(target->address == source.address); + CHECK(target->server_socket == source.server_socket); + } + + SECTION("session_invitation (host of zeroes)") { + auto zeros = std::string("\0\0\0\0", 4); + auto source = session_invitation_t{"lorem", "impsum", zeros, 1234, true}; + auto sz = serialize(source, buff); + REQUIRE(sz); + auto r = parse(buff); + auto msg = std::get_if(&r); + REQUIRE(msg); + CHECK(msg->length == sz); + auto target = std::get_if(&msg->message); + REQUIRE(target); + CHECK(target->from == source.from); + CHECK(target->key == source.key); + CHECK(target->address.empty()); + CHECK(target->server_socket == source.server_socket); + } + + SECTION("response sample") { + const unsigned char data[] = {0x9e, 0x79, 0xbc, 0x40, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, + 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x00}; + auto ptr = reinterpret_cast(data); + auto r = parse({ptr, sizeof(data)}); + auto msg = std::get_if(&r); + REQUIRE(msg); + CHECK(msg->length == 28); + auto target = std::get_if(&msg->message); + REQUIRE(target); + CHECK(target->code == 0); + auto details = std::string_view("success"); + CHECK(target->details == details); + } + + SECTION("response sample-2") { + const unsigned char data[] = {0x9e, 0x79, 0xbc, 0x40, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x6e, 0x6f, + 0x74, 0x20, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x00, 0x00, 0x00}; + auto ptr = reinterpret_cast(data); + auto r = parse({ptr, sizeof(data)}); + auto msg = std::get_if(&r); + REQUIRE(msg); + CHECK(msg->length == 32); + auto target = std::get_if(&msg->message); + REQUIRE(target); + CHECK(target->code == 1); + auto details = std::string_view("not found"); + CHECK(target->details == details); + } + + SECTION("session invitation sample") { + const unsigned char data[] = {0x9e, 0x79, 0xbc, 0x40, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x4a, 0x00, + 0x00, 0x00, 0x20, 0xf8, 0x31, 0xf5, 0x75, 0xea, 0x61, 0x8a, 0x2f, 0x15, 0xef, + 0x67, 0x68, 0x36, 0x4a, 0x62, 0x89, 0xfc, 0x76, 0xb6, 0x73, 0xc8, 0x5a, 0x2a, + 0xbe, 0x60, 0x8f, 0x4a, 0xff, 0x27, 0xba, 0x39, 0x02, 0x00, 0x00, 0x00, 0x12, + 0x6c, 0x6f, 0x72, 0x65, 0x6d, 0x2d, 0x69, 0x6d, 0x73, 0x70, 0x75, 0x6d, 0x2d, + 0x64, 0x6f, 0x6c, 0x6f, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x39, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}; + auto ptr = reinterpret_cast(data); + auto str = std::string_view(ptr, sizeof(data)); + auto r = parse(str); + auto msg = std::get_if(&r); + REQUIRE(msg); + CHECK(msg->length == str.size()); + auto target = std::get_if(&msg->message); + REQUIRE(target); + CHECK(target->from.size() == 32); + CHECK(target->key == "lorem-imspum-dolor"); + CHECK(target->address == ""); + CHECK(target->port == 12345); + CHECK(target->server_socket); + } + } + + SECTION("incompplete") { + auto r = parse(buff); + auto msg = std::get_if(&r); + REQUIRE(msg); + serialize(ping_t{}, buff); + r = parse(std::string_view(buff.data(), buff.size() - 1)); + msg = std::get_if(&r); + REQUIRE(msg); + } + + SECTION("protocol errors") { + SECTION("wrong magic") { + auto sz = serialize(ping_t{}, buff); + REQUIRE(sz); + buff[0] = -1; + auto r = parse(buff); + CHECK(std::get_if(&r)); + } + SECTION("wrong size") { + auto sz = serialize(ping_t{}, buff); + REQUIRE(sz); + buff[4] = -1; + auto r = parse(buff); + CHECK(std::get_if(&r)); + } + SECTION("wrong type") { + auto sz = serialize(ping_t{}, buff); + REQUIRE(sz); + buff[9] = -1; + auto r = parse(buff); + CHECK(std::get_if(&r)); + } + } +} + +TEST_CASE("endpoing parsing", "[relay]") { + std::string body = R""( +{ + "relays": [ + { + "url": "relay://130.61.176.206:22067/?id=OAKAXEX-7HE764M-5EWVN7U-SZCQU4D-ZPXF2TY-SNTL2LL-Y5RVGVM-U7WBRA3&pingInterval=1m30s&networkTimeout=2m0s&sessionLimitBps=0&globalLimitBps=0&statusAddr=:22070&providedBy=ina", + "location": { + "latitude": 50.1049, + "longitude": 8.6295, + "city": "Frankfurt am Main", + "country": "DE", + "continent": "EU" + }, + "stats": { + "startTime": "2022-04-16T09:12:21.941097618Z", + "uptimeSeconds": 1312802, + "numPendingSessionKeys": 0, + "numActiveSessions": 46, + "numConnections": 494, + "numProxies": 88, + "bytesProxied": 493207937616, + "goVersion": "go1.16.3", + "goOS": "linux", + "goArch": "arm64", + "goMaxProcs": 4, + "goNumRoutine": 1136, + "kbps10s1m5m15m30m60m": [ + 241, + 284, + 309, + 332, + 355, + 312 + ], + "options": { + "network-timeout": 120, + "ping-interval": 60, + "message-timeout": 60, + "per-session-rate": 0, + "global-rate": 0, + "pools": [ + "https://relays.syncthing.net/endpoint" + ], + "provided-by": "ina" + } + }, + "statsRetrieved": "2022-05-01T13:52:24.524417759Z" + } +] +} + )""; + auto r = parse_endpoint(body); + REQUIRE(r); + REQUIRE(r.value().size() == 1); + auto relay = r.value()[0]; + CHECK(relay->uri.full == "relay://130.61.176.206:22067/" + "?id=OAKAXEX-7HE764M-5EWVN7U-SZCQU4D-ZPXF2TY-SNTL2LL-Y5RVGVM-U7WBRA3&pingInterval=1m30s&" + "networkTimeout=2m0s&sessionLimitBps=0&globalLimitBps=0&statusAddr=:22070&providedBy=ina"); + CHECK(relay->device_id.get_short() == "OAKAXEX"); + auto &l = relay->location; + CHECK(abs(l.latitude - 50.1049) < 0.0001); + CHECK(abs(l.longitude - 8.6295) < 0.0001); + CHECK(l.city == "Frankfurt am Main"); + CHECK(l.country == "DE"); + CHECK(l.continent == "EU"); + CHECK(relay->ping_interval == pt::seconds{90}); + + auto device = parse_device(relay->uri); + REQUIRE(device); + CHECK(device.value() == relay->device_id); +} diff --git a/tests/033-diffs-trivial.cpp b/tests/033-diffs-trivial.cpp index d0c350bf..7a4a32d1 100644 --- a/tests/033-diffs-trivial.cpp +++ b/tests/033-diffs-trivial.cpp @@ -28,15 +28,16 @@ TEST_CASE("peer state update", "[model]") { cluster->get_devices().put(peer_device); rotor::address_ptr_t addr; - auto diff = diff::cluster_diff_ptr_t(new diff::peer::peer_state_t(*cluster, peer_id.get_sha256(), addr, true)); - CHECK(peer_device->is_online() == false); + auto diff = diff::cluster_diff_ptr_t( + new diff::peer::peer_state_t(*cluster, peer_id.get_sha256(), addr, device_state_t::online)); + CHECK(peer_device->get_state() == model::device_state_t::offline); REQUIRE(diff->apply(*cluster)); - CHECK(peer_device->is_online() == true); + CHECK(peer_device->get_state() == model::device_state_t::online); - diff = new diff::peer::peer_state_t(*cluster, peer_id.get_sha256(), addr, false); + diff = new diff::peer::peer_state_t(*cluster, peer_id.get_sha256(), addr, device_state_t::offline); REQUIRE(diff->apply(*cluster)); - CHECK(peer_device->is_online() == false); + CHECK(peer_device->get_state() == model::device_state_t::offline); } TEST_CASE("with file", "[model]") { diff --git a/tests/073-dialer.cpp b/tests/073-dialer.cpp index 890a5a55..4187beeb 100644 --- a/tests/073-dialer.cpp +++ b/tests/073-dialer.cpp @@ -104,14 +104,14 @@ void test_dialer() { auto diff = model::diff::cluster_diff_ptr_t{}; auto sample_addr = sup->get_address(); auto peer_id = peer_device->device_id().get_sha256(); - diff = new model::diff::peer::peer_state_t(*cluster, peer_id, sample_addr, true); + diff = new model::diff::peer::peer_state_t(*cluster, peer_id, sample_addr, device_state_t::online); sup->send(sup->get_address(), diff); sup->do_process(); CHECK(!discovery); CHECK(sup->timers.size() == 0); - diff = new model::diff::peer::peer_state_t(*cluster, peer_id, sample_addr, false); + diff = new model::diff::peer::peer_state_t(*cluster, peer_id, sample_addr, device_state_t::offline); sup->send(sup->get_address(), diff); sup->do_process(); diff --git a/tests/077-initiator.cpp b/tests/077-initiator.cpp new file mode 100644 index 00000000..6f642138 --- /dev/null +++ b/tests/077-initiator.cpp @@ -0,0 +1,916 @@ +#include "catch.hpp" +#include "test-utils.h" +#include "access.h" + +#include "utils/tls.h" +#include "model/cluster.h" +#include "model/messages.h" +#include "net/names.h" +#include "net/initiator_actor.h" +#include "net/resolver_actor.h" +#include "proto/relay_support.h" +#include "transport/stream.h" +#include + +using namespace syncspirit; +using namespace syncspirit::test; +using namespace syncspirit::model; +using namespace syncspirit::net; + +namespace asio = boost::asio; +namespace sys = boost::system; +namespace r = rotor; +namespace ra = r::asio; + +using configure_callback_t = std::function; +using finish_callback_t = std::function; + +auto timeout = r::pt::time_duration{r::pt::millisec{2000}}; +auto host = "127.0.0.1"; + +struct supervisor_t : ra::supervisor_asio_t { + using ra::supervisor_asio_t::supervisor_asio_t; + + void configure(r::plugin::plugin_base_t &plugin) noexcept override { + ra::supervisor_asio_t::configure(plugin); + plugin.with_casted( + [&](auto &p) { p.register_name(names::coordinator, get_address()); }); + if (configure_callback) { + configure_callback(plugin); + } + } + + void shutdown_finish() noexcept override { + ra::supervisor_asio_t::shutdown_finish(); + if (finish_callback) { + finish_callback(); + } + } + + auto get_state() noexcept { return state; } + + finish_callback_t finish_callback; + configure_callback_t configure_callback; +}; + +using supervisor_ptr_t = r::intrusive_ptr_t; +using actor_ptr_t = r::intrusive_ptr_t; + +struct fixture_t { + using acceptor_t = asio::ip::tcp::acceptor; + using ready_ptr_t = r::intrusive_ptr_t; + using diff_ptr_t = r::intrusive_ptr_t; + using diff_msgs_t = std::vector; + + fixture_t() noexcept : ctx(io_ctx), acceptor(io_ctx), peer_sock(io_ctx) { + utils::set_default("trace"); + log = utils::get_logger("fixture"); + } + + virtual void finish() { + acceptor.cancel(); + if (peer_trans) { + peer_trans->cancel(); + } + } + + void run() noexcept { + + auto strand = std::make_shared(io_ctx); + sup = ctx.create_supervisor().strand(strand).timeout(timeout).create_registry().finish(); + sup->configure_callback = [&](r::plugin::plugin_base_t &plugin) { + plugin.template with_casted([&](auto &p) { + using connected_t = typename ready_ptr_t::element_type; + using diff_t = typename diff_ptr_t::element_type; + p.subscribe_actor(r::lambda([&](connected_t &msg) { + connected_message = &msg; + LOG_INFO(log, "received message::peer_connected_t"); + })); + p.subscribe_actor(r::lambda([&](diff_t &msg) { + diff_msgs.emplace_back(&msg); + LOG_INFO(log, "received diff message"); + })); + }); + }; + sup->finish_callback = [&]() { finish(); }; + sup->start(); + + sup->create_actor().resolve_timeout(timeout / 2).timeout(timeout).finish(); + sup->do_process(); + + my_keys = utils::generate_pair("me").value(); + peer_keys = utils::generate_pair("peer").value(); + + auto md = model::device_id_t::from_cert(my_keys.cert_data).value(); + auto pd = model::device_id_t::from_cert(peer_keys.cert_data).value(); + + my_device = device_t::create(md, "my-device").value(); + peer_device = device_t::create(pd, "peer-device").value(); + + auto ep = asio::ip::tcp::endpoint(asio::ip::make_address(host), 0); + acceptor.open(ep.protocol()); + acceptor.bind(ep); + acceptor.listen(); + listening_ep = acceptor.local_endpoint(); + peer_uri = utils::parse(get_uri(listening_ep)).value(); + log->debug("listening on {}", peer_uri.full); + initiate_accept(); + + cluster = new cluster_t(my_device, 1); + + cluster->get_devices().put(my_device); + cluster->get_devices().put(peer_device); + + main(); + } + + virtual void initiate_accept() noexcept { + acceptor.async_accept(peer_sock, [this](auto ec) { this->accept(ec); }); + } + + virtual std::string get_uri(const asio::ip::tcp::endpoint &endpoint) noexcept { + return fmt::format("tcp://{}", listening_ep); + } + + virtual void accept(const sys::error_code &ec) noexcept { + LOG_INFO(log, "accept, ec: {}", ec.message()); + peer_trans = transport::initiate_tls_passive(*sup, peer_keys, std::move(peer_sock)); + initiate_peer_handshake(); + } + + virtual void initiate_peer_handshake() noexcept { + transport::handshake_fn_t handshake_fn = [this](bool valid_peer, utils::x509_t &cert, + const tcp::endpoint &peer_endpoint, + const model::device_id_t *peer_device) { + valid_handshake = valid_peer; + on_peer_hanshake(); + }; + transport::error_fn_t on_error = [](const auto &) {}; + peer_trans->async_handshake(handshake_fn, on_error); + } + + virtual void on_peer_hanshake() noexcept { LOG_INFO(log, "peer handshake"); } + + void initiate_active() noexcept { + tcp::resolver resolver(io_ctx); + auto addresses = resolver.resolve(host, std::to_string(listening_ep.port())); + peer_trans = transport::initiate_tls_active(*sup, peer_keys, my_device->device_id(), peer_uri); + + transport::error_fn_t on_error = [&](auto &ec) { + LOG_WARN(log, "initiate_active/connect, err: {}", ec.message()); + }; + transport::connect_fn_t on_connect = [&](auto arg) { + LOG_INFO(log, "initiate_active/peer connect"); + active_connect(); + }; + + peer_trans->async_connect(addresses, on_connect, on_error); + } + + virtual void active_connect() { + LOG_TRACE(log, "active_connect"); + transport::handshake_fn_t handshake_fn = [this](bool valid_peer, utils::x509_t &cert, + const tcp::endpoint &peer_endpoint, + const model::device_id_t *peer_device) { + valid_handshake = true; + LOG_INFO(log, "test_passive_success/peer handshake"); + }; + transport::error_fn_t on_hs_error = [&](const auto &ec) { + LOG_WARN(log, "test_passive_success/peer handshake, err: {}", ec.message()); + }; + peer_trans->async_handshake(handshake_fn, on_hs_error); + } + + virtual void main() noexcept {} + + virtual actor_ptr_t create_actor() noexcept { + return sup->create_actor() + .timeout(timeout) + .peer_device_id(peer_device->device_id()) + .relay_session(relay_session) + .uris({peer_uri}) + .cluster(use_model ? cluster : nullptr) + .sink(sup->get_address()) + .ssl_pair(&my_keys) + .router(*sup) + .escalate_failure() + .finish(); + } + + virtual actor_ptr_t create_passive_actor() noexcept { + return sup->create_actor() + .timeout(timeout) + .sock(std::move(peer_sock)) + .ssl_pair(&my_keys) + .router(*sup) + .cluster(cluster) + .sink(sup->get_address()) + .escalate_failure() + .finish(); + } + + cluster_ptr_t cluster; + asio::io_context io_ctx{1}; + ra::system_context_asio_t ctx; + acceptor_t acceptor; + supervisor_ptr_t sup; + asio::ip::tcp::endpoint listening_ep; + utils::logger_t log; + asio::ip::tcp::socket peer_sock; + config::bep_config_t bep_config; + utils::key_pair_t my_keys; + utils::key_pair_t peer_keys; + utils::URI peer_uri; + model::device_ptr_t my_device; + model::device_ptr_t peer_device; + transport::stream_sp_t peer_trans; + ready_ptr_t connected_message; + diff_msgs_t diff_msgs; + std::string relay_session; + bool use_model = true; + + bool valid_handshake = false; +}; + +void test_connect_timeout() { + struct F : fixture_t { + void initiate_accept() noexcept override {} + void main() noexcept override { + auto act = create_actor(); + io_ctx.run(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(!connected_message); + } + }; + F().run(); +} + +void test_connect_unsupproted_proto() { + struct F : fixture_t { + std::string get_uri(const asio::ip::tcp::endpoint &) noexcept override { + return fmt::format("xxx://{}", listening_ep); + } + void main() noexcept override { + auto act = create_actor(); + io_ctx.run(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(!connected_message); + } + }; + F().run(); +} + +void test_handshake_timeout() { + struct F : fixture_t { + + void accept(const sys::error_code &ec) noexcept override { LOG_INFO(log, "accept (ignoring)", ec.message()); } + + void main() noexcept override { + auto act = create_actor(); + io_ctx.run(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(!connected_message); + REQUIRE(diff_msgs.size() == 2); + CHECK(diff_msgs[0]->payload.diff->apply(*cluster)); + CHECK(peer_device->get_state() == device_state_t::dialing); + CHECK(diff_msgs[1]->payload.diff->apply(*cluster)); + CHECK(peer_device->get_state() == device_state_t::offline); + } + }; + F().run(); +} + +void test_handshake_garbage() { + struct F : fixture_t { + + void accept(const sys::error_code &ec) noexcept override { + auto buff = asio::buffer("garbage-garbage-garbage"); + peer_sock.write_some(buff); + } + + void main() noexcept override { + auto act = create_actor(); + io_ctx.run(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(!connected_message); + REQUIRE(diff_msgs.size() == 2); + CHECK(diff_msgs[0]->payload.diff->apply(*cluster)); + CHECK(peer_device->get_state() == device_state_t::dialing); + CHECK(diff_msgs[1]->payload.diff->apply(*cluster)); + CHECK(peer_device->get_state() == device_state_t::offline); + } + }; + F().run(); +} + +void test_connection_refused() { + struct F : fixture_t { + + std::string get_uri(const asio::ip::tcp::endpoint &) noexcept override { + return fmt::format("tcp://{}:0", host); + } + + void main() noexcept override { + auto act = create_actor(); + io_ctx.run(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(!connected_message); + } + }; + F().run(); +} + +void test_connection_refused_no_model() { + struct F : fixture_t { + F() { use_model = false; } + + std::string get_uri(const asio::ip::tcp::endpoint &) noexcept override { + return fmt::format("tcp://{}:0", host); + } + + void main() noexcept override { + auto act = create_actor(); + io_ctx.run(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(!connected_message); + } + }; + F().run(); +} + +void test_resolve_failure() { + struct F : fixture_t { + + std::string get_uri(const asio::ip::tcp::endpoint &) noexcept override { return "tcp://x.example.com"; } + + void main() noexcept override { + auto act = create_actor(); + io_ctx.run(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(!connected_message); + } + }; + F().run(); +} + +void test_success() { + struct F : fixture_t { + void main() noexcept override { + auto act = create_actor(); + io_ctx.run(); + CHECK(sup->get_state() == r::state_t::OPERATIONAL); + REQUIRE(connected_message); + CHECK(connected_message->payload.proto == "tcp"); + CHECK(connected_message->payload.peer_device_id == peer_device->device_id()); + CHECK(valid_handshake); + sup->do_shutdown(); + sup->do_process(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + REQUIRE(diff_msgs.size() == 1); + CHECK(diff_msgs[0]->payload.diff->apply(*cluster)); + CHECK(peer_device->get_state() == device_state_t::dialing); + } + }; + F().run(); +} + +void test_success_no_model() { + struct F : fixture_t { + F() { use_model = false; } + + void main() noexcept override { + auto act = create_actor(); + io_ctx.run(); + CHECK(sup->get_state() == r::state_t::OPERATIONAL); + CHECK(connected_message); + CHECK(connected_message->payload.peer_device_id == peer_device->device_id()); + CHECK(valid_handshake); + sup->do_shutdown(); + sup->do_process(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + REQUIRE(diff_msgs.size() == 0); + } + }; + F().run(); +} + +struct passive_fixture_t : fixture_t { + actor_ptr_t act; + bool active_connect_invoked = false; + + void active_connect() override { + LOG_TRACE(log, "active_connect"); + if (!act || active_connect_invoked) { + return; + } + active_connect_invoked = true; + active_connect_impl(); + } + + virtual void active_connect_impl() { fixture_t::active_connect(); } + + void accept(const sys::error_code &ec) noexcept override { + LOG_INFO(log, "test_passive_success/accept, ec: {}", ec.message()); + act = create_passive_actor(); + sup->do_process(); + active_connect(); + } +}; + +void test_passive_success() { + struct F : passive_fixture_t { + void main() noexcept override { + initiate_active(); + io_ctx.run(); + CHECK(sup->get_state() == r::state_t::OPERATIONAL); + REQUIRE(connected_message); + CHECK(connected_message->payload.proto == "tcp"); + CHECK(connected_message->payload.peer_device_id == peer_device->device_id()); + CHECK(valid_handshake); + sup->do_shutdown(); + sup->do_process(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + } + }; + F().run(); +} + +void test_passive_garbage() { + struct F : passive_fixture_t { + + tcp::socket client_sock; + tcp::resolver::results_type addresses; + + F() : client_sock{io_ctx} {} + + void active_connect_impl() noexcept override { + tcp::resolver resolver(io_ctx); + addresses = resolver.resolve(host, std::to_string(listening_ep.port())); + asio::async_connect(client_sock, addresses.begin(), addresses.end(), [&](auto ec, auto addr) { + LOG_INFO(log, "test_passive_garbage/peer connect, ec: {}", ec.message()); + auto buff = asio::buffer("garbage-garbage-garbage"); + client_sock.write_some(buff); + sup->do_process(); + }); + } + + void main() noexcept override { + initiate_active(); + io_ctx.run(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(!connected_message); + } + }; + F().run(); +} + +void test_passive_timeout() { + struct F : passive_fixture_t { + + void active_connect() noexcept override { LOG_INFO(log, "test_passive_timeout/active_connect NOOP"); } + + void main() noexcept override { + initiate_active(); + io_ctx.run(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(!connected_message); + } + }; + F().run(); +} + +struct passive_relay_fixture_t : fixture_t { + std::string rx_buff; + bool initiate_handshake = true; + passive_relay_fixture_t() { + relay_session = "relay-session-key"; + rx_buff.resize(128); + } + + void on_read(size_t bytes) noexcept { + LOG_TRACE(log, "read (relay/passive), {} bytes", bytes); + auto r = proto::relay::parse({rx_buff.data(), bytes}); + auto &wrapped = std::get(r); + auto &msg = std::get(wrapped.message); + CHECK(msg.key == relay_session); + relay_reply(); + } + + virtual void on_write(size_t bytes) noexcept { + LOG_TRACE(log, "write (relay/passive), {} bytes", bytes); + + if (initiate_handshake) { + auto upgradeable = static_cast(peer_trans.get()); + auto ssl = transport::ssl_junction_t{my_device->device_id(), &peer_keys, false, "bep"}; + peer_trans = upgradeable->upgrade(ssl, true); + initiate_peer_handshake(); + } + } + + virtual void relay_reply() noexcept { write(proto::relay::response_t{0, "success"}); } + + virtual void write(const proto::relay::message_t &msg) noexcept { + proto::relay::serialize(msg, rx_buff); + auto buff = asio::buffer(rx_buff); + transport::error_fn_t err_fn([&](auto ec) { log->error("(relay/passive), read_err: {}", ec.message()); }); + transport::io_fn_t write_fn = [this](size_t bytes) { on_write(bytes); }; + peer_trans->async_send(asio::buffer(rx_buff), write_fn, err_fn); + } + + void accept(const sys::error_code &ec) noexcept override { + LOG_INFO(log, "accept (relay/passive), ec: {}", ec.message()); + auto uri = utils::parse("tcp://127.0.0.1:0/").value(); + auto cfg = transport::transport_config_t{{}, uri, *sup, std::move(peer_sock)}; + peer_trans = transport::initiate_stream(cfg); + + transport::error_fn_t read_err_fn([&](auto ec) { log->error("(relay/passive), read_err: {}", ec.message()); }); + transport::io_fn_t read_fn = [this](size_t bytes) { on_read(bytes); }; + peer_trans->async_recv(asio::buffer(rx_buff), read_fn, read_err_fn); + } +}; + +void test_relay_passive_success() { + struct F : passive_relay_fixture_t { + void main() noexcept override { + auto act = create_actor(); + io_ctx.run(); + CHECK(sup->get_state() == r::state_t::OPERATIONAL); + REQUIRE(connected_message); + CHECK(connected_message->payload.proto == "relay"); + CHECK(connected_message->payload.peer_device_id == peer_device->device_id()); + CHECK(valid_handshake); + sup->do_shutdown(); + sup->do_process(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(diff_msgs.size() == 0); + } + }; + F().run(); +} + +void test_relay_passive_gargabe() { + struct F : passive_relay_fixture_t { + + void write(const proto::relay::message_t &) noexcept override { + rx_buff = "garbage-garbage-garbae"; + initiate_handshake = false; + auto buff = asio::buffer(rx_buff); + transport::error_fn_t err_fn([&](auto ec) { log->error("(relay/passive), read_err: {}", ec.message()); }); + transport::io_fn_t write_fn = [this](size_t bytes) { on_write(bytes); }; + peer_trans->async_send(asio::buffer(rx_buff), write_fn, err_fn); + } + + void main() noexcept override { + auto act = create_actor(); + io_ctx.run(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(!connected_message); + CHECK(!valid_handshake); + sup->do_shutdown(); + sup->do_process(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(diff_msgs.size() == 0); + } + }; + F().run(); +} + +void test_relay_passive_wrong_message() { + struct F : passive_relay_fixture_t { + + void relay_reply() noexcept override { write(proto::relay::pong_t{}); } + + void main() noexcept override { + initiate_handshake = false; + auto act = create_actor(); + io_ctx.run(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(!connected_message); + CHECK(!valid_handshake); + sup->do_shutdown(); + sup->do_process(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(diff_msgs.size() == 0); + } + }; + F().run(); +} + +void test_relay_passive_unsuccessful_join() { + struct F : passive_relay_fixture_t { + + void relay_reply() noexcept override { write(proto::relay::response_t{5, "some-fail-reason"}); } + + void main() noexcept override { + initiate_handshake = false; + auto act = create_actor(); + io_ctx.run(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(!connected_message); + CHECK(!valid_handshake); + sup->do_shutdown(); + sup->do_process(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(diff_msgs.size() == 0); + } + }; + F().run(); +} + +void test_relay_malformed_uri() { + struct F : fixture_t { + std::string get_uri(const asio::ip::tcp::endpoint &endpoint) noexcept override { + return fmt::format("relay://{}", listening_ep); + } + + void main() noexcept override { + auto act = create_actor(); + io_ctx.run(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(!connected_message); + CHECK(!valid_handshake); + sup->do_shutdown(); + sup->do_process(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(diff_msgs.size() == 2); + } + }; + F().run(); +} + +void test_relay_active_wrong_relay_deviceid() { + struct F : fixture_t { + + std::string get_uri(const asio::ip::tcp::endpoint &endpoint) noexcept override { + return fmt::format("relay://{}?id={}", listening_ep, my_device->device_id().get_value()); + } + + void main() noexcept override { + auto act = create_actor(); + io_ctx.run(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(!connected_message); + CHECK(!valid_handshake); + sup->do_shutdown(); + sup->do_process(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(diff_msgs.size() == 2); + } + }; + F().run(); +} + +struct active_relay_fixture_t : fixture_t { + utils::key_pair_t relay_keys; + model::device_id_t relay_device; + std::string rx_buff; + std::string session_key = "lorem-session-dolor"; + transport::stream_sp_t relay_trans; + bool session_mode = false; + + active_relay_fixture_t() { + relay_keys = utils::generate_pair("relay").value(); + relay_device = model::device_id_t::from_cert(relay_keys.cert_data).value(); + rx_buff.resize(128); + } + + std::string get_uri(const asio::ip::tcp::endpoint &endpoint) noexcept override { + return fmt::format("relay://{}?id={}", listening_ep, relay_device.get_value()); + } + + void accept(const sys::error_code &ec) noexcept override { + LOG_INFO(log, "relay/accept, ec: {}", ec.message()); + if (!session_mode) { + relay_trans = transport::initiate_tls_passive(*sup, relay_keys, std::move(peer_sock)); + transport::handshake_fn_t handshake_fn = [this](bool valid_peer, utils::x509_t &cert, + const tcp::endpoint &peer_endpoint, + const model::device_id_t *peer_device) { + valid_handshake = valid_peer; + on_relay_hanshake(); + }; + transport::error_fn_t on_error = [](const auto &) {}; + relay_trans->async_handshake(handshake_fn, on_error); + return; + } + auto uri = utils::parse("tcp://127.0.0.1:0/").value(); + auto cfg = transport::transport_config_t{{}, uri, *sup, std::move(peer_sock)}; + peer_trans = transport::initiate_stream(cfg); + + transport::error_fn_t read_err_fn([&](auto ec) { log->error("(relay/active), read_err: {}", ec.message()); }); + transport::io_fn_t read_fn = [this](size_t bytes) { on_read_peer(bytes); }; + peer_trans->async_recv(asio::buffer(rx_buff), read_fn, read_err_fn); + } + + virtual void on_relay_hanshake() noexcept { + transport::error_fn_t read_err_fn([&](auto ec) { log->error("(relay/active), read_err: {}", ec.message()); }); + transport::io_fn_t read_fn = [this](size_t bytes) { on_read(bytes); }; + relay_trans->async_recv(asio::buffer(rx_buff), read_fn, read_err_fn); + } + + virtual void relay_reply() noexcept { + write(relay_trans, proto::relay::session_invitation_t{std::string(peer_device->device_id().get_sha256()), + session_key, "", listening_ep.port(), false}); + } + + virtual void session_reply() noexcept { write(peer_trans, proto::relay::response_t{0, "ok"}); } + + virtual void write(transport::stream_sp_t &stream, const proto::relay::message_t &msg) noexcept { + proto::relay::serialize(msg, rx_buff); + auto buff = asio::buffer(rx_buff); + transport::error_fn_t err_fn([&](auto ec) { log->error("(relay/passive), read_err: {}", ec.message()); }); + transport::io_fn_t write_fn = [this](size_t bytes) { on_write(bytes); }; + stream->async_send(asio::buffer(rx_buff), write_fn, err_fn); + } + + virtual void on_read_peer(size_t bytes) { + log->debug("(relay/active) read peer {} bytes", bytes); + auto r = proto::relay::parse({rx_buff.data(), bytes}); + auto &wrapped = std::get(r); + auto &msg = std::get(wrapped.message); + CHECK(msg.key == session_key); + session_reply(); + } + + virtual void on_read(size_t bytes) { + log->debug("(relay/active) read {} bytes", bytes); + auto r = proto::relay::parse({rx_buff.data(), bytes}); + auto &wrapped = std::get(r); + auto &msg = std::get(wrapped.message); + CHECK(msg.device_id == peer_device->device_id().get_sha256()); + relay_reply(); + } + + virtual void on_write(size_t bytes) { + log->debug("(relay/active) write {} bytes", bytes); + if (!session_mode) { + acceptor.async_accept(peer_sock, [this](auto ec) { this->accept(ec); }); + session_mode = true; + } else { + auto upgradeable = static_cast(peer_trans.get()); + auto ssl = transport::ssl_junction_t{my_device->device_id(), &peer_keys, false, "bep"}; + peer_trans = upgradeable->upgrade(ssl, false); + initiate_peer_handshake(); + } + } +}; + +void test_relay_active_success() { + struct F : active_relay_fixture_t { + void main() noexcept override { + auto act = create_actor(); + io_ctx.run(); + CHECK(sup->get_state() == r::state_t::OPERATIONAL); + REQUIRE(connected_message); + CHECK(connected_message->payload.proto == "relay"); + CHECK(connected_message->payload.peer_device_id == peer_device->device_id()); + CHECK(valid_handshake); + sup->do_shutdown(); + sup->do_process(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + REQUIRE(diff_msgs.size() == 1); + CHECK(diff_msgs[0]->payload.diff->apply(*cluster)); + CHECK(peer_device->get_state() == device_state_t::dialing); + } + }; + F().run(); +} + +void test_relay_wrong_device() { + struct F : active_relay_fixture_t { + + void relay_reply() noexcept override { + write(relay_trans, proto::relay::session_invitation_t{std::string(relay_device.get_sha256()), session_key, + "", listening_ep.port(), false}); + } + void on_write(size_t bytes) override {} + + void main() noexcept override { + auto act = create_actor(); + io_ctx.run(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(!connected_message); + CHECK(valid_handshake); + sup->do_shutdown(); + sup->do_process(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(diff_msgs.size() == 2); + } + }; + F().run(); +} + +void test_relay_non_conneteable() { + struct F : active_relay_fixture_t { + + void relay_reply() noexcept override { + write(relay_trans, proto::relay::session_invitation_t{std::string(peer_device->device_id().get_sha256()), + session_key, "", 0, false}); + } + + void main() noexcept override { + auto act = create_actor(); + io_ctx.run(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(!connected_message); + sup->do_shutdown(); + sup->do_process(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(diff_msgs.size() == 2); + } + }; + F().run(); +} + +void test_relay_malformed_address() { + struct F : active_relay_fixture_t { + + void relay_reply() noexcept override { + write(relay_trans, proto::relay::session_invitation_t{std::string(peer_device->device_id().get_sha256()), + session_key, "8.8.8.8z", listening_ep.port(), false}); + } + + void main() noexcept override { + auto act = create_actor(); + io_ctx.run(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(!connected_message); + sup->do_shutdown(); + sup->do_process(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(diff_msgs.size() == 2); + } + }; + F().run(); +} + +void test_relay_garbage_reply() { + struct F : active_relay_fixture_t { + + void write(transport::stream_sp_t &stream, const proto::relay::message_t &) noexcept override { + rx_buff = "garbage-garbage-garbage"; + auto buff = asio::buffer(rx_buff); + transport::error_fn_t err_fn([&](auto ec) { log->error("(relay/passive), read_err: {}", ec.message()); }); + transport::io_fn_t write_fn = [this](size_t bytes) { on_write(bytes); }; + stream->async_send(asio::buffer(rx_buff), write_fn, err_fn); + } + + void on_write(size_t bytes) override {} + + void main() noexcept override { + auto act = create_actor(); + io_ctx.run(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(!connected_message); + sup->do_shutdown(); + sup->do_process(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(diff_msgs.size() == 2); + } + }; + F().run(); +} + +void test_relay_noninvitation_reply() { + struct F : active_relay_fixture_t { + + void relay_reply() noexcept override { write(relay_trans, proto::relay::pong_t{}); } + void on_write(size_t bytes) override {} + + void main() noexcept override { + auto act = create_actor(); + io_ctx.run(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(!connected_message); + sup->do_shutdown(); + sup->do_process(); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + CHECK(diff_msgs.size() == 2); + } + }; + F().run(); +} + +REGISTER_TEST_CASE(test_connect_unsupproted_proto, "test_connect_unsupproted_proto", "[initiator]"); +REGISTER_TEST_CASE(test_connect_timeout, "test_connect_timeout", "[initiator]"); +REGISTER_TEST_CASE(test_handshake_timeout, "test_handshake_timeout", "[initiator]"); +REGISTER_TEST_CASE(test_handshake_garbage, "test_handshake_garbage", "[initiator]"); +REGISTER_TEST_CASE(test_connection_refused, "test_connection_refused", "[initiator]"); +REGISTER_TEST_CASE(test_connection_refused_no_model, "test_connection_refused_no_model", "[initiator]"); +REGISTER_TEST_CASE(test_resolve_failure, "test_resolve_failure", "[initiator]"); +REGISTER_TEST_CASE(test_success, "test_success", "[initiator]"); +REGISTER_TEST_CASE(test_success_no_model, "test_success_no_model", "[initiator]"); +REGISTER_TEST_CASE(test_passive_success, "test_passive_success", "[initiator]"); +REGISTER_TEST_CASE(test_passive_garbage, "test_passive_garbage", "[initiator]"); +REGISTER_TEST_CASE(test_passive_timeout, "test_passive_timeout", "[initiator]"); +REGISTER_TEST_CASE(test_relay_passive_success, "test_relay_passive_success", "[initiator]"); +REGISTER_TEST_CASE(test_relay_passive_gargabe, "test_relay_passive_gargabe", "[initiator]"); +REGISTER_TEST_CASE(test_relay_passive_wrong_message, "test_relay_passive_wrong_message", "[initiator]"); +REGISTER_TEST_CASE(test_relay_passive_unsuccessful_join, "test_relay_passive_unsuccessful_join", "[initiator]"); +REGISTER_TEST_CASE(test_relay_malformed_uri, "test_relay_malformed_uri", "[initiator]"); +REGISTER_TEST_CASE(test_relay_active_wrong_relay_deviceid, "test_relay_active_wrong_relay_deviceid", "[initiator]"); +REGISTER_TEST_CASE(test_relay_active_success, "test_relay_active_success", "[initiator]"); +REGISTER_TEST_CASE(test_relay_wrong_device, "test_relay_wrong_device", "[initiator]"); +REGISTER_TEST_CASE(test_relay_non_conneteable, "test_relay_non_conneteable", "[initiator]"); +REGISTER_TEST_CASE(test_relay_malformed_address, "test_relay_malformed_address", "[initiator]"); +REGISTER_TEST_CASE(test_relay_garbage_reply, "test_relay_garbage_reply", "[initiator]"); +REGISTER_TEST_CASE(test_relay_noninvitation_reply, "test_relay_noninvitation_reply", "[initiator]"); diff --git a/tests/078-relay.cpp b/tests/078-relay.cpp new file mode 100644 index 00000000..f8e30d2c --- /dev/null +++ b/tests/078-relay.cpp @@ -0,0 +1,366 @@ +#include "catch.hpp" +#include "test-utils.h" +#include "access.h" + +#include "utils/tls.h" +#include "model/cluster.h" +#include "model/messages.h" +#include "model/diff/modify/relay_connect_request.h" +#include "net/names.h" +#include "net/messages.h" +#include "net/relay_actor.h" +#include "transport/stream.h" +#include +#include + +using namespace syncspirit; +using namespace syncspirit::test; +using namespace syncspirit::model; +using namespace syncspirit::net; + +namespace asio = boost::asio; +namespace sys = boost::system; +namespace r = rotor; +namespace ra = r::asio; + +using configure_callback_t = std::function; + +auto timeout = r::pt::time_duration{r::pt::millisec{1500}}; +auto host = "127.0.0.1"; + +struct supervisor_t : ra::supervisor_asio_t { + using parent_t = ra::supervisor_asio_t; + using parent_t::parent_t; + + void configure(r::plugin::plugin_base_t &plugin) noexcept override { + parent_t::configure(plugin); + plugin.with_casted([&](auto &p) { + p.register_name(names::coordinator, get_address()); + p.register_name(names::peer_supervisor, get_address()); + p.register_name(names::http11_relay, get_address()); + }); + if (configure_callback) { + configure_callback(plugin); + } + } + + void on_child_shutdown(actor_base_t *actor) noexcept override { + if (actor) { + spdlog::info("child shutdown: {}, reason: {}", actor->get_identity(), + actor->get_shutdown_reason()->message()); + } + parent_t::on_child_shutdown(actor); + } + + void shutdown_finish() noexcept override { + parent_t::shutdown_finish(); + if (acceptor) { + acceptor->cancel(); + } + } + + auto get_state() noexcept { return state; } + + asio::ip::tcp::acceptor *acceptor = nullptr; + configure_callback_t configure_callback; +}; + +using supervisor_ptr_t = r::intrusive_ptr_t; +using actor_ptr_t = r::intrusive_ptr_t; + +struct fixture_t : private model::diff::contact_visitor_t { + using acceptor_t = asio::ip::tcp::acceptor; + + fixture_t() noexcept : ctx(io_ctx), acceptor(io_ctx), peer_sock(io_ctx) { + utils::set_default("trace"); + log = utils::get_logger("fixture"); + relay_config = config::relay_config_t{ + true, + "https://some-endpoint.com/", + 1024 * 1024, + }; + } + + void run() noexcept { + + auto strand = std::make_shared(io_ctx); + sup = ctx.create_supervisor().strand(strand).timeout(timeout).create_registry().finish(); + sup->configure_callback = [&](r::plugin::plugin_base_t &plugin) { + plugin.template with_casted([&](auto &p) { + using contact_update_t = model::message::contact_update_t; + p.subscribe_actor(r::lambda([&](contact_update_t &msg) { on(msg); })); + using http_req_t = net::message::http_request_t; + p.subscribe_actor(r::lambda([&](http_req_t &req) { + LOG_INFO(log, "received http request"); + http::response res; + res.result(200); + res.body() = public_relays; + sup->reply_to(req, std::move(res), public_relays.size()); + })); + using connect_req_t = net::message::connect_request_t; + p.subscribe_actor(r::lambda([&](connect_req_t &req) { + LOG_INFO(log, "(connect request)"); + on(req); + })); + }); + }; + sup->start(); + sup->do_process(); + + auto ep = asio::ip::tcp::endpoint(asio::ip::make_address(host), 0); + acceptor.open(ep.protocol()); + acceptor.bind(ep); + acceptor.listen(); + listening_ep = acceptor.local_endpoint(); + + my_keys = utils::generate_pair("me").value(); + relay_keys = utils::generate_pair("relay").value(); + peer_keys = utils::generate_pair("peer").value(); + + auto md = model::device_id_t::from_cert(my_keys.cert_data).value(); + auto rd = model::device_id_t::from_cert(relay_keys.cert_data).value(); + auto pd = model::device_id_t::from_cert(peer_keys.cert_data).value(); + + my_device = device_t::create(md, "my-device").value(); + relay_device = device_t::create(rd, "relay-device").value(); + peer_device = device_t::create(rd, "peer-device").value(); + + public_relays = generate_public_relays(listening_ep, relay_device); + log->debug("public relays json: {}", public_relays); + initiate_accept(); + + cluster = new cluster_t(my_device, 1); + + cluster->get_devices().put(my_device); + cluster->get_devices().put(peer_device); + + session_key = "lorem-imspum-dolor"; + + main(); + } + + virtual void main() noexcept {} + + virtual std::string generate_public_relays(const asio::ip::tcp::endpoint &endpoint, + model::device_ptr_t &relay_device) noexcept { + std::string pattern = R""( + { + "relays": [ + { + "url": "##URL##&pingInterval=0m5s&networkTimeout=2m0s&sessionLimitBps=0&globalLimitBps=0&statusAddr=:22070&providedBy=ina", + "location": { + "latitude": 50.1049, + "longitude": 8.6295, + "city": "Frankfurt am Main", + "country": "DE", + "continent": "EU" + } + } + ] + } + )""; + auto url = fmt::format("relay://{}/?id={}", listening_ep, relay_device->device_id().get_value()); + return boost::algorithm::replace_first_copy(pattern, "##URL##", url); + } + + virtual void initiate_accept() noexcept { + acceptor.async_accept(peer_sock, [this](auto ec) { this->accept(ec); }); + sup->acceptor = &acceptor; + } + + virtual void accept(const sys::error_code &ec) noexcept { + LOG_INFO(log, "accept (relay), ec: {}, sock = {}", ec.message(), peer_sock.native_handle()); + auto uri = utils::parse("tcp://127.0.0.1:0/").value(); + auto cfg = transport::transport_config_t{{}, uri, *sup, std::move(peer_sock), false}; + relay_trans = transport::initiate_stream(cfg); + relay_read(); + } + + virtual actor_ptr_t create_actor() noexcept { + return sup->create_actor() + .timeout(timeout) + .cluster(cluster) + .relay_config(relay_config) + .escalate_failure() + .finish(); + } + + virtual void on(net::message::connect_request_t &req) noexcept { + auto &uri = req.payload.request_payload.uri; + log->info("requested connect to {}", uri.full); + auto cfg = transport::transport_config_t{{}, uri, *sup, {}, true}; + tcp::resolver resolver(io_ctx); + auto addresses = resolver.resolve(host, std::to_string(uri.port)); + auto addresses_ptr = std::make_shared(addresses); + auto trans = transport::initiate_stream(cfg); + transport::error_fn_t on_error = [&](auto &ec) { LOG_WARN(log, "active/connect, err: {}", ec.message()); }; + using ptr_t = model::intrusive_ptr_t>; + auto ptr = ptr_t(&req); + transport::connect_fn_t on_connect = [ptr, trans, addresses_ptr, this](transport::resolved_item_t it) { + LOG_INFO(log, "active/connected"); + sup->reply_to(*ptr, trans, it->endpoint()); + }; + trans->async_connect(*addresses_ptr, on_connect, on_error); + } + + void send_relay(const proto::relay::message_t &msg) noexcept { + proto::relay::serialize(msg, relay_tx); + transport::error_fn_t on_error = [&](auto &ec) { LOG_WARN(log, "relay/write, err: {}", ec.message()); }; + transport::io_fn_t on_write = [&](size_t bytes) { LOG_TRACE(log, "relay/write, {} bytes", bytes); }; + relay_trans->async_send(asio::buffer(relay_tx), on_write, on_error); + } + + void on(proto::relay::ping_t &) noexcept { + + }; + + void on(proto::relay::pong_t &) noexcept { + + }; + void on(proto::relay::join_relay_request_t &) noexcept { + LOG_INFO(log, "join_relay_request_t"); + send_relay(proto::relay::response_t{0, "ok"}); + }; + + void on(proto::relay::join_session_request_t &) noexcept { + + }; + void on(proto::relay::response_t &) noexcept { + + }; + void on(proto::relay::connect_request_t &) noexcept { + + }; + void on(proto::relay::session_invitation_t &) noexcept { + + }; + virtual void on(model::message::contact_update_t &update) noexcept { + auto &diff = *update.payload.diff; + auto r = diff.apply(*cluster); + if (!r) { + LOG_ERROR(log, "error applying diff: {}", r.error().message()); + } + r = diff.visit(*this); + if (!r) { + LOG_ERROR(log, "error visiting diff: {}", r.error().message()); + } + } + + void relay_read() noexcept { + transport::error_fn_t on_error = [&](auto &ec) { LOG_WARN(log, "relay/read, err: {}", ec.message()); }; + transport::io_fn_t on_read = [&](size_t bytes) { + LOG_TRACE(log, "relay/read, {} bytes", bytes); + auto msg = proto::relay::parse({relay_rx.data(), bytes}); + auto wrapped = std::get_if(&msg); + if (!wrapped) { + LOG_ERROR(log, "relay/read non-message?"); + return; + } + std::visit([&](auto &it) { on(it); }, wrapped->message); + }; + relay_rx.resize(1500); + auto buff = asio::buffer(relay_rx.data(), relay_rx.size()); + relay_trans->async_recv(buff, on_read, on_error); + LOG_TRACE(log, "relay/async recv"); + } + + config::relay_config_t relay_config; + cluster_ptr_t cluster; + asio::io_context io_ctx; + ra::system_context_asio_t ctx; + acceptor_t acceptor; + supervisor_ptr_t sup; + asio::ip::tcp::endpoint listening_ep; + utils::logger_t log; + asio::ip::tcp::socket peer_sock; + std::string public_relays; + utils::key_pair_t my_keys; + utils::key_pair_t relay_keys; + utils::key_pair_t peer_keys; + model::device_ptr_t my_device; + model::device_ptr_t relay_device; + model::device_ptr_t peer_device; + transport::stream_sp_t relay_trans; + std::string relay_rx; + std::string relay_tx; + std::string session_key; +}; + +void test_master_connect() { + struct F : fixture_t { + void main() noexcept override { + auto act = create_actor(); + io_ctx.run(); + + CHECK(sup->get_state() == r::state_t::OPERATIONAL); + REQUIRE(my_device->get_uris().size() == 1); + CHECK(my_device->get_uris()[0].proto == "relay"); + + sup->shutdown(); + io_ctx.restart(); + io_ctx.run(); + + CHECK(my_device->get_uris().size() == 0); + io_ctx.restart(); + io_ctx.run(); + + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + } + + void on(model::message::contact_update_t &update) noexcept override { + LOG_INFO(log, "contact_update_t"); + fixture_t::on(update); + io_ctx.stop(); + } + }; + + F().run(); +} + +void test_passive() { + struct F : fixture_t { + void main() noexcept override { + auto act = create_actor(); + io_ctx.run(); + CHECK(sent); + CHECK(received); + CHECK(sup->get_state() == r::state_t::OPERATIONAL); + + sup->shutdown(); + io_ctx.restart(); + io_ctx.run(); + + CHECK(my_device->get_uris().size() == 0); + CHECK(sup->get_state() == r::state_t::SHUT_DOWN); + } + + void on(model::message::contact_update_t &update) noexcept override { + LOG_INFO(log, "contact_update_t"); + fixture_t::on(update); + if (my_device->get_uris().size() == 1 && !sent) { + sent = true; + auto msg = proto::relay::session_invitation_t{ + std::string(peer_device->device_id().get_sha256()), session_key, {}, 12345, true}; + send_relay(msg); + } + } + + outcome::result operator()(const model::diff::modify::relay_connect_request_t &diff) noexcept override { + CHECK(diff.peer == peer_device->device_id()); + CHECK(diff.session_key == session_key); + CHECK(diff.relay.port() == 12345); + CHECK(diff.relay.address().to_string() == "127.0.0.1"); + received = true; + io_ctx.stop(); + return outcome::success(); + } + + bool sent = false; + bool received = false; + }; + + F().run(); +} + +REGISTER_TEST_CASE(test_master_connect, "test_master_connect", "[relay]"); +REGISTER_TEST_CASE(test_passive, "test_passive", "[relay]"); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 10ca5982..a5e78d4a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -59,6 +59,10 @@ add_executable(015-logger 015-logger.cpp $<$:win32-resource target_link_libraries(015-logger syncspirit_test_lib) add_test(015-logger "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/015-logger") +add_executable(016-relay-support 016-relay-support.cpp $<$:win32-resource.rc>) +target_link_libraries(016-relay-support syncspirit_test_lib) +add_test(016-relay-support "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/016-relay-support") + add_executable(020-generic-map 020-generic-map.cpp $<$:win32-resource.rc>) target_link_libraries(020-generic-map syncspirit_test_lib) add_test(020-generic-map "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/020-generic-map") @@ -154,3 +158,21 @@ add_test(075-controller "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/075-controller") add_executable(076-scan_actor 076-scan_actor.cpp $<$:win32-resource.rc>) target_link_libraries(076-scan_actor syncspirit_test_lib) add_test(076-scan_actor "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/076-scan_actor") + +add_executable(077-initiator 077-initiator.cpp $<$:win32-resource.rc>) +target_link_libraries(077-initiator syncspirit_test_lib + $<$:wsock32> + $<$:ws2_32> + $<$:iphlpapi> +) +add_test(077-initiator "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/077-initiator") + +add_executable(078-relay 078-relay.cpp $<$:win32-resource.rc>) +target_link_libraries(078-relay syncspirit_test_lib + $<$:wsock32> + $<$:ws2_32> + $<$:iphlpapi> +) +add_test(078-relay "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/078-relay") + + diff --git a/todo.txt b/todo.txt index 116e15fd..38a301ca 100644 --- a/todo.txt +++ b/todo.txt @@ -1,5 +1,28 @@ -- дока - - configuration +- introduce initiator + -- connect_request_t + -- update_contact_t + -- relay_connect_request_t + -- relay_update_contact_t + + + device_lock + +=========== +5. разобраться с лог-левелами? + +1. ^ если в block iterator'е были проскипанные блоки (из-за того, что были залочены), то файл(!) надо добавить на рескан через некоторое время + + +3. скан-актор не детектит скаченные файлы + + +4. надо как-то понимать, что folder is up-to-date? + + + +- syncspirit-daemon: /home/b/development/cpp/syncspirit/src/net/dialer_actor.cpp:88: void syncspirit::net::dialer_actor_t::on_timer(rotor::request_id_t, bool): Assertion `it != redial_map.end()' failed. + +- 2022-03-28 20:47:54.664] [E/25546] fs::scan_actor, on_hash, file: my_label/Camera/VID_20210122_121411.mp4, error: fs::scan_actor request timeout + ===================== - идёт много file_clone, но нету скачки файла - check 4 symlinks test (scaner) ?