diff options
Diffstat (limited to 'chromium/google_apis/gcm/engine/gcm_store_impl.cc')
-rw-r--r-- | chromium/google_apis/gcm/engine/gcm_store_impl.cc | 956 |
1 files changed, 956 insertions, 0 deletions
diff --git a/chromium/google_apis/gcm/engine/gcm_store_impl.cc b/chromium/google_apis/gcm/engine/gcm_store_impl.cc new file mode 100644 index 00000000000..e27e82e6ce2 --- /dev/null +++ b/chromium/google_apis/gcm/engine/gcm_store_impl.cc @@ -0,0 +1,956 @@ +// 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 "google_apis/gcm/engine/gcm_store_impl.h" + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/callback.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/metrics/histogram.h" +#include "base/sequenced_task_runner.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" +#include "base/time/time.h" +#include "base/tracked_objects.h" +#include "google_apis/gcm/base/encryptor.h" +#include "google_apis/gcm/base/mcs_message.h" +#include "google_apis/gcm/base/mcs_util.h" +#include "google_apis/gcm/protocol/mcs.pb.h" +#include "third_party/leveldatabase/src/include/leveldb/db.h" +#include "third_party/leveldatabase/src/include/leveldb/write_batch.h" + +namespace gcm { + +namespace { + +// Limit to the number of outstanding messages per app. +const int kMessagesPerAppLimit = 20; + +// ---- LevelDB keys. ---- +// Key for this device's android id. +const char kDeviceAIDKey[] = "device_aid_key"; +// Key for this device's android security token. +const char kDeviceTokenKey[] = "device_token_key"; +// Lowest lexicographically ordered app ids. +// Used for prefixing app id. +const char kRegistrationKeyStart[] = "reg1-"; +// Key guaranteed to be higher than all app ids. +// Used for limiting iteration. +const char kRegistrationKeyEnd[] = "reg2-"; +// Lowest lexicographically ordered incoming message key. +// Used for prefixing messages. +const char kIncomingMsgKeyStart[] = "incoming1-"; +// Key guaranteed to be higher than all incoming message keys. +// Used for limiting iteration. +const char kIncomingMsgKeyEnd[] = "incoming2-"; +// Lowest lexicographically ordered outgoing message key. +// Used for prefixing outgoing messages. +const char kOutgoingMsgKeyStart[] = "outgoing1-"; +// Key guaranteed to be higher than all outgoing message keys. +// Used for limiting iteration. +const char kOutgoingMsgKeyEnd[] = "outgoing2-"; +// Lowest lexicographically ordered G-service settings key. +// Used for prefixing G-services settings. +const char kGServiceSettingKeyStart[] = "gservice1-"; +// Key guaranteed to be higher than all G-services settings keys. +// Used for limiting iteration. +const char kGServiceSettingKeyEnd[] = "gservice2-"; +// Key for digest of the last G-services settings update. +const char kGServiceSettingsDigestKey[] = "gservices_digest"; +// Key used to timestamp last checkin (marked with G services settings update). +const char kLastCheckinTimeKey[] = "last_checkin_time"; + +std::string MakeRegistrationKey(const std::string& app_id) { + return kRegistrationKeyStart + app_id; +} + +std::string ParseRegistrationKey(const std::string& key) { + return key.substr(arraysize(kRegistrationKeyStart) - 1); +} + +std::string MakeIncomingKey(const std::string& persistent_id) { + return kIncomingMsgKeyStart + persistent_id; +} + +std::string MakeOutgoingKey(const std::string& persistent_id) { + return kOutgoingMsgKeyStart + persistent_id; +} + +std::string ParseOutgoingKey(const std::string& key) { + return key.substr(arraysize(kOutgoingMsgKeyStart) - 1); +} + +std::string MakeGServiceSettingKey(const std::string& setting_name) { + return kGServiceSettingKeyStart + setting_name; +} + +std::string ParseGServiceSettingKey(const std::string& key) { + return key.substr(arraysize(kGServiceSettingKeyStart) - 1); +} + +// Note: leveldb::Slice keeps a pointer to the data in |s|, which must therefore +// outlive the slice. +// For example: MakeSlice(MakeOutgoingKey(x)) is invalid. +leveldb::Slice MakeSlice(const base::StringPiece& s) { + return leveldb::Slice(s.begin(), s.size()); +} + +} // namespace + +class GCMStoreImpl::Backend + : public base::RefCountedThreadSafe<GCMStoreImpl::Backend> { + public: + Backend(const base::FilePath& path, + scoped_refptr<base::SequencedTaskRunner> foreground_runner, + scoped_ptr<Encryptor> encryptor); + + // Blocking implementations of GCMStoreImpl methods. + void Load(const LoadCallback& callback); + void Close(); + void Destroy(const UpdateCallback& callback); + void SetDeviceCredentials(uint64 device_android_id, + uint64 device_security_token, + const UpdateCallback& callback); + void AddRegistration(const std::string& app_id, + const linked_ptr<RegistrationInfo>& registration, + const UpdateCallback& callback); + void RemoveRegistration(const std::string& app_id, + const UpdateCallback& callback); + void AddIncomingMessage(const std::string& persistent_id, + const UpdateCallback& callback); + void RemoveIncomingMessages(const PersistentIdList& persistent_ids, + const UpdateCallback& callback); + void AddOutgoingMessage(const std::string& persistent_id, + const MCSMessage& message, + const UpdateCallback& callback); + void RemoveOutgoingMessages( + const PersistentIdList& persistent_ids, + const base::Callback<void(bool, const AppIdToMessageCountMap&)> + callback); + void AddUserSerialNumber(const std::string& username, + int64 serial_number, + const UpdateCallback& callback); + void RemoveUserSerialNumber(const std::string& username, + const UpdateCallback& callback); + void SetLastCheckinTime(const base::Time& last_checkin_time, + const UpdateCallback& callback); + void SetGServicesSettings( + const std::map<std::string, std::string>& settings, + const std::string& digest, + const UpdateCallback& callback); + + private: + friend class base::RefCountedThreadSafe<Backend>; + ~Backend(); + + bool LoadDeviceCredentials(uint64* android_id, uint64* security_token); + bool LoadRegistrations(RegistrationInfoMap* registrations); + bool LoadIncomingMessages(std::vector<std::string>* incoming_messages); + bool LoadOutgoingMessages(OutgoingMessageMap* outgoing_messages); + bool LoadLastCheckinTime(base::Time* last_checkin_time); + bool LoadGServicesSettings(std::map<std::string, std::string>* settings, + std::string* digest); + + const base::FilePath path_; + scoped_refptr<base::SequencedTaskRunner> foreground_task_runner_; + scoped_ptr<Encryptor> encryptor_; + + scoped_ptr<leveldb::DB> db_; +}; + +GCMStoreImpl::Backend::Backend( + const base::FilePath& path, + scoped_refptr<base::SequencedTaskRunner> foreground_task_runner, + scoped_ptr<Encryptor> encryptor) + : path_(path), + foreground_task_runner_(foreground_task_runner), + encryptor_(encryptor.Pass()) { +} + +GCMStoreImpl::Backend::~Backend() {} + +void GCMStoreImpl::Backend::Load(const LoadCallback& callback) { + scoped_ptr<LoadResult> result(new LoadResult()); + if (db_.get()) { + LOG(ERROR) << "Attempting to reload open database."; + foreground_task_runner_->PostTask(FROM_HERE, + base::Bind(callback, + base::Passed(&result))); + return; + } + + leveldb::Options options; + options.create_if_missing = true; + leveldb::DB* db; + leveldb::Status status = + leveldb::DB::Open(options, path_.AsUTF8Unsafe(), &db); + UMA_HISTOGRAM_BOOLEAN("GCM.LoadSucceeded", status.ok()); + if (!status.ok()) { + LOG(ERROR) << "Failed to open database " << path_.value() << ": " + << status.ToString(); + foreground_task_runner_->PostTask(FROM_HERE, + base::Bind(callback, + base::Passed(&result))); + return; + } + db_.reset(db); + + if (!LoadDeviceCredentials(&result->device_android_id, + &result->device_security_token) || + !LoadRegistrations(&result->registrations) || + !LoadIncomingMessages(&result->incoming_messages) || + !LoadOutgoingMessages(&result->outgoing_messages) || + !LoadLastCheckinTime(&result->last_checkin_time) || + !LoadGServicesSettings(&result->gservices_settings, + &result->gservices_digest)) { + result->device_android_id = 0; + result->device_security_token = 0; + result->registrations.clear(); + result->incoming_messages.clear(); + result->outgoing_messages.clear(); + result->gservices_settings.clear(); + result->gservices_digest.clear(); + result->last_checkin_time = base::Time::FromInternalValue(0LL); + foreground_task_runner_->PostTask(FROM_HERE, + base::Bind(callback, + base::Passed(&result))); + return; + } + + // Only record histograms if GCM had already been set up for this device. + if (result->device_android_id != 0 && result->device_security_token != 0) { + int64 file_size = 0; + if (base::GetFileSize(path_, &file_size)) { + UMA_HISTOGRAM_COUNTS("GCM.StoreSizeKB", + static_cast<int>(file_size / 1024)); + } + UMA_HISTOGRAM_COUNTS("GCM.RestoredRegistrations", + result->registrations.size()); + UMA_HISTOGRAM_COUNTS("GCM.RestoredOutgoingMessages", + result->outgoing_messages.size()); + UMA_HISTOGRAM_COUNTS("GCM.RestoredIncomingMessages", + result->incoming_messages.size()); + } + + DVLOG(1) << "Succeeded in loading " << result->registrations.size() + << " registrations, " + << result->incoming_messages.size() + << " unacknowledged incoming messages and " + << result->outgoing_messages.size() + << " unacknowledged outgoing messages."; + result->success = true; + foreground_task_runner_->PostTask(FROM_HERE, + base::Bind(callback, + base::Passed(&result))); + return; +} + +void GCMStoreImpl::Backend::Close() { + DVLOG(1) << "Closing GCM store."; + db_.reset(); +} + +void GCMStoreImpl::Backend::Destroy(const UpdateCallback& callback) { + DVLOG(1) << "Destroying GCM store."; + db_.reset(); + const leveldb::Status s = + leveldb::DestroyDB(path_.AsUTF8Unsafe(), leveldb::Options()); + if (s.ok()) { + foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, true)); + return; + } + LOG(ERROR) << "Destroy failed: " << s.ToString(); + foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false)); +} + +void GCMStoreImpl::Backend::SetDeviceCredentials( + uint64 device_android_id, + uint64 device_security_token, + const UpdateCallback& callback) { + DVLOG(1) << "Saving device credentials with AID " << device_android_id; + if (!db_.get()) { + LOG(ERROR) << "GCMStore db doesn't exist."; + foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false)); + return; + } + + leveldb::WriteOptions write_options; + write_options.sync = true; + + std::string encrypted_token; + encryptor_->EncryptString(base::Uint64ToString(device_security_token), + &encrypted_token); + std::string android_id_str = base::Uint64ToString(device_android_id); + leveldb::Status s = + db_->Put(write_options, + MakeSlice(kDeviceAIDKey), + MakeSlice(android_id_str)); + if (s.ok()) { + s = db_->Put( + write_options, MakeSlice(kDeviceTokenKey), MakeSlice(encrypted_token)); + } + if (s.ok()) { + foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, true)); + return; + } + LOG(ERROR) << "LevelDB put failed: " << s.ToString(); + foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false)); +} + +void GCMStoreImpl::Backend::AddRegistration( + const std::string& app_id, + const linked_ptr<RegistrationInfo>& registration, + const UpdateCallback& callback) { + DVLOG(1) << "Saving registration info for app: " << app_id; + if (!db_.get()) { + LOG(ERROR) << "GCMStore db doesn't exist."; + foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false)); + return; + } + leveldb::WriteOptions write_options; + write_options.sync = true; + + std::string key = MakeRegistrationKey(app_id); + std::string value = registration->SerializeAsString(); + const leveldb::Status status = db_->Put(write_options, + MakeSlice(key), + MakeSlice(value)); + if (status.ok()) { + foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, true)); + return; + } + LOG(ERROR) << "LevelDB put failed: " << status.ToString(); + foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false)); +} + +void GCMStoreImpl::Backend::RemoveRegistration(const std::string& app_id, + const UpdateCallback& callback) { + if (!db_.get()) { + LOG(ERROR) << "GCMStore db doesn't exist."; + foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false)); + return; + } + leveldb::WriteOptions write_options; + write_options.sync = true; + + leveldb::Status status = db_->Delete(write_options, MakeSlice(app_id)); + if (status.ok()) { + foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, true)); + return; + } + LOG(ERROR) << "LevelDB remove failed: " << status.ToString(); + foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false)); +} + +void GCMStoreImpl::Backend::AddIncomingMessage(const std::string& persistent_id, + const UpdateCallback& callback) { + DVLOG(1) << "Saving incoming message with id " << persistent_id; + if (!db_.get()) { + LOG(ERROR) << "GCMStore db doesn't exist."; + foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false)); + return; + } + + leveldb::WriteOptions write_options; + write_options.sync = true; + + std::string key = MakeIncomingKey(persistent_id); + const leveldb::Status s = db_->Put(write_options, + MakeSlice(key), + MakeSlice(persistent_id)); + if (s.ok()) { + foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, true)); + return; + } + LOG(ERROR) << "LevelDB put failed: " << s.ToString(); + foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false)); +} + +void GCMStoreImpl::Backend::RemoveIncomingMessages( + const PersistentIdList& persistent_ids, + const UpdateCallback& callback) { + if (!db_.get()) { + LOG(ERROR) << "GCMStore db doesn't exist."; + foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false)); + return; + } + leveldb::WriteOptions write_options; + write_options.sync = true; + + leveldb::Status s; + for (PersistentIdList::const_iterator iter = persistent_ids.begin(); + iter != persistent_ids.end(); + ++iter) { + DVLOG(1) << "Removing incoming message with id " << *iter; + std::string key = MakeIncomingKey(*iter); + s = db_->Delete(write_options, MakeSlice(key)); + if (!s.ok()) + break; + } + if (s.ok()) { + foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, true)); + return; + } + LOG(ERROR) << "LevelDB remove failed: " << s.ToString(); + foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false)); +} + +void GCMStoreImpl::Backend::AddOutgoingMessage(const std::string& persistent_id, + const MCSMessage& message, + const UpdateCallback& callback) { + DVLOG(1) << "Saving outgoing message with id " << persistent_id; + if (!db_.get()) { + LOG(ERROR) << "GCMStore db doesn't exist."; + foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false)); + return; + } + leveldb::WriteOptions write_options; + write_options.sync = true; + + std::string data = + static_cast<char>(message.tag()) + message.SerializeAsString(); + std::string key = MakeOutgoingKey(persistent_id); + const leveldb::Status s = db_->Put(write_options, + MakeSlice(key), + MakeSlice(data)); + if (s.ok()) { + foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, true)); + return; + } + LOG(ERROR) << "LevelDB put failed: " << s.ToString(); + foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false)); +} + +void GCMStoreImpl::Backend::RemoveOutgoingMessages( + const PersistentIdList& persistent_ids, + const base::Callback<void(bool, const AppIdToMessageCountMap&)> + callback) { + if (!db_.get()) { + LOG(ERROR) << "GCMStore db doesn't exist."; + foreground_task_runner_->PostTask(FROM_HERE, + base::Bind(callback, + false, + AppIdToMessageCountMap())); + return; + } + leveldb::ReadOptions read_options; + leveldb::WriteOptions write_options; + write_options.sync = true; + + AppIdToMessageCountMap removed_message_counts; + + leveldb::Status s; + for (PersistentIdList::const_iterator iter = persistent_ids.begin(); + iter != persistent_ids.end(); + ++iter) { + DVLOG(1) << "Removing outgoing message with id " << *iter; + std::string outgoing_message; + std::string key = MakeOutgoingKey(*iter); + s = db_->Get(read_options, + MakeSlice(key), + &outgoing_message); + if (!s.ok()) + break; + mcs_proto::DataMessageStanza data_message; + // Skip the initial tag byte and parse the rest to extract the message. + if (data_message.ParseFromString(outgoing_message.substr(1))) { + DCHECK(!data_message.category().empty()); + if (removed_message_counts.count(data_message.category()) != 0) + removed_message_counts[data_message.category()]++; + else + removed_message_counts[data_message.category()] = 1; + } + DVLOG(1) << "Removing outgoing message with id " << *iter; + s = db_->Delete(write_options, MakeSlice(key)); + if (!s.ok()) + break; + } + if (s.ok()) { + foreground_task_runner_->PostTask(FROM_HERE, + base::Bind(callback, + true, + removed_message_counts)); + return; + } + LOG(ERROR) << "LevelDB remove failed: " << s.ToString(); + foreground_task_runner_->PostTask(FROM_HERE, + base::Bind(callback, + false, + AppIdToMessageCountMap())); +} + +void GCMStoreImpl::Backend::SetLastCheckinTime( + const base::Time& last_checkin_time, + const UpdateCallback& callback) { + leveldb::WriteOptions write_options; + write_options.sync = true; + + int64 last_checkin_time_internal = last_checkin_time.ToInternalValue(); + const leveldb::Status s = + db_->Put(write_options, + MakeSlice(kLastCheckinTimeKey), + MakeSlice(base::Int64ToString(last_checkin_time_internal))); + + if (!s.ok()) + LOG(ERROR) << "LevelDB set last checkin time failed: " << s.ToString(); + foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, s.ok())); +} + +void GCMStoreImpl::Backend::SetGServicesSettings( + const std::map<std::string, std::string>& settings, + const std::string& settings_digest, + const UpdateCallback& callback) { + leveldb::WriteBatch write_batch; + + // Remove all existing settings. + leveldb::ReadOptions read_options; + read_options.verify_checksums = true; + scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(read_options)); + for (iter->Seek(MakeSlice(kGServiceSettingKeyStart)); + iter->Valid() && iter->key().ToString() < kGServiceSettingKeyEnd; + iter->Next()) { + write_batch.Delete(iter->key()); + } + + // Add the new settings. + for (std::map<std::string, std::string>::const_iterator iter = + settings.begin(); + iter != settings.end(); ++iter) { + write_batch.Put(MakeSlice(MakeGServiceSettingKey(iter->first)), + MakeSlice(iter->second)); + } + + // Update the settings digest. + write_batch.Put(MakeSlice(kGServiceSettingsDigestKey), + MakeSlice(settings_digest)); + + // Write it all in a batch. + leveldb::WriteOptions write_options; + write_options.sync = true; + + leveldb::Status s = db_->Write(write_options, &write_batch); + if (!s.ok()) + LOG(ERROR) << "LevelDB GService Settings update failed: " << s.ToString(); + foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, s.ok())); +} + +bool GCMStoreImpl::Backend::LoadDeviceCredentials(uint64* android_id, + uint64* security_token) { + leveldb::ReadOptions read_options; + read_options.verify_checksums = true; + + std::string result; + leveldb::Status s = db_->Get(read_options, MakeSlice(kDeviceAIDKey), &result); + if (s.ok()) { + if (!base::StringToUint64(result, android_id)) { + LOG(ERROR) << "Failed to restore device id."; + return false; + } + result.clear(); + s = db_->Get(read_options, MakeSlice(kDeviceTokenKey), &result); + } + if (s.ok()) { + std::string decrypted_token; + encryptor_->DecryptString(result, &decrypted_token); + if (!base::StringToUint64(decrypted_token, security_token)) { + LOG(ERROR) << "Failed to restore security token."; + return false; + } + return true; + } + + if (s.IsNotFound()) { + DVLOG(1) << "No credentials found."; + return true; + } + + LOG(ERROR) << "Error reading credentials from store."; + return false; +} + +bool GCMStoreImpl::Backend::LoadRegistrations( + RegistrationInfoMap* registrations) { + leveldb::ReadOptions read_options; + read_options.verify_checksums = true; + + scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(read_options)); + for (iter->Seek(MakeSlice(kRegistrationKeyStart)); + iter->Valid() && iter->key().ToString() < kRegistrationKeyEnd; + iter->Next()) { + leveldb::Slice s = iter->value(); + if (s.size() <= 1) { + LOG(ERROR) << "Error reading registration with key " << s.ToString(); + return false; + } + std::string app_id = ParseRegistrationKey(iter->key().ToString()); + linked_ptr<RegistrationInfo> registration(new RegistrationInfo); + if (!registration->ParseFromString(iter->value().ToString())) { + LOG(ERROR) << "Failed to parse registration with app id " << app_id; + return false; + } + DVLOG(1) << "Found registration with app id " << app_id; + (*registrations)[app_id] = registration; + } + + return true; +} + +bool GCMStoreImpl::Backend::LoadIncomingMessages( + std::vector<std::string>* incoming_messages) { + leveldb::ReadOptions read_options; + read_options.verify_checksums = true; + + scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(read_options)); + for (iter->Seek(MakeSlice(kIncomingMsgKeyStart)); + iter->Valid() && iter->key().ToString() < kIncomingMsgKeyEnd; + iter->Next()) { + leveldb::Slice s = iter->value(); + if (s.empty()) { + LOG(ERROR) << "Error reading incoming message with key " + << iter->key().ToString(); + return false; + } + DVLOG(1) << "Found incoming message with id " << s.ToString(); + incoming_messages->push_back(s.ToString()); + } + + return true; +} + +bool GCMStoreImpl::Backend::LoadOutgoingMessages( + OutgoingMessageMap* outgoing_messages) { + leveldb::ReadOptions read_options; + read_options.verify_checksums = true; + + scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(read_options)); + for (iter->Seek(MakeSlice(kOutgoingMsgKeyStart)); + iter->Valid() && iter->key().ToString() < kOutgoingMsgKeyEnd; + iter->Next()) { + leveldb::Slice s = iter->value(); + if (s.size() <= 1) { + LOG(ERROR) << "Error reading incoming message with key " << s.ToString(); + return false; + } + uint8 tag = iter->value().data()[0]; + std::string id = ParseOutgoingKey(iter->key().ToString()); + scoped_ptr<google::protobuf::MessageLite> message( + BuildProtobufFromTag(tag)); + if (!message.get() || + !message->ParseFromString(iter->value().ToString().substr(1))) { + LOG(ERROR) << "Failed to parse outgoing message with id " << id + << " and tag " << tag; + return false; + } + DVLOG(1) << "Found outgoing message with id " << id << " of type " + << base::IntToString(tag); + (*outgoing_messages)[id] = make_linked_ptr(message.release()); + } + + return true; +} + +bool GCMStoreImpl::Backend::LoadLastCheckinTime( + base::Time* last_checkin_time) { + leveldb::ReadOptions read_options; + read_options.verify_checksums = true; + + std::string result; + leveldb::Status s = db_->Get(read_options, + MakeSlice(kLastCheckinTimeKey), + &result); + int64 time_internal = 0LL; + if (s.ok() && !base::StringToInt64(result, &time_internal)) + LOG(ERROR) << "Failed to restore last checkin time. Using default = 0."; + + // In case we cannot read last checkin time, we default it to 0, as we don't + // want that situation to cause the whole load to fail. + *last_checkin_time = base::Time::FromInternalValue(time_internal); + + return true; +} + +bool GCMStoreImpl::Backend::LoadGServicesSettings( + std::map<std::string, std::string>* settings, + std::string* digest) { + leveldb::ReadOptions read_options; + read_options.verify_checksums = true; + + // Load all of the GServices settings. + scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(read_options)); + for (iter->Seek(MakeSlice(kGServiceSettingKeyStart)); + iter->Valid() && iter->key().ToString() < kGServiceSettingKeyEnd; + iter->Next()) { + std::string value = iter->value().ToString(); + if (value.empty()) { + LOG(ERROR) << "Error reading GService Settings " << value; + return false; + } + std::string id = ParseGServiceSettingKey(iter->key().ToString()); + (*settings)[id] = value; + DVLOG(1) << "Found G Service setting with key: " << id + << ", and value: " << value; + } + + // Load the settings digest. It's ok if it is empty. + db_->Get(read_options, MakeSlice(kGServiceSettingsDigestKey), digest); + + return true; +} + +GCMStoreImpl::GCMStoreImpl( + const base::FilePath& path, + scoped_refptr<base::SequencedTaskRunner> blocking_task_runner, + scoped_ptr<Encryptor> encryptor) + : backend_(new Backend(path, + base::MessageLoopProxy::current(), + encryptor.Pass())), + blocking_task_runner_(blocking_task_runner), + weak_ptr_factory_(this) { +} + +GCMStoreImpl::~GCMStoreImpl() {} + +void GCMStoreImpl::Load(const LoadCallback& callback) { + blocking_task_runner_->PostTask( + FROM_HERE, + base::Bind(&GCMStoreImpl::Backend::Load, + backend_, + base::Bind(&GCMStoreImpl::LoadContinuation, + weak_ptr_factory_.GetWeakPtr(), + callback))); +} + +void GCMStoreImpl::Close() { + weak_ptr_factory_.InvalidateWeakPtrs(); + app_message_counts_.clear(); + blocking_task_runner_->PostTask( + FROM_HERE, + base::Bind(&GCMStoreImpl::Backend::Close, backend_)); +} + +void GCMStoreImpl::Destroy(const UpdateCallback& callback) { + blocking_task_runner_->PostTask( + FROM_HERE, + base::Bind(&GCMStoreImpl::Backend::Destroy, backend_, callback)); +} + +void GCMStoreImpl::SetDeviceCredentials(uint64 device_android_id, + uint64 device_security_token, + const UpdateCallback& callback) { + blocking_task_runner_->PostTask( + FROM_HERE, + base::Bind(&GCMStoreImpl::Backend::SetDeviceCredentials, + backend_, + device_android_id, + device_security_token, + callback)); +} + +void GCMStoreImpl::AddRegistration( + const std::string& app_id, + const linked_ptr<RegistrationInfo>& registration, + const UpdateCallback& callback) { + blocking_task_runner_->PostTask( + FROM_HERE, + base::Bind(&GCMStoreImpl::Backend::AddRegistration, + backend_, + app_id, + registration, + callback)); +} + +void GCMStoreImpl::RemoveRegistration(const std::string& app_id, + const UpdateCallback& callback) { + blocking_task_runner_->PostTask( + FROM_HERE, + base::Bind(&GCMStoreImpl::Backend::RemoveRegistration, + backend_, + app_id, + callback)); +} + +void GCMStoreImpl::AddIncomingMessage(const std::string& persistent_id, + const UpdateCallback& callback) { + blocking_task_runner_->PostTask( + FROM_HERE, + base::Bind(&GCMStoreImpl::Backend::AddIncomingMessage, + backend_, + persistent_id, + callback)); +} + +void GCMStoreImpl::RemoveIncomingMessage(const std::string& persistent_id, + const UpdateCallback& callback) { + blocking_task_runner_->PostTask( + FROM_HERE, + base::Bind(&GCMStoreImpl::Backend::RemoveIncomingMessages, + backend_, + PersistentIdList(1, persistent_id), + callback)); +} + +void GCMStoreImpl::RemoveIncomingMessages( + const PersistentIdList& persistent_ids, + const UpdateCallback& callback) { + blocking_task_runner_->PostTask( + FROM_HERE, + base::Bind(&GCMStoreImpl::Backend::RemoveIncomingMessages, + backend_, + persistent_ids, + callback)); +} + +bool GCMStoreImpl::AddOutgoingMessage(const std::string& persistent_id, + const MCSMessage& message, + const UpdateCallback& callback) { + DCHECK_EQ(message.tag(), kDataMessageStanzaTag); + std::string app_id = reinterpret_cast<const mcs_proto::DataMessageStanza*>( + &message.GetProtobuf())->category(); + DCHECK(!app_id.empty()); + if (app_message_counts_.count(app_id) == 0) + app_message_counts_[app_id] = 0; + if (app_message_counts_[app_id] < kMessagesPerAppLimit) { + app_message_counts_[app_id]++; + + blocking_task_runner_->PostTask( + FROM_HERE, + base::Bind(&GCMStoreImpl::Backend::AddOutgoingMessage, + backend_, + persistent_id, + message, + base::Bind(&GCMStoreImpl::AddOutgoingMessageContinuation, + weak_ptr_factory_.GetWeakPtr(), + callback, + app_id))); + return true; + } + return false; +} + +void GCMStoreImpl::OverwriteOutgoingMessage(const std::string& persistent_id, + const MCSMessage& message, + const UpdateCallback& callback) { + DCHECK_EQ(message.tag(), kDataMessageStanzaTag); + std::string app_id = reinterpret_cast<const mcs_proto::DataMessageStanza*>( + &message.GetProtobuf())->category(); + DCHECK(!app_id.empty()); + // There should already be pending messages for this app. + DCHECK(app_message_counts_.count(app_id)); + // TODO(zea): consider verifying the specific message already exists. + blocking_task_runner_->PostTask( + FROM_HERE, + base::Bind(&GCMStoreImpl::Backend::AddOutgoingMessage, + backend_, + persistent_id, + message, + callback)); +} + +void GCMStoreImpl::RemoveOutgoingMessage(const std::string& persistent_id, + const UpdateCallback& callback) { + blocking_task_runner_->PostTask( + FROM_HERE, + base::Bind(&GCMStoreImpl::Backend::RemoveOutgoingMessages, + backend_, + PersistentIdList(1, persistent_id), + base::Bind(&GCMStoreImpl::RemoveOutgoingMessagesContinuation, + weak_ptr_factory_.GetWeakPtr(), + callback))); +} + +void GCMStoreImpl::RemoveOutgoingMessages( + const PersistentIdList& persistent_ids, + const UpdateCallback& callback) { + blocking_task_runner_->PostTask( + FROM_HERE, + base::Bind(&GCMStoreImpl::Backend::RemoveOutgoingMessages, + backend_, + persistent_ids, + base::Bind(&GCMStoreImpl::RemoveOutgoingMessagesContinuation, + weak_ptr_factory_.GetWeakPtr(), + callback))); +} + +void GCMStoreImpl::SetLastCheckinTime(const base::Time& last_checkin_time, + const UpdateCallback& callback) { + blocking_task_runner_->PostTask( + FROM_HERE, + base::Bind(&GCMStoreImpl::Backend::SetLastCheckinTime, + backend_, + last_checkin_time, + callback)); +} + +void GCMStoreImpl::SetGServicesSettings( + const std::map<std::string, std::string>& settings, + const std::string& digest, + const UpdateCallback& callback) { + blocking_task_runner_->PostTask( + FROM_HERE, + base::Bind(&GCMStoreImpl::Backend::SetGServicesSettings, + backend_, + settings, + digest, + callback)); +} + +void GCMStoreImpl::LoadContinuation(const LoadCallback& callback, + scoped_ptr<LoadResult> result) { + if (!result->success) { + callback.Run(result.Pass()); + return; + } + int num_throttled_apps = 0; + for (OutgoingMessageMap::const_iterator + iter = result->outgoing_messages.begin(); + iter != result->outgoing_messages.end(); ++iter) { + const mcs_proto::DataMessageStanza* data_message = + reinterpret_cast<mcs_proto::DataMessageStanza*>(iter->second.get()); + DCHECK(!data_message->category().empty()); + if (app_message_counts_.count(data_message->category()) == 0) + app_message_counts_[data_message->category()] = 1; + else + app_message_counts_[data_message->category()]++; + if (app_message_counts_[data_message->category()] == kMessagesPerAppLimit) + num_throttled_apps++; + } + UMA_HISTOGRAM_COUNTS("GCM.NumThrottledApps", num_throttled_apps); + callback.Run(result.Pass()); +} + +void GCMStoreImpl::AddOutgoingMessageContinuation( + const UpdateCallback& callback, + const std::string& app_id, + bool success) { + if (!success) { + DCHECK(app_message_counts_[app_id] > 0); + app_message_counts_[app_id]--; + } + callback.Run(success); +} + +void GCMStoreImpl::RemoveOutgoingMessagesContinuation( + const UpdateCallback& callback, + bool success, + const AppIdToMessageCountMap& removed_message_counts) { + if (!success) { + callback.Run(false); + return; + } + for (AppIdToMessageCountMap::const_iterator iter = + removed_message_counts.begin(); + iter != removed_message_counts.end(); ++iter) { + DCHECK_NE(app_message_counts_.count(iter->first), 0U); + app_message_counts_[iter->first] -= iter->second; + DCHECK_GE(app_message_counts_[iter->first], 0); + } + callback.Run(true); +} + +} // namespace gcm |