blob: 30e8862286cc2d98bc9632bd3dd1e0e5bc3b6a82 [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"
Stefan Sauer2d16dfa2015-09-25 17:08:35 +020024#include "src/commands/command_manager.h"
25#include "src/commands/schema_constants.h"
26#include "src/data_encoding.h"
27#include "src/http_constants.h"
28#include "src/json_error_codes.h"
29#include "src/notification/xmpp_channel.h"
30#include "src/states/state_manager.h"
31#include "src/string_utils.h"
32#include "src/utils.h"
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070033
Vitaly Bukab6f015a2015-07-09 14:59:23 -070034namespace weave {
35
36const char kErrorDomainOAuth2[] = "oauth2";
37const char kErrorDomainGCD[] = "gcd";
38const char kErrorDomainGCDServer[] = "gcd_server";
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070039
Alex Vakulenko8e34d392014-04-29 11:02:56 -070040namespace {
41
Vitaly Buka41a90d62015-09-29 16:58:39 -070042const int kPollingPeriodSeconds = 7;
Alex Vakulenkoc1fc90c2015-10-22 08:00:43 -070043const int kBackupPollingPeriodMinutes = 30;
Vitaly Buka41a90d62015-09-29 16:58:39 -070044
Alex Vakulenkoe07c29d2015-10-22 10:31:12 -070045namespace fetch_reason {
46
47const char kDeviceStart[] = "device_start"; // Initial queue fetch at startup.
48const char kRegularPull[] = "regular_pull"; // Regular fetch before XMPP is up.
49const char kNewCommand[] = "new_command"; // A new command is available.
50const char kJustInCase[] = "just_in_case"; // Backup fetch when XMPP is live.
51
52} // namespace fetch_reason
53
Vitaly Buka1e363672015-09-25 14:01:16 -070054using provider::HttpClient;
55
Vitaly Buka0801a1f2015-08-14 10:03:46 -070056inline void SetUnexpectedError(ErrorPtr* error) {
57 Error::AddTo(error, FROM_HERE, kErrorDomainGCD, "unexpected_response",
58 "Unexpected GCD error");
Alex Vakulenkob3aac252014-05-07 17:35:24 -070059}
60
Vitaly Buka0801a1f2015-08-14 10:03:46 -070061void ParseGCDError(const base::DictionaryValue* json, ErrorPtr* error) {
Alex Vakulenkob3aac252014-05-07 17:35:24 -070062 const base::Value* list_value = nullptr;
63 const base::ListValue* error_list = nullptr;
64 if (!json->Get("error.errors", &list_value) ||
65 !list_value->GetAsList(&error_list)) {
66 SetUnexpectedError(error);
67 return;
68 }
69
70 for (size_t i = 0; i < error_list->GetSize(); i++) {
71 const base::Value* error_value = nullptr;
72 const base::DictionaryValue* error_object = nullptr;
73 if (!error_list->Get(i, &error_value) ||
74 !error_value->GetAsDictionary(&error_object)) {
75 SetUnexpectedError(error);
76 continue;
77 }
78 std::string error_code, error_message;
79 if (error_object->GetString("reason", &error_code) &&
80 error_object->GetString("message", &error_message)) {
Vitaly Buka0801a1f2015-08-14 10:03:46 -070081 Error::AddTo(error, FROM_HERE, kErrorDomainGCDServer, error_code,
82 error_message);
Alex Vakulenkob3aac252014-05-07 17:35:24 -070083 } else {
84 SetUnexpectedError(error);
85 }
86 }
87}
88
Vitaly Buka7d556392015-08-13 20:06:48 -070089std::string AppendQueryParams(const std::string& url,
90 const WebParamList& params) {
Vitaly Bukab001f282015-08-13 17:07:59 -070091 CHECK_EQ(std::string::npos, url.find_first_of("?#"));
92 if (params.empty())
93 return url;
Vitaly Buka7d556392015-08-13 20:06:48 -070094 return url + '?' + WebParamsEncode(params);
Vitaly Bukab001f282015-08-13 17:07:59 -070095}
96
Alex Vakulenkobda220a2014-04-18 15:25:44 -070097std::string BuildURL(const std::string& url,
Vitaly Bukab001f282015-08-13 17:07:59 -070098 const std::string& subpath,
Vitaly Buka7d556392015-08-13 20:06:48 -070099 const WebParamList& params) {
Vitaly Bukab001f282015-08-13 17:07:59 -0700100 std::string result = url;
101 if (!result.empty() && result.back() != '/' && !subpath.empty()) {
102 CHECK_NE('/', subpath.front());
103 result += '/';
104 }
105 result += subpath;
106 return AppendQueryParams(result, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700107}
108
Vitaly Bukaf7f52d42015-10-10 22:43:55 -0700109void IgnoreCloudErrorWithCallback(const base::Closure& cb, ErrorPtr) {
Christopher Wileyba983c82015-03-05 16:32:23 -0800110 cb.Run();
111}
112
Vitaly Buka74763422015-10-11 00:39:52 -0700113void IgnoreCloudError(ErrorPtr) {}
Anton Muhin6d2569e2014-10-30 12:32:27 +0400114
Vitaly Buka74763422015-10-11 00:39:52 -0700115void IgnoreCloudResult(const base::DictionaryValue&, ErrorPtr error) {}
116
117void IgnoreCloudResultWithCallback(const DoneCallback& cb,
118 const base::DictionaryValue&,
119 ErrorPtr error) {
120 cb.Run(std::move(error));
Christopher Wileyba983c82015-03-05 16:32:23 -0800121}
122
Vitaly Buka6da94252015-08-04 15:45:14 -0700123class RequestSender final {
124 public:
Vitaly Buka1a42e142015-10-10 18:15:15 -0700125 RequestSender(HttpClient::Method method,
Vitaly Buka6da94252015-08-04 15:45:14 -0700126 const std::string& url,
127 HttpClient* transport)
128 : method_{method}, url_{url}, transport_{transport} {}
129
Vitaly Buka74763422015-10-11 00:39:52 -0700130 void Send(const HttpClient::SendRequestCallback& callback) {
Vitaly Buka866b60a2015-10-09 14:24:55 -0700131 static int debug_id = 0;
132 ++debug_id;
Vitaly Buka1a42e142015-10-10 18:15:15 -0700133 VLOG(1) << "Sending request. id:" << debug_id
134 << " method:" << EnumToString(method_) << " url:" << url_;
Vitaly Buka866b60a2015-10-09 14:24:55 -0700135 VLOG(2) << "Request data: " << data_;
Vitaly Buka74763422015-10-11 00:39:52 -0700136 auto on_done = [](
137 int debug_id, const HttpClient::SendRequestCallback& callback,
138 std::unique_ptr<HttpClient::Response> response, ErrorPtr error) {
139 if (error) {
140 VLOG(1) << "Request failed, id=" << debug_id
141 << ", reason: " << error->GetCode()
142 << ", message: " << error->GetMessage();
143 return callback.Run({}, std::move(error));
144 }
145 VLOG(1) << "Request succeeded. id:" << debug_id
146 << " status:" << response->GetStatusCode();
147 VLOG(2) << "Response data: " << response->GetData();
148 callback.Run(std::move(response), nullptr);
Vitaly Buka866b60a2015-10-09 14:24:55 -0700149 };
150 transport_->SendRequest(method_, url_, GetFullHeaders(), data_,
Vitaly Buka74763422015-10-11 00:39:52 -0700151 base::Bind(on_done, debug_id, callback));
Vitaly Buka6da94252015-08-04 15:45:14 -0700152 }
153
Vitaly Buka815b6032015-08-06 11:06:25 -0700154 void SetAccessToken(const std::string& access_token) {
155 access_token_ = access_token;
Vitaly Buka6da94252015-08-04 15:45:14 -0700156 }
157
158 void SetData(const std::string& data, const std::string& mime_type) {
159 data_ = data;
160 mime_type_ = mime_type;
161 }
162
163 void SetFormData(
164 const std::vector<std::pair<std::string, std::string>>& data) {
Vitaly Buka7d556392015-08-13 20:06:48 -0700165 SetData(WebParamsEncode(data), http::kWwwFormUrlEncoded);
Vitaly Buka6da94252015-08-04 15:45:14 -0700166 }
167
168 void SetJsonData(const base::Value& json) {
169 std::string data;
Vitaly Buka815b6032015-08-06 11:06:25 -0700170 CHECK(base::JSONWriter::Write(json, &data));
171 SetData(data, http::kJsonUtf8);
Vitaly Buka6da94252015-08-04 15:45:14 -0700172 }
173
174 private:
Vitaly Buka815b6032015-08-06 11:06:25 -0700175 HttpClient::Headers GetFullHeaders() const {
176 HttpClient::Headers headers;
177 if (!access_token_.empty())
178 headers.emplace_back(http::kAuthorization, "Bearer " + access_token_);
179 if (!mime_type_.empty())
180 headers.emplace_back(http::kContentType, mime_type_);
181 return headers;
182 }
183
Vitaly Buka1a42e142015-10-10 18:15:15 -0700184 HttpClient::Method method_;
Vitaly Buka6da94252015-08-04 15:45:14 -0700185 std::string url_;
186 std::string data_;
187 std::string mime_type_;
Vitaly Buka815b6032015-08-06 11:06:25 -0700188 std::string access_token_;
Vitaly Buka6da94252015-08-04 15:45:14 -0700189 HttpClient* transport_{nullptr};
190
191 DISALLOW_COPY_AND_ASSIGN(RequestSender);
192};
193
194std::unique_ptr<base::DictionaryValue> ParseJsonResponse(
195 const HttpClient::Response& response,
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700196 ErrorPtr* error) {
Vitaly Buka6da94252015-08-04 15:45:14 -0700197 // Make sure we have a correct content type. Do not try to parse
198 // binary files, or HTML output. Limit to application/json and text/plain.
Vitaly Buka24d6fd52015-08-13 23:22:48 -0700199 std::string content_type =
200 SplitAtFirst(response.GetContentType(), ";", true).first;
Vitaly Buka1da65992015-08-06 01:38:57 -0700201
202 if (content_type != http::kJson && content_type != http::kPlain) {
Vitaly Bukac27390d2015-11-19 14:42:35 -0800203 Error::AddTo(
204 error, FROM_HERE, errors::json::kDomain, "non_json_content_type",
205 "Unexpected content type: \'" + response.GetContentType() + "\'");
Vitaly Buka6da94252015-08-04 15:45:14 -0700206 return std::unique_ptr<base::DictionaryValue>();
207 }
208
209 const std::string& json = response.GetData();
210 std::string error_message;
211 auto value = base::JSONReader::ReadAndReturnError(json, base::JSON_PARSE_RFC,
212 nullptr, &error_message);
213 if (!value) {
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700214 Error::AddToPrintf(error, FROM_HERE, errors::json::kDomain,
215 errors::json::kParseError,
216 "Error '%s' occurred parsing JSON string '%s'",
217 error_message.c_str(), json.c_str());
Vitaly Buka6da94252015-08-04 15:45:14 -0700218 return std::unique_ptr<base::DictionaryValue>();
219 }
220 base::DictionaryValue* dict_value = nullptr;
221 if (!value->GetAsDictionary(&dict_value)) {
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700222 Error::AddToPrintf(
Vitaly Bukaea2f15f2015-08-13 15:26:20 -0700223 error, FROM_HERE, errors::json::kDomain, errors::json::kObjectExpected,
Vitaly Buka6da94252015-08-04 15:45:14 -0700224 "Response is not a valid JSON object: '%s'", json.c_str());
225 return std::unique_ptr<base::DictionaryValue>();
226 } else {
227 // |value| is now owned by |dict_value|, so release the scoped_ptr now.
228 base::IgnoreResult(value.release());
229 }
230 return std::unique_ptr<base::DictionaryValue>(dict_value);
231}
232
233bool IsSuccessful(const HttpClient::Response& response) {
234 int code = response.GetStatusCode();
Vitaly Buka1da65992015-08-06 01:38:57 -0700235 return code >= http::kContinue && code < http::kBadRequest;
Vitaly Buka6da94252015-08-04 15:45:14 -0700236}
237
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700238} // anonymous namespace
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700239
Alex Vakulenko1f30a622014-07-23 11:13:15 -0700240DeviceRegistrationInfo::DeviceRegistrationInfo(
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700241 const std::shared_ptr<CommandManager>& command_manager,
Anton Muhin332df192014-11-22 05:59:14 +0400242 const std::shared_ptr<StateManager>& state_manager,
Vitaly Buka1e363672015-09-25 14:01:16 -0700243 std::unique_ptr<Config> config,
244 provider::TaskRunner* task_runner,
245 provider::HttpClient* http_client,
246 provider::Network* network)
Vitaly Buka10206182015-08-05 11:17:43 -0700247 : http_client_{http_client},
Alex Vakulenkoa56a7e62015-06-26 12:59:50 -0700248 task_runner_{task_runner},
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700249 command_manager_{command_manager},
Anton Muhin332df192014-11-22 05:59:14 +0400250 state_manager_{state_manager},
Christopher Wiley583d64b2015-03-24 14:30:17 -0700251 config_{std::move(config)},
Vitaly Bukaddb2e382015-07-31 00:33:31 -0700252 network_{network} {
Vitaly Buka0f80f7c2015-08-13 00:57:25 -0700253 cloud_backoff_policy_.reset(new BackoffEntry::Policy{});
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700254 cloud_backoff_policy_->num_errors_to_ignore = 0;
Alex Vakulenkof61ee012015-06-24 14:21:35 -0700255 cloud_backoff_policy_->initial_delay_ms = 1000;
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700256 cloud_backoff_policy_->multiply_factor = 2.0;
257 cloud_backoff_policy_->jitter_factor = 0.1;
258 cloud_backoff_policy_->maximum_backoff_ms = 30000;
259 cloud_backoff_policy_->entry_lifetime_ms = -1;
260 cloud_backoff_policy_->always_use_initial_delay = false;
Vitaly Buka0f80f7c2015-08-13 00:57:25 -0700261 cloud_backoff_entry_.reset(new BackoffEntry{cloud_backoff_policy_.get()});
262 oauth2_backoff_entry_.reset(new BackoffEntry{cloud_backoff_policy_.get()});
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700263
Vitaly Buka672634b2015-11-20 09:49:30 -0800264 bool revoked =
265 !GetSettings().cloud_id.empty() && !HaveRegistrationCredentials();
266 gcd_state_ =
267 revoked ? GcdState::kInvalidCredentials : GcdState::kUnconfigured;
268
Vitaly Buka553a7622015-10-05 13:53:20 -0700269 command_manager_->AddCommandDefChanged(
Alex Vakulenko9ea5a322015-04-17 15:35:34 -0700270 base::Bind(&DeviceRegistrationInfo::OnCommandDefsChanged,
271 weak_factory_.GetWeakPtr()));
Vitaly Buka4c981352015-10-01 23:04:24 -0700272 state_manager_->AddChangedCallback(base::Bind(
Vitaly Bukaa647c852015-07-06 14:51:01 -0700273 &DeviceRegistrationInfo::OnStateChanged, weak_factory_.GetWeakPtr()));
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700274}
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700275
Anton Muhin332df192014-11-22 05:59:14 +0400276DeviceRegistrationInfo::~DeviceRegistrationInfo() = default;
277
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700278std::string DeviceRegistrationInfo::GetServiceURL(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700279 const std::string& subpath,
Vitaly Buka7d556392015-08-13 20:06:48 -0700280 const WebParamList& params) const {
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700281 return BuildURL(GetSettings().service_url, subpath, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700282}
283
284std::string DeviceRegistrationInfo::GetDeviceURL(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700285 const std::string& subpath,
Vitaly Buka7d556392015-08-13 20:06:48 -0700286 const WebParamList& params) const {
Johan Euphrosine312c2f52015-09-29 00:04:29 -0700287 CHECK(!GetSettings().cloud_id.empty()) << "Must have a valid device ID";
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700288 return BuildURL(GetSettings().service_url,
Johan Euphrosine312c2f52015-09-29 00:04:29 -0700289 "devices/" + GetSettings().cloud_id + "/" + subpath, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700290}
291
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700292std::string DeviceRegistrationInfo::GetOAuthURL(
293 const std::string& subpath,
Vitaly Buka7d556392015-08-13 20:06:48 -0700294 const WebParamList& params) const {
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700295 return BuildURL(GetSettings().oauth_url, subpath, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700296}
297
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700298void DeviceRegistrationInfo::Start() {
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -0700299 if (HaveRegistrationCredentials()) {
Alex Vakulenkod05725f2015-05-27 15:48:19 -0700300 StartNotificationChannel();
Christopher Wileyba983c82015-03-05 16:32:23 -0800301 // Wait a significant amount of time for local daemons to publish their
302 // state to Buffet before publishing it to the cloud.
303 // TODO(wiley) We could do a lot of things here to either expose this
304 // timeout as a configurable knob or allow local
305 // daemons to signal that their state is up to date so that
306 // we need not wait for them.
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700307 ScheduleCloudConnection(base::TimeDelta::FromSeconds(5));
Christopher Wileyba983c82015-03-05 16:32:23 -0800308 }
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700309}
310
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700311void DeviceRegistrationInfo::ScheduleCloudConnection(
312 const base::TimeDelta& delay) {
Vitaly Bukac3c6dab2015-10-01 19:41:02 -0700313 SetGcdState(GcdState::kConnecting);
Alex Vakulenkoa56a7e62015-06-26 12:59:50 -0700314 if (!task_runner_)
Vitaly Buka0f6b2ec2015-08-20 15:35:19 -0700315 return; // Assume we're in test
Alex Vakulenkoa56a7e62015-06-26 12:59:50 -0700316 task_runner_->PostDelayedTask(
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700317 FROM_HERE,
Vitaly Buka74763422015-10-11 00:39:52 -0700318 base::Bind(&DeviceRegistrationInfo::ConnectToCloud, AsWeakPtr(), nullptr),
319 delay);
Christopher Wileycd419662015-02-06 17:51:43 -0800320}
321
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -0700322bool DeviceRegistrationInfo::HaveRegistrationCredentials() const {
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700323 return !GetSettings().refresh_token.empty() &&
Johan Euphrosine312c2f52015-09-29 00:04:29 -0700324 !GetSettings().cloud_id.empty() &&
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700325 !GetSettings().robot_account.empty();
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -0700326}
327
328bool DeviceRegistrationInfo::VerifyRegistrationCredentials(
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700329 ErrorPtr* error) const {
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -0700330 const bool have_credentials = HaveRegistrationCredentials();
Christopher Wileyc900e482015-02-15 15:42:04 -0800331
Alex Vakulenkoed77a572015-07-15 10:55:43 -0700332 VLOG(2) << "Device registration record "
Christopher Wileyc900e482015-02-15 15:42:04 -0800333 << ((have_credentials) ? "found" : "not found.");
334 if (!have_credentials)
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700335 Error::AddTo(error, FROM_HERE, kErrorDomainGCD, "device_not_registered",
336 "No valid device registration record found");
Christopher Wileyc900e482015-02-15 15:42:04 -0800337 return have_credentials;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700338}
339
Nathan Bullock24d189f2015-02-26 13:09:18 -0500340std::unique_ptr<base::DictionaryValue>
Vitaly Buka6da94252015-08-04 15:45:14 -0700341DeviceRegistrationInfo::ParseOAuthResponse(const HttpClient::Response& response,
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700342 ErrorPtr* error) {
Vitaly Buka6da94252015-08-04 15:45:14 -0700343 int code = response.GetStatusCode();
344 auto resp = ParseJsonResponse(response, error);
Vitaly Buka1da65992015-08-06 01:38:57 -0700345 if (resp && code >= http::kBadRequest) {
Nathan Bullock24d189f2015-02-26 13:09:18 -0500346 std::string error_code, error_message;
347 if (!resp->GetString("error", &error_code)) {
348 error_code = "unexpected_response";
349 }
350 if (error_code == "invalid_grant") {
351 LOG(INFO) << "The device's registration has been revoked.";
Vitaly Bukac3c6dab2015-10-01 19:41:02 -0700352 SetGcdState(GcdState::kInvalidCredentials);
Nathan Bullock24d189f2015-02-26 13:09:18 -0500353 }
354 // I have never actually seen an error_description returned.
355 if (!resp->GetString("error_description", &error_message)) {
356 error_message = "Unexpected OAuth error";
357 }
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700358 Error::AddTo(error, FROM_HERE, kErrorDomainOAuth2, error_code,
359 error_message);
Nathan Bullock24d189f2015-02-26 13:09:18 -0500360 return std::unique_ptr<base::DictionaryValue>();
361 }
362 return resp;
363}
364
Vitaly Buka74763422015-10-11 00:39:52 -0700365void DeviceRegistrationInfo::RefreshAccessToken(const DoneCallback& callback) {
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700366 LOG(INFO) << "Refreshing access token.";
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -0700367
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700368 ErrorPtr error;
Vitaly Buka74763422015-10-11 00:39:52 -0700369 if (!VerifyRegistrationCredentials(&error))
370 return callback.Run(std::move(error));
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -0700371
Alex Vakulenko0f91be82015-07-27 10:11:53 -0700372 if (oauth2_backoff_entry_->ShouldRejectRequest()) {
373 VLOG(1) << "RefreshToken request delayed for "
374 << oauth2_backoff_entry_->GetTimeUntilRelease()
375 << " due to backoff policy";
376 task_runner_->PostDelayedTask(
377 FROM_HERE, base::Bind(&DeviceRegistrationInfo::RefreshAccessToken,
Vitaly Buka74763422015-10-11 00:39:52 -0700378 AsWeakPtr(), callback),
Alex Vakulenko0f91be82015-07-27 10:11:53 -0700379 oauth2_backoff_entry_->GetTimeUntilRelease());
380 return;
381 }
382
Vitaly Buka1a42e142015-10-10 18:15:15 -0700383 RequestSender sender{HttpClient::Method::kPost, GetOAuthURL("token"),
384 http_client_};
Vitaly Buka6da94252015-08-04 15:45:14 -0700385 sender.SetFormData({
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700386 {"refresh_token", GetSettings().refresh_token},
387 {"client_id", GetSettings().client_id},
388 {"client_secret", GetSettings().client_secret},
Vitaly Bukaa647c852015-07-06 14:51:01 -0700389 {"grant_type", "refresh_token"},
Vitaly Buka6da94252015-08-04 15:45:14 -0700390 });
Vitaly Buka74763422015-10-11 00:39:52 -0700391 sender.Send(base::Bind(&DeviceRegistrationInfo::OnRefreshAccessTokenDone,
392 weak_factory_.GetWeakPtr(), callback));
Vitaly Buka866b60a2015-10-09 14:24:55 -0700393 VLOG(1) << "Refresh access token request dispatched";
David Zeuthen390d1912015-03-03 14:54:48 -0500394}
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700395
Vitaly Buka74763422015-10-11 00:39:52 -0700396void DeviceRegistrationInfo::OnRefreshAccessTokenDone(
397 const DoneCallback& callback,
398 std::unique_ptr<HttpClient::Response> response,
399 ErrorPtr error) {
400 if (error) {
401 VLOG(1) << "Refresh access token failed";
402 oauth2_backoff_entry_->InformOfRequest(false);
403 return RefreshAccessToken(callback);
404 }
Vitaly Buka866b60a2015-10-09 14:24:55 -0700405 VLOG(1) << "Refresh access token request completed";
Alex Vakulenko0f91be82015-07-27 10:11:53 -0700406 oauth2_backoff_entry_->InformOfRequest(true);
Vitaly Buka74763422015-10-11 00:39:52 -0700407 auto json = ParseOAuthResponse(*response, &error);
Vitaly Bukaf7f52d42015-10-10 22:43:55 -0700408 if (!json)
Vitaly Buka74763422015-10-11 00:39:52 -0700409 return callback.Run(std::move(error));
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700410
411 int expires_in = 0;
412 if (!json->GetString("access_token", &access_token_) ||
Vitaly Bukaa647c852015-07-06 14:51:01 -0700413 !json->GetInteger("expires_in", &expires_in) || access_token_.empty() ||
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700414 expires_in <= 0) {
415 LOG(ERROR) << "Access token unavailable.";
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700416 Error::AddTo(&error, FROM_HERE, kErrorDomainOAuth2,
417 "unexpected_server_response", "Access token unavailable");
Vitaly Buka74763422015-10-11 00:39:52 -0700418 return callback.Run(std::move(error));
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700419 }
Vitaly Bukaa647c852015-07-06 14:51:01 -0700420 access_token_expiration_ =
421 base::Time::Now() + base::TimeDelta::FromSeconds(expires_in);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700422 LOG(INFO) << "Access token is refreshed for additional " << expires_in
423 << " seconds.";
Nathan Bullockd9e0bcd2015-02-11 11:36:39 -0500424
Alex Vakulenko6b028ae2015-05-29 09:38:59 -0700425 if (primary_notification_channel_ &&
426 !primary_notification_channel_->IsConnected()) {
427 // If we have disconnected channel, it is due to failed credentials.
428 // Now that we have a new access token, retry the connection.
429 StartNotificationChannel();
430 }
Vitaly Buka74763422015-10-11 00:39:52 -0700431 callback.Run(nullptr);
Alex Vakulenko6b028ae2015-05-29 09:38:59 -0700432}
433
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -0700434void DeviceRegistrationInfo::StartNotificationChannel() {
Alex Vakulenko6b028ae2015-05-29 09:38:59 -0700435 if (notification_channel_starting_)
436 return;
437
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -0700438 LOG(INFO) << "Starting notification channel";
439
Vitaly Buka0f6b2ec2015-08-20 15:35:19 -0700440 // If no TaskRunner assume we're in test.
Vitaly Bukaff1d1862015-10-07 20:40:36 -0700441 if (!network_) {
442 LOG(INFO) << "No Network, not starting notification channel";
Nathan Bullockbea91132015-02-19 09:13:33 -0500443 return;
444 }
445
Alex Vakulenkod05725f2015-05-27 15:48:19 -0700446 if (primary_notification_channel_) {
Alex Vakulenko26f557b2015-05-26 16:47:40 -0700447 primary_notification_channel_->Stop();
Alex Vakulenkod05725f2015-05-27 15:48:19 -0700448 primary_notification_channel_.reset();
449 current_notification_channel_ = nullptr;
450 }
451
452 // Start with just regular polling at the pre-configured polling interval.
453 // Once the primary notification channel is connected successfully, it will
454 // call back to OnConnected() and at that time we'll switch to use the
455 // primary channel and switch periodic poll into much more infrequent backup
456 // poll mode.
Vitaly Buka41a90d62015-09-29 16:58:39 -0700457 const base::TimeDelta pull_interval =
458 base::TimeDelta::FromSeconds(kPollingPeriodSeconds);
Alex Vakulenkod05725f2015-05-27 15:48:19 -0700459 if (!pull_channel_) {
Alex Vakulenkoa56a7e62015-06-26 12:59:50 -0700460 pull_channel_.reset(new PullChannel{pull_interval, task_runner_});
Alex Vakulenkod05725f2015-05-27 15:48:19 -0700461 pull_channel_->Start(this);
462 } else {
463 pull_channel_->UpdatePullInterval(pull_interval);
464 }
465 current_notification_channel_ = pull_channel_.get();
466
Alex Vakulenko6b028ae2015-05-29 09:38:59 -0700467 notification_channel_starting_ = true;
Vitaly Buka63cc3d22015-06-23 20:11:36 -0700468 primary_notification_channel_.reset(new XmppChannel{
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700469 GetSettings().robot_account, access_token_, task_runner_, network_});
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -0700470 primary_notification_channel_->Start(this);
Nathan Bullockf12f7f02015-02-20 14:46:53 -0500471}
472
Vitaly Bukac3c6dab2015-10-01 19:41:02 -0700473void DeviceRegistrationInfo::AddGcdStateChangedCallback(
474 const Device::GcdStateChangedCallback& callback) {
475 gcd_state_changed_callbacks_.push_back(callback);
476 callback.Run(gcd_state_);
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700477}
478
Anton Muhind8d32162014-10-02 20:37:00 +0400479std::unique_ptr<base::DictionaryValue>
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700480DeviceRegistrationInfo::BuildDeviceResource(ErrorPtr* error) {
Alex Vakulenko9ea5a322015-04-17 15:35:34 -0700481 // Limit only to commands that are visible to the cloud.
Alex Vakulenko2c7740a2015-11-30 08:51:29 -0800482 const base::DictionaryValue& commands =
483 command_manager_->GetCommandDictionary().GetCommandsAsJson();
Anton Muhind8d32162014-10-02 20:37:00 +0400484
Vitaly Bukac4305602015-11-24 23:33:09 -0800485 const base::DictionaryValue& state = state_manager_->GetState();
Anton Muhind8d32162014-10-02 20:37:00 +0400486
487 std::unique_ptr<base::DictionaryValue> resource{new base::DictionaryValue};
Johan Euphrosine312c2f52015-09-29 00:04:29 -0700488 if (!GetSettings().cloud_id.empty())
489 resource->SetString("id", GetSettings().cloud_id);
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700490 resource->SetString("name", GetSettings().name);
491 if (!GetSettings().description.empty())
492 resource->SetString("description", GetSettings().description);
493 if (!GetSettings().location.empty())
494 resource->SetString("location", GetSettings().location);
495 resource->SetString("modelManifestId", GetSettings().model_id);
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -0700496 std::unique_ptr<base::DictionaryValue> channel{new base::DictionaryValue};
Alex Vakulenkod05725f2015-05-27 15:48:19 -0700497 if (current_notification_channel_) {
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -0700498 channel->SetString("supportedType",
Alex Vakulenkod05725f2015-05-27 15:48:19 -0700499 current_notification_channel_->GetName());
500 current_notification_channel_->AddChannelParameters(channel.get());
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -0700501 } else {
Alex Vakulenkod05725f2015-05-27 15:48:19 -0700502 channel->SetString("supportedType", "pull");
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -0700503 }
504 resource->Set("channel", channel.release());
Alex Vakulenko2c7740a2015-11-30 08:51:29 -0800505 resource->Set("commandDefs", commands.DeepCopy());
Vitaly Bukac4305602015-11-24 23:33:09 -0800506 resource->Set("state", state.DeepCopy());
Anton Muhind8d32162014-10-02 20:37:00 +0400507
508 return resource;
509}
510
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700511void DeviceRegistrationInfo::GetDeviceInfo(
Vitaly Buka74763422015-10-11 00:39:52 -0700512 const CloudRequestDoneCallback& callback) {
Vitaly Buka11b2f232015-08-20 13:55:41 -0700513 ErrorPtr error;
Vitaly Buka672634b2015-11-20 09:49:30 -0800514 if (!VerifyRegistrationCredentials(&error))
Vitaly Buka74763422015-10-11 00:39:52 -0700515 return callback.Run({}, std::move(error));
Vitaly Buka74763422015-10-11 00:39:52 -0700516 DoCloudRequest(HttpClient::Method::kGet, GetDeviceURL(), nullptr, callback);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700517}
518
Vitaly Buka74763422015-10-11 00:39:52 -0700519void DeviceRegistrationInfo::RegisterDeviceError(const DoneCallback& callback,
520 ErrorPtr error) {
521 task_runner_->PostDelayedTask(FROM_HERE,
522 base::Bind(callback, base::Passed(&error)), {});
Vitaly Buka4774df22015-10-09 12:36:22 -0700523}
524
Vitaly Buka74763422015-10-11 00:39:52 -0700525void DeviceRegistrationInfo::RegisterDevice(const std::string& ticket_id,
526 const DoneCallback& callback) {
Vitaly Buka12870bd2015-10-08 23:49:39 -0700527 ErrorPtr error;
Anton Muhind8d32162014-10-02 20:37:00 +0400528 std::unique_ptr<base::DictionaryValue> device_draft =
Vitaly Buka12870bd2015-10-08 23:49:39 -0700529 BuildDeviceResource(&error);
Anton Muhind8d32162014-10-02 20:37:00 +0400530 if (!device_draft)
Vitaly Buka74763422015-10-11 00:39:52 -0700531 return RegisterDeviceError(callback, std::move(error));
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700532
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700533 base::DictionaryValue req_json;
Nathan Bullocke4408482015-02-19 11:13:21 -0500534 req_json.SetString("id", ticket_id);
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700535 req_json.SetString("oauthClientId", GetSettings().client_id);
Anton Muhind8d32162014-10-02 20:37:00 +0400536 req_json.Set("deviceDraft", device_draft.release());
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700537
Nathan Bullocke4408482015-02-19 11:13:21 -0500538 auto url = GetServiceURL("registrationTickets/" + ticket_id,
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700539 {{"key", GetSettings().api_key}});
Vitaly Buka6da94252015-08-04 15:45:14 -0700540
Vitaly Buka1a42e142015-10-10 18:15:15 -0700541 RequestSender sender{HttpClient::Method::kPatch, url, http_client_};
Vitaly Buka6da94252015-08-04 15:45:14 -0700542 sender.SetJsonData(req_json);
Vitaly Buka4774df22015-10-09 12:36:22 -0700543 sender.Send(base::Bind(&DeviceRegistrationInfo::RegisterDeviceOnTicketSent,
Vitaly Buka74763422015-10-11 00:39:52 -0700544 weak_factory_.GetWeakPtr(), ticket_id, callback));
Vitaly Buka4774df22015-10-09 12:36:22 -0700545}
Vitaly Buka6da94252015-08-04 15:45:14 -0700546
Vitaly Buka4774df22015-10-09 12:36:22 -0700547void DeviceRegistrationInfo::RegisterDeviceOnTicketSent(
548 const std::string& ticket_id,
Vitaly Buka74763422015-10-11 00:39:52 -0700549 const DoneCallback& callback,
550 std::unique_ptr<provider::HttpClient::Response> response,
551 ErrorPtr error) {
552 if (error)
553 return RegisterDeviceError(callback, std::move(error));
554 auto json_resp = ParseJsonResponse(*response, &error);
Anton Muhin532ff7e2014-09-29 23:21:21 +0400555 if (!json_resp)
Vitaly Buka74763422015-10-11 00:39:52 -0700556 return RegisterDeviceError(callback, std::move(error));
Vitaly Buka4774df22015-10-09 12:36:22 -0700557
Vitaly Buka74763422015-10-11 00:39:52 -0700558 if (!IsSuccessful(*response)) {
Vitaly Buka12870bd2015-10-08 23:49:39 -0700559 ParseGCDError(json_resp.get(), &error);
Vitaly Buka74763422015-10-11 00:39:52 -0700560 return RegisterDeviceError(callback, std::move(error));
David Zeuthen1dbad472015-02-12 15:24:21 -0500561 }
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700562
Vitaly Buka4774df22015-10-09 12:36:22 -0700563 std::string url =
564 GetServiceURL("registrationTickets/" + ticket_id + "/finalize",
565 {{"key", GetSettings().api_key}});
Vitaly Buka1a42e142015-10-10 18:15:15 -0700566 RequestSender{HttpClient::Method::kPost, url, http_client_}.Send(
Vitaly Buka4774df22015-10-09 12:36:22 -0700567 base::Bind(&DeviceRegistrationInfo::RegisterDeviceOnTicketFinalized,
Vitaly Buka74763422015-10-11 00:39:52 -0700568 weak_factory_.GetWeakPtr(), callback));
Vitaly Buka4774df22015-10-09 12:36:22 -0700569}
570
571void DeviceRegistrationInfo::RegisterDeviceOnTicketFinalized(
Vitaly Buka74763422015-10-11 00:39:52 -0700572 const DoneCallback& callback,
573 std::unique_ptr<provider::HttpClient::Response> response,
574 ErrorPtr error) {
575 if (error)
576 return RegisterDeviceError(callback, std::move(error));
577 auto json_resp = ParseJsonResponse(*response, &error);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700578 if (!json_resp)
Vitaly Buka74763422015-10-11 00:39:52 -0700579 return RegisterDeviceError(callback, std::move(error));
580 if (!IsSuccessful(*response)) {
Vitaly Buka12870bd2015-10-08 23:49:39 -0700581 ParseGCDError(json_resp.get(), &error);
Vitaly Buka74763422015-10-11 00:39:52 -0700582 return RegisterDeviceError(callback, std::move(error));
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700583 }
Anton Muhin532ff7e2014-09-29 23:21:21 +0400584
585 std::string auth_code;
Johan Euphrosine312c2f52015-09-29 00:04:29 -0700586 std::string cloud_id;
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700587 std::string robot_account;
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700588 const base::DictionaryValue* device_draft_response = nullptr;
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700589 if (!json_resp->GetString("robotAccountEmail", &robot_account) ||
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700590 !json_resp->GetString("robotAccountAuthorizationCode", &auth_code) ||
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700591 !json_resp->GetDictionary("deviceDraft", &device_draft_response) ||
Johan Euphrosine312c2f52015-09-29 00:04:29 -0700592 !device_draft_response->GetString("id", &cloud_id)) {
Vitaly Buka12870bd2015-10-08 23:49:39 -0700593 Error::AddTo(&error, FROM_HERE, kErrorDomainGCD, "unexpected_response",
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700594 "Device account missing in response");
Vitaly Buka74763422015-10-11 00:39:52 -0700595 return RegisterDeviceError(callback, std::move(error));
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700596 }
597
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700598 UpdateDeviceInfoTimestamp(*device_draft_response);
599
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700600 // Now get access_token and refresh_token
Vitaly Buka1a42e142015-10-10 18:15:15 -0700601 RequestSender sender2{HttpClient::Method::kPost, GetOAuthURL("token"),
602 http_client_};
Vitaly Buka6da94252015-08-04 15:45:14 -0700603 sender2.SetFormData(
Vitaly Bukaa647c852015-07-06 14:51:01 -0700604 {{"code", auth_code},
Vitaly Buka5cf12a32015-09-15 21:25:48 -0700605 {"client_id", GetSettings().client_id},
606 {"client_secret", GetSettings().client_secret},
Vitaly Bukaa647c852015-07-06 14:51:01 -0700607 {"redirect_uri", "oob"},
Vitaly Buka6da94252015-08-04 15:45:14 -0700608 {"grant_type", "authorization_code"}});
Vitaly Buka4774df22015-10-09 12:36:22 -0700609 sender2.Send(base::Bind(&DeviceRegistrationInfo::RegisterDeviceOnAuthCodeSent,
610 weak_factory_.GetWeakPtr(), cloud_id, robot_account,
Vitaly Buka74763422015-10-11 00:39:52 -0700611 callback));
Vitaly Buka4774df22015-10-09 12:36:22 -0700612}
Vitaly Buka6da94252015-08-04 15:45:14 -0700613
Vitaly Buka4774df22015-10-09 12:36:22 -0700614void DeviceRegistrationInfo::RegisterDeviceOnAuthCodeSent(
615 const std::string& cloud_id,
616 const std::string& robot_account,
Vitaly Buka74763422015-10-11 00:39:52 -0700617 const DoneCallback& callback,
618 std::unique_ptr<provider::HttpClient::Response> response,
619 ErrorPtr error) {
620 if (error)
621 return RegisterDeviceError(callback, std::move(error));
622 auto json_resp = ParseOAuthResponse(*response, &error);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700623 int expires_in = 0;
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700624 std::string refresh_token;
625 if (!json_resp || !json_resp->GetString("access_token", &access_token_) ||
626 !json_resp->GetString("refresh_token", &refresh_token) ||
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700627 !json_resp->GetInteger("expires_in", &expires_in) ||
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700628 access_token_.empty() || refresh_token.empty() || expires_in <= 0) {
Vitaly Buka12870bd2015-10-08 23:49:39 -0700629 Error::AddTo(&error, FROM_HERE, kErrorDomainGCD, "unexpected_response",
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700630 "Device access_token missing in response");
Vitaly Buka74763422015-10-11 00:39:52 -0700631 return RegisterDeviceError(callback, std::move(error));
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700632 }
633
Vitaly Bukaa647c852015-07-06 14:51:01 -0700634 access_token_expiration_ =
635 base::Time::Now() + base::TimeDelta::FromSeconds(expires_in);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700636
Vitaly Bukac11a17d2015-08-15 10:36:10 -0700637 Config::Transaction change{config_.get()};
Johan Euphrosine312c2f52015-09-29 00:04:29 -0700638 change.set_cloud_id(cloud_id);
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700639 change.set_robot_account(robot_account);
640 change.set_refresh_token(refresh_token);
641 change.Commit();
642
Vitaly Buka74763422015-10-11 00:39:52 -0700643 task_runner_->PostDelayedTask(FROM_HERE, base::Bind(callback, nullptr), {});
Vitaly Buka12870bd2015-10-08 23:49:39 -0700644
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -0700645 StartNotificationChannel();
Christopher Wileycd419662015-02-06 17:51:43 -0800646
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700647 // We're going to respond with our success immediately and we'll connect to
648 // cloud shortly after.
Vitaly Buka12870bd2015-10-08 23:49:39 -0700649 ScheduleCloudConnection({});
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700650}
651
Anton Muhinac661ab2014-10-03 20:29:48 +0400652void DeviceRegistrationInfo::DoCloudRequest(
Vitaly Buka1a42e142015-10-10 18:15:15 -0700653 HttpClient::Method method,
Anton Muhinac661ab2014-10-03 20:29:48 +0400654 const std::string& url,
655 const base::DictionaryValue* body,
Vitaly Buka74763422015-10-11 00:39:52 -0700656 const CloudRequestDoneCallback& callback) {
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700657 // We make CloudRequestData shared here because we want to make sure
Vitaly Buka74763422015-10-11 00:39:52 -0700658 // there is only one instance of callback and error_calback since
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700659 // those may have move-only types and making a copy of the callback with
660 // move-only types curried-in will invalidate the source callback.
661 auto data = std::make_shared<CloudRequestData>();
662 data->method = method;
663 data->url = url;
664 if (body)
665 base::JSONWriter::Write(*body, &data->body);
Vitaly Buka74763422015-10-11 00:39:52 -0700666 data->callback = callback;
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700667 SendCloudRequest(data);
668}
669
670void DeviceRegistrationInfo::SendCloudRequest(
671 const std::shared_ptr<const CloudRequestData>& data) {
Alex Vakulenko0357c032015-01-06 16:32:31 -0800672 // TODO(antonm): Add reauthorization on access token expiration (do not
Anton Muhinac661ab2014-10-03 20:29:48 +0400673 // forget about 5xx when fetching new access token).
674 // TODO(antonm): Add support for device removal.
675
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700676 ErrorPtr error;
Vitaly Buka672634b2015-11-20 09:49:30 -0800677 if (!VerifyRegistrationCredentials(&error))
Vitaly Buka74763422015-10-11 00:39:52 -0700678 return data->callback.Run({}, std::move(error));
Anton Muhinac661ab2014-10-03 20:29:48 +0400679
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700680 if (cloud_backoff_entry_->ShouldRejectRequest()) {
681 VLOG(1) << "Cloud request delayed for "
682 << cloud_backoff_entry_->GetTimeUntilRelease()
683 << " due to backoff policy";
Vitaly Bukaf7f52d42015-10-10 22:43:55 -0700684 return task_runner_->PostDelayedTask(
Vitaly Bukaa647c852015-07-06 14:51:01 -0700685 FROM_HERE, base::Bind(&DeviceRegistrationInfo::SendCloudRequest,
686 AsWeakPtr(), data),
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700687 cloud_backoff_entry_->GetTimeUntilRelease());
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700688 }
Anton Muhinac661ab2014-10-03 20:29:48 +0400689
Vitaly Buka6da94252015-08-04 15:45:14 -0700690 RequestSender sender{data->method, data->url, http_client_};
Vitaly Buka1da65992015-08-06 01:38:57 -0700691 sender.SetData(data->body, http::kJsonUtf8);
Vitaly Buka815b6032015-08-06 11:06:25 -0700692 sender.SetAccessToken(access_token_);
Vitaly Buka74763422015-10-11 00:39:52 -0700693 sender.Send(base::Bind(&DeviceRegistrationInfo::OnCloudRequestDone,
Vitaly Buka866b60a2015-10-09 14:24:55 -0700694 AsWeakPtr(), data));
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700695}
Anton Muhinac661ab2014-10-03 20:29:48 +0400696
Vitaly Buka74763422015-10-11 00:39:52 -0700697void DeviceRegistrationInfo::OnCloudRequestDone(
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700698 const std::shared_ptr<const CloudRequestData>& data,
Vitaly Buka74763422015-10-11 00:39:52 -0700699 std::unique_ptr<provider::HttpClient::Response> response,
700 ErrorPtr error) {
701 if (error)
702 return RetryCloudRequest(data);
703 int status_code = response->GetStatusCode();
Vitaly Buka1da65992015-08-06 01:38:57 -0700704 if (status_code == http::kDenied) {
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700705 cloud_backoff_entry_->InformOfRequest(true);
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700706 RefreshAccessToken(
707 base::Bind(&DeviceRegistrationInfo::OnAccessTokenRefreshed, AsWeakPtr(),
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700708 data));
709 return;
710 }
Anton Muhinac661ab2014-10-03 20:29:48 +0400711
Vitaly Buka1da65992015-08-06 01:38:57 -0700712 if (status_code >= http::kInternalServerError) {
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700713 // Request was valid, but server failed, retry.
714 // TODO(antonm): Reconsider status codes, maybe only some require
715 // retry.
716 // TODO(antonm): Support Retry-After header.
717 RetryCloudRequest(data);
718 return;
719 }
Anton Muhin633eded2014-10-03 20:40:10 +0400720
Vitaly Buka74763422015-10-11 00:39:52 -0700721 auto json_resp = ParseJsonResponse(*response, &error);
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700722 if (!json_resp) {
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700723 cloud_backoff_entry_->InformOfRequest(true);
Vitaly Buka74763422015-10-11 00:39:52 -0700724 return data->callback.Run({}, std::move(error));
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700725 }
Anton Muhin233d2ee2014-10-22 15:16:24 +0400726
Vitaly Buka74763422015-10-11 00:39:52 -0700727 if (!IsSuccessful(*response)) {
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700728 ParseGCDError(json_resp.get(), &error);
Vitaly Buka1da65992015-08-06 01:38:57 -0700729 if (status_code == http::kForbidden &&
Vitaly Bukab6f015a2015-07-09 14:59:23 -0700730 error->HasError(kErrorDomainGCDServer, "rateLimitExceeded")) {
Alex Vakulenkof61ee012015-06-24 14:21:35 -0700731 // If we exceeded server quota, retry the request later.
Vitaly Bukaf7f52d42015-10-10 22:43:55 -0700732 return RetryCloudRequest(data);
Alex Vakulenkof61ee012015-06-24 14:21:35 -0700733 }
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700734 cloud_backoff_entry_->InformOfRequest(true);
Vitaly Buka74763422015-10-11 00:39:52 -0700735 return data->callback.Run({}, std::move(error));
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700736 }
737
Alex Vakulenkof61ee012015-06-24 14:21:35 -0700738 cloud_backoff_entry_->InformOfRequest(true);
Vitaly Bukac3c6dab2015-10-01 19:41:02 -0700739 SetGcdState(GcdState::kConnected);
Vitaly Buka74763422015-10-11 00:39:52 -0700740 data->callback.Run(*json_resp, nullptr);
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700741}
742
743void DeviceRegistrationInfo::RetryCloudRequest(
744 const std::shared_ptr<const CloudRequestData>& data) {
Alex Vakulenkof61ee012015-06-24 14:21:35 -0700745 // TODO(avakulenko): Tie connecting/connected status to XMPP channel instead.
Vitaly Bukac3c6dab2015-10-01 19:41:02 -0700746 SetGcdState(GcdState::kConnecting);
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700747 cloud_backoff_entry_->InformOfRequest(false);
748 SendCloudRequest(data);
749}
750
751void DeviceRegistrationInfo::OnAccessTokenRefreshed(
Vitaly Buka74763422015-10-11 00:39:52 -0700752 const std::shared_ptr<const CloudRequestData>& data,
753 ErrorPtr error) {
754 if (error) {
755 CheckAccessTokenError(error->Clone());
756 return data->callback.Run({}, std::move(error));
757 }
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700758 SendCloudRequest(data);
759}
760
Vitaly Bukaf7f52d42015-10-10 22:43:55 -0700761void DeviceRegistrationInfo::CheckAccessTokenError(ErrorPtr error) {
Vitaly Buka74763422015-10-11 00:39:52 -0700762 if (error && error->HasError(kErrorDomainOAuth2, "invalid_grant"))
Vitaly Buka672634b2015-11-20 09:49:30 -0800763 RemoveCredentials();
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700764}
765
Vitaly Buka74763422015-10-11 00:39:52 -0700766void DeviceRegistrationInfo::ConnectToCloud(ErrorPtr error) {
767 if (error) {
768 if (error->HasError(kErrorDomainOAuth2, "invalid_grant"))
Vitaly Buka672634b2015-11-20 09:49:30 -0800769 RemoveCredentials();
Vitaly Buka74763422015-10-11 00:39:52 -0700770 return;
771 }
772
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700773 connected_to_cloud_ = false;
774 if (!VerifyRegistrationCredentials(nullptr))
Anton Muhind8d32162014-10-02 20:37:00 +0400775 return;
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700776
777 if (access_token_.empty()) {
778 RefreshAccessToken(
Vitaly Buka74763422015-10-11 00:39:52 -0700779 base::Bind(&DeviceRegistrationInfo::ConnectToCloud, AsWeakPtr()));
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700780 return;
781 }
782
783 // Connecting a device to cloud just means that we:
Christopher Wileyba983c82015-03-05 16:32:23 -0800784 // 1) push an updated device resource
785 // 2) fetch an initial set of outstanding commands
786 // 3) abort any commands that we've previously marked as "in progress"
Alex Vakulenkod05725f2015-05-27 15:48:19 -0700787 // or as being in an error state; publish queued commands
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700788 UpdateDeviceResource(
Vitaly Buka74763422015-10-11 00:39:52 -0700789 base::Bind(&DeviceRegistrationInfo::OnConnectedToCloud, AsWeakPtr()));
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700790}
791
Vitaly Buka74763422015-10-11 00:39:52 -0700792void DeviceRegistrationInfo::OnConnectedToCloud(ErrorPtr error) {
793 if (error)
794 return;
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700795 LOG(INFO) << "Device connected to cloud server";
796 connected_to_cloud_ = true;
797 FetchCommands(base::Bind(&DeviceRegistrationInfo::ProcessInitialCommandList,
Alex Vakulenkoe07c29d2015-10-22 10:31:12 -0700798 AsWeakPtr()), fetch_reason::kDeviceStart);
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700799 // In case there are any pending state updates since we sent off the initial
800 // UpdateDeviceResource() request, update the server with any state changes.
801 PublishStateUpdates();
Anton Muhinc635c592014-10-28 21:48:08 +0400802}
803
Vitaly Bukab624bc42015-09-29 19:13:55 -0700804void DeviceRegistrationInfo::UpdateDeviceInfo(const std::string& name,
Vitaly Bukafa947062015-04-17 00:41:31 -0700805 const std::string& description,
Vitaly Bukab624bc42015-09-29 19:13:55 -0700806 const std::string& location) {
Vitaly Bukac11a17d2015-08-15 10:36:10 -0700807 Config::Transaction change{config_.get()};
Vitaly Buka798a0e72015-06-02 15:37:51 -0700808 change.set_name(name);
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700809 change.set_description(description);
810 change.set_location(location);
Vitaly Bukaff81db62015-05-14 21:25:45 -0700811 change.Commit();
Vitaly Bukafa947062015-04-17 00:41:31 -0700812
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -0700813 if (HaveRegistrationCredentials()) {
Vitaly Buka74763422015-10-11 00:39:52 -0700814 UpdateDeviceResource(base::Bind(&IgnoreCloudError));
Vitaly Bukafa947062015-04-17 00:41:31 -0700815 }
Vitaly Bukafa947062015-04-17 00:41:31 -0700816}
817
Vitaly Bukab624bc42015-09-29 19:13:55 -0700818void DeviceRegistrationInfo::UpdateBaseConfig(AuthScope anonymous_access_role,
819 bool local_discovery_enabled,
820 bool local_pairing_enabled) {
Vitaly Bukac11a17d2015-08-15 10:36:10 -0700821 Config::Transaction change(config_.get());
Vitaly Bukab624bc42015-09-29 19:13:55 -0700822 change.set_local_anonymous_access_role(anonymous_access_role);
Vitaly Buka2f7efdb2015-05-27 16:00:21 -0700823 change.set_local_discovery_enabled(local_discovery_enabled);
824 change.set_local_pairing_enabled(local_pairing_enabled);
Vitaly Buka2f7efdb2015-05-27 16:00:21 -0700825}
826
Vitaly Bukaff81db62015-05-14 21:25:45 -0700827bool DeviceRegistrationInfo::UpdateServiceConfig(
828 const std::string& client_id,
829 const std::string& client_secret,
830 const std::string& api_key,
831 const std::string& oauth_url,
832 const std::string& service_url,
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700833 ErrorPtr* error) {
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -0700834 if (HaveRegistrationCredentials()) {
Vitaly Bukae2810e02015-08-16 23:31:55 -0700835 Error::AddTo(error, FROM_HERE, errors::kErrorDomain, "already_registered",
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700836 "Unable to change config for registered device");
Vitaly Bukaff81db62015-05-14 21:25:45 -0700837 return false;
838 }
Vitaly Bukac11a17d2015-08-15 10:36:10 -0700839 Config::Transaction change{config_.get()};
Vitaly Bukaff81db62015-05-14 21:25:45 -0700840 change.set_client_id(client_id);
841 change.set_client_secret(client_secret);
842 change.set_api_key(api_key);
843 change.set_oauth_url(oauth_url);
844 change.set_service_url(service_url);
845 return true;
846}
847
Anton Muhin59755522014-11-05 21:30:12 +0400848void DeviceRegistrationInfo::UpdateCommand(
849 const std::string& command_id,
Alex Vakulenkob211c102015-04-21 11:43:23 -0700850 const base::DictionaryValue& command_patch,
Vitaly Buka74763422015-10-11 00:39:52 -0700851 const DoneCallback& callback) {
Vitaly Buka1a42e142015-10-10 18:15:15 -0700852 DoCloudRequest(HttpClient::Method::kPatch,
853 GetServiceURL("commands/" + command_id), &command_patch,
Vitaly Buka74763422015-10-11 00:39:52 -0700854 base::Bind(&IgnoreCloudResultWithCallback, callback));
Anton Muhin59755522014-11-05 21:30:12 +0400855}
856
Vitaly Bukaa647c852015-07-06 14:51:01 -0700857void DeviceRegistrationInfo::NotifyCommandAborted(const std::string& command_id,
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700858 ErrorPtr error) {
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700859 base::DictionaryValue command_patch;
860 command_patch.SetString(commands::attributes::kCommand_State,
Vitaly Buka0209da42015-10-08 00:07:18 -0700861 EnumToString(Command::State::kAborted));
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700862 if (error) {
Vitaly Buka70f77d92015-10-07 15:42:40 -0700863 command_patch.Set(commands::attributes::kCommand_Error,
864 ErrorInfoToJson(*error).release());
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700865 }
Vitaly Buka74763422015-10-11 00:39:52 -0700866 UpdateCommand(command_id, command_patch, base::Bind(&IgnoreCloudError));
Alex Vakulenkod1978d32015-04-29 17:33:26 -0700867}
868
Christopher Wileyba983c82015-03-05 16:32:23 -0800869void DeviceRegistrationInfo::UpdateDeviceResource(
Vitaly Buka74763422015-10-11 00:39:52 -0700870 const DoneCallback& callback) {
871 queued_resource_update_callbacks_.emplace_back(callback);
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700872 if (!in_progress_resource_update_callbacks_.empty()) {
873 VLOG(1) << "Another request is already pending.";
Anton Muhind8d32162014-10-02 20:37:00 +0400874 return;
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700875 }
876
877 StartQueuedUpdateDeviceResource();
878}
879
880void DeviceRegistrationInfo::StartQueuedUpdateDeviceResource() {
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700881 if (in_progress_resource_update_callbacks_.empty() &&
882 queued_resource_update_callbacks_.empty())
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700883 return;
884
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700885 if (last_device_resource_updated_timestamp_.empty()) {
886 // We don't know the current time stamp of the device resource from the
887 // server side. We need to provide the time stamp to the server as part of
888 // the request to guard against out-of-order requests overwriting settings
889 // specified by later requests.
890 VLOG(1) << "Getting the last device resource timestamp from server...";
Vitaly Buka74763422015-10-11 00:39:52 -0700891 GetDeviceInfo(base::Bind(&DeviceRegistrationInfo::OnDeviceInfoRetrieved,
892 AsWeakPtr()));
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700893 return;
894 }
895
896 in_progress_resource_update_callbacks_.insert(
897 in_progress_resource_update_callbacks_.end(),
898 queued_resource_update_callbacks_.begin(),
899 queued_resource_update_callbacks_.end());
900 queued_resource_update_callbacks_.clear();
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700901
902 VLOG(1) << "Updating GCD server with CDD...";
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700903 ErrorPtr error;
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700904 std::unique_ptr<base::DictionaryValue> device_resource =
905 BuildDeviceResource(&error);
906 if (!device_resource) {
Vitaly Bukaf7f52d42015-10-10 22:43:55 -0700907 return OnUpdateDeviceResourceError(std::move(error));
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700908 }
Anton Muhind8d32162014-10-02 20:37:00 +0400909
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700910 std::string url = GetDeviceURL(
911 {}, {{"lastUpdateTimeMs", last_device_resource_updated_timestamp_}});
912
Vitaly Buka74763422015-10-11 00:39:52 -0700913 DoCloudRequest(HttpClient::Method::kPut, url, device_resource.get(),
914 base::Bind(&DeviceRegistrationInfo::OnUpdateDeviceResourceDone,
915 AsWeakPtr()));
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700916}
917
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700918void DeviceRegistrationInfo::OnDeviceInfoRetrieved(
Vitaly Buka74763422015-10-11 00:39:52 -0700919 const base::DictionaryValue& device_info,
920 ErrorPtr error) {
921 if (error)
922 return OnUpdateDeviceResourceError(std::move(error));
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700923 if (UpdateDeviceInfoTimestamp(device_info))
924 StartQueuedUpdateDeviceResource();
925}
926
927bool DeviceRegistrationInfo::UpdateDeviceInfoTimestamp(
928 const base::DictionaryValue& device_info) {
929 // For newly created devices, "lastUpdateTimeMs" may not be present, but
930 // "creationTimeMs" should be there at least.
931 if (!device_info.GetString("lastUpdateTimeMs",
932 &last_device_resource_updated_timestamp_) &&
933 !device_info.GetString("creationTimeMs",
934 &last_device_resource_updated_timestamp_)) {
935 LOG(WARNING) << "Device resource timestamp is missing";
936 return false;
937 }
938 return true;
939}
940
Vitaly Buka74763422015-10-11 00:39:52 -0700941void DeviceRegistrationInfo::OnUpdateDeviceResourceDone(
942 const base::DictionaryValue& device_info,
943 ErrorPtr error) {
944 if (error)
945 return OnUpdateDeviceResourceError(std::move(error));
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700946 UpdateDeviceInfoTimestamp(device_info);
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700947 // Make a copy of the callback list so that if the callback triggers another
948 // call to UpdateDeviceResource(), we do not modify the list we are iterating
949 // over.
950 auto callback_list = std::move(in_progress_resource_update_callbacks_);
Vitaly Buka74763422015-10-11 00:39:52 -0700951 for (const auto& callback : callback_list)
952 callback.Run(nullptr);
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700953 StartQueuedUpdateDeviceResource();
954}
955
Vitaly Bukaf7f52d42015-10-10 22:43:55 -0700956void DeviceRegistrationInfo::OnUpdateDeviceResourceError(ErrorPtr error) {
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700957 if (error->HasError(kErrorDomainGCDServer, "invalid_last_update_time_ms")) {
958 // If the server rejected our previous request, retrieve the latest
959 // timestamp from the server and retry.
960 VLOG(1) << "Getting the last device resource timestamp from server...";
Vitaly Buka74763422015-10-11 00:39:52 -0700961 GetDeviceInfo(base::Bind(&DeviceRegistrationInfo::OnDeviceInfoRetrieved,
962 AsWeakPtr()));
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700963 return;
964 }
965
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700966 // Make a copy of the callback list so that if the callback triggers another
967 // call to UpdateDeviceResource(), we do not modify the list we are iterating
968 // over.
969 auto callback_list = std::move(in_progress_resource_update_callbacks_);
Vitaly Buka74763422015-10-11 00:39:52 -0700970 for (const auto& callback : callback_list)
971 callback.Run(error->Clone());
Alex Vakulenkofb331ac2015-07-22 15:10:11 -0700972
Alex Vakulenkof3a95bf2015-07-01 07:52:13 -0700973 StartQueuedUpdateDeviceResource();
Anton Muhinc635c592014-10-28 21:48:08 +0400974}
Anton Muhina34f0d92014-10-03 21:09:40 +0400975
Vitaly Buka74763422015-10-11 00:39:52 -0700976void DeviceRegistrationInfo::OnFetchCommandsDone(
977 const base::Callback<void(const base::ListValue&, ErrorPtr)>& callback,
978 const base::DictionaryValue& json,
979 ErrorPtr error) {
Alex Vakulenko64bc9ea2015-09-14 09:24:58 -0700980 OnFetchCommandsReturned();
Vitaly Buka74763422015-10-11 00:39:52 -0700981 if (error)
982 return callback.Run({}, std::move(error));
Christopher Wileyba983c82015-03-05 16:32:23 -0800983 const base::ListValue* commands{nullptr};
Vitaly Buka74763422015-10-11 00:39:52 -0700984 if (!json.GetList("commands", &commands))
Alex Vakulenkoed77a572015-07-15 10:55:43 -0700985 VLOG(2) << "No commands in the response.";
Christopher Wileyba983c82015-03-05 16:32:23 -0800986 const base::ListValue empty;
Vitaly Buka74763422015-10-11 00:39:52 -0700987 callback.Run(commands ? *commands : empty, nullptr);
Alex Vakulenko64bc9ea2015-09-14 09:24:58 -0700988}
989
990void DeviceRegistrationInfo::OnFetchCommandsReturned() {
991 fetch_commands_request_sent_ = false;
992 // If we have additional requests queued, send them out now.
993 if (fetch_commands_request_queued_)
Alex Vakulenkoe07c29d2015-10-22 10:31:12 -0700994 FetchAndPublishCommands(queued_fetch_reason_);
Alex Vakulenko64bc9ea2015-09-14 09:24:58 -0700995}
Christopher Wileyba983c82015-03-05 16:32:23 -0800996
Anton Muhinc635c592014-10-28 21:48:08 +0400997void DeviceRegistrationInfo::FetchCommands(
Alex Vakulenkoe07c29d2015-10-22 10:31:12 -0700998 const base::Callback<void(const base::ListValue&, ErrorPtr)>& callback,
999 const std::string& reason) {
Alex Vakulenko64bc9ea2015-09-14 09:24:58 -07001000 fetch_commands_request_sent_ = true;
1001 fetch_commands_request_queued_ = false;
Anton Muhinc635c592014-10-28 21:48:08 +04001002 DoCloudRequest(
Vitaly Buka1a42e142015-10-10 18:15:15 -07001003 HttpClient::Method::kGet,
Alex Vakulenkoe07c29d2015-10-22 10:31:12 -07001004 GetServiceURL("commands/queue", {{"deviceId", GetSettings().cloud_id},
1005 {"reason", reason}}),
Vitaly Buka74763422015-10-11 00:39:52 -07001006 nullptr, base::Bind(&DeviceRegistrationInfo::OnFetchCommandsDone,
1007 AsWeakPtr(), callback));
Anton Muhinc635c592014-10-28 21:48:08 +04001008}
Anton Muhina34f0d92014-10-03 21:09:40 +04001009
Alex Vakulenkoe07c29d2015-10-22 10:31:12 -07001010void DeviceRegistrationInfo::FetchAndPublishCommands(
1011 const std::string& reason) {
Alex Vakulenko64bc9ea2015-09-14 09:24:58 -07001012 if (fetch_commands_request_sent_) {
1013 fetch_commands_request_queued_ = true;
Alex Vakulenkoe07c29d2015-10-22 10:31:12 -07001014 queued_fetch_reason_ = reason;
Alex Vakulenko64bc9ea2015-09-14 09:24:58 -07001015 return;
1016 }
1017
Alex Vakulenko1038ec12015-07-15 14:36:13 -07001018 FetchCommands(base::Bind(&DeviceRegistrationInfo::PublishCommands,
Alex Vakulenkoe07c29d2015-10-22 10:31:12 -07001019 weak_factory_.GetWeakPtr()),
1020 reason);
Alex Vakulenko1038ec12015-07-15 14:36:13 -07001021}
1022
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001023void DeviceRegistrationInfo::ProcessInitialCommandList(
Vitaly Buka74763422015-10-11 00:39:52 -07001024 const base::ListValue& commands,
1025 ErrorPtr error) {
1026 if (error)
1027 return;
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001028 for (const base::Value* command : commands) {
1029 const base::DictionaryValue* command_dict{nullptr};
1030 if (!command->GetAsDictionary(&command_dict)) {
1031 LOG(WARNING) << "Not a command dictionary: " << *command;
Anton Muhinc635c592014-10-28 21:48:08 +04001032 continue;
Anton Muhina34f0d92014-10-03 21:09:40 +04001033 }
Anton Muhinc635c592014-10-28 21:48:08 +04001034 std::string command_state;
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001035 if (!command_dict->GetString("state", &command_state)) {
1036 LOG(WARNING) << "Command with no state at " << *command;
Anton Muhinc635c592014-10-28 21:48:08 +04001037 continue;
1038 }
Vitaly Bukaa647c852015-07-06 14:51:01 -07001039 if (command_state == "error" && command_state == "inProgress" &&
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001040 command_state == "paused") {
1041 // It's a limbo command, abort it.
1042 std::string command_id;
1043 if (!command_dict->GetString("id", &command_id)) {
1044 LOG(WARNING) << "Command with no ID at " << *command;
1045 continue;
1046 }
Anton Muhin6d2569e2014-10-30 12:32:27 +04001047
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001048 std::unique_ptr<base::DictionaryValue> cmd_copy{command_dict->DeepCopy()};
1049 cmd_copy->SetString("state", "aborted");
1050 // TODO(wiley) We could consider handling this error case more gracefully.
Vitaly Buka1a42e142015-10-10 18:15:15 -07001051 DoCloudRequest(HttpClient::Method::kPut,
1052 GetServiceURL("commands/" + command_id), cmd_copy.get(),
Vitaly Buka74763422015-10-11 00:39:52 -07001053 base::Bind(&IgnoreCloudResult));
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001054 } else {
1055 // Normal command, publish it to local clients.
1056 PublishCommand(*command_dict);
1057 }
Anton Muhinc635c592014-10-28 21:48:08 +04001058 }
Anton Muhind07e2062014-10-27 10:53:29 +04001059}
1060
Vitaly Buka74763422015-10-11 00:39:52 -07001061void DeviceRegistrationInfo::PublishCommands(const base::ListValue& commands,
1062 ErrorPtr error) {
1063 if (error)
1064 return;
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001065 for (const base::Value* command : commands) {
1066 const base::DictionaryValue* command_dict{nullptr};
1067 if (!command->GetAsDictionary(&command_dict)) {
1068 LOG(WARNING) << "Not a command dictionary: " << *command;
Anton Muhind07e2062014-10-27 10:53:29 +04001069 continue;
1070 }
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001071 PublishCommand(*command_dict);
1072 }
1073}
Anton Muhind07e2062014-10-27 10:53:29 +04001074
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001075void DeviceRegistrationInfo::PublishCommand(
1076 const base::DictionaryValue& command) {
1077 std::string command_id;
Vitaly Buka0801a1f2015-08-14 10:03:46 -07001078 ErrorPtr error;
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001079 auto command_instance = CommandInstance::FromJson(
Vitaly Buka0209da42015-10-08 00:07:18 -07001080 &command, Command::Origin::kCloud,
1081 command_manager_->GetCommandDictionary(), &command_id, &error);
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001082 if (!command_instance) {
1083 LOG(WARNING) << "Failed to parse a command instance: " << command;
1084 if (!command_id.empty())
1085 NotifyCommandAborted(command_id, std::move(error));
1086 return;
1087 }
Anton Muhind07e2062014-10-27 10:53:29 +04001088
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001089 // TODO(antonm): Properly process cancellation of commands.
1090 if (!command_manager_->FindCommand(command_instance->GetID())) {
1091 LOG(INFO) << "New command '" << command_instance->GetName()
1092 << "' arrived, ID: " << command_instance->GetID();
Vitaly Buka0f80f7c2015-08-13 00:57:25 -07001093 std::unique_ptr<BackoffEntry> backoff_entry{
1094 new BackoffEntry{cloud_backoff_policy_.get()}};
Vitaly Buka157b16a2015-07-31 16:20:48 -07001095 std::unique_ptr<CloudCommandProxy> cloud_proxy{new CloudCommandProxy{
Vitaly Buka0d377a42015-07-21 10:26:08 -07001096 command_instance.get(), this, state_manager_->GetStateChangeQueue(),
1097 std::move(backoff_entry), task_runner_}};
Vitaly Buka6da94252015-08-04 15:45:14 -07001098 // CloudCommandProxy::CloudCommandProxy() subscribe itself to Command
1099 // notifications. When Command is being destroyed it sends
Vitaly Buka157b16a2015-07-31 16:20:48 -07001100 // ::OnCommandDestroyed() and CloudCommandProxy deletes itself.
1101 cloud_proxy.release();
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001102 command_manager_->AddCommand(std::move(command_instance));
Anton Muhind07e2062014-10-27 10:53:29 +04001103 }
Anton Muhind8d32162014-10-02 20:37:00 +04001104}
1105
Anton Muhinb8315622014-11-20 03:17:05 +04001106void DeviceRegistrationInfo::PublishStateUpdates() {
Alex Vakulenko93ba0bd2015-06-19 14:06:46 -07001107 // If we have pending state update requests, don't send any more for now.
1108 if (device_state_update_pending_)
1109 return;
1110
Alex Vakulenkobe4254b2015-06-26 11:34:03 -07001111 StateChangeQueueInterface::UpdateID update_id = 0;
1112 std::vector<StateChange> state_changes;
1113 std::tie(update_id, state_changes) =
Vitaly Bukaa647c852015-07-06 14:51:01 -07001114 state_manager_->GetAndClearRecordedStateChanges();
Anton Muhinb8315622014-11-20 03:17:05 +04001115 if (state_changes.empty())
1116 return;
1117
1118 std::unique_ptr<base::ListValue> patches{new base::ListValue};
Alex Vakulenko7d669212015-11-23 16:05:24 -08001119 for (auto& state_change : state_changes) {
Anton Muhinb8315622014-11-20 03:17:05 +04001120 std::unique_ptr<base::DictionaryValue> patch{new base::DictionaryValue};
Anton Muhin76933fd2014-11-21 21:25:18 +04001121 patch->SetString("timeMs",
1122 std::to_string(state_change.timestamp.ToJavaTime()));
Alex Vakulenko7d669212015-11-23 16:05:24 -08001123 patch->Set("patch", state_change.changed_properties.release());
Anton Muhinb8315622014-11-20 03:17:05 +04001124 patches->Append(patch.release());
1125 }
1126
1127 base::DictionaryValue body;
Anton Muhin76933fd2014-11-21 21:25:18 +04001128 body.SetString("requestTimeMs",
1129 std::to_string(base::Time::Now().ToJavaTime()));
Anton Muhinb8315622014-11-20 03:17:05 +04001130 body.Set("patches", patches.release());
1131
Alex Vakulenko93ba0bd2015-06-19 14:06:46 -07001132 device_state_update_pending_ = true;
Vitaly Buka74763422015-10-11 00:39:52 -07001133 DoCloudRequest(HttpClient::Method::kPost, GetDeviceURL("patchState"), &body,
1134 base::Bind(&DeviceRegistrationInfo::OnPublishStateDone,
1135 AsWeakPtr(), update_id));
Alex Vakulenko93ba0bd2015-06-19 14:06:46 -07001136}
1137
Vitaly Buka74763422015-10-11 00:39:52 -07001138void DeviceRegistrationInfo::OnPublishStateDone(
Alex Vakulenkobe4254b2015-06-26 11:34:03 -07001139 StateChangeQueueInterface::UpdateID update_id,
Vitaly Buka74763422015-10-11 00:39:52 -07001140 const base::DictionaryValue& reply,
1141 ErrorPtr error) {
Alex Vakulenko93ba0bd2015-06-19 14:06:46 -07001142 device_state_update_pending_ = false;
Vitaly Buka74763422015-10-11 00:39:52 -07001143 if (error) {
1144 LOG(ERROR) << "Permanent failure while trying to update device state";
1145 return;
1146 }
Alex Vakulenkobe4254b2015-06-26 11:34:03 -07001147 state_manager_->NotifyStateUpdatedOnServer(update_id);
Alex Vakulenko93ba0bd2015-06-19 14:06:46 -07001148 // See if there were more pending state updates since the previous request
1149 // had been sent out.
1150 PublishStateUpdates();
1151}
1152
Vitaly Bukac3c6dab2015-10-01 19:41:02 -07001153void DeviceRegistrationInfo::SetGcdState(GcdState new_state) {
1154 VLOG_IF(1, new_state != gcd_state_) << "Changing registration status to "
1155 << EnumToString(new_state);
1156 gcd_state_ = new_state;
1157 for (const auto& cb : gcd_state_changed_callbacks_)
1158 cb.Run(gcd_state_);
Christopher Wileyc900e482015-02-15 15:42:04 -08001159}
1160
Alex Vakulenko9ea5a322015-04-17 15:35:34 -07001161void DeviceRegistrationInfo::OnCommandDefsChanged() {
1162 VLOG(1) << "CommandDefinitionChanged notification received";
Alex Vakulenkofb331ac2015-07-22 15:10:11 -07001163 if (!HaveRegistrationCredentials() || !connected_to_cloud_)
Alex Vakulenko9ea5a322015-04-17 15:35:34 -07001164 return;
1165
Vitaly Buka74763422015-10-11 00:39:52 -07001166 UpdateDeviceResource(base::Bind(&IgnoreCloudError));
Alex Vakulenko9ea5a322015-04-17 15:35:34 -07001167}
1168
Vitaly Bukac903d282015-05-26 17:03:08 -07001169void DeviceRegistrationInfo::OnStateChanged() {
1170 VLOG(1) << "StateChanged notification received";
Alex Vakulenkofb331ac2015-07-22 15:10:11 -07001171 if (!HaveRegistrationCredentials() || !connected_to_cloud_)
Vitaly Bukac903d282015-05-26 17:03:08 -07001172 return;
1173
1174 // TODO(vitalybuka): Integrate BackoffEntry.
1175 PublishStateUpdates();
1176}
1177
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -07001178void DeviceRegistrationInfo::OnConnected(const std::string& channel_name) {
1179 LOG(INFO) << "Notification channel successfully established over "
1180 << channel_name;
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001181 CHECK_EQ(primary_notification_channel_->GetName(), channel_name);
Alex Vakulenko6b028ae2015-05-29 09:38:59 -07001182 notification_channel_starting_ = false;
Vitaly Buka41a90d62015-09-29 16:58:39 -07001183 pull_channel_->UpdatePullInterval(
Alex Vakulenkoc1fc90c2015-10-22 08:00:43 -07001184 base::TimeDelta::FromMinutes(kBackupPollingPeriodMinutes));
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001185 current_notification_channel_ = primary_notification_channel_.get();
Alex Vakulenkofb331ac2015-07-22 15:10:11 -07001186
Alex Vakulenko8b096cc2015-08-03 10:37:55 -07001187 // If we have not successfully connected to the cloud server and we have not
1188 // initiated the first device resource update, there is nothing we need to
1189 // do now to update the server of the notification channel change.
1190 if (!connected_to_cloud_ && in_progress_resource_update_callbacks_.empty())
Alex Vakulenkofb331ac2015-07-22 15:10:11 -07001191 return;
1192
Alex Vakulenko1038ec12015-07-15 14:36:13 -07001193 // Once we update the device resource with the new notification channel,
1194 // do the last poll for commands from the server, to make sure we have the
1195 // latest command baseline and no other commands have been queued between
1196 // the moment of the last poll and the time we successfully told the server
1197 // to send new commands over the new notification channel.
1198 UpdateDeviceResource(
Vitaly Buka74763422015-10-11 00:39:52 -07001199 base::Bind(&IgnoreCloudErrorWithCallback,
1200 base::Bind(&DeviceRegistrationInfo::FetchAndPublishCommands,
Alex Vakulenkoe07c29d2015-10-22 10:31:12 -07001201 AsWeakPtr(), fetch_reason::kRegularPull)));
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -07001202}
1203
1204void DeviceRegistrationInfo::OnDisconnected() {
1205 LOG(INFO) << "Notification channel disconnected";
Alex Vakulenkofb331ac2015-07-22 15:10:11 -07001206 if (!HaveRegistrationCredentials() || !connected_to_cloud_)
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -07001207 return;
1208
Vitaly Buka41a90d62015-09-29 16:58:39 -07001209 pull_channel_->UpdatePullInterval(
1210 base::TimeDelta::FromSeconds(kPollingPeriodSeconds));
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001211 current_notification_channel_ = pull_channel_.get();
Vitaly Buka74763422015-10-11 00:39:52 -07001212 UpdateDeviceResource(base::Bind(&IgnoreCloudError));
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -07001213}
1214
1215void DeviceRegistrationInfo::OnPermanentFailure() {
1216 LOG(ERROR) << "Failed to establish notification channel.";
Alex Vakulenko6b028ae2015-05-29 09:38:59 -07001217 notification_channel_starting_ = false;
Vitaly Buka4ebd3292015-09-23 18:04:17 -07001218 RefreshAccessToken(
Vitaly Buka4ebd3292015-09-23 18:04:17 -07001219 base::Bind(&DeviceRegistrationInfo::CheckAccessTokenError, AsWeakPtr()));
Alex Vakulenkoeedf3be2015-05-13 17:52:02 -07001220}
1221
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001222void DeviceRegistrationInfo::OnCommandCreated(
Alex Vakulenkoe07c29d2015-10-22 10:31:12 -07001223 const base::DictionaryValue& command,
1224 const std::string& channel_name) {
Alex Vakulenkofb331ac2015-07-22 15:10:11 -07001225 if (!connected_to_cloud_)
1226 return;
1227
Vitaly Buka7a350052015-10-10 23:58:20 -07001228 VLOG(1) << "Command notification received: " << command;
1229
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001230 if (!command.empty()) {
1231 // GCD spec indicates that the command parameter in notification object
1232 // "may be empty if command size is too big".
1233 PublishCommand(command);
1234 return;
1235 }
Alex Vakulenkoe07c29d2015-10-22 10:31:12 -07001236
1237 // If this request comes from a Pull channel while the primary notification
1238 // channel (XMPP) is active, we are doing a backup poll, so mark the request
1239 // appropriately.
1240 bool just_in_case =
1241 (channel_name == kPullChannelName) &&
1242 (current_notification_channel_ == primary_notification_channel_.get());
1243
1244 std::string reason =
1245 just_in_case ? fetch_reason::kJustInCase : fetch_reason::kNewCommand;
1246
Alex Vakulenkod05725f2015-05-27 15:48:19 -07001247 // If the command was too big to be delivered over a notification channel,
1248 // or OnCommandCreated() was initiated from the Pull notification,
1249 // perform a manual command fetch from the server here.
Alex Vakulenkoe07c29d2015-10-22 10:31:12 -07001250 FetchAndPublishCommands(reason);
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001251}
1252
Johan Euphrosine312c2f52015-09-29 00:04:29 -07001253void DeviceRegistrationInfo::OnDeviceDeleted(const std::string& cloud_id) {
1254 if (cloud_id != GetSettings().cloud_id) {
1255 LOG(WARNING) << "Unexpected device deletion notification for cloud ID '"
1256 << cloud_id << "'";
Alex Vakulenko6b40d8f2015-06-24 11:44:22 -07001257 return;
1258 }
Vitaly Buka672634b2015-11-20 09:49:30 -08001259 RemoveCredentials();
Alex Vakulenko6b40d8f2015-06-24 11:44:22 -07001260}
1261
Vitaly Buka672634b2015-11-20 09:49:30 -08001262void DeviceRegistrationInfo::RemoveCredentials() {
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -07001263 if (!HaveRegistrationCredentials())
1264 return;
1265
Alex Vakulenkofb331ac2015-07-22 15:10:11 -07001266 connected_to_cloud_ = false;
1267
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -07001268 LOG(INFO) << "Device is unregistered from the cloud. Deleting credentials";
Vitaly Bukac11a17d2015-08-15 10:36:10 -07001269 Config::Transaction change{config_.get()};
Vitaly Buka672634b2015-11-20 09:49:30 -08001270 // Keep cloud_id to switch to detect kInvalidCredentials after restart.
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -07001271 change.set_robot_account("");
1272 change.set_refresh_token("");
1273 change.Commit();
1274
1275 current_notification_channel_ = nullptr;
1276 if (primary_notification_channel_) {
1277 primary_notification_channel_->Stop();
1278 primary_notification_channel_.reset();
1279 }
1280 if (pull_channel_) {
1281 pull_channel_->Stop();
1282 pull_channel_.reset();
1283 }
1284 notification_channel_starting_ = false;
Vitaly Bukac3c6dab2015-10-01 19:41:02 -07001285 SetGcdState(GcdState::kInvalidCredentials);
Alex Vakulenko3fa42ae2015-06-23 15:12:22 -07001286}
Alex Vakulenko6e3c30e2015-05-21 17:39:25 -07001287
Vitaly Bukab6f015a2015-07-09 14:59:23 -07001288} // namespace weave