14615e0d5aa416ab1a8596bde68f71f7ebe431b86Vitaly Buka// Copyright 2015 The Weave Authors. All rights reserved.
23cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko// Use of this source code is governed by a BSD-style license that can be
33cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko// found in the LICENSE file.
43cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko
52d16dfa768282b29f3fd5a905b52e3393a083e0dStefan Sauer#include "src/device_registration_info.h"
63cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko
7f3a95bf41633f161184a584a4475ba2d663684c1Alex Vakulenko#include <algorithm>
8006e94e760088bb75f1a8174034f5715c979c6b7Christopher Wiley#include <memory>
99ea5a321746d94ed46c79633b75357f4cc77562eAlex Vakulenko#include <set>
10b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko#include <utility>
11b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko#include <vector>
12006e94e760088bb75f1a8174034f5715c979c6b7Christopher Wiley
13cd4196605e88eac2d5fe78a58aa5aa1c40ee8f77Christopher Wiley#include <base/bind.h>
146da9425a14ac345b657354e62307d17df4a721dbVitaly Buka#include <base/json/json_reader.h>
153cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko#include <base/json/json_writer.h>
16fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko#include <base/strings/string_number_conversions.h>
173c2b3038e8f76578fc95cccc7238e561bf73ed0eAlex Vakulenko#include <base/strings/stringprintf.h>
183cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko#include <base/values.h>
191e3636732171afb8cceb9e5cb835ec6a93787dbaVitaly Buka#include <weave/provider/http_client.h>
201e3636732171afb8cceb9e5cb835ec6a93787dbaVitaly Buka#include <weave/provider/network.h>
211e3636732171afb8cceb9e5cb835ec6a93787dbaVitaly Buka#include <weave/provider/task_runner.h>
223cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko
232d16dfa768282b29f3fd5a905b52e3393a083e0dStefan Sauer#include "src/bind_lambda.h"
242d16dfa768282b29f3fd5a905b52e3393a083e0dStefan Sauer#include "src/commands/cloud_command_proxy.h"
252d16dfa768282b29f3fd5a905b52e3393a083e0dStefan Sauer#include "src/commands/schema_constants.h"
262d16dfa768282b29f3fd5a905b52e3393a083e0dStefan Sauer#include "src/data_encoding.h"
272d16dfa768282b29f3fd5a905b52e3393a083e0dStefan Sauer#include "src/http_constants.h"
282d16dfa768282b29f3fd5a905b52e3393a083e0dStefan Sauer#include "src/json_error_codes.h"
292d16dfa768282b29f3fd5a905b52e3393a083e0dStefan Sauer#include "src/notification/xmpp_channel.h"
3072d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Buka#include "src/privet/auth_manager.h"
312d16dfa768282b29f3fd5a905b52e3393a083e0dStefan Sauer#include "src/string_utils.h"
322d16dfa768282b29f3fd5a905b52e3393a083e0dStefan Sauer#include "src/utils.h"
333cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko
34b6f015a1ef3caffbc2af53184c0ec5342e42e048Vitaly Bukanamespace weave {
35b6f015a1ef3caffbc2af53184c0ec5342e42e048Vitaly Buka
360a8ab9973e1fdf6b41d8f7b49529c9bd97a9071cVitaly Bukaconst char kErrorAlreayRegistered[] = "already_registered";
373cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko
388e34d391dada2fc3255fecd724f6302e7952be51Alex Vakulenkonamespace {
398e34d391dada2fc3255fecd724f6302e7952be51Alex Vakulenko
4041a90d69918c6367ba23de1edf0bad6c7175f3b9Vitaly Bukaconst int kPollingPeriodSeconds = 7;
41c1fc90c1724427d8e926172ed258e05ff63b8e26Alex Vakulenkoconst int kBackupPollingPeriodMinutes = 30;
4241a90d69918c6367ba23de1edf0bad6c7175f3b9Vitaly Buka
43e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenkonamespace fetch_reason {
44e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenko
45e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenkoconst char kDeviceStart[] = "device_start";  // Initial queue fetch at startup.
46e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenkoconst char kRegularPull[] = "regular_pull";  // Regular fetch before XMPP is up.
47e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenkoconst char kNewCommand[] = "new_command";    // A new command is available.
48e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenkoconst char kJustInCase[] = "just_in_case";   // Backup fetch when XMPP is live.
49e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenko
50e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenko}  // namespace fetch_reason
51e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenko
521e3636732171afb8cceb9e5cb835ec6a93787dbaVitaly Bukausing provider::HttpClient;
531e3636732171afb8cceb9e5cb835ec6a93787dbaVitaly Buka
540801a1f20a0c8f34130d567cd3b7dcbd2be9cb3cVitaly Bukainline void SetUnexpectedError(ErrorPtr* error) {
5548a8669ddc2e8d785aad9ad18a5abbf8f1224fdeVitaly Buka  Error::AddTo(error, FROM_HERE, "unexpected_response", "Unexpected GCD error");
56b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko}
57b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko
580801a1f20a0c8f34130d567cd3b7dcbd2be9cb3cVitaly Bukavoid ParseGCDError(const base::DictionaryValue* json, ErrorPtr* error) {
59b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko  const base::Value* list_value = nullptr;
60b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko  const base::ListValue* error_list = nullptr;
61b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko  if (!json->Get("error.errors", &list_value) ||
62b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko      !list_value->GetAsList(&error_list)) {
63b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko    SetUnexpectedError(error);
64b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko    return;
65b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko  }
66b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko
67b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko  for (size_t i = 0; i < error_list->GetSize(); i++) {
68b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko    const base::Value* error_value = nullptr;
69b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko    const base::DictionaryValue* error_object = nullptr;
70b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko    if (!error_list->Get(i, &error_value) ||
71b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko        !error_value->GetAsDictionary(&error_object)) {
72b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko      SetUnexpectedError(error);
73b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko      continue;
74b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko    }
75b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko    std::string error_code, error_message;
76b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko    if (error_object->GetString("reason", &error_code) &&
77b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko        error_object->GetString("message", &error_message)) {
7848a8669ddc2e8d785aad9ad18a5abbf8f1224fdeVitaly Buka      Error::AddTo(error, FROM_HERE, error_code, error_message);
79b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko    } else {
80b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko      SetUnexpectedError(error);
81b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko    }
82b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko  }
83b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko}
84b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko
857d55639775947b7b34b33702a886aac385f35102Vitaly Bukastd::string AppendQueryParams(const std::string& url,
867d55639775947b7b34b33702a886aac385f35102Vitaly Buka                              const WebParamList& params) {
87b001f28251931cb88831e1d83f81034ae0c407ddVitaly Buka  CHECK_EQ(std::string::npos, url.find_first_of("?#"));
88b001f28251931cb88831e1d83f81034ae0c407ddVitaly Buka  if (params.empty())
89b001f28251931cb88831e1d83f81034ae0c407ddVitaly Buka    return url;
907d55639775947b7b34b33702a886aac385f35102Vitaly Buka  return url + '?' + WebParamsEncode(params);
91b001f28251931cb88831e1d83f81034ae0c407ddVitaly Buka}
92b001f28251931cb88831e1d83f81034ae0c407ddVitaly Buka
93bda220ae043637838e0570d4eb695b4f6b818c06Alex Vakulenkostd::string BuildURL(const std::string& url,
94b001f28251931cb88831e1d83f81034ae0c407ddVitaly Buka                     const std::string& subpath,
957d55639775947b7b34b33702a886aac385f35102Vitaly Buka                     const WebParamList& params) {
96b001f28251931cb88831e1d83f81034ae0c407ddVitaly Buka  std::string result = url;
97b001f28251931cb88831e1d83f81034ae0c407ddVitaly Buka  if (!result.empty() && result.back() != '/' && !subpath.empty()) {
98b001f28251931cb88831e1d83f81034ae0c407ddVitaly Buka    CHECK_NE('/', subpath.front());
99b001f28251931cb88831e1d83f81034ae0c407ddVitaly Buka    result += '/';
100b001f28251931cb88831e1d83f81034ae0c407ddVitaly Buka  }
101b001f28251931cb88831e1d83f81034ae0c407ddVitaly Buka  result += subpath;
102b001f28251931cb88831e1d83f81034ae0c407ddVitaly Buka  return AppendQueryParams(result, params);
1033cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko}
1043cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko
105f7f52d4707c007bb9255bd80b23ac3428c6fc2e0Vitaly Bukavoid IgnoreCloudErrorWithCallback(const base::Closure& cb, ErrorPtr) {
106ba983c8d415b0cea83d0ee7a096860db8de93accChristopher Wiley  cb.Run();
107ba983c8d415b0cea83d0ee7a096860db8de93accChristopher Wiley}
108ba983c8d415b0cea83d0ee7a096860db8de93accChristopher Wiley
109747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Bukavoid IgnoreCloudError(ErrorPtr) {}
1106d2569e4260293816b8b9416d5e6993f843869b2Anton Muhin
111747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Bukavoid IgnoreCloudResult(const base::DictionaryValue&, ErrorPtr error) {}
112747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka
113747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Bukavoid IgnoreCloudResultWithCallback(const DoneCallback& cb,
114747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka                                   const base::DictionaryValue&,
115747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka                                   ErrorPtr error) {
116747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  cb.Run(std::move(error));
117ba983c8d415b0cea83d0ee7a096860db8de93accChristopher Wiley}
118ba983c8d415b0cea83d0ee7a096860db8de93accChristopher Wiley
1196da9425a14ac345b657354e62307d17df4a721dbVitaly Bukaclass RequestSender final {
1206da9425a14ac345b657354e62307d17df4a721dbVitaly Buka public:
1211a42e1466de3edd0c79215b739cd8e929ef8a7e8Vitaly Buka  RequestSender(HttpClient::Method method,
1226da9425a14ac345b657354e62307d17df4a721dbVitaly Buka                const std::string& url,
1236da9425a14ac345b657354e62307d17df4a721dbVitaly Buka                HttpClient* transport)
1246da9425a14ac345b657354e62307d17df4a721dbVitaly Buka      : method_{method}, url_{url}, transport_{transport} {}
1256da9425a14ac345b657354e62307d17df4a721dbVitaly Buka
126747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  void Send(const HttpClient::SendRequestCallback& callback) {
127866b60a19c99f6db4def7f18cc902370d344a33aVitaly Buka    static int debug_id = 0;
128866b60a19c99f6db4def7f18cc902370d344a33aVitaly Buka    ++debug_id;
1291a42e1466de3edd0c79215b739cd8e929ef8a7e8Vitaly Buka    VLOG(1) << "Sending request. id:" << debug_id
1301a42e1466de3edd0c79215b739cd8e929ef8a7e8Vitaly Buka            << " method:" << EnumToString(method_) << " url:" << url_;
131866b60a19c99f6db4def7f18cc902370d344a33aVitaly Buka    VLOG(2) << "Request data: " << data_;
132747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    auto on_done = [](
133747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka        int debug_id, const HttpClient::SendRequestCallback& callback,
134747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka        std::unique_ptr<HttpClient::Response> response, ErrorPtr error) {
135747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka      if (error) {
136747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka        VLOG(1) << "Request failed, id=" << debug_id
137747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka                << ", reason: " << error->GetCode()
138747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka                << ", message: " << error->GetMessage();
139747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka        return callback.Run({}, std::move(error));
140747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka      }
141747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka      VLOG(1) << "Request succeeded. id:" << debug_id
142747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka              << " status:" << response->GetStatusCode();
143747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka      VLOG(2) << "Response data: " << response->GetData();
144747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka      callback.Run(std::move(response), nullptr);
145866b60a19c99f6db4def7f18cc902370d344a33aVitaly Buka    };
146866b60a19c99f6db4def7f18cc902370d344a33aVitaly Buka    transport_->SendRequest(method_, url_, GetFullHeaders(), data_,
147747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka                            base::Bind(on_done, debug_id, callback));
1486da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  }
1496da9425a14ac345b657354e62307d17df4a721dbVitaly Buka
150815b603b59bebac4407322af61a822413ad421f6Vitaly Buka  void SetAccessToken(const std::string& access_token) {
151815b603b59bebac4407322af61a822413ad421f6Vitaly Buka    access_token_ = access_token;
1526da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  }
1536da9425a14ac345b657354e62307d17df4a721dbVitaly Buka
1546da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  void SetData(const std::string& data, const std::string& mime_type) {
1556da9425a14ac345b657354e62307d17df4a721dbVitaly Buka    data_ = data;
1566da9425a14ac345b657354e62307d17df4a721dbVitaly Buka    mime_type_ = mime_type;
1576da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  }
1586da9425a14ac345b657354e62307d17df4a721dbVitaly Buka
1596da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  void SetFormData(
1606da9425a14ac345b657354e62307d17df4a721dbVitaly Buka      const std::vector<std::pair<std::string, std::string>>& data) {
1617d55639775947b7b34b33702a886aac385f35102Vitaly Buka    SetData(WebParamsEncode(data), http::kWwwFormUrlEncoded);
1626da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  }
1636da9425a14ac345b657354e62307d17df4a721dbVitaly Buka
1646da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  void SetJsonData(const base::Value& json) {
1656da9425a14ac345b657354e62307d17df4a721dbVitaly Buka    std::string data;
166815b603b59bebac4407322af61a822413ad421f6Vitaly Buka    CHECK(base::JSONWriter::Write(json, &data));
167815b603b59bebac4407322af61a822413ad421f6Vitaly Buka    SetData(data, http::kJsonUtf8);
1686da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  }
1696da9425a14ac345b657354e62307d17df4a721dbVitaly Buka
1706da9425a14ac345b657354e62307d17df4a721dbVitaly Buka private:
171815b603b59bebac4407322af61a822413ad421f6Vitaly Buka  HttpClient::Headers GetFullHeaders() const {
172815b603b59bebac4407322af61a822413ad421f6Vitaly Buka    HttpClient::Headers headers;
173815b603b59bebac4407322af61a822413ad421f6Vitaly Buka    if (!access_token_.empty())
174815b603b59bebac4407322af61a822413ad421f6Vitaly Buka      headers.emplace_back(http::kAuthorization, "Bearer " + access_token_);
175815b603b59bebac4407322af61a822413ad421f6Vitaly Buka    if (!mime_type_.empty())
176815b603b59bebac4407322af61a822413ad421f6Vitaly Buka      headers.emplace_back(http::kContentType, mime_type_);
177815b603b59bebac4407322af61a822413ad421f6Vitaly Buka    return headers;
178815b603b59bebac4407322af61a822413ad421f6Vitaly Buka  }
179815b603b59bebac4407322af61a822413ad421f6Vitaly Buka
1801a42e1466de3edd0c79215b739cd8e929ef8a7e8Vitaly Buka  HttpClient::Method method_;
1816da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  std::string url_;
1826da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  std::string data_;
1836da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  std::string mime_type_;
184815b603b59bebac4407322af61a822413ad421f6Vitaly Buka  std::string access_token_;
1856da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  HttpClient* transport_{nullptr};
1866da9425a14ac345b657354e62307d17df4a721dbVitaly Buka
1876da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  DISALLOW_COPY_AND_ASSIGN(RequestSender);
1886da9425a14ac345b657354e62307d17df4a721dbVitaly Buka};
1896da9425a14ac345b657354e62307d17df4a721dbVitaly Buka
1906da9425a14ac345b657354e62307d17df4a721dbVitaly Bukastd::unique_ptr<base::DictionaryValue> ParseJsonResponse(
1916da9425a14ac345b657354e62307d17df4a721dbVitaly Buka    const HttpClient::Response& response,
1920801a1f20a0c8f34130d567cd3b7dcbd2be9cb3cVitaly Buka    ErrorPtr* error) {
1936da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  // Make sure we have a correct content type. Do not try to parse
1946da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  // binary files, or HTML output. Limit to application/json and text/plain.
19524d6fd54e9b10ae13e09ca91c726a994853def0dVitaly Buka  std::string content_type =
19624d6fd54e9b10ae13e09ca91c726a994853def0dVitaly Buka      SplitAtFirst(response.GetContentType(), ";", true).first;
1971da659947e800069a6352dbe7f829dfdf1697fedVitaly Buka
1981da659947e800069a6352dbe7f829dfdf1697fedVitaly Buka  if (content_type != http::kJson && content_type != http::kPlain) {
1990dbbf605efb8f72b3c2c15c14e613323fc2ac0a2Vitaly Buka    return Error::AddTo(
20048a8669ddc2e8d785aad9ad18a5abbf8f1224fdeVitaly Buka        error, FROM_HERE, "non_json_content_type",
201c27390d203688b847378944d5ae0ec8f1938c598Vitaly Buka        "Unexpected content type: \'" + response.GetContentType() + "\'");
2026da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  }
2036da9425a14ac345b657354e62307d17df4a721dbVitaly Buka
2046da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  const std::string& json = response.GetData();
2056da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  std::string error_message;
2066da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  auto value = base::JSONReader::ReadAndReturnError(json, base::JSON_PARSE_RFC,
2076da9425a14ac345b657354e62307d17df4a721dbVitaly Buka                                                    nullptr, &error_message);
2086da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  if (!value) {
20948a8669ddc2e8d785aad9ad18a5abbf8f1224fdeVitaly Buka    Error::AddToPrintf(error, FROM_HERE, errors::json::kParseError,
2100801a1f20a0c8f34130d567cd3b7dcbd2be9cb3cVitaly Buka                       "Error '%s' occurred parsing JSON string '%s'",
2110801a1f20a0c8f34130d567cd3b7dcbd2be9cb3cVitaly Buka                       error_message.c_str(), json.c_str());
2126da9425a14ac345b657354e62307d17df4a721dbVitaly Buka    return std::unique_ptr<base::DictionaryValue>();
2136da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  }
2146da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  base::DictionaryValue* dict_value = nullptr;
2156da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  if (!value->GetAsDictionary(&dict_value)) {
21648a8669ddc2e8d785aad9ad18a5abbf8f1224fdeVitaly Buka    Error::AddToPrintf(error, FROM_HERE, errors::json::kObjectExpected,
21748a8669ddc2e8d785aad9ad18a5abbf8f1224fdeVitaly Buka                       "Response is not a valid JSON object: '%s'",
21848a8669ddc2e8d785aad9ad18a5abbf8f1224fdeVitaly Buka                       json.c_str());
2196da9425a14ac345b657354e62307d17df4a721dbVitaly Buka    return std::unique_ptr<base::DictionaryValue>();
2206da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  } else {
2216da9425a14ac345b657354e62307d17df4a721dbVitaly Buka    // |value| is now owned by |dict_value|, so release the scoped_ptr now.
2226da9425a14ac345b657354e62307d17df4a721dbVitaly Buka    base::IgnoreResult(value.release());
2236da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  }
2246da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  return std::unique_ptr<base::DictionaryValue>(dict_value);
2256da9425a14ac345b657354e62307d17df4a721dbVitaly Buka}
2266da9425a14ac345b657354e62307d17df4a721dbVitaly Buka
2276da9425a14ac345b657354e62307d17df4a721dbVitaly Bukabool IsSuccessful(const HttpClient::Response& response) {
2286da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  int code = response.GetStatusCode();
2291da659947e800069a6352dbe7f829dfdf1697fedVitaly Buka  return code >= http::kContinue && code < http::kBadRequest;
2306da9425a14ac345b657354e62307d17df4a721dbVitaly Buka}
2316da9425a14ac345b657354e62307d17df4a721dbVitaly Buka
2328e34d391dada2fc3255fecd724f6302e7952be51Alex Vakulenko}  // anonymous namespace
2333cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko
2341f30a622b607def02ff9558f8baf8a9b919eee91Alex VakulenkoDeviceRegistrationInfo::DeviceRegistrationInfo(
235666b43e92729fa170bc53eab9040a4dfe58b8062Vitaly Buka    Config* config,
236d91d625f29b752be035c5fb49bb29d7ee85fcb90Alex Vakulenko    ComponentManager* component_manager,
2371e3636732171afb8cceb9e5cb835ec6a93787dbaVitaly Buka    provider::TaskRunner* task_runner,
2381e3636732171afb8cceb9e5cb835ec6a93787dbaVitaly Buka    provider::HttpClient* http_client,
2393d851b43ae4a779b507b253dd920c6c33497722fVitaly Buka    provider::Network* network,
2403d851b43ae4a779b507b253dd920c6c33497722fVitaly Buka    privet::AuthManager* auth_manager)
2411020618923f7bf3066c8d36bbfe0a855eb58a974Vitaly Buka    : http_client_{http_client},
242a56a7e630a25e66118f0cba2678d58b3c20cbdacAlex Vakulenko      task_runner_{task_runner},
243666b43e92729fa170bc53eab9040a4dfe58b8062Vitaly Buka      config_{config},
244d91d625f29b752be035c5fb49bb29d7ee85fcb90Alex Vakulenko      component_manager_{component_manager},
2453d851b43ae4a779b507b253dd920c6c33497722fVitaly Buka      network_{network},
2463d851b43ae4a779b507b253dd920c6c33497722fVitaly Buka      auth_manager_{auth_manager} {
2470f80f7c281a0363c2e1e7302f938291b968aea87Vitaly Buka  cloud_backoff_policy_.reset(new BackoffEntry::Policy{});
248266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko  cloud_backoff_policy_->num_errors_to_ignore = 0;
249f61ee017872aa93e53d23fdd79ab16c6fac98c0cAlex Vakulenko  cloud_backoff_policy_->initial_delay_ms = 1000;
250266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko  cloud_backoff_policy_->multiply_factor = 2.0;
251266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko  cloud_backoff_policy_->jitter_factor = 0.1;
252266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko  cloud_backoff_policy_->maximum_backoff_ms = 30000;
253266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko  cloud_backoff_policy_->entry_lifetime_ms = -1;
254266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko  cloud_backoff_policy_->always_use_initial_delay = false;
2550f80f7c281a0363c2e1e7302f938291b968aea87Vitaly Buka  cloud_backoff_entry_.reset(new BackoffEntry{cloud_backoff_policy_.get()});
2560f80f7c281a0363c2e1e7302f938291b968aea87Vitaly Buka  oauth2_backoff_entry_.reset(new BackoffEntry{cloud_backoff_policy_.get()});
257266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko
258672634b8b0ed31891fb48e02dce75b6aead0be27Vitaly Buka  bool revoked =
259672634b8b0ed31891fb48e02dce75b6aead0be27Vitaly Buka      !GetSettings().cloud_id.empty() && !HaveRegistrationCredentials();
260672634b8b0ed31891fb48e02dce75b6aead0be27Vitaly Buka  gcd_state_ =
261672634b8b0ed31891fb48e02dce75b6aead0be27Vitaly Buka      revoked ? GcdState::kInvalidCredentials : GcdState::kUnconfigured;
262672634b8b0ed31891fb48e02dce75b6aead0be27Vitaly Buka
26334668e731bb194b443bc0e6029d6d3583f08de28Vitaly Buka  component_manager_->AddTraitDefChangedCallback(base::Bind(
26434668e731bb194b443bc0e6029d6d3583f08de28Vitaly Buka      &DeviceRegistrationInfo::OnTraitDefsChanged, weak_factory_.GetWeakPtr()));
26534668e731bb194b443bc0e6029d6d3583f08de28Vitaly Buka  component_manager_->AddComponentTreeChangedCallback(
26634668e731bb194b443bc0e6029d6d3583f08de28Vitaly Buka      base::Bind(&DeviceRegistrationInfo::OnComponentTreeChanged,
2679ea5a321746d94ed46c79633b75357f4cc77562eAlex Vakulenko                 weak_factory_.GetWeakPtr()));
268d91d625f29b752be035c5fb49bb29d7ee85fcb90Alex Vakulenko  component_manager_->AddStateChangedCallback(base::Bind(
269a647c857f3098b366b379bde308d051f6a9aac2fVitaly Buka      &DeviceRegistrationInfo::OnStateChanged, weak_factory_.GetWeakPtr()));
270a3062c51e29efac97c51272b2b7d8b6b9fd593c1Alex Vakulenko}
2713cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko
272332df1989dfbba0ba339ef6377f453984efcc4a7Anton MuhinDeviceRegistrationInfo::~DeviceRegistrationInfo() = default;
273332df1989dfbba0ba339ef6377f453984efcc4a7Anton Muhin
2743cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenkostd::string DeviceRegistrationInfo::GetServiceURL(
275b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko    const std::string& subpath,
2767d55639775947b7b34b33702a886aac385f35102Vitaly Buka    const WebParamList& params) const {
2775cf12a3e1f9e8e01f9fdb5322277c34096afde8bVitaly Buka  return BuildURL(GetSettings().service_url, subpath, params);
2783cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko}
2793cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko
2803cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenkostd::string DeviceRegistrationInfo::GetDeviceURL(
281b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko    const std::string& subpath,
2827d55639775947b7b34b33702a886aac385f35102Vitaly Buka    const WebParamList& params) const {
283312c2f5d0d5e2ef0cc7acc6dce5137094332c276Johan Euphrosine  CHECK(!GetSettings().cloud_id.empty()) << "Must have a valid device ID";
2845cf12a3e1f9e8e01f9fdb5322277c34096afde8bVitaly Buka  return BuildURL(GetSettings().service_url,
285312c2f5d0d5e2ef0cc7acc6dce5137094332c276Johan Euphrosine                  "devices/" + GetSettings().cloud_id + "/" + subpath, params);
2863cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko}
2873cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko
288b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenkostd::string DeviceRegistrationInfo::GetOAuthURL(
289b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko    const std::string& subpath,
2907d55639775947b7b34b33702a886aac385f35102Vitaly Buka    const WebParamList& params) const {
2915cf12a3e1f9e8e01f9fdb5322277c34096afde8bVitaly Buka  return BuildURL(GetSettings().oauth_url, subpath, params);
2923cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko}
2933cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko
294ee7a3af2e8ab08e0a71a407f2bfa8d3ca78b0f79Vitaly Bukavoid DeviceRegistrationInfo::Start() {
2953fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko  if (HaveRegistrationCredentials()) {
296d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko    StartNotificationChannel();
297ba983c8d415b0cea83d0ee7a096860db8de93accChristopher Wiley    // Wait a significant amount of time for local daemons to publish their
298ba983c8d415b0cea83d0ee7a096860db8de93accChristopher Wiley    // state to Buffet before publishing it to the cloud.
299ba983c8d415b0cea83d0ee7a096860db8de93accChristopher Wiley    // TODO(wiley) We could do a lot of things here to either expose this
300ba983c8d415b0cea83d0ee7a096860db8de93accChristopher Wiley    //             timeout as a configurable knob or allow local
301ba983c8d415b0cea83d0ee7a096860db8de93accChristopher Wiley    //             daemons to signal that their state is up to date so that
302ba983c8d415b0cea83d0ee7a096860db8de93accChristopher Wiley    //             we need not wait for them.
303fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko    ScheduleCloudConnection(base::TimeDelta::FromSeconds(5));
304ba983c8d415b0cea83d0ee7a096860db8de93accChristopher Wiley  }
3053cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko}
3063cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko
307fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenkovoid DeviceRegistrationInfo::ScheduleCloudConnection(
308fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko    const base::TimeDelta& delay) {
309c3c6dab99bb333417756817d154cd99ae71f4668Vitaly Buka  SetGcdState(GcdState::kConnecting);
310a56a7e630a25e66118f0cba2678d58b3c20cbdacAlex Vakulenko  if (!task_runner_)
3110f6b2eccc9ca4aa35cf0ee1e3ac7bc6edc38f0a4Vitaly Buka    return;  // Assume we're in test
312a56a7e630a25e66118f0cba2678d58b3c20cbdacAlex Vakulenko  task_runner_->PostDelayedTask(
313fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko      FROM_HERE,
314747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka      base::Bind(&DeviceRegistrationInfo::ConnectToCloud, AsWeakPtr(), nullptr),
315747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka      delay);
316cd4196605e88eac2d5fe78a58aa5aa1c40ee8f77Christopher Wiley}
317cd4196605e88eac2d5fe78a58aa5aa1c40ee8f77Christopher Wiley
3183fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenkobool DeviceRegistrationInfo::HaveRegistrationCredentials() const {
3195cf12a3e1f9e8e01f9fdb5322277c34096afde8bVitaly Buka  return !GetSettings().refresh_token.empty() &&
320312c2f5d0d5e2ef0cc7acc6dce5137094332c276Johan Euphrosine         !GetSettings().cloud_id.empty() &&
3215cf12a3e1f9e8e01f9fdb5322277c34096afde8bVitaly Buka         !GetSettings().robot_account.empty();
3223fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko}
3233fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko
3243fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenkobool DeviceRegistrationInfo::VerifyRegistrationCredentials(
3250801a1f20a0c8f34130d567cd3b7dcbd2be9cb3cVitaly Buka    ErrorPtr* error) const {
3263fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko  const bool have_credentials = HaveRegistrationCredentials();
327c900e48b8d482b1462a4d262856e344d22bcc6a5Christopher Wiley
328ed77a57d86bab20ff668fc00503dfa771561781bAlex Vakulenko  VLOG(2) << "Device registration record "
329c900e48b8d482b1462a4d262856e344d22bcc6a5Christopher Wiley          << ((have_credentials) ? "found" : "not found.");
3300dbbf605efb8f72b3c2c15c14e613323fc2ac0a2Vitaly Buka  if (!have_credentials) {
3310dbbf605efb8f72b3c2c15c14e613323fc2ac0a2Vitaly Buka    return Error::AddTo(error, FROM_HERE, "device_not_registered",
3320dbbf605efb8f72b3c2c15c14e613323fc2ac0a2Vitaly Buka                        "No valid device registration record found");
3330dbbf605efb8f72b3c2c15c14e613323fc2ac0a2Vitaly Buka  }
3340dbbf605efb8f72b3c2c15c14e613323fc2ac0a2Vitaly Buka  return true;
3353cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko}
3363cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko
33724d189fbf5b5ee787831f9905a30bf7383453322Nathan Bullockstd::unique_ptr<base::DictionaryValue>
3386da9425a14ac345b657354e62307d17df4a721dbVitaly BukaDeviceRegistrationInfo::ParseOAuthResponse(const HttpClient::Response& response,
3390801a1f20a0c8f34130d567cd3b7dcbd2be9cb3cVitaly Buka                                           ErrorPtr* error) {
3406da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  int code = response.GetStatusCode();
3416da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  auto resp = ParseJsonResponse(response, error);
3421da659947e800069a6352dbe7f829dfdf1697fedVitaly Buka  if (resp && code >= http::kBadRequest) {
34324d189fbf5b5ee787831f9905a30bf7383453322Nathan Bullock    std::string error_code, error_message;
34424d189fbf5b5ee787831f9905a30bf7383453322Nathan Bullock    if (!resp->GetString("error", &error_code)) {
34524d189fbf5b5ee787831f9905a30bf7383453322Nathan Bullock      error_code = "unexpected_response";
34624d189fbf5b5ee787831f9905a30bf7383453322Nathan Bullock    }
34724d189fbf5b5ee787831f9905a30bf7383453322Nathan Bullock    if (error_code == "invalid_grant") {
34824d189fbf5b5ee787831f9905a30bf7383453322Nathan Bullock      LOG(INFO) << "The device's registration has been revoked.";
349c3c6dab99bb333417756817d154cd99ae71f4668Vitaly Buka      SetGcdState(GcdState::kInvalidCredentials);
35024d189fbf5b5ee787831f9905a30bf7383453322Nathan Bullock    }
35124d189fbf5b5ee787831f9905a30bf7383453322Nathan Bullock    // I have never actually seen an error_description returned.
35224d189fbf5b5ee787831f9905a30bf7383453322Nathan Bullock    if (!resp->GetString("error_description", &error_message)) {
35324d189fbf5b5ee787831f9905a30bf7383453322Nathan Bullock      error_message = "Unexpected OAuth error";
35424d189fbf5b5ee787831f9905a30bf7383453322Nathan Bullock    }
3550dbbf605efb8f72b3c2c15c14e613323fc2ac0a2Vitaly Buka    return Error::AddTo(error, FROM_HERE, error_code, error_message);
35624d189fbf5b5ee787831f9905a30bf7383453322Nathan Bullock  }
35724d189fbf5b5ee787831f9905a30bf7383453322Nathan Bullock  return resp;
35824d189fbf5b5ee787831f9905a30bf7383453322Nathan Bullock}
35924d189fbf5b5ee787831f9905a30bf7383453322Nathan Bullock
360747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Bukavoid DeviceRegistrationInfo::RefreshAccessToken(const DoneCallback& callback) {
361390d1911a68b765eef645026756f7290bacb2e15David Zeuthen  LOG(INFO) << "Refreshing access token.";
3623fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko
3630801a1f20a0c8f34130d567cd3b7dcbd2be9cb3cVitaly Buka  ErrorPtr error;
364747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  if (!VerifyRegistrationCredentials(&error))
365747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    return callback.Run(std::move(error));
3663fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko
3670f91be869e37c5961fb40e686a5d0795ba39bd44Alex Vakulenko  if (oauth2_backoff_entry_->ShouldRejectRequest()) {
3680f91be869e37c5961fb40e686a5d0795ba39bd44Alex Vakulenko    VLOG(1) << "RefreshToken request delayed for "
3690f91be869e37c5961fb40e686a5d0795ba39bd44Alex Vakulenko            << oauth2_backoff_entry_->GetTimeUntilRelease()
3700f91be869e37c5961fb40e686a5d0795ba39bd44Alex Vakulenko            << " due to backoff policy";
3710f91be869e37c5961fb40e686a5d0795ba39bd44Alex Vakulenko    task_runner_->PostDelayedTask(
3720f91be869e37c5961fb40e686a5d0795ba39bd44Alex Vakulenko        FROM_HERE, base::Bind(&DeviceRegistrationInfo::RefreshAccessToken,
373747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka                              AsWeakPtr(), callback),
3740f91be869e37c5961fb40e686a5d0795ba39bd44Alex Vakulenko        oauth2_backoff_entry_->GetTimeUntilRelease());
3750f91be869e37c5961fb40e686a5d0795ba39bd44Alex Vakulenko    return;
3760f91be869e37c5961fb40e686a5d0795ba39bd44Alex Vakulenko  }
3770f91be869e37c5961fb40e686a5d0795ba39bd44Alex Vakulenko
3781a42e1466de3edd0c79215b739cd8e929ef8a7e8Vitaly Buka  RequestSender sender{HttpClient::Method::kPost, GetOAuthURL("token"),
3791a42e1466de3edd0c79215b739cd8e929ef8a7e8Vitaly Buka                       http_client_};
3806da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  sender.SetFormData({
3815cf12a3e1f9e8e01f9fdb5322277c34096afde8bVitaly Buka      {"refresh_token", GetSettings().refresh_token},
3825cf12a3e1f9e8e01f9fdb5322277c34096afde8bVitaly Buka      {"client_id", GetSettings().client_id},
3835cf12a3e1f9e8e01f9fdb5322277c34096afde8bVitaly Buka      {"client_secret", GetSettings().client_secret},
384a647c857f3098b366b379bde308d051f6a9aac2fVitaly Buka      {"grant_type", "refresh_token"},
3856da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  });
386747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  sender.Send(base::Bind(&DeviceRegistrationInfo::OnRefreshAccessTokenDone,
387747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka                         weak_factory_.GetWeakPtr(), callback));
388866b60a19c99f6db4def7f18cc902370d344a33aVitaly Buka  VLOG(1) << "Refresh access token request dispatched";
389266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko}
390266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko
391747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Bukavoid DeviceRegistrationInfo::OnRefreshAccessTokenDone(
392747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    const DoneCallback& callback,
393747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    std::unique_ptr<HttpClient::Response> response,
394747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    ErrorPtr error) {
395747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  if (error) {
396747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    VLOG(1) << "Refresh access token failed";
397747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    oauth2_backoff_entry_->InformOfRequest(false);
398747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    return RefreshAccessToken(callback);
399747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  }
400866b60a19c99f6db4def7f18cc902370d344a33aVitaly Buka  VLOG(1) << "Refresh access token request completed";
4010f91be869e37c5961fb40e686a5d0795ba39bd44Alex Vakulenko  oauth2_backoff_entry_->InformOfRequest(true);
402747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  auto json = ParseOAuthResponse(*response, &error);
403f7f52d4707c007bb9255bd80b23ac3428c6fc2e0Vitaly Buka  if (!json)
404747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    return callback.Run(std::move(error));
4053cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko
4063cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko  int expires_in = 0;
4073cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko  if (!json->GetString("access_token", &access_token_) ||
408a647c857f3098b366b379bde308d051f6a9aac2fVitaly Buka      !json->GetInteger("expires_in", &expires_in) || access_token_.empty() ||
4093cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko      expires_in <= 0) {
4103cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko    LOG(ERROR) << "Access token unavailable.";
41148a8669ddc2e8d785aad9ad18a5abbf8f1224fdeVitaly Buka    Error::AddTo(&error, FROM_HERE, "unexpected_server_response",
41248a8669ddc2e8d785aad9ad18a5abbf8f1224fdeVitaly Buka                 "Access token unavailable");
413747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    return callback.Run(std::move(error));
4143cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko  }
415a647c857f3098b366b379bde308d051f6a9aac2fVitaly Buka  access_token_expiration_ =
416a647c857f3098b366b379bde308d051f6a9aac2fVitaly Buka      base::Time::Now() + base::TimeDelta::FromSeconds(expires_in);
4173cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko  LOG(INFO) << "Access token is refreshed for additional " << expires_in
4183cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko            << " seconds.";
419d9e0bcd8e1a1cd5983c30673365acf60021ebb95Nathan Bullock
4206b028ae747f94bd9201f71690a9aa7c0ad585f5fAlex Vakulenko  if (primary_notification_channel_ &&
4216b028ae747f94bd9201f71690a9aa7c0ad585f5fAlex Vakulenko      !primary_notification_channel_->IsConnected()) {
4226b028ae747f94bd9201f71690a9aa7c0ad585f5fAlex Vakulenko    // If we have disconnected channel, it is due to failed credentials.
4236b028ae747f94bd9201f71690a9aa7c0ad585f5fAlex Vakulenko    // Now that we have a new access token, retry the connection.
4246b028ae747f94bd9201f71690a9aa7c0ad585f5fAlex Vakulenko    StartNotificationChannel();
4256b028ae747f94bd9201f71690a9aa7c0ad585f5fAlex Vakulenko  }
42672d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Buka
42772d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Buka  SendAuthInfo();
42872d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Buka
429747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  callback.Run(nullptr);
4306b028ae747f94bd9201f71690a9aa7c0ad585f5fAlex Vakulenko}
4316b028ae747f94bd9201f71690a9aa7c0ad585f5fAlex Vakulenko
432eedf3be451ea83ce21aa22294c9605f15bd3eebaAlex Vakulenkovoid DeviceRegistrationInfo::StartNotificationChannel() {
4336b028ae747f94bd9201f71690a9aa7c0ad585f5fAlex Vakulenko  if (notification_channel_starting_)
4346b028ae747f94bd9201f71690a9aa7c0ad585f5fAlex Vakulenko    return;
4356b028ae747f94bd9201f71690a9aa7c0ad585f5fAlex Vakulenko
4363fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko  LOG(INFO) << "Starting notification channel";
4373fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko
4380f6b2eccc9ca4aa35cf0ee1e3ac7bc6edc38f0a4Vitaly Buka  // If no TaskRunner assume we're in test.
439ff1d1862455e0e1aea2dffedf505eea5fdce2c6cVitaly Buka  if (!network_) {
440ff1d1862455e0e1aea2dffedf505eea5fdce2c6cVitaly Buka    LOG(INFO) << "No Network, not starting notification channel";
441bea91132a204b583d51e50181dbd14b761df040fNathan Bullock    return;
442d9e0bcd8e1a1cd5983c30673365acf60021ebb95Nathan Bullock  }
443d9e0bcd8e1a1cd5983c30673365acf60021ebb95Nathan Bullock
444d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko  if (primary_notification_channel_) {
44526f557b524b63b72e4828e52f923d09ed184fffdAlex Vakulenko    primary_notification_channel_->Stop();
446d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko    primary_notification_channel_.reset();
447d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko    current_notification_channel_ = nullptr;
448d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko  }
449d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko
450d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko  // Start with just regular polling at the pre-configured polling interval.
451d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko  // Once the primary notification channel is connected successfully, it will
452d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko  // call back to OnConnected() and at that time we'll switch to use the
453d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko  // primary channel and switch periodic poll into much more infrequent backup
454d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko  // poll mode.
45541a90d69918c6367ba23de1edf0bad6c7175f3b9Vitaly Buka  const base::TimeDelta pull_interval =
45641a90d69918c6367ba23de1edf0bad6c7175f3b9Vitaly Buka      base::TimeDelta::FromSeconds(kPollingPeriodSeconds);
457d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko  if (!pull_channel_) {
458a56a7e630a25e66118f0cba2678d58b3c20cbdacAlex Vakulenko    pull_channel_.reset(new PullChannel{pull_interval, task_runner_});
459d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko    pull_channel_->Start(this);
460d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko  } else {
461d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko    pull_channel_->UpdatePullInterval(pull_interval);
462d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko  }
463d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko  current_notification_channel_ = pull_channel_.get();
464d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko
4656b028ae747f94bd9201f71690a9aa7c0ad585f5fAlex Vakulenko  notification_channel_starting_ = true;
4663b8fbc546262ac5335e9ddfd219c195b224a4427Vitaly Buka  primary_notification_channel_.reset(
4673b8fbc546262ac5335e9ddfd219c195b224a4427Vitaly Buka      new XmppChannel{GetSettings().robot_account, access_token_,
4683b8fbc546262ac5335e9ddfd219c195b224a4427Vitaly Buka                      GetSettings().xmpp_endpoint, task_runner_, network_});
469eedf3be451ea83ce21aa22294c9605f15bd3eebaAlex Vakulenko  primary_notification_channel_->Start(this);
470f12f7f005e90f60b56e8e4612575b850c57ee712Nathan Bullock}
471f12f7f005e90f60b56e8e4612575b850c57ee712Nathan Bullock
472c3c6dab99bb333417756817d154cd99ae71f4668Vitaly Bukavoid DeviceRegistrationInfo::AddGcdStateChangedCallback(
473c3c6dab99bb333417756817d154cd99ae71f4668Vitaly Buka    const Device::GcdStateChangedCallback& callback) {
474c3c6dab99bb333417756817d154cd99ae71f4668Vitaly Buka  gcd_state_changed_callbacks_.push_back(callback);
475c3c6dab99bb333417756817d154cd99ae71f4668Vitaly Buka  callback.Run(gcd_state_);
476ee7a3af2e8ab08e0a71a407f2bfa8d3ca78b0f79Vitaly Buka}
477ee7a3af2e8ab08e0a71a407f2bfa8d3ca78b0f79Vitaly Buka
478d8d3216bc2fb413e8100ff0b17c53f275300534cAnton Muhinstd::unique_ptr<base::DictionaryValue>
4791a108717d3d6ac17974b835dc18f6c177fb609ffVitaly BukaDeviceRegistrationInfo::BuildDeviceResource() const {
480d8d3216bc2fb413e8100ff0b17c53f275300534cAnton Muhin  std::unique_ptr<base::DictionaryValue> resource{new base::DictionaryValue};
481312c2f5d0d5e2ef0cc7acc6dce5137094332c276Johan Euphrosine  if (!GetSettings().cloud_id.empty())
482312c2f5d0d5e2ef0cc7acc6dce5137094332c276Johan Euphrosine    resource->SetString("id", GetSettings().cloud_id);
4835cf12a3e1f9e8e01f9fdb5322277c34096afde8bVitaly Buka  resource->SetString("name", GetSettings().name);
4845cf12a3e1f9e8e01f9fdb5322277c34096afde8bVitaly Buka  if (!GetSettings().description.empty())
4855cf12a3e1f9e8e01f9fdb5322277c34096afde8bVitaly Buka    resource->SetString("description", GetSettings().description);
4865cf12a3e1f9e8e01f9fdb5322277c34096afde8bVitaly Buka  if (!GetSettings().location.empty())
4875cf12a3e1f9e8e01f9fdb5322277c34096afde8bVitaly Buka    resource->SetString("location", GetSettings().location);
4885cf12a3e1f9e8e01f9fdb5322277c34096afde8bVitaly Buka  resource->SetString("modelManifestId", GetSettings().model_id);
489eedf3be451ea83ce21aa22294c9605f15bd3eebaAlex Vakulenko  std::unique_ptr<base::DictionaryValue> channel{new base::DictionaryValue};
490d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko  if (current_notification_channel_) {
491eedf3be451ea83ce21aa22294c9605f15bd3eebaAlex Vakulenko    channel->SetString("supportedType",
492d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko                       current_notification_channel_->GetName());
493d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko    current_notification_channel_->AddChannelParameters(channel.get());
494eedf3be451ea83ce21aa22294c9605f15bd3eebaAlex Vakulenko  } else {
495d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko    channel->SetString("supportedType", "pull");
496eedf3be451ea83ce21aa22294c9605f15bd3eebaAlex Vakulenko  }
497eedf3be451ea83ce21aa22294c9605f15bd3eebaAlex Vakulenko  resource->Set("channel", channel.release());
498d91d625f29b752be035c5fb49bb29d7ee85fcb90Alex Vakulenko  resource->Set("traits", component_manager_->GetTraits().DeepCopy());
499d91d625f29b752be035c5fb49bb29d7ee85fcb90Alex Vakulenko  resource->Set("components", component_manager_->GetComponents().DeepCopy());
500d91d625f29b752be035c5fb49bb29d7ee85fcb90Alex Vakulenko
501d8d3216bc2fb413e8100ff0b17c53f275300534cAnton Muhin  return resource;
502d8d3216bc2fb413e8100ff0b17c53f275300534cAnton Muhin}
503d8d3216bc2fb413e8100ff0b17c53f275300534cAnton Muhin
504266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenkovoid DeviceRegistrationInfo::GetDeviceInfo(
505747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    const CloudRequestDoneCallback& callback) {
50611b2f2382db08ce26f4c4875a4e2d77586165720Vitaly Buka  ErrorPtr error;
507672634b8b0ed31891fb48e02dce75b6aead0be27Vitaly Buka  if (!VerifyRegistrationCredentials(&error))
508747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    return callback.Run({}, std::move(error));
509747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  DoCloudRequest(HttpClient::Method::kGet, GetDeviceURL(), nullptr, callback);
5103cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko}
5113cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko
512747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Bukavoid DeviceRegistrationInfo::RegisterDeviceError(const DoneCallback& callback,
513747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka                                                 ErrorPtr error) {
514747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  task_runner_->PostDelayedTask(FROM_HERE,
515747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka                                base::Bind(callback, base::Passed(&error)), {});
5164774df2734a2b74f16047983751673388b6cb5b5Vitaly Buka}
5174774df2734a2b74f16047983751673388b6cb5b5Vitaly Buka
518747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Bukavoid DeviceRegistrationInfo::RegisterDevice(const std::string& ticket_id,
519747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka                                            const DoneCallback& callback) {
5200a8ab9973e1fdf6b41d8f7b49529c9bd97a9071cVitaly Buka  if (HaveRegistrationCredentials()) {
5210a8ab9973e1fdf6b41d8f7b49529c9bd97a9071cVitaly Buka    ErrorPtr error;
52248a8669ddc2e8d785aad9ad18a5abbf8f1224fdeVitaly Buka    Error::AddTo(&error, FROM_HERE, kErrorAlreayRegistered,
5230a8ab9973e1fdf6b41d8f7b49529c9bd97a9071cVitaly Buka                 "Unable to register already registered device");
5240a8ab9973e1fdf6b41d8f7b49529c9bd97a9071cVitaly Buka    return RegisterDeviceError(callback, std::move(error));
5250a8ab9973e1fdf6b41d8f7b49529c9bd97a9071cVitaly Buka  }
5260a8ab9973e1fdf6b41d8f7b49529c9bd97a9071cVitaly Buka
5271a108717d3d6ac17974b835dc18f6c177fb609ffVitaly Buka  std::unique_ptr<base::DictionaryValue> device_draft = BuildDeviceResource();
5281a108717d3d6ac17974b835dc18f6c177fb609ffVitaly Buka  CHECK(device_draft);
52907216fe6005df7ab402426485f2823905c6a19d7Alex Vakulenko
5303cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko  base::DictionaryValue req_json;
531e440848385ed31861a509181c063f7f8854d7559Nathan Bullock  req_json.SetString("id", ticket_id);
5325cf12a3e1f9e8e01f9fdb5322277c34096afde8bVitaly Buka  req_json.SetString("oauthClientId", GetSettings().client_id);
533d8d3216bc2fb413e8100ff0b17c53f275300534cAnton Muhin  req_json.Set("deviceDraft", device_draft.release());
5343cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko
535e440848385ed31861a509181c063f7f8854d7559Nathan Bullock  auto url = GetServiceURL("registrationTickets/" + ticket_id,
5365cf12a3e1f9e8e01f9fdb5322277c34096afde8bVitaly Buka                           {{"key", GetSettings().api_key}});
5376da9425a14ac345b657354e62307d17df4a721dbVitaly Buka
5381a42e1466de3edd0c79215b739cd8e929ef8a7e8Vitaly Buka  RequestSender sender{HttpClient::Method::kPatch, url, http_client_};
5396da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  sender.SetJsonData(req_json);
5404774df2734a2b74f16047983751673388b6cb5b5Vitaly Buka  sender.Send(base::Bind(&DeviceRegistrationInfo::RegisterDeviceOnTicketSent,
541747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka                         weak_factory_.GetWeakPtr(), ticket_id, callback));
5424774df2734a2b74f16047983751673388b6cb5b5Vitaly Buka}
5436da9425a14ac345b657354e62307d17df4a721dbVitaly Buka
5444774df2734a2b74f16047983751673388b6cb5b5Vitaly Bukavoid DeviceRegistrationInfo::RegisterDeviceOnTicketSent(
5454774df2734a2b74f16047983751673388b6cb5b5Vitaly Buka    const std::string& ticket_id,
546747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    const DoneCallback& callback,
547747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    std::unique_ptr<provider::HttpClient::Response> response,
548747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    ErrorPtr error) {
549747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  if (error)
550747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    return RegisterDeviceError(callback, std::move(error));
551747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  auto json_resp = ParseJsonResponse(*response, &error);
552532ff7e5174da55010d31cdc4c6a76f52e3bd1aeAnton Muhin  if (!json_resp)
553747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    return RegisterDeviceError(callback, std::move(error));
5544774df2734a2b74f16047983751673388b6cb5b5Vitaly Buka
555747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  if (!IsSuccessful(*response)) {
55612870bd07dd3c8f44528638a9c290b72e22d32d2Vitaly Buka    ParseGCDError(json_resp.get(), &error);
557747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    return RegisterDeviceError(callback, std::move(error));
5581dbad47c63dceb5a7ad49871fc256e3b1179511cDavid Zeuthen  }
5593cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko
5604774df2734a2b74f16047983751673388b6cb5b5Vitaly Buka  std::string url =
5614774df2734a2b74f16047983751673388b6cb5b5Vitaly Buka      GetServiceURL("registrationTickets/" + ticket_id + "/finalize",
5624774df2734a2b74f16047983751673388b6cb5b5Vitaly Buka                    {{"key", GetSettings().api_key}});
5631a42e1466de3edd0c79215b739cd8e929ef8a7e8Vitaly Buka  RequestSender{HttpClient::Method::kPost, url, http_client_}.Send(
5644774df2734a2b74f16047983751673388b6cb5b5Vitaly Buka      base::Bind(&DeviceRegistrationInfo::RegisterDeviceOnTicketFinalized,
565747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka                 weak_factory_.GetWeakPtr(), callback));
5664774df2734a2b74f16047983751673388b6cb5b5Vitaly Buka}
5674774df2734a2b74f16047983751673388b6cb5b5Vitaly Buka
5684774df2734a2b74f16047983751673388b6cb5b5Vitaly Bukavoid DeviceRegistrationInfo::RegisterDeviceOnTicketFinalized(
569747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    const DoneCallback& callback,
570747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    std::unique_ptr<provider::HttpClient::Response> response,
571747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    ErrorPtr error) {
572747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  if (error)
573747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    return RegisterDeviceError(callback, std::move(error));
574747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  auto json_resp = ParseJsonResponse(*response, &error);
575b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko  if (!json_resp)
576747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    return RegisterDeviceError(callback, std::move(error));
577747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  if (!IsSuccessful(*response)) {
57812870bd07dd3c8f44528638a9c290b72e22d32d2Vitaly Buka    ParseGCDError(json_resp.get(), &error);
579747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    return RegisterDeviceError(callback, std::move(error));
580b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko  }
581532ff7e5174da55010d31cdc4c6a76f52e3bd1aeAnton Muhin
582532ff7e5174da55010d31cdc4c6a76f52e3bd1aeAnton Muhin  std::string auth_code;
583312c2f5d0d5e2ef0cc7acc6dce5137094332c276Johan Euphrosine  std::string cloud_id;
584ee7a3af2e8ab08e0a71a407f2bfa8d3ca78b0f79Vitaly Buka  std::string robot_account;
585fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  const base::DictionaryValue* device_draft_response = nullptr;
586ee7a3af2e8ab08e0a71a407f2bfa8d3ca78b0f79Vitaly Buka  if (!json_resp->GetString("robotAccountEmail", &robot_account) ||
587b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko      !json_resp->GetString("robotAccountAuthorizationCode", &auth_code) ||
588fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko      !json_resp->GetDictionary("deviceDraft", &device_draft_response) ||
589312c2f5d0d5e2ef0cc7acc6dce5137094332c276Johan Euphrosine      !device_draft_response->GetString("id", &cloud_id)) {
59048a8669ddc2e8d785aad9ad18a5abbf8f1224fdeVitaly Buka    Error::AddTo(&error, FROM_HERE, "unexpected_response",
5910801a1f20a0c8f34130d567cd3b7dcbd2be9cb3cVitaly Buka                 "Device account missing in response");
592747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    return RegisterDeviceError(callback, std::move(error));
593b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko  }
5943cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko
595fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  UpdateDeviceInfoTimestamp(*device_draft_response);
596fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko
597b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko  // Now get access_token and refresh_token
5981a42e1466de3edd0c79215b739cd8e929ef8a7e8Vitaly Buka  RequestSender sender2{HttpClient::Method::kPost, GetOAuthURL("token"),
5991a42e1466de3edd0c79215b739cd8e929ef8a7e8Vitaly Buka                        http_client_};
60034668e731bb194b443bc0e6029d6d3583f08de28Vitaly Buka  sender2.SetFormData({{"code", auth_code},
60134668e731bb194b443bc0e6029d6d3583f08de28Vitaly Buka                       {"client_id", GetSettings().client_id},
60234668e731bb194b443bc0e6029d6d3583f08de28Vitaly Buka                       {"client_secret", GetSettings().client_secret},
60334668e731bb194b443bc0e6029d6d3583f08de28Vitaly Buka                       {"redirect_uri", "oob"},
60434668e731bb194b443bc0e6029d6d3583f08de28Vitaly Buka                       {"grant_type", "authorization_code"}});
6054774df2734a2b74f16047983751673388b6cb5b5Vitaly Buka  sender2.Send(base::Bind(&DeviceRegistrationInfo::RegisterDeviceOnAuthCodeSent,
6064774df2734a2b74f16047983751673388b6cb5b5Vitaly Buka                          weak_factory_.GetWeakPtr(), cloud_id, robot_account,
607747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka                          callback));
6084774df2734a2b74f16047983751673388b6cb5b5Vitaly Buka}
6093cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko
6104774df2734a2b74f16047983751673388b6cb5b5Vitaly Bukavoid DeviceRegistrationInfo::RegisterDeviceOnAuthCodeSent(
6114774df2734a2b74f16047983751673388b6cb5b5Vitaly Buka    const std::string& cloud_id,
6124774df2734a2b74f16047983751673388b6cb5b5Vitaly Buka    const std::string& robot_account,
613747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    const DoneCallback& callback,
614747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    std::unique_ptr<provider::HttpClient::Response> response,
615747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    ErrorPtr error) {
616747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  if (error)
617747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    return RegisterDeviceError(callback, std::move(error));
618747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  auto json_resp = ParseOAuthResponse(*response, &error);
619b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko  int expires_in = 0;
620ee7a3af2e8ab08e0a71a407f2bfa8d3ca78b0f79Vitaly Buka  std::string refresh_token;
621ee7a3af2e8ab08e0a71a407f2bfa8d3ca78b0f79Vitaly Buka  if (!json_resp || !json_resp->GetString("access_token", &access_token_) ||
622ee7a3af2e8ab08e0a71a407f2bfa8d3ca78b0f79Vitaly Buka      !json_resp->GetString("refresh_token", &refresh_token) ||
623b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko      !json_resp->GetInteger("expires_in", &expires_in) ||
624ee7a3af2e8ab08e0a71a407f2bfa8d3ca78b0f79Vitaly Buka      access_token_.empty() || refresh_token.empty() || expires_in <= 0) {
62548a8669ddc2e8d785aad9ad18a5abbf8f1224fdeVitaly Buka    Error::AddTo(&error, FROM_HERE, "unexpected_response",
6260801a1f20a0c8f34130d567cd3b7dcbd2be9cb3cVitaly Buka                 "Device access_token missing in response");
627747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    return RegisterDeviceError(callback, std::move(error));
6283cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko  }
629b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko
630a647c857f3098b366b379bde308d051f6a9aac2fVitaly Buka  access_token_expiration_ =
631a647c857f3098b366b379bde308d051f6a9aac2fVitaly Buka      base::Time::Now() + base::TimeDelta::FromSeconds(expires_in);
632b3aac2520d0ba88ba1aa69c30213bbc21944401eAlex Vakulenko
633666b43e92729fa170bc53eab9040a4dfe58b8062Vitaly Buka  Config::Transaction change{config_};
634312c2f5d0d5e2ef0cc7acc6dce5137094332c276Johan Euphrosine  change.set_cloud_id(cloud_id);
635ee7a3af2e8ab08e0a71a407f2bfa8d3ca78b0f79Vitaly Buka  change.set_robot_account(robot_account);
636ee7a3af2e8ab08e0a71a407f2bfa8d3ca78b0f79Vitaly Buka  change.set_refresh_token(refresh_token);
637ee7a3af2e8ab08e0a71a407f2bfa8d3ca78b0f79Vitaly Buka  change.Commit();
638ee7a3af2e8ab08e0a71a407f2bfa8d3ca78b0f79Vitaly Buka
639747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  task_runner_->PostDelayedTask(FROM_HERE, base::Bind(callback, nullptr), {});
64012870bd07dd3c8f44528638a9c290b72e22d32d2Vitaly Buka
641eedf3be451ea83ce21aa22294c9605f15bd3eebaAlex Vakulenko  StartNotificationChannel();
64272d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Buka  SendAuthInfo();
643cd4196605e88eac2d5fe78a58aa5aa1c40ee8f77Christopher Wiley
644fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  // We're going to respond with our success immediately and we'll connect to
645fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  // cloud shortly after.
64612870bd07dd3c8f44528638a9c290b72e22d32d2Vitaly Buka  ScheduleCloudConnection({});
6473cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko}
6483cb466ce672499ab4447e9716c8a70714ffe18c7Alex Vakulenko
649ac661abad20f63d506b84574e23e55d8fa019347Anton Muhinvoid DeviceRegistrationInfo::DoCloudRequest(
6501a42e1466de3edd0c79215b739cd8e929ef8a7e8Vitaly Buka    HttpClient::Method method,
651ac661abad20f63d506b84574e23e55d8fa019347Anton Muhin    const std::string& url,
652ac661abad20f63d506b84574e23e55d8fa019347Anton Muhin    const base::DictionaryValue* body,
653747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    const CloudRequestDoneCallback& callback) {
654266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko  // We make CloudRequestData shared here because we want to make sure
655747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  // there is only one instance of callback and error_calback since
656266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko  // those may have move-only types and making a copy of the callback with
657266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko  // move-only types curried-in will invalidate the source callback.
658266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko  auto data = std::make_shared<CloudRequestData>();
659266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko  data->method = method;
660266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko  data->url = url;
661266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko  if (body)
662266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko    base::JSONWriter::Write(*body, &data->body);
663747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  data->callback = callback;
664266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko  SendCloudRequest(data);
665266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko}
666266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko
667266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenkovoid DeviceRegistrationInfo::SendCloudRequest(
668266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko    const std::shared_ptr<const CloudRequestData>& data) {
6690357c0384d9cbee356648c18d2aed0244874c47fAlex Vakulenko  // TODO(antonm): Add reauthorization on access token expiration (do not
670ac661abad20f63d506b84574e23e55d8fa019347Anton Muhin  // forget about 5xx when fetching new access token).
671ac661abad20f63d506b84574e23e55d8fa019347Anton Muhin  // TODO(antonm): Add support for device removal.
672ac661abad20f63d506b84574e23e55d8fa019347Anton Muhin
6730801a1f20a0c8f34130d567cd3b7dcbd2be9cb3cVitaly Buka  ErrorPtr error;
674672634b8b0ed31891fb48e02dce75b6aead0be27Vitaly Buka  if (!VerifyRegistrationCredentials(&error))
675747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    return data->callback.Run({}, std::move(error));
676ac661abad20f63d506b84574e23e55d8fa019347Anton Muhin
677266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko  if (cloud_backoff_entry_->ShouldRejectRequest()) {
678266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko    VLOG(1) << "Cloud request delayed for "
679266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko            << cloud_backoff_entry_->GetTimeUntilRelease()
680266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko            << " due to backoff policy";
681f7f52d4707c007bb9255bd80b23ac3428c6fc2e0Vitaly Buka    return task_runner_->PostDelayedTask(
682a647c857f3098b366b379bde308d051f6a9aac2fVitaly Buka        FROM_HERE, base::Bind(&DeviceRegistrationInfo::SendCloudRequest,
683a647c857f3098b366b379bde308d051f6a9aac2fVitaly Buka                              AsWeakPtr(), data),
684266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko        cloud_backoff_entry_->GetTimeUntilRelease());
685266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko  }
686633eded4cc099cdb5590a330a4b1f6a6012c1292Anton Muhin
6876da9425a14ac345b657354e62307d17df4a721dbVitaly Buka  RequestSender sender{data->method, data->url, http_client_};
6881da659947e800069a6352dbe7f829dfdf1697fedVitaly Buka  sender.SetData(data->body, http::kJsonUtf8);
689815b603b59bebac4407322af61a822413ad421f6Vitaly Buka  sender.SetAccessToken(access_token_);
690747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  sender.Send(base::Bind(&DeviceRegistrationInfo::OnCloudRequestDone,
691866b60a19c99f6db4def7f18cc902370d344a33aVitaly Buka                         AsWeakPtr(), data));
692266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko}
693266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko
694747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Bukavoid DeviceRegistrationInfo::OnCloudRequestDone(
695266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko    const std::shared_ptr<const CloudRequestData>& data,
696747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    std::unique_ptr<provider::HttpClient::Response> response,
697747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    ErrorPtr error) {
698747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  if (error)
699747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    return RetryCloudRequest(data);
700747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  int status_code = response->GetStatusCode();
7011da659947e800069a6352dbe7f829dfdf1697fedVitaly Buka  if (status_code == http::kDenied) {
702fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko    cloud_backoff_entry_->InformOfRequest(true);
70334668e731bb194b443bc0e6029d6d3583f08de28Vitaly Buka    RefreshAccessToken(base::Bind(
70434668e731bb194b443bc0e6029d6d3583f08de28Vitaly Buka        &DeviceRegistrationInfo::OnAccessTokenRefreshed, AsWeakPtr(), data));
705266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko    return;
706266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko  }
707266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko
7081da659947e800069a6352dbe7f829dfdf1697fedVitaly Buka  if (status_code >= http::kInternalServerError) {
709266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko    // Request was valid, but server failed, retry.
710266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko    // TODO(antonm): Reconsider status codes, maybe only some require
711266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko    // retry.
712266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko    // TODO(antonm): Support Retry-After header.
713266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko    RetryCloudRequest(data);
714266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko    return;
715266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko  }
716266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko
71761820306dd6a61f5c824d9236f7d48d07167c515Vitaly Buka  if (response->GetContentType().empty()) {
71861820306dd6a61f5c824d9236f7d48d07167c515Vitaly Buka    // Assume no body if no content type.
7197717d93d22d34c9974ca09bf6bde95a3caa75bcbVitaly Buka    cloud_backoff_entry_->InformOfRequest(true);
7207717d93d22d34c9974ca09bf6bde95a3caa75bcbVitaly Buka    return data->callback.Run({}, nullptr);
7217717d93d22d34c9974ca09bf6bde95a3caa75bcbVitaly Buka  }
7227717d93d22d34c9974ca09bf6bde95a3caa75bcbVitaly Buka
723747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  auto json_resp = ParseJsonResponse(*response, &error);
724266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko  if (!json_resp) {
7255fddf87b36b0528e8251b25ae2eba3336fd028ddVitaly Buka    cloud_backoff_entry_->InformOfRequest(false);
726747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    return data->callback.Run({}, std::move(error));
727266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko  }
728266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko
729747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  if (!IsSuccessful(*response)) {
730266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko    ParseGCDError(json_resp.get(), &error);
7311da659947e800069a6352dbe7f829dfdf1697fedVitaly Buka    if (status_code == http::kForbidden &&
73248a8669ddc2e8d785aad9ad18a5abbf8f1224fdeVitaly Buka        error->HasError("rateLimitExceeded")) {
733f61ee017872aa93e53d23fdd79ab16c6fac98c0cAlex Vakulenko      // If we exceeded server quota, retry the request later.
734f7f52d4707c007bb9255bd80b23ac3428c6fc2e0Vitaly Buka      return RetryCloudRequest(data);
735f61ee017872aa93e53d23fdd79ab16c6fac98c0cAlex Vakulenko    }
7365fddf87b36b0528e8251b25ae2eba3336fd028ddVitaly Buka
7375fddf87b36b0528e8251b25ae2eba3336fd028ddVitaly Buka    cloud_backoff_entry_->InformOfRequest(false);
738747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    return data->callback.Run({}, std::move(error));
739266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko  }
7406401d01c768e996b48803e268da5fc10fc44cdacAlex Vakulenko
741f61ee017872aa93e53d23fdd79ab16c6fac98c0cAlex Vakulenko  cloud_backoff_entry_->InformOfRequest(true);
742c3c6dab99bb333417756817d154cd99ae71f4668Vitaly Buka  SetGcdState(GcdState::kConnected);
743747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  data->callback.Run(*json_resp, nullptr);
744266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko}
745266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko
746266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenkovoid DeviceRegistrationInfo::RetryCloudRequest(
747266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko    const std::shared_ptr<const CloudRequestData>& data) {
748f61ee017872aa93e53d23fdd79ab16c6fac98c0cAlex Vakulenko  // TODO(avakulenko): Tie connecting/connected status to XMPP channel instead.
749c3c6dab99bb333417756817d154cd99ae71f4668Vitaly Buka  SetGcdState(GcdState::kConnecting);
750266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko  cloud_backoff_entry_->InformOfRequest(false);
751266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko  SendCloudRequest(data);
752266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko}
753266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko
754266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenkovoid DeviceRegistrationInfo::OnAccessTokenRefreshed(
755266b2b15731ff30ada2cb9cf040c465e774b6e09Alex Vakulenko    const std::shared_ptr<const CloudRequestData>& data,
756f7f52d4707c007bb9255bd80b23ac3428c6fc2e0Vitaly Buka    ErrorPtr error) {
757747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  if (error) {
758747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    CheckAccessTokenError(error->Clone());
759747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    return data->callback.Run({}, std::move(error));
760747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  }
761747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  SendCloudRequest(data);
762fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko}
763fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko
764f7f52d4707c007bb9255bd80b23ac3428c6fc2e0Vitaly Bukavoid DeviceRegistrationInfo::CheckAccessTokenError(ErrorPtr error) {
76548a8669ddc2e8d785aad9ad18a5abbf8f1224fdeVitaly Buka  if (error && error->HasError("invalid_grant"))
766672634b8b0ed31891fb48e02dce75b6aead0be27Vitaly Buka    RemoveCredentials();
767ac661abad20f63d506b84574e23e55d8fa019347Anton Muhin}
768ac661abad20f63d506b84574e23e55d8fa019347Anton Muhin
769747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Bukavoid DeviceRegistrationInfo::ConnectToCloud(ErrorPtr error) {
770747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  if (error) {
77148a8669ddc2e8d785aad9ad18a5abbf8f1224fdeVitaly Buka    if (error->HasError("invalid_grant"))
772672634b8b0ed31891fb48e02dce75b6aead0be27Vitaly Buka      RemoveCredentials();
773747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    return;
774747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  }
775747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka
776fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  connected_to_cloud_ = false;
777fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  if (!VerifyRegistrationCredentials(nullptr))
778fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko    return;
779fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko
780fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  if (access_token_.empty()) {
781fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko    RefreshAccessToken(
782747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka        base::Bind(&DeviceRegistrationInfo::ConnectToCloud, AsWeakPtr()));
783d8d3216bc2fb413e8100ff0b17c53f275300534cAnton Muhin    return;
784fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  }
785fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko
786fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  // Connecting a device to cloud just means that we:
787ba983c8d415b0cea83d0ee7a096860db8de93accChristopher Wiley  //   1) push an updated device resource
788ba983c8d415b0cea83d0ee7a096860db8de93accChristopher Wiley  //   2) fetch an initial set of outstanding commands
789ba983c8d415b0cea83d0ee7a096860db8de93accChristopher Wiley  //   3) abort any commands that we've previously marked as "in progress"
790d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko  //      or as being in an error state; publish queued commands
791fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  UpdateDeviceResource(
792747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka      base::Bind(&DeviceRegistrationInfo::OnConnectedToCloud, AsWeakPtr()));
793fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko}
794fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko
795747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Bukavoid DeviceRegistrationInfo::OnConnectedToCloud(ErrorPtr error) {
796747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  if (error)
797747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    return;
798fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  LOG(INFO) << "Device connected to cloud server";
799fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  connected_to_cloud_ = true;
800fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  FetchCommands(base::Bind(&DeviceRegistrationInfo::ProcessInitialCommandList,
80134668e731bb194b443bc0e6029d6d3583f08de28Vitaly Buka                           AsWeakPtr()),
80234668e731bb194b443bc0e6029d6d3583f08de28Vitaly Buka                fetch_reason::kDeviceStart);
803fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  // In case there are any pending state updates since we sent off the initial
804fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  // UpdateDeviceResource() request, update the server with any state changes.
805fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  PublishStateUpdates();
806c635c59187409cd66c3dc3d1fc749106b2d92522Anton Muhin}
807c635c59187409cd66c3dc3d1fc749106b2d92522Anton Muhin
808b624bc47fb70294185478e4c758657a54b859cb1Vitaly Bukavoid DeviceRegistrationInfo::UpdateDeviceInfo(const std::string& name,
809fa94706d3e37dbb6da611506faca801eb3772824Vitaly Buka                                              const std::string& description,
810b624bc47fb70294185478e4c758657a54b859cb1Vitaly Buka                                              const std::string& location) {
811666b43e92729fa170bc53eab9040a4dfe58b8062Vitaly Buka  Config::Transaction change{config_};
812798a0e746d3f97779ccfc6bc86bfbfe854f68445Vitaly Buka  change.set_name(name);
813ee7a3af2e8ab08e0a71a407f2bfa8d3ca78b0f79Vitaly Buka  change.set_description(description);
814ee7a3af2e8ab08e0a71a407f2bfa8d3ca78b0f79Vitaly Buka  change.set_location(location);
815ff81db68e447f15ac7bc50c8c3c6572fdc7b3b7bVitaly Buka  change.Commit();
816fa94706d3e37dbb6da611506faca801eb3772824Vitaly Buka
8173fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko  if (HaveRegistrationCredentials()) {
818747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    UpdateDeviceResource(base::Bind(&IgnoreCloudError));
819fa94706d3e37dbb6da611506faca801eb3772824Vitaly Buka  }
820fa94706d3e37dbb6da611506faca801eb3772824Vitaly Buka}
821fa94706d3e37dbb6da611506faca801eb3772824Vitaly Buka
822b624bc47fb70294185478e4c758657a54b859cb1Vitaly Bukavoid DeviceRegistrationInfo::UpdateBaseConfig(AuthScope anonymous_access_role,
823b624bc47fb70294185478e4c758657a54b859cb1Vitaly Buka                                              bool local_discovery_enabled,
824b624bc47fb70294185478e4c758657a54b859cb1Vitaly Buka                                              bool local_pairing_enabled) {
825666b43e92729fa170bc53eab9040a4dfe58b8062Vitaly Buka  Config::Transaction change(config_);
826b624bc47fb70294185478e4c758657a54b859cb1Vitaly Buka  change.set_local_anonymous_access_role(anonymous_access_role);
8272f7efdb7a0f3ad0923d61290534b37c6c8351cc8Vitaly Buka  change.set_local_discovery_enabled(local_discovery_enabled);
8282f7efdb7a0f3ad0923d61290534b37c6c8351cc8Vitaly Buka  change.set_local_pairing_enabled(local_pairing_enabled);
8292f7efdb7a0f3ad0923d61290534b37c6c8351cc8Vitaly Buka}
8302f7efdb7a0f3ad0923d61290534b37c6c8351cc8Vitaly Buka
831ff81db68e447f15ac7bc50c8c3c6572fdc7b3b7bVitaly Bukabool DeviceRegistrationInfo::UpdateServiceConfig(
832ff81db68e447f15ac7bc50c8c3c6572fdc7b3b7bVitaly Buka    const std::string& client_id,
833ff81db68e447f15ac7bc50c8c3c6572fdc7b3b7bVitaly Buka    const std::string& client_secret,
834ff81db68e447f15ac7bc50c8c3c6572fdc7b3b7bVitaly Buka    const std::string& api_key,
835ff81db68e447f15ac7bc50c8c3c6572fdc7b3b7bVitaly Buka    const std::string& oauth_url,
836ff81db68e447f15ac7bc50c8c3c6572fdc7b3b7bVitaly Buka    const std::string& service_url,
8373b8fbc546262ac5335e9ddfd219c195b224a4427Vitaly Buka    const std::string& xmpp_endpoint,
8380801a1f20a0c8f34130d567cd3b7dcbd2be9cb3cVitaly Buka    ErrorPtr* error) {
8393fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko  if (HaveRegistrationCredentials()) {
8400dbbf605efb8f72b3c2c15c14e613323fc2ac0a2Vitaly Buka    return Error::AddTo(error, FROM_HERE, kErrorAlreayRegistered,
8410dbbf605efb8f72b3c2c15c14e613323fc2ac0a2Vitaly Buka                        "Unable to change config for registered device");
842ff81db68e447f15ac7bc50c8c3c6572fdc7b3b7bVitaly Buka  }
843666b43e92729fa170bc53eab9040a4dfe58b8062Vitaly Buka  Config::Transaction change{config_};
8443b8fbc546262ac5335e9ddfd219c195b224a4427Vitaly Buka  if (!client_id.empty())
8453b8fbc546262ac5335e9ddfd219c195b224a4427Vitaly Buka    change.set_client_id(client_id);
8463b8fbc546262ac5335e9ddfd219c195b224a4427Vitaly Buka  if (!client_secret.empty())
8473b8fbc546262ac5335e9ddfd219c195b224a4427Vitaly Buka    change.set_client_secret(client_secret);
8483b8fbc546262ac5335e9ddfd219c195b224a4427Vitaly Buka  if (!api_key.empty())
8493b8fbc546262ac5335e9ddfd219c195b224a4427Vitaly Buka    change.set_api_key(api_key);
8503b8fbc546262ac5335e9ddfd219c195b224a4427Vitaly Buka  if (!oauth_url.empty())
8513b8fbc546262ac5335e9ddfd219c195b224a4427Vitaly Buka    change.set_oauth_url(oauth_url);
8523b8fbc546262ac5335e9ddfd219c195b224a4427Vitaly Buka  if (!service_url.empty())
8533b8fbc546262ac5335e9ddfd219c195b224a4427Vitaly Buka    change.set_service_url(service_url);
8543b8fbc546262ac5335e9ddfd219c195b224a4427Vitaly Buka  if (!xmpp_endpoint.empty())
8553b8fbc546262ac5335e9ddfd219c195b224a4427Vitaly Buka    change.set_xmpp_endpoint(xmpp_endpoint);
856ff81db68e447f15ac7bc50c8c3c6572fdc7b3b7bVitaly Buka  return true;
857ff81db68e447f15ac7bc50c8c3c6572fdc7b3b7bVitaly Buka}
858ff81db68e447f15ac7bc50c8c3c6572fdc7b3b7bVitaly Buka
8595975552606c5bf51fd8d66b9b9bfa020999ef4b6Anton Muhinvoid DeviceRegistrationInfo::UpdateCommand(
8605975552606c5bf51fd8d66b9b9bfa020999ef4b6Anton Muhin    const std::string& command_id,
861b211c104af747704dd9796e0f585bc3825fc802fAlex Vakulenko    const base::DictionaryValue& command_patch,
862747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    const DoneCallback& callback) {
8631a42e1466de3edd0c79215b739cd8e929ef8a7e8Vitaly Buka  DoCloudRequest(HttpClient::Method::kPatch,
8641a42e1466de3edd0c79215b739cd8e929ef8a7e8Vitaly Buka                 GetServiceURL("commands/" + command_id), &command_patch,
865747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka                 base::Bind(&IgnoreCloudResultWithCallback, callback));
8665975552606c5bf51fd8d66b9b9bfa020999ef4b6Anton Muhin}
8675975552606c5bf51fd8d66b9b9bfa020999ef4b6Anton Muhin
868a647c857f3098b366b379bde308d051f6a9aac2fVitaly Bukavoid DeviceRegistrationInfo::NotifyCommandAborted(const std::string& command_id,
8690801a1f20a0c8f34130d567cd3b7dcbd2be9cb3cVitaly Buka                                                  ErrorPtr error) {
870d1978d3a3d74f62a12a06acafc4b08b50a6c6834Alex Vakulenko  base::DictionaryValue command_patch;
871d1978d3a3d74f62a12a06acafc4b08b50a6c6834Alex Vakulenko  command_patch.SetString(commands::attributes::kCommand_State,
8720209da4821a019d2805d417b513b2743b2e750caVitaly Buka                          EnumToString(Command::State::kAborted));
873d1978d3a3d74f62a12a06acafc4b08b50a6c6834Alex Vakulenko  if (error) {
87470f77d92d48bce3d2f20a42997ee94332859d3ddVitaly Buka    command_patch.Set(commands::attributes::kCommand_Error,
87570f77d92d48bce3d2f20a42997ee94332859d3ddVitaly Buka                      ErrorInfoToJson(*error).release());
876d1978d3a3d74f62a12a06acafc4b08b50a6c6834Alex Vakulenko  }
877747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  UpdateCommand(command_id, command_patch, base::Bind(&IgnoreCloudError));
878d1978d3a3d74f62a12a06acafc4b08b50a6c6834Alex Vakulenko}
879d1978d3a3d74f62a12a06acafc4b08b50a6c6834Alex Vakulenko
880ba983c8d415b0cea83d0ee7a096860db8de93accChristopher Wileyvoid DeviceRegistrationInfo::UpdateDeviceResource(
881747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    const DoneCallback& callback) {
882747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  queued_resource_update_callbacks_.emplace_back(callback);
883f3a95bf41633f161184a584a4475ba2d663684c1Alex Vakulenko  if (!in_progress_resource_update_callbacks_.empty()) {
884f3a95bf41633f161184a584a4475ba2d663684c1Alex Vakulenko    VLOG(1) << "Another request is already pending.";
885f3a95bf41633f161184a584a4475ba2d663684c1Alex Vakulenko    return;
886f3a95bf41633f161184a584a4475ba2d663684c1Alex Vakulenko  }
887f3a95bf41633f161184a584a4475ba2d663684c1Alex Vakulenko
888f3a95bf41633f161184a584a4475ba2d663684c1Alex Vakulenko  StartQueuedUpdateDeviceResource();
889f3a95bf41633f161184a584a4475ba2d663684c1Alex Vakulenko}
890f3a95bf41633f161184a584a4475ba2d663684c1Alex Vakulenko
891f3a95bf41633f161184a584a4475ba2d663684c1Alex Vakulenkovoid DeviceRegistrationInfo::StartQueuedUpdateDeviceResource() {
892fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  if (in_progress_resource_update_callbacks_.empty() &&
893fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko      queued_resource_update_callbacks_.empty())
894fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko    return;
895fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko
896fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  if (last_device_resource_updated_timestamp_.empty()) {
897fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko    // We don't know the current time stamp of the device resource from the
898fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko    // server side. We need to provide the time stamp to the server as part of
899fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko    // the request to guard against out-of-order requests overwriting settings
900fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko    // specified by later requests.
901fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko    VLOG(1) << "Getting the last device resource timestamp from server...";
902747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    GetDeviceInfo(base::Bind(&DeviceRegistrationInfo::OnDeviceInfoRetrieved,
903747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka                             AsWeakPtr()));
904f3a95bf41633f161184a584a4475ba2d663684c1Alex Vakulenko    return;
905fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  }
906f3a95bf41633f161184a584a4475ba2d663684c1Alex Vakulenko
907fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  in_progress_resource_update_callbacks_.insert(
908fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko      in_progress_resource_update_callbacks_.end(),
909fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko      queued_resource_update_callbacks_.begin(),
910fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko      queued_resource_update_callbacks_.end());
911fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  queued_resource_update_callbacks_.clear();
912f3a95bf41633f161184a584a4475ba2d663684c1Alex Vakulenko
9139ea5a321746d94ed46c79633b75357f4cc77562eAlex Vakulenko  VLOG(1) << "Updating GCD server with CDD...";
914d8d3216bc2fb413e8100ff0b17c53f275300534cAnton Muhin  std::unique_ptr<base::DictionaryValue> device_resource =
9151a108717d3d6ac17974b835dc18f6c177fb609ffVitaly Buka      BuildDeviceResource();
9161a108717d3d6ac17974b835dc18f6c177fb609ffVitaly Buka  CHECK(device_resource);
917d8d3216bc2fb413e8100ff0b17c53f275300534cAnton Muhin
918fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  std::string url = GetDeviceURL(
919fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko      {}, {{"lastUpdateTimeMs", last_device_resource_updated_timestamp_}});
920fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko
921747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  DoCloudRequest(HttpClient::Method::kPut, url, device_resource.get(),
922747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka                 base::Bind(&DeviceRegistrationInfo::OnUpdateDeviceResourceDone,
923747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka                            AsWeakPtr()));
924f3a95bf41633f161184a584a4475ba2d663684c1Alex Vakulenko}
925f3a95bf41633f161184a584a4475ba2d663684c1Alex Vakulenko
92672d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Bukavoid DeviceRegistrationInfo::SendAuthInfo() {
92772d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Buka  if (!auth_manager_ || auth_info_update_inprogress_)
92872d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Buka    return;
9294ab500249f346a9fcfe084ee1619a39259f7471cVitaly Buka
9304ab500249f346a9fcfe084ee1619a39259f7471cVitaly Buka  if (GetSettings().root_client_token_owner == RootClientTokenOwner::kCloud) {
9314ab500249f346a9fcfe084ee1619a39259f7471cVitaly Buka    // Avoid re-claiming if device is already claimed by the Cloud. Cloud is
9324ab500249f346a9fcfe084ee1619a39259f7471cVitaly Buka    // allowed to re-claim device at any time. However this will invalidate all
9334ab500249f346a9fcfe084ee1619a39259f7471cVitaly Buka    // issued tokens.
9344ab500249f346a9fcfe084ee1619a39259f7471cVitaly Buka    return;
9354ab500249f346a9fcfe084ee1619a39259f7471cVitaly Buka  }
9364ab500249f346a9fcfe084ee1619a39259f7471cVitaly Buka
93772d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Buka  auth_info_update_inprogress_ = true;
93872d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Buka
9394ab500249f346a9fcfe084ee1619a39259f7471cVitaly Buka  std::vector<uint8_t> token = auth_manager_->ClaimRootClientAuthToken(
9404ab500249f346a9fcfe084ee1619a39259f7471cVitaly Buka      RootClientTokenOwner::kCloud, nullptr);
9414ab500249f346a9fcfe084ee1619a39259f7471cVitaly Buka  CHECK(!token.empty());
94272d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Buka  std::string id = GetSettings().device_id;
9430c190b39cd7629168b7008b34555881f7830289dVitaly Buka  std::string token_base64 = Base64Encode(token);
94472d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Buka  std::string fingerprint =
94572d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Buka      Base64Encode(auth_manager_->GetCertificateFingerprint());
94672d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Buka
947f3fd9c30d383b8f0fc4d4d6cefd172a5fa1200c4Vitaly Buka  std::unique_ptr<base::DictionaryValue> auth{new base::DictionaryValue};
948f3fd9c30d383b8f0fc4d4d6cefd172a5fa1200c4Vitaly Buka  auth->SetString("localId", id);
949f3fd9c30d383b8f0fc4d4d6cefd172a5fa1200c4Vitaly Buka  auth->SetString("clientToken", token_base64);
950f3fd9c30d383b8f0fc4d4d6cefd172a5fa1200c4Vitaly Buka  auth->SetString("certFingerprint", fingerprint);
951f3fd9c30d383b8f0fc4d4d6cefd172a5fa1200c4Vitaly Buka  std::unique_ptr<base::DictionaryValue> root{new base::DictionaryValue};
952f3fd9c30d383b8f0fc4d4d6cefd172a5fa1200c4Vitaly Buka  root->Set("localAuthInfo", auth.release());
95372d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Buka
954f3fd9c30d383b8f0fc4d4d6cefd172a5fa1200c4Vitaly Buka  std::string url = GetDeviceURL("upsertLocalAuthInfo", {});
955f3fd9c30d383b8f0fc4d4d6cefd172a5fa1200c4Vitaly Buka  DoCloudRequest(HttpClient::Method::kPost, url, root.get(),
9560c190b39cd7629168b7008b34555881f7830289dVitaly Buka                 base::Bind(&DeviceRegistrationInfo::OnSendAuthInfoDone,
9570c190b39cd7629168b7008b34555881f7830289dVitaly Buka                            AsWeakPtr(), token));
95872d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Buka}
95972d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Buka
96072d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Bukavoid DeviceRegistrationInfo::OnSendAuthInfoDone(
9610c190b39cd7629168b7008b34555881f7830289dVitaly Buka    const std::vector<uint8_t>& token,
96272d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Buka    const base::DictionaryValue& body,
96372d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Buka    ErrorPtr error) {
96472d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Buka  CHECK(auth_info_update_inprogress_);
96572d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Buka  auth_info_update_inprogress_ = false;
96672d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Buka
967305ab613de85f6640f300010a17cb6ea22be2081Vitaly Buka  if (!error && auth_manager_->ConfirmClientAuthToken(token, nullptr))
96872d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Buka    return;
96972d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Buka
97072d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Buka  task_runner_->PostDelayedTask(
97172d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Buka      FROM_HERE, base::Bind(&DeviceRegistrationInfo::SendAuthInfo, AsWeakPtr()),
97272d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Buka      {});
97372d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Buka}
97472d8d1611efb8c0dd87d466e971bea9468b7c3a1Vitaly Buka
975fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenkovoid DeviceRegistrationInfo::OnDeviceInfoRetrieved(
976747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    const base::DictionaryValue& device_info,
977747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    ErrorPtr error) {
978747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  if (error)
979747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    return OnUpdateDeviceResourceError(std::move(error));
980fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  if (UpdateDeviceInfoTimestamp(device_info))
981fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko    StartQueuedUpdateDeviceResource();
982fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko}
983fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko
984fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenkobool DeviceRegistrationInfo::UpdateDeviceInfoTimestamp(
985fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko    const base::DictionaryValue& device_info) {
986fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  // For newly created devices, "lastUpdateTimeMs" may not be present, but
987fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  // "creationTimeMs" should be there at least.
988fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  if (!device_info.GetString("lastUpdateTimeMs",
989fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko                             &last_device_resource_updated_timestamp_) &&
990fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko      !device_info.GetString("creationTimeMs",
991fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko                             &last_device_resource_updated_timestamp_)) {
992fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko    LOG(WARNING) << "Device resource timestamp is missing";
993fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko    return false;
994fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  }
995fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  return true;
996fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko}
997fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko
998747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Bukavoid DeviceRegistrationInfo::OnUpdateDeviceResourceDone(
999747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    const base::DictionaryValue& device_info,
1000747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    ErrorPtr error) {
1001747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  if (error)
1002747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    return OnUpdateDeviceResourceError(std::move(error));
1003fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  UpdateDeviceInfoTimestamp(device_info);
1004f3a95bf41633f161184a584a4475ba2d663684c1Alex Vakulenko  // Make a copy of the callback list so that if the callback triggers another
1005f3a95bf41633f161184a584a4475ba2d663684c1Alex Vakulenko  // call to UpdateDeviceResource(), we do not modify the list we are iterating
1006f3a95bf41633f161184a584a4475ba2d663684c1Alex Vakulenko  // over.
1007f3a95bf41633f161184a584a4475ba2d663684c1Alex Vakulenko  auto callback_list = std::move(in_progress_resource_update_callbacks_);
1008747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  for (const auto& callback : callback_list)
1009747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    callback.Run(nullptr);
1010f3a95bf41633f161184a584a4475ba2d663684c1Alex Vakulenko  StartQueuedUpdateDeviceResource();
1011f3a95bf41633f161184a584a4475ba2d663684c1Alex Vakulenko}
1012f3a95bf41633f161184a584a4475ba2d663684c1Alex Vakulenko
1013f7f52d4707c007bb9255bd80b23ac3428c6fc2e0Vitaly Bukavoid DeviceRegistrationInfo::OnUpdateDeviceResourceError(ErrorPtr error) {
101448a8669ddc2e8d785aad9ad18a5abbf8f1224fdeVitaly Buka  if (error->HasError("invalid_last_update_time_ms")) {
1015fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko    // If the server rejected our previous request, retrieve the latest
1016fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko    // timestamp from the server and retry.
1017fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko    VLOG(1) << "Getting the last device resource timestamp from server...";
1018747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    GetDeviceInfo(base::Bind(&DeviceRegistrationInfo::OnDeviceInfoRetrieved,
1019747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka                             AsWeakPtr()));
1020fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko    return;
1021fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  }
1022fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko
1023f3a95bf41633f161184a584a4475ba2d663684c1Alex Vakulenko  // Make a copy of the callback list so that if the callback triggers another
1024f3a95bf41633f161184a584a4475ba2d663684c1Alex Vakulenko  // call to UpdateDeviceResource(), we do not modify the list we are iterating
1025f3a95bf41633f161184a584a4475ba2d663684c1Alex Vakulenko  // over.
1026f3a95bf41633f161184a584a4475ba2d663684c1Alex Vakulenko  auto callback_list = std::move(in_progress_resource_update_callbacks_);
1027747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  for (const auto& callback : callback_list)
1028747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    callback.Run(error->Clone());
1029fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko
1030f3a95bf41633f161184a584a4475ba2d663684c1Alex Vakulenko  StartQueuedUpdateDeviceResource();
1031ba983c8d415b0cea83d0ee7a096860db8de93accChristopher Wiley}
1032ba983c8d415b0cea83d0ee7a096860db8de93accChristopher Wiley
1033747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Bukavoid DeviceRegistrationInfo::OnFetchCommandsDone(
1034747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    const base::Callback<void(const base::ListValue&, ErrorPtr)>& callback,
1035747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    const base::DictionaryValue& json,
1036747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    ErrorPtr error) {
103764bc9ea9f24d9eb18f77e1e0af360f5e8d9c35f3Alex Vakulenko  OnFetchCommandsReturned();
1038747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  if (error)
1039747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    return callback.Run({}, std::move(error));
1040ba983c8d415b0cea83d0ee7a096860db8de93accChristopher Wiley  const base::ListValue* commands{nullptr};
1041747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  if (!json.GetList("commands", &commands))
1042ed77a57d86bab20ff668fc00503dfa771561781bAlex Vakulenko    VLOG(2) << "No commands in the response.";
1043ba983c8d415b0cea83d0ee7a096860db8de93accChristopher Wiley  const base::ListValue empty;
1044747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  callback.Run(commands ? *commands : empty, nullptr);
104564bc9ea9f24d9eb18f77e1e0af360f5e8d9c35f3Alex Vakulenko}
104664bc9ea9f24d9eb18f77e1e0af360f5e8d9c35f3Alex Vakulenko
104764bc9ea9f24d9eb18f77e1e0af360f5e8d9c35f3Alex Vakulenkovoid DeviceRegistrationInfo::OnFetchCommandsReturned() {
104864bc9ea9f24d9eb18f77e1e0af360f5e8d9c35f3Alex Vakulenko  fetch_commands_request_sent_ = false;
104964bc9ea9f24d9eb18f77e1e0af360f5e8d9c35f3Alex Vakulenko  // If we have additional requests queued, send them out now.
105064bc9ea9f24d9eb18f77e1e0af360f5e8d9c35f3Alex Vakulenko  if (fetch_commands_request_queued_)
1051e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenko    FetchAndPublishCommands(queued_fetch_reason_);
105264bc9ea9f24d9eb18f77e1e0af360f5e8d9c35f3Alex Vakulenko}
1053ba983c8d415b0cea83d0ee7a096860db8de93accChristopher Wiley
1054c635c59187409cd66c3dc3d1fc749106b2d92522Anton Muhinvoid DeviceRegistrationInfo::FetchCommands(
1055e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenko    const base::Callback<void(const base::ListValue&, ErrorPtr)>& callback,
1056e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenko    const std::string& reason) {
105764bc9ea9f24d9eb18f77e1e0af360f5e8d9c35f3Alex Vakulenko  fetch_commands_request_sent_ = true;
105864bc9ea9f24d9eb18f77e1e0af360f5e8d9c35f3Alex Vakulenko  fetch_commands_request_queued_ = false;
1059c635c59187409cd66c3dc3d1fc749106b2d92522Anton Muhin  DoCloudRequest(
10601a42e1466de3edd0c79215b739cd8e929ef8a7e8Vitaly Buka      HttpClient::Method::kGet,
106134668e731bb194b443bc0e6029d6d3583f08de28Vitaly Buka      GetServiceURL("commands/queue",
106234668e731bb194b443bc0e6029d6d3583f08de28Vitaly Buka                    {{"deviceId", GetSettings().cloud_id}, {"reason", reason}}),
1063747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka      nullptr, base::Bind(&DeviceRegistrationInfo::OnFetchCommandsDone,
1064747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka                          AsWeakPtr(), callback));
1065c635c59187409cd66c3dc3d1fc749106b2d92522Anton Muhin}
1066c635c59187409cd66c3dc3d1fc749106b2d92522Anton Muhin
1067e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenkovoid DeviceRegistrationInfo::FetchAndPublishCommands(
1068e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenko    const std::string& reason) {
106964bc9ea9f24d9eb18f77e1e0af360f5e8d9c35f3Alex Vakulenko  if (fetch_commands_request_sent_) {
107064bc9ea9f24d9eb18f77e1e0af360f5e8d9c35f3Alex Vakulenko    fetch_commands_request_queued_ = true;
1071e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenko    queued_fetch_reason_ = reason;
107264bc9ea9f24d9eb18f77e1e0af360f5e8d9c35f3Alex Vakulenko    return;
107364bc9ea9f24d9eb18f77e1e0af360f5e8d9c35f3Alex Vakulenko  }
107464bc9ea9f24d9eb18f77e1e0af360f5e8d9c35f3Alex Vakulenko
10751038ec1b83cf2afe6132dc7dcd005b23463f5858Alex Vakulenko  FetchCommands(base::Bind(&DeviceRegistrationInfo::PublishCommands,
1076e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenko                           weak_factory_.GetWeakPtr()),
1077e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenko                reason);
10781038ec1b83cf2afe6132dc7dcd005b23463f5858Alex Vakulenko}
10791038ec1b83cf2afe6132dc7dcd005b23463f5858Alex Vakulenko
1080d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenkovoid DeviceRegistrationInfo::ProcessInitialCommandList(
1081747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    const base::ListValue& commands,
1082747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    ErrorPtr error) {
1083747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  if (error)
1084747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    return;
1085d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko  for (const base::Value* command : commands) {
1086d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko    const base::DictionaryValue* command_dict{nullptr};
1087d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko    if (!command->GetAsDictionary(&command_dict)) {
1088d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko      LOG(WARNING) << "Not a command dictionary: " << *command;
1089c635c59187409cd66c3dc3d1fc749106b2d92522Anton Muhin      continue;
1090a34f0d9d6da24bcaf60fe9046b67e9c4d45aa7bdAnton Muhin    }
1091c635c59187409cd66c3dc3d1fc749106b2d92522Anton Muhin    std::string command_state;
1092d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko    if (!command_dict->GetString("state", &command_state)) {
1093d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko      LOG(WARNING) << "Command with no state at " << *command;
1094c635c59187409cd66c3dc3d1fc749106b2d92522Anton Muhin      continue;
1095c635c59187409cd66c3dc3d1fc749106b2d92522Anton Muhin    }
1096a647c857f3098b366b379bde308d051f6a9aac2fVitaly Buka    if (command_state == "error" && command_state == "inProgress" &&
1097d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko        command_state == "paused") {
1098d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko      // It's a limbo command, abort it.
1099d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko      std::string command_id;
1100d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko      if (!command_dict->GetString("id", &command_id)) {
1101d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko        LOG(WARNING) << "Command with no ID at " << *command;
1102d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko        continue;
1103d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko      }
11046d2569e4260293816b8b9416d5e6993f843869b2Anton Muhin
1105d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko      std::unique_ptr<base::DictionaryValue> cmd_copy{command_dict->DeepCopy()};
1106d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko      cmd_copy->SetString("state", "aborted");
1107d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko      // TODO(wiley) We could consider handling this error case more gracefully.
11081a42e1466de3edd0c79215b739cd8e929ef8a7e8Vitaly Buka      DoCloudRequest(HttpClient::Method::kPut,
11091a42e1466de3edd0c79215b739cd8e929ef8a7e8Vitaly Buka                     GetServiceURL("commands/" + command_id), cmd_copy.get(),
1110747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka                     base::Bind(&IgnoreCloudResult));
1111d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko    } else {
1112d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko      // Normal command, publish it to local clients.
1113d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko      PublishCommand(*command_dict);
1114d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko    }
1115c635c59187409cd66c3dc3d1fc749106b2d92522Anton Muhin  }
1116d07e206c4d71c118516054752a69911d04c98fbaAnton Muhin}
1117d07e206c4d71c118516054752a69911d04c98fbaAnton Muhin
1118747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Bukavoid DeviceRegistrationInfo::PublishCommands(const base::ListValue& commands,
1119747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka                                             ErrorPtr error) {
1120747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  if (error)
1121747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    return;
11226e3c30e823a0ee72dc7aa1ccf9f8b71aa9d99ab4Alex Vakulenko  for (const base::Value* command : commands) {
11236e3c30e823a0ee72dc7aa1ccf9f8b71aa9d99ab4Alex Vakulenko    const base::DictionaryValue* command_dict{nullptr};
11246e3c30e823a0ee72dc7aa1ccf9f8b71aa9d99ab4Alex Vakulenko    if (!command->GetAsDictionary(&command_dict)) {
11256e3c30e823a0ee72dc7aa1ccf9f8b71aa9d99ab4Alex Vakulenko      LOG(WARNING) << "Not a command dictionary: " << *command;
1126d07e206c4d71c118516054752a69911d04c98fbaAnton Muhin      continue;
1127d07e206c4d71c118516054752a69911d04c98fbaAnton Muhin    }
11286e3c30e823a0ee72dc7aa1ccf9f8b71aa9d99ab4Alex Vakulenko    PublishCommand(*command_dict);
11296e3c30e823a0ee72dc7aa1ccf9f8b71aa9d99ab4Alex Vakulenko  }
11306e3c30e823a0ee72dc7aa1ccf9f8b71aa9d99ab4Alex Vakulenko}
1131d07e206c4d71c118516054752a69911d04c98fbaAnton Muhin
11326e3c30e823a0ee72dc7aa1ccf9f8b71aa9d99ab4Alex Vakulenkovoid DeviceRegistrationInfo::PublishCommand(
11336e3c30e823a0ee72dc7aa1ccf9f8b71aa9d99ab4Alex Vakulenko    const base::DictionaryValue& command) {
11346e3c30e823a0ee72dc7aa1ccf9f8b71aa9d99ab4Alex Vakulenko  std::string command_id;
11350801a1f20a0c8f34130d567cd3b7dcbd2be9cb3cVitaly Buka  ErrorPtr error;
1136d91d625f29b752be035c5fb49bb29d7ee85fcb90Alex Vakulenko  auto command_instance = component_manager_->ParseCommandInstance(
1137d91d625f29b752be035c5fb49bb29d7ee85fcb90Alex Vakulenko      command, Command::Origin::kCloud, UserRole::kOwner, &command_id, &error);
11386e3c30e823a0ee72dc7aa1ccf9f8b71aa9d99ab4Alex Vakulenko  if (!command_instance) {
11396e3c30e823a0ee72dc7aa1ccf9f8b71aa9d99ab4Alex Vakulenko    LOG(WARNING) << "Failed to parse a command instance: " << command;
11406e3c30e823a0ee72dc7aa1ccf9f8b71aa9d99ab4Alex Vakulenko    if (!command_id.empty())
11416e3c30e823a0ee72dc7aa1ccf9f8b71aa9d99ab4Alex Vakulenko      NotifyCommandAborted(command_id, std::move(error));
11426e3c30e823a0ee72dc7aa1ccf9f8b71aa9d99ab4Alex Vakulenko    return;
11436e3c30e823a0ee72dc7aa1ccf9f8b71aa9d99ab4Alex Vakulenko  }
1144d07e206c4d71c118516054752a69911d04c98fbaAnton Muhin
11456e3c30e823a0ee72dc7aa1ccf9f8b71aa9d99ab4Alex Vakulenko  // TODO(antonm): Properly process cancellation of commands.
1146d91d625f29b752be035c5fb49bb29d7ee85fcb90Alex Vakulenko  if (!component_manager_->FindCommand(command_instance->GetID())) {
11476e3c30e823a0ee72dc7aa1ccf9f8b71aa9d99ab4Alex Vakulenko    LOG(INFO) << "New command '" << command_instance->GetName()
11486e3c30e823a0ee72dc7aa1ccf9f8b71aa9d99ab4Alex Vakulenko              << "' arrived, ID: " << command_instance->GetID();
11490f80f7c281a0363c2e1e7302f938291b968aea87Vitaly Buka    std::unique_ptr<BackoffEntry> backoff_entry{
11500f80f7c281a0363c2e1e7302f938291b968aea87Vitaly Buka        new BackoffEntry{cloud_backoff_policy_.get()}};
115134668e731bb194b443bc0e6029d6d3583f08de28Vitaly Buka    std::unique_ptr<CloudCommandProxy> cloud_proxy{
115234668e731bb194b443bc0e6029d6d3583f08de28Vitaly Buka        new CloudCommandProxy{command_instance.get(), this, component_manager_,
115334668e731bb194b443bc0e6029d6d3583f08de28Vitaly Buka                              std::move(backoff_entry), task_runner_}};
11546da9425a14ac345b657354e62307d17df4a721dbVitaly Buka    // CloudCommandProxy::CloudCommandProxy() subscribe itself to Command
11556da9425a14ac345b657354e62307d17df4a721dbVitaly Buka    // notifications. When Command is being destroyed it sends
1156157b16aa9906a39e67c1f894f8fbf6f2130ea007Vitaly Buka    // ::OnCommandDestroyed() and CloudCommandProxy deletes itself.
1157157b16aa9906a39e67c1f894f8fbf6f2130ea007Vitaly Buka    cloud_proxy.release();
1158d91d625f29b752be035c5fb49bb29d7ee85fcb90Alex Vakulenko    component_manager_->AddCommand(std::move(command_instance));
1159d07e206c4d71c118516054752a69911d04c98fbaAnton Muhin  }
1160d8d3216bc2fb413e8100ff0b17c53f275300534cAnton Muhin}
1161d8d3216bc2fb413e8100ff0b17c53f275300534cAnton Muhin
1162b8315621198220890057fafe842574fc71a6d93bAnton Muhinvoid DeviceRegistrationInfo::PublishStateUpdates() {
116393ba0bd7fd68ebe91b43a94b1025dd6d435f67c0Alex Vakulenko  // If we have pending state update requests, don't send any more for now.
116493ba0bd7fd68ebe91b43a94b1025dd6d435f67c0Alex Vakulenko  if (device_state_update_pending_)
116593ba0bd7fd68ebe91b43a94b1025dd6d435f67c0Alex Vakulenko    return;
116693ba0bd7fd68ebe91b43a94b1025dd6d435f67c0Alex Vakulenko
1167d91d625f29b752be035c5fb49bb29d7ee85fcb90Alex Vakulenko  auto snapshot = component_manager_->GetAndClearRecordedStateChanges();
1168d91d625f29b752be035c5fb49bb29d7ee85fcb90Alex Vakulenko  if (snapshot.state_changes.empty())
1169b8315621198220890057fafe842574fc71a6d93bAnton Muhin    return;
1170b8315621198220890057fafe842574fc71a6d93bAnton Muhin
1171b8315621198220890057fafe842574fc71a6d93bAnton Muhin  std::unique_ptr<base::ListValue> patches{new base::ListValue};
1172d91d625f29b752be035c5fb49bb29d7ee85fcb90Alex Vakulenko  for (auto& state_change : snapshot.state_changes) {
1173b8315621198220890057fafe842574fc71a6d93bAnton Muhin    std::unique_ptr<base::DictionaryValue> patch{new base::DictionaryValue};
117476933fdbf7ec8e05ca0549db1e1f9df9ec3eb9a6Anton Muhin    patch->SetString("timeMs",
117576933fdbf7ec8e05ca0549db1e1f9df9ec3eb9a6Anton Muhin                     std::to_string(state_change.timestamp.ToJavaTime()));
11769921b87a837f324951eb0eba4d971cfe854edf27Alex Vakulenko    patch->SetString("component", state_change.component);
11777d66921b44464e4480d86c362294831f9587a061Alex Vakulenko    patch->Set("patch", state_change.changed_properties.release());
1178b8315621198220890057fafe842574fc71a6d93bAnton Muhin    patches->Append(patch.release());
1179b8315621198220890057fafe842574fc71a6d93bAnton Muhin  }
1180b8315621198220890057fafe842574fc71a6d93bAnton Muhin
1181b8315621198220890057fafe842574fc71a6d93bAnton Muhin  base::DictionaryValue body;
118276933fdbf7ec8e05ca0549db1e1f9df9ec3eb9a6Anton Muhin  body.SetString("requestTimeMs",
118376933fdbf7ec8e05ca0549db1e1f9df9ec3eb9a6Anton Muhin                 std::to_string(base::Time::Now().ToJavaTime()));
1184b8315621198220890057fafe842574fc71a6d93bAnton Muhin  body.Set("patches", patches.release());
1185b8315621198220890057fafe842574fc71a6d93bAnton Muhin
118693ba0bd7fd68ebe91b43a94b1025dd6d435f67c0Alex Vakulenko  device_state_update_pending_ = true;
1187747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  DoCloudRequest(HttpClient::Method::kPost, GetDeviceURL("patchState"), &body,
1188747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka                 base::Bind(&DeviceRegistrationInfo::OnPublishStateDone,
1189d91d625f29b752be035c5fb49bb29d7ee85fcb90Alex Vakulenko                            AsWeakPtr(), snapshot.update_id));
119093ba0bd7fd68ebe91b43a94b1025dd6d435f67c0Alex Vakulenko}
119193ba0bd7fd68ebe91b43a94b1025dd6d435f67c0Alex Vakulenko
1192747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Bukavoid DeviceRegistrationInfo::OnPublishStateDone(
1193d91d625f29b752be035c5fb49bb29d7ee85fcb90Alex Vakulenko    ComponentManager::UpdateID update_id,
1194747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    const base::DictionaryValue& reply,
1195747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    ErrorPtr error) {
119693ba0bd7fd68ebe91b43a94b1025dd6d435f67c0Alex Vakulenko  device_state_update_pending_ = false;
1197747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  if (error) {
1198747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    LOG(ERROR) << "Permanent failure while trying to update device state";
1199747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka    return;
1200747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  }
1201d91d625f29b752be035c5fb49bb29d7ee85fcb90Alex Vakulenko  component_manager_->NotifyStateUpdatedOnServer(update_id);
120293ba0bd7fd68ebe91b43a94b1025dd6d435f67c0Alex Vakulenko  // See if there were more pending state updates since the previous request
120393ba0bd7fd68ebe91b43a94b1025dd6d435f67c0Alex Vakulenko  // had been sent out.
120493ba0bd7fd68ebe91b43a94b1025dd6d435f67c0Alex Vakulenko  PublishStateUpdates();
120593ba0bd7fd68ebe91b43a94b1025dd6d435f67c0Alex Vakulenko}
120693ba0bd7fd68ebe91b43a94b1025dd6d435f67c0Alex Vakulenko
1207c3c6dab99bb333417756817d154cd99ae71f4668Vitaly Bukavoid DeviceRegistrationInfo::SetGcdState(GcdState new_state) {
1208c3c6dab99bb333417756817d154cd99ae71f4668Vitaly Buka  VLOG_IF(1, new_state != gcd_state_) << "Changing registration status to "
1209c3c6dab99bb333417756817d154cd99ae71f4668Vitaly Buka                                      << EnumToString(new_state);
1210c3c6dab99bb333417756817d154cd99ae71f4668Vitaly Buka  gcd_state_ = new_state;
1211c3c6dab99bb333417756817d154cd99ae71f4668Vitaly Buka  for (const auto& cb : gcd_state_changed_callbacks_)
1212c3c6dab99bb333417756817d154cd99ae71f4668Vitaly Buka    cb.Run(gcd_state_);
1213c900e48b8d482b1462a4d262856e344d22bcc6a5Christopher Wiley}
1214c900e48b8d482b1462a4d262856e344d22bcc6a5Christopher Wiley
1215d91d625f29b752be035c5fb49bb29d7ee85fcb90Alex Vakulenkovoid DeviceRegistrationInfo::OnTraitDefsChanged() {
12169ea5a321746d94ed46c79633b75357f4cc77562eAlex Vakulenko  VLOG(1) << "CommandDefinitionChanged notification received";
1217fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  if (!HaveRegistrationCredentials() || !connected_to_cloud_)
12189ea5a321746d94ed46c79633b75357f4cc77562eAlex Vakulenko    return;
12199ea5a321746d94ed46c79633b75357f4cc77562eAlex Vakulenko
1220747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  UpdateDeviceResource(base::Bind(&IgnoreCloudError));
12219ea5a321746d94ed46c79633b75357f4cc77562eAlex Vakulenko}
12229ea5a321746d94ed46c79633b75357f4cc77562eAlex Vakulenko
1223c903d281e1196398af49f28b15c0456b1ff28edeVitaly Bukavoid DeviceRegistrationInfo::OnStateChanged() {
1224c903d281e1196398af49f28b15c0456b1ff28edeVitaly Buka  VLOG(1) << "StateChanged notification received";
1225fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  if (!HaveRegistrationCredentials() || !connected_to_cloud_)
1226c903d281e1196398af49f28b15c0456b1ff28edeVitaly Buka    return;
1227c903d281e1196398af49f28b15c0456b1ff28edeVitaly Buka
1228c903d281e1196398af49f28b15c0456b1ff28edeVitaly Buka  // TODO(vitalybuka): Integrate BackoffEntry.
1229c903d281e1196398af49f28b15c0456b1ff28edeVitaly Buka  PublishStateUpdates();
1230c903d281e1196398af49f28b15c0456b1ff28edeVitaly Buka}
1231c903d281e1196398af49f28b15c0456b1ff28edeVitaly Buka
1232d91d625f29b752be035c5fb49bb29d7ee85fcb90Alex Vakulenkovoid DeviceRegistrationInfo::OnComponentTreeChanged() {
1233d91d625f29b752be035c5fb49bb29d7ee85fcb90Alex Vakulenko  VLOG(1) << "ComponentTreeChanged notification received";
1234d91d625f29b752be035c5fb49bb29d7ee85fcb90Alex Vakulenko  if (!HaveRegistrationCredentials() || !connected_to_cloud_)
1235d91d625f29b752be035c5fb49bb29d7ee85fcb90Alex Vakulenko    return;
1236d91d625f29b752be035c5fb49bb29d7ee85fcb90Alex Vakulenko
1237d91d625f29b752be035c5fb49bb29d7ee85fcb90Alex Vakulenko  UpdateDeviceResource(base::Bind(&IgnoreCloudError));
1238d91d625f29b752be035c5fb49bb29d7ee85fcb90Alex Vakulenko}
1239d91d625f29b752be035c5fb49bb29d7ee85fcb90Alex Vakulenko
1240eedf3be451ea83ce21aa22294c9605f15bd3eebaAlex Vakulenkovoid DeviceRegistrationInfo::OnConnected(const std::string& channel_name) {
1241eedf3be451ea83ce21aa22294c9605f15bd3eebaAlex Vakulenko  LOG(INFO) << "Notification channel successfully established over "
1242eedf3be451ea83ce21aa22294c9605f15bd3eebaAlex Vakulenko            << channel_name;
1243d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko  CHECK_EQ(primary_notification_channel_->GetName(), channel_name);
12446b028ae747f94bd9201f71690a9aa7c0ad585f5fAlex Vakulenko  notification_channel_starting_ = false;
124541a90d69918c6367ba23de1edf0bad6c7175f3b9Vitaly Buka  pull_channel_->UpdatePullInterval(
1246c1fc90c1724427d8e926172ed258e05ff63b8e26Alex Vakulenko      base::TimeDelta::FromMinutes(kBackupPollingPeriodMinutes));
1247d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko  current_notification_channel_ = primary_notification_channel_.get();
1248fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko
12498b096cc83ae31f66f02adfb68d10507e90520212Alex Vakulenko  // If we have not successfully connected to the cloud server and we have not
12508b096cc83ae31f66f02adfb68d10507e90520212Alex Vakulenko  // initiated the first device resource update, there is nothing we need to
12518b096cc83ae31f66f02adfb68d10507e90520212Alex Vakulenko  // do now to update the server of the notification channel change.
12528b096cc83ae31f66f02adfb68d10507e90520212Alex Vakulenko  if (!connected_to_cloud_ && in_progress_resource_update_callbacks_.empty())
1253fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko    return;
1254fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko
12551038ec1b83cf2afe6132dc7dcd005b23463f5858Alex Vakulenko  // Once we update the device resource with the new notification channel,
12561038ec1b83cf2afe6132dc7dcd005b23463f5858Alex Vakulenko  // do the last poll for commands from the server, to make sure we have the
12571038ec1b83cf2afe6132dc7dcd005b23463f5858Alex Vakulenko  // latest command baseline and no other commands have been queued between
12581038ec1b83cf2afe6132dc7dcd005b23463f5858Alex Vakulenko  // the moment of the last poll and the time we successfully told the server
12591038ec1b83cf2afe6132dc7dcd005b23463f5858Alex Vakulenko  // to send new commands over the new notification channel.
12601038ec1b83cf2afe6132dc7dcd005b23463f5858Alex Vakulenko  UpdateDeviceResource(
1261747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka      base::Bind(&IgnoreCloudErrorWithCallback,
1262747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka                 base::Bind(&DeviceRegistrationInfo::FetchAndPublishCommands,
1263e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenko                            AsWeakPtr(), fetch_reason::kRegularPull)));
1264eedf3be451ea83ce21aa22294c9605f15bd3eebaAlex Vakulenko}
1265eedf3be451ea83ce21aa22294c9605f15bd3eebaAlex Vakulenko
1266eedf3be451ea83ce21aa22294c9605f15bd3eebaAlex Vakulenkovoid DeviceRegistrationInfo::OnDisconnected() {
1267eedf3be451ea83ce21aa22294c9605f15bd3eebaAlex Vakulenko  LOG(INFO) << "Notification channel disconnected";
1268fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  if (!HaveRegistrationCredentials() || !connected_to_cloud_)
12693fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko    return;
12703fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko
127141a90d69918c6367ba23de1edf0bad6c7175f3b9Vitaly Buka  pull_channel_->UpdatePullInterval(
127241a90d69918c6367ba23de1edf0bad6c7175f3b9Vitaly Buka      base::TimeDelta::FromSeconds(kPollingPeriodSeconds));
1273d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko  current_notification_channel_ = pull_channel_.get();
1274747634273144e0df7b37475375ad4790b6a7b0e8Vitaly Buka  UpdateDeviceResource(base::Bind(&IgnoreCloudError));
1275eedf3be451ea83ce21aa22294c9605f15bd3eebaAlex Vakulenko}
1276eedf3be451ea83ce21aa22294c9605f15bd3eebaAlex Vakulenko
1277eedf3be451ea83ce21aa22294c9605f15bd3eebaAlex Vakulenkovoid DeviceRegistrationInfo::OnPermanentFailure() {
1278eedf3be451ea83ce21aa22294c9605f15bd3eebaAlex Vakulenko  LOG(ERROR) << "Failed to establish notification channel.";
12796b028ae747f94bd9201f71690a9aa7c0ad585f5fAlex Vakulenko  notification_channel_starting_ = false;
12804ebd329b95f6d596c8ec97844d346b8a3149bb76Vitaly Buka  RefreshAccessToken(
12814ebd329b95f6d596c8ec97844d346b8a3149bb76Vitaly Buka      base::Bind(&DeviceRegistrationInfo::CheckAccessTokenError, AsWeakPtr()));
1282eedf3be451ea83ce21aa22294c9605f15bd3eebaAlex Vakulenko}
1283eedf3be451ea83ce21aa22294c9605f15bd3eebaAlex Vakulenko
12846e3c30e823a0ee72dc7aa1ccf9f8b71aa9d99ab4Alex Vakulenkovoid DeviceRegistrationInfo::OnCommandCreated(
1285e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenko    const base::DictionaryValue& command,
1286e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenko    const std::string& channel_name) {
1287fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  if (!connected_to_cloud_)
1288fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko    return;
1289fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko
12907a35005ccd95adcb42125d602d9a997d0bf33d86Vitaly Buka  VLOG(1) << "Command notification received: " << command;
12917a35005ccd95adcb42125d602d9a997d0bf33d86Vitaly Buka
12926e3c30e823a0ee72dc7aa1ccf9f8b71aa9d99ab4Alex Vakulenko  if (!command.empty()) {
12936e3c30e823a0ee72dc7aa1ccf9f8b71aa9d99ab4Alex Vakulenko    // GCD spec indicates that the command parameter in notification object
12946e3c30e823a0ee72dc7aa1ccf9f8b71aa9d99ab4Alex Vakulenko    // "may be empty if command size is too big".
12956e3c30e823a0ee72dc7aa1ccf9f8b71aa9d99ab4Alex Vakulenko    PublishCommand(command);
12966e3c30e823a0ee72dc7aa1ccf9f8b71aa9d99ab4Alex Vakulenko    return;
12976e3c30e823a0ee72dc7aa1ccf9f8b71aa9d99ab4Alex Vakulenko  }
1298e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenko
1299e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenko  // If this request comes from a Pull channel while the primary notification
1300e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenko  // channel (XMPP) is active, we are doing a backup poll, so mark the request
1301e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenko  // appropriately.
1302e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenko  bool just_in_case =
130334668e731bb194b443bc0e6029d6d3583f08de28Vitaly Buka      (channel_name == kPullChannelName) &&
130434668e731bb194b443bc0e6029d6d3583f08de28Vitaly Buka      (current_notification_channel_ == primary_notification_channel_.get());
1305e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenko
1306e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenko  std::string reason =
1307e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenko      just_in_case ? fetch_reason::kJustInCase : fetch_reason::kNewCommand;
1308e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenko
1309d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko  // If the command was too big to be delivered over a notification channel,
1310d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko  // or OnCommandCreated() was initiated from the Pull notification,
1311d05725f1d796ea298d31a9a421c8e6d80fb88cedAlex Vakulenko  // perform a manual command fetch from the server here.
1312e07c29d604d1b1c3a179f40140934a806bd9a425Alex Vakulenko  FetchAndPublishCommands(reason);
13136e3c30e823a0ee72dc7aa1ccf9f8b71aa9d99ab4Alex Vakulenko}
13146e3c30e823a0ee72dc7aa1ccf9f8b71aa9d99ab4Alex Vakulenko
1315312c2f5d0d5e2ef0cc7acc6dce5137094332c276Johan Euphrosinevoid DeviceRegistrationInfo::OnDeviceDeleted(const std::string& cloud_id) {
1316312c2f5d0d5e2ef0cc7acc6dce5137094332c276Johan Euphrosine  if (cloud_id != GetSettings().cloud_id) {
1317312c2f5d0d5e2ef0cc7acc6dce5137094332c276Johan Euphrosine    LOG(WARNING) << "Unexpected device deletion notification for cloud ID '"
1318312c2f5d0d5e2ef0cc7acc6dce5137094332c276Johan Euphrosine                 << cloud_id << "'";
13196b40d8fae84cdb589a7bdd65e341957bc1ce5c33Alex Vakulenko    return;
13206b40d8fae84cdb589a7bdd65e341957bc1ce5c33Alex Vakulenko  }
1321672634b8b0ed31891fb48e02dce75b6aead0be27Vitaly Buka  RemoveCredentials();
13226b40d8fae84cdb589a7bdd65e341957bc1ce5c33Alex Vakulenko}
13236b40d8fae84cdb589a7bdd65e341957bc1ce5c33Alex Vakulenko
1324672634b8b0ed31891fb48e02dce75b6aead0be27Vitaly Bukavoid DeviceRegistrationInfo::RemoveCredentials() {
13253fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko  if (!HaveRegistrationCredentials())
13263fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko    return;
13273fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko
1328fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko  connected_to_cloud_ = false;
1329fb331acff6d29bef16ccbfda2006c173b3a58bf4Alex Vakulenko
13303fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko  LOG(INFO) << "Device is unregistered from the cloud. Deleting credentials";
13314ab500249f346a9fcfe084ee1619a39259f7471cVitaly Buka  if (auth_manager_)
13320bc02ede1d7ac6b0ed264b8891844d15bdb4733eVitaly Buka    auth_manager_->SetAuthSecret({}, RootClientTokenOwner::kNone);
13334ab500249f346a9fcfe084ee1619a39259f7471cVitaly Buka
1334666b43e92729fa170bc53eab9040a4dfe58b8062Vitaly Buka  Config::Transaction change{config_};
1335672634b8b0ed31891fb48e02dce75b6aead0be27Vitaly Buka  // Keep cloud_id to switch to detect kInvalidCredentials after restart.
13363fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko  change.set_robot_account("");
13373fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko  change.set_refresh_token("");
13383fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko  change.Commit();
13393fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko
13403fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko  current_notification_channel_ = nullptr;
13413fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko  if (primary_notification_channel_) {
13423fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko    primary_notification_channel_->Stop();
13433fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko    primary_notification_channel_.reset();
13443fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko  }
13453fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko  if (pull_channel_) {
13463fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko    pull_channel_->Stop();
13473fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko    pull_channel_.reset();
13483fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko  }
13493fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko  notification_channel_starting_ = false;
1350c3c6dab99bb333417756817d154cd99ae71f4668Vitaly Buka  SetGcdState(GcdState::kInvalidCredentials);
13513fa42aebf88af42a17026b41a8eb7cc510ef7897Alex Vakulenko}
13526e3c30e823a0ee72dc7aa1ccf9f8b71aa9d99ab4Alex Vakulenko
1353b6f015a1ef3caffbc2af53184c0ec5342e42e048Vitaly Buka}  // namespace weave
1354