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