chromium/chrome/browser/nearby_sharing/instantmessaging/send_message_express.cc

// 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
}