blob: c0a6357d53eb9a868a438284bbc1a3b61a77a227 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/gcm_driver/gcm_driver.h"
#include <stddef.h>
#include <algorithm>
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "components/gcm_driver/crypto/gcm_decryption_result.h"
#include "components/gcm_driver/crypto/gcm_encryption_result.h"
#include "components/gcm_driver/gcm_app_handler.h"
namespace gcm {
InstanceIDHandler::InstanceIDHandler() = default;
InstanceIDHandler::~InstanceIDHandler() = default;
void InstanceIDHandler::DeleteAllTokensForApp(const std::string& app_id,
DeleteTokenCallback callback) {
DeleteToken(app_id, "*", "*", std::move(callback));
}
GCMDriver::GCMDriver(
const base::FilePath& store_path,
const scoped_refptr<base::SequencedTaskRunner>& blocking_task_runner,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
: web_push_sender_(std::move(url_loader_factory)) {
// The |blocking_task_runner| can be nullptr for tests that do not need the
// encryption capabilities of the GCMDriver class.
if (blocking_task_runner)
encryption_provider_.Init(store_path, blocking_task_runner);
}
GCMDriver::~GCMDriver() = default;
void GCMDriver::Register(const std::string& app_id,
const std::vector<std::string>& sender_ids,
RegisterCallback callback) {
DCHECK(!app_id.empty());
DCHECK(!sender_ids.empty() && sender_ids.size() <= kMaxSenders);
DCHECK(!callback.is_null());
GCMClient::Result result = EnsureStarted(GCMClient::IMMEDIATE_START);
if (result != GCMClient::SUCCESS) {
std::move(callback).Run(std::string(), result);
return;
}
// If previous register operation is still in progress, bail out.
if (register_callbacks_.find(app_id) != register_callbacks_.end()) {
std::move(callback).Run(std::string(), GCMClient::ASYNC_OPERATION_PENDING);
return;
}
// Normalize the sender IDs by making them sorted.
std::vector<std::string> normalized_sender_ids = sender_ids;
std::sort(normalized_sender_ids.begin(), normalized_sender_ids.end());
register_callbacks_[app_id] = std::move(callback);
// If previous unregister operation is still in progress, wait until it
// finishes. We don't want to throw ASYNC_OPERATION_PENDING when the user
// uninstalls an app (ungistering) and then reinstalls the app again
// (registering).
auto unregister_iter = unregister_callbacks_.find(app_id);
if (unregister_iter != unregister_callbacks_.end()) {
// Replace the original unregister callback with an intermediate callback
// that will invoke the original unregister callback and trigger the pending
// registration after the unregistration finishes.
// Note that some parameters to RegisterAfterUnregister are specified here
// when the callback is created (base::Bind supports the partial binding
// of parameters).
unregister_iter->second = base::BindOnce(
&GCMDriver::RegisterAfterUnregister, weak_ptr_factory_.GetWeakPtr(),
app_id, normalized_sender_ids, std::move(unregister_iter->second));
return;
}
RegisterImpl(app_id, normalized_sender_ids);
}
void GCMDriver::Unregister(const std::string& app_id,
UnregisterCallback callback) {
UnregisterInternal(app_id, nullptr /* sender_id */, std::move(callback));
}
void GCMDriver::UnregisterWithSenderId(const std::string& app_id,
const std::string& sender_id,
UnregisterCallback callback) {
DCHECK(!sender_id.empty());
UnregisterInternal(app_id, &sender_id, std::move(callback));
}
void GCMDriver::UnregisterInternal(const std::string& app_id,
const std::string* sender_id,
UnregisterCallback callback) {
DCHECK(!app_id.empty());
DCHECK(!callback.is_null());
GCMClient::Result result = EnsureStarted(GCMClient::IMMEDIATE_START);
if (result != GCMClient::SUCCESS) {
std::move(callback).Run(result);
return;
}
// If previous un/register operation is still in progress, bail out.
if (register_callbacks_.find(app_id) != register_callbacks_.end() ||
unregister_callbacks_.find(app_id) != unregister_callbacks_.end()) {
std::move(callback).Run(GCMClient::ASYNC_OPERATION_PENDING);
return;
}
unregister_callbacks_[app_id] = std::move(callback);
if (sender_id)
UnregisterWithSenderIdImpl(app_id, *sender_id);
else
UnregisterImpl(app_id);
}
void GCMDriver::Send(const std::string& app_id,
const std::string& receiver_id,
const OutgoingMessage& message,
const SendCallback& callback) {
DCHECK(!app_id.empty());
DCHECK(!receiver_id.empty());
DCHECK(!callback.is_null());
GCMClient::Result result = EnsureStarted(GCMClient::IMMEDIATE_START);
if (result != GCMClient::SUCCESS) {
callback.Run(std::string(), result);
return;
}
// If the message with send ID is still in progress, bail out.
std::pair<std::string, std::string> key(app_id, message.id);
if (send_callbacks_.find(key) != send_callbacks_.end()) {
callback.Run(message.id, GCMClient::INVALID_PARAMETER);
return;
}
send_callbacks_[key] = callback;
SendImpl(app_id, receiver_id, message);
}
void GCMDriver::GetEncryptionInfo(const std::string& app_id,
GetEncryptionInfoCallback callback) {
encryption_provider_.GetEncryptionInfo(app_id, "" /* authorized_entity */,
std::move(callback));
}
void GCMDriver::UnregisterWithSenderIdImpl(const std::string& app_id,
const std::string& sender_id) {
NOTREACHED();
}
void GCMDriver::RegisterFinished(const std::string& app_id,
const std::string& registration_id,
GCMClient::Result result) {
auto callback_iter = register_callbacks_.find(app_id);
if (callback_iter == register_callbacks_.end()) {
// The callback could have been removed when the app is uninstalled.
return;
}
RegisterCallback callback = std::move(callback_iter->second);
register_callbacks_.erase(callback_iter);
std::move(callback).Run(registration_id, result);
}
void GCMDriver::RemoveEncryptionInfoAfterUnregister(const std::string& app_id,
GCMClient::Result result) {
encryption_provider_.RemoveEncryptionInfo(
app_id, "" /* authorized_entity */,
base::Bind(&GCMDriver::UnregisterFinished, weak_ptr_factory_.GetWeakPtr(),
app_id, result));
}
void GCMDriver::UnregisterFinished(const std::string& app_id,
GCMClient::Result result) {
auto callback_iter = unregister_callbacks_.find(app_id);
if (callback_iter == unregister_callbacks_.end())
return;
UnregisterCallback callback = std::move(callback_iter->second);
unregister_callbacks_.erase(callback_iter);
std::move(callback).Run(result);
}
void GCMDriver::SendFinished(const std::string& app_id,
const std::string& message_id,
GCMClient::Result result) {
auto callback_iter = send_callbacks_.find(
std::pair<std::string, std::string>(app_id, message_id));
if (callback_iter == send_callbacks_.end()) {
// The callback could have been removed when the app is uninstalled.
return;
}
SendCallback callback = callback_iter->second;
send_callbacks_.erase(callback_iter);
callback.Run(message_id, result);
}
void GCMDriver::Shutdown() {
for (GCMAppHandlerMap::const_iterator iter = app_handlers_.begin();
iter != app_handlers_.end(); ++iter) {
DVLOG(1) << "Calling ShutdownHandler for: " << iter->first;
iter->second->ShutdownHandler();
}
app_handlers_.clear();
}
void GCMDriver::AddAppHandler(const std::string& app_id,
GCMAppHandler* handler) {
DCHECK(!app_id.empty());
DCHECK(handler);
DCHECK_EQ(app_handlers_.count(app_id), 0u);
app_handlers_[app_id] = handler;
DVLOG(1) << "App handler added for: " << app_id;
}
void GCMDriver::RemoveAppHandler(const std::string& app_id) {
DCHECK(!app_id.empty());
app_handlers_.erase(app_id);
DVLOG(1) << "App handler removed for: " << app_id;
}
GCMAppHandler* GCMDriver::GetAppHandler(const std::string& app_id) {
// Look for exact match.
GCMAppHandlerMap::const_iterator iter = app_handlers_.find(app_id);
if (iter != app_handlers_.end())
return iter->second;
// Ask the handlers whether they know how to handle it.
for (iter = app_handlers_.begin(); iter != app_handlers_.end(); ++iter) {
if (iter->second->CanHandle(app_id))
return iter->second;
}
return nullptr;
}
GCMEncryptionProvider* GCMDriver::GetEncryptionProviderInternal() {
return &encryption_provider_;
}
bool GCMDriver::HasRegisterCallback(const std::string& app_id) {
return register_callbacks_.find(app_id) != register_callbacks_.end();
}
void GCMDriver::ClearCallbacks() {
register_callbacks_.clear();
unregister_callbacks_.clear();
send_callbacks_.clear();
}
void GCMDriver::DispatchMessage(const std::string& app_id,
const IncomingMessage& message) {
encryption_provider_.DecryptMessage(
app_id, message, base::Bind(&GCMDriver::DispatchMessageInternal,
weak_ptr_factory_.GetWeakPtr(), app_id));
}
void GCMDriver::DispatchMessageInternal(const std::string& app_id,
GCMDecryptionResult result,
const IncomingMessage& message) {
UMA_HISTOGRAM_ENUMERATION("GCM.Crypto.DecryptMessageResult", result,
GCMDecryptionResult::ENUM_SIZE);
switch (result) {
case GCMDecryptionResult::UNENCRYPTED:
case GCMDecryptionResult::DECRYPTED_DRAFT_03:
case GCMDecryptionResult::DECRYPTED_DRAFT_08: {
GCMAppHandler* handler = GetAppHandler(app_id);
if (handler)
handler->OnMessage(app_id, message);
// TODO(peter/harkness): Surface unavailable app handlers on
// chrome://gcm-internals and send a delivery receipt.
return;
}
case GCMDecryptionResult::INVALID_ENCRYPTION_HEADER:
case GCMDecryptionResult::INVALID_CRYPTO_KEY_HEADER:
case GCMDecryptionResult::NO_KEYS:
case GCMDecryptionResult::INVALID_SHARED_SECRET:
case GCMDecryptionResult::INVALID_PAYLOAD:
case GCMDecryptionResult::INVALID_BINARY_HEADER_PAYLOAD_LENGTH:
case GCMDecryptionResult::INVALID_BINARY_HEADER_RECORD_SIZE:
case GCMDecryptionResult::INVALID_BINARY_HEADER_PUBLIC_KEY_LENGTH:
case GCMDecryptionResult::INVALID_BINARY_HEADER_PUBLIC_KEY_FORMAT: {
RecordDecryptionFailure(app_id, result);
GCMAppHandler* handler = GetAppHandler(app_id);
if (handler) {
handler->OnMessageDecryptionFailed(
app_id, message.message_id,
ToGCMDecryptionResultDetailsString(result));
}
return;
}
case GCMDecryptionResult::ENUM_SIZE:
break; // deliberate fall-through
}
NOTREACHED();
}
void GCMDriver::RegisterAfterUnregister(
const std::string& app_id,
const std::vector<std::string>& normalized_sender_ids,
UnregisterCallback unregister_callback,
GCMClient::Result result) {
// Invoke the original unregister callback.
std::move(unregister_callback).Run(result);
// Trigger the pending registration.
DCHECK(register_callbacks_.find(app_id) != register_callbacks_.end());
RegisterImpl(app_id, normalized_sender_ids);
}
void GCMDriver::SendWebPushMessage(const std::string& app_id,
const std::string& authorized_entity,
const std::string& p256dh,
const std::string& auth_secret,
const std::string& fcm_token,
crypto::ECPrivateKey* vapid_key,
WebPushMessage message,
WebPushCallback callback) {
std::string payload_copy = message.payload;
encryption_provider_.EncryptMessage(
app_id, authorized_entity, p256dh, auth_secret, payload_copy,
base::BindOnce(&GCMDriver::OnMessageEncrypted,
weak_ptr_factory_.GetWeakPtr(), fcm_token, vapid_key,
std::move(message), std::move(callback)));
}
void GCMDriver::OnMessageEncrypted(const std::string& fcm_token,
crypto::ECPrivateKey* vapid_key,
WebPushMessage message,
WebPushCallback callback,
GCMEncryptionResult result,
std::string payload) {
UMA_HISTOGRAM_ENUMERATION("GCM.Crypto.EncryptMessageResult", result,
GCMEncryptionResult::ENUM_SIZE);
switch (result) {
case GCMEncryptionResult::ENCRYPTED_DRAFT_08: {
message.payload = std::move(payload);
web_push_sender_.SendMessage(fcm_token, vapid_key, std::move(message),
std::move(callback));
return;
}
case GCMEncryptionResult::NO_KEYS:
case GCMEncryptionResult::INVALID_SHARED_SECRET:
case GCMEncryptionResult::ENCRYPTION_FAILED: {
InvokeWebPushCallback(std::move(callback),
SendWebPushMessageResult::kEncryptionFailed);
return;
}
case GCMEncryptionResult::ENUM_SIZE:
break; // deliberate fall-through
}
NOTREACHED();
}
} // namespace gcm