Home | History | Annotate | Download | only in notifier
      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