1 // Copyright 2014 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 "components/invalidation/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 "components/invalidation/invalidation_handler.h" 15 #include "components/invalidation/invalidation_util.h" 16 #include "components/invalidation/notifier_reason_util.h" 17 #include "components/invalidation/object_id_invalidation_map.h" 18 #include "jingle/notifier/listener/push_client.h" 19 20 namespace syncer { 21 22 const char kSyncP2PNotificationChannel[] = "http://www.google.com/chrome/sync"; 23 24 namespace { 25 26 const char kNotifySelf[] = "notifySelf"; 27 const char kNotifyOthers[] = "notifyOthers"; 28 const char kNotifyAll[] = "notifyAll"; 29 30 const char kSenderIdKey[] = "senderId"; 31 const char kNotificationTypeKey[] = "notificationType"; 32 const char kInvalidationsKey[] = "invalidations"; 33 34 } // namespace 35 36 std::string P2PNotificationTargetToString(P2PNotificationTarget target) { 37 switch (target) { 38 case NOTIFY_SELF: 39 return kNotifySelf; 40 case NOTIFY_OTHERS: 41 return kNotifyOthers; 42 case NOTIFY_ALL: 43 return kNotifyAll; 44 default: 45 NOTREACHED(); 46 return std::string(); 47 } 48 } 49 50 P2PNotificationTarget P2PNotificationTargetFromString( 51 const std::string& target_str) { 52 if (target_str == kNotifySelf) { 53 return NOTIFY_SELF; 54 } 55 if (target_str == kNotifyOthers) { 56 return NOTIFY_OTHERS; 57 } 58 if (target_str == kNotifyAll) { 59 return NOTIFY_ALL; 60 } 61 LOG(WARNING) << "Could not parse " << target_str; 62 return NOTIFY_SELF; 63 } 64 65 P2PNotificationData::P2PNotificationData() 66 : target_(NOTIFY_SELF) {} 67 68 P2PNotificationData::P2PNotificationData( 69 const std::string& sender_id, 70 P2PNotificationTarget target, 71 const ObjectIdInvalidationMap& invalidation_map) 72 : sender_id_(sender_id), 73 target_(target), 74 invalidation_map_(invalidation_map) {} 75 76 P2PNotificationData::~P2PNotificationData() {} 77 78 bool P2PNotificationData::IsTargeted(const std::string& id) const { 79 switch (target_) { 80 case NOTIFY_SELF: 81 return sender_id_ == id; 82 case NOTIFY_OTHERS: 83 return sender_id_ != id; 84 case NOTIFY_ALL: 85 return true; 86 default: 87 NOTREACHED(); 88 return false; 89 } 90 } 91 92 const ObjectIdInvalidationMap& 93 P2PNotificationData::GetIdInvalidationMap() const { 94 return invalidation_map_; 95 } 96 97 bool P2PNotificationData::Equals(const P2PNotificationData& other) const { 98 return 99 (sender_id_ == other.sender_id_) && 100 (target_ == other.target_) && 101 (invalidation_map_ == other.invalidation_map_); 102 } 103 104 std::string P2PNotificationData::ToString() const { 105 scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); 106 dict->SetString(kSenderIdKey, sender_id_); 107 dict->SetString(kNotificationTypeKey, 108 P2PNotificationTargetToString(target_)); 109 dict->Set(kInvalidationsKey, invalidation_map_.ToValue().release()); 110 std::string json; 111 base::JSONWriter::Write(dict.get(), &json); 112 return json; 113 } 114 115 bool P2PNotificationData::ResetFromString(const std::string& str) { 116 scoped_ptr<base::Value> data_value(base::JSONReader::Read(str)); 117 const base::DictionaryValue* data_dict = NULL; 118 if (!data_value.get() || !data_value->GetAsDictionary(&data_dict)) { 119 LOG(WARNING) << "Could not parse " << str << " as a dictionary"; 120 return false; 121 } 122 if (!data_dict->GetString(kSenderIdKey, &sender_id_)) { 123 LOG(WARNING) << "Could not find string value for " << kSenderIdKey; 124 } 125 std::string target_str; 126 if (!data_dict->GetString(kNotificationTypeKey, &target_str)) { 127 LOG(WARNING) << "Could not find string value for " 128 << kNotificationTypeKey; 129 } 130 target_ = P2PNotificationTargetFromString(target_str); 131 const base::ListValue* invalidation_map_list = NULL; 132 if (!data_dict->GetList(kInvalidationsKey, &invalidation_map_list) || 133 !invalidation_map_.ResetFromValue(*invalidation_map_list)) { 134 LOG(WARNING) << "Could not parse " << kInvalidationsKey; 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 DCHECK(thread_checker_.CalledOnValidThread()); 165 ObjectIdSet new_ids; 166 const ObjectIdSet& old_ids = registrar_.GetRegisteredIds(handler); 167 std::set_difference(ids.begin(), ids.end(), 168 old_ids.begin(), old_ids.end(), 169 std::inserter(new_ids, new_ids.end()), 170 ObjectIdLessThan()); 171 registrar_.UpdateRegisteredIds(handler, ids); 172 const P2PNotificationData notification_data( 173 invalidator_client_id_, 174 send_notification_target_, 175 ObjectIdInvalidationMap::InvalidateAll(ids)); 176 SendNotificationData(notification_data); 177 } 178 179 void P2PInvalidator::UnregisterHandler(InvalidationHandler* handler) { 180 DCHECK(thread_checker_.CalledOnValidThread()); 181 registrar_.UnregisterHandler(handler); 182 } 183 184 InvalidatorState P2PInvalidator::GetInvalidatorState() const { 185 DCHECK(thread_checker_.CalledOnValidThread()); 186 return registrar_.GetInvalidatorState(); 187 } 188 189 void P2PInvalidator::UpdateCredentials( 190 const std::string& email, const std::string& token) { 191 DCHECK(thread_checker_.CalledOnValidThread()); 192 notifier::Subscription subscription; 193 subscription.channel = kSyncP2PNotificationChannel; 194 // There may be some subtle issues around case sensitivity of the 195 // from field, but it doesn't matter too much since this is only 196 // used in p2p mode (which is only used in testing). 197 subscription.from = email; 198 push_client_->UpdateSubscriptions( 199 notifier::SubscriptionList(1, subscription)); 200 // If already logged in, the new credentials will take effect on the 201 // next reconnection. 202 push_client_->UpdateCredentials(email, token); 203 logged_in_ = true; 204 } 205 206 void P2PInvalidator::RequestDetailedStatus( 207 base::Callback<void(const base::DictionaryValue&)> callback) const { 208 DCHECK(thread_checker_.CalledOnValidThread()); 209 // TODO(mferreria): Make the P2P Invalidator work. 210 scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue()); 211 callback.Run(*value); 212 } 213 214 void P2PInvalidator::SendInvalidation(const ObjectIdSet& ids) { 215 DCHECK(thread_checker_.CalledOnValidThread()); 216 ObjectIdInvalidationMap invalidation_map = 217 ObjectIdInvalidationMap::InvalidateAll(ids); 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 ObjectIdInvalidationMap::InvalidateAll( 233 registrar_.GetAllRegisteredIds())); 234 SendNotificationData(notification_data); 235 } 236 } 237 238 void P2PInvalidator::OnNotificationsDisabled( 239 notifier::NotificationsDisabledReason reason) { 240 DCHECK(thread_checker_.CalledOnValidThread()); 241 registrar_.UpdateInvalidatorState(FromNotifierReason(reason)); 242 } 243 244 void P2PInvalidator::OnIncomingNotification( 245 const notifier::Notification& notification) { 246 DCHECK(thread_checker_.CalledOnValidThread()); 247 DVLOG(1) << "Received notification " << notification.ToString(); 248 if (!logged_in_) { 249 DVLOG(1) << "Not logged in yet -- not emitting notification"; 250 return; 251 } 252 if (!notifications_enabled_) { 253 DVLOG(1) << "Notifications not on -- not emitting notification"; 254 return; 255 } 256 if (notification.channel != kSyncP2PNotificationChannel) { 257 LOG(WARNING) << "Notification from unexpected source " 258 << notification.channel; 259 } 260 P2PNotificationData notification_data; 261 if (!notification_data.ResetFromString(notification.data)) { 262 LOG(WARNING) << "Could not parse notification data from " 263 << notification.data; 264 notification_data = P2PNotificationData( 265 invalidator_client_id_, 266 NOTIFY_ALL, 267 ObjectIdInvalidationMap::InvalidateAll( 268 registrar_.GetAllRegisteredIds())); 269 } 270 if (!notification_data.IsTargeted(invalidator_client_id_)) { 271 DVLOG(1) << "Not a target of the notification -- " 272 << "not emitting notification"; 273 return; 274 } 275 registrar_.DispatchInvalidationsToHandlers( 276 notification_data.GetIdInvalidationMap()); 277 } 278 279 void P2PInvalidator::SendNotificationDataForTest( 280 const P2PNotificationData& notification_data) { 281 DCHECK(thread_checker_.CalledOnValidThread()); 282 SendNotificationData(notification_data); 283 } 284 285 void P2PInvalidator::SendNotificationData( 286 const P2PNotificationData& notification_data) { 287 DCHECK(thread_checker_.CalledOnValidThread()); 288 if (notification_data.GetIdInvalidationMap().Empty()) { 289 DVLOG(1) << "Not sending XMPP notification with empty state map: " 290 << notification_data.ToString(); 291 return; 292 } 293 notifier::Notification notification; 294 notification.channel = kSyncP2PNotificationChannel; 295 notification.data = notification_data.ToString(); 296 DVLOG(1) << "Sending XMPP notification: " << notification.ToString(); 297 push_client_->SendNotification(notification); 298 } 299 300 } // namespace syncer 301