blob: 5c8625a13359adf9ffd3e5580e22bff4195d780e [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
Alex Vakulenkoe07c29d2015-10-22 10:31:12 -070046namespace fetch_reason {
47
48const char kDeviceStart[] = "device_start"; // Initial queue fetch at startup.
49const char kRegularPull[] = "regular_pull"; // Regular fetch before XMPP is up.
50const char kNewCommand[] = "new_command"; // A new command is available.
51const char kJustInCase[] = "just_in_case"; // Backup fetch when XMPP is live.
52
53} // namespace fetch_reason
54
Vitaly Buka1e363672015-09-25 14:01:16 -070055using provider::HttpClient;
56
Vitaly Buka0801a1f2015-08-14 10:03:46 -070057inline void SetUnexpectedError(ErrorPtr* error) {
58 Error::AddTo(error, FROM_HERE, kErrorDomainGCD, "unexpected_response",
59 "Unexpected GCD error");
Alex Vakulenkob3aac252014-05-07 17:35:24 -070060}
61
Vitaly Buka0801a1f2015-08-14 10:03:46 -070062void ParseGCDError(const base::DictionaryValue* json, ErrorPtr* error) {
Alex Vakulenkob3aac252014-05-07 17:35:24 -070063 const base::Value* list_value = nullptr;
64 const base::ListValue* error_list = nullptr;
65 if (!json->Get("error.errors", &list_value) ||
66 !list_value->GetAsList(&error_list)) {
67 SetUnexpectedError(error);
68 return;
69 }
70
71 for (size_t i = 0; i < error_list->GetSize(); i++) {
72 const base::Value* error_value = nullptr;
73 const base::DictionaryValue* error_object = nullptr;
74 if (!error_list->Get(i, &error_value) ||
75 !error_value->GetAsDictionary(&error_object)) {
76 SetUnexpectedError(error);
77 continue;
78 }
79 std::string error_code, error_message;
80 if (error_object->GetString("reason", &error_code) &&
81 error_object->GetString("message", &error_message)) {
Vitaly Buka0801a1f2015-08-14 10:03:46 -070082 Error::AddTo(error, FROM_HERE, kErrorDomainGCDServer, error_code,
83 error_message);
Alex Vakulenkob3aac252014-05-07 17:35:24 -070084 } else {
85 SetUnexpectedError(error);
86 }
87 }
88}
89
Vitaly Buka7d556392015-08-13 20:06:48 -070090std::string AppendQueryParams(const std::string& url,
91 const WebParamList& params) {
Vitaly Bukab001f282015-08-13 17:07:59 -070092 CHECK_EQ(std::string::npos, url.find_first_of("?#"));
93 if (params.empty())
94 return url;
Vitaly Buka7d556392015-08-13 20:06:48 -070095 return url + '?' + WebParamsEncode(params);
Vitaly Bukab001f282015-08-13 17:07:59 -070096}
97
Alex Vakulenkobda220a2014-04-18 15:25:44 -070098std::string BuildURL(const std::string& url,
Vitaly Bukab001f282015-08-13 17:07:59 -070099 const std::string& subpath,
Vitaly Buka7d556392015-08-13 20:06:48 -0700100 const WebParamList& params) {
Vitaly Bukab001f282015-08-13 17:07:59 -0700101 std::string result = url;
102 if (!result.empty() && result.back() != '/' && !subpath.empty()) {
103 CHECK_NE('/', subpath.front());
104 result += '/';
105 }
106 result += subpath;
107 return AppendQueryParams(result, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700108}
109
Vitaly Bukaf7f52d42015-10-10 22:43:55 -0700110void IgnoreCloudErrorWithCallback(const base::Closure& cb, ErrorPtr) {
Christopher Wileyba983c82015-03-05 16:32:23 -0800111 cb.Run();
112}
113
Vitaly Buka74763422015-10-11 00:39:52 -0700114void IgnoreCloudError(ErrorPtr) {}
Anton Muhin6d2569e2014-10-30 12:32:27 +0400115
Vitaly Buka74763422015-10-11 00:39:52 -0700116void IgnoreCloudResult(const base::DictionaryValue&, ErrorPtr error) {}
117
118void IgnoreCloudResultWithCallback(const DoneCallback& cb,
119 const base::DictionaryValue&,
120 ErrorPtr error) {
121 cb.Run(std::move(error));
Christopher Wileyba983c82015-03-05 16:32:23 -0800122}
123
Vitaly Buka6da94252015-08-04 15:45:14 -0700124class RequestSender final {
125 public:
Vitaly Buka1a42e142015-10-10 18:15:15 -0700126 RequestSender(HttpClient::Method method,
Vitaly Buka6da94252015-08-04 15:45:14 -0700127 const std::string& url,
128 HttpClient* transport)
129 : method_{method}, url_{url}, transport_{transport} {}
130
Vitaly Buka74763422015-10-11 00:39:52 -0700131 void Send(const HttpClient::SendRequestCallback& callback) {
Vitaly Buka866b60a2015-10-09 14:24:55 -0700132 static int debug_id = 0;
133 ++debug_id;
Vitaly Buka1a42e142015-10-10 18:15:15 -0700134 VLOG(1) << "Sending request. id:" << debug_id
135 << " method:" << EnumToString(method_) << " url:" << url_;
Vitaly Buka866b60a2015-10-09 14:24:55 -0700136 VLOG(2) << "Request data: " << data_;
Vitaly Buka74763422015-10-11 00:39:52 -0700137 auto on_done = [](
138 int debug_id, const HttpClient::SendRequestCallback& callback,
139 std::unique_ptr<HttpClient::Response> response, ErrorPtr error) {
140 if (error) {
141 VLOG(1) << "Request failed, id=" << debug_id
142 << ", reason: " << error->GetCode()
143 << ", message: " << error->GetMessage();
144 return callback.Run({}, std::move(error));
145 }
146 VLOG(1) << "Request succeeded. id:" << debug_id
147 << " status:" << response->GetStatusCode();
148 VLOG(2) << "Response data: " << response->GetData();
149 callback.Run(std::move(response), nullptr);
Vitaly Buka866b60a2015-10-09 14:24:55 -0700150 };
151 transport_->SendRequest(method_, url_, GetFullHeaders(), data_,
Vitaly Buka74763422015-10-11 00:39:52 -0700152 base::Bind(on_done, debug_id, callback));
Vitaly Buka6da94252015-08-04 15:45:14 -0700153 }
154
Vitaly Buka815b6032015-08-06 11:06:25 -0700155 void SetAccessToken(const std::string& access_token) {
156 access_token_ = access_token;
Vitaly Buka6da94252015-08-04 15:45:14 -0700157 }
158
159 void SetData(const std::string& data, const std::string& mime_type) {
160 data_ = data;
161 mime_type_ = mime_type;
162 }
163
164 void SetFormData(
165 const std::vector<std::pair<std::string, std::string>>& data) {
Vitaly Buka7d556392015-08-13 20:06:48 -0700166 SetData(WebParamsEncode(data), http::kWwwFormUrlEncoded);
Vitaly Buka6da94252015-08-04 15:45:14 -0700167 }
168
169 void SetJsonData(const base::Value& json) {
170 std::string data;
Vitaly Buka815b6032015-08-06 11:06:25 -0700171 CHECK(base::JSONWriter::Write(json, &data));
172 SetData(data, http::kJsonUtf8);
Vitaly Buka6da94252015-08-04 15:45:14 -0700173 }
174
175 private:
Vitaly Buka815b6032015-08-06 11:06:25 -0700176 HttpClient::Headers GetFullHeaders() const {
177 HttpClient::Headers headers;
178 if (!access_token_.empty())
179 headers.emplace_back(http::kAuthorization, "Bearer " + access_token_);
180 if (!mime_type_.empty())
181 headers.emplace_back(http::kContentType, mime_type_);
182 return headers;
183 }
184
Vitaly Buka1a42e142015-10-10 18:15:15 -0700185 HttpClient::Method method_;
Vitaly Buka6da94252015-08-04 15:45:14 -0700186 std::string url_;
187 std::string data_;
188 std::string mime_type_;
Vitaly Buka815b6032015-08-06 11:06:25 -0700189 std::string access_token_;
Vitaly Buka6da94252015-08-04 15:45:14 -0700190 HttpClient* transport_{nullptr};
191
192 DISALLOW_COPY_AND_ASSIGN(RequestSender);
193};
194
195std::unique_ptr<base::DictionaryValue> ParseJsonResponse(
196 const HttpClient::Response& response,
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700197 ErrorPtr* error) {
Vitaly Buka6da94252015-08-04 15:45:14 -0700198 // Make sure we have a correct content type. Do not try to parse
199 // binary files, or HTML output. Limit to application/json and text/plain.
Vitaly Buka24d6fd52015-08-13 23:22:48 -0700200 std::string content_type =
201 SplitAtFirst(response.GetContentType(), ";", true).first;
Vitaly Buka1da65992015-08-06 01:38:57 -0700202
203 if (content_type != http::kJson && content_type != http::kPlain) {
Vitaly Bukac27390d2015-11-19 14:42:35 -0800204 Error::AddTo(
205 error, FROM_HERE, errors::json::kDomain, "non_json_content_type",
206 "Unexpected content type: \'" + response.GetContentType() + "\'");
Vitaly Buka6da94252015-08-04 15:45:14 -0700207 return std::unique_ptr<base::DictionaryValue>();
208 }
209
210 const std::string& json = response.GetData();
211 std::string error_message;
212 auto value = base::JSONReader::ReadAndReturnError(json, base::JSON_PARSE_RFC,
213 nullptr, &error_message);
214 if (!value) {
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700215 Error::AddToPrintf(error, FROM_HERE, errors::json::kDomain,
216 errors::json::kParseError,
217 "Error '%s' occurred parsing JSON string '%s'",
218 error_message.c_str(), json.c_str());
Vitaly Buka6da94252015-08-04 15:45:14 -0700219 return std::unique_ptr<base::DictionaryValue>();
220 }
221 base::DictionaryValue* dict_value = nullptr;
222 if (!value->GetAsDictionary(&dict_value)) {
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700223 Error::AddToPrintf(
Vitaly Bukaea2f15f2015-08-13 15:26:20 -0700224 error, FROM_HERE, errors::json::kDomain, errors::json::kObjectExpected,
Vitaly Buka6da94252015-08-04 15:45:14 -0700225 "Response is not a valid JSON object: '%s'", json.c_str());
226 return std::unique_ptr<base::DictionaryValue>();
227 } else {
228 // |value| is now owned by |dict_value|, so release the scoped_ptr now.
229 base::IgnoreResult(value.release());
230 }
231 return std::unique_ptr<base::DictionaryValue>(dict_value);
232}
233
234bool IsSuccessful(const HttpClient::Response& response) {
235 int code = response.GetStatusCode();
Vitaly Buka1da65992015-08-06 01:38:57 -0700236 return code >= http::kContinue && code < http::kBadRequest;
Vitaly Buka6da94252015-08-04 15:45:14 -0700237}
238
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700239} // anonymous namespace
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700240
Alex Vakulenko1f30a622014-07-23 11:13:15 -0700241DeviceRegistrationInfo::DeviceRegistrationInfo(
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700242 const std::shared_ptr<CommandManager>& command_manager,
Anton Muhin332df192014-11-22 05:59:14 +0400243 const std::shared_ptr<StateManager>& state_manager,
Vitaly Buka1e363672015-09-25 14:01:16 -0700244 std::unique_ptr<Config> config,
245 provider::TaskRunner* task_runner,
246 provider::HttpClient* http_client,
247 provider::Network* network)
Vitaly Buka10206182015-08-05 11:17:43 -0700248 : http_client_{http_client},
Alex Vakulenkoa56a7e62015-06-26 12:59:50 -0700249 task_runner_{task_runner},
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700250 command_manager_{command_manager},
Anton Muhin332df192014-11-22 05:59:14 +0400251 state_manager_{state_manager},
Christopher Wiley583d64b2015-03-24 14:30:17 -0700252 config_{std::move(config)},
Vitaly Bukaddb2e382015-07-31 00:33:31 -0700253 network_{network} {
Vitaly Buka0f80f7c2015-08-13 00:57:25 -0700254 cloud_backoff_policy_.reset(new BackoffEntry::Policy{});
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700255 cloud_backoff_policy_->num_errors_to_ignore = 0;
Alex Vakulenkof61ee012015-06-24 14:21:35 -0700256 cloud_backoff_policy_->initial_delay_ms = 1000;
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700257 cloud_backoff_policy_->multiply_factor = 2.0;
258 cloud_backoff_policy_->jitter_factor = 0.1;
259 cloud_backoff_policy_->maximum_backoff_ms = 30000;
260 cloud_backoff_policy_->entry_lifetime_ms = -1;
261 cloud_backoff_policy_->always_use_initial_delay = false;
Vitaly Buka0f80f7c2015-08-13 00:57:25 -0700262 cloud_backoff_entry_.reset(new BackoffEntry{cloud_backoff_policy_.get()});
263 oauth2_backoff_entry_.reset(new BackoffEntry{cloud_backoff_policy_.get()});
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700264
Vitaly Buka672634b2015-11-20 09:49:30 -0800265 bool revoked =
266 !GetSettings().cloud_id.empty() && !HaveRegistrationCredentials();
267 gcd_state_ =
268 revoked ? GcdState::kInvalidCredentials : GcdState::kUnconfigured;
269
Vitaly Buka553a7622015-10-05 13:53:20 -0700270 command_manager_->AddCommandDefChanged(
Alex Vakulenko9ea5a322015-04-17 15:35:34 -0700271 base::Bind(&DeviceRegistrationInfo::OnCommandDefsChanged,
272 weak_factory_.GetWeakPtr()));
Vitaly Buka4c981352015-10-01 23:04:24 -0700273 state_manager_->AddChangedCallback(base::Bind(
Vitaly Bukaa647c852015-07-06 14:51:01 -0700274 &DeviceRegistrationInfo::OnStateChanged, weak_factory_.GetWeakPtr()));
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700275}
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700276
Anton Muhin332df192014-11-22 05:59:14 +0400277DeviceRegistrationInfo::~DeviceRegistrationInfo() = default;
278
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700279std::string DeviceRegistrationInfo::GetServiceURL(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700280 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().service_url, subpath, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700283}
284
285std::string DeviceRegistrationInfo::GetDeviceURL(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700286 const std::string& subpath,
Vitaly Buka7d556392015-08-13 20:06:48 -0700287 const WebParamList& params) const {
Johan Euphrosine312c2f52015-09-29 00:04:29 -0700288 CHECK(!GetSettings().cloud_id.empty()) << "Must have a valid device ID";
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700289 return BuildURL(GetSettings().service_url,
Johan Euphrosine312c2f52015-09-29 00:04:29 -0700290 "devices/" + GetSettings().cloud_id + "/" + subpath, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700291}
292
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700293std::string DeviceRegistrationInfo::GetOAuthURL(
294 const std::string& subpath,
Vitaly Buka7d556392015-08-13 20:06:48 -0700295 const WebParamList& params) const {
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700296 return BuildURL(GetSettings().oauth_url, subpath, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700297}
298
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700299void DeviceRegistrationInfo::Start() {
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -0700300 if (HaveRegistrationCredentials()) {
Alex Vakulenkod05725f2015-05-27 15:48:19 -0700301 StartNotificationChannel();
Christopher Wileyba983c82015-03-05 16:32:23 -0800302 // Wait a significant amount of time for local daemons to publish their
303 // state to Buffet before publishing it to the cloud.
304 // TODO(wiley) We could do a lot of things here to either expose this
305 // timeout as a configurable knob or allow local
306 // daemons to signal that their state is up to date so that
307 // we need not wait for them.
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700308 ScheduleCloudConnection(base::TimeDelta::FromSeconds(5));
Christopher Wileyba983c82015-03-05 16:32:23 -0800309 }
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700310}
311
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700312void DeviceRegistrationInfo::ScheduleCloudConnection(
313 const base::TimeDelta& delay) {
Vitaly Bukac3c6dab2015-10-01 19:41:02 -0700314 SetGcdState(GcdState::kConnecting);
Alex Vakulenkoa56a7e62015-06-26 12:59:50 -0700315 if (!task_runner_)
Vitaly Buka0f6b2ec2015-08-20 15:35:19 -0700316 return; // Assume we're in test
Alex Vakulenkoa56a7e62015-06-26 12:59:50 -0700317 task_runner_->PostDelayedTask(
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700318 FROM_HERE,
Vitaly Buka74763422015-10-11 00:39:52 -0700319 base::Bind(&DeviceRegistrationInfo::ConnectToCloud, AsWeakPtr(), nullptr),
320 delay);
Christopher Wileycd419662015-02-06 17:51:43 -0800321}
322
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -0700323bool DeviceRegistrationInfo::HaveRegistrationCredentials() const {
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700324 return !GetSettings().refresh_token.empty() &&
Johan Euphrosine312c2f52015-09-29 00:04:29 -0700325 !GetSettings().cloud_id.empty() &&
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700326 !GetSettings().robot_account.empty();
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -0700327}
328
329bool DeviceRegistrationInfo::VerifyRegistrationCredentials(
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700330 ErrorPtr* error) const {
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -0700331 const bool have_credentials = HaveRegistrationCredentials();
Christopher Wileyc900e482015-02-15 15:42:04 -0800332
Alex Vakulenkoed77a572015-07-15 10:55:43 -0700333 VLOG(2) << "Device registration record "
Christopher Wileyc900e482015-02-15 15:42:04 -0800334 << ((have_credentials) ? "found" : "not found.");
335 if (!have_credentials)
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700336 Error::AddTo(error, FROM_HERE, kErrorDomainGCD, "device_not_registered",
337 "No valid device registration record found");
Christopher Wileyc900e482015-02-15 15:42:04 -0800338 return have_credentials;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700339}
340
Nathan Bullock24d189f2015-02-26 13:09:18 -0500341std::unique_ptr<base::DictionaryValue>
Vitaly Buka6da94252015-08-04 15:45:14 -0700342DeviceRegistrationInfo::ParseOAuthResponse(const HttpClient::Response& response,
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700343 ErrorPtr* error) {
Vitaly Buka6da94252015-08-04 15:45:14 -0700344 int code = response.GetStatusCode();
345 auto resp = ParseJsonResponse(response, error);
Vitaly Buka1da65992015-08-06 01:38:57 -0700346 if (resp && code >= http::kBadRequest) {
Nathan Bullock24d189f2015-02-26 13:09:18 -0500347 std::string error_code, error_message;
348 if (!resp->GetString("error", &error_code)) {
349 error_code = "unexpected_response";
350 }
351 if (error_code == "invalid_grant") {
352 LOG(INFO) << "The device's registration has been revoked.";
Vitaly Bukac3c6dab2015-10-01 19:41:02 -0700353 SetGcdState(GcdState::kInvalidCredentials);
Nathan Bullock24d189f2015-02-26 13:09:18 -0500354 }
355 // I have never actually seen an error_description returned.
356 if (!resp->GetString("error_description", &error_message)) {
357 error_message = "Unexpected OAuth error";
358 }
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700359 Error::AddTo(error, FROM_HERE, kErrorDomainOAuth2, error_code,
360 error_message);
Nathan Bullock24d189f2015-02-26 13:09:18 -0500361 return std::unique_ptr<base::DictionaryValue>();
362 }
363 return resp;
364}
365
Vitaly Buka74763422015-10-11 00:39:52 -0700366void DeviceRegistrationInfo::RefreshAccessToken(const DoneCallback& callback) {
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700367 LOG(INFO) << "Refreshing access token.";
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -0700368
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700369 ErrorPtr error;
Vitaly Buka74763422015-10-11 00:39:52 -0700370 if (!VerifyRegistrationCredentials(&error))
371 return callback.Run(std::move(error));
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -0700372
Alex Vakulenko0f91be82015-07-27 10:11:53 -0700373 if (oauth2_backoff_entry_->ShouldRejectRequest()) {
374 VLOG(1) << "RefreshToken request delayed for "
375 << oauth2_backoff_entry_->GetTimeUntilRelease()
376 << " due to backoff policy";
377 task_runner_->PostDelayedTask(
378 FROM_HERE, base::Bind(&DeviceRegistrationInfo::RefreshAccessToken,
Vitaly Buka74763422015-10-11 00:39:52 -0700379 AsWeakPtr(), callback),
Alex Vakulenko0f91be82015-07-27 10:11:53 -0700380 oauth2_backoff_entry_->GetTimeUntilRelease());
381 return;
382 }
383
Vitaly Buka1a42e142015-10-10 18:15:15 -0700384 RequestSender sender{HttpClient::Method::kPost, GetOAuthURL("token"),
385 http_client_};
Vitaly Buka6da94252015-08-04 15:45:14 -0700386 sender.SetFormData({
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700387 {"refresh_token", GetSettings().refresh_token},
388 {"client_id", GetSettings().client_id},
389 {"client_secret", GetSettings().client_secret},
Vitaly Bukaa647c852015-07-06 14:51:01 -0700390 {"grant_type", "refresh_token"},
Vitaly Buka6da94252015-08-04 15:45:14 -0700391 });
Vitaly Buka74763422015-10-11 00:39:52 -0700392 sender.Send(base::Bind(&DeviceRegistrationInfo::OnRefreshAccessTokenDone,
393 weak_factory_.GetWeakPtr(), callback));
Vitaly Buka866b60a2015-10-09 14:24:55 -0700394 VLOG(1) << "Refresh access token request dispatched";
David Zeuthen390d1912015-03-03 14:54:48 -0500395}
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700396
Vitaly Buka74763422015-10-11 00:39:52 -0700397void DeviceRegistrationInfo::OnRefreshAccessTokenDone(
398 const DoneCallback& callback,
399 std::unique_ptr<HttpClient::Response> response,
400 ErrorPtr error) {
401 if (error) {
402 VLOG(1) << "Refresh access token failed";
403 oauth2_backoff_entry_->InformOfRequest(false);
404 return RefreshAccessToken(callback);
405 }
Vitaly Buka866b60a2015-10-09 14:24:55 -0700406 VLOG(1) << "Refresh access token request completed";
Alex Vakulenko0f91be82015-07-27 10:11:53 -0700407 oauth2_backoff_entry_->InformOfRequest(true);
Vitaly Buka74763422015-10-11 00:39:52 -0700408 auto json = ParseOAuthResponse(*response, &error);
Vitaly Bukaf7f52d42015-10-10 22:43:55 -0700409 if (!json)
Vitaly Buka74763422015-10-11 00:39:52 -0700410 return callback.Run(std::move(error));
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700411
412 int expires_in = 0;
413 if (!json->GetString("access_token", &access_token_) ||
Vitaly Bukaa647c852015-07-06 14:51:01 -0700414 !json->GetInteger("expires_in", &expires_in) || access_token_.empty() ||
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700415 expires_in <= 0) {
416 LOG(ERROR) << "Access token unavailable.";
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700417 Error::AddTo(&error, FROM_HERE, kErrorDomainOAuth2,
418 "unexpected_server_response", "Access token unavailable");
Vitaly Buka74763422015-10-11 00:39:52 -0700419 return callback.Run(std::move(error));
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700420 }
Vitaly Bukaa647c852015-07-06 14:51:01 -0700421 access_token_expiration_ =
422 base::Time::Now() + base::TimeDelta::FromSeconds(expires_in);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700423 LOG(INFO) << "Access token is refreshed for additional " << expires_in
424 << " seconds.";
Nathan Bullockd9e0bcd2015-02-11 11:36:39 -0500425
Alex Vakulenko6b028ae2015-05-29 09:38:59 -0700426 if (primary_notification_channel_ &&
427 !primary_notification_channel_->IsConnected()) {
428 // If we have disconnected channel, it is due to failed credentials.
429 // Now that we have a new access token, retry the connection.
430 StartNotificationChannel();
431 }
Vitaly Buka74763422015-10-11 00:39:52 -0700432 callback.Run(nullptr);
Alex Vakulenko6b028ae2015-05-29 09:38:59 -0700433}
434
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -0700435void DeviceRegistrationInfo::StartNotificationChannel() {
Alex Vakulenko6b028ae2015-05-29 09:38:59 -0700436 if (notification_channel_starting_)
437 return;
438
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -0700439 LOG(INFO) << "Starting notification channel";
440
Vitaly Buka0f6b2ec2015-08-20 15:35:19 -0700441 // If no TaskRunner assume we're in test.
Vitaly Bukaff1d1862015-10-07 20:40:36 -0700442 if (!network_) {
443 LOG(INFO) << "No Network, not starting notification channel";
Nathan Bullockbea91132015-02-19 09:13:33 -0500444 return;
445 }
446
Alex Vakulenkod05725f2015-05-27 15:48:19 -0700447 if (primary_notification_channel_) {
Alex Vakulenko26f557b2015-05-26 16:47:40 -0700448 primary_notification_channel_->Stop();
Alex Vakulenkod05725f2015-05-27 15:48:19 -0700449 primary_notification_channel_.reset();
450 current_notification_channel_ = nullptr;
451 }
452
453 // Start with just regular polling at the pre-configured polling interval.
454 // Once the primary notification channel is connected successfully, it will
455 // call back to OnConnected() and at that time we'll switch to use the
456 // primary channel and switch periodic poll into much more infrequent backup
457 // poll mode.
Vitaly Buka41a90d62015-09-29 16:58:39 -0700458 const base::TimeDelta pull_interval =
459 base::TimeDelta::FromSeconds(kPollingPeriodSeconds);
Alex Vakulenkod05725f2015-05-27 15:48:19 -0700460 if (!pull_channel_) {
Alex Vakulenkoa56a7e62015-06-26 12:59:50 -0700461 pull_channel_.reset(new PullChannel{pull_interval, task_runner_});
Alex Vakulenkod05725f2015-05-27 15:48:19 -0700462 pull_channel_->Start(this);
463 } else {
464 pull_channel_->UpdatePullInterval(pull_interval);
465 }
466 current_notification_channel_ = pull_channel_.get();
467
Alex Vakulenko6b028ae2015-05-29 09:38:59 -0700468 notification_channel_starting_ = true;
Vitaly Buka63cc3d22015-06-23 20:11:36 -0700469 primary_notification_channel_.reset(new XmppChannel{
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700470 GetSettings().robot_account, access_token_, task_runner_, network_});
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -0700471 primary_notification_channel_->Start(this);
Nathan Bullockf12f7f02015-02-20 14:46:53 -0500472}
473
Vitaly Bukac3c6dab2015-10-01 19:41:02 -0700474void DeviceRegistrationInfo::AddGcdStateChangedCallback(
475 const Device::GcdStateChangedCallback& callback) {
476 gcd_state_changed_callbacks_.push_back(callback);
477 callback.Run(gcd_state_);
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700478}
479
Anton Muhind8d32162014-10-02 20:37:00 +0400480std::unique_ptr<base::DictionaryValue>
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700481DeviceRegistrationInfo::BuildDeviceResource(ErrorPtr* error) {
Alex Vakulenko9ea5a322015-04-17 15:35:34 -0700482 // Limit only to commands that are visible to the cloud.
Alex Vakulenko7e894da2015-11-23 11:47:49 -0800483 auto commands =
484 command_manager_->GetCommandDictionary().GetCommandsAsJson(error);
Anton Muhind8d32162014-10-02 20:37:00 +0400485 if (!commands)
486 return nullptr;
487
Vitaly Buka95d62562015-10-01 22:05:27 -0700488 std::unique_ptr<base::DictionaryValue> state = state_manager_->GetState();
Vitaly Buka6942e1f2015-07-28 15:33:55 -0700489 CHECK(state);
Anton Muhind8d32162014-10-02 20:37:00 +0400490
491 std::unique_ptr<base::DictionaryValue> resource{new base::DictionaryValue};
Johan Euphrosine312c2f52015-09-29 00:04:29 -0700492 if (!GetSettings().cloud_id.empty())
493 resource->SetString("id", GetSettings().cloud_id);
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700494 resource->SetString("name", GetSettings().name);
495 if (!GetSettings().description.empty())
496 resource->SetString("description", GetSettings().description);
497 if (!GetSettings().location.empty())
498 resource->SetString("location", GetSettings().location);
499 resource->SetString("modelManifestId", GetSettings().model_id);
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -0700500 std::unique_ptr<base::DictionaryValue> channel{new base::DictionaryValue};
Alex Vakulenkod05725f2015-05-27 15:48:19 -0700501 if (current_notification_channel_) {
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -0700502 channel->SetString("supportedType",
Alex Vakulenkod05725f2015-05-27 15:48:19 -0700503 current_notification_channel_->GetName());
504 current_notification_channel_->AddChannelParameters(channel.get());
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -0700505 } else {
Alex Vakulenkod05725f2015-05-27 15:48:19 -0700506 channel->SetString("supportedType", "pull");
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -0700507 }
508 resource->Set("channel", channel.release());
Anton Muhind8d32162014-10-02 20:37:00 +0400509 resource->Set("commandDefs", commands.release());
510 resource->Set("state", state.release());
511
512 return resource;
513}
514
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700515void DeviceRegistrationInfo::GetDeviceInfo(
Vitaly Buka74763422015-10-11 00:39:52 -0700516 const CloudRequestDoneCallback& callback) {
Vitaly Buka11b2f232015-08-20 13:55:41 -0700517 ErrorPtr error;
Vitaly Buka672634b2015-11-20 09:49:30 -0800518 if (!VerifyRegistrationCredentials(&error))
Vitaly Buka74763422015-10-11 00:39:52 -0700519 return callback.Run({}, std::move(error));
Vitaly Buka74763422015-10-11 00:39:52 -0700520 DoCloudRequest(HttpClient::Method::kGet, GetDeviceURL(), nullptr, callback);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700521}
522
Vitaly Buka74763422015-10-11 00:39:52 -0700523void DeviceRegistrationInfo::RegisterDeviceError(const DoneCallback& callback,
524 ErrorPtr error) {
525 task_runner_->PostDelayedTask(FROM_HERE,
526 base::Bind(callback, base::Passed(&error)), {});
Vitaly Buka4774df22015-10-09 12:36:22 -0700527}
528
Vitaly Buka74763422015-10-11 00:39:52 -0700529void DeviceRegistrationInfo::RegisterDevice(const std::string& ticket_id,
530 const DoneCallback& callback) {
Vitaly Buka12870bd2015-10-08 23:49:39 -0700531 ErrorPtr error;
Anton Muhind8d32162014-10-02 20:37:00 +0400532 std::unique_ptr<base::DictionaryValue> device_draft =
Vitaly Buka12870bd2015-10-08 23:49:39 -0700533 BuildDeviceResource(&error);
Anton Muhind8d32162014-10-02 20:37:00 +0400534 if (!device_draft)
Vitaly Buka74763422015-10-11 00:39:52 -0700535 return RegisterDeviceError(callback, std::move(error));
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700536
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700537 base::DictionaryValue req_json;
Nathan Bullocke4408482015-02-19 11:13:21 -0500538 req_json.SetString("id", ticket_id);
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700539 req_json.SetString("oauthClientId", GetSettings().client_id);
Anton Muhind8d32162014-10-02 20:37:00 +0400540 req_json.Set("deviceDraft", device_draft.release());
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700541
Nathan Bullocke4408482015-02-19 11:13:21 -0500542 auto url = GetServiceURL("registrationTickets/" + ticket_id,
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700543 {{"key", GetSettings().api_key}});
Vitaly Buka6da94252015-08-04 15:45:14 -0700544
Vitaly Buka1a42e142015-10-10 18:15:15 -0700545 RequestSender sender{HttpClient::Method::kPatch, url, http_client_};
Vitaly Buka6da94252015-08-04 15:45:14 -0700546 sender.SetJsonData(req_json);
Vitaly Buka4774df22015-10-09 12:36:22 -0700547 sender.Send(base::Bind(&DeviceRegistrationInfo::RegisterDeviceOnTicketSent,
Vitaly Buka74763422015-10-11 00:39:52 -0700548 weak_factory_.GetWeakPtr(), ticket_id, callback));
Vitaly Buka4774df22015-10-09 12:36:22 -0700549}
Vitaly Buka6da94252015-08-04 15:45:14 -0700550
Vitaly Buka4774df22015-10-09 12:36:22 -0700551void DeviceRegistrationInfo::RegisterDeviceOnTicketSent(
552 const std::string& ticket_id,
Vitaly Buka74763422015-10-11 00:39:52 -0700553 const DoneCallback& callback,
554 std::unique_ptr<provider::HttpClient::Response> response,
555 ErrorPtr error) {
556 if (error)
557 return RegisterDeviceError(callback, std::move(error));
558 auto json_resp = ParseJsonResponse(*response, &error);
Anton Muhin532ff7e2014-09-29 23:21:21 +0400559 if (!json_resp)
Vitaly Buka74763422015-10-11 00:39:52 -0700560 return RegisterDeviceError(callback, std::move(error));
Vitaly Buka4774df22015-10-09 12:36:22 -0700561
Vitaly Buka74763422015-10-11 00:39:52 -0700562 if (!IsSuccessful(*response)) {
Vitaly Buka12870bd2015-10-08 23:49:39 -0700563 ParseGCDError(json_resp.get(), &error);
Vitaly Buka74763422015-10-11 00:39:52 -0700564 return RegisterDeviceError(callback, std::move(error));
David Zeuthen1dbad472015-02-12 15:24:21 -0500565 }
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700566
Vitaly Buka4774df22015-10-09 12:36:22 -0700567 std::string url =
568 GetServiceURL("registrationTickets/" + ticket_id + "/finalize",
569 {{"key", GetSettings().api_key}});
Vitaly Buka1a42e142015-10-10 18:15:15 -0700570 RequestSender{HttpClient::Method::kPost, url, http_client_}.Send(
Vitaly Buka4774df22015-10-09 12:36:22 -0700571 base::Bind(&DeviceRegistrationInfo::RegisterDeviceOnTicketFinalized,
Vitaly Buka74763422015-10-11 00:39:52 -0700572 weak_factory_.GetWeakPtr(), callback));
Vitaly Buka4774df22015-10-09 12:36:22 -0700573}
574
575void DeviceRegistrationInfo::RegisterDeviceOnTicketFinalized(
Vitaly Buka74763422015-10-11 00:39:52 -0700576 const DoneCallback& callback,
577 std::unique_ptr<provider::HttpClient::Response> response,
578 ErrorPtr error) {
579 if (error)
580 return RegisterDeviceError(callback, std::move(error));
581 auto json_resp = ParseJsonResponse(*response, &error);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700582 if (!json_resp)
Vitaly Buka74763422015-10-11 00:39:52 -0700583 return RegisterDeviceError(callback, std::move(error));
584 if (!IsSuccessful(*response)) {
Vitaly Buka12870bd2015-10-08 23:49:39 -0700585 ParseGCDError(json_resp.get(), &error);
Vitaly Buka74763422015-10-11 00:39:52 -0700586 return RegisterDeviceError(callback, std::move(error));
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700587 }
Anton Muhin532ff7e2014-09-29 23:21:21 +0400588
589 std::string auth_code;
Johan Euphrosine312c2f52015-09-29 00:04:29 -0700590 std::string cloud_id;
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700591 std::string robot_account;
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700592 const base::DictionaryValue* device_draft_response = nullptr;
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700593 if (!json_resp->GetString("robotAccountEmail", &robot_account) ||
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700594 !json_resp->GetString("robotAccountAuthorizationCode", &auth_code) ||
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700595 !json_resp->GetDictionary("deviceDraft", &device_draft_response) ||
Johan Euphrosine312c2f52015-09-29 00:04:29 -0700596 !device_draft_response->GetString("id", &cloud_id)) {
Vitaly Buka12870bd2015-10-08 23:49:39 -0700597 Error::AddTo(&error, FROM_HERE, kErrorDomainGCD, "unexpected_response",
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700598 "Device account missing in response");
Vitaly Buka74763422015-10-11 00:39:52 -0700599 return RegisterDeviceError(callback, std::move(error));
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700600 }
601
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700602 UpdateDeviceInfoTimestamp(*device_draft_response);
603
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700604 // Now get access_token and refresh_token
Vitaly Buka1a42e142015-10-10 18:15:15 -0700605 RequestSender sender2{HttpClient::Method::kPost, GetOAuthURL("token"),
606 http_client_};
Vitaly Buka6da94252015-08-04 15:45:14 -0700607 sender2.SetFormData(
Vitaly Bukaa647c852015-07-06 14:51:01 -0700608 {{"code", auth_code},
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700609 {"client_id", GetSettings().client_id},
610 {"client_secret", GetSettings().client_secret},
Vitaly Bukaa647c852015-07-06 14:51:01 -0700611 {"redirect_uri", "oob"},
Vitaly Buka6da94252015-08-04 15:45:14 -0700612 {"grant_type", "authorization_code"}});
Vitaly Buka4774df22015-10-09 12:36:22 -0700613 sender2.Send(base::Bind(&DeviceRegistrationInfo::RegisterDeviceOnAuthCodeSent,
614 weak_factory_.GetWeakPtr(), cloud_id, robot_account,
Vitaly Buka74763422015-10-11 00:39:52 -0700615 callback));
Vitaly Buka4774df22015-10-09 12:36:22 -0700616}
Vitaly Buka6da94252015-08-04 15:45:14 -0700617
Vitaly Buka4774df22015-10-09 12:36:22 -0700618void DeviceRegistrationInfo::RegisterDeviceOnAuthCodeSent(
619 const std::string& cloud_id,
620 const std::string& robot_account,
Vitaly Buka74763422015-10-11 00:39:52 -0700621 const DoneCallback& callback,
622 std::unique_ptr<provider::HttpClient::Response> response,
623 ErrorPtr error) {
624 if (error)
625 return RegisterDeviceError(callback, std::move(error));
626 auto json_resp = ParseOAuthResponse(*response, &error);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700627 int expires_in = 0;
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700628 std::string refresh_token;
629 if (!json_resp || !json_resp->GetString("access_token", &access_token_) ||
630 !json_resp->GetString("refresh_token", &refresh_token) ||
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700631 !json_resp->GetInteger("expires_in", &expires_in) ||
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700632 access_token_.empty() || refresh_token.empty() || expires_in <= 0) {
Vitaly Buka12870bd2015-10-08 23:49:39 -0700633 Error::AddTo(&error, FROM_HERE, kErrorDomainGCD, "unexpected_response",
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700634 "Device access_token missing in response");
Vitaly Buka74763422015-10-11 00:39:52 -0700635 return RegisterDeviceError(callback, std::move(error));
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700636 }
637
Vitaly Bukaa647c852015-07-06 14:51:01 -0700638 access_token_expiration_ =
639 base::Time::Now() + base::TimeDelta::FromSeconds(expires_in);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700640
Vitaly Bukac11a17d2015-08-15 10:36:10 -0700641 Config::Transaction change{config_.get()};
Johan Euphrosine312c2f52015-09-29 00:04:29 -0700642 change.set_cloud_id(cloud_id);
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700643 change.set_robot_account(robot_account);
644 change.set_refresh_token(refresh_token);
645 change.Commit();
646
Vitaly Buka74763422015-10-11 00:39:52 -0700647 task_runner_->PostDelayedTask(FROM_HERE, base::Bind(callback, nullptr), {});
Vitaly Buka12870bd2015-10-08 23:49:39 -0700648
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -0700649 StartNotificationChannel();
Christopher Wileycd419662015-02-06 17:51:43 -0800650
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700651 // We're going to respond with our success immediately and we'll connect to
652 // cloud shortly after.
Vitaly Buka12870bd2015-10-08 23:49:39 -0700653 ScheduleCloudConnection({});
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700654}
655
Anton Muhinac661ab2014-10-03 20:29:48 +0400656void DeviceRegistrationInfo::DoCloudRequest(
Vitaly Buka1a42e142015-10-10 18:15:15 -0700657 HttpClient::Method method,
Anton Muhinac661ab2014-10-03 20:29:48 +0400658 const std::string& url,
659 const base::DictionaryValue* body,
Vitaly Buka74763422015-10-11 00:39:52 -0700660 const CloudRequestDoneCallback& callback) {
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700661 // We make CloudRequestData shared here because we want to make sure
Vitaly Buka74763422015-10-11 00:39:52 -0700662 // there is only one instance of callback and error_calback since
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700663 // those may have move-only types and making a copy of the callback with
664 // move-only types curried-in will invalidate the source callback.
665 auto data = std::make_shared<CloudRequestData>();
666 data->method = method;
667 data->url = url;
668 if (body)
669 base::JSONWriter::Write(*body, &data->body);
Vitaly Buka74763422015-10-11 00:39:52 -0700670 data->callback = callback;
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700671 SendCloudRequest(data);
672}
673
674void DeviceRegistrationInfo::SendCloudRequest(
675 const std::shared_ptr<const CloudRequestData>& data) {
Alex Vakulenko0357c032015-01-06 16:32:31 -0800676 // TODO(antonm): Add reauthorization on access token expiration (do not
Anton Muhinac661ab2014-10-03 20:29:48 +0400677 // forget about 5xx when fetching new access token).
678 // TODO(antonm): Add support for device removal.
679
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700680 ErrorPtr error;
Vitaly Buka672634b2015-11-20 09:49:30 -0800681 if (!VerifyRegistrationCredentials(&error))
Vitaly Buka74763422015-10-11 00:39:52 -0700682 return data->callback.Run({}, std::move(error));
Anton Muhinac661ab2014-10-03 20:29:48 +0400683
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700684 if (cloud_backoff_entry_->ShouldRejectRequest()) {
685 VLOG(1) << "Cloud request delayed for "
686 << cloud_backoff_entry_->GetTimeUntilRelease()
687 << " due to backoff policy";
Vitaly Bukaf7f52d42015-10-10 22:43:55 -0700688 return task_runner_->PostDelayedTask(
Vitaly Bukaa647c852015-07-06 14:51:01 -0700689 FROM_HERE, base::Bind(&DeviceRegistrationInfo::SendCloudRequest,
690 AsWeakPtr(), data),
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700691 cloud_backoff_entry_->GetTimeUntilRelease());
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700692 }
Anton Muhinac661ab2014-10-03 20:29:48 +0400693
Vitaly Buka6da94252015-08-04 15:45:14 -0700694 RequestSender sender{data->method, data->url, http_client_};
Vitaly Buka1da65992015-08-06 01:38:57 -0700695 sender.SetData(data->body, http::kJsonUtf8);
Vitaly Buka815b6032015-08-06 11:06:25 -0700696 sender.SetAccessToken(access_token_);
Vitaly Buka74763422015-10-11 00:39:52 -0700697 sender.Send(base::Bind(&DeviceRegistrationInfo::OnCloudRequestDone,
Vitaly Buka866b60a2015-10-09 14:24:55 -0700698 AsWeakPtr(), data));
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700699}
Anton Muhinac661ab2014-10-03 20:29:48 +0400700
Vitaly Buka74763422015-10-11 00:39:52 -0700701void DeviceRegistrationInfo::OnCloudRequestDone(
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700702 const std::shared_ptr<const CloudRequestData>& data,
Vitaly Buka74763422015-10-11 00:39:52 -0700703 std::unique_ptr<provider::HttpClient::Response> response,
704 ErrorPtr error) {
705 if (error)
706 return RetryCloudRequest(data);
707 int status_code = response->GetStatusCode();
Vitaly Buka1da65992015-08-06 01:38:57 -0700708 if (status_code == http::kDenied) {
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700709 cloud_backoff_entry_->InformOfRequest(true);
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700710 RefreshAccessToken(
711 base::Bind(&DeviceRegistrationInfo::OnAccessTokenRefreshed, AsWeakPtr(),
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700712 data));
713 return;
714 }
Anton Muhinac661ab2014-10-03 20:29:48 +0400715
Vitaly Buka1da65992015-08-06 01:38:57 -0700716 if (status_code >= http::kInternalServerError) {
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700717 // Request was valid, but server failed, retry.
718 // TODO(antonm): Reconsider status codes, maybe only some require
719 // retry.
720 // TODO(antonm): Support Retry-After header.
721 RetryCloudRequest(data);
722 return;
723 }
Anton Muhin633eded2014-10-03 20:40:10 +0400724
Vitaly Buka74763422015-10-11 00:39:52 -0700725 auto json_resp = ParseJsonResponse(*response, &error);
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700726 if (!json_resp) {
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700727 cloud_backoff_entry_->InformOfRequest(true);
Vitaly Buka74763422015-10-11 00:39:52 -0700728 return data->callback.Run({}, std::move(error));
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700729 }
Anton Muhin233d2ee2014-10-22 15:16:24 +0400730
Vitaly Buka74763422015-10-11 00:39:52 -0700731 if (!IsSuccessful(*response)) {
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700732 ParseGCDError(json_resp.get(), &error);
Vitaly Buka1da65992015-08-06 01:38:57 -0700733 if (status_code == http::kForbidden &&
Vitaly Bukab6f015a2015-07-09 14:59:23 -0700734 error->HasError(kErrorDomainGCDServer, "rateLimitExceeded")) {
Alex Vakulenkof61ee012015-06-24 14:21:35 -0700735 // If we exceeded server quota, retry the request later.
Vitaly Bukaf7f52d42015-10-10 22:43:55 -0700736 return RetryCloudRequest(data);
Alex Vakulenkof61ee012015-06-24 14:21:35 -0700737 }
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700738 cloud_backoff_entry_->InformOfRequest(true);
Vitaly Buka74763422015-10-11 00:39:52 -0700739 return data->callback.Run({}, std::move(error));
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700740 }
741
Alex Vakulenkof61ee012015-06-24 14:21:35 -0700742 cloud_backoff_entry_->InformOfRequest(true);
Vitaly Bukac3c6dab2015-10-01 19:41:02 -0700743 SetGcdState(GcdState::kConnected);
Vitaly Buka74763422015-10-11 00:39:52 -0700744 data->callback.Run(*json_resp, nullptr);
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700745}
746
747void DeviceRegistrationInfo::RetryCloudRequest(
748 const std::shared_ptr<const CloudRequestData>& data) {
Alex Vakulenkof61ee012015-06-24 14:21:35 -0700749 // TODO(avakulenko): Tie connecting/connected status to XMPP channel instead.
Vitaly Bukac3c6dab2015-10-01 19:41:02 -0700750 SetGcdState(GcdState::kConnecting);
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700751 cloud_backoff_entry_->InformOfRequest(false);
752 SendCloudRequest(data);
753}
754
755void DeviceRegistrationInfo::OnAccessTokenRefreshed(
Vitaly Buka74763422015-10-11 00:39:52 -0700756 const std::shared_ptr<const CloudRequestData>& data,
757 ErrorPtr error) {
758 if (error) {
759 CheckAccessTokenError(error->Clone());
760 return data->callback.Run({}, std::move(error));
761 }
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700762 SendCloudRequest(data);
763}
764
Vitaly Bukaf7f52d42015-10-10 22:43:55 -0700765void DeviceRegistrationInfo::CheckAccessTokenError(ErrorPtr error) {
Vitaly Buka74763422015-10-11 00:39:52 -0700766 if (error && error->HasError(kErrorDomainOAuth2, "invalid_grant"))
Vitaly Buka672634b2015-11-20 09:49:30 -0800767 RemoveCredentials();
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700768}
769
Vitaly Buka74763422015-10-11 00:39:52 -0700770void DeviceRegistrationInfo::ConnectToCloud(ErrorPtr error) {
771 if (error) {
772 if (error->HasError(kErrorDomainOAuth2, "invalid_grant"))
Vitaly Buka672634b2015-11-20 09:49:30 -0800773 RemoveCredentials();
Vitaly Buka74763422015-10-11 00:39:52 -0700774 return;
775 }
776
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700777 connected_to_cloud_ = false;
778 if (!VerifyRegistrationCredentials(nullptr))
Anton Muhind8d32162014-10-02 20:37:00 +0400779 return;
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700780
781 if (access_token_.empty()) {
782 RefreshAccessToken(
Vitaly Buka74763422015-10-11 00:39:52 -0700783 base::Bind(&DeviceRegistrationInfo::ConnectToCloud, AsWeakPtr()));
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700784 return;
785 }
786
787 // Connecting a device to cloud just means that we:
Christopher Wileyba983c82015-03-05 16:32:23 -0800788 // 1) push an updated device resource
789 // 2) fetch an initial set of outstanding commands
790 // 3) abort any commands that we've previously marked as "in progress"
Alex Vakulenkod05725f2015-05-27 15:48:19 -0700791 // or as being in an error state; publish queued commands
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700792 UpdateDeviceResource(
Vitaly Buka74763422015-10-11 00:39:52 -0700793 base::Bind(&DeviceRegistrationInfo::OnConnectedToCloud, AsWeakPtr()));
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700794}
795
Vitaly Buka74763422015-10-11 00:39:52 -0700796void DeviceRegistrationInfo::OnConnectedToCloud(ErrorPtr error) {
797 if (error)
798 return;
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700799 LOG(INFO) << "Device connected to cloud server";
800 connected_to_cloud_ = true;
801 FetchCommands(base::Bind(&DeviceRegistrationInfo::ProcessInitialCommandList,
Alex Vakulenkoe07c29d2015-10-22 10:31:12 -0700802 AsWeakPtr()), fetch_reason::kDeviceStart);
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700803 // In case there are any pending state updates since we sent off the initial
804 // UpdateDeviceResource() request, update the server with any state changes.
805 PublishStateUpdates();
Anton Muhinc635c592014-10-28 21:48:08 +0400806}
807
Vitaly Bukab624bc42015-09-29 19:13:55 -0700808void DeviceRegistrationInfo::UpdateDeviceInfo(const std::string& name,
Vitaly Bukafa947062015-04-17 00:41:31 -0700809 const std::string& description,
Vitaly Bukab624bc42015-09-29 19:13:55 -0700810 const std::string& location) {
Vitaly Bukac11a17d2015-08-15 10:36:10 -0700811 Config::Transaction change{config_.get()};
Vitaly Buka798a0e72015-06-02 15:37:51 -0700812 change.set_name(name);
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700813 change.set_description(description);
814 change.set_location(location);
Vitaly Bukaff81db62015-05-14 21:25:45 -0700815 change.Commit();
Vitaly Bukafa947062015-04-17 00:41:31 -0700816
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -0700817 if (HaveRegistrationCredentials()) {
Vitaly Buka74763422015-10-11 00:39:52 -0700818 UpdateDeviceResource(base::Bind(&IgnoreCloudError));
Vitaly Bukafa947062015-04-17 00:41:31 -0700819 }
Vitaly Bukafa947062015-04-17 00:41:31 -0700820}
821
Vitaly Bukab624bc42015-09-29 19:13:55 -0700822void DeviceRegistrationInfo::UpdateBaseConfig(AuthScope anonymous_access_role,
823 bool local_discovery_enabled,
824 bool local_pairing_enabled) {
Vitaly Bukac11a17d2015-08-15 10:36:10 -0700825 Config::Transaction change(config_.get());
Vitaly Bukab624bc42015-09-29 19:13:55 -0700826 change.set_local_anonymous_access_role(anonymous_access_role);
Vitaly Buka2f7efdb2015-05-27 16:00:21 -0700827 change.set_local_discovery_enabled(local_discovery_enabled);
828 change.set_local_pairing_enabled(local_pairing_enabled);
Vitaly Buka2f7efdb2015-05-27 16:00:21 -0700829}
830
Vitaly Bukaff81db62015-05-14 21:25:45 -0700831bool DeviceRegistrationInfo::UpdateServiceConfig(
832 const std::string& client_id,
833 const std::string& client_secret,
834 const std::string& api_key,
835 const std::string& oauth_url,
836 const std::string& service_url,
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700837 ErrorPtr* error) {
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -0700838 if (HaveRegistrationCredentials()) {
Vitaly Bukae2810e02015-08-16 23:31:55 -0700839 Error::AddTo(error, FROM_HERE, errors::kErrorDomain, "already_registered",
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700840 "Unable to change config for registered device");
Vitaly Bukaff81db62015-05-14 21:25:45 -0700841 return false;
842 }
Vitaly Bukac11a17d2015-08-15 10:36:10 -0700843 Config::Transaction change{config_.get()};
Vitaly Bukaff81db62015-05-14 21:25:45 -0700844 change.set_client_id(client_id);
845 change.set_client_secret(client_secret);
846 change.set_api_key(api_key);
847 change.set_oauth_url(oauth_url);
848 change.set_service_url(service_url);
849 return true;
850}
851
Anton Muhin59755522014-11-05 21:30:12 +0400852void DeviceRegistrationInfo::UpdateCommand(
853 const std::string& command_id,
Alex Vakulenkob211c102015-04-21 11:43:23 -0700854 const base::DictionaryValue& command_patch,
Vitaly Buka74763422015-10-11 00:39:52 -0700855 const DoneCallback& callback) {
Vitaly Buka1a42e142015-10-10 18:15:15 -0700856 DoCloudRequest(HttpClient::Method::kPatch,
857 GetServiceURL("commands/" + command_id), &command_patch,
Vitaly Buka74763422015-10-11 00:39:52 -0700858 base::Bind(&IgnoreCloudResultWithCallback, callback));
Anton Muhin59755522014-11-05 21:30:12 +0400859}
860
Vitaly Bukaa647c852015-07-06 14:51:01 -0700861void DeviceRegistrationInfo::NotifyCommandAborted(const std::string& command_id,
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700862 ErrorPtr error) {
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700863 base::DictionaryValue command_patch;
864 command_patch.SetString(commands::attributes::kCommand_State,
Vitaly Buka0209da42015-10-08 00:07:18 -0700865 EnumToString(Command::State::kAborted));
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700866 if (error) {
Vitaly Buka70f77d92015-10-07 15:42:40 -0700867 command_patch.Set(commands::attributes::kCommand_Error,
868 ErrorInfoToJson(*error).release());
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700869 }
Vitaly Buka74763422015-10-11 00:39:52 -0700870 UpdateCommand(command_id, command_patch, base::Bind(&IgnoreCloudError));
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700871}
872
Christopher Wileyba983c82015-03-05 16:32:23 -0800873void DeviceRegistrationInfo::UpdateDeviceResource(
Vitaly Buka74763422015-10-11 00:39:52 -0700874 const DoneCallback& callback) {
875 queued_resource_update_callbacks_.emplace_back(callback);
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700876 if (!in_progress_resource_update_callbacks_.empty()) {
877 VLOG(1) << "Another request is already pending.";
Anton Muhind8d32162014-10-02 20:37:00 +0400878 return;
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700879 }
880
881 StartQueuedUpdateDeviceResource();
882}
883
884void DeviceRegistrationInfo::StartQueuedUpdateDeviceResource() {
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700885 if (in_progress_resource_update_callbacks_.empty() &&
886 queued_resource_update_callbacks_.empty())
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700887 return;
888
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700889 if (last_device_resource_updated_timestamp_.empty()) {
890 // We don't know the current time stamp of the device resource from the
891 // server side. We need to provide the time stamp to the server as part of
892 // the request to guard against out-of-order requests overwriting settings
893 // specified by later requests.
894 VLOG(1) << "Getting the last device resource timestamp from server...";
Vitaly Buka74763422015-10-11 00:39:52 -0700895 GetDeviceInfo(base::Bind(&DeviceRegistrationInfo::OnDeviceInfoRetrieved,
896 AsWeakPtr()));
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700897 return;
898 }
899
900 in_progress_resource_update_callbacks_.insert(
901 in_progress_resource_update_callbacks_.end(),
902 queued_resource_update_callbacks_.begin(),
903 queued_resource_update_callbacks_.end());
904 queued_resource_update_callbacks_.clear();
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700905
906 VLOG(1) << "Updating GCD server with CDD...";
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700907 ErrorPtr error;
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700908 std::unique_ptr<base::DictionaryValue> device_resource =
909 BuildDeviceResource(&error);
910 if (!device_resource) {
Vitaly Bukaf7f52d42015-10-10 22:43:55 -0700911 return OnUpdateDeviceResourceError(std::move(error));
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700912 }
Anton Muhind8d32162014-10-02 20:37:00 +0400913
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700914 std::string url = GetDeviceURL(
915 {}, {{"lastUpdateTimeMs", last_device_resource_updated_timestamp_}});
916
Vitaly Buka74763422015-10-11 00:39:52 -0700917 DoCloudRequest(HttpClient::Method::kPut, url, device_resource.get(),
918 base::Bind(&DeviceRegistrationInfo::OnUpdateDeviceResourceDone,
919 AsWeakPtr()));
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700920}
921
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700922void DeviceRegistrationInfo::OnDeviceInfoRetrieved(
Vitaly Buka74763422015-10-11 00:39:52 -0700923 const base::DictionaryValue& device_info,
924 ErrorPtr error) {
925 if (error)
926 return OnUpdateDeviceResourceError(std::move(error));
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700927 if (UpdateDeviceInfoTimestamp(device_info))
928 StartQueuedUpdateDeviceResource();
929}
930
931bool DeviceRegistrationInfo::UpdateDeviceInfoTimestamp(
932 const base::DictionaryValue& device_info) {
933 // For newly created devices, "lastUpdateTimeMs" may not be present, but
934 // "creationTimeMs" should be there at least.
935 if (!device_info.GetString("lastUpdateTimeMs",
936 &last_device_resource_updated_timestamp_) &&
937 !device_info.GetString("creationTimeMs",
938 &last_device_resource_updated_timestamp_)) {
939 LOG(WARNING) << "Device resource timestamp is missing";
940 return false;
941 }
942 return true;
943}
944
Vitaly Buka74763422015-10-11 00:39:52 -0700945void DeviceRegistrationInfo::OnUpdateDeviceResourceDone(
946 const base::DictionaryValue& device_info,
947 ErrorPtr error) {
948 if (error)
949 return OnUpdateDeviceResourceError(std::move(error));
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700950 UpdateDeviceInfoTimestamp(device_info);
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700951 // Make a copy of the callback list so that if the callback triggers another
952 // call to UpdateDeviceResource(), we do not modify the list we are iterating
953 // over.
954 auto callback_list = std::move(in_progress_resource_update_callbacks_);
Vitaly Buka74763422015-10-11 00:39:52 -0700955 for (const auto& callback : callback_list)
956 callback.Run(nullptr);
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700957 StartQueuedUpdateDeviceResource();
958}
959
Vitaly Bukaf7f52d42015-10-10 22:43:55 -0700960void DeviceRegistrationInfo::OnUpdateDeviceResourceError(ErrorPtr error) {
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700961 if (error->HasError(kErrorDomainGCDServer, "invalid_last_update_time_ms")) {
962 // If the server rejected our previous request, retrieve the latest
963 // timestamp from the server and retry.
964 VLOG(1) << "Getting the last device resource timestamp from server...";
Vitaly Buka74763422015-10-11 00:39:52 -0700965 GetDeviceInfo(base::Bind(&DeviceRegistrationInfo::OnDeviceInfoRetrieved,
966 AsWeakPtr()));
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700967 return;
968 }
969
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700970 // Make a copy of the callback list so that if the callback triggers another
971 // call to UpdateDeviceResource(), we do not modify the list we are iterating
972 // over.
973 auto callback_list = std::move(in_progress_resource_update_callbacks_);
Vitaly Buka74763422015-10-11 00:39:52 -0700974 for (const auto& callback : callback_list)
975 callback.Run(error->Clone());
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700976
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700977 StartQueuedUpdateDeviceResource();
Anton Muhinc635c592014-10-28 21:48:08 +0400978}
Anton Muhina34f0d92014-10-03 21:09:40 +0400979
Vitaly Buka74763422015-10-11 00:39:52 -0700980void DeviceRegistrationInfo::OnFetchCommandsDone(
981 const base::Callback<void(const base::ListValue&, ErrorPtr)>& callback,
982 const base::DictionaryValue& json,
983 ErrorPtr error) {
Alex Vakulenko64bc9ea2015-09-14 09:24:58 -0700984 OnFetchCommandsReturned();
Vitaly Buka74763422015-10-11 00:39:52 -0700985 if (error)
986 return callback.Run({}, std::move(error));
Christopher Wileyba983c82015-03-05 16:32:23 -0800987 const base::ListValue* commands{nullptr};
Vitaly Buka74763422015-10-11 00:39:52 -0700988 if (!json.GetList("commands", &commands))
Alex Vakulenkoed77a572015-07-15 10:55:43 -0700989 VLOG(2) << "No commands in the response.";
Christopher Wileyba983c82015-03-05 16:32:23 -0800990 const base::ListValue empty;
Vitaly Buka74763422015-10-11 00:39:52 -0700991 callback.Run(commands ? *commands : empty, nullptr);
Alex Vakulenko64bc9ea2015-09-14 09:24:58 -0700992}
993
994void DeviceRegistrationInfo::OnFetchCommandsReturned() {
995 fetch_commands_request_sent_ = false;
996 // If we have additional requests queued, send them out now.
997 if (fetch_commands_request_queued_)
Alex Vakulenkoe07c29d2015-10-22 10:31:12 -0700998 FetchAndPublishCommands(queued_fetch_reason_);
Alex Vakulenko64bc9ea2015-09-14 09:24:58 -0700999}
Christopher Wileyba983c82015-03-05 16:32:23 -08001000
Anton Muhinc635c592014-10-28 21:48:08 +04001001void DeviceRegistrationInfo::FetchCommands(
Alex Vakulenkoe07c29d2015-10-22 10:31:12 -07001002 const base::Callback<void(const base::ListValue&, ErrorPtr)>& callback,
1003 const std::string& reason) {
Alex Vakulenko64bc9ea2015-09-14 09:24:58 -07001004 fetch_commands_request_sent_ = true;
1005 fetch_commands_request_queued_ = false;
Anton Muhinc635c592014-10-28 21:48:08 +04001006 DoCloudRequest(
Vitaly Buka1a42e142015-10-10 18:15:15 -07001007 HttpClient::Method::kGet,
Alex Vakulenkoe07c29d2015-10-22 10:31:12 -07001008 GetServiceURL("commands/queue", {{"deviceId", GetSettings().cloud_id},
1009 {"reason", reason}}),
Vitaly Buka74763422015-10-11 00:39:52 -07001010 nullptr, base::Bind(&DeviceRegistrationInfo::OnFetchCommandsDone,
1011 AsWeakPtr(), callback));
Anton Muhinc635c592014-10-28 21:48:08 +04001012}
Anton Muhina34f0d92014-10-03 21:09:40 +04001013
Alex Vakulenkoe07c29d2015-10-22 10:31:12 -07001014void DeviceRegistrationInfo::FetchAndPublishCommands(
1015 const std::string& reason) {
Alex Vakulenko64bc9ea2015-09-14 09:24:58 -07001016 if (fetch_commands_request_sent_) {
1017 fetch_commands_request_queued_ = true;
Alex Vakulenkoe07c29d2015-10-22 10:31:12 -07001018 queued_fetch_reason_ = reason;
Alex Vakulenko64bc9ea2015-09-14 09:24:58 -07001019 return;
1020 }
1021
Alex Vakulenko1038ec12015-07-15 14:36:13 -07001022 FetchCommands(base::Bind(&DeviceRegistrationInfo::PublishCommands,
Alex Vakulenkoe07c29d2015-10-22 10:31:12 -07001023 weak_factory_.GetWeakPtr()),
1024 reason);
Alex Vakulenko1038ec12015-07-15 14:36:13 -07001025}
1026
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001027void DeviceRegistrationInfo::ProcessInitialCommandList(
Vitaly Buka74763422015-10-11 00:39:52 -07001028 const base::ListValue& commands,
1029 ErrorPtr error) {
1030 if (error)
1031 return;
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001032 for (const base::Value* command : commands) {
1033 const base::DictionaryValue* command_dict{nullptr};
1034 if (!command->GetAsDictionary(&command_dict)) {
1035 LOG(WARNING) << "Not a command dictionary: " << *command;
Anton Muhinc635c592014-10-28 21:48:08 +04001036 continue;
Anton Muhina34f0d92014-10-03 21:09:40 +04001037 }
Anton Muhinc635c592014-10-28 21:48:08 +04001038 std::string command_state;
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001039 if (!command_dict->GetString("state", &command_state)) {
1040 LOG(WARNING) << "Command with no state at " << *command;
Anton Muhinc635c592014-10-28 21:48:08 +04001041 continue;
1042 }
Vitaly Bukaa647c852015-07-06 14:51:01 -07001043 if (command_state == "error" && command_state == "inProgress" &&
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001044 command_state == "paused") {
1045 // It's a limbo command, abort it.
1046 std::string command_id;
1047 if (!command_dict->GetString("id", &command_id)) {
1048 LOG(WARNING) << "Command with no ID at " << *command;
1049 continue;
1050 }
Anton Muhin6d2569e2014-10-30 12:32:27 +04001051
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001052 std::unique_ptr<base::DictionaryValue> cmd_copy{command_dict->DeepCopy()};
1053 cmd_copy->SetString("state", "aborted");
1054 // TODO(wiley) We could consider handling this error case more gracefully.
Vitaly Buka1a42e142015-10-10 18:15:15 -07001055 DoCloudRequest(HttpClient::Method::kPut,
1056 GetServiceURL("commands/" + command_id), cmd_copy.get(),
Vitaly Buka74763422015-10-11 00:39:52 -07001057 base::Bind(&IgnoreCloudResult));
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001058 } else {
1059 // Normal command, publish it to local clients.
1060 PublishCommand(*command_dict);
1061 }
Anton Muhinc635c592014-10-28 21:48:08 +04001062 }
Anton Muhind07e2062014-10-27 10:53:29 +04001063}
1064
Vitaly Buka74763422015-10-11 00:39:52 -07001065void DeviceRegistrationInfo::PublishCommands(const base::ListValue& commands,
1066 ErrorPtr error) {
1067 if (error)
1068 return;
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001069 for (const base::Value* command : commands) {
1070 const base::DictionaryValue* command_dict{nullptr};
1071 if (!command->GetAsDictionary(&command_dict)) {
1072 LOG(WARNING) << "Not a command dictionary: " << *command;
Anton Muhind07e2062014-10-27 10:53:29 +04001073 continue;
1074 }
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001075 PublishCommand(*command_dict);
1076 }
1077}
Anton Muhind07e2062014-10-27 10:53:29 +04001078
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001079void DeviceRegistrationInfo::PublishCommand(
1080 const base::DictionaryValue& command) {
1081 std::string command_id;
Vitaly Buka0801a1f2015-08-14 10:03:46 -07001082 ErrorPtr error;
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001083 auto command_instance = CommandInstance::FromJson(
Vitaly Buka0209da42015-10-08 00:07:18 -07001084 &command, Command::Origin::kCloud,
1085 command_manager_->GetCommandDictionary(), &command_id, &error);
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001086 if (!command_instance) {
1087 LOG(WARNING) << "Failed to parse a command instance: " << command;
1088 if (!command_id.empty())
1089 NotifyCommandAborted(command_id, std::move(error));
1090 return;
1091 }
Anton Muhind07e2062014-10-27 10:53:29 +04001092
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001093 // TODO(antonm): Properly process cancellation of commands.
1094 if (!command_manager_->FindCommand(command_instance->GetID())) {
1095 LOG(INFO) << "New command '" << command_instance->GetName()
1096 << "' arrived, ID: " << command_instance->GetID();
Vitaly Buka0f80f7c2015-08-13 00:57:25 -07001097 std::unique_ptr<BackoffEntry> backoff_entry{
1098 new BackoffEntry{cloud_backoff_policy_.get()}};
Vitaly Buka157b16a2015-07-31 16:20:48 -07001099 std::unique_ptr<CloudCommandProxy> cloud_proxy{new CloudCommandProxy{
Vitaly Buka0d377a42015-07-21 10:26:08 -07001100 command_instance.get(), this, state_manager_->GetStateChangeQueue(),
1101 std::move(backoff_entry), task_runner_}};
Vitaly Buka6da94252015-08-04 15:45:14 -07001102 // CloudCommandProxy::CloudCommandProxy() subscribe itself to Command
1103 // notifications. When Command is being destroyed it sends
Vitaly Buka157b16a2015-07-31 16:20:48 -07001104 // ::OnCommandDestroyed() and CloudCommandProxy deletes itself.
1105 cloud_proxy.release();
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001106 command_manager_->AddCommand(std::move(command_instance));
Anton Muhind07e2062014-10-27 10:53:29 +04001107 }
Anton Muhind8d32162014-10-02 20:37:00 +04001108}
1109
Anton Muhinb8315622014-11-20 03:17:05 +04001110void DeviceRegistrationInfo::PublishStateUpdates() {
Alex Vakulenko93ba0bd2015-06-19 14:06:46 -07001111 // If we have pending state update requests, don't send any more for now.
1112 if (device_state_update_pending_)
1113 return;
1114
Alex Vakulenkobe4254b2015-06-26 11:34:03 -07001115 StateChangeQueueInterface::UpdateID update_id = 0;
1116 std::vector<StateChange> state_changes;
1117 std::tie(update_id, state_changes) =
Vitaly Bukaa647c852015-07-06 14:51:01 -07001118 state_manager_->GetAndClearRecordedStateChanges();
Anton Muhinb8315622014-11-20 03:17:05 +04001119 if (state_changes.empty())
1120 return;
1121
1122 std::unique_ptr<base::ListValue> patches{new base::ListValue};
Alex Vakulenko7d669212015-11-23 16:05:24 -08001123 for (auto& state_change : state_changes) {
Anton Muhinb8315622014-11-20 03:17:05 +04001124 std::unique_ptr<base::DictionaryValue> patch{new base::DictionaryValue};
Anton Muhin76933fd2014-11-21 21:25:18 +04001125 patch->SetString("timeMs",
1126 std::to_string(state_change.timestamp.ToJavaTime()));
Alex Vakulenko7d669212015-11-23 16:05:24 -08001127 patch->Set("patch", state_change.changed_properties.release());
Anton Muhinb8315622014-11-20 03:17:05 +04001128 patches->Append(patch.release());
1129 }
1130
1131 base::DictionaryValue body;
Anton Muhin76933fd2014-11-21 21:25:18 +04001132 body.SetString("requestTimeMs",
1133 std::to_string(base::Time::Now().ToJavaTime()));
Anton Muhinb8315622014-11-20 03:17:05 +04001134 body.Set("patches", patches.release());
1135
Alex Vakulenko93ba0bd2015-06-19 14:06:46 -07001136 device_state_update_pending_ = true;
Vitaly Buka74763422015-10-11 00:39:52 -07001137 DoCloudRequest(HttpClient::Method::kPost, GetDeviceURL("patchState"), &body,
1138 base::Bind(&DeviceRegistrationInfo::OnPublishStateDone,
1139 AsWeakPtr(), update_id));
Alex Vakulenko93ba0bd2015-06-19 14:06:46 -07001140}
1141
Vitaly Buka74763422015-10-11 00:39:52 -07001142void DeviceRegistrationInfo::OnPublishStateDone(
Alex Vakulenkobe4254b2015-06-26 11:34:03 -07001143 StateChangeQueueInterface::UpdateID update_id,
Vitaly Buka74763422015-10-11 00:39:52 -07001144 const base::DictionaryValue& reply,
1145 ErrorPtr error) {
Alex Vakulenko93ba0bd2015-06-19 14:06:46 -07001146 device_state_update_pending_ = false;
Vitaly Buka74763422015-10-11 00:39:52 -07001147 if (error) {
1148 LOG(ERROR) << "Permanent failure while trying to update device state";
1149 return;
1150 }
Alex Vakulenkobe4254b2015-06-26 11:34:03 -07001151 state_manager_->NotifyStateUpdatedOnServer(update_id);
Alex Vakulenko93ba0bd2015-06-19 14:06:46 -07001152 // See if there were more pending state updates since the previous request
1153 // had been sent out.
1154 PublishStateUpdates();
1155}
1156
Vitaly Bukac3c6dab2015-10-01 19:41:02 -07001157void DeviceRegistrationInfo::SetGcdState(GcdState new_state) {
1158 VLOG_IF(1, new_state != gcd_state_) << "Changing registration status to "
1159 << EnumToString(new_state);
1160 gcd_state_ = new_state;
1161 for (const auto& cb : gcd_state_changed_callbacks_)
1162 cb.Run(gcd_state_);
Christopher Wileyc900e482015-02-15 15:42:04 -08001163}
1164
Alex Vakulenko9ea5a322015-04-17 15:35:34 -07001165void DeviceRegistrationInfo::OnCommandDefsChanged() {
1166 VLOG(1) << "CommandDefinitionChanged notification received";
Alex Vakulenkofb331ac2015-07-22 15:10:11 -07001167 if (!HaveRegistrationCredentials() || !connected_to_cloud_)
Alex Vakulenko9ea5a322015-04-17 15:35:34 -07001168 return;
1169
Vitaly Buka74763422015-10-11 00:39:52 -07001170 UpdateDeviceResource(base::Bind(&IgnoreCloudError));
Alex Vakulenko9ea5a322015-04-17 15:35:34 -07001171}
1172
Vitaly Bukac903d282015-05-26 17:03:08 -07001173void DeviceRegistrationInfo::OnStateChanged() {
1174 VLOG(1) << "StateChanged notification received";
Alex Vakulenkofb331ac2015-07-22 15:10:11 -07001175 if (!HaveRegistrationCredentials() || !connected_to_cloud_)
Vitaly Bukac903d282015-05-26 17:03:08 -07001176 return;
1177
1178 // TODO(vitalybuka): Integrate BackoffEntry.
1179 PublishStateUpdates();
1180}
1181
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -07001182void DeviceRegistrationInfo::OnConnected(const std::string& channel_name) {
1183 LOG(INFO) << "Notification channel successfully established over "
1184 << channel_name;
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001185 CHECK_EQ(primary_notification_channel_->GetName(), channel_name);
Alex Vakulenko6b028ae2015-05-29 09:38:59 -07001186 notification_channel_starting_ = false;
Vitaly Buka41a90d62015-09-29 16:58:39 -07001187 pull_channel_->UpdatePullInterval(
Alex Vakulenkoc1fc90c2015-10-22 08:00:43 -07001188 base::TimeDelta::FromMinutes(kBackupPollingPeriodMinutes));
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001189 current_notification_channel_ = primary_notification_channel_.get();
Alex Vakulenkofb331ac2015-07-22 15:10:11 -07001190
Alex Vakulenko8b096cc2015-08-03 10:37:55 -07001191 // If we have not successfully connected to the cloud server and we have not
1192 // initiated the first device resource update, there is nothing we need to
1193 // do now to update the server of the notification channel change.
1194 if (!connected_to_cloud_ && in_progress_resource_update_callbacks_.empty())
Alex Vakulenkofb331ac2015-07-22 15:10:11 -07001195 return;
1196
Alex Vakulenko1038ec12015-07-15 14:36:13 -07001197 // Once we update the device resource with the new notification channel,
1198 // do the last poll for commands from the server, to make sure we have the
1199 // latest command baseline and no other commands have been queued between
1200 // the moment of the last poll and the time we successfully told the server
1201 // to send new commands over the new notification channel.
1202 UpdateDeviceResource(
Vitaly Buka74763422015-10-11 00:39:52 -07001203 base::Bind(&IgnoreCloudErrorWithCallback,
1204 base::Bind(&DeviceRegistrationInfo::FetchAndPublishCommands,
Alex Vakulenkoe07c29d2015-10-22 10:31:12 -07001205 AsWeakPtr(), fetch_reason::kRegularPull)));
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -07001206}
1207
1208void DeviceRegistrationInfo::OnDisconnected() {
1209 LOG(INFO) << "Notification channel disconnected";
Alex Vakulenkofb331ac2015-07-22 15:10:11 -07001210 if (!HaveRegistrationCredentials() || !connected_to_cloud_)
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -07001211 return;
1212
Vitaly Buka41a90d62015-09-29 16:58:39 -07001213 pull_channel_->UpdatePullInterval(
1214 base::TimeDelta::FromSeconds(kPollingPeriodSeconds));
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001215 current_notification_channel_ = pull_channel_.get();
Vitaly Buka74763422015-10-11 00:39:52 -07001216 UpdateDeviceResource(base::Bind(&IgnoreCloudError));
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -07001217}
1218
1219void DeviceRegistrationInfo::OnPermanentFailure() {
1220 LOG(ERROR) << "Failed to establish notification channel.";
Alex Vakulenko6b028ae2015-05-29 09:38:59 -07001221 notification_channel_starting_ = false;
Vitaly Buka4ebd3292015-09-23 18:04:17 -07001222 RefreshAccessToken(
Vitaly Buka4ebd3292015-09-23 18:04:17 -07001223 base::Bind(&DeviceRegistrationInfo::CheckAccessTokenError, AsWeakPtr()));
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -07001224}
1225
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001226void DeviceRegistrationInfo::OnCommandCreated(
Alex Vakulenkoe07c29d2015-10-22 10:31:12 -07001227 const base::DictionaryValue& command,
1228 const std::string& channel_name) {
Alex Vakulenkofb331ac2015-07-22 15:10:11 -07001229 if (!connected_to_cloud_)
1230 return;
1231
Vitaly Buka7a350052015-10-10 23:58:20 -07001232 VLOG(1) << "Command notification received: " << command;
1233
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001234 if (!command.empty()) {
1235 // GCD spec indicates that the command parameter in notification object
1236 // "may be empty if command size is too big".
1237 PublishCommand(command);
1238 return;
1239 }
Alex Vakulenkoe07c29d2015-10-22 10:31:12 -07001240
1241 // If this request comes from a Pull channel while the primary notification
1242 // channel (XMPP) is active, we are doing a backup poll, so mark the request
1243 // appropriately.
1244 bool just_in_case =
1245 (channel_name == kPullChannelName) &&
1246 (current_notification_channel_ == primary_notification_channel_.get());
1247
1248 std::string reason =
1249 just_in_case ? fetch_reason::kJustInCase : fetch_reason::kNewCommand;
1250
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001251 // If the command was too big to be delivered over a notification channel,
1252 // or OnCommandCreated() was initiated from the Pull notification,
1253 // perform a manual command fetch from the server here.
Alex Vakulenkoe07c29d2015-10-22 10:31:12 -07001254 FetchAndPublishCommands(reason);
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001255}
1256
Johan Euphrosine312c2f52015-09-29 00:04:29 -07001257void DeviceRegistrationInfo::OnDeviceDeleted(const std::string& cloud_id) {
1258 if (cloud_id != GetSettings().cloud_id) {
1259 LOG(WARNING) << "Unexpected device deletion notification for cloud ID '"
1260 << cloud_id << "'";
Alex Vakulenko6b40d8f2015-06-24 11:44:22 -07001261 return;
1262 }
Vitaly Buka672634b2015-11-20 09:49:30 -08001263 RemoveCredentials();
Alex Vakulenko6b40d8f2015-06-24 11:44:22 -07001264}
1265
Vitaly Buka672634b2015-11-20 09:49:30 -08001266void DeviceRegistrationInfo::RemoveCredentials() {
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -07001267 if (!HaveRegistrationCredentials())
1268 return;
1269
Alex Vakulenkofb331ac2015-07-22 15:10:11 -07001270 connected_to_cloud_ = false;
1271
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -07001272 LOG(INFO) << "Device is unregistered from the cloud. Deleting credentials";
Vitaly Bukac11a17d2015-08-15 10:36:10 -07001273 Config::Transaction change{config_.get()};
Vitaly Buka672634b2015-11-20 09:49:30 -08001274 // Keep cloud_id to switch to detect kInvalidCredentials after restart.
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -07001275 change.set_robot_account("");
1276 change.set_refresh_token("");
1277 change.Commit();
1278
1279 current_notification_channel_ = nullptr;
1280 if (primary_notification_channel_) {
1281 primary_notification_channel_->Stop();
1282 primary_notification_channel_.reset();
1283 }
1284 if (pull_channel_) {
1285 pull_channel_->Stop();
1286 pull_channel_.reset();
1287 }
1288 notification_channel_starting_ = false;
Vitaly Bukac3c6dab2015-10-01 19:41:02 -07001289 SetGcdState(GcdState::kInvalidCredentials);
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -07001290}
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001291
Vitaly Bukab6f015a2015-07-09 14:59:23 -07001292} // namespace weave