1// Copyright 2015 The Weave 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 "src/device_registration_info.h"
6
7#include <algorithm>
8#include <memory>
9#include <set>
10#include <utility>
11#include <vector>
12
13#include <base/bind.h>
14#include <base/json/json_reader.h>
15#include <base/json/json_writer.h>
16#include <base/strings/string_number_conversions.h>
17#include <base/strings/stringprintf.h>
18#include <base/values.h>
19#include <weave/provider/http_client.h>
20#include <weave/provider/network.h>
21#include <weave/provider/task_runner.h>
22
23#include "src/bind_lambda.h"
24#include "src/commands/cloud_command_proxy.h"
25#include "src/commands/schema_constants.h"
26#include "src/data_encoding.h"
27#include "src/http_constants.h"
28#include "src/json_error_codes.h"
29#include "src/notification/xmpp_channel.h"
30#include "src/privet/auth_manager.h"
31#include "src/string_utils.h"
32#include "src/utils.h"
33
34namespace weave {
35
36const char kErrorAlreayRegistered[] = "already_registered";
37
38namespace {
39
40const int kPollingPeriodSeconds = 7;
41const int kBackupPollingPeriodMinutes = 30;
42
43namespace fetch_reason {
44
45const char kDeviceStart[] = "device_start";  // Initial queue fetch at startup.
46const char kRegularPull[] = "regular_pull";  // Regular fetch before XMPP is up.
47const char kNewCommand[] = "new_command";    // A new command is available.
48const char kJustInCase[] = "just_in_case";   // Backup fetch when XMPP is live.
49
50}  // namespace fetch_reason
51
52using provider::HttpClient;
53
54inline void SetUnexpectedError(ErrorPtr* error) {
55  Error::AddTo(error, FROM_HERE, "unexpected_response", "Unexpected GCD error");
56}
57
58void ParseGCDError(const base::DictionaryValue* json, ErrorPtr* error) {
59  const base::Value* list_value = nullptr;
60  const base::ListValue* error_list = nullptr;
61  if (!json->Get("error.errors", &list_value) ||
62      !list_value->GetAsList(&error_list)) {
63    SetUnexpectedError(error);
64    return;
65  }
66
67  for (size_t i = 0; i < error_list->GetSize(); i++) {
68    const base::Value* error_value = nullptr;
69    const base::DictionaryValue* error_object = nullptr;
70    if (!error_list->Get(i, &error_value) ||
71        !error_value->GetAsDictionary(&error_object)) {
72      SetUnexpectedError(error);
73      continue;
74    }
75    std::string error_code, error_message;
76    if (error_object->GetString("reason", &error_code) &&
77        error_object->GetString("message", &error_message)) {
78      Error::AddTo(error, FROM_HERE, error_code, error_message);
79    } else {
80      SetUnexpectedError(error);
81    }
82  }
83}
84
85std::string AppendQueryParams(const std::string& url,
86                              const WebParamList& params) {
87  CHECK_EQ(std::string::npos, url.find_first_of("?#"));
88  if (params.empty())
89    return url;
90  return url + '?' + WebParamsEncode(params);
91}
92
93std::string BuildURL(const std::string& url,
94                     const std::string& subpath,
95                     const WebParamList& params) {
96  std::string result = url;
97  if (!result.empty() && result.back() != '/' && !subpath.empty()) {
98    CHECK_NE('/', subpath.front());
99    result += '/';
100  }
101  result += subpath;
102  return AppendQueryParams(result, params);
103}
104
105void IgnoreCloudErrorWithCallback(const base::Closure& cb, ErrorPtr) {
106  cb.Run();
107}
108
109void IgnoreCloudError(ErrorPtr) {}
110
111void IgnoreCloudResult(const base::DictionaryValue&, ErrorPtr error) {}
112
113void IgnoreCloudResultWithCallback(const DoneCallback& cb,
114                                   const base::DictionaryValue&,
115                                   ErrorPtr error) {
116  cb.Run(std::move(error));
117}
118
119class RequestSender final {
120 public:
121  RequestSender(HttpClient::Method method,
122                const std::string& url,
123                HttpClient* transport)
124      : method_{method}, url_{url}, transport_{transport} {}
125
126  void Send(const HttpClient::SendRequestCallback& callback) {
127    static int debug_id = 0;
128    ++debug_id;
129    VLOG(1) << "Sending request. id:" << debug_id
130            << " method:" << EnumToString(method_) << " url:" << url_;
131    VLOG(2) << "Request data: " << data_;
132    auto on_done = [](
133        int debug_id, const HttpClient::SendRequestCallback& callback,
134        std::unique_ptr<HttpClient::Response> response, ErrorPtr error) {
135      if (error) {
136        VLOG(1) << "Request failed, id=" << debug_id
137                << ", reason: " << error->GetCode()
138                << ", message: " << error->GetMessage();
139        return callback.Run({}, std::move(error));
140      }
141      VLOG(1) << "Request succeeded. id:" << debug_id
142              << " status:" << response->GetStatusCode();
143      VLOG(2) << "Response data: " << response->GetData();
144      callback.Run(std::move(response), nullptr);
145    };
146    transport_->SendRequest(method_, url_, GetFullHeaders(), data_,
147                            base::Bind(on_done, debug_id, callback));
148  }
149
150  void SetAccessToken(const std::string& access_token) {
151    access_token_ = access_token;
152  }
153
154  void SetData(const std::string& data, const std::string& mime_type) {
155    data_ = data;
156    mime_type_ = mime_type;
157  }
158
159  void SetFormData(
160      const std::vector<std::pair<std::string, std::string>>& data) {
161    SetData(WebParamsEncode(data), http::kWwwFormUrlEncoded);
162  }
163
164  void SetJsonData(const base::Value& json) {
165    std::string data;
166    CHECK(base::JSONWriter::Write(json, &data));
167    SetData(data, http::kJsonUtf8);
168  }
169
170 private:
171  HttpClient::Headers GetFullHeaders() const {
172    HttpClient::Headers headers;
173    if (!access_token_.empty())
174      headers.emplace_back(http::kAuthorization, "Bearer " + access_token_);
175    if (!mime_type_.empty())
176      headers.emplace_back(http::kContentType, mime_type_);
177    return headers;
178  }
179
180  HttpClient::Method method_;
181  std::string url_;
182  std::string data_;
183  std::string mime_type_;
184  std::string access_token_;
185  HttpClient* transport_{nullptr};
186
187  DISALLOW_COPY_AND_ASSIGN(RequestSender);
188};
189
190std::unique_ptr<base::DictionaryValue> ParseJsonResponse(
191    const HttpClient::Response& response,
192    ErrorPtr* error) {
193  // Make sure we have a correct content type. Do not try to parse
194  // binary files, or HTML output. Limit to application/json and text/plain.
195  std::string content_type =
196      SplitAtFirst(response.GetContentType(), ";", true).first;
197
198  if (content_type != http::kJson && content_type != http::kPlain) {
199    return Error::AddTo(
200        error, FROM_HERE, "non_json_content_type",
201        "Unexpected content type: \'" + response.GetContentType() + "\'");
202  }
203
204  const std::string& json = response.GetData();
205  std::string error_message;
206  auto value = base::JSONReader::ReadAndReturnError(json, base::JSON_PARSE_RFC,
207                                                    nullptr, &error_message);
208  if (!value) {
209    Error::AddToPrintf(error, FROM_HERE, errors::json::kParseError,
210                       "Error '%s' occurred parsing JSON string '%s'",
211                       error_message.c_str(), json.c_str());
212    return std::unique_ptr<base::DictionaryValue>();
213  }
214  base::DictionaryValue* dict_value = nullptr;
215  if (!value->GetAsDictionary(&dict_value)) {
216    Error::AddToPrintf(error, FROM_HERE, errors::json::kObjectExpected,
217                       "Response is not a valid JSON object: '%s'",
218                       json.c_str());
219    return std::unique_ptr<base::DictionaryValue>();
220  } else {
221    // |value| is now owned by |dict_value|, so release the scoped_ptr now.
222    base::IgnoreResult(value.release());
223  }
224  return std::unique_ptr<base::DictionaryValue>(dict_value);
225}
226
227bool IsSuccessful(const HttpClient::Response& response) {
228  int code = response.GetStatusCode();
229  return code >= http::kContinue && code < http::kBadRequest;
230}
231
232}  // anonymous namespace
233
234DeviceRegistrationInfo::DeviceRegistrationInfo(
235    Config* config,
236    ComponentManager* component_manager,
237    provider::TaskRunner* task_runner,
238    provider::HttpClient* http_client,
239    provider::Network* network,
240    privet::AuthManager* auth_manager)
241    : http_client_{http_client},
242      task_runner_{task_runner},
243      config_{config},
244      component_manager_{component_manager},
245      network_{network},
246      auth_manager_{auth_manager} {
247  cloud_backoff_policy_.reset(new BackoffEntry::Policy{});
248  cloud_backoff_policy_->num_errors_to_ignore = 0;
249  cloud_backoff_policy_->initial_delay_ms = 1000;
250  cloud_backoff_policy_->multiply_factor = 2.0;
251  cloud_backoff_policy_->jitter_factor = 0.1;
252  cloud_backoff_policy_->maximum_backoff_ms = 30000;
253  cloud_backoff_policy_->entry_lifetime_ms = -1;
254  cloud_backoff_policy_->always_use_initial_delay = false;
255  cloud_backoff_entry_.reset(new BackoffEntry{cloud_backoff_policy_.get()});
256  oauth2_backoff_entry_.reset(new BackoffEntry{cloud_backoff_policy_.get()});
257
258  bool revoked =
259      !GetSettings().cloud_id.empty() && !HaveRegistrationCredentials();
260  gcd_state_ =
261      revoked ? GcdState::kInvalidCredentials : GcdState::kUnconfigured;
262
263  component_manager_->AddTraitDefChangedCallback(base::Bind(
264      &DeviceRegistrationInfo::OnTraitDefsChanged, weak_factory_.GetWeakPtr()));
265  component_manager_->AddComponentTreeChangedCallback(
266      base::Bind(&DeviceRegistrationInfo::OnComponentTreeChanged,
267                 weak_factory_.GetWeakPtr()));
268  component_manager_->AddStateChangedCallback(base::Bind(
269      &DeviceRegistrationInfo::OnStateChanged, weak_factory_.GetWeakPtr()));
270}
271
272DeviceRegistrationInfo::~DeviceRegistrationInfo() = default;
273
274std::string DeviceRegistrationInfo::GetServiceURL(
275    const std::string& subpath,
276    const WebParamList& params) const {
277  return BuildURL(GetSettings().service_url, subpath, params);
278}
279
280std::string DeviceRegistrationInfo::GetDeviceURL(
281    const std::string& subpath,
282    const WebParamList& params) const {
283  CHECK(!GetSettings().cloud_id.empty()) << "Must have a valid device ID";
284  return BuildURL(GetSettings().service_url,
285                  "devices/" + GetSettings().cloud_id + "/" + subpath, params);
286}
287
288std::string DeviceRegistrationInfo::GetOAuthURL(
289    const std::string& subpath,
290    const WebParamList& params) const {
291  return BuildURL(GetSettings().oauth_url, subpath, params);
292}
293
294void DeviceRegistrationInfo::Start() {
295  if (HaveRegistrationCredentials()) {
296    StartNotificationChannel();
297    // Wait a significant amount of time for local daemons to publish their
298    // state to Buffet before publishing it to the cloud.
299    // TODO(wiley) We could do a lot of things here to either expose this
300    //             timeout as a configurable knob or allow local
301    //             daemons to signal that their state is up to date so that
302    //             we need not wait for them.
303    ScheduleCloudConnection(base::TimeDelta::FromSeconds(5));
304  }
305}
306
307void DeviceRegistrationInfo::ScheduleCloudConnection(
308    const base::TimeDelta& delay) {
309  SetGcdState(GcdState::kConnecting);
310  if (!task_runner_)
311    return;  // Assume we're in test
312  task_runner_->PostDelayedTask(
313      FROM_HERE,
314      base::Bind(&DeviceRegistrationInfo::ConnectToCloud, AsWeakPtr(), nullptr),
315      delay);
316}
317
318bool DeviceRegistrationInfo::HaveRegistrationCredentials() const {
319  return !GetSettings().refresh_token.empty() &&
320         !GetSettings().cloud_id.empty() &&
321         !GetSettings().robot_account.empty();
322}
323
324bool DeviceRegistrationInfo::VerifyRegistrationCredentials(
325    ErrorPtr* error) const {
326  const bool have_credentials = HaveRegistrationCredentials();
327
328  VLOG(2) << "Device registration record "
329          << ((have_credentials) ? "found" : "not found.");
330  if (!have_credentials) {
331    return Error::AddTo(error, FROM_HERE, "device_not_registered",
332                        "No valid device registration record found");
333  }
334  return true;
335}
336
337std::unique_ptr<base::DictionaryValue>
338DeviceRegistrationInfo::ParseOAuthResponse(const HttpClient::Response& response,
339                                           ErrorPtr* error) {
340  int code = response.GetStatusCode();
341  auto resp = ParseJsonResponse(response, error);
342  if (resp && code >= http::kBadRequest) {
343    std::string error_code, error_message;
344    if (!resp->GetString("error", &error_code)) {
345      error_code = "unexpected_response";
346    }
347    if (error_code == "invalid_grant") {
348      LOG(INFO) << "The device's registration has been revoked.";
349      SetGcdState(GcdState::kInvalidCredentials);
350    }
351    // I have never actually seen an error_description returned.
352    if (!resp->GetString("error_description", &error_message)) {
353      error_message = "Unexpected OAuth error";
354    }
355    return Error::AddTo(error, FROM_HERE, error_code, error_message);
356  }
357  return resp;
358}
359
360void DeviceRegistrationInfo::RefreshAccessToken(const DoneCallback& callback) {
361  LOG(INFO) << "Refreshing access token.";
362
363  ErrorPtr error;
364  if (!VerifyRegistrationCredentials(&error))
365    return callback.Run(std::move(error));
366
367  if (oauth2_backoff_entry_->ShouldRejectRequest()) {
368    VLOG(1) << "RefreshToken request delayed for "
369            << oauth2_backoff_entry_->GetTimeUntilRelease()
370            << " due to backoff policy";
371    task_runner_->PostDelayedTask(
372        FROM_HERE, base::Bind(&DeviceRegistrationInfo::RefreshAccessToken,
373                              AsWeakPtr(), callback),
374        oauth2_backoff_entry_->GetTimeUntilRelease());
375    return;
376  }
377
378  RequestSender sender{HttpClient::Method::kPost, GetOAuthURL("token"),
379                       http_client_};
380  sender.SetFormData({
381      {"refresh_token", GetSettings().refresh_token},
382      {"client_id", GetSettings().client_id},
383      {"client_secret", GetSettings().client_secret},
384      {"grant_type", "refresh_token"},
385  });
386  sender.Send(base::Bind(&DeviceRegistrationInfo::OnRefreshAccessTokenDone,
387                         weak_factory_.GetWeakPtr(), callback));
388  VLOG(1) << "Refresh access token request dispatched";
389}
390
391void DeviceRegistrationInfo::OnRefreshAccessTokenDone(
392    const DoneCallback& callback,
393    std::unique_ptr<HttpClient::Response> response,
394    ErrorPtr error) {
395  if (error) {
396    VLOG(1) << "Refresh access token failed";
397    oauth2_backoff_entry_->InformOfRequest(false);
398    return RefreshAccessToken(callback);
399  }
400  VLOG(1) << "Refresh access token request completed";
401  oauth2_backoff_entry_->InformOfRequest(true);
402  auto json = ParseOAuthResponse(*response, &error);
403  if (!json)
404    return callback.Run(std::move(error));
405
406  int expires_in = 0;
407  if (!json->GetString("access_token", &access_token_) ||
408      !json->GetInteger("expires_in", &expires_in) || access_token_.empty() ||
409      expires_in <= 0) {
410    LOG(ERROR) << "Access token unavailable.";
411    Error::AddTo(&error, FROM_HERE, "unexpected_server_response",
412                 "Access token unavailable");
413    return callback.Run(std::move(error));
414  }
415  access_token_expiration_ =
416      base::Time::Now() + base::TimeDelta::FromSeconds(expires_in);
417  LOG(INFO) << "Access token is refreshed for additional " << expires_in
418            << " seconds.";
419
420  if (primary_notification_channel_ &&
421      !primary_notification_channel_->IsConnected()) {
422    // If we have disconnected channel, it is due to failed credentials.
423    // Now that we have a new access token, retry the connection.
424    StartNotificationChannel();
425  }
426
427  SendAuthInfo();
428
429  callback.Run(nullptr);
430}
431
432void DeviceRegistrationInfo::StartNotificationChannel() {
433  if (notification_channel_starting_)
434    return;
435
436  LOG(INFO) << "Starting notification channel";
437
438  // If no TaskRunner assume we're in test.
439  if (!network_) {
440    LOG(INFO) << "No Network, not starting notification channel";
441    return;
442  }
443
444  if (primary_notification_channel_) {
445    primary_notification_channel_->Stop();
446    primary_notification_channel_.reset();
447    current_notification_channel_ = nullptr;
448  }
449
450  // Start with just regular polling at the pre-configured polling interval.
451  // Once the primary notification channel is connected successfully, it will
452  // call back to OnConnected() and at that time we'll switch to use the
453  // primary channel and switch periodic poll into much more infrequent backup
454  // poll mode.
455  const base::TimeDelta pull_interval =
456      base::TimeDelta::FromSeconds(kPollingPeriodSeconds);
457  if (!pull_channel_) {
458    pull_channel_.reset(new PullChannel{pull_interval, task_runner_});
459    pull_channel_->Start(this);
460  } else {
461    pull_channel_->UpdatePullInterval(pull_interval);
462  }
463  current_notification_channel_ = pull_channel_.get();
464
465  notification_channel_starting_ = true;
466  primary_notification_channel_.reset(
467      new XmppChannel{GetSettings().robot_account, access_token_,
468                      GetSettings().xmpp_endpoint, task_runner_, network_});
469  primary_notification_channel_->Start(this);
470}
471
472void DeviceRegistrationInfo::AddGcdStateChangedCallback(
473    const Device::GcdStateChangedCallback& callback) {
474  gcd_state_changed_callbacks_.push_back(callback);
475  callback.Run(gcd_state_);
476}
477
478std::unique_ptr<base::DictionaryValue>
479DeviceRegistrationInfo::BuildDeviceResource() const {
480  std::unique_ptr<base::DictionaryValue> resource{new base::DictionaryValue};
481  if (!GetSettings().cloud_id.empty())
482    resource->SetString("id", GetSettings().cloud_id);
483  resource->SetString("name", GetSettings().name);
484  if (!GetSettings().description.empty())
485    resource->SetString("description", GetSettings().description);
486  if (!GetSettings().location.empty())
487    resource->SetString("location", GetSettings().location);
488  resource->SetString("modelManifestId", GetSettings().model_id);
489  std::unique_ptr<base::DictionaryValue> channel{new base::DictionaryValue};
490  if (current_notification_channel_) {
491    channel->SetString("supportedType",
492                       current_notification_channel_->GetName());
493    current_notification_channel_->AddChannelParameters(channel.get());
494  } else {
495    channel->SetString("supportedType", "pull");
496  }
497  resource->Set("channel", channel.release());
498  resource->Set("traits", component_manager_->GetTraits().DeepCopy());
499  resource->Set("components", component_manager_->GetComponents().DeepCopy());
500
501  return resource;
502}
503
504void DeviceRegistrationInfo::GetDeviceInfo(
505    const CloudRequestDoneCallback& callback) {
506  ErrorPtr error;
507  if (!VerifyRegistrationCredentials(&error))
508    return callback.Run({}, std::move(error));
509  DoCloudRequest(HttpClient::Method::kGet, GetDeviceURL(), nullptr, callback);
510}
511
512void DeviceRegistrationInfo::RegisterDeviceError(const DoneCallback& callback,
513                                                 ErrorPtr error) {
514  task_runner_->PostDelayedTask(FROM_HERE,
515                                base::Bind(callback, base::Passed(&error)), {});
516}
517
518void DeviceRegistrationInfo::RegisterDevice(const std::string& ticket_id,
519                                            const DoneCallback& callback) {
520  if (HaveRegistrationCredentials()) {
521    ErrorPtr error;
522    Error::AddTo(&error, FROM_HERE, kErrorAlreayRegistered,
523                 "Unable to register already registered device");
524    return RegisterDeviceError(callback, std::move(error));
525  }
526
527  std::unique_ptr<base::DictionaryValue> device_draft = BuildDeviceResource();
528  CHECK(device_draft);
529
530  base::DictionaryValue req_json;
531  req_json.SetString("id", ticket_id);
532  req_json.SetString("oauthClientId", GetSettings().client_id);
533  req_json.Set("deviceDraft", device_draft.release());
534
535  auto url = GetServiceURL("registrationTickets/" + ticket_id,
536                           {{"key", GetSettings().api_key}});
537
538  RequestSender sender{HttpClient::Method::kPatch, url, http_client_};
539  sender.SetJsonData(req_json);
540  sender.Send(base::Bind(&DeviceRegistrationInfo::RegisterDeviceOnTicketSent,
541                         weak_factory_.GetWeakPtr(), ticket_id, callback));
542}
543
544void DeviceRegistrationInfo::RegisterDeviceOnTicketSent(
545    const std::string& ticket_id,
546    const DoneCallback& callback,
547    std::unique_ptr<provider::HttpClient::Response> response,
548    ErrorPtr error) {
549  if (error)
550    return RegisterDeviceError(callback, std::move(error));
551  auto json_resp = ParseJsonResponse(*response, &error);
552  if (!json_resp)
553    return RegisterDeviceError(callback, std::move(error));
554
555  if (!IsSuccessful(*response)) {
556    ParseGCDError(json_resp.get(), &error);
557    return RegisterDeviceError(callback, std::move(error));
558  }
559
560  std::string url =
561      GetServiceURL("registrationTickets/" + ticket_id + "/finalize",
562                    {{"key", GetSettings().api_key}});
563  RequestSender{HttpClient::Method::kPost, url, http_client_}.Send(
564      base::Bind(&DeviceRegistrationInfo::RegisterDeviceOnTicketFinalized,
565                 weak_factory_.GetWeakPtr(), callback));
566}
567
568void DeviceRegistrationInfo::RegisterDeviceOnTicketFinalized(
569    const DoneCallback& callback,
570    std::unique_ptr<provider::HttpClient::Response> response,
571    ErrorPtr error) {
572  if (error)
573    return RegisterDeviceError(callback, std::move(error));
574  auto json_resp = ParseJsonResponse(*response, &error);
575  if (!json_resp)
576    return RegisterDeviceError(callback, std::move(error));
577  if (!IsSuccessful(*response)) {
578    ParseGCDError(json_resp.get(), &error);
579    return RegisterDeviceError(callback, std::move(error));
580  }
581
582  std::string auth_code;
583  std::string cloud_id;
584  std::string robot_account;
585  const base::DictionaryValue* device_draft_response = nullptr;
586  if (!json_resp->GetString("robotAccountEmail", &robot_account) ||
587      !json_resp->GetString("robotAccountAuthorizationCode", &auth_code) ||
588      !json_resp->GetDictionary("deviceDraft", &device_draft_response) ||
589      !device_draft_response->GetString("id", &cloud_id)) {
590    Error::AddTo(&error, FROM_HERE, "unexpected_response",
591                 "Device account missing in response");
592    return RegisterDeviceError(callback, std::move(error));
593  }
594
595  UpdateDeviceInfoTimestamp(*device_draft_response);
596
597  // Now get access_token and refresh_token
598  RequestSender sender2{HttpClient::Method::kPost, GetOAuthURL("token"),
599                        http_client_};
600  sender2.SetFormData({{"code", auth_code},
601                       {"client_id", GetSettings().client_id},
602                       {"client_secret", GetSettings().client_secret},
603                       {"redirect_uri", "oob"},
604                       {"grant_type", "authorization_code"}});
605  sender2.Send(base::Bind(&DeviceRegistrationInfo::RegisterDeviceOnAuthCodeSent,
606                          weak_factory_.GetWeakPtr(), cloud_id, robot_account,
607                          callback));
608}
609
610void DeviceRegistrationInfo::RegisterDeviceOnAuthCodeSent(
611    const std::string& cloud_id,
612    const std::string& robot_account,
613    const DoneCallback& callback,
614    std::unique_ptr<provider::HttpClient::Response> response,
615    ErrorPtr error) {
616  if (error)
617    return RegisterDeviceError(callback, std::move(error));
618  auto json_resp = ParseOAuthResponse(*response, &error);
619  int expires_in = 0;
620  std::string refresh_token;
621  if (!json_resp || !json_resp->GetString("access_token", &access_token_) ||
622      !json_resp->GetString("refresh_token", &refresh_token) ||
623      !json_resp->GetInteger("expires_in", &expires_in) ||
624      access_token_.empty() || refresh_token.empty() || expires_in <= 0) {
625    Error::AddTo(&error, FROM_HERE, "unexpected_response",
626                 "Device access_token missing in response");
627    return RegisterDeviceError(callback, std::move(error));
628  }
629
630  access_token_expiration_ =
631      base::Time::Now() + base::TimeDelta::FromSeconds(expires_in);
632
633  Config::Transaction change{config_};
634  change.set_cloud_id(cloud_id);
635  change.set_robot_account(robot_account);
636  change.set_refresh_token(refresh_token);
637  change.Commit();
638
639  task_runner_->PostDelayedTask(FROM_HERE, base::Bind(callback, nullptr), {});
640
641  StartNotificationChannel();
642  SendAuthInfo();
643
644  // We're going to respond with our success immediately and we'll connect to
645  // cloud shortly after.
646  ScheduleCloudConnection({});
647}
648
649void DeviceRegistrationInfo::DoCloudRequest(
650    HttpClient::Method method,
651    const std::string& url,
652    const base::DictionaryValue* body,
653    const CloudRequestDoneCallback& callback) {
654  // We make CloudRequestData shared here because we want to make sure
655  // there is only one instance of callback and error_calback since
656  // those may have move-only types and making a copy of the callback with
657  // move-only types curried-in will invalidate the source callback.
658  auto data = std::make_shared<CloudRequestData>();
659  data->method = method;
660  data->url = url;
661  if (body)
662    base::JSONWriter::Write(*body, &data->body);
663  data->callback = callback;
664  SendCloudRequest(data);
665}
666
667void DeviceRegistrationInfo::SendCloudRequest(
668    const std::shared_ptr<const CloudRequestData>& data) {
669  // TODO(antonm): Add reauthorization on access token expiration (do not
670  // forget about 5xx when fetching new access token).
671  // TODO(antonm): Add support for device removal.
672
673  ErrorPtr error;
674  if (!VerifyRegistrationCredentials(&error))
675    return data->callback.Run({}, std::move(error));
676
677  if (cloud_backoff_entry_->ShouldRejectRequest()) {
678    VLOG(1) << "Cloud request delayed for "
679            << cloud_backoff_entry_->GetTimeUntilRelease()
680            << " due to backoff policy";
681    return task_runner_->PostDelayedTask(
682        FROM_HERE, base::Bind(&DeviceRegistrationInfo::SendCloudRequest,
683                              AsWeakPtr(), data),
684        cloud_backoff_entry_->GetTimeUntilRelease());
685  }
686
687  RequestSender sender{data->method, data->url, http_client_};
688  sender.SetData(data->body, http::kJsonUtf8);
689  sender.SetAccessToken(access_token_);
690  sender.Send(base::Bind(&DeviceRegistrationInfo::OnCloudRequestDone,
691                         AsWeakPtr(), data));
692}
693
694void DeviceRegistrationInfo::OnCloudRequestDone(
695    const std::shared_ptr<const CloudRequestData>& data,
696    std::unique_ptr<provider::HttpClient::Response> response,
697    ErrorPtr error) {
698  if (error)
699    return RetryCloudRequest(data);
700  int status_code = response->GetStatusCode();
701  if (status_code == http::kDenied) {
702    cloud_backoff_entry_->InformOfRequest(true);
703    RefreshAccessToken(base::Bind(
704        &DeviceRegistrationInfo::OnAccessTokenRefreshed, AsWeakPtr(), data));
705    return;
706  }
707
708  if (status_code >= http::kInternalServerError) {
709    // Request was valid, but server failed, retry.
710    // TODO(antonm): Reconsider status codes, maybe only some require
711    // retry.
712    // TODO(antonm): Support Retry-After header.
713    RetryCloudRequest(data);
714    return;
715  }
716
717  if (response->GetContentType().empty()) {
718    // Assume no body if no content type.
719    cloud_backoff_entry_->InformOfRequest(true);
720    return data->callback.Run({}, nullptr);
721  }
722
723  auto json_resp = ParseJsonResponse(*response, &error);
724  if (!json_resp) {
725    cloud_backoff_entry_->InformOfRequest(false);
726    return data->callback.Run({}, std::move(error));
727  }
728
729  if (!IsSuccessful(*response)) {
730    ParseGCDError(json_resp.get(), &error);
731    if (status_code == http::kForbidden &&
732        error->HasError("rateLimitExceeded")) {
733      // If we exceeded server quota, retry the request later.
734      return RetryCloudRequest(data);
735    }
736
737    cloud_backoff_entry_->InformOfRequest(false);
738    return data->callback.Run({}, std::move(error));
739  }
740
741  cloud_backoff_entry_->InformOfRequest(true);
742  SetGcdState(GcdState::kConnected);
743  data->callback.Run(*json_resp, nullptr);
744}
745
746void DeviceRegistrationInfo::RetryCloudRequest(
747    const std::shared_ptr<const CloudRequestData>& data) {
748  // TODO(avakulenko): Tie connecting/connected status to XMPP channel instead.
749  SetGcdState(GcdState::kConnecting);
750  cloud_backoff_entry_->InformOfRequest(false);
751  SendCloudRequest(data);
752}
753
754void DeviceRegistrationInfo::OnAccessTokenRefreshed(
755    const std::shared_ptr<const CloudRequestData>& data,
756    ErrorPtr error) {
757  if (error) {
758    CheckAccessTokenError(error->Clone());
759    return data->callback.Run({}, std::move(error));
760  }
761  SendCloudRequest(data);
762}
763
764void DeviceRegistrationInfo::CheckAccessTokenError(ErrorPtr error) {
765  if (error && error->HasError("invalid_grant"))
766    RemoveCredentials();
767}
768
769void DeviceRegistrationInfo::ConnectToCloud(ErrorPtr error) {
770  if (error) {
771    if (error->HasError("invalid_grant"))
772      RemoveCredentials();
773    return;
774  }
775
776  connected_to_cloud_ = false;
777  if (!VerifyRegistrationCredentials(nullptr))
778    return;
779
780  if (access_token_.empty()) {
781    RefreshAccessToken(
782        base::Bind(&DeviceRegistrationInfo::ConnectToCloud, AsWeakPtr()));
783    return;
784  }
785
786  // Connecting a device to cloud just means that we:
787  //   1) push an updated device resource
788  //   2) fetch an initial set of outstanding commands
789  //   3) abort any commands that we've previously marked as "in progress"
790  //      or as being in an error state; publish queued commands
791  UpdateDeviceResource(
792      base::Bind(&DeviceRegistrationInfo::OnConnectedToCloud, AsWeakPtr()));
793}
794
795void DeviceRegistrationInfo::OnConnectedToCloud(ErrorPtr error) {
796  if (error)
797    return;
798  LOG(INFO) << "Device connected to cloud server";
799  connected_to_cloud_ = true;
800  FetchCommands(base::Bind(&DeviceRegistrationInfo::ProcessInitialCommandList,
801                           AsWeakPtr()),
802                fetch_reason::kDeviceStart);
803  // In case there are any pending state updates since we sent off the initial
804  // UpdateDeviceResource() request, update the server with any state changes.
805  PublishStateUpdates();
806}
807
808void DeviceRegistrationInfo::UpdateDeviceInfo(const std::string& name,
809                                              const std::string& description,
810                                              const std::string& location) {
811  Config::Transaction change{config_};
812  change.set_name(name);
813  change.set_description(description);
814  change.set_location(location);
815  change.Commit();
816
817  if (HaveRegistrationCredentials()) {
818    UpdateDeviceResource(base::Bind(&IgnoreCloudError));
819  }
820}
821
822void DeviceRegistrationInfo::UpdateBaseConfig(AuthScope anonymous_access_role,
823                                              bool local_discovery_enabled,
824                                              bool local_pairing_enabled) {
825  Config::Transaction change(config_);
826  change.set_local_anonymous_access_role(anonymous_access_role);
827  change.set_local_discovery_enabled(local_discovery_enabled);
828  change.set_local_pairing_enabled(local_pairing_enabled);
829}
830
831bool DeviceRegistrationInfo::UpdateServiceConfig(
832    const std::string& client_id,
833    const std::string& client_secret,
834    const std::string& api_key,
835    const std::string& oauth_url,
836    const std::string& service_url,
837    const std::string& xmpp_endpoint,
838    ErrorPtr* error) {
839  if (HaveRegistrationCredentials()) {
840    return Error::AddTo(error, FROM_HERE, kErrorAlreayRegistered,
841                        "Unable to change config for registered device");
842  }
843  Config::Transaction change{config_};
844  if (!client_id.empty())
845    change.set_client_id(client_id);
846  if (!client_secret.empty())
847    change.set_client_secret(client_secret);
848  if (!api_key.empty())
849    change.set_api_key(api_key);
850  if (!oauth_url.empty())
851    change.set_oauth_url(oauth_url);
852  if (!service_url.empty())
853    change.set_service_url(service_url);
854  if (!xmpp_endpoint.empty())
855    change.set_xmpp_endpoint(xmpp_endpoint);
856  return true;
857}
858
859void DeviceRegistrationInfo::UpdateCommand(
860    const std::string& command_id,
861    const base::DictionaryValue& command_patch,
862    const DoneCallback& callback) {
863  DoCloudRequest(HttpClient::Method::kPatch,
864                 GetServiceURL("commands/" + command_id), &command_patch,
865                 base::Bind(&IgnoreCloudResultWithCallback, callback));
866}
867
868void DeviceRegistrationInfo::NotifyCommandAborted(const std::string& command_id,
869                                                  ErrorPtr error) {
870  base::DictionaryValue command_patch;
871  command_patch.SetString(commands::attributes::kCommand_State,
872                          EnumToString(Command::State::kAborted));
873  if (error) {
874    command_patch.Set(commands::attributes::kCommand_Error,
875                      ErrorInfoToJson(*error).release());
876  }
877  UpdateCommand(command_id, command_patch, base::Bind(&IgnoreCloudError));
878}
879
880void DeviceRegistrationInfo::UpdateDeviceResource(
881    const DoneCallback& callback) {
882  queued_resource_update_callbacks_.emplace_back(callback);
883  if (!in_progress_resource_update_callbacks_.empty()) {
884    VLOG(1) << "Another request is already pending.";
885    return;
886  }
887
888  StartQueuedUpdateDeviceResource();
889}
890
891void DeviceRegistrationInfo::StartQueuedUpdateDeviceResource() {
892  if (in_progress_resource_update_callbacks_.empty() &&
893      queued_resource_update_callbacks_.empty())
894    return;
895
896  if (last_device_resource_updated_timestamp_.empty()) {
897    // We don't know the current time stamp of the device resource from the
898    // server side. We need to provide the time stamp to the server as part of
899    // the request to guard against out-of-order requests overwriting settings
900    // specified by later requests.
901    VLOG(1) << "Getting the last device resource timestamp from server...";
902    GetDeviceInfo(base::Bind(&DeviceRegistrationInfo::OnDeviceInfoRetrieved,
903                             AsWeakPtr()));
904    return;
905  }
906
907  in_progress_resource_update_callbacks_.insert(
908      in_progress_resource_update_callbacks_.end(),
909      queued_resource_update_callbacks_.begin(),
910      queued_resource_update_callbacks_.end());
911  queued_resource_update_callbacks_.clear();
912
913  VLOG(1) << "Updating GCD server with CDD...";
914  std::unique_ptr<base::DictionaryValue> device_resource =
915      BuildDeviceResource();
916  CHECK(device_resource);
917
918  std::string url = GetDeviceURL(
919      {}, {{"lastUpdateTimeMs", last_device_resource_updated_timestamp_}});
920
921  DoCloudRequest(HttpClient::Method::kPut, url, device_resource.get(),
922                 base::Bind(&DeviceRegistrationInfo::OnUpdateDeviceResourceDone,
923                            AsWeakPtr()));
924}
925
926void DeviceRegistrationInfo::SendAuthInfo() {
927  if (!auth_manager_ || auth_info_update_inprogress_)
928    return;
929
930  if (GetSettings().root_client_token_owner == RootClientTokenOwner::kCloud) {
931    // Avoid re-claiming if device is already claimed by the Cloud. Cloud is
932    // allowed to re-claim device at any time. However this will invalidate all
933    // issued tokens.
934    return;
935  }
936
937  auth_info_update_inprogress_ = true;
938
939  std::vector<uint8_t> token = auth_manager_->ClaimRootClientAuthToken(
940      RootClientTokenOwner::kCloud, nullptr);
941  CHECK(!token.empty());
942  std::string id = GetSettings().device_id;
943  std::string token_base64 = Base64Encode(token);
944  std::string fingerprint =
945      Base64Encode(auth_manager_->GetCertificateFingerprint());
946
947  std::unique_ptr<base::DictionaryValue> auth{new base::DictionaryValue};
948  auth->SetString("localId", id);
949  auth->SetString("clientToken", token_base64);
950  auth->SetString("certFingerprint", fingerprint);
951  std::unique_ptr<base::DictionaryValue> root{new base::DictionaryValue};
952  root->Set("localAuthInfo", auth.release());
953
954  std::string url = GetDeviceURL("upsertLocalAuthInfo", {});
955  DoCloudRequest(HttpClient::Method::kPost, url, root.get(),
956                 base::Bind(&DeviceRegistrationInfo::OnSendAuthInfoDone,
957                            AsWeakPtr(), token));
958}
959
960void DeviceRegistrationInfo::OnSendAuthInfoDone(
961    const std::vector<uint8_t>& token,
962    const base::DictionaryValue& body,
963    ErrorPtr error) {
964  CHECK(auth_info_update_inprogress_);
965  auth_info_update_inprogress_ = false;
966
967  if (!error && auth_manager_->ConfirmClientAuthToken(token, nullptr))
968    return;
969
970  task_runner_->PostDelayedTask(
971      FROM_HERE, base::Bind(&DeviceRegistrationInfo::SendAuthInfo, AsWeakPtr()),
972      {});
973}
974
975void DeviceRegistrationInfo::OnDeviceInfoRetrieved(
976    const base::DictionaryValue& device_info,
977    ErrorPtr error) {
978  if (error)
979    return OnUpdateDeviceResourceError(std::move(error));
980  if (UpdateDeviceInfoTimestamp(device_info))
981    StartQueuedUpdateDeviceResource();
982}
983
984bool DeviceRegistrationInfo::UpdateDeviceInfoTimestamp(
985    const base::DictionaryValue& device_info) {
986  // For newly created devices, "lastUpdateTimeMs" may not be present, but
987  // "creationTimeMs" should be there at least.
988  if (!device_info.GetString("lastUpdateTimeMs",
989                             &last_device_resource_updated_timestamp_) &&
990      !device_info.GetString("creationTimeMs",
991                             &last_device_resource_updated_timestamp_)) {
992    LOG(WARNING) << "Device resource timestamp is missing";
993    return false;
994  }
995  return true;
996}
997
998void DeviceRegistrationInfo::OnUpdateDeviceResourceDone(
999    const base::DictionaryValue& device_info,
1000    ErrorPtr error) {
1001  if (error)
1002    return OnUpdateDeviceResourceError(std::move(error));
1003  UpdateDeviceInfoTimestamp(device_info);
1004  // Make a copy of the callback list so that if the callback triggers another
1005  // call to UpdateDeviceResource(), we do not modify the list we are iterating
1006  // over.
1007  auto callback_list = std::move(in_progress_resource_update_callbacks_);
1008  for (const auto& callback : callback_list)
1009    callback.Run(nullptr);
1010  StartQueuedUpdateDeviceResource();
1011}
1012
1013void DeviceRegistrationInfo::OnUpdateDeviceResourceError(ErrorPtr error) {
1014  if (error->HasError("invalid_last_update_time_ms")) {
1015    // If the server rejected our previous request, retrieve the latest
1016    // timestamp from the server and retry.
1017    VLOG(1) << "Getting the last device resource timestamp from server...";
1018    GetDeviceInfo(base::Bind(&DeviceRegistrationInfo::OnDeviceInfoRetrieved,
1019                             AsWeakPtr()));
1020    return;
1021  }
1022
1023  // Make a copy of the callback list so that if the callback triggers another
1024  // call to UpdateDeviceResource(), we do not modify the list we are iterating
1025  // over.
1026  auto callback_list = std::move(in_progress_resource_update_callbacks_);
1027  for (const auto& callback : callback_list)
1028    callback.Run(error->Clone());
1029
1030  StartQueuedUpdateDeviceResource();
1031}
1032
1033void DeviceRegistrationInfo::OnFetchCommandsDone(
1034    const base::Callback<void(const base::ListValue&, ErrorPtr)>& callback,
1035    const base::DictionaryValue& json,
1036    ErrorPtr error) {
1037  OnFetchCommandsReturned();
1038  if (error)
1039    return callback.Run({}, std::move(error));
1040  const base::ListValue* commands{nullptr};
1041  if (!json.GetList("commands", &commands))
1042    VLOG(2) << "No commands in the response.";
1043  const base::ListValue empty;
1044  callback.Run(commands ? *commands : empty, nullptr);
1045}
1046
1047void DeviceRegistrationInfo::OnFetchCommandsReturned() {
1048  fetch_commands_request_sent_ = false;
1049  // If we have additional requests queued, send them out now.
1050  if (fetch_commands_request_queued_)
1051    FetchAndPublishCommands(queued_fetch_reason_);
1052}
1053
1054void DeviceRegistrationInfo::FetchCommands(
1055    const base::Callback<void(const base::ListValue&, ErrorPtr)>& callback,
1056    const std::string& reason) {
1057  fetch_commands_request_sent_ = true;
1058  fetch_commands_request_queued_ = false;
1059  DoCloudRequest(
1060      HttpClient::Method::kGet,
1061      GetServiceURL("commands/queue",
1062                    {{"deviceId", GetSettings().cloud_id}, {"reason", reason}}),
1063      nullptr, base::Bind(&DeviceRegistrationInfo::OnFetchCommandsDone,
1064                          AsWeakPtr(), callback));
1065}
1066
1067void DeviceRegistrationInfo::FetchAndPublishCommands(
1068    const std::string& reason) {
1069  if (fetch_commands_request_sent_) {
1070    fetch_commands_request_queued_ = true;
1071    queued_fetch_reason_ = reason;
1072    return;
1073  }
1074
1075  FetchCommands(base::Bind(&DeviceRegistrationInfo::PublishCommands,
1076                           weak_factory_.GetWeakPtr()),
1077                reason);
1078}
1079
1080void DeviceRegistrationInfo::ProcessInitialCommandList(
1081    const base::ListValue& commands,
1082    ErrorPtr error) {
1083  if (error)
1084    return;
1085  for (const base::Value* command : commands) {
1086    const base::DictionaryValue* command_dict{nullptr};
1087    if (!command->GetAsDictionary(&command_dict)) {
1088      LOG(WARNING) << "Not a command dictionary: " << *command;
1089      continue;
1090    }
1091    std::string command_state;
1092    if (!command_dict->GetString("state", &command_state)) {
1093      LOG(WARNING) << "Command with no state at " << *command;
1094      continue;
1095    }
1096    if (command_state == "error" && command_state == "inProgress" &&
1097        command_state == "paused") {
1098      // It's a limbo command, abort it.
1099      std::string command_id;
1100      if (!command_dict->GetString("id", &command_id)) {
1101        LOG(WARNING) << "Command with no ID at " << *command;
1102        continue;
1103      }
1104
1105      std::unique_ptr<base::DictionaryValue> cmd_copy{command_dict->DeepCopy()};
1106      cmd_copy->SetString("state", "aborted");
1107      // TODO(wiley) We could consider handling this error case more gracefully.
1108      DoCloudRequest(HttpClient::Method::kPut,
1109                     GetServiceURL("commands/" + command_id), cmd_copy.get(),
1110                     base::Bind(&IgnoreCloudResult));
1111    } else {
1112      // Normal command, publish it to local clients.
1113      PublishCommand(*command_dict);
1114    }
1115  }
1116}
1117
1118void DeviceRegistrationInfo::PublishCommands(const base::ListValue& commands,
1119                                             ErrorPtr error) {
1120  if (error)
1121    return;
1122  for (const base::Value* command : commands) {
1123    const base::DictionaryValue* command_dict{nullptr};
1124    if (!command->GetAsDictionary(&command_dict)) {
1125      LOG(WARNING) << "Not a command dictionary: " << *command;
1126      continue;
1127    }
1128    PublishCommand(*command_dict);
1129  }
1130}
1131
1132void DeviceRegistrationInfo::PublishCommand(
1133    const base::DictionaryValue& command) {
1134  std::string command_id;
1135  ErrorPtr error;
1136  auto command_instance = component_manager_->ParseCommandInstance(
1137      command, Command::Origin::kCloud, UserRole::kOwner, &command_id, &error);
1138  if (!command_instance) {
1139    LOG(WARNING) << "Failed to parse a command instance: " << command;
1140    if (!command_id.empty())
1141      NotifyCommandAborted(command_id, std::move(error));
1142    return;
1143  }
1144
1145  // TODO(antonm): Properly process cancellation of commands.
1146  if (!component_manager_->FindCommand(command_instance->GetID())) {
1147    LOG(INFO) << "New command '" << command_instance->GetName()
1148              << "' arrived, ID: " << command_instance->GetID();
1149    std::unique_ptr<BackoffEntry> backoff_entry{
1150        new BackoffEntry{cloud_backoff_policy_.get()}};
1151    std::unique_ptr<CloudCommandProxy> cloud_proxy{
1152        new CloudCommandProxy{command_instance.get(), this, component_manager_,
1153                              std::move(backoff_entry), task_runner_}};
1154    // CloudCommandProxy::CloudCommandProxy() subscribe itself to Command
1155    // notifications. When Command is being destroyed it sends
1156    // ::OnCommandDestroyed() and CloudCommandProxy deletes itself.
1157    cloud_proxy.release();
1158    component_manager_->AddCommand(std::move(command_instance));
1159  }
1160}
1161
1162void DeviceRegistrationInfo::PublishStateUpdates() {
1163  // If we have pending state update requests, don't send any more for now.
1164  if (device_state_update_pending_)
1165    return;
1166
1167  auto snapshot = component_manager_->GetAndClearRecordedStateChanges();
1168  if (snapshot.state_changes.empty())
1169    return;
1170
1171  std::unique_ptr<base::ListValue> patches{new base::ListValue};
1172  for (auto& state_change : snapshot.state_changes) {
1173    std::unique_ptr<base::DictionaryValue> patch{new base::DictionaryValue};
1174    patch->SetString("timeMs",
1175                     std::to_string(state_change.timestamp.ToJavaTime()));
1176    patch->SetString("component", state_change.component);
1177    patch->Set("patch", state_change.changed_properties.release());
1178    patches->Append(patch.release());
1179  }
1180
1181  base::DictionaryValue body;
1182  body.SetString("requestTimeMs",
1183                 std::to_string(base::Time::Now().ToJavaTime()));
1184  body.Set("patches", patches.release());
1185
1186  device_state_update_pending_ = true;
1187  DoCloudRequest(HttpClient::Method::kPost, GetDeviceURL("patchState"), &body,
1188                 base::Bind(&DeviceRegistrationInfo::OnPublishStateDone,
1189                            AsWeakPtr(), snapshot.update_id));
1190}
1191
1192void DeviceRegistrationInfo::OnPublishStateDone(
1193    ComponentManager::UpdateID update_id,
1194    const base::DictionaryValue& reply,
1195    ErrorPtr error) {
1196  device_state_update_pending_ = false;
1197  if (error) {
1198    LOG(ERROR) << "Permanent failure while trying to update device state";
1199    return;
1200  }
1201  component_manager_->NotifyStateUpdatedOnServer(update_id);
1202  // See if there were more pending state updates since the previous request
1203  // had been sent out.
1204  PublishStateUpdates();
1205}
1206
1207void DeviceRegistrationInfo::SetGcdState(GcdState new_state) {
1208  VLOG_IF(1, new_state != gcd_state_) << "Changing registration status to "
1209                                      << EnumToString(new_state);
1210  gcd_state_ = new_state;
1211  for (const auto& cb : gcd_state_changed_callbacks_)
1212    cb.Run(gcd_state_);
1213}
1214
1215void DeviceRegistrationInfo::OnTraitDefsChanged() {
1216  VLOG(1) << "CommandDefinitionChanged notification received";
1217  if (!HaveRegistrationCredentials() || !connected_to_cloud_)
1218    return;
1219
1220  UpdateDeviceResource(base::Bind(&IgnoreCloudError));
1221}
1222
1223void DeviceRegistrationInfo::OnStateChanged() {
1224  VLOG(1) << "StateChanged notification received";
1225  if (!HaveRegistrationCredentials() || !connected_to_cloud_)
1226    return;
1227
1228  // TODO(vitalybuka): Integrate BackoffEntry.
1229  PublishStateUpdates();
1230}
1231
1232void DeviceRegistrationInfo::OnComponentTreeChanged() {
1233  VLOG(1) << "ComponentTreeChanged notification received";
1234  if (!HaveRegistrationCredentials() || !connected_to_cloud_)
1235    return;
1236
1237  UpdateDeviceResource(base::Bind(&IgnoreCloudError));
1238}
1239
1240void DeviceRegistrationInfo::OnConnected(const std::string& channel_name) {
1241  LOG(INFO) << "Notification channel successfully established over "
1242            << channel_name;
1243  CHECK_EQ(primary_notification_channel_->GetName(), channel_name);
1244  notification_channel_starting_ = false;
1245  pull_channel_->UpdatePullInterval(
1246      base::TimeDelta::FromMinutes(kBackupPollingPeriodMinutes));
1247  current_notification_channel_ = primary_notification_channel_.get();
1248
1249  // If we have not successfully connected to the cloud server and we have not
1250  // initiated the first device resource update, there is nothing we need to
1251  // do now to update the server of the notification channel change.
1252  if (!connected_to_cloud_ && in_progress_resource_update_callbacks_.empty())
1253    return;
1254
1255  // Once we update the device resource with the new notification channel,
1256  // do the last poll for commands from the server, to make sure we have the
1257  // latest command baseline and no other commands have been queued between
1258  // the moment of the last poll and the time we successfully told the server
1259  // to send new commands over the new notification channel.
1260  UpdateDeviceResource(
1261      base::Bind(&IgnoreCloudErrorWithCallback,
1262                 base::Bind(&DeviceRegistrationInfo::FetchAndPublishCommands,
1263                            AsWeakPtr(), fetch_reason::kRegularPull)));
1264}
1265
1266void DeviceRegistrationInfo::OnDisconnected() {
1267  LOG(INFO) << "Notification channel disconnected";
1268  if (!HaveRegistrationCredentials() || !connected_to_cloud_)
1269    return;
1270
1271  pull_channel_->UpdatePullInterval(
1272      base::TimeDelta::FromSeconds(kPollingPeriodSeconds));
1273  current_notification_channel_ = pull_channel_.get();
1274  UpdateDeviceResource(base::Bind(&IgnoreCloudError));
1275}
1276
1277void DeviceRegistrationInfo::OnPermanentFailure() {
1278  LOG(ERROR) << "Failed to establish notification channel.";
1279  notification_channel_starting_ = false;
1280  RefreshAccessToken(
1281      base::Bind(&DeviceRegistrationInfo::CheckAccessTokenError, AsWeakPtr()));
1282}
1283
1284void DeviceRegistrationInfo::OnCommandCreated(
1285    const base::DictionaryValue& command,
1286    const std::string& channel_name) {
1287  if (!connected_to_cloud_)
1288    return;
1289
1290  VLOG(1) << "Command notification received: " << command;
1291
1292  if (!command.empty()) {
1293    // GCD spec indicates that the command parameter in notification object
1294    // "may be empty if command size is too big".
1295    PublishCommand(command);
1296    return;
1297  }
1298
1299  // If this request comes from a Pull channel while the primary notification
1300  // channel (XMPP) is active, we are doing a backup poll, so mark the request
1301  // appropriately.
1302  bool just_in_case =
1303      (channel_name == kPullChannelName) &&
1304      (current_notification_channel_ == primary_notification_channel_.get());
1305
1306  std::string reason =
1307      just_in_case ? fetch_reason::kJustInCase : fetch_reason::kNewCommand;
1308
1309  // If the command was too big to be delivered over a notification channel,
1310  // or OnCommandCreated() was initiated from the Pull notification,
1311  // perform a manual command fetch from the server here.
1312  FetchAndPublishCommands(reason);
1313}
1314
1315void DeviceRegistrationInfo::OnDeviceDeleted(const std::string& cloud_id) {
1316  if (cloud_id != GetSettings().cloud_id) {
1317    LOG(WARNING) << "Unexpected device deletion notification for cloud ID '"
1318                 << cloud_id << "'";
1319    return;
1320  }
1321  RemoveCredentials();
1322}
1323
1324void DeviceRegistrationInfo::RemoveCredentials() {
1325  if (!HaveRegistrationCredentials())
1326    return;
1327
1328  connected_to_cloud_ = false;
1329
1330  LOG(INFO) << "Device is unregistered from the cloud. Deleting credentials";
1331  if (auth_manager_)
1332    auth_manager_->SetAuthSecret({}, RootClientTokenOwner::kNone);
1333
1334  Config::Transaction change{config_};
1335  // Keep cloud_id to switch to detect kInvalidCredentials after restart.
1336  change.set_robot_account("");
1337  change.set_refresh_token("");
1338  change.Commit();
1339
1340  current_notification_channel_ = nullptr;
1341  if (primary_notification_channel_) {
1342    primary_notification_channel_->Stop();
1343    primary_notification_channel_.reset();
1344  }
1345  if (pull_channel_) {
1346    pull_channel_->Stop();
1347    pull_channel_.reset();
1348  }
1349  notification_channel_starting_ = false;
1350  SetGcdState(GcdState::kInvalidCredentials);
1351}
1352
1353}  // namespace weave
1354