New ConfigRecoverer scheme.
GitOrigin-RevId: 1101ddc56b0836387faf089ca52fe7376db9f88f
This commit is contained in:
parent
ede1d58e0f
commit
0e48dd8a81
@ -14,8 +14,10 @@ vector#1cb5c415 {t:Type} # [ t ] = Vector t;
|
|||||||
|
|
||||||
error#c4b9f9bb code:int text:string = Error;
|
error#c4b9f9bb code:int text:string = Error;
|
||||||
|
|
||||||
ipPort ipv4:int port:int = IpPort;
|
ipPort#d433ad73 ipv4:int port:int = IpPort;
|
||||||
help.configSimple#d997c3c5 date:int expires:int dc_id:int ip_port_list:Vector<ipPort> = help.ConfigSimple;
|
ipPortSecret#37982646 ipv4:int port:int secret:bytes = IpPort;
|
||||||
|
accessPointRule#4679b65f phone_prefix_rules:string dc_id:int ips:vector<IpPort> = AccessPointRule;
|
||||||
|
help.configSimple#5a592a6c date:int expires:int rules:vector<AccessPointRule> = help.ConfigSimple;
|
||||||
|
|
||||||
---functions---
|
---functions---
|
||||||
|
|
||||||
@ -1093,7 +1095,7 @@ help.saveAppLog#6f02f748 events:Vector<InputAppEvent> = Bool;
|
|||||||
help.getInviteText#4d392343 = help.InviteText;
|
help.getInviteText#4d392343 = help.InviteText;
|
||||||
help.getSupport#9cdf08cd = help.Support;
|
help.getSupport#9cdf08cd = help.Support;
|
||||||
help.getAppChangelog#9010ef6f prev_app_version:string = Updates;
|
help.getAppChangelog#9010ef6f prev_app_version:string = Updates;
|
||||||
help.getTermsOfService#350170f3 = help.TermsOfService;
|
help.getTermsOfService#fa796a44 country_iso2:string lang_code:string = help.TermsOfService;
|
||||||
help.setBotUpdatesStatus#ec22cfcd pending_updates_count:int message:string = Bool;
|
help.setBotUpdatesStatus#ec22cfcd pending_updates_count:int message:string = Bool;
|
||||||
help.getCdnConfig#52029342 = CdnConfig;
|
help.getCdnConfig#52029342 = CdnConfig;
|
||||||
help.getRecentMeUrls#3dc0f114 referer:string = help.RecentMeUrls;
|
help.getRecentMeUrls#3dc0f114 referer:string = help.RecentMeUrls;
|
||||||
|
Binary file not shown.
@ -124,15 +124,11 @@ static ActorOwn<> get_simple_config_impl(Promise<SimpleConfig> promise, int32 sc
|
|||||||
}
|
}
|
||||||
|
|
||||||
ActorOwn<> get_simple_config_azure(Promise<SimpleConfig> promise, bool is_test, int32 scheduler_id) {
|
ActorOwn<> get_simple_config_azure(Promise<SimpleConfig> promise, bool is_test, int32 scheduler_id) {
|
||||||
string url = PSTRING() << "https://software-download.microsoft.com/" << (is_test ? "test" : "prod") << "/config.txt";
|
string url = PSTRING() << "https://software-download.microsoft.com/" << (is_test ? "test" : "prod")
|
||||||
|
<< "v2/config.txt";
|
||||||
return get_simple_config_impl(std::move(promise), scheduler_id, std::move(url), "tcdnb.azureedge.net");
|
return get_simple_config_impl(std::move(promise), scheduler_id, std::move(url), "tcdnb.azureedge.net");
|
||||||
}
|
}
|
||||||
|
|
||||||
ActorOwn<> get_simple_config_google_app(Promise<SimpleConfig> promise, bool is_test, int32 scheduler_id) {
|
|
||||||
string url = PSTRING() << "https://www.google.com/" << (is_test ? "test/" : "");
|
|
||||||
return get_simple_config_impl(std::move(promise), scheduler_id, std::move(url), "dns-telegram.appspot.com");
|
|
||||||
}
|
|
||||||
|
|
||||||
ActorOwn<> get_simple_config_google_dns(Promise<SimpleConfig> promise, bool is_test, int32 scheduler_id) {
|
ActorOwn<> get_simple_config_google_dns(Promise<SimpleConfig> promise, bool is_test, int32 scheduler_id) {
|
||||||
VLOG(config_recoverer) << "Request simple config from Google DNS";
|
VLOG(config_recoverer) << "Request simple config from Google DNS";
|
||||||
#if TD_EMSCRIPTEN // FIXME
|
#if TD_EMSCRIPTEN // FIXME
|
||||||
@ -171,7 +167,7 @@ ActorOwn<> get_simple_config_google_dns(Promise<SimpleConfig> promise, bool is_t
|
|||||||
return decode_config(data);
|
return decode_config(data);
|
||||||
}());
|
}());
|
||||||
}),
|
}),
|
||||||
PSTRING() << "https://google.com/resolve?name=" << (is_test ? "t" : "") << "ap.stel.com&type=16",
|
PSTRING() << "https://www.google.com/resolve?name=" << (is_test ? "t" : "") << "apv2.stel.com&type=16",
|
||||||
std::vector<std::pair<string, string>>({{"Host", "dns.google.com"}}), 10 /*timeout*/, 3 /*ttl*/,
|
std::vector<std::pair<string, string>>({{"Host", "dns.google.com"}}), 10 /*timeout*/, 3 /*ttl*/,
|
||||||
SslFd::VerifyPeer::Off));
|
SslFd::VerifyPeer::Off));
|
||||||
#endif
|
#endif
|
||||||
@ -370,25 +366,54 @@ class ConfigRecoverer : public Actor {
|
|||||||
loop();
|
loop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool check_phone_number_rules(Slice phone_number, Slice rules) {
|
||||||
|
bool found = false;
|
||||||
|
for (auto prefix : full_split(rules, ',')) {
|
||||||
|
if (prefix.empty()) {
|
||||||
|
found = true;
|
||||||
|
} else if (prefix[0] == '+' && begins_with(phone_number, prefix.substr(1))) {
|
||||||
|
found = true;
|
||||||
|
} else if (prefix[0] == '-' && begins_with(phone_number, prefix.substr(1))) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
LOG(ERROR) << "Invalid prefix rule " << prefix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
void on_simple_config(Result<SimpleConfig> r_simple_config, bool dummy) {
|
void on_simple_config(Result<SimpleConfig> r_simple_config, bool dummy) {
|
||||||
simple_config_query_.reset();
|
simple_config_query_.reset();
|
||||||
auto r_dc_options = [&]() -> Result<DcOptions> {
|
|
||||||
if (r_simple_config.is_error()) {
|
|
||||||
return r_simple_config.move_as_error();
|
|
||||||
}
|
|
||||||
return DcOptions(*r_simple_config.ok());
|
|
||||||
}();
|
|
||||||
dc_options_i_ = 0;
|
dc_options_i_ = 0;
|
||||||
if (r_dc_options.is_ok()) {
|
if (r_simple_config.is_ok()) {
|
||||||
simple_config_ = r_dc_options.move_as_ok();
|
auto config = r_simple_config.move_as_ok();
|
||||||
VLOG(config_recoverer) << "Got SimpleConfig " << simple_config_;
|
LOG(ERROR) << to_string(config);
|
||||||
|
if (config->expires_ >= G()->unix_time()) {
|
||||||
|
string phone_number = G()->shared_config().get_option_string("my_phone_number");
|
||||||
|
simple_config_.dc_options.clear();
|
||||||
|
|
||||||
|
for (auto &rule : config->rules_) {
|
||||||
|
if (check_phone_number_rules(phone_number, rule->phone_prefix_rules_) && DcId::is_valid(rule->dc_id_)) {
|
||||||
|
DcId dc_id = DcId::internal(rule->dc_id_);
|
||||||
|
for (auto &ip_port : rule->ips_) {
|
||||||
|
DcOption option(dc_id, *ip_port);
|
||||||
|
if (option.is_valid()) {
|
||||||
|
simple_config_.dc_options.push_back(std::move(option));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VLOG(config_recoverer) << "Got SimpleConfig " << simple_config_;
|
||||||
|
LOG(ERROR) << "Got SimpleConfig " << simple_config_;
|
||||||
|
}
|
||||||
|
|
||||||
simple_config_expire_at_ = get_config_expire_time();
|
simple_config_expire_at_ = get_config_expire_time();
|
||||||
simple_config_at_ = Time::now_cached();
|
simple_config_at_ = Time::now_cached();
|
||||||
for (size_t i = 1; i < simple_config_.dc_options.size(); i++) {
|
for (size_t i = 1; i < simple_config_.dc_options.size(); i++) {
|
||||||
std::swap(simple_config_.dc_options[i], simple_config_.dc_options[Random::fast(0, static_cast<int>(i))]);
|
std::swap(simple_config_.dc_options[i], simple_config_.dc_options[Random::fast(0, static_cast<int>(i))]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
VLOG(config_recoverer) << "Get SimpleConfig error " << r_dc_options.error();
|
VLOG(config_recoverer) << "Get SimpleConfig error " << r_simple_config.error();
|
||||||
simple_config_ = DcOptions();
|
simple_config_ = DcOptions();
|
||||||
simple_config_expire_at_ = get_failed_config_expire_time();
|
simple_config_expire_at_ = get_failed_config_expire_time();
|
||||||
}
|
}
|
||||||
@ -515,10 +540,9 @@ class ConfigRecoverer : public Actor {
|
|||||||
});
|
});
|
||||||
auto get_simple_config = [&]() {
|
auto get_simple_config = [&]() {
|
||||||
switch (simple_config_turn_ % 3) {
|
switch (simple_config_turn_ % 3) {
|
||||||
case 0:
|
|
||||||
return get_simple_config_azure;
|
|
||||||
case 1:
|
case 1:
|
||||||
return get_simple_config_google_app;
|
return get_simple_config_azure;
|
||||||
|
case 0:
|
||||||
case 2:
|
case 2:
|
||||||
default:
|
default:
|
||||||
return get_simple_config_google_dns;
|
return get_simple_config_google_dns;
|
||||||
|
@ -24,11 +24,9 @@ using SimpleConfig = tl_object_ptr<telegram_api::help_configSimple>;
|
|||||||
|
|
||||||
Result<SimpleConfig> decode_config(Slice input);
|
Result<SimpleConfig> decode_config(Slice input);
|
||||||
|
|
||||||
ActorOwn<> get_simple_config_azure(Promise<SimpleConfig> promise, bool is_test = false, int32 scheduler_id = -1);
|
ActorOwn<> get_simple_config_azure(Promise<SimpleConfig> promise, bool is_test, int32 scheduler_id);
|
||||||
|
|
||||||
ActorOwn<> get_simple_config_google_app(Promise<SimpleConfig> promise, bool is_test = false, int32 scheduler_id = -1);
|
ActorOwn<> get_simple_config_google_dns(Promise<SimpleConfig> promise, bool is_test, int32 scheduler_id);
|
||||||
|
|
||||||
ActorOwn<> get_simple_config_google_dns(Promise<SimpleConfig> promise, bool is_test = false, int32 scheduler_id = -1);
|
|
||||||
|
|
||||||
using FullConfig = tl_object_ptr<telegram_api::config>;
|
using FullConfig = tl_object_ptr<telegram_api::config>;
|
||||||
|
|
||||||
|
@ -4933,6 +4933,9 @@ void ContactsManager::on_get_user(tl_object_ptr<telegram_api::User> &&user_ptr,
|
|||||||
if (flags & USER_FLAG_IS_ME) {
|
if (flags & USER_FLAG_IS_ME) {
|
||||||
set_my_id(user_id);
|
set_my_id(user_id);
|
||||||
td_->auth_manager_->set_is_bot(is_bot);
|
td_->auth_manager_->set_is_bot(is_bot);
|
||||||
|
if (!is_bot) {
|
||||||
|
G()->shared_config().set_option_string("my_phone_number", user->phone_);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
/*
|
/*
|
||||||
if (!(flags & USER_FLAG_HAS_ACCESS_HASH) && !(flags & USER_FLAG_IS_DELETED) &&
|
if (!(flags & USER_FLAG_HAS_ACCESS_HASH) && !(flags & USER_FLAG_IS_DELETED) &&
|
||||||
|
@ -4204,7 +4204,7 @@ bool Td::is_internal_config_option(Slice name) {
|
|||||||
return name == "call_ring_timeout_ms" || name == "call_receive_timeout_ms" || name == "channels_read_media_period" ||
|
return name == "call_ring_timeout_ms" || name == "call_receive_timeout_ms" || name == "channels_read_media_period" ||
|
||||||
name == "edit_time_limit" || name == "revoke_pm_inbox" || name == "revoke_time_limit" ||
|
name == "edit_time_limit" || name == "revoke_pm_inbox" || name == "revoke_time_limit" ||
|
||||||
name == "revoke_pm_time_limit" || name == "rating_e_decay" || name == "saved_animations_limit" ||
|
name == "revoke_pm_time_limit" || name == "rating_e_decay" || name == "saved_animations_limit" ||
|
||||||
name == "recent_stickers_limit" || name == "expect_blocking" || name == "auth";
|
name == "recent_stickers_limit" || name == "expect_blocking" || name == "my_phone_number" || name == "auth";
|
||||||
}
|
}
|
||||||
|
|
||||||
void Td::on_config_option_updated(const string &name) {
|
void Td::on_config_option_updated(const string &name) {
|
||||||
|
@ -671,6 +671,7 @@ void ConnectionCreator::request_raw_connection_by_ip(IPAddress ip_address,
|
|||||||
|
|
||||||
Result<SocketFd> ConnectionCreator::find_connection(const ConnectionCreator::ProxyInfo &proxy, DcId dc_id,
|
Result<SocketFd> ConnectionCreator::find_connection(const ConnectionCreator::ProxyInfo &proxy, DcId dc_id,
|
||||||
bool allow_media_only, FindConnectionExtra &extra) {
|
bool allow_media_only, FindConnectionExtra &extra) {
|
||||||
|
extra.debug_str = PSTRING() << "Failed to find valid IP for " << dc_id;
|
||||||
TRY_RESULT(info, dc_options_set_.find_connection(dc_id, allow_media_only, proxy.use_proxy()));
|
TRY_RESULT(info, dc_options_set_.find_connection(dc_id, allow_media_only, proxy.use_proxy()));
|
||||||
extra.stat = info.stat;
|
extra.stat = info.stat;
|
||||||
int32 int_dc_id = dc_id.get_raw_id();
|
int32 int_dc_id = dc_id.get_raw_id();
|
||||||
@ -680,10 +681,11 @@ Result<SocketFd> ConnectionCreator::find_connection(const ConnectionCreator::Pro
|
|||||||
int16 raw_dc_id = narrow_cast<int16>(info.option->is_media_only() ? -int_dc_id : int_dc_id);
|
int16 raw_dc_id = narrow_cast<int16>(info.option->is_media_only() ? -int_dc_id : int_dc_id);
|
||||||
|
|
||||||
if (proxy.use_mtproto_proxy()) {
|
if (proxy.use_mtproto_proxy()) {
|
||||||
|
extra.debug_str = PSTRING() << "Mtproto " << proxy.ip_address() << " to DC" << raw_dc_id;
|
||||||
|
|
||||||
TRY_RESULT(secret, hex_decode(proxy.proxy().secret()));
|
TRY_RESULT(secret, hex_decode(proxy.proxy().secret()));
|
||||||
extra.transport_type = {mtproto::TransportType::ObfuscatedTcp, raw_dc_id, std::move(secret)};
|
extra.transport_type = {mtproto::TransportType::ObfuscatedTcp, raw_dc_id, std::move(secret)};
|
||||||
|
|
||||||
extra.debug_str = PSTRING() << "Mtproto " << proxy.ip_address() << " to DC" << raw_dc_id;
|
|
||||||
LOG(INFO) << "Create: " << extra.debug_str;
|
LOG(INFO) << "Create: " << extra.debug_str;
|
||||||
return SocketFd::open(proxy.ip_address());
|
return SocketFd::open(proxy.ip_address());
|
||||||
}
|
}
|
||||||
@ -787,7 +789,7 @@ void ConnectionCreator::client_loop(ClientInfo &client) {
|
|||||||
auto r_socket_fd = find_connection(proxy, client.dc_id, client.allow_media_only, extra);
|
auto r_socket_fd = find_connection(proxy, client.dc_id, client.allow_media_only, extra);
|
||||||
check_mode |= extra.check_mode;
|
check_mode |= extra.check_mode;
|
||||||
if (r_socket_fd.is_error()) {
|
if (r_socket_fd.is_error()) {
|
||||||
LOG(WARNING) << r_socket_fd.error();
|
LOG(WARNING) << extra.debug_str << ": " << r_socket_fd.error();
|
||||||
if (extra.stat) {
|
if (extra.stat) {
|
||||||
extra.stat->on_error(); // TODO: different kind of error
|
extra.stat->on_error(); // TODO: different kind of error
|
||||||
}
|
}
|
||||||
|
@ -81,9 +81,28 @@ class DcOption {
|
|||||||
init_ip_address(ip, port);
|
init_ip_address(ip, port);
|
||||||
}
|
}
|
||||||
|
|
||||||
DcOption(DcId new_dc_id, const telegram_api::ipPort &ip_port) {
|
DcOption(DcId new_dc_id, const telegram_api::IpPort &ip_port_ref) {
|
||||||
|
switch (ip_port_ref.get_id()) {
|
||||||
|
case telegram_api::ipPort::ID: {
|
||||||
|
auto &ip_port = static_cast<const telegram_api::ipPort &>(ip_port_ref);
|
||||||
|
init_ip_address(IPAddress::ipv4_to_str(ip_port.ipv4_), ip_port.port_);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case telegram_api::ipPortSecret::ID: {
|
||||||
|
auto &ip_port = static_cast<const telegram_api::ipPortSecret &>(ip_port_ref);
|
||||||
|
if (ip_port.secret_.size() != 16u) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
flags_ |= Flags::HasSecret;
|
||||||
|
secret_ = ip_port.secret_.as_slice().str();
|
||||||
|
init_ip_address(IPAddress::ipv4_to_str(ip_port.ipv4_), ip_port.port_);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
flags_ |= Flags::ObfuscatedTcpOnly;
|
||||||
dc_id_ = new_dc_id;
|
dc_id_ = new_dc_id;
|
||||||
init_ip_address(IPAddress::ipv4_to_str(ip_port.ipv4_), ip_port.port_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DcId get_dc_id() const {
|
DcId get_dc_id() const {
|
||||||
@ -204,15 +223,7 @@ class DcOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
explicit DcOptions(const telegram_api::help_configSimple &config_simple) {
|
|
||||||
auto dc_id = DcId::is_valid(config_simple.dc_id_) ? DcId::internal(config_simple.dc_id_) : DcId();
|
|
||||||
for (auto &ip_port : config_simple.ip_port_list_) {
|
|
||||||
DcOption option(dc_id, *ip_port);
|
|
||||||
if (option.is_valid()) {
|
|
||||||
dc_options.push_back(std::move(option));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
template <class StorerT>
|
template <class StorerT>
|
||||||
void store(StorerT &storer) const {
|
void store(StorerT &storer) const {
|
||||||
::td::store(dc_options, storer);
|
::td::store(dc_options, storer);
|
||||||
|
@ -31,67 +31,49 @@ REGISTER_TESTS(mtproto);
|
|||||||
using namespace td;
|
using namespace td;
|
||||||
using namespace mtproto;
|
using namespace mtproto;
|
||||||
|
|
||||||
#if !TD_WINDOWS && !TD_EMSCRIPTEN // TODO
|
|
||||||
TEST(Mtproto, config) {
|
TEST(Mtproto, config) {
|
||||||
ConcurrentScheduler sched;
|
ConcurrentScheduler sched;
|
||||||
int threads_n = 0;
|
int threads_n = 0;
|
||||||
sched.init(threads_n);
|
sched.init(threads_n);
|
||||||
|
|
||||||
int cnt = 3;
|
int cnt = 1;
|
||||||
{
|
{
|
||||||
auto guard = sched.get_current_guard();
|
auto guard = sched.get_current_guard();
|
||||||
get_simple_config_azure(PromiseCreator::lambda([&](Result<SimpleConfig> r_simple_config) {
|
|
||||||
if (r_simple_config.is_ok()) {
|
|
||||||
LOG(ERROR) << to_string(r_simple_config.ok());
|
|
||||||
} else {
|
|
||||||
LOG(ERROR) << r_simple_config.error();
|
|
||||||
}
|
|
||||||
if (--cnt == 0) {
|
|
||||||
Scheduler::instance()->finish();
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.release();
|
|
||||||
|
|
||||||
get_simple_config_google_app(PromiseCreator::lambda([&](Result<SimpleConfig> r_simple_config) {
|
auto run = [&](auto &func, bool is_test) {
|
||||||
if (r_simple_config.is_ok()) {
|
cnt++;
|
||||||
LOG(ERROR) << to_string(r_simple_config.ok());
|
auto promise = PromiseCreator::lambda([&](Result<SimpleConfig> r_simple_config) {
|
||||||
} else {
|
if (r_simple_config.is_ok()) {
|
||||||
LOG(ERROR) << r_simple_config.error();
|
LOG(ERROR) << to_string(r_simple_config.ok());
|
||||||
}
|
} else {
|
||||||
if (--cnt == 0) {
|
LOG(ERROR) << r_simple_config.error();
|
||||||
Scheduler::instance()->finish();
|
}
|
||||||
}
|
if (--cnt == 0) {
|
||||||
}))
|
Scheduler::instance()->finish();
|
||||||
.release();
|
}
|
||||||
|
});
|
||||||
|
func(std::move(promise), is_test, -1).release();
|
||||||
|
};
|
||||||
|
|
||||||
get_simple_config_google_dns(PromiseCreator::lambda([&](Result<SimpleConfig> r_simple_config) {
|
run(get_simple_config_azure, false);
|
||||||
if (r_simple_config.is_ok()) {
|
run(get_simple_config_google_dns, false);
|
||||||
LOG(ERROR) << to_string(r_simple_config.ok());
|
run(get_simple_config_azure, true);
|
||||||
} else {
|
run(get_simple_config_google_dns, true);
|
||||||
LOG(ERROR) << r_simple_config.error();
|
|
||||||
}
|
|
||||||
if (--cnt == 0) {
|
|
||||||
Scheduler::instance()->finish();
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.release();
|
|
||||||
}
|
}
|
||||||
|
cnt--;
|
||||||
sched.start();
|
sched.start();
|
||||||
while (sched.run_main(10)) {
|
while (sched.run_main(10)) {
|
||||||
// empty;
|
// empty;
|
||||||
}
|
}
|
||||||
sched.finish();
|
sched.finish();
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
TEST(Mtproto, encrypted_config) {
|
TEST(Mtproto, encrypted_config) {
|
||||||
string data =
|
string data =
|
||||||
" LQ2 \b\n\tru6xVXpHHckW4eQWK0X3uThupVOor5sXT8t298IjDksYeUseQTOIrnUqiQj7o"
|
" hO//tt \b\n\tiwPVovorKtIYtQ8y2ik7CqfJiJ4pJOCLRa4fBmNPixuRPXnBFF/3mTAAZoSyHq4SNylGHz0Cv1/"
|
||||||
"+ZgPfhnfe+lfcQA+naax9akgllimjlJtL5riTf3O7iqZSnJ614qmCucxqqVTbXk/"
|
"FnWWdEV+BPJeOTk+ARHcNkuJBt0CqnfcVCoDOpKqGyq0U31s2MOpQvHgAG+Tlpg02syuH0E4dCGRw5CbJPARiynteb9y5fT5x/"
|
||||||
"hY2KaJTtsMqk7cShJjM3aQ4DD40h2InTaG7uyVO2q7K0GMUTeY3AM0Rt1lUjKHLD"
|
"kmdp6BMR5tWQSQF0liH16zLh8BDSIdiMsikdcwnAvBwdNhRqQBqGx9MTh62MDmlebjtczE9Gz0z5cscUO2yhzGdphgIy6SP+"
|
||||||
"g4RwjTzZaG8TwfgL/mZ7jsvgTTTATPWKUo7SmxQ9Hsj+07NMGqr6JKZS6aiU1Knz"
|
"bwaqLWYF0XdPGjKLMUEJW+rou6fbL1t/EUXPtU0XmQAnO0Fh86h+AqDMOe30N4qKrPQ== ";
|
||||||
"VGCZ3OJEyRYocktN4HjaLpkguilaHWlVM2UNFUd5a+ajfLIiiKlH0FRC3XZ12CND"
|
|
||||||
"Y+NBjv0I57N2O4fBfswTlA== ";
|
|
||||||
auto config = decode_config(data).move_as_ok();
|
auto config = decode_config(data).move_as_ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user