// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/nearby_sharing/instantmessaging/send_message_express.h"
#include <optional>
#include <sstream>
#include "base/metrics/histogram_functions.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/nearby_sharing/instantmessaging/constants.h"
#include "chrome/browser/nearby_sharing/instantmessaging/proto/instantmessaging.pb.h"
#include "chrome/browser/nearby_sharing/instantmessaging/token_fetcher.h"
#include "chromeos/ash/components/nearby/common/client/nearby_http_result.h"
#include "components/cross_device/logging/logging.h"
#include "net/base/load_flags.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "url/gurl.h"
namespace {
// 256 KB as max response size.
constexpr int kMaxSendResponseSize = 256;
// Timeout for network calls to instantmessaging servers.
const base::TimeDelta kNetworkTimeout = base::Milliseconds(2500);
const net::NetworkTrafficAnnotationTag kTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("send_message_express", R"(
semantics {
sender: "SendMessageExpress"
description:
"Sends a message to another device via a Gaia authenticated Google"
" messaging backend."
trigger:
"User uses any Chrome cross-device sharing feature and selects a"
" peer device to send the data to."
data: "WebRTC session description protocol messages are exchanged "
"between devices to set up a peer to peer connection as documented "
"in https://tools.ietf.org/html/rfc4566 and "
"https://www.w3.org/TR/webrtc/#session-description-model. No user "
"data is sent in the request."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: NO
setting:
"This feature is only enabled for signed-in users who enable "
"Nearby sharing or Phone Hub."
chrome_policy {
NearbyShareAllowed {
policy_options {mode: MANDATORY}
NearbyShareAllowed: false
},
PhoneHubAllowed {
policy_options {mode: MANDATORY}
PhoneHubAllowed: false
}
}
})");
void LogSendResult(bool success,
const ash::nearby::NearbyHttpStatus& http_status,
const std::string& request_id) {
std::stringstream ss;
ss << "Instant messaging send express " << (success ? "succeeded" : "failed")
<< " for request " << request_id << ". HTTP status: " << http_status;
if (success) {
CD_LOG(VERBOSE, Feature::NS) << ss.str();
} else {
CD_LOG(ERROR, Feature::NS) << ss.str();
}
base::UmaHistogramBoolean(
"Nearby.Connections.InstantMessaging.SendExpress.Result", success);
if (!success) {
base::UmaHistogramSparse(
"Nearby.Connections.InstantMessaging.SendExpress.Result.FailureReason",
http_status.GetResultCodeForMetrics());
}
}
} // namespace
SendMessageExpress::SendMessageExpress(
signin::IdentityManager* identity_manager,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
: token_fetcher_(identity_manager),
url_loader_factory_(std::move(url_loader_factory)) {}
SendMessageExpress::~SendMessageExpress() = default;
void SendMessageExpress::SendMessage(
const chrome_browser_nearby_sharing_instantmessaging::
SendMessageExpressRequest& request,
SuccessCallback callback) {
token_fetcher_.GetAccessToken(base::BindOnce(
&SendMessageExpress::DoSendMessage, weak_ptr_factory_.GetWeakPtr(),
request, std::move(callback)));
}
void SendMessageExpress::DoSendMessage(
const chrome_browser_nearby_sharing_instantmessaging::
SendMessageExpressRequest& request,
SuccessCallback callback,
const std::string& oauth_token) {
base::UmaHistogramBoolean(
"Nearby.Connections.InstantMessaging.SendExpress.OAuthTokenFetchResult",
!oauth_token.empty());
if (oauth_token.empty()) {
CD_LOG(ERROR, Feature::NS) << __func__ << ": Failed to fetch OAuth token.";
std::move(callback).Run(false);
// NOTE: |this| might be destroyed here after running the callback
return;
}
std::string request_id = request.header().request_id();
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = GURL(kInstantMessagingSendMessageAPI);
resource_request->load_flags =
net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE;
resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
resource_request->method = net::HttpRequestHeaders::kPostMethod;
resource_request->headers.AddHeaderFromString(
base::StringPrintf(kAuthorizationHeaderFormat, oauth_token.c_str()));
std::unique_ptr<network::SimpleURLLoader> send_url_loader =
network::SimpleURLLoader::Create(std::move(resource_request),
kTrafficAnnotation);
auto* const send_url_loader_ptr = send_url_loader.get();
send_url_loader->SetTimeoutDuration(kNetworkTimeout);
send_url_loader->AttachStringForUpload(request.SerializeAsString(),
"application/x-protobuf");
send_url_loader_ptr->DownloadToString(
url_loader_factory_.get(),
base::BindOnce(&SendMessageExpress::OnSendMessageResponse,
weak_ptr_factory_.GetWeakPtr(), request_id,
std::move(send_url_loader), std::move(callback)),
kMaxSendResponseSize);
}
void SendMessageExpress::OnSendMessageResponse(
const std::string& request_id,
std::unique_ptr<network::SimpleURLLoader> url_loader,
SuccessCallback callback,
std::unique_ptr<std::string> response_body) {
ash::nearby::NearbyHttpStatus http_status(url_loader->NetError(),
url_loader->ResponseInfo());
bool success =
http_status.IsSuccess() && response_body && !response_body->empty();
LogSendResult(success, http_status, request_id);
std::move(callback).Run(success);
// NOTE: |this| might be destroyed here after running the callback
}