OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/sync/engine/syncapi.h" | 5 #include "chrome/browser/sync/engine/syncapi.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 #include <bitset> | 8 #include <bitset> |
9 #include <iomanip> | 9 #include <iomanip> |
10 #include <list> | 10 #include <list> |
(...skipping 184 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
195 } | 195 } |
196 } | 196 } |
197 | 197 |
198 UserShare::UserShare() {} | 198 UserShare::UserShare() {} |
199 | 199 |
200 UserShare::~UserShare() {} | 200 UserShare::~UserShare() {} |
201 | 201 |
202 //////////////////////////////////// | 202 //////////////////////////////////// |
203 // BaseNode member definitions. | 203 // BaseNode member definitions. |
204 | 204 |
205 BaseNode::BaseNode() {} | 205 BaseNode::BaseNode() : password_data_(new sync_pb::PasswordSpecificsData) {} |
206 | 206 |
207 BaseNode::~BaseNode() {} | 207 BaseNode::~BaseNode() {} |
208 | 208 |
209 std::string BaseNode::GenerateSyncableHash( | 209 std::string BaseNode::GenerateSyncableHash( |
210 syncable::ModelType model_type, const std::string& client_tag) { | 210 syncable::ModelType model_type, const std::string& client_tag) { |
211 // blank PB with just the extension in it has termination symbol, | 211 // blank PB with just the extension in it has termination symbol, |
212 // handy for delimiter | 212 // handy for delimiter |
213 sync_pb::EntitySpecifics serialized_type; | 213 sync_pb::EntitySpecifics serialized_type; |
214 syncable::AddDefaultExtensionValue(model_type, &serialized_type); | 214 syncable::AddDefaultExtensionValue(model_type, &serialized_type); |
215 std::string hash_input; | 215 std::string hash_input; |
(...skipping 22 matching lines...) Expand all Loading... |
238 } | 238 } |
239 | 239 |
240 bool BaseNode::DecryptIfNecessary() { | 240 bool BaseNode::DecryptIfNecessary() { |
241 if (GetIsFolder()) return true; // Ignore the top-level datatype folder. | 241 if (GetIsFolder()) return true; // Ignore the top-level datatype folder. |
242 const sync_pb::EntitySpecifics& specifics = | 242 const sync_pb::EntitySpecifics& specifics = |
243 GetEntry()->Get(syncable::SPECIFICS); | 243 GetEntry()->Get(syncable::SPECIFICS); |
244 if (specifics.HasExtension(sync_pb::password)) { | 244 if (specifics.HasExtension(sync_pb::password)) { |
245 // Passwords have their own legacy encryption structure. | 245 // Passwords have their own legacy encryption structure. |
246 scoped_ptr<sync_pb::PasswordSpecificsData> data(DecryptPasswordSpecifics( | 246 scoped_ptr<sync_pb::PasswordSpecificsData> data(DecryptPasswordSpecifics( |
247 specifics, GetTransaction()->GetCryptographer())); | 247 specifics, GetTransaction()->GetCryptographer())); |
248 if (!data.get()) | 248 if (!data.get()) { |
| 249 LOG(ERROR) << "Failed to decrypt password specifics."; |
249 return false; | 250 return false; |
| 251 } |
250 password_data_.swap(data); | 252 password_data_.swap(data); |
251 return true; | 253 return true; |
252 } | 254 } |
253 | 255 |
254 // We assume any node with the encrypted field set has encrypted data. | 256 // We assume any node with the encrypted field set has encrypted data. |
255 if (!specifics.has_encrypted()) | 257 if (!specifics.has_encrypted()) |
256 return true; | 258 return true; |
257 | 259 |
258 const sync_pb::EncryptedData& encrypted = | 260 const sync_pb::EncryptedData& encrypted = |
259 specifics.encrypted(); | 261 specifics.encrypted(); |
260 std::string plaintext_data = GetTransaction()->GetCryptographer()-> | 262 std::string plaintext_data = GetTransaction()->GetCryptographer()-> |
261 DecryptToString(encrypted); | 263 DecryptToString(encrypted); |
262 if (plaintext_data.length() == 0) | 264 if (plaintext_data.length() == 0 || |
263 return false; | 265 !unencrypted_data_.ParseFromString(plaintext_data)) { |
264 if (!unencrypted_data_.ParseFromString(plaintext_data)) { | |
265 LOG(ERROR) << "Failed to decrypt encrypted node of type " << | 266 LOG(ERROR) << "Failed to decrypt encrypted node of type " << |
266 syncable::ModelTypeToString(GetModelType()) << "."; | 267 syncable::ModelTypeToString(GetModelType()) << "."; |
267 return false; | 268 return false; |
268 } | 269 } |
269 return true; | 270 return true; |
270 } | 271 } |
271 | 272 |
272 const sync_pb::EntitySpecifics& BaseNode::GetUnencryptedSpecifics( | 273 const sync_pb::EntitySpecifics& BaseNode::GetUnencryptedSpecifics( |
273 const syncable::Entry* entry) const { | 274 const syncable::Entry* entry) const { |
274 const sync_pb::EntitySpecifics& specifics = entry->Get(SPECIFICS); | 275 const sync_pb::EntitySpecifics& specifics = entry->Get(SPECIFICS); |
(...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
397 return GetEntitySpecifics().GetExtension(sync_pb::bookmark); | 398 return GetEntitySpecifics().GetExtension(sync_pb::bookmark); |
398 } | 399 } |
399 | 400 |
400 const sync_pb::NigoriSpecifics& BaseNode::GetNigoriSpecifics() const { | 401 const sync_pb::NigoriSpecifics& BaseNode::GetNigoriSpecifics() const { |
401 DCHECK_EQ(syncable::NIGORI, GetModelType()); | 402 DCHECK_EQ(syncable::NIGORI, GetModelType()); |
402 return GetEntitySpecifics().GetExtension(sync_pb::nigori); | 403 return GetEntitySpecifics().GetExtension(sync_pb::nigori); |
403 } | 404 } |
404 | 405 |
405 const sync_pb::PasswordSpecificsData& BaseNode::GetPasswordSpecifics() const { | 406 const sync_pb::PasswordSpecificsData& BaseNode::GetPasswordSpecifics() const { |
406 DCHECK_EQ(syncable::PASSWORDS, GetModelType()); | 407 DCHECK_EQ(syncable::PASSWORDS, GetModelType()); |
407 DCHECK(password_data_.get()); | |
408 return *password_data_; | 408 return *password_data_; |
409 } | 409 } |
410 | 410 |
411 const sync_pb::ThemeSpecifics& BaseNode::GetThemeSpecifics() const { | 411 const sync_pb::ThemeSpecifics& BaseNode::GetThemeSpecifics() const { |
412 DCHECK_EQ(syncable::THEMES, GetModelType()); | 412 DCHECK_EQ(syncable::THEMES, GetModelType()); |
413 return GetEntitySpecifics().GetExtension(sync_pb::theme); | 413 return GetEntitySpecifics().GetExtension(sync_pb::theme); |
414 } | 414 } |
415 | 415 |
416 const sync_pb::TypedUrlSpecifics& BaseNode::GetTypedUrlSpecifics() const { | 416 const sync_pb::TypedUrlSpecifics& BaseNode::GetTypedUrlSpecifics() const { |
417 DCHECK_EQ(syncable::TYPED_URLS, GetModelType()); | 417 DCHECK_EQ(syncable::TYPED_URLS, GetModelType()); |
(...skipping 146 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
564 scoped_ptr<sync_pb::PasswordSpecificsData> old_plaintext( | 564 scoped_ptr<sync_pb::PasswordSpecificsData> old_plaintext( |
565 DecryptPasswordSpecifics(GetEntry()->Get(SPECIFICS), cryptographer)); | 565 DecryptPasswordSpecifics(GetEntry()->Get(SPECIFICS), cryptographer)); |
566 if (old_plaintext.get() && | 566 if (old_plaintext.get() && |
567 old_plaintext->SerializeAsString() == data.SerializeAsString() && | 567 old_plaintext->SerializeAsString() == data.SerializeAsString() && |
568 cryptographer->CanDecryptUsingDefaultKey(old_ciphertext)) { | 568 cryptographer->CanDecryptUsingDefaultKey(old_ciphertext)) { |
569 return; | 569 return; |
570 } | 570 } |
571 | 571 |
572 sync_pb::PasswordSpecifics new_value; | 572 sync_pb::PasswordSpecifics new_value; |
573 if (!cryptographer->Encrypt(data, new_value.mutable_encrypted())) { | 573 if (!cryptographer->Encrypt(data, new_value.mutable_encrypted())) { |
574 NOTREACHED(); | 574 NOTREACHED() << "Failed to encrypt password, possibly due to sync node " |
| 575 << "corruption"; |
| 576 return; |
575 } | 577 } |
576 | 578 |
577 sync_pb::EntitySpecifics entity_specifics; | 579 sync_pb::EntitySpecifics entity_specifics; |
578 entity_specifics.MutableExtension(sync_pb::password)->CopyFrom(new_value); | 580 entity_specifics.MutableExtension(sync_pb::password)->CopyFrom(new_value); |
579 SetEntitySpecifics(entity_specifics); | 581 SetEntitySpecifics(entity_specifics); |
580 } | 582 } |
581 | 583 |
582 void WriteNode::SetThemeSpecifics( | 584 void WriteNode::SetThemeSpecifics( |
583 const sync_pb::ThemeSpecifics& new_value) { | 585 const sync_pb::ThemeSpecifics& new_value) { |
584 sync_pb::EntitySpecifics entity_specifics; | 586 sync_pb::EntitySpecifics entity_specifics; |
(...skipping 611 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1196 | 1198 |
1197 // Called when the user disables or enables a sync type. | 1199 // Called when the user disables or enables a sync type. |
1198 void UpdateEnabledTypes(); | 1200 void UpdateEnabledTypes(); |
1199 | 1201 |
1200 // Tell the sync engine to start the syncing process. | 1202 // Tell the sync engine to start the syncing process. |
1201 void StartSyncingNormally(); | 1203 void StartSyncingNormally(); |
1202 | 1204 |
1203 // Whether or not the Nigori node is encrypted using an explicit passphrase. | 1205 // Whether or not the Nigori node is encrypted using an explicit passphrase. |
1204 bool IsUsingExplicitPassphrase(); | 1206 bool IsUsingExplicitPassphrase(); |
1205 | 1207 |
| 1208 // Update the Cryptographer from the current nigori node. |
| 1209 // Note: opens a transaction and can trigger an ON_PASSPHRASE_REQUIRED, so |
| 1210 // should only be called after syncapi is fully initialized. |
| 1211 // Returns true if cryptographer is ready, false otherwise. |
| 1212 bool UpdateCryptographerFromNigori(); |
| 1213 |
1206 // Set the datatypes we want to encrypt and encrypt any nodes as necessary. | 1214 // Set the datatypes we want to encrypt and encrypt any nodes as necessary. |
1207 void EncryptDataTypes(const syncable::ModelTypeSet& encrypted_types); | 1215 void EncryptDataTypes(const syncable::ModelTypeSet& encrypted_types); |
1208 | 1216 |
1209 // Try to set the current passphrase to |passphrase|, and record whether | 1217 // Try to set the current passphrase to |passphrase|, and record whether |
1210 // it is an explicit passphrase or implicitly using gaia in the Nigori | 1218 // it is an explicit passphrase or implicitly using gaia in the Nigori |
1211 // node. | 1219 // node. |
1212 void SetPassphrase(const std::string& passphrase, bool is_explicit); | 1220 void SetPassphrase(const std::string& passphrase, bool is_explicit); |
1213 | 1221 |
1214 // Call periodically from a database-safe thread to persist recent changes | 1222 // Call periodically from a database-safe thread to persist recent changes |
1215 // to the syncapi model. | 1223 // to the syncapi model. |
(...skipping 577 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1793 } | 1801 } |
1794 | 1802 |
1795 // Do this once the directory is opened. | 1803 // Do this once the directory is opened. |
1796 BootstrapEncryption(restored_key_for_bootstrapping); | 1804 BootstrapEncryption(restored_key_for_bootstrapping); |
1797 MarkAndNotifyInitializationComplete(); | 1805 MarkAndNotifyInitializationComplete(); |
1798 return signed_in; | 1806 return signed_in; |
1799 } | 1807 } |
1800 | 1808 |
1801 void SyncManager::SyncInternal::BootstrapEncryption( | 1809 void SyncManager::SyncInternal::BootstrapEncryption( |
1802 const std::string& restored_key_for_bootstrapping) { | 1810 const std::string& restored_key_for_bootstrapping) { |
| 1811 // Cryptographer should only be accessed while holding a transaction. |
| 1812 ReadTransaction trans(GetUserShare()); |
| 1813 Cryptographer* cryptographer = trans.GetCryptographer(); |
| 1814 |
| 1815 // Set the bootstrap token before bailing out if nigori node is not there. |
| 1816 // This could happen if server asked us to migrate nigri. |
| 1817 cryptographer->Bootstrap(restored_key_for_bootstrapping); |
| 1818 } |
| 1819 |
| 1820 bool SyncManager::SyncInternal::UpdateCryptographerFromNigori() { |
1803 syncable::ScopedDirLookup lookup(dir_manager(), username_for_share()); | 1821 syncable::ScopedDirLookup lookup(dir_manager(), username_for_share()); |
1804 if (!lookup.good()) { | 1822 if (!lookup.good()) { |
1805 LOG(INFO) << "BootstrapEncryption: lookup not good so bailing out"; | 1823 NOTREACHED() << "BootstrapEncryption: lookup not good so bailing out"; |
| 1824 return false; |
| 1825 } |
| 1826 if (!lookup->initial_sync_ended_for_type(syncable::NIGORI)) |
| 1827 return false; // Should only happen during first time sync. |
| 1828 |
| 1829 ReadTransaction trans(GetUserShare()); |
| 1830 Cryptographer* cryptographer = trans.GetCryptographer(); |
| 1831 |
| 1832 ReadNode node(&trans); |
| 1833 if (!node.InitByTagLookup(kNigoriTag)) { |
1806 NOTREACHED(); | 1834 NOTREACHED(); |
1807 return; | 1835 return false; |
| 1836 } |
| 1837 Cryptographer::UpdateResult result = |
| 1838 cryptographer->Update(node.GetNigoriSpecifics()); |
| 1839 if (result == Cryptographer::NEEDS_PASSPHRASE) { |
| 1840 ObserverList<SyncManager::Observer> temp_obs_list; |
| 1841 CopyObservers(&temp_obs_list); |
| 1842 FOR_EACH_OBSERVER(SyncManager::Observer, temp_obs_list, |
| 1843 OnPassphraseRequired(sync_api::REASON_DECRYPTION)); |
1808 } | 1844 } |
1809 | 1845 |
1810 sync_pb::NigoriSpecifics nigori; | 1846 return cryptographer->is_ready(); |
1811 syncable::ModelTypeSet encrypted_types; | |
1812 { | |
1813 // Cryptographer should only be accessed while holding a transaction. | |
1814 ReadTransaction trans(GetUserShare()); | |
1815 Cryptographer* cryptographer = trans.GetCryptographer(); | |
1816 | |
1817 // Set the bootstrap token before bailing out if nigori node is not there. | |
1818 // This could happen if server asked us to migrate nigri. | |
1819 cryptographer->Bootstrap(restored_key_for_bootstrapping); | |
1820 | |
1821 if (!lookup->initial_sync_ended_for_type(syncable::NIGORI)) | |
1822 return; | |
1823 | |
1824 ReadNode node(&trans); | |
1825 if (!node.InitByTagLookup(kNigoriTag)) { | |
1826 NOTREACHED(); | |
1827 return; | |
1828 } | |
1829 | |
1830 nigori.CopyFrom(node.GetNigoriSpecifics()); | |
1831 Cryptographer::UpdateResult result = cryptographer->Update(nigori); | |
1832 if (result == Cryptographer::NEEDS_PASSPHRASE) { | |
1833 ObserverList<SyncManager::Observer> temp_obs_list; | |
1834 CopyObservers(&temp_obs_list); | |
1835 FOR_EACH_OBSERVER(SyncManager::Observer, temp_obs_list, | |
1836 OnPassphraseRequired(sync_api::REASON_DECRYPTION)); | |
1837 } | |
1838 | |
1839 // Refresh list of encrypted datatypes. | |
1840 encrypted_types = GetEncryptedTypes(&trans); | |
1841 } | |
1842 | |
1843 | |
1844 | |
1845 // Ensure any datatypes that need encryption are encrypted. | |
1846 EncryptDataTypes(encrypted_types); | |
1847 } | 1847 } |
1848 | 1848 |
1849 void SyncManager::SyncInternal::StartSyncingNormally() { | 1849 void SyncManager::SyncInternal::StartSyncingNormally() { |
1850 // Start the sync scheduler. This won't actually result in any | 1850 // Start the sync scheduler. This won't actually result in any |
1851 // syncing until at least the DirectoryManager broadcasts the OPENED | 1851 // syncing until at least the DirectoryManager broadcasts the OPENED |
1852 // event, and a valid server connection is detected. | 1852 // event, and a valid server connection is detected. |
1853 if (scheduler()) // NULL during certain unittests. | 1853 if (scheduler()) // NULL during certain unittests. |
1854 scheduler()->Start(SyncScheduler::NORMAL_MODE, NULL); | 1854 scheduler()->Start(SyncScheduler::NORMAL_MODE, NULL); |
1855 } | 1855 } |
1856 | 1856 |
(...skipping 225 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2082 } | 2082 } |
2083 | 2083 |
2084 void SyncManager::SyncInternal::EncryptDataTypes( | 2084 void SyncManager::SyncInternal::EncryptDataTypes( |
2085 const syncable::ModelTypeSet& encrypted_types) { | 2085 const syncable::ModelTypeSet& encrypted_types) { |
2086 VLOG(1) << "Attempting to encrypt datatypes " | 2086 VLOG(1) << "Attempting to encrypt datatypes " |
2087 << syncable::ModelTypeSetToString(encrypted_types); | 2087 << syncable::ModelTypeSetToString(encrypted_types); |
2088 | 2088 |
2089 WriteTransaction trans(GetUserShare()); | 2089 WriteTransaction trans(GetUserShare()); |
2090 WriteNode node(&trans); | 2090 WriteNode node(&trans); |
2091 if (!node.InitByTagLookup(kNigoriTag)) { | 2091 if (!node.InitByTagLookup(kNigoriTag)) { |
2092 LOG(ERROR) << "Unable to set encrypted datatypes because Nigori node not " | 2092 NOTREACHED() << "Unable to set encrypted datatypes because Nigori node not " |
2093 << "found."; | 2093 << "found."; |
2094 NOTREACHED(); | |
2095 return; | 2094 return; |
2096 } | 2095 } |
2097 | 2096 |
2098 Cryptographer* cryptographer = trans.GetCryptographer(); | 2097 Cryptographer* cryptographer = trans.GetCryptographer(); |
2099 | 2098 |
| 2099 if (!cryptographer->is_initialized()) { |
| 2100 NOTREACHED() << "Attempting to encrypt datatypes when cryptographer not " |
| 2101 << "initialized."; |
| 2102 return; |
| 2103 } |
| 2104 |
2100 // Update the Nigori node set of encrypted datatypes so other machines notice. | 2105 // Update the Nigori node set of encrypted datatypes so other machines notice. |
2101 // Note, we merge the current encrypted types with those requested. Once a | 2106 // Note, we merge the current encrypted types with those requested. Once a |
2102 // datatypes is marked as needing encryption, it is never unmarked. | 2107 // datatypes is marked as needing encryption, it is never unmarked. |
2103 sync_pb::NigoriSpecifics nigori; | 2108 sync_pb::NigoriSpecifics nigori; |
2104 nigori.CopyFrom(node.GetNigoriSpecifics()); | 2109 nigori.CopyFrom(node.GetNigoriSpecifics()); |
2105 syncable::ModelTypeSet current_encrypted_types = GetEncryptedTypes(&trans); | 2110 syncable::ModelTypeSet current_encrypted_types = GetEncryptedTypes(&trans); |
2106 syncable::ModelTypeSet newly_encrypted_types; | 2111 syncable::ModelTypeSet newly_encrypted_types; |
2107 std::set_union(current_encrypted_types.begin(), current_encrypted_types.end(), | 2112 std::set_union(current_encrypted_types.begin(), current_encrypted_types.end(), |
2108 encrypted_types.begin(), encrypted_types.end(), | 2113 encrypted_types.begin(), encrypted_types.end(), |
2109 std::inserter(newly_encrypted_types, | 2114 std::inserter(newly_encrypted_types, |
(...skipping 885 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2995 } | 3000 } |
2996 BaseTransaction::~BaseTransaction() { | 3001 BaseTransaction::~BaseTransaction() { |
2997 delete lookup_; | 3002 delete lookup_; |
2998 } | 3003 } |
2999 | 3004 |
3000 UserShare* SyncManager::GetUserShare() const { | 3005 UserShare* SyncManager::GetUserShare() const { |
3001 DCHECK(data_->initialized()) << "GetUserShare requires initialization!"; | 3006 DCHECK(data_->initialized()) << "GetUserShare requires initialization!"; |
3002 return data_->GetUserShare(); | 3007 return data_->GetUserShare(); |
3003 } | 3008 } |
3004 | 3009 |
| 3010 void SyncManager::RefreshEncryption() { |
| 3011 DCHECK(data_->initialized()); |
| 3012 if (data_->UpdateCryptographerFromNigori()) |
| 3013 data_->EncryptDataTypes(syncable::ModelTypeSet()); |
| 3014 } |
| 3015 |
3005 syncable::ModelTypeSet SyncManager::GetEncryptedDataTypes() const { | 3016 syncable::ModelTypeSet SyncManager::GetEncryptedDataTypes() const { |
3006 sync_api::ReadTransaction trans(GetUserShare()); | 3017 sync_api::ReadTransaction trans(GetUserShare()); |
3007 return GetEncryptedTypes(&trans); | 3018 return GetEncryptedTypes(&trans); |
3008 } | 3019 } |
3009 | 3020 |
3010 | |
3011 bool SyncManager::HasUnsyncedItems() const { | 3021 bool SyncManager::HasUnsyncedItems() const { |
3012 sync_api::ReadTransaction trans(GetUserShare()); | 3022 sync_api::ReadTransaction trans(GetUserShare()); |
3013 return (trans.GetWrappedTrans()->directory()->unsynced_entity_count() != 0); | 3023 return (trans.GetWrappedTrans()->directory()->unsynced_entity_count() != 0); |
3014 } | 3024 } |
3015 | 3025 |
3016 void SyncManager::LogUnsyncedItems(int level) const { | 3026 void SyncManager::LogUnsyncedItems(int level) const { |
3017 std::vector<int64> unsynced_handles; | 3027 std::vector<int64> unsynced_handles; |
3018 sync_api::ReadTransaction trans(GetUserShare()); | 3028 sync_api::ReadTransaction trans(GetUserShare()); |
3019 trans.GetWrappedTrans()->directory()->GetUnsyncedMetaHandles( | 3029 trans.GetWrappedTrans()->directory()->GetUnsyncedMetaHandles( |
3020 trans.GetWrappedTrans(), &unsynced_handles); | 3030 trans.GetWrappedTrans(), &unsynced_handles); |
(...skipping 18 matching lines...) Expand all Loading... |
3039 void SyncManager::TriggerOnIncomingNotificationForTest( | 3049 void SyncManager::TriggerOnIncomingNotificationForTest( |
3040 const syncable::ModelTypeBitSet& model_types) { | 3050 const syncable::ModelTypeBitSet& model_types) { |
3041 syncable::ModelTypePayloadMap model_types_with_payloads = | 3051 syncable::ModelTypePayloadMap model_types_with_payloads = |
3042 syncable::ModelTypePayloadMapFromBitSet(model_types, | 3052 syncable::ModelTypePayloadMapFromBitSet(model_types, |
3043 std::string()); | 3053 std::string()); |
3044 | 3054 |
3045 data_->OnIncomingNotification(model_types_with_payloads); | 3055 data_->OnIncomingNotification(model_types_with_payloads); |
3046 } | 3056 } |
3047 | 3057 |
3048 } // namespace sync_api | 3058 } // namespace sync_api |
OLD | NEW |