blob: 0120d08c4546d50f664826815e473b3947e86f21 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/host/gcd_rest_client.h"
#include <stdint.h>
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/json/json_writer.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/default_clock.h"
#include "base/values.h"
#include "remoting/base/logging.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
namespace remoting {
GcdRestClient::GcdRestClient(
const std::string& gcd_base_url,
const std::string& gcd_device_id,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
OAuthTokenGetter* token_getter)
: gcd_base_url_(gcd_base_url),
gcd_device_id_(gcd_device_id),
url_loader_factory_(url_loader_factory),
token_getter_(token_getter),
clock_(base::DefaultClock::GetInstance()) {}
GcdRestClient::~GcdRestClient() = default;
void GcdRestClient::PatchState(
std::unique_ptr<base::DictionaryValue> patch_details,
const GcdRestClient::ResultCallback& callback) {
DCHECK(!HasPendingRequest());
// Construct a status update message in the format GCD expects. The
// message looks like this, where "..." is filled in from
// |patch_details|:
//
// {
// requestTimeMs: T,
// patches: [{
// timeMs: T,
// patch: {...}
// }]
// }
//
// Note that |now| is deliberately using a double to hold an integer
// value because |DictionaryValue| doesn't support int64_t values, and
// GCD doesn't accept fractional values.
double now = clock_->Now().ToJavaTime();
std::unique_ptr<base::DictionaryValue> patch_dict(new base::DictionaryValue);
patch_dict->SetDouble("requestTimeMs", now);
std::unique_ptr<base::ListValue> patch_list(new base::ListValue);
std::unique_ptr<base::DictionaryValue> patch_item(new base::DictionaryValue);
patch_item->Set("patch", std::move(patch_details));
patch_item->SetDouble("timeMs", now);
patch_list->Append(std::move(patch_item));
patch_dict->Set("patches", std::move(patch_list));
// Stringify the message.
if (!base::JSONWriter::Write(*patch_dict, &patch_string_)) {
LOG(ERROR) << "Error building GCD device state patch.";
callback.Run(OTHER_ERROR);
return;
}
DLOG(INFO) << "sending state patch: " << patch_string_;
std::string url =
gcd_base_url_ + "/devices/" + gcd_device_id_ + "/patchState";
// Prepare an HTTP request to issue once an auth token is available.
callback_ = callback;
resource_request_ = std::make_unique<network::ResourceRequest>();
resource_request_->url = GURL(url);
resource_request_->method = "POST";
token_getter_->CallWithToken(
base::Bind(&GcdRestClient::OnTokenReceived, base::Unretained(this)));
}
void GcdRestClient::SetClockForTest(base::Clock* clock) {
clock_ = clock;
}
void GcdRestClient::OnTokenReceived(OAuthTokenGetter::Status status,
const std::string& user_email,
const std::string& access_token) {
DCHECK(HasPendingRequest());
if (status != OAuthTokenGetter::SUCCESS) {
LOG(ERROR) << "Error getting OAuth token for GCD request: "
<< resource_request_->url;
if (status == OAuthTokenGetter::NETWORK_ERROR) {
FinishCurrentRequest(NETWORK_ERROR);
} else {
FinishCurrentRequest(OTHER_ERROR);
}
return;
}
resource_request_->headers.SetHeader("Authorization",
std::string("Bearer ") + access_token);
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("gcd_rest_client",
R"(
semantics {
sender: "Gcd Rest Client"
description: "Alternative signaling mechanism for Chrome Remote "
"Desktop."
trigger:
"The GCD code was added for an investigation about alternative "
"signaling mechanisms, but is not being used in production."
data: "No user data."
destination: OTHER
destination_other:
"The Chrome Remote Desktop client/host the user is connecting to."
}
policy {
cookies_allowed: NO
setting:
"This request cannot be stopped in settings, but will not be sent "
"if user does not use Chrome Remote Desktop."
policy_exception_justification:
"Not implemented."
})");
url_loader_ = network::SimpleURLLoader::Create(std::move(resource_request_),
traffic_annotation);
url_loader_->AttachStringForUpload(patch_string_, "application/json");
url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
url_loader_factory_.get(),
base::BindOnce(&GcdRestClient::OnURLLoadComplete,
base::Unretained(this)));
}
void GcdRestClient::FinishCurrentRequest(Result result) {
DCHECK(HasPendingRequest());
resource_request_.reset();
url_loader_.reset();
base::ResetAndReturn(&callback_).Run(result);
}
void GcdRestClient::OnURLLoadComplete(
std::unique_ptr<std::string> response_body) {
DCHECK(HasPendingRequest());
const GURL& request_url = url_loader_->GetFinalURL();
Result status = !!response_body ? SUCCESS : OTHER_ERROR;
int response_code = -1;
if (url_loader_->ResponseInfo() && url_loader_->ResponseInfo()->headers) {
response_code = url_loader_->ResponseInfo()->headers->response_code();
}
if (status == SUCCESS) {
DCHECK(response_code == -1 ||
(response_code >= 200 && response_code < 300));
DLOG(INFO) << "GCD request succeeded:" << request_url;
} else if (response_code == 404) {
LOG(WARNING) << "Host not found (" << response_code
<< ") loading URL: " << request_url;
status = NO_SUCH_HOST;
} else if (response_code == -1) {
LOG(ERROR) << "Network error (" << response_code
<< ") loading URL: " << request_url;
status = NETWORK_ERROR;
} else {
LOG(ERROR) << "Error (" << response_code
<< ") loading URL: " << request_url;
}
FinishCurrentRequest(status);
}
} // namespace remoting