// // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/telegram/net/DcOptionsSet.h" #include "td/telegram/ConfigManager.h" #include "td/telegram/Global.h" #include "td/actor/actor.h" #include "td/utils/algorithm.h" #include "td/utils/format.h" #include "td/utils/logging.h" #include "td/utils/SliceBuilder.h" #include <algorithm> #include <set> #include <utility> namespace td { void DcOptionsSet::add_dc_options(DcOptions dc_options) { std::vector<DcOptionId> new_ordered_options; for (auto &option : dc_options.dc_options) { auto *info = register_dc_option(std::move(option)); new_ordered_options.push_back(DcOptionId{info->pos}); } std::set<DcOptionId> new_ordered_options_set(new_ordered_options.begin(), new_ordered_options.end()); for (auto option_id : ordered_options_) { if (!new_ordered_options_set.count(option_id)) { new_ordered_options.push_back(option_id); } } ordered_options_ = std::move(new_ordered_options); for (size_t i = 0; i < ordered_options_.size(); i++) { options_[ordered_options_[i].pos]->order = i; } } DcOptions DcOptionsSet::get_dc_options() const { DcOptions result; for (auto id : ordered_options_) { result.dc_options.push_back(options_[id.pos]->option); } return result; } vector<DcOptionsSet::ConnectionInfo> DcOptionsSet::find_all_connections(DcId dc_id, bool allow_media_only, bool use_static, bool prefer_ipv6, bool only_http) { LOG(DEBUG) << "Find all " << (allow_media_only ? "media " : "") << "connections in " << dc_id << ". use_static = " << use_static << ", prefer_ipv6 = " << prefer_ipv6 << ", only_http = " << only_http; std::vector<ConnectionInfo> options; std::vector<ConnectionInfo> static_options; if (prefer_ipv6) { use_static = false; } for (auto &option_info : options_) { auto &option = option_info->option; if (option.get_dc_id() != dc_id) { continue; } if (!option.is_valid()) { LOG(INFO) << "Skip invalid DC option"; continue; } if (!allow_media_only && option.is_media_only()) { LOG(DEBUG) << "Skip media only option"; continue; } ConnectionInfo info; info.option = &option; info.order = option_info->order; OptionStat *option_stat = get_option_stat(option_info.get()); if (!only_http) { info.use_http = false; info.stat = &option_stat->tcp_stat; if (option.is_static()) { static_options.push_back(info); } else { options.push_back(info); } } if (only_http) { #if TD_DARWIN_WATCH_OS bool allow_ipv6 = true; #else bool allow_ipv6 = prefer_ipv6; #endif if (!option.is_obfuscated_tcp_only() && !option.is_static() && (allow_ipv6 || !option.is_ipv6())) { info.use_http = true; info.stat = &option_stat->http_stat; options.push_back(info); } } } if (use_static) { if (!static_options.empty()) { options = std::move(static_options); } else { bool have_ipv4 = any_of(options, [](const auto &v) { return !v.option->is_ipv6(); }); if (have_ipv4) { td::remove_if(options, [](auto &v) { return v.option->is_ipv6(); }); } } } else { if (options.empty()) { options = std::move(static_options); } } if (prefer_ipv6) { bool have_ipv6 = any_of(options, [](const auto &v) { return v.option->is_ipv6(); }); if (have_ipv6) { td::remove_if(options, [](auto &v) { return !v.option->is_ipv6(); }); } } bool have_media_only = any_of(options, [](const auto &v) { return v.option->is_media_only(); }); if (have_media_only) { td::remove_if(options, [](auto &v) { return !v.option->is_media_only(); }); } return options; } Result<DcOptionsSet::ConnectionInfo> DcOptionsSet::find_connection(DcId dc_id, bool allow_media_only, bool use_static, bool prefer_ipv6, bool only_http) { auto options = find_all_connections(dc_id, allow_media_only, use_static, prefer_ipv6, only_http); if (options.empty()) { send_closure(G()->config_manager(), &ConfigManager::lazy_request_config); return Status::Error(PSLICE() << "No such connection: " << tag("dc_id", dc_id) << tag("allow_media_only", allow_media_only) << tag("use_static", use_static) << tag("prefer_ipv6", prefer_ipv6)); } auto last_error_at = std::min_element(options.begin(), options.end(), [](const auto &a_option, const auto &b_option) { return a_option.stat->error_at > b_option.stat->error_at; })->stat->error_at; auto result = *std::min_element(options.begin(), options.end(), [](const auto &a_option, const auto &b_option) { auto &a = *a_option.stat; auto &b = *b_option.stat; auto a_state = a.state(); auto b_state = b.state(); if (a_state != b_state) { return a_state < b_state; } if (a_state == Stat::State::Ok) { if (a_option.order == b_option.order) { return a_option.use_http < b_option.use_http; } return a_option.order < b_option.order; } else if (a_state == Stat::State::Error) { return a.error_at < b.error_at; } return a_option.order < b_option.order; }); result.should_check = !result.stat->is_ok() || result.use_http || last_error_at > Time::now_cached() - 10; return result; } void DcOptionsSet::reset() { options_.clear(); ordered_options_.clear(); } DcOptionsSet::DcOptionInfo *DcOptionsSet::register_dc_option(DcOption &&option) { auto info = make_unique<DcOptionInfo>(std::move(option), options_.size()); init_option_stat(info.get()); auto result = info.get(); options_.push_back(std::move(info)); return result; } void DcOptionsSet::init_option_stat(DcOptionInfo *option_info) { const auto &ip_address = option_info->option.get_ip_address(); auto it_ok = option_to_stat_id_.emplace(ip_address, 0); if (it_ok.second) { it_ok.first->second = option_stats_.create(make_unique<OptionStat>()); } option_info->stat_id = it_ok.first->second; } DcOptionsSet::OptionStat *DcOptionsSet::get_option_stat(const DcOptionInfo *option_info) { return option_stats_.get(option_info->stat_id)->get(); } } // namespace td