diff options
Diffstat (limited to 'libs/tdlib/td/td/telegram/DeviceTokenManager.cpp')
-rw-r--r-- | libs/tdlib/td/td/telegram/DeviceTokenManager.cpp | 383 |
1 files changed, 383 insertions, 0 deletions
diff --git a/libs/tdlib/td/td/telegram/DeviceTokenManager.cpp b/libs/tdlib/td/td/telegram/DeviceTokenManager.cpp new file mode 100644 index 0000000000..8182d6dac8 --- /dev/null +++ b/libs/tdlib/td/td/telegram/DeviceTokenManager.cpp @@ -0,0 +1,383 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// +// 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/DeviceTokenManager.h" + +#include "td/telegram/Global.h" +#include "td/telegram/misc.h" +#include "td/telegram/net/NetQueryDispatcher.h" +#include "td/telegram/UserId.h" + +#include "td/telegram/td_api.hpp" +#include "td/telegram/telegram_api.h" + +#include "td/utils/base64.h" +#include "td/utils/buffer.h" +#include "td/utils/format.h" +#include "td/utils/JsonBuilder.h" +#include "td/utils/logging.h" +#include "td/utils/Slice.h" +#include "td/utils/Status.h" +#include "td/utils/tl_helpers.h" + +#include <type_traits> + +namespace td { + +template <class StorerT> +void DeviceTokenManager::TokenInfo::store(StorerT &storer) const { + using td::store; + bool has_other_user_ids = !other_user_ids.empty(); + bool is_sync = state == State::Sync; + bool is_unregister = state == State::Unregister; + bool is_register = state == State::Register; + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_other_user_ids); + STORE_FLAG(is_sync); + STORE_FLAG(is_unregister); + STORE_FLAG(is_register); + STORE_FLAG(is_app_sandbox); + END_STORE_FLAGS(); + store(token, storer); + if (has_other_user_ids) { + store(other_user_ids, storer); + } +} + +template <class ParserT> +void DeviceTokenManager::TokenInfo::parse(ParserT &parser) { + using td::parse; + bool has_other_user_ids; + bool is_sync; + bool is_unregister; + bool is_register; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_other_user_ids); + PARSE_FLAG(is_sync); + PARSE_FLAG(is_unregister); + PARSE_FLAG(is_register); + PARSE_FLAG(is_app_sandbox); + END_PARSE_FLAGS(); + CHECK(is_sync + is_unregister + is_register == 1); + if (is_sync) { + state = State::Sync; + } else if (is_unregister) { + state = State::Unregister; + } else { + state = State::Register; + } + parse(token, parser); + if (has_other_user_ids) { + parse(other_user_ids, parser); + } +} + +StringBuilder &operator<<(StringBuilder &string_builder, const DeviceTokenManager::TokenInfo &token_info) { + switch (token_info.state) { + case DeviceTokenManager::TokenInfo::State::Sync: + string_builder << "Synchronized"; + break; + case DeviceTokenManager::TokenInfo::State::Unregister: + string_builder << "Unregister"; + break; + case DeviceTokenManager::TokenInfo::State::Register: + string_builder << "Register"; + break; + default: + UNREACHABLE(); + } + string_builder << " token \"" << format::escaped(token_info.token) << "\""; + if (!token_info.other_user_ids.empty()) { + string_builder << ", with other users " << token_info.other_user_ids; + } + if (token_info.is_app_sandbox) { + string_builder << ", sandboxed"; + } + return string_builder; +} + +void DeviceTokenManager::register_device(tl_object_ptr<td_api::DeviceToken> device_token_ptr, + vector<int32> other_user_ids, Promise<tl_object_ptr<td_api::ok>> promise) { + CHECK(device_token_ptr != nullptr); + TokenType token_type; + string token; + bool is_app_sandbox = false; + switch (device_token_ptr->get_id()) { + case td_api::deviceTokenApplePush::ID: { + auto device_token = static_cast<td_api::deviceTokenApplePush *>(device_token_ptr.get()); + token = std::move(device_token->device_token_); + token_type = TokenType::APNS; + is_app_sandbox = device_token->is_app_sandbox_; + break; + } + case td_api::deviceTokenGoogleCloudMessaging::ID: { + auto device_token = static_cast<td_api::deviceTokenGoogleCloudMessaging *>(device_token_ptr.get()); + token = std::move(device_token->token_); + token_type = TokenType::GCM; + break; + } + case td_api::deviceTokenMicrosoftPush::ID: { + auto device_token = static_cast<td_api::deviceTokenMicrosoftPush *>(device_token_ptr.get()); + token = std::move(device_token->channel_uri_); + token_type = TokenType::MPNS; + break; + } + case td_api::deviceTokenSimplePush::ID: { + auto device_token = static_cast<td_api::deviceTokenSimplePush *>(device_token_ptr.get()); + token = std::move(device_token->endpoint_); + token_type = TokenType::SIMPLE_PUSH; + break; + } + case td_api::deviceTokenUbuntuPush::ID: { + auto device_token = static_cast<td_api::deviceTokenUbuntuPush *>(device_token_ptr.get()); + token = std::move(device_token->token_); + token_type = TokenType::UBUNTU_PHONE; + break; + } + case td_api::deviceTokenBlackBerryPush::ID: { + auto device_token = static_cast<td_api::deviceTokenBlackBerryPush *>(device_token_ptr.get()); + token = std::move(device_token->token_); + token_type = TokenType::BLACKBERRY; + break; + } + case td_api::deviceTokenWindowsPush::ID: { + auto device_token = static_cast<td_api::deviceTokenWindowsPush *>(device_token_ptr.get()); + token = std::move(device_token->access_token_); + token_type = TokenType::WNS; + break; + } + case td_api::deviceTokenApplePushVoIP::ID: { + auto device_token = static_cast<td_api::deviceTokenApplePushVoIP *>(device_token_ptr.get()); + token = std::move(device_token->device_token_); + token_type = TokenType::APNS_VOIP; + is_app_sandbox = device_token->is_app_sandbox_; + break; + } + case td_api::deviceTokenWebPush::ID: { + auto device_token = static_cast<td_api::deviceTokenWebPush *>(device_token_ptr.get()); + if (device_token->endpoint_.find(',') != string::npos) { + return promise.set_error(Status::Error(400, "Illegal endpoint value")); + } + if (!is_base64url(device_token->p256dh_base64url_)) { + return promise.set_error(Status::Error(400, "Public key must be base64url-encoded")); + } + if (!is_base64url(device_token->auth_base64url_)) { + return promise.set_error(Status::Error(400, "Authentication secret must be base64url-encoded")); + } + if (!clean_input_string(device_token->endpoint_)) { + return promise.set_error(Status::Error(400, "Endpoint must be encoded in UTF-8")); + } + + if (!device_token->endpoint_.empty()) { + class JsonKeys : public Jsonable { + public: + JsonKeys(Slice p256dh, Slice auth) : p256dh_(p256dh), auth_(auth) { + } + void store(JsonValueScope *scope) const { + auto object = scope->enter_object(); + object << ctie("p256dh", p256dh_); + object << ctie("auth", auth_); + } + + private: + Slice p256dh_; + Slice auth_; + }; + class JsonWebPushToken : public Jsonable { + public: + JsonWebPushToken(Slice endpoint, Slice p256dh, Slice auth) + : endpoint_(endpoint), p256dh_(p256dh), auth_(auth) { + } + void store(JsonValueScope *scope) const { + auto object = scope->enter_object(); + object << ctie("endpoint", endpoint_); + object << ctie("keys", JsonKeys(p256dh_, auth_)); + } + + private: + Slice endpoint_; + Slice p256dh_; + Slice auth_; + }; + + token = json_encode<string>( + JsonWebPushToken(device_token->endpoint_, device_token->p256dh_base64url_, device_token->auth_base64url_)); + } + token_type = TokenType::WEB_PUSH; + break; + } + case td_api::deviceTokenMicrosoftPushVoIP::ID: { + auto device_token = static_cast<td_api::deviceTokenMicrosoftPushVoIP *>(device_token_ptr.get()); + token = std::move(device_token->channel_uri_); + token_type = TokenType::MPNS_VOIP; + break; + } + case td_api::deviceTokenTizenPush::ID: { + auto device_token = static_cast<td_api::deviceTokenTizenPush *>(device_token_ptr.get()); + token = std::move(device_token->reg_id_); + token_type = TokenType::TIZEN; + break; + } + default: + UNREACHABLE(); + } + + if (!clean_input_string(token)) { + return promise.set_error(Status::Error(400, "Device token must be encoded in UTF-8")); + } + for (auto &other_user_id : other_user_ids) { + UserId user_id(other_user_id); + if (!user_id.is_valid()) { + return promise.set_error(Status::Error(400, "Invalid user_id among other user_ids")); + } + } + if (other_user_ids.size() > MAX_OTHER_USER_IDS) { + return promise.set_error(Status::Error(400, "Too much other user_ids")); + } + + auto &info = tokens_[token_type]; + info.net_query_id = 0; + if (token.empty()) { + if (info.token.empty()) { + // already unregistered + return promise.set_value(make_tl_object<td_api::ok>()); + } + + info.state = TokenInfo::State::Unregister; + } else { + info.state = TokenInfo::State::Register; + info.token = std::move(token); + } + info.other_user_ids = std::move(other_user_ids); + info.is_app_sandbox = is_app_sandbox; + info.promise.set_value(make_tl_object<td_api::ok>()); + info.promise = std::move(promise); + save_info(token_type); +} + +string DeviceTokenManager::get_database_key(int32 token_type) { + return PSTRING() << "device_token" << token_type; +} + +void DeviceTokenManager::start_up() { + for (int32 token_type = 1; token_type < TokenType::SIZE; token_type++) { + auto serialized = G()->td_db()->get_binlog_pmc()->get(get_database_key(token_type)); + if (serialized.empty()) { + continue; + } + + auto &token = tokens_[token_type]; + char c = serialized[0]; + if (c == '*') { + unserialize(token, serialized.substr(1)).ensure(); + } else { + // legacy + if (c == '+') { + token.state = TokenInfo::State::Register; + } else if (c == '-') { + token.state = TokenInfo::State::Unregister; + } else if (c == '=') { + token.state = TokenInfo::State::Sync; + } else { + LOG(ERROR) << "Invalid serialized TokenInfo: " << format::escaped(serialized); + continue; + } + token.token = serialized.substr(1); + } + LOG(INFO) << "GET device token " << token_type << "--->" << tokens_[token_type]; + } + loop(); +} + +void DeviceTokenManager::save_info(int32 token_type) { + LOG(INFO) << "SET device token " << token_type << "--->" << tokens_[token_type]; + if (tokens_[token_type].token.empty()) { + G()->td_db()->get_binlog_pmc()->erase(get_database_key(token_type)); + } else { + G()->td_db()->get_binlog_pmc()->set(get_database_key(token_type), "*" + serialize(tokens_[token_type])); + } + sync_cnt_++; + G()->td_db()->get_binlog_pmc()->force_sync( + PromiseCreator::event(self_closure(this, &DeviceTokenManager::dec_sync_cnt))); +} + +void DeviceTokenManager::dec_sync_cnt() { + sync_cnt_--; + loop(); +} + +void DeviceTokenManager::loop() { + if (sync_cnt_ != 0) { + return; + } + for (int32 token_type = 1; token_type < TokenType::SIZE; token_type++) { + auto &info = tokens_[token_type]; + if (info.state == TokenInfo::State::Sync) { + continue; + } + if (info.net_query_id != 0) { + continue; + } + // have to send query + NetQueryPtr net_query; + auto other_user_ids = info.other_user_ids; + if (info.state == TokenInfo::State::Unregister) { + net_query = G()->net_query_creator().create( + create_storer(telegram_api::account_unregisterDevice(token_type, info.token, std::move(other_user_ids)))); + } else { + net_query = G()->net_query_creator().create(create_storer(telegram_api::account_registerDevice( + token_type, info.token, info.is_app_sandbox, BufferSlice(), std::move(other_user_ids)))); + } + info.net_query_id = net_query->id(); + G()->net_query_dispatcher().dispatch_with_callback(std::move(net_query), actor_shared(this, token_type)); + } +} + +void DeviceTokenManager::on_result(NetQueryPtr net_query) { + auto token_type = static_cast<TokenType>(get_link_token()); + CHECK(token_type >= 1 && token_type < TokenType::SIZE); + auto &info = tokens_[token_type]; + if (info.net_query_id != net_query->id()) { + net_query->clear(); + return; + } + info.net_query_id = 0; + static_assert(std::is_same<telegram_api::account_registerDevice::ReturnType, + telegram_api::account_unregisterDevice::ReturnType>::value, + ""); + auto r_flag = fetch_result<telegram_api::account_registerDevice>(std::move(net_query)); + + info.net_query_id = 0; + if (r_flag.is_ok() && r_flag.ok()) { + if (info.promise) { + info.promise.set_value(make_tl_object<td_api::ok>()); + } + if (info.state == TokenInfo::State::Unregister) { + info.token = ""; + } + info.state = TokenInfo::State::Sync; + } else { + if (info.promise) { + if (r_flag.is_error()) { + info.promise.set_error(r_flag.error().clone()); + } else { + info.promise.set_error(Status::Error(5, "Got false as result")); + } + } + if (info.state == TokenInfo::State::Register) { + info.state = TokenInfo::State::Unregister; + } else { + info.state = TokenInfo::State::Sync; + info.token = ""; + } + if (r_flag.is_error()) { + LOG(ERROR) << r_flag.error(); + } + } + save_info(token_type); +} + +} // namespace td |