blob: 3f7e17ce15bdc61bf26894151c34a50939355d90 [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,
Vitaly Buka7ad8ffb2015-03-20 09:46:57 -0700129 const base::Closure& on_status_changed)
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700130 : transport_{transport},
Christopher Wileye0fdeee2015-02-07 18:29:32 -0800131 storage_{state_store},
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700132 command_manager_{command_manager},
Anton Muhin332df192014-11-22 05:59:14 +0400133 state_manager_{state_manager},
Christopher Wiley583d64b2015-03-24 14:30:17 -0700134 config_{std::move(config)},
Vitaly Buka7ad8ffb2015-03-20 09:46:57 -0700135 on_status_changed_{on_status_changed} {
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700136}
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700137
Anton Muhin332df192014-11-22 05:59:14 +0400138DeviceRegistrationInfo::~DeviceRegistrationInfo() = default;
139
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700140std::pair<std::string, std::string>
141 DeviceRegistrationInfo::GetAuthorizationHeader() const {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700142 return BuildAuthHeader("Bearer", access_token_);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700143}
144
145std::string DeviceRegistrationInfo::GetServiceURL(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700146 const std::string& subpath,
Alex Vakulenkoe4879a22014-08-20 15:47:36 -0700147 const chromeos::data_encoding::WebParamList& params) const {
Christopher Wiley583d64b2015-03-24 14:30:17 -0700148 return BuildURL(config_->service_url(), {subpath}, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700149}
150
151std::string DeviceRegistrationInfo::GetDeviceURL(
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700152 const std::string& subpath,
Alex Vakulenkoe4879a22014-08-20 15:47:36 -0700153 const chromeos::data_encoding::WebParamList& params) const {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700154 CHECK(!device_id_.empty()) << "Must have a valid device ID";
Christopher Wiley583d64b2015-03-24 14:30:17 -0700155 return BuildURL(config_->service_url(),
156 {"devices", device_id_, subpath}, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700157}
158
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700159std::string DeviceRegistrationInfo::GetOAuthURL(
160 const std::string& subpath,
Alex Vakulenkoe4879a22014-08-20 15:47:36 -0700161 const chromeos::data_encoding::WebParamList& params) const {
Christopher Wiley583d64b2015-03-24 14:30:17 -0700162 return BuildURL(config_->oauth_url(), {subpath}, params);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700163}
164
Vitaly Buka620bd7e2015-03-16 01:07:01 -0700165const std::string& DeviceRegistrationInfo::GetDeviceId() const {
166 return device_id_;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700167}
168
169bool DeviceRegistrationInfo::Load() {
Vitaly Buka7ad8ffb2015-03-20 09:46:57 -0700170 // Set kInvalidCredentials to trigger on_status_changed_ callback.
171 registration_status_ = RegistrationStatus::kInvalidCredentials;
Vitaly Bukab055f152015-03-12 13:41:43 -0700172 SetRegistrationStatus(RegistrationStatus::kUnconfigured);
Vitaly Buka7ad8ffb2015-03-20 09:46:57 -0700173
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700174 auto value = storage_->Load();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700175 const base::DictionaryValue* dict = nullptr;
176 if (!value || !value->GetAsDictionary(&dict))
177 return false;
178
179 // Get the values into temp variables first to make sure we can get
180 // all the data correctly before changing the state of this object.
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700181 std::string refresh_token;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700182 std::string device_id;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700183 std::string device_robot_account;
Nathan Bullock02ca28b2015-02-11 16:22:16 -0500184 std::string display_name;
Nathan Bullock02ca28b2015-02-11 16:22:16 -0500185 std::string description;
Nathan Bullock02ca28b2015-02-11 16:22:16 -0500186 std::string location;
Christopher Wiley583d64b2015-03-24 14:30:17 -0700187 if (!dict->GetString(storage_keys::kRefreshToken, &refresh_token) ||
188 !dict->GetString(storage_keys::kDeviceId, &device_id) ||
189 !dict->GetString(storage_keys::kRobotAccount, &device_robot_account) ||
190 !dict->GetString(storage_keys::kDisplayName, &display_name) ||
191 !dict->GetString(storage_keys::kDescription, &description) ||
192 !dict->GetString(storage_keys::kLocation, &location)) {
Nathan Bullock02ca28b2015-02-11 16:22:16 -0500193 return false;
Christopher Wiley583d64b2015-03-24 14:30:17 -0700194 }
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700195 refresh_token_ = refresh_token;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700196 device_robot_account_ = device_robot_account;
Nathan Bullock02ca28b2015-02-11 16:22:16 -0500197 display_name_ = display_name;
198 description_ = description;
199 location_ = location;
Vitaly Buka6522ab62015-02-19 10:26:31 -0800200
Vitaly Buka620bd7e2015-03-16 01:07:01 -0700201 SetDeviceId(device_id);
202
Christopher Wileyba983c82015-03-05 16:32:23 -0800203 if (HaveRegistrationCredentials(nullptr)) {
Christopher Wileyba983c82015-03-05 16:32:23 -0800204 // Wait a significant amount of time for local daemons to publish their
205 // state to Buffet before publishing it to the cloud.
206 // TODO(wiley) We could do a lot of things here to either expose this
207 // timeout as a configurable knob or allow local
208 // daemons to signal that their state is up to date so that
209 // we need not wait for them.
210 ScheduleStartDevice(base::TimeDelta::FromSeconds(5));
211 }
Christopher Wileyc900e482015-02-15 15:42:04 -0800212
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700213 return true;
214}
215
216bool DeviceRegistrationInfo::Save() const {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700217 base::DictionaryValue dict;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700218 dict.SetString(storage_keys::kRefreshToken, refresh_token_);
219 dict.SetString(storage_keys::kDeviceId, device_id_);
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700220 dict.SetString(storage_keys::kRobotAccount, device_robot_account_);
Nathan Bullock02ca28b2015-02-11 16:22:16 -0500221 dict.SetString(storage_keys::kDisplayName, display_name_);
222 dict.SetString(storage_keys::kDescription, description_);
223 dict.SetString(storage_keys::kLocation, location_);
224
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700225 return storage_->Save(&dict);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700226}
227
Christopher Wileycd419662015-02-06 17:51:43 -0800228void DeviceRegistrationInfo::ScheduleStartDevice(const base::TimeDelta& later) {
Vitaly Bukab055f152015-03-12 13:41:43 -0700229 SetRegistrationStatus(RegistrationStatus::kConnecting);
Christopher Wileycd419662015-02-06 17:51:43 -0800230 base::MessageLoop* current = base::MessageLoop::current();
231 if (!current)
232 return; // Assume we're in unittests
Christopher Wileyba983c82015-03-05 16:32:23 -0800233 base::TimeDelta max_delay =
234 base::TimeDelta::FromMinutes(kMaxStartDeviceRetryDelayMinutes);
235 base::TimeDelta min_delay =
236 base::TimeDelta::FromSeconds(kMinStartDeviceRetryDelaySeconds);
237 base::TimeDelta retry_delay = later * 2;
238 if (retry_delay > max_delay) { retry_delay = max_delay; }
239 if (retry_delay < min_delay) { retry_delay = min_delay; }
Christopher Wileycd419662015-02-06 17:51:43 -0800240 current->PostDelayedTask(
241 FROM_HERE,
242 base::Bind(&DeviceRegistrationInfo::StartDevice,
Christopher Wileyba983c82015-03-05 16:32:23 -0800243 weak_factory_.GetWeakPtr(), nullptr,
244 retry_delay),
Christopher Wileycd419662015-02-06 17:51:43 -0800245 later);
246}
247
Alex Vakulenko5f472062014-08-14 17:54:04 -0700248bool DeviceRegistrationInfo::CheckRegistration(chromeos::ErrorPtr* error) {
Christopher Wileyc900e482015-02-15 15:42:04 -0800249 return HaveRegistrationCredentials(error) &&
David Zeuthen390d1912015-03-03 14:54:48 -0500250 MaybeRefreshAccessToken(error);
Christopher Wileyc900e482015-02-15 15:42:04 -0800251}
252
253bool DeviceRegistrationInfo::HaveRegistrationCredentials(
254 chromeos::ErrorPtr* error) {
255 const bool have_credentials = !refresh_token_.empty() &&
256 !device_id_.empty() &&
257 !device_robot_account_.empty();
258
259 VLOG(1) << "Device registration record "
260 << ((have_credentials) ? "found" : "not found.");
261 if (!have_credentials)
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800262 chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainGCD,
263 "device_not_registered",
Alex Vakulenko5f472062014-08-14 17:54:04 -0700264 "No valid device registration record found");
Christopher Wileyc900e482015-02-15 15:42:04 -0800265 return have_credentials;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700266}
267
Nathan Bullock24d189f2015-02-26 13:09:18 -0500268std::unique_ptr<base::DictionaryValue>
269DeviceRegistrationInfo::ParseOAuthResponse(
270 const chromeos::http::Response* response, chromeos::ErrorPtr* error) {
271 int code = 0;
272 auto resp = chromeos::http::ParseJsonResponse(response, &code, error);
273 if (resp && code >= chromeos::http::status_code::BadRequest) {
274 std::string error_code, error_message;
275 if (!resp->GetString("error", &error_code)) {
276 error_code = "unexpected_response";
277 }
278 if (error_code == "invalid_grant") {
279 LOG(INFO) << "The device's registration has been revoked.";
280 SetRegistrationStatus(RegistrationStatus::kInvalidCredentials);
281 }
282 // I have never actually seen an error_description returned.
283 if (!resp->GetString("error_description", &error_message)) {
284 error_message = "Unexpected OAuth error";
285 }
286 chromeos::Error::AddTo(error, FROM_HERE, buffet::kErrorDomainOAuth2,
287 error_code, error_message);
288 return std::unique_ptr<base::DictionaryValue>();
289 }
290 return resp;
291}
292
David Zeuthen390d1912015-03-03 14:54:48 -0500293bool DeviceRegistrationInfo::MaybeRefreshAccessToken(
Alex Vakulenko5f472062014-08-14 17:54:04 -0700294 chromeos::ErrorPtr* error) {
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700295 LOG(INFO) << "Checking access token expiration.";
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700296 if (!access_token_.empty() &&
297 !access_token_expiration_.is_null() &&
298 access_token_expiration_ > base::Time::Now()) {
299 LOG(INFO) << "Access token is still valid.";
300 return true;
301 }
David Zeuthen390d1912015-03-03 14:54:48 -0500302 return RefreshAccessToken(error);
303}
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700304
David Zeuthen390d1912015-03-03 14:54:48 -0500305bool DeviceRegistrationInfo::RefreshAccessToken(
306 chromeos::ErrorPtr* error) {
307 LOG(INFO) << "Refreshing access token.";
Alex Vakulenko89dde2f2015-01-07 12:05:12 -0800308 auto response = chromeos::http::PostFormDataAndBlock(GetOAuthURL("token"), {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700309 {"refresh_token", refresh_token_},
Christopher Wiley583d64b2015-03-24 14:30:17 -0700310 {"client_id", config_->client_id()},
311 {"client_secret", config_->client_secret()},
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700312 {"grant_type", "refresh_token"},
Alex Vakulenko89dde2f2015-01-07 12:05:12 -0800313 }, {}, transport_, error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700314 if (!response)
315 return false;
316
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700317 auto json = ParseOAuthResponse(response.get(), error);
318 if (!json)
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700319 return false;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700320
321 int expires_in = 0;
322 if (!json->GetString("access_token", &access_token_) ||
323 !json->GetInteger("expires_in", &expires_in) ||
324 access_token_.empty() ||
325 expires_in <= 0) {
326 LOG(ERROR) << "Access token unavailable.";
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800327 chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainOAuth2,
Alex Vakulenko5f472062014-08-14 17:54:04 -0700328 "unexpected_server_response",
329 "Access token unavailable");
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700330 return false;
331 }
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700332 access_token_expiration_ = base::Time::Now() +
333 base::TimeDelta::FromSeconds(expires_in);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700334 LOG(INFO) << "Access token is refreshed for additional " << expires_in
335 << " seconds.";
Nathan Bullockd9e0bcd2015-02-11 11:36:39 -0500336
Nathan Bullockbea91132015-02-19 09:13:33 -0500337 StartXmpp();
Nathan Bullockd9e0bcd2015-02-11 11:36:39 -0500338
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700339 return true;
340}
341
Nathan Bullockbea91132015-02-19 09:13:33 -0500342void DeviceRegistrationInfo::StartXmpp() {
343 // If no MessageLoop assume we're in unittests.
344 if (!base::MessageLoop::current()) {
345 LOG(INFO) << "No MessageLoop, not starting XMPP";
346 return;
347 }
348
349 if (!fd_watcher_.StopWatchingFileDescriptor()) {
350 LOG(WARNING) << "Failed to stop the previous watcher";
351 return;
352 }
353
354 std::unique_ptr<XmppConnection> connection(new XmppConnection());
355 if (!connection->Initialize()) {
356 LOG(WARNING) << "Failed to connect to XMPP server";
357 return;
358 }
359 xmpp_client_.reset(new XmppClient(device_robot_account_, access_token_,
360 std::move(connection)));
361 if (!base::MessageLoopForIO::current()->WatchFileDescriptor(
362 xmpp_client_->GetFileDescriptor(), true /* persistent */,
363 base::MessageLoopForIO::WATCH_READ, &fd_watcher_, this)) {
364 LOG(WARNING) << "Failed to watch XMPP file descriptor";
365 return;
366 }
Vitaly Bukab055f152015-03-12 13:41:43 -0700367
Nathan Bullockbea91132015-02-19 09:13:33 -0500368 xmpp_client_->StartStream();
369}
370
Nathan Bullockf12f7f02015-02-20 14:46:53 -0500371void DeviceRegistrationInfo::OnFileCanReadWithoutBlocking(int fd) {
Vitaly Bukab055f152015-03-12 13:41:43 -0700372 if (!xmpp_client_ || xmpp_client_->GetFileDescriptor() != fd)
373 return;
374 if (!xmpp_client_->Read()) {
375 // Authentication failed or the socket was closed.
376 if (!fd_watcher_.StopWatchingFileDescriptor())
377 LOG(WARNING) << "Failed to stop the watcher";
378 return;
Nathan Bullockf12f7f02015-02-20 14:46:53 -0500379 }
380}
381
Anton Muhind8d32162014-10-02 20:37:00 +0400382std::unique_ptr<base::DictionaryValue>
383DeviceRegistrationInfo::BuildDeviceResource(chromeos::ErrorPtr* error) {
384 std::unique_ptr<base::DictionaryValue> commands =
385 command_manager_->GetCommandDictionary().GetCommandsAsJson(true, error);
386 if (!commands)
387 return nullptr;
388
389 std::unique_ptr<base::DictionaryValue> state =
390 state_manager_->GetStateValuesAsJson(error);
391 if (!state)
392 return nullptr;
393
394 std::unique_ptr<base::DictionaryValue> resource{new base::DictionaryValue};
395 if (!device_id_.empty())
396 resource->SetString("id", device_id_);
Christopher Wiley583d64b2015-03-24 14:30:17 -0700397 resource->SetString("deviceKind", config_->device_kind());
398 resource->SetString("name", config_->name());
Anton Muhind8d32162014-10-02 20:37:00 +0400399 if (!display_name_.empty())
400 resource->SetString("displayName", display_name_);
Vitaly Bukad3eb19c2014-11-21 13:39:43 -0800401 if (!description_.empty())
402 resource->SetString("description", description_);
403 if (!location_.empty())
404 resource->SetString("location", location_);
Christopher Wiley583d64b2015-03-24 14:30:17 -0700405 if (!config_->model_id().empty())
406 resource->SetString("modelManifestId", config_->model_id());
Anton Muhind8d32162014-10-02 20:37:00 +0400407 resource->SetString("channel.supportedType", "xmpp");
408 resource->Set("commandDefs", commands.release());
409 resource->Set("state", state.release());
410
411 return resource;
412}
413
Vitaly Buka6ca3ad62015-03-11 17:03:23 -0700414std::unique_ptr<base::DictionaryValue> DeviceRegistrationInfo::GetDeviceInfo(
Alex Vakulenko5f472062014-08-14 17:54:04 -0700415 chromeos::ErrorPtr* error) {
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700416 if (!CheckRegistration(error))
Vitaly Buka6ca3ad62015-03-11 17:03:23 -0700417 return std::unique_ptr<base::DictionaryValue>();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700418
Anton Muhinac661ab2014-10-03 20:29:48 +0400419 // TODO(antonm): Switch to DoCloudRequest later.
Alex Vakulenko89dde2f2015-01-07 12:05:12 -0800420 auto response = chromeos::http::GetAndBlock(
Alex Vakulenkocca20932014-08-20 17:35:12 -0700421 GetDeviceURL(), {GetAuthorizationHeader()}, transport_, error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700422 int status_code = 0;
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700423 std::unique_ptr<base::DictionaryValue> json =
Alex Vakulenkocca20932014-08-20 17:35:12 -0700424 chromeos::http::ParseJsonResponse(response.get(), &status_code, error);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700425 if (json) {
Alex Vakulenkocca20932014-08-20 17:35:12 -0700426 if (status_code >= chromeos::http::status_code::BadRequest) {
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700427 LOG(WARNING) << "Failed to retrieve the device info. Response code = "
428 << status_code;
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700429 ParseGCDError(json.get(), error);
Vitaly Buka6ca3ad62015-03-11 17:03:23 -0700430 return std::unique_ptr<base::DictionaryValue>();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700431 }
432 }
Vitaly Buka6ca3ad62015-03-11 17:03:23 -0700433 return json;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700434}
435
Christopher Wiley583d64b2015-03-24 14:30:17 -0700436namespace {
437
438bool GetWithDefault(const std::map<std::string, std::string>& source,
439 const std::string& key,
440 const std::string& default_value,
441 std::string* output) {
442 auto it = source.find(key);
443 if (it == source.end()) {
444 *output = default_value;
445 return false;
446 }
447 *output = it->second;
448 return true;
449}
450
451} // namespace
452
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400453std::string DeviceRegistrationInfo::RegisterDevice(
Alex Vakulenkoa9044342014-08-23 19:31:27 -0700454 const std::map<std::string, std::string>& params,
Alex Vakulenko5f472062014-08-14 17:54:04 -0700455 chromeos::ErrorPtr* error) {
Nathan Bullocke4408482015-02-19 11:13:21 -0500456 std::string ticket_id;
Christopher Wiley583d64b2015-03-24 14:30:17 -0700457 if (!GetWithDefault(params, "ticket_id", "", &ticket_id)) {
458 chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainBuffet,
459 "missing_parameter",
460 "Need ticket_id parameter for RegisterDevice().");
David Zeuthend0db55c2015-02-12 14:47:35 -0500461 return std::string();
Vitaly Buka6522ab62015-02-19 10:26:31 -0800462 }
Christopher Wiley583d64b2015-03-24 14:30:17 -0700463 // These fields are optional, and will default to values from the manufacturer
464 // supplied config.
465 GetWithDefault(params, storage_keys::kDisplayName,
466 config_->default_display_name(), &display_name_);
467 GetWithDefault(params, storage_keys::kDescription,
468 config_->default_description(), &description_);
469 GetWithDefault(params, storage_keys::kLocation,
470 config_->default_location(), &location_);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700471
Anton Muhind8d32162014-10-02 20:37:00 +0400472 std::unique_ptr<base::DictionaryValue> device_draft =
473 BuildDeviceResource(error);
474 if (!device_draft)
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700475 return std::string();
476
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700477 base::DictionaryValue req_json;
Nathan Bullocke4408482015-02-19 11:13:21 -0500478 req_json.SetString("id", ticket_id);
Christopher Wiley583d64b2015-03-24 14:30:17 -0700479 req_json.SetString("oauthClientId", config_->client_id());
Anton Muhind8d32162014-10-02 20:37:00 +0400480 req_json.Set("deviceDraft", device_draft.release());
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700481
Nathan Bullocke4408482015-02-19 11:13:21 -0500482 auto url = GetServiceURL("registrationTickets/" + ticket_id,
Christopher Wiley583d64b2015-03-24 14:30:17 -0700483 {{"key", config_->api_key()}});
Anton Muhin532ff7e2014-09-29 23:21:21 +0400484 std::unique_ptr<chromeos::http::Response> response =
Alex Vakulenko89dde2f2015-01-07 12:05:12 -0800485 chromeos::http::PatchJsonAndBlock(url, &req_json, {}, transport_, error);
Anton Muhin532ff7e2014-09-29 23:21:21 +0400486 auto json_resp = chromeos::http::ParseJsonResponse(response.get(), nullptr,
487 error);
488 if (!json_resp)
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700489 return std::string();
David Zeuthen1dbad472015-02-12 15:24:21 -0500490 if (!response->IsSuccessful()) {
491 ParseGCDError(json_resp.get(), error);
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700492 return std::string();
David Zeuthen1dbad472015-02-12 15:24:21 -0500493 }
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700494
Nathan Bullocke4408482015-02-19 11:13:21 -0500495 url = GetServiceURL("registrationTickets/" + ticket_id +
Christopher Wiley583d64b2015-03-24 14:30:17 -0700496 "/finalize?key=" + config_->api_key());
Alex Vakulenko89dde2f2015-01-07 12:05:12 -0800497 response = chromeos::http::SendRequestWithNoDataAndBlock(
498 chromeos::http::request_type::kPost, url, {}, transport_, error);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700499 if (!response)
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400500 return std::string();
501 json_resp = chromeos::http::ParseJsonResponse(response.get(), nullptr, error);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700502 if (!json_resp)
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400503 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700504 if (!response->IsSuccessful()) {
505 ParseGCDError(json_resp.get(), error);
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400506 return std::string();
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700507 }
Anton Muhin532ff7e2014-09-29 23:21:21 +0400508
509 std::string auth_code;
Vitaly Buka620bd7e2015-03-16 01:07:01 -0700510 std::string device_id;
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700511 if (!json_resp->GetString("robotAccountEmail", &device_robot_account_) ||
512 !json_resp->GetString("robotAccountAuthorizationCode", &auth_code) ||
Vitaly Buka620bd7e2015-03-16 01:07:01 -0700513 !json_resp->GetString("deviceDraft.id", &device_id)) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800514 chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainGCD,
515 "unexpected_response",
Alex Vakulenko5f472062014-08-14 17:54:04 -0700516 "Device account missing in response");
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400517 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700518 }
Vitaly Buka620bd7e2015-03-16 01:07:01 -0700519 SetDeviceId(device_id);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700520
521 // Now get access_token and refresh_token
Alex Vakulenko89dde2f2015-01-07 12:05:12 -0800522 response = chromeos::http::PostFormDataAndBlock(GetOAuthURL("token"), {
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700523 {"code", auth_code},
Christopher Wiley583d64b2015-03-24 14:30:17 -0700524 {"client_id", config_->client_id()},
525 {"client_secret", config_->client_secret()},
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700526 {"redirect_uri", "oob"},
527 {"scope", "https://www.googleapis.com/auth/clouddevices"},
528 {"grant_type", "authorization_code"}
Alex Vakulenko89dde2f2015-01-07 12:05:12 -0800529 }, {}, transport_, error);
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700530 if (!response)
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400531 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700532
533 json_resp = ParseOAuthResponse(response.get(), error);
534 int expires_in = 0;
535 if (!json_resp ||
536 !json_resp->GetString("access_token", &access_token_) ||
537 !json_resp->GetString("refresh_token", &refresh_token_) ||
538 !json_resp->GetInteger("expires_in", &expires_in) ||
539 access_token_.empty() ||
540 refresh_token_.empty() ||
541 expires_in <= 0) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800542 chromeos::Error::AddTo(error, FROM_HERE,
543 kErrorDomainGCD, "unexpected_response",
Alex Vakulenko5f472062014-08-14 17:54:04 -0700544 "Device access_token missing in response");
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400545 return std::string();
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700546 }
547
548 access_token_expiration_ = base::Time::Now() +
549 base::TimeDelta::FromSeconds(expires_in);
550
551 Save();
Nathan Bullockbea91132015-02-19 09:13:33 -0500552 StartXmpp();
Christopher Wileycd419662015-02-06 17:51:43 -0800553
554 // We're going to respond with our success immediately and we'll StartDevice
555 // shortly after.
556 ScheduleStartDevice(base::TimeDelta::FromSeconds(0));
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400557 return device_id_;
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700558}
559
Anton Muhin633eded2014-10-03 20:40:10 +0400560namespace {
561
562template <class T>
563void PostToCallback(base::Callback<void(const T&)> callback,
564 std::unique_ptr<T> value) {
565 auto cb = [callback] (T* result) {
566 callback.Run(*result);
567 };
568 base::MessageLoop::current()->PostTask(
569 FROM_HERE, base::Bind(cb, base::Owned(value.release())));
570}
571
Anton Muhin0ae1fba2014-10-22 19:30:40 +0400572void PostRepeatingTask(const tracked_objects::Location& from_here,
573 base::Closure task,
574 base::TimeDelta delay) {
575 task.Run();
576 base::MessageLoop::current()->PostDelayedTask(
577 from_here, base::Bind(&PostRepeatingTask, from_here, task, delay), delay);
578}
579
Alex Vakulenko0357c032015-01-06 16:32:31 -0800580using ResponsePtr = scoped_ptr<chromeos::http::Response>;
Anton Muhin633eded2014-10-03 20:40:10 +0400581
Alex Vakulenko0357c032015-01-06 16:32:31 -0800582void SendRequestWithRetries(
Anton Muhin633eded2014-10-03 20:40:10 +0400583 const std::string& method,
584 const std::string& url,
585 const std::string& data,
586 const std::string& mime_type,
587 const chromeos::http::HeaderList& headers,
588 std::shared_ptr<chromeos::http::Transport> transport,
589 int num_retries,
Alex Vakulenko0357c032015-01-06 16:32:31 -0800590 const chromeos::http::SuccessCallback& success_callback,
591 const chromeos::http::ErrorCallback& error_callback) {
592 auto on_failure =
593 [method, url, data, mime_type, headers, transport, num_retries,
Alex Vakulenko6401d012015-01-16 07:40:43 -0800594 success_callback, error_callback](int request_id,
595 const chromeos::Error* error) {
Anton Muhin633eded2014-10-03 20:40:10 +0400596 if (num_retries > 0) {
Alex Vakulenko0357c032015-01-06 16:32:31 -0800597 SendRequestWithRetries(method, url, data, mime_type,
598 headers, transport, num_retries - 1,
599 success_callback, error_callback);
Anton Muhin633eded2014-10-03 20:40:10 +0400600 } else {
Alex Vakulenko6401d012015-01-16 07:40:43 -0800601 error_callback.Run(request_id, error);
Anton Muhin633eded2014-10-03 20:40:10 +0400602 }
603 };
604
Alex Vakulenko0357c032015-01-06 16:32:31 -0800605 auto on_success =
Alex Vakulenko6401d012015-01-16 07:40:43 -0800606 [on_failure, success_callback, error_callback](int request_id,
607 ResponsePtr response) {
Alex Vakulenko0357c032015-01-06 16:32:31 -0800608 int status_code = response->GetStatusCode();
609 if (status_code >= chromeos::http::status_code::Continue &&
610 status_code < chromeos::http::status_code::BadRequest) {
Alex Vakulenko6401d012015-01-16 07:40:43 -0800611 success_callback.Run(request_id, response.Pass());
Anton Muhin633eded2014-10-03 20:40:10 +0400612 return;
613 }
Anton Muhin633eded2014-10-03 20:40:10 +0400614
Alex Vakulenko0357c032015-01-06 16:32:31 -0800615 // TODO(antonm): Should add some useful information to error.
616 LOG(WARNING) << "Request failed. Response code = " << status_code;
Anton Muhin633eded2014-10-03 20:40:10 +0400617
Alex Vakulenko0357c032015-01-06 16:32:31 -0800618 chromeos::ErrorPtr error;
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800619 chromeos::Error::AddTo(&error, FROM_HERE, chromeos::errors::http::kDomain,
Anton Muhin233d2ee2014-10-22 15:16:24 +0400620 std::to_string(status_code),
621 response->GetStatusText());
Alex Vakulenko0357c032015-01-06 16:32:31 -0800622 if (status_code >= chromeos::http::status_code::InternalServerError &&
623 status_code < 600) {
624 // Request was valid, but server failed, retry.
625 // TODO(antonm): Implement exponential backoff.
626 // TODO(antonm): Reconsider status codes, maybe only some require
627 // retry.
628 // TODO(antonm): Support Retry-After header.
Alex Vakulenko6401d012015-01-16 07:40:43 -0800629 on_failure(request_id, error.get());
Alex Vakulenko0357c032015-01-06 16:32:31 -0800630 } else {
Alex Vakulenko6401d012015-01-16 07:40:43 -0800631 error_callback.Run(request_id, error.get());
Alex Vakulenko0357c032015-01-06 16:32:31 -0800632 }
633 };
634
635 chromeos::http::SendRequest(method, url, data.c_str(), data.size(),
636 mime_type, headers, transport,
637 base::Bind(on_success),
638 base::Bind(on_failure));
Anton Muhin633eded2014-10-03 20:40:10 +0400639}
640
641} // namespace
642
Anton Muhinac661ab2014-10-03 20:29:48 +0400643void DeviceRegistrationInfo::DoCloudRequest(
Anton Muhin233d2ee2014-10-22 15:16:24 +0400644 const std::string& method,
Anton Muhinac661ab2014-10-03 20:29:48 +0400645 const std::string& url,
646 const base::DictionaryValue* body,
Alex Vakulenko0357c032015-01-06 16:32:31 -0800647 const CloudRequestCallback& success_callback,
648 const CloudRequestErrorCallback& error_callback) {
649 // TODO(antonm): Add reauthorization on access token expiration (do not
Anton Muhinac661ab2014-10-03 20:29:48 +0400650 // forget about 5xx when fetching new access token).
651 // TODO(antonm): Add support for device removal.
652
Anton Muhinac661ab2014-10-03 20:29:48 +0400653 std::string data;
654 if (body)
655 base::JSONWriter::Write(body, &data);
656
657 const std::string mime_type{chromeos::mime::AppendParameter(
658 chromeos::mime::application::kJson,
659 chromeos::mime::parameters::kCharset,
660 "utf-8")};
661
Vitaly Bukab055f152015-03-12 13:41:43 -0700662 auto status_cb = base::Bind(&DeviceRegistrationInfo::SetRegistrationStatus,
663 weak_factory_.GetWeakPtr());
664
665 auto request_cb = [success_callback, error_callback, status_cb](
666 int request_id, ResponsePtr response) {
667 status_cb.Run(RegistrationStatus::kConnected);
Anton Muhin633eded2014-10-03 20:40:10 +0400668 chromeos::ErrorPtr error;
Anton Muhinac661ab2014-10-03 20:29:48 +0400669
Anton Muhin633eded2014-10-03 20:40:10 +0400670 std::unique_ptr<base::DictionaryValue> json_resp{
Alex Vakulenko0357c032015-01-06 16:32:31 -0800671 chromeos::http::ParseJsonResponse(response.get(), nullptr, &error)};
Anton Muhin633eded2014-10-03 20:40:10 +0400672 if (!json_resp) {
Alex Vakulenko0357c032015-01-06 16:32:31 -0800673 error_callback.Run(error.get());
Anton Muhin633eded2014-10-03 20:40:10 +0400674 return;
675 }
Anton Muhinac661ab2014-10-03 20:29:48 +0400676
Alex Vakulenko0357c032015-01-06 16:32:31 -0800677 success_callback.Run(*json_resp);
Anton Muhinac661ab2014-10-03 20:29:48 +0400678 };
Anton Muhin633eded2014-10-03 20:40:10 +0400679
Alex Vakulenko6401d012015-01-16 07:40:43 -0800680 auto error_cb =
681 [error_callback](int request_id, const chromeos::Error* error) {
682 error_callback.Run(error);
683 };
684
Anton Muhin233d2ee2014-10-22 15:16:24 +0400685 auto transport = transport_;
Vitaly Bukab055f152015-03-12 13:41:43 -0700686 auto error_callackback_with_reauthorization = base::Bind(
687 [method, url, data, mime_type, transport, request_cb, error_cb,
688 status_cb](DeviceRegistrationInfo* self, int request_id,
689 const chromeos::Error* error) {
690 status_cb.Run(RegistrationStatus::kConnecting);
691 if (error->HasError(
692 chromeos::errors::http::kDomain,
693 std::to_string(chromeos::http::status_code::Denied))) {
694 chromeos::ErrorPtr reauthorization_error;
695 // Forcibly refresh the access token.
696 if (!self->RefreshAccessToken(&reauthorization_error)) {
697 // TODO(antonm): Check if the device has been actually removed.
698 error_cb(request_id, reauthorization_error.get());
699 return;
700 }
701 SendRequestWithRetries(method, url, data, mime_type,
702 {self->GetAuthorizationHeader()}, transport, 7,
703 base::Bind(request_cb), base::Bind(error_cb));
704 } else {
705 error_cb(request_id, error);
706 }
707 },
708 base::Unretained(this));
Anton Muhin233d2ee2014-10-22 15:16:24 +0400709
Alex Vakulenko0357c032015-01-06 16:32:31 -0800710 SendRequestWithRetries(method, url,
711 data, mime_type,
712 {GetAuthorizationHeader()},
713 transport,
714 7,
715 base::Bind(request_cb),
716 error_callackback_with_reauthorization);
Anton Muhinac661ab2014-10-03 20:29:48 +0400717}
718
Christopher Wileyba983c82015-03-05 16:32:23 -0800719void DeviceRegistrationInfo::StartDevice(
720 chromeos::ErrorPtr* error,
721 const base::TimeDelta& retry_delay) {
Nathan Bullock8fb6de72015-02-24 11:58:39 -0500722 if (!HaveRegistrationCredentials(error))
Anton Muhind8d32162014-10-02 20:37:00 +0400723 return;
Christopher Wileyba983c82015-03-05 16:32:23 -0800724 auto handle_start_device_failure_cb = base::Bind(
725 &IgnoreCloudErrorWithCallback,
726 base::Bind(&DeviceRegistrationInfo::ScheduleStartDevice,
727 weak_factory_.GetWeakPtr(),
728 retry_delay));
729 // "Starting" a device just means that we:
730 // 1) push an updated device resource
731 // 2) fetch an initial set of outstanding commands
732 // 3) abort any commands that we've previously marked as "in progress"
733 // or as being in an error state.
734 // 4) Initiate periodic polling for commands.
735 auto periodically_poll_commands_cb = base::Bind(
736 &DeviceRegistrationInfo::PeriodicallyPollCommands,
737 weak_factory_.GetWeakPtr());
738 auto abort_commands_cb = base::Bind(
739 &DeviceRegistrationInfo::AbortLimboCommands,
740 weak_factory_.GetWeakPtr(),
741 periodically_poll_commands_cb);
742 auto fetch_commands_cb = base::Bind(
743 &DeviceRegistrationInfo::FetchCommands,
744 weak_factory_.GetWeakPtr(),
745 abort_commands_cb,
746 handle_start_device_failure_cb);
747 UpdateDeviceResource(fetch_commands_cb, handle_start_device_failure_cb);
Anton Muhinc635c592014-10-28 21:48:08 +0400748}
749
Anton Muhin59755522014-11-05 21:30:12 +0400750void DeviceRegistrationInfo::UpdateCommand(
751 const std::string& command_id,
752 const base::DictionaryValue& command_patch) {
753 DoCloudRequest(
754 chromeos::http::request_type::kPatch,
755 GetServiceURL("commands/" + command_id),
756 &command_patch,
757 base::Bind(&IgnoreCloudResult), base::Bind(&IgnoreCloudError));
758}
759
Christopher Wileyba983c82015-03-05 16:32:23 -0800760void DeviceRegistrationInfo::UpdateDeviceResource(
761 const base::Closure& on_success,
762 const CloudRequestErrorCallback& on_failure) {
Anton Muhind8d32162014-10-02 20:37:00 +0400763 std::unique_ptr<base::DictionaryValue> device_resource =
Anton Muhinc635c592014-10-28 21:48:08 +0400764 BuildDeviceResource(nullptr);
Anton Muhind8d32162014-10-02 20:37:00 +0400765 if (!device_resource)
766 return;
767
Anton Muhinc635c592014-10-28 21:48:08 +0400768 DoCloudRequest(
769 chromeos::http::request_type::kPut,
770 GetDeviceURL(),
771 device_resource.get(),
Christopher Wileyba983c82015-03-05 16:32:23 -0800772 base::Bind(&IgnoreCloudResultWithCallback, on_success),
773 on_failure);
Anton Muhinc635c592014-10-28 21:48:08 +0400774}
Anton Muhina34f0d92014-10-03 21:09:40 +0400775
Christopher Wileyba983c82015-03-05 16:32:23 -0800776namespace {
777
778void HandleFetchCommandsResult(
779 const base::Callback<void(const base::ListValue&)>& callback,
780 const base::DictionaryValue& json) {
781 const base::ListValue* commands{nullptr};
782 if (!json.GetList("commands", &commands)) {
783 VLOG(1) << "No commands in the response.";
784 }
785 const base::ListValue empty;
786 callback.Run(commands ? *commands : empty);
787}
788
789} // namespace
790
Anton Muhinc635c592014-10-28 21:48:08 +0400791void DeviceRegistrationInfo::FetchCommands(
Christopher Wileyba983c82015-03-05 16:32:23 -0800792 const base::Callback<void(const base::ListValue&)>& on_success,
793 const CloudRequestErrorCallback& on_failure) {
Anton Muhinc635c592014-10-28 21:48:08 +0400794 DoCloudRequest(
795 chromeos::http::request_type::kGet,
796 GetServiceURL("commands/queue", {{"deviceId", device_id_}}),
797 nullptr,
Christopher Wileyba983c82015-03-05 16:32:23 -0800798 base::Bind(&HandleFetchCommandsResult, on_success),
799 on_failure);
Anton Muhinc635c592014-10-28 21:48:08 +0400800}
Anton Muhina34f0d92014-10-03 21:09:40 +0400801
Anton Muhinc635c592014-10-28 21:48:08 +0400802void DeviceRegistrationInfo::AbortLimboCommands(
Christopher Wileyba983c82015-03-05 16:32:23 -0800803 const base::Closure& callback, const base::ListValue& commands) {
Anton Muhinc635c592014-10-28 21:48:08 +0400804 const size_t size{commands.GetSize()};
805 for (size_t i = 0; i < size; ++i) {
806 const base::DictionaryValue* command{nullptr};
807 if (!commands.GetDictionary(i, &command)) {
808 LOG(WARNING) << "No command resource at " << i;
809 continue;
Anton Muhina34f0d92014-10-03 21:09:40 +0400810 }
Anton Muhinc635c592014-10-28 21:48:08 +0400811 std::string command_state;
812 if (!command->GetString("state", &command_state)) {
813 LOG(WARNING) << "Command with no state at " << i;
814 continue;
815 }
816 if (command_state != "error" &&
817 command_state != "inProgress" &&
818 command_state != "paused") {
819 // It's not a limbo command, ignore.
820 continue;
821 }
822 std::string command_id;
823 if (!command->GetString("id", &command_id)) {
824 LOG(WARNING) << "Command with no ID at " << i;
825 continue;
826 }
Anton Muhin6d2569e2014-10-30 12:32:27 +0400827
828 std::unique_ptr<base::DictionaryValue> command_copy{command->DeepCopy()};
829 command_copy->SetString("state", "aborted");
Christopher Wileyba983c82015-03-05 16:32:23 -0800830 // TODO(wiley) We could consider handling this error case more gracefully.
Anton Muhin6d2569e2014-10-30 12:32:27 +0400831 DoCloudRequest(
832 chromeos::http::request_type::kPut,
833 GetServiceURL("commands/" + command_id),
834 command_copy.get(),
Anton Muhin5191e812014-10-30 17:49:48 +0400835 base::Bind(&IgnoreCloudResult), base::Bind(&IgnoreCloudError));
Anton Muhinc635c592014-10-28 21:48:08 +0400836 }
Anton Muhin0ae1fba2014-10-22 19:30:40 +0400837
Anton Muhinc635c592014-10-28 21:48:08 +0400838 base::MessageLoop::current()->PostTask(FROM_HERE, callback);
839}
840
841void DeviceRegistrationInfo::PeriodicallyPollCommands() {
Anton Muhind07e2062014-10-27 10:53:29 +0400842 VLOG(1) << "Poll commands";
843 PostRepeatingTask(
844 FROM_HERE,
845 base::Bind(
846 &DeviceRegistrationInfo::FetchCommands,
847 base::Unretained(this),
848 base::Bind(&DeviceRegistrationInfo::PublishCommands,
Christopher Wileyba983c82015-03-05 16:32:23 -0800849 base::Unretained(this)),
850 base::Bind(&IgnoreCloudError)),
Anton Muhind07e2062014-10-27 10:53:29 +0400851 base::TimeDelta::FromSeconds(7));
Anton Muhinb8315622014-11-20 03:17:05 +0400852 // TODO(antonm): Use better trigger: when StateManager registers new updates,
Alex Vakulenko0357c032015-01-06 16:32:31 -0800853 // it should call closure which will post a task, probably with some
854 // throttling, to publish state updates.
Anton Muhinb8315622014-11-20 03:17:05 +0400855 PostRepeatingTask(
856 FROM_HERE,
857 base::Bind(&DeviceRegistrationInfo::PublishStateUpdates,
858 base::Unretained(this)),
859 base::TimeDelta::FromSeconds(7));
Anton Muhind07e2062014-10-27 10:53:29 +0400860}
861
862void DeviceRegistrationInfo::PublishCommands(const base::ListValue& commands) {
863 const CommandDictionary& command_dictionary =
864 command_manager_->GetCommandDictionary();
865
866 const size_t size{commands.GetSize()};
867 for (size_t i = 0; i < size; ++i) {
868 const base::DictionaryValue* command{nullptr};
869 if (!commands.GetDictionary(i, &command)) {
870 LOG(WARNING) << "No command resource at " << i;
871 continue;
872 }
873
874 std::unique_ptr<CommandInstance> command_instance =
875 CommandInstance::FromJson(command, command_dictionary, nullptr);
876 if (!command_instance) {
877 LOG(WARNING) << "Failed to parse a command";
878 continue;
879 }
880
Anton Muhin5191e812014-10-30 17:49:48 +0400881 // TODO(antonm): Properly process cancellation of commands.
Anton Muhin59755522014-11-05 21:30:12 +0400882 if (!command_manager_->FindCommand(command_instance->GetID())) {
883 std::unique_ptr<CommandProxyInterface> cloud_proxy{
884 new CloudCommandProxy(command_instance.get(), this)};
885 command_instance->AddProxy(std::move(cloud_proxy));
Anton Muhin5191e812014-10-30 17:49:48 +0400886 command_manager_->AddCommand(std::move(command_instance));
Anton Muhin59755522014-11-05 21:30:12 +0400887 }
Anton Muhind07e2062014-10-27 10:53:29 +0400888 }
Anton Muhind8d32162014-10-02 20:37:00 +0400889}
890
Anton Muhinb8315622014-11-20 03:17:05 +0400891void DeviceRegistrationInfo::PublishStateUpdates() {
892 VLOG(1) << "PublishStateUpdates";
893 const std::vector<StateChange> state_changes{
894 state_manager_->GetAndClearRecordedStateChanges()};
895 if (state_changes.empty())
896 return;
897
898 std::unique_ptr<base::ListValue> patches{new base::ListValue};
899 for (const auto& state_change : state_changes) {
900 std::unique_ptr<base::DictionaryValue> patch{new base::DictionaryValue};
Anton Muhin76933fd2014-11-21 21:25:18 +0400901 patch->SetString("timeMs",
902 std::to_string(state_change.timestamp.ToJavaTime()));
Anton Muhinb8315622014-11-20 03:17:05 +0400903
904 std::unique_ptr<base::DictionaryValue> changes{new base::DictionaryValue};
905 for (const auto& pair : state_change.changed_properties) {
906 auto value = pair.second->ToJson(nullptr);
907 if (!value) {
908 return;
909 }
Alex Vakulenko61ad4db2015-01-20 10:50:04 -0800910 // The key in |pair.first| is the full property name in format
911 // "package.property_name", so must use DictionaryValue::Set() instead of
912 // DictionaryValue::SetWithoutPathExpansion to recreate the JSON
913 // property tree properly.
914 changes->Set(pair.first, value.release());
Anton Muhinb8315622014-11-20 03:17:05 +0400915 }
916 patch->Set("patch", changes.release());
917
918 patches->Append(patch.release());
919 }
920
921 base::DictionaryValue body;
Anton Muhin76933fd2014-11-21 21:25:18 +0400922 body.SetString("requestTimeMs",
923 std::to_string(base::Time::Now().ToJavaTime()));
Anton Muhinb8315622014-11-20 03:17:05 +0400924 body.Set("patches", patches.release());
925
926 DoCloudRequest(
927 chromeos::http::request_type::kPost,
928 GetDeviceURL("patchState"),
929 &body,
930 base::Bind(&IgnoreCloudResult), base::Bind(&IgnoreCloudError));
931}
932
Christopher Wileyc900e482015-02-15 15:42:04 -0800933void DeviceRegistrationInfo::SetRegistrationStatus(
934 RegistrationStatus new_status) {
935 if (new_status == registration_status_)
936 return;
937 VLOG(1) << "Changing registration status to " << StatusToString(new_status);
938 registration_status_ = new_status;
Vitaly Buka7ad8ffb2015-03-20 09:46:57 -0700939 if (!on_status_changed_.is_null())
940 on_status_changed_.Run();
Vitaly Buka620bd7e2015-03-16 01:07:01 -0700941}
942
943void DeviceRegistrationInfo::SetDeviceId(const std::string& device_id) {
944 if (device_id == device_id_)
945 return;
946 device_id_ = device_id;
Vitaly Buka7ad8ffb2015-03-20 09:46:57 -0700947 if (!on_status_changed_.is_null())
948 on_status_changed_.Run();
Christopher Wileyc900e482015-02-15 15:42:04 -0800949}
950
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700951} // namespace buffet