This source file includes following definitions.
- RecordIncomingMessageStatus
- RecordOutgoingMessageStatus
- weak_factory_
- Register
- OnRegisterComplete
- SendMessage
- RequestAccessToken
- OnGetTokenComplete
- OnURLFetchComplete
- OnIncomingMessage
- OnNetworkChanged
- BuildUrl
- Base64EncodeURLSafe
- Base64DecodeURLSafe
- SetMessageReceiver
- RequestDetailedStatus
- UpdateCredentials
- ResetRegisterBackoffEntryForTest
- sent_messages_count_
- CollectDebugData
- GCMClientResultToString
#include "base/base64.h"
#include "base/i18n/time_formatting.h"
#include "base/metrics/histogram.h"
#include "base/sha1.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#if !defined(OS_ANDROID)
#include "google/cacheinvalidation/android_channel.pb.h"
#include "google/cacheinvalidation/channel_common.pb.h"
#endif
#include "google_apis/gaia/google_service_auth_error.h"
#include "net/http/http_status_code.h"
#include "net/url_request/url_fetcher.h"
#include "net/url_request/url_request_status.h"
#include "sync/notifier/gcm_network_channel.h"
#include "sync/notifier/gcm_network_channel_delegate.h"
namespace syncer {
namespace {
const char kCacheInvalidationEndpointUrl[] =
"https://clients4.google.com/invalidation/android/request/";
const char kCacheInvalidationPackageName[] = "com.google.chrome.invalidations";
const net::BackoffEntry::Policy kRegisterBackoffPolicy = {
0,
2000,
2,
0.2,
1000 * 3600 * 4,
-1,
false,
};
enum IncomingMessageStatus {
INCOMING_MESSAGE_SUCCESS,
MESSAGE_EMPTY,
INVALID_ENCODING,
INVALID_PROTO,
INCOMING_MESSAGE_STATUS_COUNT
};
enum OutgoingMessageStatus {
OUTGOING_MESSAGE_SUCCESS,
MESSAGE_DISCARDED,
ACCESS_TOKEN_FAILURE,
POST_FAILURE,
OUTGOING_MESSAGE_STATUS_COUNT
};
const char kIncomingMessageStatusHistogram[] =
"GCMInvalidations.IncomingMessageStatus";
const char kOutgoingMessageStatusHistogram[] =
"GCMInvalidations.OutgoingMessageStatus";
void RecordIncomingMessageStatus(IncomingMessageStatus status) {
UMA_HISTOGRAM_ENUMERATION(kIncomingMessageStatusHistogram,
status,
INCOMING_MESSAGE_STATUS_COUNT);
}
void RecordOutgoingMessageStatus(OutgoingMessageStatus status) {
UMA_HISTOGRAM_ENUMERATION(kOutgoingMessageStatusHistogram,
MESSAGE_DISCARDED,
OUTGOING_MESSAGE_STATUS_COUNT);
}
}
GCMNetworkChannel::GCMNetworkChannel(
scoped_refptr<net::URLRequestContextGetter> request_context_getter,
scoped_ptr<GCMNetworkChannelDelegate> delegate)
: request_context_getter_(request_context_getter),
delegate_(delegate.Pass()),
register_backoff_entry_(new net::BackoffEntry(&kRegisterBackoffPolicy)),
diagnostic_info_(this),
weak_factory_(this) {
net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
delegate_->Initialize();
Register();
}
GCMNetworkChannel::~GCMNetworkChannel() {
net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
}
void GCMNetworkChannel::Register() {
delegate_->Register(base::Bind(&GCMNetworkChannel::OnRegisterComplete,
weak_factory_.GetWeakPtr()));
}
void GCMNetworkChannel::OnRegisterComplete(
const std::string& registration_id,
gcm::GCMClient::Result result) {
DCHECK(CalledOnValidThread());
if (result == gcm::GCMClient::SUCCESS) {
DCHECK(!registration_id.empty());
DVLOG(2) << "Got registration_id";
register_backoff_entry_->Reset();
registration_id_ = registration_id;
if (!cached_message_.empty())
RequestAccessToken();
} else {
DVLOG(2) << "Register failed: " << result;
switch (result) {
case gcm::GCMClient::NETWORK_ERROR:
case gcm::GCMClient::SERVER_ERROR:
case gcm::GCMClient::TTL_EXCEEDED:
case gcm::GCMClient::UNKNOWN_ERROR: {
register_backoff_entry_->InformOfRequest(false);
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&GCMNetworkChannel::Register,
weak_factory_.GetWeakPtr()),
register_backoff_entry_->GetTimeUntilRelease());
break;
}
default:
break;
}
}
diagnostic_info_.registration_id_ = registration_id_;
diagnostic_info_.registration_result_ = result;
}
void GCMNetworkChannel::SendMessage(const std::string& message) {
DCHECK(CalledOnValidThread());
DCHECK(!message.empty());
DVLOG(2) << "SendMessage";
diagnostic_info_.sent_messages_count_++;
if (!cached_message_.empty()) {
RecordOutgoingMessageStatus(MESSAGE_DISCARDED);
}
cached_message_ = message;
if (!registration_id_.empty()) {
RequestAccessToken();
}
}
void GCMNetworkChannel::RequestAccessToken() {
DCHECK(CalledOnValidThread());
delegate_->RequestToken(base::Bind(&GCMNetworkChannel::OnGetTokenComplete,
weak_factory_.GetWeakPtr()));
}
void GCMNetworkChannel::OnGetTokenComplete(
const GoogleServiceAuthError& error,
const std::string& token) {
DCHECK(CalledOnValidThread());
if (cached_message_.empty()) {
return;
}
if (error.state() != GoogleServiceAuthError::NONE) {
DVLOG(1) << "RequestAccessToken failed: " << error.ToString();
RecordOutgoingMessageStatus(ACCESS_TOKEN_FAILURE);
if (error.state() == GoogleServiceAuthError::CONNECTION_FAILED)
NotifyStateChange(TRANSIENT_INVALIDATION_ERROR);
cached_message_.clear();
return;
}
DCHECK(!token.empty());
access_token_ = token;
DVLOG(2) << "Got access token, sending message";
fetcher_.reset(net::URLFetcher::Create(
BuildUrl(registration_id_), net::URLFetcher::POST, this));
fetcher_->SetRequestContext(request_context_getter_);
const std::string auth_header("Authorization: Bearer " + access_token_);
fetcher_->AddExtraRequestHeader(auth_header);
if (!echo_token_.empty()) {
const std::string echo_header("echo-token: " + echo_token_);
fetcher_->AddExtraRequestHeader(echo_header);
}
fetcher_->SetUploadData("application/x-protobuffer", cached_message_);
fetcher_->Start();
cached_message_.clear();
}
void GCMNetworkChannel::OnURLFetchComplete(const net::URLFetcher* source) {
DCHECK(CalledOnValidThread());
DCHECK_EQ(fetcher_, source);
scoped_ptr<net::URLFetcher> fetcher = fetcher_.Pass();
net::URLRequestStatus status = fetcher->GetStatus();
diagnostic_info_.last_post_response_code_ =
status.is_success() ? source->GetResponseCode() : status.error();
if (status.is_success() &&
fetcher->GetResponseCode() == net::HTTP_UNAUTHORIZED) {
DVLOG(1) << "URLFetcher failure: HTTP_UNAUTHORIZED";
delegate_->InvalidateToken(access_token_);
}
if (!status.is_success() ||
(fetcher->GetResponseCode() != net::HTTP_OK &&
fetcher->GetResponseCode() != net::HTTP_NO_CONTENT)) {
DVLOG(1) << "URLFetcher failure";
RecordOutgoingMessageStatus(POST_FAILURE);
NotifyStateChange(TRANSIENT_INVALIDATION_ERROR);
return;
}
RecordOutgoingMessageStatus(OUTGOING_MESSAGE_SUCCESS);
NotifyStateChange(INVALIDATIONS_ENABLED);
DVLOG(2) << "URLFetcher success";
}
void GCMNetworkChannel::OnIncomingMessage(const std::string& message,
const std::string& echo_token) {
#if !defined(OS_ANDROID)
if (!echo_token.empty())
echo_token_ = echo_token;
diagnostic_info_.last_message_empty_echo_token_ = echo_token.empty();
diagnostic_info_.last_message_received_time_ = base::Time::Now();
if (message.empty()) {
RecordIncomingMessageStatus(MESSAGE_EMPTY);
return;
}
std::string data;
if (!Base64DecodeURLSafe(message, &data)) {
RecordIncomingMessageStatus(INVALID_ENCODING);
return;
}
ipc::invalidation::AddressedAndroidMessage android_message;
if (!android_message.ParseFromString(data) ||
!android_message.has_message()) {
RecordIncomingMessageStatus(INVALID_PROTO);
return;
}
DVLOG(2) << "Deliver incoming message";
RecordIncomingMessageStatus(INCOMING_MESSAGE_SUCCESS);
DeliverIncomingMessage(android_message.message());
#else
NOTREACHED();
#endif
}
void GCMNetworkChannel::OnNetworkChanged(
net::NetworkChangeNotifier::ConnectionType connection_type) {
if (connection_type != net::NetworkChangeNotifier::CONNECTION_NONE)
NotifyStateChange(INVALIDATIONS_ENABLED);
}
GURL GCMNetworkChannel::BuildUrl(const std::string& registration_id) {
DCHECK(!registration_id.empty());
#if !defined(OS_ANDROID)
ipc::invalidation::EndpointId endpoint_id;
endpoint_id.set_c2dm_registration_id(registration_id);
endpoint_id.set_client_key(std::string());
endpoint_id.set_package_name(kCacheInvalidationPackageName);
endpoint_id.mutable_channel_version()->set_major_version(
ipc::invalidation::INITIAL);
std::string endpoint_id_buffer;
endpoint_id.SerializeToString(&endpoint_id_buffer);
ipc::invalidation::NetworkEndpointId network_endpoint_id;
network_endpoint_id.set_network_address(
ipc::invalidation::NetworkEndpointId_NetworkAddress_ANDROID);
network_endpoint_id.set_client_address(endpoint_id_buffer);
std::string network_endpoint_id_buffer;
network_endpoint_id.SerializeToString(&network_endpoint_id_buffer);
std::string base64URLPiece;
Base64EncodeURLSafe(network_endpoint_id_buffer, &base64URLPiece);
std::string url(kCacheInvalidationEndpointUrl);
url += base64URLPiece;
return GURL(url);
#else
NOTREACHED();
return GURL();
#endif
}
void GCMNetworkChannel::Base64EncodeURLSafe(const std::string& input,
std::string* output) {
base::Base64Encode(input, output);
base::ReplaceChars(*output, "+", "-", output);
base::ReplaceChars(*output, "/", "_", output);
size_t padding_size = 0;
for (size_t i = output->size(); i > 0 && (*output)[i - 1] == '='; --i)
++padding_size;
output->resize(output->size() - padding_size);
}
bool GCMNetworkChannel::Base64DecodeURLSafe(const std::string& input,
std::string* output) {
size_t padded_size = (input.size() + 3) - (input.size() + 3) % 4;
std::string padded_input(input);
padded_input.resize(padded_size, '=');
base::ReplaceChars(padded_input, "-", "+", &padded_input);
base::ReplaceChars(padded_input, "_", "/", &padded_input);
return base::Base64Decode(padded_input, output);
}
void GCMNetworkChannel::SetMessageReceiver(
invalidation::MessageCallback* incoming_receiver) {
delegate_->SetMessageReceiver(base::Bind(
&GCMNetworkChannel::OnIncomingMessage, weak_factory_.GetWeakPtr()));
SyncNetworkChannel::SetMessageReceiver(incoming_receiver);
}
void GCMNetworkChannel::RequestDetailedStatus(
base::Callback<void(const base::DictionaryValue&)> callback) {
callback.Run(*diagnostic_info_.CollectDebugData());
}
void GCMNetworkChannel::UpdateCredentials(const std::string& email,
const std::string& token) {
}
void GCMNetworkChannel::ResetRegisterBackoffEntryForTest(
const net::BackoffEntry::Policy* policy) {
register_backoff_entry_.reset(new net::BackoffEntry(policy));
}
GCMNetworkChannelDiagnostic::GCMNetworkChannelDiagnostic(
GCMNetworkChannel* parent)
: parent_(parent),
last_message_empty_echo_token_(false),
last_post_response_code_(0),
registration_result_(gcm::GCMClient::UNKNOWN_ERROR),
sent_messages_count_(0) {}
scoped_ptr<base::DictionaryValue>
GCMNetworkChannelDiagnostic::CollectDebugData() const {
scoped_ptr<base::DictionaryValue> status(new base::DictionaryValue);
status->SetString("GCMNetworkChannel.Channel", "GCM");
std::string reg_id_hash = base::SHA1HashString(registration_id_);
status->SetString("GCMNetworkChannel.HashedRegistrationID",
base::HexEncode(reg_id_hash.c_str(), reg_id_hash.size()));
status->SetString("GCMNetworkChannel.RegistrationResult",
GCMClientResultToString(registration_result_));
status->SetBoolean("GCMNetworkChannel.HadLastMessageEmptyEchoToken",
last_message_empty_echo_token_);
status->SetString(
"GCMNetworkChannel.LastMessageReceivedTime",
base::TimeFormatShortDateAndTime(last_message_received_time_));
status->SetInteger("GCMNetworkChannel.LastPostResponseCode",
last_post_response_code_);
status->SetInteger("GCMNetworkChannel.SentMessages", sent_messages_count_);
status->SetInteger("GCMNetworkChannel.ReceivedMessages",
parent_->GetReceivedMessagesCount());
return status.Pass();
}
std::string GCMNetworkChannelDiagnostic::GCMClientResultToString(
const gcm::GCMClient::Result result) const {
#define ENUM_CASE(x) case x: return #x; break;
switch (result) {
ENUM_CASE(gcm::GCMClient::SUCCESS);
ENUM_CASE(gcm::GCMClient::NETWORK_ERROR);
ENUM_CASE(gcm::GCMClient::SERVER_ERROR);
ENUM_CASE(gcm::GCMClient::TTL_EXCEEDED);
ENUM_CASE(gcm::GCMClient::UNKNOWN_ERROR);
ENUM_CASE(gcm::GCMClient::NOT_SIGNED_IN);
ENUM_CASE(gcm::GCMClient::INVALID_PARAMETER);
ENUM_CASE(gcm::GCMClient::ASYNC_OPERATION_PENDING);
}
NOTREACHED();
return "";
}
}