blob: 458b57dcc91c459cf76b157d389c3ef073f0299e [file] [log] [blame]
Vitaly Buka4615e0d2015-10-14 15:35:12 -07001// Copyright 2015 The Weave Authors. All rights reserved.
Alex Vakulenko3cb466c2014-04-15 11:36:32 -07002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Stefan Sauer2d16dfa2015-09-25 17:08:35 +02005#include "src/device_registration_info.h"
Alex Vakulenko3cb466c2014-04-15 11:36:32 -07006
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -07007#include <algorithm>
Christopher Wiley006e94e2014-05-02 13:44:48 -07008#include <memory>
Alex Vakulenko9ea5a322015-04-17 15:35:34 -07009#include <set>
Alex Vakulenkob3aac252014-05-07 17:35:24 -070010#include <utility>
11#include <vector>
Christopher Wiley006e94e2014-05-02 13:44:48 -070012
Christopher Wileycd419662015-02-06 17:51:43 -080013#include <base/bind.h>
Vitaly Buka6da94252015-08-04 15:45:14 -070014#include <base/json/json_reader.h>
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070015#include <base/json/json_writer.h>
Alex Vakulenkofb331ac2015-07-22 15:10:11 -070016#include <base/strings/string_number_conversions.h>
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070017#include <base/values.h>
Vitaly Buka1e363672015-09-25 14:01:16 -070018#include <weave/provider/http_client.h>
19#include <weave/provider/network.h>
20#include <weave/provider/task_runner.h>
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070021
Stefan Sauer2d16dfa2015-09-25 17:08:35 +020022#include "src/bind_lambda.h"
23#include "src/commands/cloud_command_proxy.h"
24#include "src/commands/command_definition.h"
25#include "src/commands/command_manager.h"
26#include "src/commands/schema_constants.h"
27#include "src/data_encoding.h"
28#include "src/http_constants.h"
29#include "src/json_error_codes.h"
30#include "src/notification/xmpp_channel.h"
31#include "src/states/state_manager.h"
32#include "src/string_utils.h"
33#include "src/utils.h"
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070034
Vitaly Bukab6f015a2015-07-09 14:59:23 -070035namespace weave {
36
37const char kErrorDomainOAuth2[] = "oauth2";
38const char kErrorDomainGCD[] = "gcd";
39const char kErrorDomainGCDServer[] = "gcd_server";
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070040
Alex Vakulenko8e34d392014-04-29 11:02:56 -070041namespace {
42
Vitaly Buka41a90d62015-09-29 16:58:39 -070043const int kPollingPeriodSeconds = 7;
Alex Vakulenkoc1fc90c2015-10-22 08:00:43 -070044const int kBackupPollingPeriodMinutes = 30;
Vitaly Buka41a90d62015-09-29 16:58:39 -070045
Vitaly Buka1e363672015-09-25 14:01:16 -070046using provider::HttpClient;
47
Vitaly Buka0801a1f2015-08-14 10:03:46 -070048inline void SetUnexpectedError(ErrorPtr* error) {
49 Error::AddTo(error, FROM_HERE, kErrorDomainGCD, "unexpected_response",
50 "Unexpected GCD error");
Alex Vakulenkob3aac252014-05-07 17:35:24 -070051}
52
Vitaly Buka0801a1f2015-08-14 10:03:46 -070053void ParseGCDError(const base::DictionaryValue* json, ErrorPtr* error) {
Alex Vakulenkob3aac252014-05-07 17:35:24 -070054 const base::Value* list_value = nullptr;
55 const base::ListValue* error_list = nullptr;
56 if (!json->Get("error.errors", &list_value) ||
57 !list_value->GetAsList(&error_list)) {
58 SetUnexpectedError(error);
59 return;
60 }
61
62 for (size_t i = 0; i < error_list->GetSize(); i++) {
63 const base::Value* error_value = nullptr;
64 const base::DictionaryValue* error_object = nullptr;
65 if (!error_list->Get(i, &error_value) ||
66 !error_value->GetAsDictionary(&error_object)) {
67 SetUnexpectedError(error);
68 continue;
69 }
70 std::string error_code, error_message;
71 if (error_object->GetString("reason", &error_code) &&
72 error_object->GetString("message", &error_message)) {
Vitaly Buka0801a1f2015-08-14 10:03:46 -070073 Error::AddTo(error, FROM_HERE, kErrorDomainGCDServer, error_code,
74 error_message);
Alex Vakulenkob3aac252014-05-07 17:35:24 -070075 } else {
76 SetUnexpectedError(error);
77 }
78 }
79}
80
Vitaly Buka7d556392015-08-13 20:06:48 -070081std::string AppendQueryParams(const std::string& url,
82 const WebParamList& params) {
Vitaly Bukab001f282015-08-13 17:07:59 -070083 CHECK_EQ(std::string::npos, url.find_first_of("?#"));
84 if (params.empty())
85 return url;
Vitaly Buka7d556392015-08-13 20:06:48 -070086 return url + '?' + WebParamsEncode(params);
Vitaly Bukab001f282015-08-13 17:07:59 -070087}
88
Alex Vakulenkobda220a2014-04-18 15:25:44 -070089std::string BuildURL(const std::string& url,
Vitaly Bukab001f282015-08-13 17:07:59 -070090 const std::string& subpath,
Vitaly Buka7d556392015-08-13 20:06:48 -070091 const WebParamList& params) {
Vitaly Bukab001f282015-08-13 17:07:59 -070092 std::string result = url;
93 if (!result.empty() && result.back() != '/' && !subpath.empty()) {
94 CHECK_NE('/', subpath.front());
95 result += '/';
96 }
97 result += subpath;
98 return AppendQueryParams(result, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070099}
100
Vitaly Bukaf7f52d42015-10-10 22:43:55 -0700101void IgnoreCloudErrorWithCallback(const base::Closure& cb, ErrorPtr) {
Christopher Wileyba983c82015-03-05 16:32:23 -0800102 cb.Run();
103}
104
Vitaly Buka74763422015-10-11 00:39:52 -0700105void IgnoreCloudError(ErrorPtr) {}
Anton Muhin6d2569e2014-10-30 12:32:27 +0400106
Vitaly Buka74763422015-10-11 00:39:52 -0700107void IgnoreCloudResult(const base::DictionaryValue&, ErrorPtr error) {}
108
109void IgnoreCloudResultWithCallback(const DoneCallback& cb,
110 const base::DictionaryValue&,
111 ErrorPtr error) {
112 cb.Run(std::move(error));
Christopher Wileyba983c82015-03-05 16:32:23 -0800113}
114
Vitaly Buka6da94252015-08-04 15:45:14 -0700115class RequestSender final {
116 public:
Vitaly Buka1a42e142015-10-10 18:15:15 -0700117 RequestSender(HttpClient::Method method,
Vitaly Buka6da94252015-08-04 15:45:14 -0700118 const std::string& url,
119 HttpClient* transport)
120 : method_{method}, url_{url}, transport_{transport} {}
121
Vitaly Buka74763422015-10-11 00:39:52 -0700122 void Send(const HttpClient::SendRequestCallback& callback) {
Vitaly Buka866b60a2015-10-09 14:24:55 -0700123 static int debug_id = 0;
124 ++debug_id;
Vitaly Buka1a42e142015-10-10 18:15:15 -0700125 VLOG(1) << "Sending request. id:" << debug_id
126 << " method:" << EnumToString(method_) << " url:" << url_;
Vitaly Buka866b60a2015-10-09 14:24:55 -0700127 VLOG(2) << "Request data: " << data_;
Vitaly Buka74763422015-10-11 00:39:52 -0700128 auto on_done = [](
129 int debug_id, const HttpClient::SendRequestCallback& callback,
130 std::unique_ptr<HttpClient::Response> response, ErrorPtr error) {
131 if (error) {
132 VLOG(1) << "Request failed, id=" << debug_id
133 << ", reason: " << error->GetCode()
134 << ", message: " << error->GetMessage();
135 return callback.Run({}, std::move(error));
136 }
137 VLOG(1) << "Request succeeded. id:" << debug_id
138 << " status:" << response->GetStatusCode();
139 VLOG(2) << "Response data: " << response->GetData();
140 callback.Run(std::move(response), nullptr);
Vitaly Buka866b60a2015-10-09 14:24:55 -0700141 };
142 transport_->SendRequest(method_, url_, GetFullHeaders(), data_,
Vitaly Buka74763422015-10-11 00:39:52 -0700143 base::Bind(on_done, debug_id, callback));
Vitaly Buka6da94252015-08-04 15:45:14 -0700144 }
145
Vitaly Buka815b6032015-08-06 11:06:25 -0700146 void SetAccessToken(const std::string& access_token) {
147 access_token_ = access_token;
Vitaly Buka6da94252015-08-04 15:45:14 -0700148 }
149
150 void SetData(const std::string& data, const std::string& mime_type) {
151 data_ = data;
152 mime_type_ = mime_type;
153 }
154
155 void SetFormData(
156 const std::vector<std::pair<std::string, std::string>>& data) {
Vitaly Buka7d556392015-08-13 20:06:48 -0700157 SetData(WebParamsEncode(data), http::kWwwFormUrlEncoded);
Vitaly Buka6da94252015-08-04 15:45:14 -0700158 }
159
160 void SetJsonData(const base::Value& json) {
161 std::string data;
Vitaly Buka815b6032015-08-06 11:06:25 -0700162 CHECK(base::JSONWriter::Write(json, &data));
163 SetData(data, http::kJsonUtf8);
Vitaly Buka6da94252015-08-04 15:45:14 -0700164 }
165
166 private:
Vitaly Buka815b6032015-08-06 11:06:25 -0700167 HttpClient::Headers GetFullHeaders() const {
168 HttpClient::Headers headers;
169 if (!access_token_.empty())
170 headers.emplace_back(http::kAuthorization, "Bearer " + access_token_);
171 if (!mime_type_.empty())
172 headers.emplace_back(http::kContentType, mime_type_);
173 return headers;
174 }
175
Vitaly Buka1a42e142015-10-10 18:15:15 -0700176 HttpClient::Method method_;
Vitaly Buka6da94252015-08-04 15:45:14 -0700177 std::string url_;
178 std::string data_;
179 std::string mime_type_;
Vitaly Buka815b6032015-08-06 11:06:25 -0700180 std::string access_token_;
Vitaly Buka6da94252015-08-04 15:45:14 -0700181 HttpClient* transport_{nullptr};
182
183 DISALLOW_COPY_AND_ASSIGN(RequestSender);
184};
185
186std::unique_ptr<base::DictionaryValue> ParseJsonResponse(
187 const HttpClient::Response& response,
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700188 ErrorPtr* error) {
Vitaly Buka6da94252015-08-04 15:45:14 -0700189 // Make sure we have a correct content type. Do not try to parse
190 // binary files, or HTML output. Limit to application/json and text/plain.
Vitaly Buka24d6fd52015-08-13 23:22:48 -0700191 std::string content_type =
192 SplitAtFirst(response.GetContentType(), ";", true).first;
Vitaly Buka1da65992015-08-06 01:38:57 -0700193
194 if (content_type != http::kJson && content_type != http::kPlain) {
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700195 Error::AddTo(error, FROM_HERE, errors::json::kDomain,
196 "non_json_content_type",
197 "Unexpected response content type: " + content_type);
Vitaly Buka6da94252015-08-04 15:45:14 -0700198 return std::unique_ptr<base::DictionaryValue>();
199 }
200
201 const std::string& json = response.GetData();
202 std::string error_message;
203 auto value = base::JSONReader::ReadAndReturnError(json, base::JSON_PARSE_RFC,
204 nullptr, &error_message);
205 if (!value) {
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700206 Error::AddToPrintf(error, FROM_HERE, errors::json::kDomain,
207 errors::json::kParseError,
208 "Error '%s' occurred parsing JSON string '%s'",
209 error_message.c_str(), json.c_str());
Vitaly Buka6da94252015-08-04 15:45:14 -0700210 return std::unique_ptr<base::DictionaryValue>();
211 }
212 base::DictionaryValue* dict_value = nullptr;
213 if (!value->GetAsDictionary(&dict_value)) {
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700214 Error::AddToPrintf(
Vitaly Bukaea2f15f2015-08-13 15:26:20 -0700215 error, FROM_HERE, errors::json::kDomain, errors::json::kObjectExpected,
Vitaly Buka6da94252015-08-04 15:45:14 -0700216 "Response is not a valid JSON object: '%s'", json.c_str());
217 return std::unique_ptr<base::DictionaryValue>();
218 } else {
219 // |value| is now owned by |dict_value|, so release the scoped_ptr now.
220 base::IgnoreResult(value.release());
221 }
222 return std::unique_ptr<base::DictionaryValue>(dict_value);
223}
224
225bool IsSuccessful(const HttpClient::Response& response) {
226 int code = response.GetStatusCode();
Vitaly Buka1da65992015-08-06 01:38:57 -0700227 return code >= http::kContinue && code < http::kBadRequest;
Vitaly Buka6da94252015-08-04 15:45:14 -0700228}
229
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700230} // anonymous namespace
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700231
Alex Vakulenko1f30a622014-07-23 11:13:15 -0700232DeviceRegistrationInfo::DeviceRegistrationInfo(
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700233 const std::shared_ptr<CommandManager>& command_manager,
Anton Muhin332df192014-11-22 05:59:14 +0400234 const std::shared_ptr<StateManager>& state_manager,
Vitaly Buka1e363672015-09-25 14:01:16 -0700235 std::unique_ptr<Config> config,
236 provider::TaskRunner* task_runner,
237 provider::HttpClient* http_client,
238 provider::Network* network)
Vitaly Buka10206182015-08-05 11:17:43 -0700239 : http_client_{http_client},
Alex Vakulenkoa56a7e62015-06-26 12:59:50 -0700240 task_runner_{task_runner},
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700241 command_manager_{command_manager},
Anton Muhin332df192014-11-22 05:59:14 +0400242 state_manager_{state_manager},
Christopher Wiley583d64b2015-03-24 14:30:17 -0700243 config_{std::move(config)},
Vitaly Bukaddb2e382015-07-31 00:33:31 -0700244 network_{network} {
Vitaly Buka0f80f7c2015-08-13 00:57:25 -0700245 cloud_backoff_policy_.reset(new BackoffEntry::Policy{});
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700246 cloud_backoff_policy_->num_errors_to_ignore = 0;
Alex Vakulenkof61ee012015-06-24 14:21:35 -0700247 cloud_backoff_policy_->initial_delay_ms = 1000;
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700248 cloud_backoff_policy_->multiply_factor = 2.0;
249 cloud_backoff_policy_->jitter_factor = 0.1;
250 cloud_backoff_policy_->maximum_backoff_ms = 30000;
251 cloud_backoff_policy_->entry_lifetime_ms = -1;
252 cloud_backoff_policy_->always_use_initial_delay = false;
Vitaly Buka0f80f7c2015-08-13 00:57:25 -0700253 cloud_backoff_entry_.reset(new BackoffEntry{cloud_backoff_policy_.get()});
254 oauth2_backoff_entry_.reset(new BackoffEntry{cloud_backoff_policy_.get()});
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700255
Vitaly Buka553a7622015-10-05 13:53:20 -0700256 command_manager_->AddCommandDefChanged(
Alex Vakulenko9ea5a322015-04-17 15:35:34 -0700257 base::Bind(&DeviceRegistrationInfo::OnCommandDefsChanged,
258 weak_factory_.GetWeakPtr()));
Vitaly Buka4c981352015-10-01 23:04:24 -0700259 state_manager_->AddChangedCallback(base::Bind(
Vitaly Bukaa647c852015-07-06 14:51:01 -0700260 &DeviceRegistrationInfo::OnStateChanged, weak_factory_.GetWeakPtr()));
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700261}
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700262
Anton Muhin332df192014-11-22 05:59:14 +0400263DeviceRegistrationInfo::~DeviceRegistrationInfo() = default;
264
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700265std::string DeviceRegistrationInfo::GetServiceURL(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700266 const std::string& subpath,
Vitaly Buka7d556392015-08-13 20:06:48 -0700267 const WebParamList& params) const {
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700268 return BuildURL(GetSettings().service_url, subpath, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700269}
270
271std::string DeviceRegistrationInfo::GetDeviceURL(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700272 const std::string& subpath,
Vitaly Buka7d556392015-08-13 20:06:48 -0700273 const WebParamList& params) const {
Johan Euphrosine312c2f52015-09-29 00:04:29 -0700274 CHECK(!GetSettings().cloud_id.empty()) << "Must have a valid device ID";
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700275 return BuildURL(GetSettings().service_url,
Johan Euphrosine312c2f52015-09-29 00:04:29 -0700276 "devices/" + GetSettings().cloud_id + "/" + subpath, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700277}
278
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700279std::string DeviceRegistrationInfo::GetOAuthURL(
280 const std::string& subpath,
Vitaly Buka7d556392015-08-13 20:06:48 -0700281 const WebParamList& params) const {
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700282 return BuildURL(GetSettings().oauth_url, subpath, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700283}
284
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700285void DeviceRegistrationInfo::Start() {
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -0700286 if (HaveRegistrationCredentials()) {
Alex Vakulenkod05725f2015-05-27 15:48:19 -0700287 StartNotificationChannel();
Christopher Wileyba983c82015-03-05 16:32:23 -0800288 // Wait a significant amount of time for local daemons to publish their
289 // state to Buffet before publishing it to the cloud.
290 // TODO(wiley) We could do a lot of things here to either expose this
291 // timeout as a configurable knob or allow local
292 // daemons to signal that their state is up to date so that
293 // we need not wait for them.
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700294 ScheduleCloudConnection(base::TimeDelta::FromSeconds(5));
Christopher Wileyba983c82015-03-05 16:32:23 -0800295 }
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700296}
297
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700298void DeviceRegistrationInfo::ScheduleCloudConnection(
299 const base::TimeDelta& delay) {
Vitaly Bukac3c6dab2015-10-01 19:41:02 -0700300 SetGcdState(GcdState::kConnecting);
Alex Vakulenkoa56a7e62015-06-26 12:59:50 -0700301 if (!task_runner_)
Vitaly Buka0f6b2ec2015-08-20 15:35:19 -0700302 return; // Assume we're in test
Alex Vakulenkoa56a7e62015-06-26 12:59:50 -0700303 task_runner_->PostDelayedTask(
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700304 FROM_HERE,
Vitaly Buka74763422015-10-11 00:39:52 -0700305 base::Bind(&DeviceRegistrationInfo::ConnectToCloud, AsWeakPtr(), nullptr),
306 delay);
Christopher Wileycd419662015-02-06 17:51:43 -0800307}
308
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -0700309bool DeviceRegistrationInfo::HaveRegistrationCredentials() const {
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700310 return !GetSettings().refresh_token.empty() &&
Johan Euphrosine312c2f52015-09-29 00:04:29 -0700311 !GetSettings().cloud_id.empty() &&
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700312 !GetSettings().robot_account.empty();
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -0700313}
314
315bool DeviceRegistrationInfo::VerifyRegistrationCredentials(
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700316 ErrorPtr* error) const {
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -0700317 const bool have_credentials = HaveRegistrationCredentials();
Christopher Wileyc900e482015-02-15 15:42:04 -0800318
Alex Vakulenkoed77a572015-07-15 10:55:43 -0700319 VLOG(2) << "Device registration record "
Christopher Wileyc900e482015-02-15 15:42:04 -0800320 << ((have_credentials) ? "found" : "not found.");
321 if (!have_credentials)
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700322 Error::AddTo(error, FROM_HERE, kErrorDomainGCD, "device_not_registered",
323 "No valid device registration record found");
Christopher Wileyc900e482015-02-15 15:42:04 -0800324 return have_credentials;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700325}
326
Nathan Bullock24d189f2015-02-26 13:09:18 -0500327std::unique_ptr<base::DictionaryValue>
Vitaly Buka6da94252015-08-04 15:45:14 -0700328DeviceRegistrationInfo::ParseOAuthResponse(const HttpClient::Response& response,
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700329 ErrorPtr* error) {
Vitaly Buka6da94252015-08-04 15:45:14 -0700330 int code = response.GetStatusCode();
331 auto resp = ParseJsonResponse(response, error);
Vitaly Buka1da65992015-08-06 01:38:57 -0700332 if (resp && code >= http::kBadRequest) {
Nathan Bullock24d189f2015-02-26 13:09:18 -0500333 std::string error_code, error_message;
334 if (!resp->GetString("error", &error_code)) {
335 error_code = "unexpected_response";
336 }
337 if (error_code == "invalid_grant") {
338 LOG(INFO) << "The device's registration has been revoked.";
Vitaly Bukac3c6dab2015-10-01 19:41:02 -0700339 SetGcdState(GcdState::kInvalidCredentials);
Nathan Bullock24d189f2015-02-26 13:09:18 -0500340 }
341 // I have never actually seen an error_description returned.
342 if (!resp->GetString("error_description", &error_message)) {
343 error_message = "Unexpected OAuth error";
344 }
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700345 Error::AddTo(error, FROM_HERE, kErrorDomainOAuth2, error_code,
346 error_message);
Nathan Bullock24d189f2015-02-26 13:09:18 -0500347 return std::unique_ptr<base::DictionaryValue>();
348 }
349 return resp;
350}
351
Vitaly Buka74763422015-10-11 00:39:52 -0700352void DeviceRegistrationInfo::RefreshAccessToken(const DoneCallback& callback) {
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700353 LOG(INFO) << "Refreshing access token.";
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -0700354
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700355 ErrorPtr error;
Vitaly Buka74763422015-10-11 00:39:52 -0700356 if (!VerifyRegistrationCredentials(&error))
357 return callback.Run(std::move(error));
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -0700358
Alex Vakulenko0f91be82015-07-27 10:11:53 -0700359 if (oauth2_backoff_entry_->ShouldRejectRequest()) {
360 VLOG(1) << "RefreshToken request delayed for "
361 << oauth2_backoff_entry_->GetTimeUntilRelease()
362 << " due to backoff policy";
363 task_runner_->PostDelayedTask(
364 FROM_HERE, base::Bind(&DeviceRegistrationInfo::RefreshAccessToken,
Vitaly Buka74763422015-10-11 00:39:52 -0700365 AsWeakPtr(), callback),
Alex Vakulenko0f91be82015-07-27 10:11:53 -0700366 oauth2_backoff_entry_->GetTimeUntilRelease());
367 return;
368 }
369
Vitaly Buka1a42e142015-10-10 18:15:15 -0700370 RequestSender sender{HttpClient::Method::kPost, GetOAuthURL("token"),
371 http_client_};
Vitaly Buka6da94252015-08-04 15:45:14 -0700372 sender.SetFormData({
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700373 {"refresh_token", GetSettings().refresh_token},
374 {"client_id", GetSettings().client_id},
375 {"client_secret", GetSettings().client_secret},
Vitaly Bukaa647c852015-07-06 14:51:01 -0700376 {"grant_type", "refresh_token"},
Vitaly Buka6da94252015-08-04 15:45:14 -0700377 });
Vitaly Buka74763422015-10-11 00:39:52 -0700378 sender.Send(base::Bind(&DeviceRegistrationInfo::OnRefreshAccessTokenDone,
379 weak_factory_.GetWeakPtr(), callback));
Vitaly Buka866b60a2015-10-09 14:24:55 -0700380 VLOG(1) << "Refresh access token request dispatched";
David Zeuthen390d1912015-03-03 14:54:48 -0500381}
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700382
Vitaly Buka74763422015-10-11 00:39:52 -0700383void DeviceRegistrationInfo::OnRefreshAccessTokenDone(
384 const DoneCallback& callback,
385 std::unique_ptr<HttpClient::Response> response,
386 ErrorPtr error) {
387 if (error) {
388 VLOG(1) << "Refresh access token failed";
389 oauth2_backoff_entry_->InformOfRequest(false);
390 return RefreshAccessToken(callback);
391 }
Vitaly Buka866b60a2015-10-09 14:24:55 -0700392 VLOG(1) << "Refresh access token request completed";
Alex Vakulenko0f91be82015-07-27 10:11:53 -0700393 oauth2_backoff_entry_->InformOfRequest(true);
Vitaly Buka74763422015-10-11 00:39:52 -0700394 auto json = ParseOAuthResponse(*response, &error);
Vitaly Bukaf7f52d42015-10-10 22:43:55 -0700395 if (!json)
Vitaly Buka74763422015-10-11 00:39:52 -0700396 return callback.Run(std::move(error));
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700397
398 int expires_in = 0;
399 if (!json->GetString("access_token", &access_token_) ||
Vitaly Bukaa647c852015-07-06 14:51:01 -0700400 !json->GetInteger("expires_in", &expires_in) || access_token_.empty() ||
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700401 expires_in <= 0) {
402 LOG(ERROR) << "Access token unavailable.";
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700403 Error::AddTo(&error, FROM_HERE, kErrorDomainOAuth2,
404 "unexpected_server_response", "Access token unavailable");
Vitaly Buka74763422015-10-11 00:39:52 -0700405 return callback.Run(std::move(error));
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700406 }
Vitaly Bukaa647c852015-07-06 14:51:01 -0700407 access_token_expiration_ =
408 base::Time::Now() + base::TimeDelta::FromSeconds(expires_in);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700409 LOG(INFO) << "Access token is refreshed for additional " << expires_in
410 << " seconds.";
Nathan Bullockd9e0bcd2015-02-11 11:36:39 -0500411
Alex Vakulenko6b028ae2015-05-29 09:38:59 -0700412 if (primary_notification_channel_ &&
413 !primary_notification_channel_->IsConnected()) {
414 // If we have disconnected channel, it is due to failed credentials.
415 // Now that we have a new access token, retry the connection.
416 StartNotificationChannel();
417 }
Vitaly Buka74763422015-10-11 00:39:52 -0700418 callback.Run(nullptr);
Alex Vakulenko6b028ae2015-05-29 09:38:59 -0700419}
420
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -0700421void DeviceRegistrationInfo::StartNotificationChannel() {
Alex Vakulenko6b028ae2015-05-29 09:38:59 -0700422 if (notification_channel_starting_)
423 return;
424
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -0700425 LOG(INFO) << "Starting notification channel";
426
Vitaly Buka0f6b2ec2015-08-20 15:35:19 -0700427 // If no TaskRunner assume we're in test.
Vitaly Bukaff1d1862015-10-07 20:40:36 -0700428 if (!network_) {
429 LOG(INFO) << "No Network, not starting notification channel";
Nathan Bullockbea91132015-02-19 09:13:33 -0500430 return;
431 }
432
Alex Vakulenkod05725f2015-05-27 15:48:19 -0700433 if (primary_notification_channel_) {
Alex Vakulenko26f557b2015-05-26 16:47:40 -0700434 primary_notification_channel_->Stop();
Alex Vakulenkod05725f2015-05-27 15:48:19 -0700435 primary_notification_channel_.reset();
436 current_notification_channel_ = nullptr;
437 }
438
439 // Start with just regular polling at the pre-configured polling interval.
440 // Once the primary notification channel is connected successfully, it will
441 // call back to OnConnected() and at that time we'll switch to use the
442 // primary channel and switch periodic poll into much more infrequent backup
443 // poll mode.
Vitaly Buka41a90d62015-09-29 16:58:39 -0700444 const base::TimeDelta pull_interval =
445 base::TimeDelta::FromSeconds(kPollingPeriodSeconds);
Alex Vakulenkod05725f2015-05-27 15:48:19 -0700446 if (!pull_channel_) {
Alex Vakulenkoa56a7e62015-06-26 12:59:50 -0700447 pull_channel_.reset(new PullChannel{pull_interval, task_runner_});
Alex Vakulenkod05725f2015-05-27 15:48:19 -0700448 pull_channel_->Start(this);
449 } else {
450 pull_channel_->UpdatePullInterval(pull_interval);
451 }
452 current_notification_channel_ = pull_channel_.get();
453
Alex Vakulenko6b028ae2015-05-29 09:38:59 -0700454 notification_channel_starting_ = true;
Vitaly Buka63cc3d22015-06-23 20:11:36 -0700455 primary_notification_channel_.reset(new XmppChannel{
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700456 GetSettings().robot_account, access_token_, task_runner_, network_});
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -0700457 primary_notification_channel_->Start(this);
Nathan Bullockf12f7f02015-02-20 14:46:53 -0500458}
459
Vitaly Bukac3c6dab2015-10-01 19:41:02 -0700460void DeviceRegistrationInfo::AddGcdStateChangedCallback(
461 const Device::GcdStateChangedCallback& callback) {
462 gcd_state_changed_callbacks_.push_back(callback);
463 callback.Run(gcd_state_);
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700464}
465
Anton Muhind8d32162014-10-02 20:37:00 +0400466std::unique_ptr<base::DictionaryValue>
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700467DeviceRegistrationInfo::BuildDeviceResource(ErrorPtr* error) {
Alex Vakulenko9ea5a322015-04-17 15:35:34 -0700468 // Limit only to commands that are visible to the cloud.
469 auto commands = command_manager_->GetCommandDictionary().GetCommandsAsJson(
470 [](const CommandDefinition* def) { return def->GetVisibility().cloud; },
471 true, error);
Anton Muhind8d32162014-10-02 20:37:00 +0400472 if (!commands)
473 return nullptr;
474
Vitaly Buka95d62562015-10-01 22:05:27 -0700475 std::unique_ptr<base::DictionaryValue> state = state_manager_->GetState();
Vitaly Buka6942e1f2015-07-28 15:33:55 -0700476 CHECK(state);
Anton Muhind8d32162014-10-02 20:37:00 +0400477
478 std::unique_ptr<base::DictionaryValue> resource{new base::DictionaryValue};
Johan Euphrosine312c2f52015-09-29 00:04:29 -0700479 if (!GetSettings().cloud_id.empty())
480 resource->SetString("id", GetSettings().cloud_id);
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700481 resource->SetString("name", GetSettings().name);
482 if (!GetSettings().description.empty())
483 resource->SetString("description", GetSettings().description);
484 if (!GetSettings().location.empty())
485 resource->SetString("location", GetSettings().location);
486 resource->SetString("modelManifestId", GetSettings().model_id);
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -0700487 std::unique_ptr<base::DictionaryValue> channel{new base::DictionaryValue};
Alex Vakulenkod05725f2015-05-27 15:48:19 -0700488 if (current_notification_channel_) {
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -0700489 channel->SetString("supportedType",
Alex Vakulenkod05725f2015-05-27 15:48:19 -0700490 current_notification_channel_->GetName());
491 current_notification_channel_->AddChannelParameters(channel.get());
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -0700492 } else {
Alex Vakulenkod05725f2015-05-27 15:48:19 -0700493 channel->SetString("supportedType", "pull");
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -0700494 }
495 resource->Set("channel", channel.release());
Anton Muhind8d32162014-10-02 20:37:00 +0400496 resource->Set("commandDefs", commands.release());
497 resource->Set("state", state.release());
498
499 return resource;
500}
501
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700502void DeviceRegistrationInfo::GetDeviceInfo(
Vitaly Buka74763422015-10-11 00:39:52 -0700503 const CloudRequestDoneCallback& callback) {
Vitaly Buka11b2f232015-08-20 13:55:41 -0700504 ErrorPtr error;
505 if (!VerifyRegistrationCredentials(&error)) {
Vitaly Buka74763422015-10-11 00:39:52 -0700506 return callback.Run({}, std::move(error));
Vitaly Buka11b2f232015-08-20 13:55:41 -0700507 }
Vitaly Buka74763422015-10-11 00:39:52 -0700508 DoCloudRequest(HttpClient::Method::kGet, GetDeviceURL(), nullptr, callback);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700509}
510
Vitaly Buka74763422015-10-11 00:39:52 -0700511void DeviceRegistrationInfo::RegisterDeviceError(const DoneCallback& callback,
512 ErrorPtr error) {
513 task_runner_->PostDelayedTask(FROM_HERE,
514 base::Bind(callback, base::Passed(&error)), {});
Vitaly Buka4774df22015-10-09 12:36:22 -0700515}
516
Vitaly Buka74763422015-10-11 00:39:52 -0700517void DeviceRegistrationInfo::RegisterDevice(const std::string& ticket_id,
518 const DoneCallback& callback) {
Vitaly Buka12870bd2015-10-08 23:49:39 -0700519 ErrorPtr error;
Anton Muhind8d32162014-10-02 20:37:00 +0400520 std::unique_ptr<base::DictionaryValue> device_draft =
Vitaly Buka12870bd2015-10-08 23:49:39 -0700521 BuildDeviceResource(&error);
Anton Muhind8d32162014-10-02 20:37:00 +0400522 if (!device_draft)
Vitaly Buka74763422015-10-11 00:39:52 -0700523 return RegisterDeviceError(callback, std::move(error));
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700524
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700525 base::DictionaryValue req_json;
Nathan Bullocke4408482015-02-19 11:13:21 -0500526 req_json.SetString("id", ticket_id);
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700527 req_json.SetString("oauthClientId", GetSettings().client_id);
Anton Muhind8d32162014-10-02 20:37:00 +0400528 req_json.Set("deviceDraft", device_draft.release());
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700529
Nathan Bullocke4408482015-02-19 11:13:21 -0500530 auto url = GetServiceURL("registrationTickets/" + ticket_id,
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700531 {{"key", GetSettings().api_key}});
Vitaly Buka6da94252015-08-04 15:45:14 -0700532
Vitaly Buka1a42e142015-10-10 18:15:15 -0700533 RequestSender sender{HttpClient::Method::kPatch, url, http_client_};
Vitaly Buka6da94252015-08-04 15:45:14 -0700534 sender.SetJsonData(req_json);
Vitaly Buka4774df22015-10-09 12:36:22 -0700535 sender.Send(base::Bind(&DeviceRegistrationInfo::RegisterDeviceOnTicketSent,
Vitaly Buka74763422015-10-11 00:39:52 -0700536 weak_factory_.GetWeakPtr(), ticket_id, callback));
Vitaly Buka4774df22015-10-09 12:36:22 -0700537}
Vitaly Buka6da94252015-08-04 15:45:14 -0700538
Vitaly Buka4774df22015-10-09 12:36:22 -0700539void DeviceRegistrationInfo::RegisterDeviceOnTicketSent(
540 const std::string& ticket_id,
Vitaly Buka74763422015-10-11 00:39:52 -0700541 const DoneCallback& callback,
542 std::unique_ptr<provider::HttpClient::Response> response,
543 ErrorPtr error) {
544 if (error)
545 return RegisterDeviceError(callback, std::move(error));
546 auto json_resp = ParseJsonResponse(*response, &error);
Anton Muhin532ff7e2014-09-29 23:21:21 +0400547 if (!json_resp)
Vitaly Buka74763422015-10-11 00:39:52 -0700548 return RegisterDeviceError(callback, std::move(error));
Vitaly Buka4774df22015-10-09 12:36:22 -0700549
Vitaly Buka74763422015-10-11 00:39:52 -0700550 if (!IsSuccessful(*response)) {
Vitaly Buka12870bd2015-10-08 23:49:39 -0700551 ParseGCDError(json_resp.get(), &error);
Vitaly Buka74763422015-10-11 00:39:52 -0700552 return RegisterDeviceError(callback, std::move(error));
David Zeuthen1dbad472015-02-12 15:24:21 -0500553 }
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700554
Vitaly Buka4774df22015-10-09 12:36:22 -0700555 std::string url =
556 GetServiceURL("registrationTickets/" + ticket_id + "/finalize",
557 {{"key", GetSettings().api_key}});
Vitaly Buka1a42e142015-10-10 18:15:15 -0700558 RequestSender{HttpClient::Method::kPost, url, http_client_}.Send(
Vitaly Buka4774df22015-10-09 12:36:22 -0700559 base::Bind(&DeviceRegistrationInfo::RegisterDeviceOnTicketFinalized,
Vitaly Buka74763422015-10-11 00:39:52 -0700560 weak_factory_.GetWeakPtr(), callback));
Vitaly Buka4774df22015-10-09 12:36:22 -0700561}
562
563void DeviceRegistrationInfo::RegisterDeviceOnTicketFinalized(
Vitaly Buka74763422015-10-11 00:39:52 -0700564 const DoneCallback& callback,
565 std::unique_ptr<provider::HttpClient::Response> response,
566 ErrorPtr error) {
567 if (error)
568 return RegisterDeviceError(callback, std::move(error));
569 auto json_resp = ParseJsonResponse(*response, &error);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700570 if (!json_resp)
Vitaly Buka74763422015-10-11 00:39:52 -0700571 return RegisterDeviceError(callback, std::move(error));
572 if (!IsSuccessful(*response)) {
Vitaly Buka12870bd2015-10-08 23:49:39 -0700573 ParseGCDError(json_resp.get(), &error);
Vitaly Buka74763422015-10-11 00:39:52 -0700574 return RegisterDeviceError(callback, std::move(error));
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700575 }
Anton Muhin532ff7e2014-09-29 23:21:21 +0400576
577 std::string auth_code;
Johan Euphrosine312c2f52015-09-29 00:04:29 -0700578 std::string cloud_id;
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700579 std::string robot_account;
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700580 const base::DictionaryValue* device_draft_response = nullptr;
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700581 if (!json_resp->GetString("robotAccountEmail", &robot_account) ||
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700582 !json_resp->GetString("robotAccountAuthorizationCode", &auth_code) ||
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700583 !json_resp->GetDictionary("deviceDraft", &device_draft_response) ||
Johan Euphrosine312c2f52015-09-29 00:04:29 -0700584 !device_draft_response->GetString("id", &cloud_id)) {
Vitaly Buka12870bd2015-10-08 23:49:39 -0700585 Error::AddTo(&error, FROM_HERE, kErrorDomainGCD, "unexpected_response",
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700586 "Device account missing in response");
Vitaly Buka74763422015-10-11 00:39:52 -0700587 return RegisterDeviceError(callback, std::move(error));
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700588 }
589
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700590 UpdateDeviceInfoTimestamp(*device_draft_response);
591
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700592 // Now get access_token and refresh_token
Vitaly Buka1a42e142015-10-10 18:15:15 -0700593 RequestSender sender2{HttpClient::Method::kPost, GetOAuthURL("token"),
594 http_client_};
Vitaly Buka6da94252015-08-04 15:45:14 -0700595 sender2.SetFormData(
Vitaly Bukaa647c852015-07-06 14:51:01 -0700596 {{"code", auth_code},
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700597 {"client_id", GetSettings().client_id},
598 {"client_secret", GetSettings().client_secret},
Vitaly Bukaa647c852015-07-06 14:51:01 -0700599 {"redirect_uri", "oob"},
600 {"scope", "https://www.googleapis.com/auth/clouddevices"},
Vitaly Buka6da94252015-08-04 15:45:14 -0700601 {"grant_type", "authorization_code"}});
Vitaly Buka4774df22015-10-09 12:36:22 -0700602 sender2.Send(base::Bind(&DeviceRegistrationInfo::RegisterDeviceOnAuthCodeSent,
603 weak_factory_.GetWeakPtr(), cloud_id, robot_account,
Vitaly Buka74763422015-10-11 00:39:52 -0700604 callback));
Vitaly Buka4774df22015-10-09 12:36:22 -0700605}
Vitaly Buka6da94252015-08-04 15:45:14 -0700606
Vitaly Buka4774df22015-10-09 12:36:22 -0700607void DeviceRegistrationInfo::RegisterDeviceOnAuthCodeSent(
608 const std::string& cloud_id,
609 const std::string& robot_account,
Vitaly Buka74763422015-10-11 00:39:52 -0700610 const DoneCallback& callback,
611 std::unique_ptr<provider::HttpClient::Response> response,
612 ErrorPtr error) {
613 if (error)
614 return RegisterDeviceError(callback, std::move(error));
615 auto json_resp = ParseOAuthResponse(*response, &error);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700616 int expires_in = 0;
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700617 std::string refresh_token;
618 if (!json_resp || !json_resp->GetString("access_token", &access_token_) ||
619 !json_resp->GetString("refresh_token", &refresh_token) ||
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700620 !json_resp->GetInteger("expires_in", &expires_in) ||
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700621 access_token_.empty() || refresh_token.empty() || expires_in <= 0) {
Vitaly Buka12870bd2015-10-08 23:49:39 -0700622 Error::AddTo(&error, FROM_HERE, kErrorDomainGCD, "unexpected_response",
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700623 "Device access_token missing in response");
Vitaly Buka74763422015-10-11 00:39:52 -0700624 return RegisterDeviceError(callback, std::move(error));
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700625 }
626
Vitaly Bukaa647c852015-07-06 14:51:01 -0700627 access_token_expiration_ =
628 base::Time::Now() + base::TimeDelta::FromSeconds(expires_in);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700629
Vitaly Bukac11a17d2015-08-15 10:36:10 -0700630 Config::Transaction change{config_.get()};
Johan Euphrosine312c2f52015-09-29 00:04:29 -0700631 change.set_cloud_id(cloud_id);
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700632 change.set_robot_account(robot_account);
633 change.set_refresh_token(refresh_token);
634 change.Commit();
635
Vitaly Buka74763422015-10-11 00:39:52 -0700636 task_runner_->PostDelayedTask(FROM_HERE, base::Bind(callback, nullptr), {});
Vitaly Buka12870bd2015-10-08 23:49:39 -0700637
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -0700638 StartNotificationChannel();
Christopher Wileycd419662015-02-06 17:51:43 -0800639
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700640 // We're going to respond with our success immediately and we'll connect to
641 // cloud shortly after.
Vitaly Buka12870bd2015-10-08 23:49:39 -0700642 ScheduleCloudConnection({});
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700643}
644
Anton Muhinac661ab2014-10-03 20:29:48 +0400645void DeviceRegistrationInfo::DoCloudRequest(
Vitaly Buka1a42e142015-10-10 18:15:15 -0700646 HttpClient::Method method,
Anton Muhinac661ab2014-10-03 20:29:48 +0400647 const std::string& url,
648 const base::DictionaryValue* body,
Vitaly Buka74763422015-10-11 00:39:52 -0700649 const CloudRequestDoneCallback& callback) {
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700650 // We make CloudRequestData shared here because we want to make sure
Vitaly Buka74763422015-10-11 00:39:52 -0700651 // there is only one instance of callback and error_calback since
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700652 // those may have move-only types and making a copy of the callback with
653 // move-only types curried-in will invalidate the source callback.
654 auto data = std::make_shared<CloudRequestData>();
655 data->method = method;
656 data->url = url;
657 if (body)
658 base::JSONWriter::Write(*body, &data->body);
Vitaly Buka74763422015-10-11 00:39:52 -0700659 data->callback = callback;
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700660 SendCloudRequest(data);
661}
662
663void DeviceRegistrationInfo::SendCloudRequest(
664 const std::shared_ptr<const CloudRequestData>& data) {
Alex Vakulenko0357c032015-01-06 16:32:31 -0800665 // TODO(antonm): Add reauthorization on access token expiration (do not
Anton Muhinac661ab2014-10-03 20:29:48 +0400666 // forget about 5xx when fetching new access token).
667 // TODO(antonm): Add support for device removal.
668
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700669 ErrorPtr error;
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -0700670 if (!VerifyRegistrationCredentials(&error)) {
Vitaly Buka74763422015-10-11 00:39:52 -0700671 return data->callback.Run({}, std::move(error));
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700672 }
Anton Muhinac661ab2014-10-03 20:29:48 +0400673
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700674 if (cloud_backoff_entry_->ShouldRejectRequest()) {
675 VLOG(1) << "Cloud request delayed for "
676 << cloud_backoff_entry_->GetTimeUntilRelease()
677 << " due to backoff policy";
Vitaly Bukaf7f52d42015-10-10 22:43:55 -0700678 return task_runner_->PostDelayedTask(
Vitaly Bukaa647c852015-07-06 14:51:01 -0700679 FROM_HERE, base::Bind(&DeviceRegistrationInfo::SendCloudRequest,
680 AsWeakPtr(), data),
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700681 cloud_backoff_entry_->GetTimeUntilRelease());
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700682 }
Anton Muhinac661ab2014-10-03 20:29:48 +0400683
Vitaly Buka6da94252015-08-04 15:45:14 -0700684 RequestSender sender{data->method, data->url, http_client_};
Vitaly Buka1da65992015-08-06 01:38:57 -0700685 sender.SetData(data->body, http::kJsonUtf8);
Vitaly Buka815b6032015-08-06 11:06:25 -0700686 sender.SetAccessToken(access_token_);
Vitaly Buka74763422015-10-11 00:39:52 -0700687 sender.Send(base::Bind(&DeviceRegistrationInfo::OnCloudRequestDone,
Vitaly Buka866b60a2015-10-09 14:24:55 -0700688 AsWeakPtr(), data));
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700689}
Anton Muhinac661ab2014-10-03 20:29:48 +0400690
Vitaly Buka74763422015-10-11 00:39:52 -0700691void DeviceRegistrationInfo::OnCloudRequestDone(
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700692 const std::shared_ptr<const CloudRequestData>& data,
Vitaly Buka74763422015-10-11 00:39:52 -0700693 std::unique_ptr<provider::HttpClient::Response> response,
694 ErrorPtr error) {
695 if (error)
696 return RetryCloudRequest(data);
697 int status_code = response->GetStatusCode();
Vitaly Buka1da65992015-08-06 01:38:57 -0700698 if (status_code == http::kDenied) {
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700699 cloud_backoff_entry_->InformOfRequest(true);
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700700 RefreshAccessToken(
701 base::Bind(&DeviceRegistrationInfo::OnAccessTokenRefreshed, AsWeakPtr(),
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700702 data));
703 return;
704 }
Anton Muhinac661ab2014-10-03 20:29:48 +0400705
Vitaly Buka1da65992015-08-06 01:38:57 -0700706 if (status_code >= http::kInternalServerError) {
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700707 // Request was valid, but server failed, retry.
708 // TODO(antonm): Reconsider status codes, maybe only some require
709 // retry.
710 // TODO(antonm): Support Retry-After header.
711 RetryCloudRequest(data);
712 return;
713 }
Anton Muhin633eded2014-10-03 20:40:10 +0400714
Vitaly Buka74763422015-10-11 00:39:52 -0700715 auto json_resp = ParseJsonResponse(*response, &error);
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700716 if (!json_resp) {
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700717 cloud_backoff_entry_->InformOfRequest(true);
Vitaly Buka74763422015-10-11 00:39:52 -0700718 return data->callback.Run({}, std::move(error));
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700719 }
Anton Muhin233d2ee2014-10-22 15:16:24 +0400720
Vitaly Buka74763422015-10-11 00:39:52 -0700721 if (!IsSuccessful(*response)) {
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700722 ParseGCDError(json_resp.get(), &error);
Vitaly Buka1da65992015-08-06 01:38:57 -0700723 if (status_code == http::kForbidden &&
Vitaly Bukab6f015a2015-07-09 14:59:23 -0700724 error->HasError(kErrorDomainGCDServer, "rateLimitExceeded")) {
Alex Vakulenkof61ee012015-06-24 14:21:35 -0700725 // If we exceeded server quota, retry the request later.
Vitaly Bukaf7f52d42015-10-10 22:43:55 -0700726 return RetryCloudRequest(data);
Alex Vakulenkof61ee012015-06-24 14:21:35 -0700727 }
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700728 cloud_backoff_entry_->InformOfRequest(true);
Vitaly Buka74763422015-10-11 00:39:52 -0700729 return data->callback.Run({}, std::move(error));
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700730 }
731
Alex Vakulenkof61ee012015-06-24 14:21:35 -0700732 cloud_backoff_entry_->InformOfRequest(true);
Vitaly Bukac3c6dab2015-10-01 19:41:02 -0700733 SetGcdState(GcdState::kConnected);
Vitaly Buka74763422015-10-11 00:39:52 -0700734 data->callback.Run(*json_resp, nullptr);
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700735}
736
737void DeviceRegistrationInfo::RetryCloudRequest(
738 const std::shared_ptr<const CloudRequestData>& data) {
Alex Vakulenkof61ee012015-06-24 14:21:35 -0700739 // TODO(avakulenko): Tie connecting/connected status to XMPP channel instead.
Vitaly Bukac3c6dab2015-10-01 19:41:02 -0700740 SetGcdState(GcdState::kConnecting);
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700741 cloud_backoff_entry_->InformOfRequest(false);
742 SendCloudRequest(data);
743}
744
745void DeviceRegistrationInfo::OnAccessTokenRefreshed(
Vitaly Buka74763422015-10-11 00:39:52 -0700746 const std::shared_ptr<const CloudRequestData>& data,
747 ErrorPtr error) {
748 if (error) {
749 CheckAccessTokenError(error->Clone());
750 return data->callback.Run({}, std::move(error));
751 }
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700752 SendCloudRequest(data);
753}
754
Vitaly Bukaf7f52d42015-10-10 22:43:55 -0700755void DeviceRegistrationInfo::CheckAccessTokenError(ErrorPtr error) {
Vitaly Buka74763422015-10-11 00:39:52 -0700756 if (error && error->HasError(kErrorDomainOAuth2, "invalid_grant"))
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700757 MarkDeviceUnregistered();
758}
759
Vitaly Buka74763422015-10-11 00:39:52 -0700760void DeviceRegistrationInfo::ConnectToCloud(ErrorPtr error) {
761 if (error) {
762 if (error->HasError(kErrorDomainOAuth2, "invalid_grant"))
763 MarkDeviceUnregistered();
764 return;
765 }
766
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700767 connected_to_cloud_ = false;
768 if (!VerifyRegistrationCredentials(nullptr))
Anton Muhind8d32162014-10-02 20:37:00 +0400769 return;
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700770
771 if (access_token_.empty()) {
772 RefreshAccessToken(
Vitaly Buka74763422015-10-11 00:39:52 -0700773 base::Bind(&DeviceRegistrationInfo::ConnectToCloud, AsWeakPtr()));
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700774 return;
775 }
776
777 // Connecting a device to cloud just means that we:
Christopher Wileyba983c82015-03-05 16:32:23 -0800778 // 1) push an updated device resource
779 // 2) fetch an initial set of outstanding commands
780 // 3) abort any commands that we've previously marked as "in progress"
Alex Vakulenkod05725f2015-05-27 15:48:19 -0700781 // or as being in an error state; publish queued commands
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700782 UpdateDeviceResource(
Vitaly Buka74763422015-10-11 00:39:52 -0700783 base::Bind(&DeviceRegistrationInfo::OnConnectedToCloud, AsWeakPtr()));
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700784}
785
Vitaly Buka74763422015-10-11 00:39:52 -0700786void DeviceRegistrationInfo::OnConnectedToCloud(ErrorPtr error) {
787 if (error)
788 return;
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700789 LOG(INFO) << "Device connected to cloud server";
790 connected_to_cloud_ = true;
791 FetchCommands(base::Bind(&DeviceRegistrationInfo::ProcessInitialCommandList,
Vitaly Buka74763422015-10-11 00:39:52 -0700792 AsWeakPtr()));
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700793 // In case there are any pending state updates since we sent off the initial
794 // UpdateDeviceResource() request, update the server with any state changes.
795 PublishStateUpdates();
Anton Muhinc635c592014-10-28 21:48:08 +0400796}
797
Vitaly Bukab624bc42015-09-29 19:13:55 -0700798void DeviceRegistrationInfo::UpdateDeviceInfo(const std::string& name,
Vitaly Bukafa947062015-04-17 00:41:31 -0700799 const std::string& description,
Vitaly Bukab624bc42015-09-29 19:13:55 -0700800 const std::string& location) {
Vitaly Bukac11a17d2015-08-15 10:36:10 -0700801 Config::Transaction change{config_.get()};
Vitaly Buka798a0e72015-06-02 15:37:51 -0700802 change.set_name(name);
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700803 change.set_description(description);
804 change.set_location(location);
Vitaly Bukaff81db62015-05-14 21:25:45 -0700805 change.Commit();
Vitaly Bukafa947062015-04-17 00:41:31 -0700806
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -0700807 if (HaveRegistrationCredentials()) {
Vitaly Buka74763422015-10-11 00:39:52 -0700808 UpdateDeviceResource(base::Bind(&IgnoreCloudError));
Vitaly Bukafa947062015-04-17 00:41:31 -0700809 }
Vitaly Bukafa947062015-04-17 00:41:31 -0700810}
811
Vitaly Bukab624bc42015-09-29 19:13:55 -0700812void DeviceRegistrationInfo::UpdateBaseConfig(AuthScope anonymous_access_role,
813 bool local_discovery_enabled,
814 bool local_pairing_enabled) {
Vitaly Bukac11a17d2015-08-15 10:36:10 -0700815 Config::Transaction change(config_.get());
Vitaly Bukab624bc42015-09-29 19:13:55 -0700816 change.set_local_anonymous_access_role(anonymous_access_role);
Vitaly Buka2f7efdb2015-05-27 16:00:21 -0700817 change.set_local_discovery_enabled(local_discovery_enabled);
818 change.set_local_pairing_enabled(local_pairing_enabled);
Vitaly Buka2f7efdb2015-05-27 16:00:21 -0700819}
820
Vitaly Bukaff81db62015-05-14 21:25:45 -0700821bool DeviceRegistrationInfo::UpdateServiceConfig(
822 const std::string& client_id,
823 const std::string& client_secret,
824 const std::string& api_key,
825 const std::string& oauth_url,
826 const std::string& service_url,
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700827 ErrorPtr* error) {
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -0700828 if (HaveRegistrationCredentials()) {
Vitaly Bukae2810e02015-08-16 23:31:55 -0700829 Error::AddTo(error, FROM_HERE, errors::kErrorDomain, "already_registered",
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700830 "Unable to change config for registered device");
Vitaly Bukaff81db62015-05-14 21:25:45 -0700831 return false;
832 }
Vitaly Bukac11a17d2015-08-15 10:36:10 -0700833 Config::Transaction change{config_.get()};
Vitaly Bukaff81db62015-05-14 21:25:45 -0700834 change.set_client_id(client_id);
835 change.set_client_secret(client_secret);
836 change.set_api_key(api_key);
837 change.set_oauth_url(oauth_url);
838 change.set_service_url(service_url);
839 return true;
840}
841
Anton Muhin59755522014-11-05 21:30:12 +0400842void DeviceRegistrationInfo::UpdateCommand(
843 const std::string& command_id,
Alex Vakulenkob211c102015-04-21 11:43:23 -0700844 const base::DictionaryValue& command_patch,
Vitaly Buka74763422015-10-11 00:39:52 -0700845 const DoneCallback& callback) {
Vitaly Buka1a42e142015-10-10 18:15:15 -0700846 DoCloudRequest(HttpClient::Method::kPatch,
847 GetServiceURL("commands/" + command_id), &command_patch,
Vitaly Buka74763422015-10-11 00:39:52 -0700848 base::Bind(&IgnoreCloudResultWithCallback, callback));
Anton Muhin59755522014-11-05 21:30:12 +0400849}
850
Vitaly Bukaa647c852015-07-06 14:51:01 -0700851void DeviceRegistrationInfo::NotifyCommandAborted(const std::string& command_id,
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700852 ErrorPtr error) {
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700853 base::DictionaryValue command_patch;
854 command_patch.SetString(commands::attributes::kCommand_State,
Vitaly Buka0209da42015-10-08 00:07:18 -0700855 EnumToString(Command::State::kAborted));
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700856 if (error) {
Vitaly Buka70f77d92015-10-07 15:42:40 -0700857 command_patch.Set(commands::attributes::kCommand_Error,
858 ErrorInfoToJson(*error).release());
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700859 }
Vitaly Buka74763422015-10-11 00:39:52 -0700860 UpdateCommand(command_id, command_patch, base::Bind(&IgnoreCloudError));
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700861}
862
Christopher Wileyba983c82015-03-05 16:32:23 -0800863void DeviceRegistrationInfo::UpdateDeviceResource(
Vitaly Buka74763422015-10-11 00:39:52 -0700864 const DoneCallback& callback) {
865 queued_resource_update_callbacks_.emplace_back(callback);
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700866 if (!in_progress_resource_update_callbacks_.empty()) {
867 VLOG(1) << "Another request is already pending.";
Anton Muhind8d32162014-10-02 20:37:00 +0400868 return;
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700869 }
870
871 StartQueuedUpdateDeviceResource();
872}
873
874void DeviceRegistrationInfo::StartQueuedUpdateDeviceResource() {
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700875 if (in_progress_resource_update_callbacks_.empty() &&
876 queued_resource_update_callbacks_.empty())
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700877 return;
878
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700879 if (last_device_resource_updated_timestamp_.empty()) {
880 // We don't know the current time stamp of the device resource from the
881 // server side. We need to provide the time stamp to the server as part of
882 // the request to guard against out-of-order requests overwriting settings
883 // specified by later requests.
884 VLOG(1) << "Getting the last device resource timestamp from server...";
Vitaly Buka74763422015-10-11 00:39:52 -0700885 GetDeviceInfo(base::Bind(&DeviceRegistrationInfo::OnDeviceInfoRetrieved,
886 AsWeakPtr()));
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700887 return;
888 }
889
890 in_progress_resource_update_callbacks_.insert(
891 in_progress_resource_update_callbacks_.end(),
892 queued_resource_update_callbacks_.begin(),
893 queued_resource_update_callbacks_.end());
894 queued_resource_update_callbacks_.clear();
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700895
896 VLOG(1) << "Updating GCD server with CDD...";
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700897 ErrorPtr error;
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700898 std::unique_ptr<base::DictionaryValue> device_resource =
899 BuildDeviceResource(&error);
900 if (!device_resource) {
Vitaly Bukaf7f52d42015-10-10 22:43:55 -0700901 return OnUpdateDeviceResourceError(std::move(error));
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700902 }
Anton Muhind8d32162014-10-02 20:37:00 +0400903
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700904 std::string url = GetDeviceURL(
905 {}, {{"lastUpdateTimeMs", last_device_resource_updated_timestamp_}});
906
Vitaly Buka74763422015-10-11 00:39:52 -0700907 DoCloudRequest(HttpClient::Method::kPut, url, device_resource.get(),
908 base::Bind(&DeviceRegistrationInfo::OnUpdateDeviceResourceDone,
909 AsWeakPtr()));
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700910}
911
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700912void DeviceRegistrationInfo::OnDeviceInfoRetrieved(
Vitaly Buka74763422015-10-11 00:39:52 -0700913 const base::DictionaryValue& device_info,
914 ErrorPtr error) {
915 if (error)
916 return OnUpdateDeviceResourceError(std::move(error));
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700917 if (UpdateDeviceInfoTimestamp(device_info))
918 StartQueuedUpdateDeviceResource();
919}
920
921bool DeviceRegistrationInfo::UpdateDeviceInfoTimestamp(
922 const base::DictionaryValue& device_info) {
923 // For newly created devices, "lastUpdateTimeMs" may not be present, but
924 // "creationTimeMs" should be there at least.
925 if (!device_info.GetString("lastUpdateTimeMs",
926 &last_device_resource_updated_timestamp_) &&
927 !device_info.GetString("creationTimeMs",
928 &last_device_resource_updated_timestamp_)) {
929 LOG(WARNING) << "Device resource timestamp is missing";
930 return false;
931 }
932 return true;
933}
934
Vitaly Buka74763422015-10-11 00:39:52 -0700935void DeviceRegistrationInfo::OnUpdateDeviceResourceDone(
936 const base::DictionaryValue& device_info,
937 ErrorPtr error) {
938 if (error)
939 return OnUpdateDeviceResourceError(std::move(error));
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700940 UpdateDeviceInfoTimestamp(device_info);
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700941 // Make a copy of the callback list so that if the callback triggers another
942 // call to UpdateDeviceResource(), we do not modify the list we are iterating
943 // over.
944 auto callback_list = std::move(in_progress_resource_update_callbacks_);
Vitaly Buka74763422015-10-11 00:39:52 -0700945 for (const auto& callback : callback_list)
946 callback.Run(nullptr);
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700947 StartQueuedUpdateDeviceResource();
948}
949
Vitaly Bukaf7f52d42015-10-10 22:43:55 -0700950void DeviceRegistrationInfo::OnUpdateDeviceResourceError(ErrorPtr error) {
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700951 if (error->HasError(kErrorDomainGCDServer, "invalid_last_update_time_ms")) {
952 // If the server rejected our previous request, retrieve the latest
953 // timestamp from the server and retry.
954 VLOG(1) << "Getting the last device resource timestamp from server...";
Vitaly Buka74763422015-10-11 00:39:52 -0700955 GetDeviceInfo(base::Bind(&DeviceRegistrationInfo::OnDeviceInfoRetrieved,
956 AsWeakPtr()));
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700957 return;
958 }
959
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700960 // Make a copy of the callback list so that if the callback triggers another
961 // call to UpdateDeviceResource(), we do not modify the list we are iterating
962 // over.
963 auto callback_list = std::move(in_progress_resource_update_callbacks_);
Vitaly Buka74763422015-10-11 00:39:52 -0700964 for (const auto& callback : callback_list)
965 callback.Run(error->Clone());
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700966
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700967 StartQueuedUpdateDeviceResource();
Anton Muhinc635c592014-10-28 21:48:08 +0400968}
Anton Muhina34f0d92014-10-03 21:09:40 +0400969
Vitaly Buka74763422015-10-11 00:39:52 -0700970void DeviceRegistrationInfo::OnFetchCommandsDone(
971 const base::Callback<void(const base::ListValue&, ErrorPtr)>& callback,
972 const base::DictionaryValue& json,
973 ErrorPtr error) {
Alex Vakulenko64bc9ea2015-09-14 09:24:58 -0700974 OnFetchCommandsReturned();
Vitaly Buka74763422015-10-11 00:39:52 -0700975 if (error)
976 return callback.Run({}, std::move(error));
Christopher Wileyba983c82015-03-05 16:32:23 -0800977 const base::ListValue* commands{nullptr};
Vitaly Buka74763422015-10-11 00:39:52 -0700978 if (!json.GetList("commands", &commands))
Alex Vakulenkoed77a572015-07-15 10:55:43 -0700979 VLOG(2) << "No commands in the response.";
Christopher Wileyba983c82015-03-05 16:32:23 -0800980 const base::ListValue empty;
Vitaly Buka74763422015-10-11 00:39:52 -0700981 callback.Run(commands ? *commands : empty, nullptr);
Alex Vakulenko64bc9ea2015-09-14 09:24:58 -0700982}
983
984void DeviceRegistrationInfo::OnFetchCommandsReturned() {
985 fetch_commands_request_sent_ = false;
986 // If we have additional requests queued, send them out now.
987 if (fetch_commands_request_queued_)
988 FetchAndPublishCommands();
989}
Christopher Wileyba983c82015-03-05 16:32:23 -0800990
Anton Muhinc635c592014-10-28 21:48:08 +0400991void DeviceRegistrationInfo::FetchCommands(
Vitaly Buka74763422015-10-11 00:39:52 -0700992 const base::Callback<void(const base::ListValue&, ErrorPtr error)>&
993 callback) {
Alex Vakulenko64bc9ea2015-09-14 09:24:58 -0700994 fetch_commands_request_sent_ = true;
995 fetch_commands_request_queued_ = false;
Anton Muhinc635c592014-10-28 21:48:08 +0400996 DoCloudRequest(
Vitaly Buka1a42e142015-10-10 18:15:15 -0700997 HttpClient::Method::kGet,
Johan Euphrosine312c2f52015-09-29 00:04:29 -0700998 GetServiceURL("commands/queue", {{"deviceId", GetSettings().cloud_id}}),
Vitaly Buka74763422015-10-11 00:39:52 -0700999 nullptr, base::Bind(&DeviceRegistrationInfo::OnFetchCommandsDone,
1000 AsWeakPtr(), callback));
Anton Muhinc635c592014-10-28 21:48:08 +04001001}
Anton Muhina34f0d92014-10-03 21:09:40 +04001002
Alex Vakulenko1038ec12015-07-15 14:36:13 -07001003void DeviceRegistrationInfo::FetchAndPublishCommands() {
Alex Vakulenko64bc9ea2015-09-14 09:24:58 -07001004 if (fetch_commands_request_sent_) {
1005 fetch_commands_request_queued_ = true;
1006 return;
1007 }
1008
Alex Vakulenko1038ec12015-07-15 14:36:13 -07001009 FetchCommands(base::Bind(&DeviceRegistrationInfo::PublishCommands,
Vitaly Buka74763422015-10-11 00:39:52 -07001010 weak_factory_.GetWeakPtr()));
Alex Vakulenko1038ec12015-07-15 14:36:13 -07001011}
1012
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001013void DeviceRegistrationInfo::ProcessInitialCommandList(
Vitaly Buka74763422015-10-11 00:39:52 -07001014 const base::ListValue& commands,
1015 ErrorPtr error) {
1016 if (error)
1017 return;
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001018 for (const base::Value* command : commands) {
1019 const base::DictionaryValue* command_dict{nullptr};
1020 if (!command->GetAsDictionary(&command_dict)) {
1021 LOG(WARNING) << "Not a command dictionary: " << *command;
Anton Muhinc635c592014-10-28 21:48:08 +04001022 continue;
Anton Muhina34f0d92014-10-03 21:09:40 +04001023 }
Anton Muhinc635c592014-10-28 21:48:08 +04001024 std::string command_state;
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001025 if (!command_dict->GetString("state", &command_state)) {
1026 LOG(WARNING) << "Command with no state at " << *command;
Anton Muhinc635c592014-10-28 21:48:08 +04001027 continue;
1028 }
Vitaly Bukaa647c852015-07-06 14:51:01 -07001029 if (command_state == "error" && command_state == "inProgress" &&
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001030 command_state == "paused") {
1031 // It's a limbo command, abort it.
1032 std::string command_id;
1033 if (!command_dict->GetString("id", &command_id)) {
1034 LOG(WARNING) << "Command with no ID at " << *command;
1035 continue;
1036 }
Anton Muhin6d2569e2014-10-30 12:32:27 +04001037
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001038 std::unique_ptr<base::DictionaryValue> cmd_copy{command_dict->DeepCopy()};
1039 cmd_copy->SetString("state", "aborted");
1040 // TODO(wiley) We could consider handling this error case more gracefully.
Vitaly Buka1a42e142015-10-10 18:15:15 -07001041 DoCloudRequest(HttpClient::Method::kPut,
1042 GetServiceURL("commands/" + command_id), cmd_copy.get(),
Vitaly Buka74763422015-10-11 00:39:52 -07001043 base::Bind(&IgnoreCloudResult));
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001044 } else {
1045 // Normal command, publish it to local clients.
1046 PublishCommand(*command_dict);
1047 }
Anton Muhinc635c592014-10-28 21:48:08 +04001048 }
Anton Muhind07e2062014-10-27 10:53:29 +04001049}
1050
Vitaly Buka74763422015-10-11 00:39:52 -07001051void DeviceRegistrationInfo::PublishCommands(const base::ListValue& commands,
1052 ErrorPtr error) {
1053 if (error)
1054 return;
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001055 for (const base::Value* command : commands) {
1056 const base::DictionaryValue* command_dict{nullptr};
1057 if (!command->GetAsDictionary(&command_dict)) {
1058 LOG(WARNING) << "Not a command dictionary: " << *command;
Anton Muhind07e2062014-10-27 10:53:29 +04001059 continue;
1060 }
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001061 PublishCommand(*command_dict);
1062 }
1063}
Anton Muhind07e2062014-10-27 10:53:29 +04001064
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001065void DeviceRegistrationInfo::PublishCommand(
1066 const base::DictionaryValue& command) {
1067 std::string command_id;
Vitaly Buka0801a1f2015-08-14 10:03:46 -07001068 ErrorPtr error;
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001069 auto command_instance = CommandInstance::FromJson(
Vitaly Buka0209da42015-10-08 00:07:18 -07001070 &command, Command::Origin::kCloud,
1071 command_manager_->GetCommandDictionary(), &command_id, &error);
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001072 if (!command_instance) {
1073 LOG(WARNING) << "Failed to parse a command instance: " << command;
1074 if (!command_id.empty())
1075 NotifyCommandAborted(command_id, std::move(error));
1076 return;
1077 }
Anton Muhind07e2062014-10-27 10:53:29 +04001078
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001079 // TODO(antonm): Properly process cancellation of commands.
1080 if (!command_manager_->FindCommand(command_instance->GetID())) {
1081 LOG(INFO) << "New command '" << command_instance->GetName()
1082 << "' arrived, ID: " << command_instance->GetID();
Vitaly Buka0f80f7c2015-08-13 00:57:25 -07001083 std::unique_ptr<BackoffEntry> backoff_entry{
1084 new BackoffEntry{cloud_backoff_policy_.get()}};
Vitaly Buka157b16a2015-07-31 16:20:48 -07001085 std::unique_ptr<CloudCommandProxy> cloud_proxy{new CloudCommandProxy{
Vitaly Buka0d377a42015-07-21 10:26:08 -07001086 command_instance.get(), this, state_manager_->GetStateChangeQueue(),
1087 std::move(backoff_entry), task_runner_}};
Vitaly Buka6da94252015-08-04 15:45:14 -07001088 // CloudCommandProxy::CloudCommandProxy() subscribe itself to Command
1089 // notifications. When Command is being destroyed it sends
Vitaly Buka157b16a2015-07-31 16:20:48 -07001090 // ::OnCommandDestroyed() and CloudCommandProxy deletes itself.
1091 cloud_proxy.release();
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001092 command_manager_->AddCommand(std::move(command_instance));
Anton Muhind07e2062014-10-27 10:53:29 +04001093 }
Anton Muhind8d32162014-10-02 20:37:00 +04001094}
1095
Anton Muhinb8315622014-11-20 03:17:05 +04001096void DeviceRegistrationInfo::PublishStateUpdates() {
Alex Vakulenko93ba0bd2015-06-19 14:06:46 -07001097 // If we have pending state update requests, don't send any more for now.
1098 if (device_state_update_pending_)
1099 return;
1100
Alex Vakulenkobe4254b2015-06-26 11:34:03 -07001101 StateChangeQueueInterface::UpdateID update_id = 0;
1102 std::vector<StateChange> state_changes;
1103 std::tie(update_id, state_changes) =
Vitaly Bukaa647c852015-07-06 14:51:01 -07001104 state_manager_->GetAndClearRecordedStateChanges();
Anton Muhinb8315622014-11-20 03:17:05 +04001105 if (state_changes.empty())
1106 return;
1107
1108 std::unique_ptr<base::ListValue> patches{new base::ListValue};
1109 for (const auto& state_change : state_changes) {
1110 std::unique_ptr<base::DictionaryValue> patch{new base::DictionaryValue};
Anton Muhin76933fd2014-11-21 21:25:18 +04001111 patch->SetString("timeMs",
1112 std::to_string(state_change.timestamp.ToJavaTime()));
Anton Muhinb8315622014-11-20 03:17:05 +04001113
1114 std::unique_ptr<base::DictionaryValue> changes{new base::DictionaryValue};
1115 for (const auto& pair : state_change.changed_properties) {
Vitaly Buka6942e1f2015-07-28 15:33:55 -07001116 auto value = pair.second->ToJson();
1117 CHECK(value);
Alex Vakulenko61ad4db2015-01-20 10:50:04 -08001118 // The key in |pair.first| is the full property name in format
1119 // "package.property_name", so must use DictionaryValue::Set() instead of
1120 // DictionaryValue::SetWithoutPathExpansion to recreate the JSON
1121 // property tree properly.
1122 changes->Set(pair.first, value.release());
Anton Muhinb8315622014-11-20 03:17:05 +04001123 }
1124 patch->Set("patch", changes.release());
1125
1126 patches->Append(patch.release());
1127 }
1128
1129 base::DictionaryValue body;
Anton Muhin76933fd2014-11-21 21:25:18 +04001130 body.SetString("requestTimeMs",
1131 std::to_string(base::Time::Now().ToJavaTime()));
Anton Muhinb8315622014-11-20 03:17:05 +04001132 body.Set("patches", patches.release());
1133
Alex Vakulenko93ba0bd2015-06-19 14:06:46 -07001134 device_state_update_pending_ = true;
Vitaly Buka74763422015-10-11 00:39:52 -07001135 DoCloudRequest(HttpClient::Method::kPost, GetDeviceURL("patchState"), &body,
1136 base::Bind(&DeviceRegistrationInfo::OnPublishStateDone,
1137 AsWeakPtr(), update_id));
Alex Vakulenko93ba0bd2015-06-19 14:06:46 -07001138}
1139
Vitaly Buka74763422015-10-11 00:39:52 -07001140void DeviceRegistrationInfo::OnPublishStateDone(
Alex Vakulenkobe4254b2015-06-26 11:34:03 -07001141 StateChangeQueueInterface::UpdateID update_id,
Vitaly Buka74763422015-10-11 00:39:52 -07001142 const base::DictionaryValue& reply,
1143 ErrorPtr error) {
Alex Vakulenko93ba0bd2015-06-19 14:06:46 -07001144 device_state_update_pending_ = false;
Vitaly Buka74763422015-10-11 00:39:52 -07001145 if (error) {
1146 LOG(ERROR) << "Permanent failure while trying to update device state";
1147 return;
1148 }
Alex Vakulenkobe4254b2015-06-26 11:34:03 -07001149 state_manager_->NotifyStateUpdatedOnServer(update_id);
Alex Vakulenko93ba0bd2015-06-19 14:06:46 -07001150 // See if there were more pending state updates since the previous request
1151 // had been sent out.
1152 PublishStateUpdates();
1153}
1154
Vitaly Bukac3c6dab2015-10-01 19:41:02 -07001155void DeviceRegistrationInfo::SetGcdState(GcdState new_state) {
1156 VLOG_IF(1, new_state != gcd_state_) << "Changing registration status to "
1157 << EnumToString(new_state);
1158 gcd_state_ = new_state;
1159 for (const auto& cb : gcd_state_changed_callbacks_)
1160 cb.Run(gcd_state_);
Christopher Wileyc900e482015-02-15 15:42:04 -08001161}
1162
Alex Vakulenko9ea5a322015-04-17 15:35:34 -07001163void DeviceRegistrationInfo::OnCommandDefsChanged() {
1164 VLOG(1) << "CommandDefinitionChanged notification received";
Alex Vakulenkofb331ac2015-07-22 15:10:11 -07001165 if (!HaveRegistrationCredentials() || !connected_to_cloud_)
Alex Vakulenko9ea5a322015-04-17 15:35:34 -07001166 return;
1167
Vitaly Buka74763422015-10-11 00:39:52 -07001168 UpdateDeviceResource(base::Bind(&IgnoreCloudError));
Alex Vakulenko9ea5a322015-04-17 15:35:34 -07001169}
1170
Vitaly Bukac903d282015-05-26 17:03:08 -07001171void DeviceRegistrationInfo::OnStateChanged() {
1172 VLOG(1) << "StateChanged notification received";
Alex Vakulenkofb331ac2015-07-22 15:10:11 -07001173 if (!HaveRegistrationCredentials() || !connected_to_cloud_)
Vitaly Bukac903d282015-05-26 17:03:08 -07001174 return;
1175
1176 // TODO(vitalybuka): Integrate BackoffEntry.
1177 PublishStateUpdates();
1178}
1179
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -07001180void DeviceRegistrationInfo::OnConnected(const std::string& channel_name) {
1181 LOG(INFO) << "Notification channel successfully established over "
1182 << channel_name;
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001183 CHECK_EQ(primary_notification_channel_->GetName(), channel_name);
Alex Vakulenko6b028ae2015-05-29 09:38:59 -07001184 notification_channel_starting_ = false;
Vitaly Buka41a90d62015-09-29 16:58:39 -07001185 pull_channel_->UpdatePullInterval(
Alex Vakulenkoc1fc90c2015-10-22 08:00:43 -07001186 base::TimeDelta::FromMinutes(kBackupPollingPeriodMinutes));
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001187 current_notification_channel_ = primary_notification_channel_.get();
Alex Vakulenkofb331ac2015-07-22 15:10:11 -07001188
Alex Vakulenko8b096cc2015-08-03 10:37:55 -07001189 // If we have not successfully connected to the cloud server and we have not
1190 // initiated the first device resource update, there is nothing we need to
1191 // do now to update the server of the notification channel change.
1192 if (!connected_to_cloud_ && in_progress_resource_update_callbacks_.empty())
Alex Vakulenkofb331ac2015-07-22 15:10:11 -07001193 return;
1194
Alex Vakulenko1038ec12015-07-15 14:36:13 -07001195 // Once we update the device resource with the new notification channel,
1196 // do the last poll for commands from the server, to make sure we have the
1197 // latest command baseline and no other commands have been queued between
1198 // the moment of the last poll and the time we successfully told the server
1199 // to send new commands over the new notification channel.
1200 UpdateDeviceResource(
Vitaly Buka74763422015-10-11 00:39:52 -07001201 base::Bind(&IgnoreCloudErrorWithCallback,
1202 base::Bind(&DeviceRegistrationInfo::FetchAndPublishCommands,
1203 AsWeakPtr())));
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -07001204}
1205
1206void DeviceRegistrationInfo::OnDisconnected() {
1207 LOG(INFO) << "Notification channel disconnected";
Alex Vakulenkofb331ac2015-07-22 15:10:11 -07001208 if (!HaveRegistrationCredentials() || !connected_to_cloud_)
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -07001209 return;
1210
Vitaly Buka41a90d62015-09-29 16:58:39 -07001211 pull_channel_->UpdatePullInterval(
1212 base::TimeDelta::FromSeconds(kPollingPeriodSeconds));
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001213 current_notification_channel_ = pull_channel_.get();
Vitaly Buka74763422015-10-11 00:39:52 -07001214 UpdateDeviceResource(base::Bind(&IgnoreCloudError));
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -07001215}
1216
1217void DeviceRegistrationInfo::OnPermanentFailure() {
1218 LOG(ERROR) << "Failed to establish notification channel.";
Alex Vakulenko6b028ae2015-05-29 09:38:59 -07001219 notification_channel_starting_ = false;
Vitaly Buka4ebd3292015-09-23 18:04:17 -07001220 RefreshAccessToken(
Vitaly Buka4ebd3292015-09-23 18:04:17 -07001221 base::Bind(&DeviceRegistrationInfo::CheckAccessTokenError, AsWeakPtr()));
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -07001222}
1223
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001224void DeviceRegistrationInfo::OnCommandCreated(
1225 const base::DictionaryValue& command) {
Alex Vakulenkofb331ac2015-07-22 15:10:11 -07001226 if (!connected_to_cloud_)
1227 return;
1228
Vitaly Buka7a350052015-10-10 23:58:20 -07001229 VLOG(1) << "Command notification received: " << command;
1230
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001231 if (!command.empty()) {
1232 // GCD spec indicates that the command parameter in notification object
1233 // "may be empty if command size is too big".
1234 PublishCommand(command);
1235 return;
1236 }
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001237 // If the command was too big to be delivered over a notification channel,
1238 // or OnCommandCreated() was initiated from the Pull notification,
1239 // perform a manual command fetch from the server here.
Alex Vakulenko1038ec12015-07-15 14:36:13 -07001240 FetchAndPublishCommands();
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001241}
1242
Johan Euphrosine312c2f52015-09-29 00:04:29 -07001243void DeviceRegistrationInfo::OnDeviceDeleted(const std::string& cloud_id) {
1244 if (cloud_id != GetSettings().cloud_id) {
1245 LOG(WARNING) << "Unexpected device deletion notification for cloud ID '"
1246 << cloud_id << "'";
Alex Vakulenko6b40d8f2015-06-24 11:44:22 -07001247 return;
1248 }
1249 MarkDeviceUnregistered();
1250}
1251
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -07001252void DeviceRegistrationInfo::MarkDeviceUnregistered() {
1253 if (!HaveRegistrationCredentials())
1254 return;
1255
Alex Vakulenkofb331ac2015-07-22 15:10:11 -07001256 connected_to_cloud_ = false;
1257
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -07001258 LOG(INFO) << "Device is unregistered from the cloud. Deleting credentials";
Vitaly Bukac11a17d2015-08-15 10:36:10 -07001259 Config::Transaction change{config_.get()};
Johan Euphrosine312c2f52015-09-29 00:04:29 -07001260 change.set_cloud_id("");
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -07001261 change.set_robot_account("");
1262 change.set_refresh_token("");
1263 change.Commit();
1264
1265 current_notification_channel_ = nullptr;
1266 if (primary_notification_channel_) {
1267 primary_notification_channel_->Stop();
1268 primary_notification_channel_.reset();
1269 }
1270 if (pull_channel_) {
1271 pull_channel_->Stop();
1272 pull_channel_.reset();
1273 }
1274 notification_channel_starting_ = false;
Vitaly Bukac3c6dab2015-10-01 19:41:02 -07001275 SetGcdState(GcdState::kInvalidCredentials);
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -07001276}
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001277
Vitaly Bukab6f015a2015-07-09 14:59:23 -07001278} // namespace weave