1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "sync/notifier/p2p_invalidator.h" 6 7 #include <algorithm> 8 #include <iterator> 9 10 #include "base/json/json_reader.h" 11 #include "base/json/json_writer.h" 12 #include "base/logging.h" 13 #include "base/values.h" 14 #include "jingle/notifier/listener/push_client.h" 15 #include "sync/notifier/invalidation_handler.h" 16 #include "sync/notifier/invalidation_util.h" 17 #include "sync/notifier/object_id_invalidation_map.h" 18 19 namespace syncer { 20 21 const char kSyncP2PNotificationChannel[] = "http://www.google.com/chrome/sync"; 22 23 namespace { 24 25 const char kNotifySelf[] = "notifySelf"; 26 const char kNotifyOthers[] = "notifyOthers"; 27 const char kNotifyAll[] = "notifyAll"; 28 29 const char kSenderIdKey[] = "senderId"; 30 const char kNotificationTypeKey[] = "notificationType"; 31 const char kInvalidationsKey[] = "invalidations"; 32 33 } // namespace 34 35 std::string P2PNotificationTargetToString(P2PNotificationTarget target) { 36 switch (target) { 37 case NOTIFY_SELF: 38 return kNotifySelf; 39 case NOTIFY_OTHERS: 40 return kNotifyOthers; 41 case NOTIFY_ALL: 42 return kNotifyAll; 43 default: 44 NOTREACHED(); 45 return std::string(); 46 } 47 } 48 49 P2PNotificationTarget P2PNotificationTargetFromString( 50 const std::string& target_str) { 51 if (target_str == kNotifySelf) { 52 return NOTIFY_SELF; 53 } 54 if (target_str == kNotifyOthers) { 55 return NOTIFY_OTHERS; 56 } 57 if (target_str == kNotifyAll) { 58 return NOTIFY_ALL; 59 } 60 LOG(WARNING) << "Could not parse " << target_str; 61 return NOTIFY_SELF; 62 } 63 64 P2PNotificationData::P2PNotificationData() 65 : target_(NOTIFY_SELF) {} 66 67 P2PNotificationData::P2PNotificationData( 68 const std::string& sender_id, 69 P2PNotificationTarget target, 70 const ObjectIdInvalidationMap& invalidation_map) 71 : sender_id_(sender_id), 72 target_(target), 73 invalidation_map_(invalidation_map) {} 74 75 P2PNotificationData::~P2PNotificationData() {} 76 77 bool P2PNotificationData::IsTargeted(const std::string& id) const { 78 switch (target_) { 79 case NOTIFY_SELF: 80 return sender_id_ == id; 81 case NOTIFY_OTHERS: 82 return sender_id_ != id; 83 case NOTIFY_ALL: 84 return true; 85 default: 86 NOTREACHED(); 87 return false; 88 } 89 } 90 91 const ObjectIdInvalidationMap& 92 P2PNotificationData::GetIdInvalidationMap() const { 93 return invalidation_map_; 94 } 95 96 bool P2PNotificationData::Equals(const P2PNotificationData& other) const { 97 return 98 (sender_id_ == other.sender_id_) && 99 (target_ == other.target_) && 100 (invalidation_map_ == other.invalidation_map_); 101 } 102 103 std::string P2PNotificationData::ToString() const { 104 scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); 105 dict->SetString(kSenderIdKey, sender_id_); 106 dict->SetString(kNotificationTypeKey, 107 P2PNotificationTargetToString(target_)); 108 dict->Set(kInvalidationsKey, invalidation_map_.ToValue().release()); 109 std::string json; 110 base::JSONWriter::Write(dict.get(), &json); 111 return json; 112 } 113 114 bool P2PNotificationData::ResetFromString(const std::string& str) { 115 scoped_ptr<base::Value> data_value(base::JSONReader::Read(str)); 116 const base::DictionaryValue* data_dict = NULL; 117 if (!data_value.get() || !data_value->GetAsDictionary(&data_dict)) { 118 LOG(WARNING) << "Could not parse " << str << " as a dictionary"; 119 return false; 120 } 121 if (!data_dict->GetString(kSenderIdKey, &sender_id_)) { 122 LOG(WARNING) << "Could not find string value for " << kSenderIdKey; 123 } 124 std::string target_str; 125 if (!data_dict->GetString(kNotificationTypeKey, &target_str)) { 126 LOG(WARNING) << "Could not find string value for " 127 << kNotificationTypeKey; 128 } 129 target_ = P2PNotificationTargetFromString(target_str); 130 const base::ListValue* invalidation_map_list = NULL; 131 if (!data_dict->GetList(kInvalidationsKey, &invalidation_map_list) || 132 !invalidation_map_.ResetFromValue(*invalidation_map_list)) { 133 LOG(WARNING) << "Could not parse " << kInvalidationsKey; 134 } 135 return true; 136 } 137 138 P2PInvalidator::P2PInvalidator(scoped_ptr<notifier::PushClient> push_client, 139 const std::string& invalidator_client_id, 140 P2PNotificationTarget send_notification_target) 141 : push_client_(push_client.Pass()), 142 invalidator_client_id_(invalidator_client_id), 143 logged_in_(false), 144 notifications_enabled_(false), 145 send_notification_target_(send_notification_target) { 146 DCHECK(send_notification_target_ == NOTIFY_OTHERS || 147 send_notification_target_ == NOTIFY_ALL); 148 push_client_->AddObserver(this); 149 } 150 151 P2PInvalidator::~P2PInvalidator() { 152 DCHECK(thread_checker_.CalledOnValidThread()); 153 push_client_->RemoveObserver(this); 154 } 155 156 void P2PInvalidator::RegisterHandler(InvalidationHandler* handler) { 157 DCHECK(thread_checker_.CalledOnValidThread()); 158 registrar_.RegisterHandler(handler); 159 } 160 161 void P2PInvalidator::UpdateRegisteredIds(InvalidationHandler* handler, 162 const ObjectIdSet& ids) { 163 DCHECK(thread_checker_.CalledOnValidThread()); 164 ObjectIdSet new_ids; 165 const ObjectIdSet& old_ids = registrar_.GetRegisteredIds(handler); 166 std::set_difference(ids.begin(), ids.end(), 167 old_ids.begin(), old_ids.end(), 168 std::inserter(new_ids, new_ids.end()), 169 ObjectIdLessThan()); 170 registrar_.UpdateRegisteredIds(handler, ids); 171 const P2PNotificationData notification_data( 172 invalidator_client_id_, 173 send_notification_target_, 174 ObjectIdInvalidationMap::InvalidateAll(ids)); 175 SendNotificationData(notification_data); 176 } 177 178 void P2PInvalidator::UnregisterHandler(InvalidationHandler* handler) { 179 DCHECK(thread_checker_.CalledOnValidThread()); 180 registrar_.UnregisterHandler(handler); 181 } 182 183 InvalidatorState P2PInvalidator::GetInvalidatorState() const { 184 DCHECK(thread_checker_.CalledOnValidThread()); 185 return registrar_.GetInvalidatorState(); 186 } 187 188 void P2PInvalidator::UpdateCredentials( 189 const std::string& email, const std::string& token) { 190 DCHECK(thread_checker_.CalledOnValidThread()); 191 notifier::Subscription subscription; 192 subscription.channel = kSyncP2PNotificationChannel; 193 // There may be some subtle issues around case sensitivity of the 194 // from field, but it doesn't matter too much since this is only 195 // used in p2p mode (which is only used in testing). 196 subscription.from = email; 197 push_client_->UpdateSubscriptions( 198 notifier::SubscriptionList(1, subscription)); 199 // If already logged in, the new credentials will take effect on the 200 // next reconnection. 201 push_client_->UpdateCredentials(email, token); 202 logged_in_ = true; 203 } 204 205 void P2PInvalidator::SendInvalidation(const ObjectIdSet& ids) { 206 DCHECK(thread_checker_.CalledOnValidThread()); 207 ObjectIdInvalidationMap invalidation_map = 208 ObjectIdInvalidationMap::InvalidateAll(ids); 209 const P2PNotificationData notification_data( 210 invalidator_client_id_, send_notification_target_, invalidation_map); 211 SendNotificationData(notification_data); 212 } 213 214 void P2PInvalidator::OnNotificationsEnabled() { 215 DCHECK(thread_checker_.CalledOnValidThread()); 216 bool just_turned_on = (notifications_enabled_ == false); 217 notifications_enabled_ = true; 218 registrar_.UpdateInvalidatorState(INVALIDATIONS_ENABLED); 219 if (just_turned_on) { 220 const P2PNotificationData notification_data( 221 invalidator_client_id_, 222 NOTIFY_SELF, 223 ObjectIdInvalidationMap::InvalidateAll( 224 registrar_.GetAllRegisteredIds())); 225 SendNotificationData(notification_data); 226 } 227 } 228 229 void P2PInvalidator::OnNotificationsDisabled( 230 notifier::NotificationsDisabledReason reason) { 231 DCHECK(thread_checker_.CalledOnValidThread()); 232 registrar_.UpdateInvalidatorState(FromNotifierReason(reason)); 233 } 234 235 void P2PInvalidator::OnIncomingNotification( 236 const notifier::Notification& notification) { 237 DCHECK(thread_checker_.CalledOnValidThread()); 238 DVLOG(1) << "Received notification " << notification.ToString(); 239 if (!logged_in_) { 240 DVLOG(1) << "Not logged in yet -- not emitting notification"; 241 return; 242 } 243 if (!notifications_enabled_) { 244 DVLOG(1) << "Notifications not on -- not emitting notification"; 245 return; 246 } 247 if (notification.channel != kSyncP2PNotificationChannel) { 248 LOG(WARNING) << "Notification from unexpected source " 249 << notification.channel; 250 } 251 P2PNotificationData notification_data; 252 if (!notification_data.ResetFromString(notification.data)) { 253 LOG(WARNING) << "Could not parse notification data from " 254 << notification.data; 255 notification_data = P2PNotificationData( 256 invalidator_client_id_, 257 NOTIFY_ALL, 258 ObjectIdInvalidationMap::InvalidateAll( 259 registrar_.GetAllRegisteredIds())); 260 } 261 if (!notification_data.IsTargeted(invalidator_client_id_)) { 262 DVLOG(1) << "Not a target of the notification -- " 263 << "not emitting notification"; 264 return; 265 } 266 registrar_.DispatchInvalidationsToHandlers( 267 notification_data.GetIdInvalidationMap()); 268 } 269 270 void P2PInvalidator::SendNotificationDataForTest( 271 const P2PNotificationData& notification_data) { 272 DCHECK(thread_checker_.CalledOnValidThread()); 273 SendNotificationData(notification_data); 274 } 275 276 void P2PInvalidator::SendNotificationData( 277 const P2PNotificationData& notification_data) { 278 DCHECK(thread_checker_.CalledOnValidThread()); 279 if (notification_data.GetIdInvalidationMap().Empty()) { 280 DVLOG(1) << "Not sending XMPP notification with empty state map: " 281 << notification_data.ToString(); 282 return; 283 } 284 notifier::Notification notification; 285 notification.channel = kSyncP2PNotificationChannel; 286 notification.data = notification_data.ToString(); 287 DVLOG(1) << "Sending XMPP notification: " << notification.ToString(); 288 push_client_->SendNotification(notification); 289 } 290 291 } // namespace syncer 292