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/notifier_reason_util.h"
15#include "jingle/notifier/listener/push_client.h"
16#include "sync/notifier/invalidation_handler.h"
17#include "sync/notifier/invalidation_util.h"
18#include "sync/notifier/object_id_invalidation_map.h"
19
20namespace syncer {
21
22const char kSyncP2PNotificationChannel[] = "http://www.google.com/chrome/sync";
23
24namespace {
25
26const char kNotifySelf[] = "notifySelf";
27const char kNotifyOthers[] = "notifyOthers";
28const char kNotifyAll[] = "notifyAll";
29
30const char kSenderIdKey[] = "senderId";
31const char kNotificationTypeKey[] = "notificationType";
32const char kInvalidationsKey[] = "invalidations";
33
34}  // namespace
35
36std::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
50P2PNotificationTarget 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
65P2PNotificationData::P2PNotificationData()
66    : target_(NOTIFY_SELF) {}
67
68P2PNotificationData::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
76P2PNotificationData::~P2PNotificationData() {}
77
78bool 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
92const ObjectIdInvalidationMap&
93P2PNotificationData::GetIdInvalidationMap() const {
94  return invalidation_map_;
95}
96
97bool 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
104std::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
115bool 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
139P2PInvalidator::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
152P2PInvalidator::~P2PInvalidator() {
153  DCHECK(thread_checker_.CalledOnValidThread());
154  push_client_->RemoveObserver(this);
155}
156
157void P2PInvalidator::RegisterHandler(InvalidationHandler* handler) {
158  DCHECK(thread_checker_.CalledOnValidThread());
159  registrar_.RegisterHandler(handler);
160}
161
162void 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
179void P2PInvalidator::UnregisterHandler(InvalidationHandler* handler) {
180  DCHECK(thread_checker_.CalledOnValidThread());
181  registrar_.UnregisterHandler(handler);
182}
183
184InvalidatorState P2PInvalidator::GetInvalidatorState() const {
185  DCHECK(thread_checker_.CalledOnValidThread());
186  return registrar_.GetInvalidatorState();
187}
188
189void 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
206void 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
214void 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
223void 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
238void P2PInvalidator::OnNotificationsDisabled(
239    notifier::NotificationsDisabledReason reason) {
240  DCHECK(thread_checker_.CalledOnValidThread());
241  registrar_.UpdateInvalidatorState(FromNotifierReason(reason));
242}
243
244void 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
279void P2PInvalidator::SendNotificationDataForTest(
280    const P2PNotificationData& notification_data) {
281  DCHECK(thread_checker_.CalledOnValidThread());
282  SendNotificationData(notification_data);
283}
284
285void 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