blob: a87436d98ae16c26b4805799aee445cbb2f56c33 [file] [log] [blame]
Alex Vakulenko3cb466c2014-04-15 11:36:32 -07001// Copyright 2014 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "buffet/device_registration_info.h"
6
Christopher Wiley006e94e2014-05-02 13:44:48 -07007#include <memory>
Alex Vakulenkob3aac252014-05-07 17:35:24 -07008#include <utility>
9#include <vector>
Christopher Wiley006e94e2014-05-02 13:44:48 -070010
Christopher Wileycd419662015-02-06 17:51:43 -080011#include <base/bind.h>
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070012#include <base/json/json_writer.h>
Anton Muhinac661ab2014-10-03 20:29:48 +040013#include <base/message_loop/message_loop.h>
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070014#include <base/values.h>
Anton Muhinac661ab2014-10-03 20:29:48 +040015#include <chromeos/bind_lambda.h>
Alex Vakulenkoe4879a22014-08-20 15:47:36 -070016#include <chromeos/data_encoding.h>
Anton Muhin233d2ee2014-10-22 15:16:24 +040017#include <chromeos/errors/error_codes.h>
Alex Vakulenkoa8b95bc2014-08-27 11:00:57 -070018#include <chromeos/http/http_utils.h>
Anton Muhin332df192014-11-22 05:59:14 +040019#include <chromeos/key_value_store.h>
Alex Vakulenko3aeea1c2014-08-20 16:33:12 -070020#include <chromeos/mime_utils.h>
Alex Vakulenkoa8b95bc2014-08-27 11:00:57 -070021#include <chromeos/strings/string_utils.h>
Alex Vakulenkobd5b5442014-08-20 16:16:34 -070022#include <chromeos/url_utils.h>
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070023
Anton Muhin59755522014-11-05 21:30:12 +040024#include "buffet/commands/cloud_command_proxy.h"
Alex Vakulenko45109442014-07-29 11:07:10 -070025#include "buffet/commands/command_definition.h"
26#include "buffet/commands/command_manager.h"
Alex Vakulenko8e34d392014-04-29 11:02:56 -070027#include "buffet/device_registration_storage_keys.h"
Alex Vakulenko07216fe2014-09-19 15:31:09 -070028#include "buffet/states/state_manager.h"
Alex Vakulenkob04936f2014-09-19 14:53:58 -070029#include "buffet/utils.h"
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070030
Alex Vakulenkob3aac252014-05-07 17:35:24 -070031const char buffet::kErrorDomainOAuth2[] = "oauth2";
32const char buffet::kErrorDomainGCD[] = "gcd";
33const char buffet::kErrorDomainGCDServer[] = "gcd_server";
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070034
Alex Vakulenko8e34d392014-04-29 11:02:56 -070035namespace buffet {
36namespace storage_keys {
37
Christopher Wileybb5b8482015-02-12 13:42:16 -080038// Persistent keys
Christopher Wileybb5b8482015-02-12 13:42:16 -080039const char kRefreshToken[] = "refresh_token";
40const char kDeviceId[] = "device_id";
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070041const char kRobotAccount[] = "robot_account";
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070042const char kDisplayName[] = "display_name";
Vitaly Bukad3eb19c2014-11-21 13:39:43 -080043const char kDescription[] = "description";
44const char kLocation[] = "location";
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070045
Alex Vakulenko8e34d392014-04-29 11:02:56 -070046} // namespace storage_keys
47} // namespace buffet
48
49namespace {
50
Christopher Wileyba983c82015-03-05 16:32:23 -080051const int kMaxStartDeviceRetryDelayMinutes{1};
52const int64_t kMinStartDeviceRetryDelaySeconds{5};
53
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070054std::pair<std::string, std::string> BuildAuthHeader(
55 const std::string& access_token_type,
56 const std::string& access_token) {
Alex Vakulenkoaf23b322014-05-08 16:25:45 -070057 std::string authorization =
Vitaly Bukadb770e72015-03-10 19:33:33 -070058 chromeos::string_utils::Join(" ", access_token_type, access_token);
Alex Vakulenkocca20932014-08-20 17:35:12 -070059 return {chromeos::http::request_header::kAuthorization, authorization};
Alex Vakulenko3cb466c2014-04-15 11:36:32 -070060}
61
Alex Vakulenko5f472062014-08-14 17:54:04 -070062inline void SetUnexpectedError(chromeos::ErrorPtr* error) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -080063 chromeos::Error::AddTo(error, FROM_HERE, buffet::kErrorDomainGCD,
64 "unexpected_response", "Unexpected GCD error");
Alex Vakulenkob3aac252014-05-07 17:35:24 -070065}
66
Alex Vakulenko5f472062014-08-14 17:54:04 -070067void ParseGCDError(const base::DictionaryValue* json,
68 chromeos::ErrorPtr* error) {
Alex Vakulenkob3aac252014-05-07 17:35:24 -070069 const base::Value* list_value = nullptr;
70 const base::ListValue* error_list = nullptr;
71 if (!json->Get("error.errors", &list_value) ||
72 !list_value->GetAsList(&error_list)) {
73 SetUnexpectedError(error);
74 return;
75 }
76
77 for (size_t i = 0; i < error_list->GetSize(); i++) {
78 const base::Value* error_value = nullptr;
79 const base::DictionaryValue* error_object = nullptr;
80 if (!error_list->Get(i, &error_value) ||
81 !error_value->GetAsDictionary(&error_object)) {
82 SetUnexpectedError(error);
83 continue;
84 }
85 std::string error_code, error_message;
86 if (error_object->GetString("reason", &error_code) &&
87 error_object->GetString("message", &error_message)) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -080088 chromeos::Error::AddTo(error, FROM_HERE, buffet::kErrorDomainGCDServer,
Alex Vakulenko5f472062014-08-14 17:54:04 -070089 error_code, error_message);
Alex Vakulenkob3aac252014-05-07 17:35:24 -070090 } else {
91 SetUnexpectedError(error);
92 }
93 }
94}
95
Alex Vakulenkobda220a2014-04-18 15:25:44 -070096std::string BuildURL(const std::string& url,
97 const std::vector<std::string>& subpaths,
Alex Vakulenkoe4879a22014-08-20 15:47:36 -070098 const chromeos::data_encoding::WebParamList& params) {
Alex Vakulenkobd5b5442014-08-20 16:16:34 -070099 std::string result = chromeos::url::CombineMultiple(url, subpaths);
100 return chromeos::url::AppendQueryParams(result, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700101}
102
Alex Vakulenko0357c032015-01-06 16:32:31 -0800103void IgnoreCloudError(const chromeos::Error*) {
Anton Muhin5191e812014-10-30 17:49:48 +0400104}
105
Christopher Wileyba983c82015-03-05 16:32:23 -0800106void IgnoreCloudErrorWithCallback(const base::Closure& cb,
107 const chromeos::Error*) {
108 cb.Run();
109}
110
Anton Muhin5191e812014-10-30 17:49:48 +0400111void IgnoreCloudResult(const base::DictionaryValue&) {
112}
Anton Muhin6d2569e2014-10-30 12:32:27 +0400113
Christopher Wileyba983c82015-03-05 16:32:23 -0800114void IgnoreCloudResultWithCallback(const base::Closure& cb,
115 const base::DictionaryValue&) {
116 cb.Run();
117}
118
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700119} // anonymous namespace
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700120
121namespace buffet {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700122
Alex Vakulenko1f30a622014-07-23 11:13:15 -0700123DeviceRegistrationInfo::DeviceRegistrationInfo(
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700124 const std::shared_ptr<CommandManager>& command_manager,
Anton Muhin332df192014-11-22 05:59:14 +0400125 const std::shared_ptr<StateManager>& state_manager,
Christopher Wiley583d64b2015-03-24 14:30:17 -0700126 std::unique_ptr<const BuffetConfig> config,
Alex Vakulenkocca20932014-08-20 17:35:12 -0700127 const std::shared_ptr<chromeos::http::Transport>& transport,
Christopher Wileyc900e482015-02-15 15:42:04 -0800128 const std::shared_ptr<StorageInterface>& state_store,
Christopher Wileyd732bd02015-04-07 11:11:18 -0700129 bool xmpp_enabled,
Vitaly Buka7ad8ffb2015-03-20 09:46:57 -0700130 const base::Closure& on_status_changed)
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700131 : transport_{transport},
Christopher Wileye0fdeee2015-02-07 18:29:32 -0800132 storage_{state_store},
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700133 command_manager_{command_manager},
Anton Muhin332df192014-11-22 05:59:14 +0400134 state_manager_{state_manager},
Christopher Wiley583d64b2015-03-24 14:30:17 -0700135 config_{std::move(config)},
Christopher Wileyd732bd02015-04-07 11:11:18 -0700136 xmpp_enabled_{xmpp_enabled},
Vitaly Buka7ad8ffb2015-03-20 09:46:57 -0700137 on_status_changed_{on_status_changed} {
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700138}
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700139
Anton Muhin332df192014-11-22 05:59:14 +0400140DeviceRegistrationInfo::~DeviceRegistrationInfo() = default;
141
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700142std::pair<std::string, std::string>
143 DeviceRegistrationInfo::GetAuthorizationHeader() const {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700144 return BuildAuthHeader("Bearer", access_token_);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700145}
146
147std::string DeviceRegistrationInfo::GetServiceURL(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700148 const std::string& subpath,
Alex Vakulenkoe4879a22014-08-20 15:47:36 -0700149 const chromeos::data_encoding::WebParamList& params) const {
Christopher Wiley583d64b2015-03-24 14:30:17 -0700150 return BuildURL(config_->service_url(), {subpath}, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700151}
152
153std::string DeviceRegistrationInfo::GetDeviceURL(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700154 const std::string& subpath,
Alex Vakulenkoe4879a22014-08-20 15:47:36 -0700155 const chromeos::data_encoding::WebParamList& params) const {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700156 CHECK(!device_id_.empty()) << "Must have a valid device ID";
Christopher Wiley583d64b2015-03-24 14:30:17 -0700157 return BuildURL(config_->service_url(),
158 {"devices", device_id_, subpath}, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700159}
160
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700161std::string DeviceRegistrationInfo::GetOAuthURL(
162 const std::string& subpath,
Alex Vakulenkoe4879a22014-08-20 15:47:36 -0700163 const chromeos::data_encoding::WebParamList& params) const {
Christopher Wiley583d64b2015-03-24 14:30:17 -0700164 return BuildURL(config_->oauth_url(), {subpath}, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700165}
166
Vitaly Buka620bd7e2015-03-16 01:07:01 -0700167const std::string& DeviceRegistrationInfo::GetDeviceId() const {
168 return device_id_;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700169}
170
171bool DeviceRegistrationInfo::Load() {
Vitaly Buka7ad8ffb2015-03-20 09:46:57 -0700172 // Set kInvalidCredentials to trigger on_status_changed_ callback.
173 registration_status_ = RegistrationStatus::kInvalidCredentials;
Vitaly Bukab055f152015-03-12 13:41:43 -0700174 SetRegistrationStatus(RegistrationStatus::kUnconfigured);
Vitaly Buka7ad8ffb2015-03-20 09:46:57 -0700175
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700176 auto value = storage_->Load();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700177 const base::DictionaryValue* dict = nullptr;
178 if (!value || !value->GetAsDictionary(&dict))
179 return false;
180
181 // Get the values into temp variables first to make sure we can get
182 // all the data correctly before changing the state of this object.
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700183 std::string refresh_token;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700184 std::string device_id;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700185 std::string device_robot_account;
Nathan Bullock02ca28b2015-02-11 16:22:16 -0500186 std::string display_name;
Nathan Bullock02ca28b2015-02-11 16:22:16 -0500187 std::string description;
Nathan Bullock02ca28b2015-02-11 16:22:16 -0500188 std::string location;
Christopher Wiley583d64b2015-03-24 14:30:17 -0700189 if (!dict->GetString(storage_keys::kRefreshToken, &refresh_token) ||
190 !dict->GetString(storage_keys::kDeviceId, &device_id) ||
191 !dict->GetString(storage_keys::kRobotAccount, &device_robot_account) ||
192 !dict->GetString(storage_keys::kDisplayName, &display_name) ||
193 !dict->GetString(storage_keys::kDescription, &description) ||
194 !dict->GetString(storage_keys::kLocation, &location)) {
Nathan Bullock02ca28b2015-02-11 16:22:16 -0500195 return false;
Christopher Wiley583d64b2015-03-24 14:30:17 -0700196 }
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700197 refresh_token_ = refresh_token;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700198 device_robot_account_ = device_robot_account;
Nathan Bullock02ca28b2015-02-11 16:22:16 -0500199 display_name_ = display_name;
200 description_ = description;
201 location_ = location;
Vitaly Buka6522ab62015-02-19 10:26:31 -0800202
Vitaly Buka620bd7e2015-03-16 01:07:01 -0700203 SetDeviceId(device_id);
204
Christopher Wileyba983c82015-03-05 16:32:23 -0800205 if (HaveRegistrationCredentials(nullptr)) {
Christopher Wileyba983c82015-03-05 16:32:23 -0800206 // Wait a significant amount of time for local daemons to publish their
207 // state to Buffet before publishing it to the cloud.
208 // TODO(wiley) We could do a lot of things here to either expose this
209 // timeout as a configurable knob or allow local
210 // daemons to signal that their state is up to date so that
211 // we need not wait for them.
212 ScheduleStartDevice(base::TimeDelta::FromSeconds(5));
213 }
Christopher Wileyc900e482015-02-15 15:42:04 -0800214
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700215 return true;
216}
217
218bool DeviceRegistrationInfo::Save() const {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700219 base::DictionaryValue dict;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700220 dict.SetString(storage_keys::kRefreshToken, refresh_token_);
221 dict.SetString(storage_keys::kDeviceId, device_id_);
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700222 dict.SetString(storage_keys::kRobotAccount, device_robot_account_);
Nathan Bullock02ca28b2015-02-11 16:22:16 -0500223 dict.SetString(storage_keys::kDisplayName, display_name_);
224 dict.SetString(storage_keys::kDescription, description_);
225 dict.SetString(storage_keys::kLocation, location_);
226
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700227 return storage_->Save(&dict);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700228}
229
Christopher Wileycd419662015-02-06 17:51:43 -0800230void DeviceRegistrationInfo::ScheduleStartDevice(const base::TimeDelta& later) {
Vitaly Bukab055f152015-03-12 13:41:43 -0700231 SetRegistrationStatus(RegistrationStatus::kConnecting);
Christopher Wileycd419662015-02-06 17:51:43 -0800232 base::MessageLoop* current = base::MessageLoop::current();
233 if (!current)
234 return; // Assume we're in unittests
Christopher Wileyba983c82015-03-05 16:32:23 -0800235 base::TimeDelta max_delay =
236 base::TimeDelta::FromMinutes(kMaxStartDeviceRetryDelayMinutes);
237 base::TimeDelta min_delay =
238 base::TimeDelta::FromSeconds(kMinStartDeviceRetryDelaySeconds);
239 base::TimeDelta retry_delay = later * 2;
240 if (retry_delay > max_delay) { retry_delay = max_delay; }
241 if (retry_delay < min_delay) { retry_delay = min_delay; }
Christopher Wileycd419662015-02-06 17:51:43 -0800242 current->PostDelayedTask(
243 FROM_HERE,
244 base::Bind(&DeviceRegistrationInfo::StartDevice,
Christopher Wileyba983c82015-03-05 16:32:23 -0800245 weak_factory_.GetWeakPtr(), nullptr,
246 retry_delay),
Christopher Wileycd419662015-02-06 17:51:43 -0800247 later);
248}
249
Alex Vakulenko5f472062014-08-14 17:54:04 -0700250bool DeviceRegistrationInfo::CheckRegistration(chromeos::ErrorPtr* error) {
Christopher Wileyc900e482015-02-15 15:42:04 -0800251 return HaveRegistrationCredentials(error) &&
David Zeuthen390d1912015-03-03 14:54:48 -0500252 MaybeRefreshAccessToken(error);
Christopher Wileyc900e482015-02-15 15:42:04 -0800253}
254
255bool DeviceRegistrationInfo::HaveRegistrationCredentials(
256 chromeos::ErrorPtr* error) {
257 const bool have_credentials = !refresh_token_.empty() &&
258 !device_id_.empty() &&
259 !device_robot_account_.empty();
260
261 VLOG(1) << "Device registration record "
262 << ((have_credentials) ? "found" : "not found.");
263 if (!have_credentials)
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800264 chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainGCD,
265 "device_not_registered",
Alex Vakulenko5f472062014-08-14 17:54:04 -0700266 "No valid device registration record found");
Christopher Wileyc900e482015-02-15 15:42:04 -0800267 return have_credentials;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700268}
269
Nathan Bullock24d189f2015-02-26 13:09:18 -0500270std::unique_ptr<base::DictionaryValue>
271DeviceRegistrationInfo::ParseOAuthResponse(
272 const chromeos::http::Response* response, chromeos::ErrorPtr* error) {
273 int code = 0;
274 auto resp = chromeos::http::ParseJsonResponse(response, &code, error);
275 if (resp && code >= chromeos::http::status_code::BadRequest) {
276 std::string error_code, error_message;
277 if (!resp->GetString("error", &error_code)) {
278 error_code = "unexpected_response";
279 }
280 if (error_code == "invalid_grant") {
281 LOG(INFO) << "The device's registration has been revoked.";
282 SetRegistrationStatus(RegistrationStatus::kInvalidCredentials);
283 }
284 // I have never actually seen an error_description returned.
285 if (!resp->GetString("error_description", &error_message)) {
286 error_message = "Unexpected OAuth error";
287 }
288 chromeos::Error::AddTo(error, FROM_HERE, buffet::kErrorDomainOAuth2,
289 error_code, error_message);
290 return std::unique_ptr<base::DictionaryValue>();
291 }
292 return resp;
293}
294
David Zeuthen390d1912015-03-03 14:54:48 -0500295bool DeviceRegistrationInfo::MaybeRefreshAccessToken(
Alex Vakulenko5f472062014-08-14 17:54:04 -0700296 chromeos::ErrorPtr* error) {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700297 LOG(INFO) << "Checking access token expiration.";
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700298 if (!access_token_.empty() &&
299 !access_token_expiration_.is_null() &&
300 access_token_expiration_ > base::Time::Now()) {
301 LOG(INFO) << "Access token is still valid.";
302 return true;
303 }
David Zeuthen390d1912015-03-03 14:54:48 -0500304 return RefreshAccessToken(error);
305}
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700306
David Zeuthen390d1912015-03-03 14:54:48 -0500307bool DeviceRegistrationInfo::RefreshAccessToken(
308 chromeos::ErrorPtr* error) {
309 LOG(INFO) << "Refreshing access token.";
Alex Vakulenko89dde2f2015-01-07 12:05:12 -0800310 auto response = chromeos::http::PostFormDataAndBlock(GetOAuthURL("token"), {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700311 {"refresh_token", refresh_token_},
Christopher Wiley583d64b2015-03-24 14:30:17 -0700312 {"client_id", config_->client_id()},
313 {"client_secret", config_->client_secret()},
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700314 {"grant_type", "refresh_token"},
Alex Vakulenko89dde2f2015-01-07 12:05:12 -0800315 }, {}, transport_, error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700316 if (!response)
317 return false;
318
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700319 auto json = ParseOAuthResponse(response.get(), error);
320 if (!json)
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700321 return false;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700322
323 int expires_in = 0;
324 if (!json->GetString("access_token", &access_token_) ||
325 !json->GetInteger("expires_in", &expires_in) ||
326 access_token_.empty() ||
327 expires_in <= 0) {
328 LOG(ERROR) << "Access token unavailable.";
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800329 chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainOAuth2,
Alex Vakulenko5f472062014-08-14 17:54:04 -0700330 "unexpected_server_response",
331 "Access token unavailable");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700332 return false;
333 }
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700334 access_token_expiration_ = base::Time::Now() +
335 base::TimeDelta::FromSeconds(expires_in);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700336 LOG(INFO) << "Access token is refreshed for additional " << expires_in
337 << " seconds.";
Nathan Bullockd9e0bcd2015-02-11 11:36:39 -0500338
Nathan Bullockbea91132015-02-19 09:13:33 -0500339 StartXmpp();
Nathan Bullockd9e0bcd2015-02-11 11:36:39 -0500340
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700341 return true;
342}
343
Nathan Bullockbea91132015-02-19 09:13:33 -0500344void DeviceRegistrationInfo::StartXmpp() {
Christopher Wileyd732bd02015-04-07 11:11:18 -0700345 if (!xmpp_enabled_) {
346 LOG(WARNING) << "XMPP support disabled by flag.";
347 return;
348 }
Nathan Bullockbea91132015-02-19 09:13:33 -0500349 // If no MessageLoop assume we're in unittests.
350 if (!base::MessageLoop::current()) {
351 LOG(INFO) << "No MessageLoop, not starting XMPP";
352 return;
353 }
354
355 if (!fd_watcher_.StopWatchingFileDescriptor()) {
356 LOG(WARNING) << "Failed to stop the previous watcher";
357 return;
358 }
359
360 std::unique_ptr<XmppConnection> connection(new XmppConnection());
361 if (!connection->Initialize()) {
362 LOG(WARNING) << "Failed to connect to XMPP server";
363 return;
364 }
365 xmpp_client_.reset(new XmppClient(device_robot_account_, access_token_,
366 std::move(connection)));
367 if (!base::MessageLoopForIO::current()->WatchFileDescriptor(
368 xmpp_client_->GetFileDescriptor(), true /* persistent */,
369 base::MessageLoopForIO::WATCH_READ, &fd_watcher_, this)) {
370 LOG(WARNING) << "Failed to watch XMPP file descriptor";
371 return;
372 }
Vitaly Bukab055f152015-03-12 13:41:43 -0700373
Nathan Bullockbea91132015-02-19 09:13:33 -0500374 xmpp_client_->StartStream();
375}
376
Nathan Bullockf12f7f02015-02-20 14:46:53 -0500377void DeviceRegistrationInfo::OnFileCanReadWithoutBlocking(int fd) {
Vitaly Bukab055f152015-03-12 13:41:43 -0700378 if (!xmpp_client_ || xmpp_client_->GetFileDescriptor() != fd)
379 return;
380 if (!xmpp_client_->Read()) {
381 // Authentication failed or the socket was closed.
382 if (!fd_watcher_.StopWatchingFileDescriptor())
383 LOG(WARNING) << "Failed to stop the watcher";
384 return;
Nathan Bullockf12f7f02015-02-20 14:46:53 -0500385 }
386}
387
Anton Muhind8d32162014-10-02 20:37:00 +0400388std::unique_ptr<base::DictionaryValue>
389DeviceRegistrationInfo::BuildDeviceResource(chromeos::ErrorPtr* error) {
390 std::unique_ptr<base::DictionaryValue> commands =
391 command_manager_->GetCommandDictionary().GetCommandsAsJson(true, error);
392 if (!commands)
393 return nullptr;
394
395 std::unique_ptr<base::DictionaryValue> state =
396 state_manager_->GetStateValuesAsJson(error);
397 if (!state)
398 return nullptr;
399
400 std::unique_ptr<base::DictionaryValue> resource{new base::DictionaryValue};
401 if (!device_id_.empty())
402 resource->SetString("id", device_id_);
Christopher Wiley583d64b2015-03-24 14:30:17 -0700403 resource->SetString("deviceKind", config_->device_kind());
404 resource->SetString("name", config_->name());
Anton Muhind8d32162014-10-02 20:37:00 +0400405 if (!display_name_.empty())
406 resource->SetString("displayName", display_name_);
Vitaly Bukad3eb19c2014-11-21 13:39:43 -0800407 if (!description_.empty())
408 resource->SetString("description", description_);
409 if (!location_.empty())
410 resource->SetString("location", location_);
Christopher Wiley583d64b2015-03-24 14:30:17 -0700411 if (!config_->model_id().empty())
412 resource->SetString("modelManifestId", config_->model_id());
Anton Muhind8d32162014-10-02 20:37:00 +0400413 resource->SetString("channel.supportedType", "xmpp");
414 resource->Set("commandDefs", commands.release());
415 resource->Set("state", state.release());
416
417 return resource;
418}
419
Vitaly Buka6ca3ad62015-03-11 17:03:23 -0700420std::unique_ptr<base::DictionaryValue> DeviceRegistrationInfo::GetDeviceInfo(
Alex Vakulenko5f472062014-08-14 17:54:04 -0700421 chromeos::ErrorPtr* error) {
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700422 if (!CheckRegistration(error))
Vitaly Buka6ca3ad62015-03-11 17:03:23 -0700423 return std::unique_ptr<base::DictionaryValue>();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700424
Anton Muhinac661ab2014-10-03 20:29:48 +0400425 // TODO(antonm): Switch to DoCloudRequest later.
Alex Vakulenko89dde2f2015-01-07 12:05:12 -0800426 auto response = chromeos::http::GetAndBlock(
Alex Vakulenkocca20932014-08-20 17:35:12 -0700427 GetDeviceURL(), {GetAuthorizationHeader()}, transport_, error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700428 int status_code = 0;
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700429 std::unique_ptr<base::DictionaryValue> json =
Alex Vakulenkocca20932014-08-20 17:35:12 -0700430 chromeos::http::ParseJsonResponse(response.get(), &status_code, error);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700431 if (json) {
Alex Vakulenkocca20932014-08-20 17:35:12 -0700432 if (status_code >= chromeos::http::status_code::BadRequest) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700433 LOG(WARNING) << "Failed to retrieve the device info. Response code = "
434 << status_code;
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700435 ParseGCDError(json.get(), error);
Vitaly Buka6ca3ad62015-03-11 17:03:23 -0700436 return std::unique_ptr<base::DictionaryValue>();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700437 }
438 }
Vitaly Buka6ca3ad62015-03-11 17:03:23 -0700439 return json;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700440}
441
Christopher Wiley583d64b2015-03-24 14:30:17 -0700442namespace {
443
444bool GetWithDefault(const std::map<std::string, std::string>& source,
445 const std::string& key,
446 const std::string& default_value,
447 std::string* output) {
448 auto it = source.find(key);
449 if (it == source.end()) {
450 *output = default_value;
451 return false;
452 }
453 *output = it->second;
454 return true;
455}
456
457} // namespace
458
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400459std::string DeviceRegistrationInfo::RegisterDevice(
Alex Vakulenkoa9044342014-08-23 19:31:27 -0700460 const std::map<std::string, std::string>& params,
Alex Vakulenko5f472062014-08-14 17:54:04 -0700461 chromeos::ErrorPtr* error) {
Nathan Bullocke4408482015-02-19 11:13:21 -0500462 std::string ticket_id;
Christopher Wiley583d64b2015-03-24 14:30:17 -0700463 if (!GetWithDefault(params, "ticket_id", "", &ticket_id)) {
464 chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainBuffet,
465 "missing_parameter",
466 "Need ticket_id parameter for RegisterDevice().");
David Zeuthend0db55c2015-02-12 14:47:35 -0500467 return std::string();
Vitaly Buka6522ab62015-02-19 10:26:31 -0800468 }
Christopher Wiley583d64b2015-03-24 14:30:17 -0700469 // These fields are optional, and will default to values from the manufacturer
470 // supplied config.
471 GetWithDefault(params, storage_keys::kDisplayName,
472 config_->default_display_name(), &display_name_);
473 GetWithDefault(params, storage_keys::kDescription,
474 config_->default_description(), &description_);
475 GetWithDefault(params, storage_keys::kLocation,
476 config_->default_location(), &location_);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700477
Anton Muhind8d32162014-10-02 20:37:00 +0400478 std::unique_ptr<base::DictionaryValue> device_draft =
479 BuildDeviceResource(error);
480 if (!device_draft)
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700481 return std::string();
482
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700483 base::DictionaryValue req_json;
Nathan Bullocke4408482015-02-19 11:13:21 -0500484 req_json.SetString("id", ticket_id);
Christopher Wiley583d64b2015-03-24 14:30:17 -0700485 req_json.SetString("oauthClientId", config_->client_id());
Anton Muhind8d32162014-10-02 20:37:00 +0400486 req_json.Set("deviceDraft", device_draft.release());
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700487
Nathan Bullocke4408482015-02-19 11:13:21 -0500488 auto url = GetServiceURL("registrationTickets/" + ticket_id,
Christopher Wiley583d64b2015-03-24 14:30:17 -0700489 {{"key", config_->api_key()}});
Anton Muhin532ff7e2014-09-29 23:21:21 +0400490 std::unique_ptr<chromeos::http::Response> response =
Alex Vakulenko89dde2f2015-01-07 12:05:12 -0800491 chromeos::http::PatchJsonAndBlock(url, &req_json, {}, transport_, error);
Anton Muhin532ff7e2014-09-29 23:21:21 +0400492 auto json_resp = chromeos::http::ParseJsonResponse(response.get(), nullptr,
493 error);
494 if (!json_resp)
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700495 return std::string();
David Zeuthen1dbad472015-02-12 15:24:21 -0500496 if (!response->IsSuccessful()) {
497 ParseGCDError(json_resp.get(), error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700498 return std::string();
David Zeuthen1dbad472015-02-12 15:24:21 -0500499 }
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700500
Nathan Bullocke4408482015-02-19 11:13:21 -0500501 url = GetServiceURL("registrationTickets/" + ticket_id +
Christopher Wiley583d64b2015-03-24 14:30:17 -0700502 "/finalize?key=" + config_->api_key());
Alex Vakulenko89dde2f2015-01-07 12:05:12 -0800503 response = chromeos::http::SendRequestWithNoDataAndBlock(
504 chromeos::http::request_type::kPost, url, {}, transport_, error);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700505 if (!response)
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400506 return std::string();
507 json_resp = chromeos::http::ParseJsonResponse(response.get(), nullptr, error);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700508 if (!json_resp)
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400509 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700510 if (!response->IsSuccessful()) {
511 ParseGCDError(json_resp.get(), error);
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400512 return std::string();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700513 }
Anton Muhin532ff7e2014-09-29 23:21:21 +0400514
515 std::string auth_code;
Vitaly Buka620bd7e2015-03-16 01:07:01 -0700516 std::string device_id;
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700517 if (!json_resp->GetString("robotAccountEmail", &device_robot_account_) ||
518 !json_resp->GetString("robotAccountAuthorizationCode", &auth_code) ||
Vitaly Buka620bd7e2015-03-16 01:07:01 -0700519 !json_resp->GetString("deviceDraft.id", &device_id)) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800520 chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainGCD,
521 "unexpected_response",
Alex Vakulenko5f472062014-08-14 17:54:04 -0700522 "Device account missing in response");
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400523 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700524 }
Vitaly Buka620bd7e2015-03-16 01:07:01 -0700525 SetDeviceId(device_id);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700526
527 // Now get access_token and refresh_token
Alex Vakulenko89dde2f2015-01-07 12:05:12 -0800528 response = chromeos::http::PostFormDataAndBlock(GetOAuthURL("token"), {
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700529 {"code", auth_code},
Christopher Wiley583d64b2015-03-24 14:30:17 -0700530 {"client_id", config_->client_id()},
531 {"client_secret", config_->client_secret()},
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700532 {"redirect_uri", "oob"},
533 {"scope", "https://www.googleapis.com/auth/clouddevices"},
534 {"grant_type", "authorization_code"}
Alex Vakulenko89dde2f2015-01-07 12:05:12 -0800535 }, {}, transport_, error);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700536 if (!response)
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400537 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700538
539 json_resp = ParseOAuthResponse(response.get(), error);
540 int expires_in = 0;
541 if (!json_resp ||
542 !json_resp->GetString("access_token", &access_token_) ||
543 !json_resp->GetString("refresh_token", &refresh_token_) ||
544 !json_resp->GetInteger("expires_in", &expires_in) ||
545 access_token_.empty() ||
546 refresh_token_.empty() ||
547 expires_in <= 0) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800548 chromeos::Error::AddTo(error, FROM_HERE,
549 kErrorDomainGCD, "unexpected_response",
Alex Vakulenko5f472062014-08-14 17:54:04 -0700550 "Device access_token missing in response");
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400551 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700552 }
553
554 access_token_expiration_ = base::Time::Now() +
555 base::TimeDelta::FromSeconds(expires_in);
556
557 Save();
Nathan Bullockbea91132015-02-19 09:13:33 -0500558 StartXmpp();
Christopher Wileycd419662015-02-06 17:51:43 -0800559
560 // We're going to respond with our success immediately and we'll StartDevice
561 // shortly after.
562 ScheduleStartDevice(base::TimeDelta::FromSeconds(0));
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400563 return device_id_;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700564}
565
Anton Muhin633eded2014-10-03 20:40:10 +0400566namespace {
567
568template <class T>
569void PostToCallback(base::Callback<void(const T&)> callback,
570 std::unique_ptr<T> value) {
571 auto cb = [callback] (T* result) {
572 callback.Run(*result);
573 };
574 base::MessageLoop::current()->PostTask(
575 FROM_HERE, base::Bind(cb, base::Owned(value.release())));
576}
577
Alex Vakulenko0357c032015-01-06 16:32:31 -0800578using ResponsePtr = scoped_ptr<chromeos::http::Response>;
Anton Muhin633eded2014-10-03 20:40:10 +0400579
Alex Vakulenko0357c032015-01-06 16:32:31 -0800580void SendRequestWithRetries(
Anton Muhin633eded2014-10-03 20:40:10 +0400581 const std::string& method,
582 const std::string& url,
583 const std::string& data,
584 const std::string& mime_type,
585 const chromeos::http::HeaderList& headers,
586 std::shared_ptr<chromeos::http::Transport> transport,
587 int num_retries,
Alex Vakulenko0357c032015-01-06 16:32:31 -0800588 const chromeos::http::SuccessCallback& success_callback,
589 const chromeos::http::ErrorCallback& error_callback) {
590 auto on_failure =
591 [method, url, data, mime_type, headers, transport, num_retries,
Alex Vakulenko6401d012015-01-16 07:40:43 -0800592 success_callback, error_callback](int request_id,
593 const chromeos::Error* error) {
Anton Muhin633eded2014-10-03 20:40:10 +0400594 if (num_retries > 0) {
Alex Vakulenko0357c032015-01-06 16:32:31 -0800595 SendRequestWithRetries(method, url, data, mime_type,
596 headers, transport, num_retries - 1,
597 success_callback, error_callback);
Anton Muhin633eded2014-10-03 20:40:10 +0400598 } else {
Alex Vakulenko6401d012015-01-16 07:40:43 -0800599 error_callback.Run(request_id, error);
Anton Muhin633eded2014-10-03 20:40:10 +0400600 }
601 };
602
Alex Vakulenko0357c032015-01-06 16:32:31 -0800603 auto on_success =
Alex Vakulenko6401d012015-01-16 07:40:43 -0800604 [on_failure, success_callback, error_callback](int request_id,
605 ResponsePtr response) {
Alex Vakulenko0357c032015-01-06 16:32:31 -0800606 int status_code = response->GetStatusCode();
607 if (status_code >= chromeos::http::status_code::Continue &&
608 status_code < chromeos::http::status_code::BadRequest) {
Alex Vakulenko6401d012015-01-16 07:40:43 -0800609 success_callback.Run(request_id, response.Pass());
Anton Muhin633eded2014-10-03 20:40:10 +0400610 return;
611 }
Anton Muhin633eded2014-10-03 20:40:10 +0400612
Alex Vakulenko0357c032015-01-06 16:32:31 -0800613 // TODO(antonm): Should add some useful information to error.
614 LOG(WARNING) << "Request failed. Response code = " << status_code;
Anton Muhin633eded2014-10-03 20:40:10 +0400615
Alex Vakulenko0357c032015-01-06 16:32:31 -0800616 chromeos::ErrorPtr error;
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800617 chromeos::Error::AddTo(&error, FROM_HERE, chromeos::errors::http::kDomain,
Anton Muhin233d2ee2014-10-22 15:16:24 +0400618 std::to_string(status_code),
619 response->GetStatusText());
Alex Vakulenko0357c032015-01-06 16:32:31 -0800620 if (status_code >= chromeos::http::status_code::InternalServerError &&
621 status_code < 600) {
622 // Request was valid, but server failed, retry.
623 // TODO(antonm): Implement exponential backoff.
624 // TODO(antonm): Reconsider status codes, maybe only some require
625 // retry.
626 // TODO(antonm): Support Retry-After header.
Alex Vakulenko6401d012015-01-16 07:40:43 -0800627 on_failure(request_id, error.get());
Alex Vakulenko0357c032015-01-06 16:32:31 -0800628 } else {
Alex Vakulenko6401d012015-01-16 07:40:43 -0800629 error_callback.Run(request_id, error.get());
Alex Vakulenko0357c032015-01-06 16:32:31 -0800630 }
631 };
632
633 chromeos::http::SendRequest(method, url, data.c_str(), data.size(),
634 mime_type, headers, transport,
635 base::Bind(on_success),
636 base::Bind(on_failure));
Anton Muhin633eded2014-10-03 20:40:10 +0400637}
638
639} // namespace
640
Anton Muhinac661ab2014-10-03 20:29:48 +0400641void DeviceRegistrationInfo::DoCloudRequest(
Anton Muhin233d2ee2014-10-22 15:16:24 +0400642 const std::string& method,
Anton Muhinac661ab2014-10-03 20:29:48 +0400643 const std::string& url,
644 const base::DictionaryValue* body,
Alex Vakulenko0357c032015-01-06 16:32:31 -0800645 const CloudRequestCallback& success_callback,
646 const CloudRequestErrorCallback& error_callback) {
647 // TODO(antonm): Add reauthorization on access token expiration (do not
Anton Muhinac661ab2014-10-03 20:29:48 +0400648 // forget about 5xx when fetching new access token).
649 // TODO(antonm): Add support for device removal.
650
Anton Muhinac661ab2014-10-03 20:29:48 +0400651 std::string data;
652 if (body)
653 base::JSONWriter::Write(body, &data);
654
655 const std::string mime_type{chromeos::mime::AppendParameter(
656 chromeos::mime::application::kJson,
657 chromeos::mime::parameters::kCharset,
658 "utf-8")};
659
Vitaly Bukab055f152015-03-12 13:41:43 -0700660 auto status_cb = base::Bind(&DeviceRegistrationInfo::SetRegistrationStatus,
661 weak_factory_.GetWeakPtr());
662
663 auto request_cb = [success_callback, error_callback, status_cb](
664 int request_id, ResponsePtr response) {
665 status_cb.Run(RegistrationStatus::kConnected);
Anton Muhin633eded2014-10-03 20:40:10 +0400666 chromeos::ErrorPtr error;
Anton Muhinac661ab2014-10-03 20:29:48 +0400667
Anton Muhin633eded2014-10-03 20:40:10 +0400668 std::unique_ptr<base::DictionaryValue> json_resp{
Alex Vakulenko0357c032015-01-06 16:32:31 -0800669 chromeos::http::ParseJsonResponse(response.get(), nullptr, &error)};
Anton Muhin633eded2014-10-03 20:40:10 +0400670 if (!json_resp) {
Alex Vakulenko0357c032015-01-06 16:32:31 -0800671 error_callback.Run(error.get());
Anton Muhin633eded2014-10-03 20:40:10 +0400672 return;
673 }
Anton Muhinac661ab2014-10-03 20:29:48 +0400674
Alex Vakulenko0357c032015-01-06 16:32:31 -0800675 success_callback.Run(*json_resp);
Anton Muhinac661ab2014-10-03 20:29:48 +0400676 };
Anton Muhin633eded2014-10-03 20:40:10 +0400677
Alex Vakulenko6401d012015-01-16 07:40:43 -0800678 auto error_cb =
679 [error_callback](int request_id, const chromeos::Error* error) {
680 error_callback.Run(error);
681 };
682
Anton Muhin233d2ee2014-10-22 15:16:24 +0400683 auto transport = transport_;
Vitaly Bukab055f152015-03-12 13:41:43 -0700684 auto error_callackback_with_reauthorization = base::Bind(
685 [method, url, data, mime_type, transport, request_cb, error_cb,
686 status_cb](DeviceRegistrationInfo* self, int request_id,
687 const chromeos::Error* error) {
688 status_cb.Run(RegistrationStatus::kConnecting);
689 if (error->HasError(
690 chromeos::errors::http::kDomain,
691 std::to_string(chromeos::http::status_code::Denied))) {
692 chromeos::ErrorPtr reauthorization_error;
693 // Forcibly refresh the access token.
694 if (!self->RefreshAccessToken(&reauthorization_error)) {
695 // TODO(antonm): Check if the device has been actually removed.
696 error_cb(request_id, reauthorization_error.get());
697 return;
698 }
699 SendRequestWithRetries(method, url, data, mime_type,
700 {self->GetAuthorizationHeader()}, transport, 7,
701 base::Bind(request_cb), base::Bind(error_cb));
702 } else {
703 error_cb(request_id, error);
704 }
705 },
706 base::Unretained(this));
Anton Muhin233d2ee2014-10-22 15:16:24 +0400707
Alex Vakulenko0357c032015-01-06 16:32:31 -0800708 SendRequestWithRetries(method, url,
709 data, mime_type,
710 {GetAuthorizationHeader()},
711 transport,
712 7,
713 base::Bind(request_cb),
714 error_callackback_with_reauthorization);
Anton Muhinac661ab2014-10-03 20:29:48 +0400715}
716
Christopher Wileyba983c82015-03-05 16:32:23 -0800717void DeviceRegistrationInfo::StartDevice(
718 chromeos::ErrorPtr* error,
719 const base::TimeDelta& retry_delay) {
Nathan Bullock8fb6de72015-02-24 11:58:39 -0500720 if (!HaveRegistrationCredentials(error))
Anton Muhind8d32162014-10-02 20:37:00 +0400721 return;
Christopher Wileyba983c82015-03-05 16:32:23 -0800722 auto handle_start_device_failure_cb = base::Bind(
723 &IgnoreCloudErrorWithCallback,
724 base::Bind(&DeviceRegistrationInfo::ScheduleStartDevice,
725 weak_factory_.GetWeakPtr(),
726 retry_delay));
727 // "Starting" a device just means that we:
728 // 1) push an updated device resource
729 // 2) fetch an initial set of outstanding commands
730 // 3) abort any commands that we've previously marked as "in progress"
731 // or as being in an error state.
732 // 4) Initiate periodic polling for commands.
733 auto periodically_poll_commands_cb = base::Bind(
734 &DeviceRegistrationInfo::PeriodicallyPollCommands,
735 weak_factory_.GetWeakPtr());
736 auto abort_commands_cb = base::Bind(
737 &DeviceRegistrationInfo::AbortLimboCommands,
738 weak_factory_.GetWeakPtr(),
739 periodically_poll_commands_cb);
740 auto fetch_commands_cb = base::Bind(
741 &DeviceRegistrationInfo::FetchCommands,
742 weak_factory_.GetWeakPtr(),
743 abort_commands_cb,
744 handle_start_device_failure_cb);
745 UpdateDeviceResource(fetch_commands_cb, handle_start_device_failure_cb);
Anton Muhinc635c592014-10-28 21:48:08 +0400746}
747
Anton Muhin59755522014-11-05 21:30:12 +0400748void DeviceRegistrationInfo::UpdateCommand(
749 const std::string& command_id,
750 const base::DictionaryValue& command_patch) {
751 DoCloudRequest(
752 chromeos::http::request_type::kPatch,
753 GetServiceURL("commands/" + command_id),
754 &command_patch,
755 base::Bind(&IgnoreCloudResult), base::Bind(&IgnoreCloudError));
756}
757
Christopher Wileyba983c82015-03-05 16:32:23 -0800758void DeviceRegistrationInfo::UpdateDeviceResource(
759 const base::Closure& on_success,
760 const CloudRequestErrorCallback& on_failure) {
Anton Muhind8d32162014-10-02 20:37:00 +0400761 std::unique_ptr<base::DictionaryValue> device_resource =
Anton Muhinc635c592014-10-28 21:48:08 +0400762 BuildDeviceResource(nullptr);
Anton Muhind8d32162014-10-02 20:37:00 +0400763 if (!device_resource)
764 return;
765
Anton Muhinc635c592014-10-28 21:48:08 +0400766 DoCloudRequest(
767 chromeos::http::request_type::kPut,
768 GetDeviceURL(),
769 device_resource.get(),
Christopher Wileyba983c82015-03-05 16:32:23 -0800770 base::Bind(&IgnoreCloudResultWithCallback, on_success),
771 on_failure);
Anton Muhinc635c592014-10-28 21:48:08 +0400772}
Anton Muhina34f0d92014-10-03 21:09:40 +0400773
Christopher Wileyba983c82015-03-05 16:32:23 -0800774namespace {
775
776void HandleFetchCommandsResult(
777 const base::Callback<void(const base::ListValue&)>& callback,
778 const base::DictionaryValue& json) {
779 const base::ListValue* commands{nullptr};
780 if (!json.GetList("commands", &commands)) {
781 VLOG(1) << "No commands in the response.";
782 }
783 const base::ListValue empty;
784 callback.Run(commands ? *commands : empty);
785}
786
787} // namespace
788
Anton Muhinc635c592014-10-28 21:48:08 +0400789void DeviceRegistrationInfo::FetchCommands(
Christopher Wileyba983c82015-03-05 16:32:23 -0800790 const base::Callback<void(const base::ListValue&)>& on_success,
791 const CloudRequestErrorCallback& on_failure) {
Anton Muhinc635c592014-10-28 21:48:08 +0400792 DoCloudRequest(
793 chromeos::http::request_type::kGet,
794 GetServiceURL("commands/queue", {{"deviceId", device_id_}}),
795 nullptr,
Christopher Wileyba983c82015-03-05 16:32:23 -0800796 base::Bind(&HandleFetchCommandsResult, on_success),
797 on_failure);
Anton Muhinc635c592014-10-28 21:48:08 +0400798}
Anton Muhina34f0d92014-10-03 21:09:40 +0400799
Anton Muhinc635c592014-10-28 21:48:08 +0400800void DeviceRegistrationInfo::AbortLimboCommands(
Christopher Wileyba983c82015-03-05 16:32:23 -0800801 const base::Closure& callback, const base::ListValue& commands) {
Anton Muhinc635c592014-10-28 21:48:08 +0400802 const size_t size{commands.GetSize()};
803 for (size_t i = 0; i < size; ++i) {
804 const base::DictionaryValue* command{nullptr};
805 if (!commands.GetDictionary(i, &command)) {
806 LOG(WARNING) << "No command resource at " << i;
807 continue;
Anton Muhina34f0d92014-10-03 21:09:40 +0400808 }
Anton Muhinc635c592014-10-28 21:48:08 +0400809 std::string command_state;
810 if (!command->GetString("state", &command_state)) {
811 LOG(WARNING) << "Command with no state at " << i;
812 continue;
813 }
814 if (command_state != "error" &&
815 command_state != "inProgress" &&
816 command_state != "paused") {
817 // It's not a limbo command, ignore.
818 continue;
819 }
820 std::string command_id;
821 if (!command->GetString("id", &command_id)) {
822 LOG(WARNING) << "Command with no ID at " << i;
823 continue;
824 }
Anton Muhin6d2569e2014-10-30 12:32:27 +0400825
826 std::unique_ptr<base::DictionaryValue> command_copy{command->DeepCopy()};
827 command_copy->SetString("state", "aborted");
Christopher Wileyba983c82015-03-05 16:32:23 -0800828 // TODO(wiley) We could consider handling this error case more gracefully.
Anton Muhin6d2569e2014-10-30 12:32:27 +0400829 DoCloudRequest(
830 chromeos::http::request_type::kPut,
831 GetServiceURL("commands/" + command_id),
832 command_copy.get(),
Anton Muhin5191e812014-10-30 17:49:48 +0400833 base::Bind(&IgnoreCloudResult), base::Bind(&IgnoreCloudError));
Anton Muhinc635c592014-10-28 21:48:08 +0400834 }
Anton Muhin0ae1fba2014-10-22 19:30:40 +0400835
Anton Muhinc635c592014-10-28 21:48:08 +0400836 base::MessageLoop::current()->PostTask(FROM_HERE, callback);
837}
838
839void DeviceRegistrationInfo::PeriodicallyPollCommands() {
Anton Muhind07e2062014-10-27 10:53:29 +0400840 VLOG(1) << "Poll commands";
Christopher Wiley34eae042015-03-18 10:25:08 -0700841 command_poll_timer_.Start(
Anton Muhind07e2062014-10-27 10:53:29 +0400842 FROM_HERE,
Christopher Wiley34eae042015-03-18 10:25:08 -0700843 base::TimeDelta::FromMilliseconds(config_->polling_period_ms()),
844 base::Bind(&DeviceRegistrationInfo::FetchCommands,
845 base::Unretained(this),
846 base::Bind(&DeviceRegistrationInfo::PublishCommands,
847 base::Unretained(this)),
848 base::Bind(&IgnoreCloudError)));
Anton Muhinb8315622014-11-20 03:17:05 +0400849 // TODO(antonm): Use better trigger: when StateManager registers new updates,
Alex Vakulenko0357c032015-01-06 16:32:31 -0800850 // it should call closure which will post a task, probably with some
851 // throttling, to publish state updates.
Christopher Wiley34eae042015-03-18 10:25:08 -0700852 state_push_timer_.Start(
Anton Muhinb8315622014-11-20 03:17:05 +0400853 FROM_HERE,
Christopher Wiley34eae042015-03-18 10:25:08 -0700854 base::TimeDelta::FromMilliseconds(config_->polling_period_ms()),
Anton Muhinb8315622014-11-20 03:17:05 +0400855 base::Bind(&DeviceRegistrationInfo::PublishStateUpdates,
Christopher Wiley34eae042015-03-18 10:25:08 -0700856 base::Unretained(this)));
Anton Muhind07e2062014-10-27 10:53:29 +0400857}
858
859void DeviceRegistrationInfo::PublishCommands(const base::ListValue& commands) {
860 const CommandDictionary& command_dictionary =
861 command_manager_->GetCommandDictionary();
862
863 const size_t size{commands.GetSize()};
864 for (size_t i = 0; i < size; ++i) {
865 const base::DictionaryValue* command{nullptr};
866 if (!commands.GetDictionary(i, &command)) {
867 LOG(WARNING) << "No command resource at " << i;
868 continue;
869 }
870
871 std::unique_ptr<CommandInstance> command_instance =
872 CommandInstance::FromJson(command, command_dictionary, nullptr);
873 if (!command_instance) {
874 LOG(WARNING) << "Failed to parse a command";
875 continue;
876 }
877
Anton Muhin5191e812014-10-30 17:49:48 +0400878 // TODO(antonm): Properly process cancellation of commands.
Anton Muhin59755522014-11-05 21:30:12 +0400879 if (!command_manager_->FindCommand(command_instance->GetID())) {
880 std::unique_ptr<CommandProxyInterface> cloud_proxy{
881 new CloudCommandProxy(command_instance.get(), this)};
882 command_instance->AddProxy(std::move(cloud_proxy));
Anton Muhin5191e812014-10-30 17:49:48 +0400883 command_manager_->AddCommand(std::move(command_instance));
Anton Muhin59755522014-11-05 21:30:12 +0400884 }
Anton Muhind07e2062014-10-27 10:53:29 +0400885 }
Anton Muhind8d32162014-10-02 20:37:00 +0400886}
887
Anton Muhinb8315622014-11-20 03:17:05 +0400888void DeviceRegistrationInfo::PublishStateUpdates() {
889 VLOG(1) << "PublishStateUpdates";
890 const std::vector<StateChange> state_changes{
891 state_manager_->GetAndClearRecordedStateChanges()};
892 if (state_changes.empty())
893 return;
894
895 std::unique_ptr<base::ListValue> patches{new base::ListValue};
896 for (const auto& state_change : state_changes) {
897 std::unique_ptr<base::DictionaryValue> patch{new base::DictionaryValue};
Anton Muhin76933fd2014-11-21 21:25:18 +0400898 patch->SetString("timeMs",
899 std::to_string(state_change.timestamp.ToJavaTime()));
Anton Muhinb8315622014-11-20 03:17:05 +0400900
901 std::unique_ptr<base::DictionaryValue> changes{new base::DictionaryValue};
902 for (const auto& pair : state_change.changed_properties) {
903 auto value = pair.second->ToJson(nullptr);
904 if (!value) {
905 return;
906 }
Alex Vakulenko61ad4db2015-01-20 10:50:04 -0800907 // The key in |pair.first| is the full property name in format
908 // "package.property_name", so must use DictionaryValue::Set() instead of
909 // DictionaryValue::SetWithoutPathExpansion to recreate the JSON
910 // property tree properly.
911 changes->Set(pair.first, value.release());
Anton Muhinb8315622014-11-20 03:17:05 +0400912 }
913 patch->Set("patch", changes.release());
914
915 patches->Append(patch.release());
916 }
917
918 base::DictionaryValue body;
Anton Muhin76933fd2014-11-21 21:25:18 +0400919 body.SetString("requestTimeMs",
920 std::to_string(base::Time::Now().ToJavaTime()));
Anton Muhinb8315622014-11-20 03:17:05 +0400921 body.Set("patches", patches.release());
922
923 DoCloudRequest(
924 chromeos::http::request_type::kPost,
925 GetDeviceURL("patchState"),
926 &body,
927 base::Bind(&IgnoreCloudResult), base::Bind(&IgnoreCloudError));
928}
929
Christopher Wileyc900e482015-02-15 15:42:04 -0800930void DeviceRegistrationInfo::SetRegistrationStatus(
931 RegistrationStatus new_status) {
932 if (new_status == registration_status_)
933 return;
934 VLOG(1) << "Changing registration status to " << StatusToString(new_status);
935 registration_status_ = new_status;
Vitaly Buka7ad8ffb2015-03-20 09:46:57 -0700936 if (!on_status_changed_.is_null())
937 on_status_changed_.Run();
Vitaly Buka620bd7e2015-03-16 01:07:01 -0700938}
939
940void DeviceRegistrationInfo::SetDeviceId(const std::string& device_id) {
941 if (device_id == device_id_)
942 return;
943 device_id_ = device_id;
Vitaly Buka7ad8ffb2015-03-20 09:46:57 -0700944 if (!on_status_changed_.is_null())
945 on_status_changed_.Run();
Christopher Wileyc900e482015-02-15 15:42:04 -0800946}
947
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700948} // namespace buffet