blob: b16e62610bb0bdbfbd4125b87d0765c4e4bc7737 [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
Alex Vakulenko0357c032015-01-06 16:32:31 -0800572using ResponsePtr = scoped_ptr<chromeos::http::Response>;
Anton Muhin633eded2014-10-03 20:40:10 +0400573
Alex Vakulenko0357c032015-01-06 16:32:31 -0800574void SendRequestWithRetries(
Anton Muhin633eded2014-10-03 20:40:10 +0400575 const std::string& method,
576 const std::string& url,
577 const std::string& data,
578 const std::string& mime_type,
579 const chromeos::http::HeaderList& headers,
580 std::shared_ptr<chromeos::http::Transport> transport,
581 int num_retries,
Alex Vakulenko0357c032015-01-06 16:32:31 -0800582 const chromeos::http::SuccessCallback& success_callback,
583 const chromeos::http::ErrorCallback& error_callback) {
584 auto on_failure =
585 [method, url, data, mime_type, headers, transport, num_retries,
Alex Vakulenko6401d012015-01-16 07:40:43 -0800586 success_callback, error_callback](int request_id,
587 const chromeos::Error* error) {
Anton Muhin633eded2014-10-03 20:40:10 +0400588 if (num_retries > 0) {
Alex Vakulenko0357c032015-01-06 16:32:31 -0800589 SendRequestWithRetries(method, url, data, mime_type,
590 headers, transport, num_retries - 1,
591 success_callback, error_callback);
Anton Muhin633eded2014-10-03 20:40:10 +0400592 } else {
Alex Vakulenko6401d012015-01-16 07:40:43 -0800593 error_callback.Run(request_id, error);
Anton Muhin633eded2014-10-03 20:40:10 +0400594 }
595 };
596
Alex Vakulenko0357c032015-01-06 16:32:31 -0800597 auto on_success =
Alex Vakulenko6401d012015-01-16 07:40:43 -0800598 [on_failure, success_callback, error_callback](int request_id,
599 ResponsePtr response) {
Alex Vakulenko0357c032015-01-06 16:32:31 -0800600 int status_code = response->GetStatusCode();
601 if (status_code >= chromeos::http::status_code::Continue &&
602 status_code < chromeos::http::status_code::BadRequest) {
Alex Vakulenko6401d012015-01-16 07:40:43 -0800603 success_callback.Run(request_id, response.Pass());
Anton Muhin633eded2014-10-03 20:40:10 +0400604 return;
605 }
Anton Muhin633eded2014-10-03 20:40:10 +0400606
Alex Vakulenko0357c032015-01-06 16:32:31 -0800607 // TODO(antonm): Should add some useful information to error.
608 LOG(WARNING) << "Request failed. Response code = " << status_code;
Anton Muhin633eded2014-10-03 20:40:10 +0400609
Alex Vakulenko0357c032015-01-06 16:32:31 -0800610 chromeos::ErrorPtr error;
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800611 chromeos::Error::AddTo(&error, FROM_HERE, chromeos::errors::http::kDomain,
Anton Muhin233d2ee2014-10-22 15:16:24 +0400612 std::to_string(status_code),
613 response->GetStatusText());
Alex Vakulenko0357c032015-01-06 16:32:31 -0800614 if (status_code >= chromeos::http::status_code::InternalServerError &&
615 status_code < 600) {
616 // Request was valid, but server failed, retry.
617 // TODO(antonm): Implement exponential backoff.
618 // TODO(antonm): Reconsider status codes, maybe only some require
619 // retry.
620 // TODO(antonm): Support Retry-After header.
Alex Vakulenko6401d012015-01-16 07:40:43 -0800621 on_failure(request_id, error.get());
Alex Vakulenko0357c032015-01-06 16:32:31 -0800622 } else {
Alex Vakulenko6401d012015-01-16 07:40:43 -0800623 error_callback.Run(request_id, error.get());
Alex Vakulenko0357c032015-01-06 16:32:31 -0800624 }
625 };
626
627 chromeos::http::SendRequest(method, url, data.c_str(), data.size(),
628 mime_type, headers, transport,
629 base::Bind(on_success),
630 base::Bind(on_failure));
Anton Muhin633eded2014-10-03 20:40:10 +0400631}
632
633} // namespace
634
Anton Muhinac661ab2014-10-03 20:29:48 +0400635void DeviceRegistrationInfo::DoCloudRequest(
Anton Muhin233d2ee2014-10-22 15:16:24 +0400636 const std::string& method,
Anton Muhinac661ab2014-10-03 20:29:48 +0400637 const std::string& url,
638 const base::DictionaryValue* body,
Alex Vakulenko0357c032015-01-06 16:32:31 -0800639 const CloudRequestCallback& success_callback,
640 const CloudRequestErrorCallback& error_callback) {
641 // TODO(antonm): Add reauthorization on access token expiration (do not
Anton Muhinac661ab2014-10-03 20:29:48 +0400642 // forget about 5xx when fetching new access token).
643 // TODO(antonm): Add support for device removal.
644
Anton Muhinac661ab2014-10-03 20:29:48 +0400645 std::string data;
646 if (body)
647 base::JSONWriter::Write(body, &data);
648
649 const std::string mime_type{chromeos::mime::AppendParameter(
650 chromeos::mime::application::kJson,
651 chromeos::mime::parameters::kCharset,
652 "utf-8")};
653
Vitaly Bukab055f152015-03-12 13:41:43 -0700654 auto status_cb = base::Bind(&DeviceRegistrationInfo::SetRegistrationStatus,
655 weak_factory_.GetWeakPtr());
656
657 auto request_cb = [success_callback, error_callback, status_cb](
658 int request_id, ResponsePtr response) {
659 status_cb.Run(RegistrationStatus::kConnected);
Anton Muhin633eded2014-10-03 20:40:10 +0400660 chromeos::ErrorPtr error;
Anton Muhinac661ab2014-10-03 20:29:48 +0400661
Anton Muhin633eded2014-10-03 20:40:10 +0400662 std::unique_ptr<base::DictionaryValue> json_resp{
Alex Vakulenko0357c032015-01-06 16:32:31 -0800663 chromeos::http::ParseJsonResponse(response.get(), nullptr, &error)};
Anton Muhin633eded2014-10-03 20:40:10 +0400664 if (!json_resp) {
Alex Vakulenko0357c032015-01-06 16:32:31 -0800665 error_callback.Run(error.get());
Anton Muhin633eded2014-10-03 20:40:10 +0400666 return;
667 }
Anton Muhinac661ab2014-10-03 20:29:48 +0400668
Alex Vakulenko0357c032015-01-06 16:32:31 -0800669 success_callback.Run(*json_resp);
Anton Muhinac661ab2014-10-03 20:29:48 +0400670 };
Anton Muhin633eded2014-10-03 20:40:10 +0400671
Alex Vakulenko6401d012015-01-16 07:40:43 -0800672 auto error_cb =
673 [error_callback](int request_id, const chromeos::Error* error) {
674 error_callback.Run(error);
675 };
676
Anton Muhin233d2ee2014-10-22 15:16:24 +0400677 auto transport = transport_;
Vitaly Bukab055f152015-03-12 13:41:43 -0700678 auto error_callackback_with_reauthorization = base::Bind(
679 [method, url, data, mime_type, transport, request_cb, error_cb,
680 status_cb](DeviceRegistrationInfo* self, int request_id,
681 const chromeos::Error* error) {
682 status_cb.Run(RegistrationStatus::kConnecting);
683 if (error->HasError(
684 chromeos::errors::http::kDomain,
685 std::to_string(chromeos::http::status_code::Denied))) {
686 chromeos::ErrorPtr reauthorization_error;
687 // Forcibly refresh the access token.
688 if (!self->RefreshAccessToken(&reauthorization_error)) {
689 // TODO(antonm): Check if the device has been actually removed.
690 error_cb(request_id, reauthorization_error.get());
691 return;
692 }
693 SendRequestWithRetries(method, url, data, mime_type,
694 {self->GetAuthorizationHeader()}, transport, 7,
695 base::Bind(request_cb), base::Bind(error_cb));
696 } else {
697 error_cb(request_id, error);
698 }
699 },
700 base::Unretained(this));
Anton Muhin233d2ee2014-10-22 15:16:24 +0400701
Alex Vakulenko0357c032015-01-06 16:32:31 -0800702 SendRequestWithRetries(method, url,
703 data, mime_type,
704 {GetAuthorizationHeader()},
705 transport,
706 7,
707 base::Bind(request_cb),
708 error_callackback_with_reauthorization);
Anton Muhinac661ab2014-10-03 20:29:48 +0400709}
710
Christopher Wileyba983c82015-03-05 16:32:23 -0800711void DeviceRegistrationInfo::StartDevice(
712 chromeos::ErrorPtr* error,
713 const base::TimeDelta& retry_delay) {
Nathan Bullock8fb6de72015-02-24 11:58:39 -0500714 if (!HaveRegistrationCredentials(error))
Anton Muhind8d32162014-10-02 20:37:00 +0400715 return;
Christopher Wileyba983c82015-03-05 16:32:23 -0800716 auto handle_start_device_failure_cb = base::Bind(
717 &IgnoreCloudErrorWithCallback,
718 base::Bind(&DeviceRegistrationInfo::ScheduleStartDevice,
719 weak_factory_.GetWeakPtr(),
720 retry_delay));
721 // "Starting" a device just means that we:
722 // 1) push an updated device resource
723 // 2) fetch an initial set of outstanding commands
724 // 3) abort any commands that we've previously marked as "in progress"
725 // or as being in an error state.
726 // 4) Initiate periodic polling for commands.
727 auto periodically_poll_commands_cb = base::Bind(
728 &DeviceRegistrationInfo::PeriodicallyPollCommands,
729 weak_factory_.GetWeakPtr());
730 auto abort_commands_cb = base::Bind(
731 &DeviceRegistrationInfo::AbortLimboCommands,
732 weak_factory_.GetWeakPtr(),
733 periodically_poll_commands_cb);
734 auto fetch_commands_cb = base::Bind(
735 &DeviceRegistrationInfo::FetchCommands,
736 weak_factory_.GetWeakPtr(),
737 abort_commands_cb,
738 handle_start_device_failure_cb);
739 UpdateDeviceResource(fetch_commands_cb, handle_start_device_failure_cb);
Anton Muhinc635c592014-10-28 21:48:08 +0400740}
741
Anton Muhin59755522014-11-05 21:30:12 +0400742void DeviceRegistrationInfo::UpdateCommand(
743 const std::string& command_id,
744 const base::DictionaryValue& command_patch) {
745 DoCloudRequest(
746 chromeos::http::request_type::kPatch,
747 GetServiceURL("commands/" + command_id),
748 &command_patch,
749 base::Bind(&IgnoreCloudResult), base::Bind(&IgnoreCloudError));
750}
751
Christopher Wileyba983c82015-03-05 16:32:23 -0800752void DeviceRegistrationInfo::UpdateDeviceResource(
753 const base::Closure& on_success,
754 const CloudRequestErrorCallback& on_failure) {
Anton Muhind8d32162014-10-02 20:37:00 +0400755 std::unique_ptr<base::DictionaryValue> device_resource =
Anton Muhinc635c592014-10-28 21:48:08 +0400756 BuildDeviceResource(nullptr);
Anton Muhind8d32162014-10-02 20:37:00 +0400757 if (!device_resource)
758 return;
759
Anton Muhinc635c592014-10-28 21:48:08 +0400760 DoCloudRequest(
761 chromeos::http::request_type::kPut,
762 GetDeviceURL(),
763 device_resource.get(),
Christopher Wileyba983c82015-03-05 16:32:23 -0800764 base::Bind(&IgnoreCloudResultWithCallback, on_success),
765 on_failure);
Anton Muhinc635c592014-10-28 21:48:08 +0400766}
Anton Muhina34f0d92014-10-03 21:09:40 +0400767
Christopher Wileyba983c82015-03-05 16:32:23 -0800768namespace {
769
770void HandleFetchCommandsResult(
771 const base::Callback<void(const base::ListValue&)>& callback,
772 const base::DictionaryValue& json) {
773 const base::ListValue* commands{nullptr};
774 if (!json.GetList("commands", &commands)) {
775 VLOG(1) << "No commands in the response.";
776 }
777 const base::ListValue empty;
778 callback.Run(commands ? *commands : empty);
779}
780
781} // namespace
782
Anton Muhinc635c592014-10-28 21:48:08 +0400783void DeviceRegistrationInfo::FetchCommands(
Christopher Wileyba983c82015-03-05 16:32:23 -0800784 const base::Callback<void(const base::ListValue&)>& on_success,
785 const CloudRequestErrorCallback& on_failure) {
Anton Muhinc635c592014-10-28 21:48:08 +0400786 DoCloudRequest(
787 chromeos::http::request_type::kGet,
788 GetServiceURL("commands/queue", {{"deviceId", device_id_}}),
789 nullptr,
Christopher Wileyba983c82015-03-05 16:32:23 -0800790 base::Bind(&HandleFetchCommandsResult, on_success),
791 on_failure);
Anton Muhinc635c592014-10-28 21:48:08 +0400792}
Anton Muhina34f0d92014-10-03 21:09:40 +0400793
Anton Muhinc635c592014-10-28 21:48:08 +0400794void DeviceRegistrationInfo::AbortLimboCommands(
Christopher Wileyba983c82015-03-05 16:32:23 -0800795 const base::Closure& callback, const base::ListValue& commands) {
Anton Muhinc635c592014-10-28 21:48:08 +0400796 const size_t size{commands.GetSize()};
797 for (size_t i = 0; i < size; ++i) {
798 const base::DictionaryValue* command{nullptr};
799 if (!commands.GetDictionary(i, &command)) {
800 LOG(WARNING) << "No command resource at " << i;
801 continue;
Anton Muhina34f0d92014-10-03 21:09:40 +0400802 }
Anton Muhinc635c592014-10-28 21:48:08 +0400803 std::string command_state;
804 if (!command->GetString("state", &command_state)) {
805 LOG(WARNING) << "Command with no state at " << i;
806 continue;
807 }
808 if (command_state != "error" &&
809 command_state != "inProgress" &&
810 command_state != "paused") {
811 // It's not a limbo command, ignore.
812 continue;
813 }
814 std::string command_id;
815 if (!command->GetString("id", &command_id)) {
816 LOG(WARNING) << "Command with no ID at " << i;
817 continue;
818 }
Anton Muhin6d2569e2014-10-30 12:32:27 +0400819
820 std::unique_ptr<base::DictionaryValue> command_copy{command->DeepCopy()};
821 command_copy->SetString("state", "aborted");
Christopher Wileyba983c82015-03-05 16:32:23 -0800822 // TODO(wiley) We could consider handling this error case more gracefully.
Anton Muhin6d2569e2014-10-30 12:32:27 +0400823 DoCloudRequest(
824 chromeos::http::request_type::kPut,
825 GetServiceURL("commands/" + command_id),
826 command_copy.get(),
Anton Muhin5191e812014-10-30 17:49:48 +0400827 base::Bind(&IgnoreCloudResult), base::Bind(&IgnoreCloudError));
Anton Muhinc635c592014-10-28 21:48:08 +0400828 }
Anton Muhin0ae1fba2014-10-22 19:30:40 +0400829
Anton Muhinc635c592014-10-28 21:48:08 +0400830 base::MessageLoop::current()->PostTask(FROM_HERE, callback);
831}
832
833void DeviceRegistrationInfo::PeriodicallyPollCommands() {
Anton Muhind07e2062014-10-27 10:53:29 +0400834 VLOG(1) << "Poll commands";
Christopher Wiley34eae042015-03-18 10:25:08 -0700835 command_poll_timer_.Start(
Anton Muhind07e2062014-10-27 10:53:29 +0400836 FROM_HERE,
Christopher Wiley34eae042015-03-18 10:25:08 -0700837 base::TimeDelta::FromMilliseconds(config_->polling_period_ms()),
838 base::Bind(&DeviceRegistrationInfo::FetchCommands,
839 base::Unretained(this),
840 base::Bind(&DeviceRegistrationInfo::PublishCommands,
841 base::Unretained(this)),
842 base::Bind(&IgnoreCloudError)));
Anton Muhinb8315622014-11-20 03:17:05 +0400843 // TODO(antonm): Use better trigger: when StateManager registers new updates,
Alex Vakulenko0357c032015-01-06 16:32:31 -0800844 // it should call closure which will post a task, probably with some
845 // throttling, to publish state updates.
Christopher Wiley34eae042015-03-18 10:25:08 -0700846 state_push_timer_.Start(
Anton Muhinb8315622014-11-20 03:17:05 +0400847 FROM_HERE,
Christopher Wiley34eae042015-03-18 10:25:08 -0700848 base::TimeDelta::FromMilliseconds(config_->polling_period_ms()),
Anton Muhinb8315622014-11-20 03:17:05 +0400849 base::Bind(&DeviceRegistrationInfo::PublishStateUpdates,
Christopher Wiley34eae042015-03-18 10:25:08 -0700850 base::Unretained(this)));
Anton Muhind07e2062014-10-27 10:53:29 +0400851}
852
853void DeviceRegistrationInfo::PublishCommands(const base::ListValue& commands) {
854 const CommandDictionary& command_dictionary =
855 command_manager_->GetCommandDictionary();
856
857 const size_t size{commands.GetSize()};
858 for (size_t i = 0; i < size; ++i) {
859 const base::DictionaryValue* command{nullptr};
860 if (!commands.GetDictionary(i, &command)) {
861 LOG(WARNING) << "No command resource at " << i;
862 continue;
863 }
864
865 std::unique_ptr<CommandInstance> command_instance =
866 CommandInstance::FromJson(command, command_dictionary, nullptr);
867 if (!command_instance) {
868 LOG(WARNING) << "Failed to parse a command";
869 continue;
870 }
871
Anton Muhin5191e812014-10-30 17:49:48 +0400872 // TODO(antonm): Properly process cancellation of commands.
Anton Muhin59755522014-11-05 21:30:12 +0400873 if (!command_manager_->FindCommand(command_instance->GetID())) {
874 std::unique_ptr<CommandProxyInterface> cloud_proxy{
875 new CloudCommandProxy(command_instance.get(), this)};
876 command_instance->AddProxy(std::move(cloud_proxy));
Anton Muhin5191e812014-10-30 17:49:48 +0400877 command_manager_->AddCommand(std::move(command_instance));
Anton Muhin59755522014-11-05 21:30:12 +0400878 }
Anton Muhind07e2062014-10-27 10:53:29 +0400879 }
Anton Muhind8d32162014-10-02 20:37:00 +0400880}
881
Anton Muhinb8315622014-11-20 03:17:05 +0400882void DeviceRegistrationInfo::PublishStateUpdates() {
883 VLOG(1) << "PublishStateUpdates";
884 const std::vector<StateChange> state_changes{
885 state_manager_->GetAndClearRecordedStateChanges()};
886 if (state_changes.empty())
887 return;
888
889 std::unique_ptr<base::ListValue> patches{new base::ListValue};
890 for (const auto& state_change : state_changes) {
891 std::unique_ptr<base::DictionaryValue> patch{new base::DictionaryValue};
Anton Muhin76933fd2014-11-21 21:25:18 +0400892 patch->SetString("timeMs",
893 std::to_string(state_change.timestamp.ToJavaTime()));
Anton Muhinb8315622014-11-20 03:17:05 +0400894
895 std::unique_ptr<base::DictionaryValue> changes{new base::DictionaryValue};
896 for (const auto& pair : state_change.changed_properties) {
897 auto value = pair.second->ToJson(nullptr);
898 if (!value) {
899 return;
900 }
Alex Vakulenko61ad4db2015-01-20 10:50:04 -0800901 // The key in |pair.first| is the full property name in format
902 // "package.property_name", so must use DictionaryValue::Set() instead of
903 // DictionaryValue::SetWithoutPathExpansion to recreate the JSON
904 // property tree properly.
905 changes->Set(pair.first, value.release());
Anton Muhinb8315622014-11-20 03:17:05 +0400906 }
907 patch->Set("patch", changes.release());
908
909 patches->Append(patch.release());
910 }
911
912 base::DictionaryValue body;
Anton Muhin76933fd2014-11-21 21:25:18 +0400913 body.SetString("requestTimeMs",
914 std::to_string(base::Time::Now().ToJavaTime()));
Anton Muhinb8315622014-11-20 03:17:05 +0400915 body.Set("patches", patches.release());
916
917 DoCloudRequest(
918 chromeos::http::request_type::kPost,
919 GetDeviceURL("patchState"),
920 &body,
921 base::Bind(&IgnoreCloudResult), base::Bind(&IgnoreCloudError));
922}
923
Christopher Wileyc900e482015-02-15 15:42:04 -0800924void DeviceRegistrationInfo::SetRegistrationStatus(
925 RegistrationStatus new_status) {
926 if (new_status == registration_status_)
927 return;
928 VLOG(1) << "Changing registration status to " << StatusToString(new_status);
929 registration_status_ = new_status;
Vitaly Buka7ad8ffb2015-03-20 09:46:57 -0700930 if (!on_status_changed_.is_null())
931 on_status_changed_.Run();
Vitaly Buka620bd7e2015-03-16 01:07:01 -0700932}
933
934void DeviceRegistrationInfo::SetDeviceId(const std::string& device_id) {
935 if (device_id == device_id_)
936 return;
937 device_id_ = device_id;
Vitaly Buka7ad8ffb2015-03-20 09:46:57 -0700938 if (!on_status_changed_.is_null())
939 on_status_changed_.Run();
Christopher Wileyc900e482015-02-15 15:42:04 -0800940}
941
Alex Vakulenko3cb466c2014-04-15 11:36:32 -0700942} // namespace buffet