chromium/chrome/browser/ash/secure_channel/nearby_connection_broker_impl_unittest.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.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chrome/browser/ash/secure_channel/nearby_connection_broker_impl.h"

#include <memory>
#include <vector>

#include "ash/constants/ash_features.h"
#include "base/containers/span.h"
#include "base/files/file_util.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/timer/mock_timer.h"
#include "chrome/browser/ash/secure_channel/fake_nearby_endpoint_finder.h"
#include "chrome/browser/ash/secure_channel/util/histogram_util.h"
#include "chromeos/ash/components/multidevice/logging/logging.h"
#include "chromeos/ash/services/nearby/public/cpp/mock_nearby_connections.h"
#include "chromeos/ash/services/secure_channel/public/mojom/nearby_connector.mojom-shared.h"
#include "chromeos/ash/services/secure_channel/public/mojom/nearby_connector.mojom.h"
#include "chromeos/ash/services/secure_channel/public/mojom/secure_channel_types.mojom.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/shared_remote.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {
namespace secure_channel {
namespace {

using ::nearby::connections::mojom::BytesPayload;
using ::nearby::connections::mojom::ConnectionInfo;
using ::nearby::connections::mojom::ConnectionLifecycleListener;
using ::nearby::connections::mojom::ConnectionOptionsPtr;
using ::nearby::connections::mojom::DiscoveredEndpointInfo;
using ::nearby::connections::mojom::FilePayload;
using ::nearby::connections::mojom::Payload;
using ::nearby::connections::mojom::PayloadContent;
using ::nearby::connections::mojom::PayloadListener;
using ::nearby::connections::mojom::PayloadPtr;
using ::nearby::connections::mojom::PayloadStatus;
using ::nearby::connections::mojom::PayloadTransferUpdate;
using ::nearby::connections::mojom::PayloadTransferUpdatePtr;
using ::nearby::connections::mojom::Status;
using ::testing::_;
using ::testing::Invoke;

const char kEndpointId[] = "endpointId";

const std::vector<uint8_t>& GetEid() {
  static const std::vector<uint8_t> eid{0, 1};
  return eid;
}

const std::vector<uint8_t>& GetBluetoothAddress() {
  static const std::vector<uint8_t> address{0, 1, 2, 3, 4, 5};
  return address;
}

const std::vector<uint8_t>& GetEndpointInfo() {
  static const std::vector<uint8_t> info{6, 7, 8, 9, 10};
  return info;
}

}  // namespace

class NearbyConnectionBrokerImplTest
    : public testing::Test,
      public mojom::NearbyMessageReceiver,
      public mojom::NearbyConnectionStateListener,
      public mojom::FilePayloadListener {
 protected:
  NearbyConnectionBrokerImplTest() = default;
  ~NearbyConnectionBrokerImplTest() override = default;

  // testing::Test:
  void SetUp() override {
    auto mock_timer = std::make_unique<base::MockOneShotTimer>();
    mock_timer_ = mock_timer.get();

    broker_ = NearbyConnectionBrokerImpl::Factory::Create(
        GetBluetoothAddress(), GetEid(), &fake_endpoint_finder_,
        message_sender_.BindNewPipeAndPassReceiver(),
        file_payload_handler_.BindNewPipeAndPassReceiver(),
        message_receiver_.BindNewPipeAndPassRemote(),
        nearby_connection_state_listener_.BindNewPipeAndPassRemote(),
        mock_nearby_connections_.shared_remote(),
        base::BindOnce(&NearbyConnectionBrokerImplTest::OnConnected,
                       base::Unretained(this)),
        base::BindOnce(&NearbyConnectionBrokerImplTest::OnDisconnected,
                       base::Unretained(this)),
        std::move(mock_timer));
    EXPECT_EQ(GetBluetoothAddress(),
              fake_endpoint_finder_.remote_device_bluetooth_address());
  }

  void DiscoverEndpoint() {
    base::RunLoop run_loop;
    EXPECT_CALL(mock_nearby_connections_, RequestConnection(_, _, _, _, _, _))
        .WillOnce(Invoke(
            [&](const std::string& service_id,
                const std::vector<uint8_t>& endpoint_info,
                const std::string& endpoint_id, ConnectionOptionsPtr options,
                mojo::PendingRemote<ConnectionLifecycleListener> listener,
                NearbyConnectionsMojom::RequestConnectionCallback callback) {
              request_connection_callback_ = std::move(callback);
              connection_lifecycle_listener_.Bind(std::move(listener));
              run_loop.Quit();
            }));

    fake_endpoint_finder_.NotifyEndpointFound(
        kEndpointId,
        DiscoveredEndpointInfo::New(GetEndpointInfo(), mojom::kServiceId));

    run_loop.Run();
  }

  void FailDiscovery() {
    base::RunLoop run_loop;
    on_disconnected_closure_ = run_loop.QuitClosure();
    fake_endpoint_finder_.NotifyEndpointDiscoveryFailure(
        ::nearby::connections::mojom::Status::kAlreadyDiscovering);
    run_loop.Run();
  }

  void InvokeRequestConnectionCallback(Status status) {
    if (status != Status::kSuccess) {
      base::RunLoop run_loop;
      on_disconnected_closure_ = run_loop.QuitClosure();
      std::move(request_connection_callback_).Run(status);
      run_loop.Run();
      return;
    }

    std::move(request_connection_callback_).Run(status);

    // Ensure that callback result is received; cannot use external event
    // because the success callback only updates internal state.
    base::RunLoop().RunUntilIdle();
  }

  void NotifyConnectionInitiated() {
    base::RunLoop run_loop;
    EXPECT_CALL(mock_nearby_connections_, AcceptConnection(_, _, _, _))
        .WillOnce(Invoke(
            [&](const std::string& service_id, const std::string& endpoint_id,
                mojo::PendingRemote<PayloadListener> listener,
                NearbyConnectionsMojom::AcceptConnectionCallback callback) {
              accept_connection_callback_ = std::move(callback);
              payload_listener_.Bind(std::move(listener));
              run_loop.Quit();
            }));

    connection_lifecycle_listener_->OnConnectionInitiated(
        kEndpointId, ConnectionInfo::New());

    run_loop.Run();
  }

  void InvokeAcceptConnectionCallback(Status status) {
    if (status != Status::kSuccess) {
      base::RunLoop run_loop;
      ExpectDisconnectFromEndpoint(run_loop.QuitClosure());
      std::move(accept_connection_callback_).Run(status);
      run_loop.Run();
      return;
    }

    std::move(accept_connection_callback_).Run(status);

    // Ensure that callback result is received; cannot use external event
    // because the success callback only updates internal state.
    base::RunLoop().RunUntilIdle();
  }

  void NotifyConnectionAccepted() {
    base::RunLoop run_loop;
    on_connected_closure_ = run_loop.QuitClosure();
    connection_lifecycle_listener_->OnConnectionAccepted(kEndpointId);
    run_loop.Run();
  }

  void SetUpFullConnection() {
    DiscoverEndpoint();
    InvokeRequestConnectionCallback(Status::kSuccess);
    NotifyConnectionInitiated();
    InvokeAcceptConnectionCallback(Status::kSuccess);
    NotifyConnectionAccepted();
  }

  void InvokeDisconnectedCallback() {
    base::RunLoop disconnect_run_loop;
    on_disconnected_closure_ = disconnect_run_loop.QuitClosure();
    connection_lifecycle_listener_->OnDisconnected(kEndpointId);
    disconnect_run_loop.Run();
  }

  void SendMessage(const std::string& message, bool success) {
    base::RunLoop send_message_run_loop;
    base::RunLoop send_message_response_run_loop;

    NearbyConnectionsMojom::SendPayloadCallback send_payload_callback;
    std::string sent_message;
    EXPECT_CALL(mock_nearby_connections_, SendPayload(_, _, _, _))
        .WillOnce(
            Invoke([&](const std::string& service_id,
                       const std::vector<std::string>& endpoint_ids,
                       PayloadPtr payload,
                       NearbyConnectionsMojom::SendPayloadCallback callback) {
              send_payload_callback = std::move(callback);

              const std::vector<uint8_t>& payload_bytes =
                  payload->content->get_bytes()->bytes;
              sent_message =
                  std::string(payload_bytes.begin(), payload_bytes.end());

              send_message_run_loop.Quit();
            }));

    message_sender_->SendMessage(
        message, base::BindLambdaForTesting([&](bool did_send_succeeed) {
          EXPECT_EQ(success, did_send_succeeed);
          send_message_response_run_loop.Quit();
        }));
    send_message_run_loop.Run();

    EXPECT_EQ(message, sent_message);

    if (success) {
      std::move(send_payload_callback).Run(Status::kSuccess);
      send_message_response_run_loop.Run();
      return;
    }

    // Failure to send should disconnect the ongoing connection.
    base::RunLoop disconnect_run_loop;
    ExpectDisconnectFromEndpoint(disconnect_run_loop.QuitClosure());
    std::move(send_payload_callback).Run(Status::kError);
    send_message_response_run_loop.Run();
    disconnect_run_loop.Run();
  }

  void ReceiveMessage(const std::string& message) {
    static int64_t next_payload_id = 0;

    base::RunLoop receive_run_loop;
    on_message_received_closure_ = receive_run_loop.QuitClosure();

    std::vector<uint8_t> message_as_bytes(message.begin(), message.end());
    payload_listener_->OnPayloadReceived(
        kEndpointId, Payload::New(next_payload_id++,
                                  PayloadContent::NewBytes(
                                      BytesPayload::New(message_as_bytes))));
    receive_run_loop.Run();

    EXPECT_EQ(received_messages_.back(), message);
  }

  void ReceiveFilePayload(int64_t payload_id, const base::FilePath& file_path) {
    // Create fake file to receive.
    const std::vector<uint8_t> kFakeFileContent{0x01, 0x02, 0x03};
    base::File output_file(file_path, base::File::Flags::FLAG_CREATE_ALWAYS |
                                          base::File::Flags::FLAG_WRITE);
    output_file.WriteAndCheck(
        /*offset=*/0,
        base::make_span(kFakeFileContent.begin(), kFakeFileContent.end()));
    output_file.Flush();
    output_file.Close();
    base::File input_file(
        file_path, base::File::Flags::FLAG_OPEN | base::File::Flags::FLAG_READ);

    payload_listener_->OnPayloadReceived(
        kEndpointId,
        Payload::New(payload_id, PayloadContent::NewFile(
                                     FilePayload::New(std::move(input_file)))));
  }

  void RegisterPayloadFile(int64_t payload_id,
                           const base::FilePath& file_path,
                           bool expect_success) {
    base::File input_file(
        file_path, base::File::Flags::FLAG_OPEN | base::File::Flags::FLAG_READ);
    base::File output_file(file_path, base::File::Flags::FLAG_CREATE_ALWAYS |
                                          base::File::Flags::FLAG_WRITE);

    base::RunLoop nearby_connections_run_loop;
    base::RunLoop file_payload_handler_run_loop;

    NearbyConnectionsMojom::RegisterPayloadFileCallback
        register_payload_file_callback;
    EXPECT_CALL(mock_nearby_connections_, RegisterPayloadFile(_, _, _, _, _))
        .WillOnce(Invoke(
            [&](const std::string& service_id, int64_t payload_id,
                const base::File& input_file, const base::File& output_file,
                NearbyConnectionsMojom::RegisterPayloadFileCallback callback) {
              register_payload_file_callback = std::move(callback);
              nearby_connections_run_loop.Quit();
            }));

    file_payload_handler_->RegisterPayloadFile(
        payload_id,
        mojom::PayloadFiles::New(std::move(input_file), std::move(output_file)),
        file_payload_listener_.BindNewPipeAndPassRemote(),
        base::BindLambdaForTesting([&](bool success) {
          EXPECT_EQ(expect_success, success);
          file_payload_handler_run_loop.Quit();
        }));
    nearby_connections_run_loop.Run();

    std::move(register_payload_file_callback)
        .Run(expect_success ? Status::kSuccess : Status::kError);
    file_payload_handler_run_loop.Run();
  }

  void ReceiveFileTransferUpdate(PayloadTransferUpdatePtr update) {
    payload_listener_->OnPayloadTransferUpdate(kEndpointId, std::move(update));
    payload_listener_.FlushForTesting();
  }

  void DisconnectMojoBindings(bool expected_to_disconnect) {
    if (!expected_to_disconnect) {
      base::RunLoop disconnect_run_loop;
      on_disconnected_closure_ = disconnect_run_loop.QuitClosure();
      EXPECT_CALL(mock_nearby_connections_, DisconnectFromEndpoint(_, _, _))
          .Times(0);
      message_sender_.reset();
      file_payload_handler_.reset();
      disconnect_run_loop.Run();
      return;
    }

    base::RunLoop disconnect_from_endpoint_run_loop;
    ExpectDisconnectFromEndpoint(
        disconnect_from_endpoint_run_loop.QuitClosure());
    message_sender_.reset();
    file_payload_handler_.reset();
    disconnect_from_endpoint_run_loop.Run();
  }

  void InvokeDisconnectedFromEndpointCallback(bool success) {
    if (success) {
      std::move(disconnect_from_endpoint_callback_).Run(Status::kSuccess);
      // Ensure that callback result is received; cannot use external event
      // because the success callback only updates internal state.
      base::RunLoop().RunUntilIdle();
      return;
    }

    base::RunLoop disconnect_run_loop;
    on_disconnected_closure_ = disconnect_run_loop.QuitClosure();
    std::move(disconnect_from_endpoint_callback_).Run(Status::kError);
    disconnect_run_loop.Run();
  }

  void SimulateTimeout(bool expected_to_disconnect) {
    if (!expected_to_disconnect) {
      base::RunLoop disconnect_run_loop;
      on_disconnected_closure_ = disconnect_run_loop.QuitClosure();
      EXPECT_CALL(mock_nearby_connections_, DisconnectFromEndpoint(_, _, _))
          .Times(0);
      mock_timer_->Fire();
      disconnect_run_loop.Run();
      return;
    }

    base::RunLoop disconnect_from_endpoint_run_loop;
    ExpectDisconnectFromEndpoint(
        disconnect_from_endpoint_run_loop.QuitClosure());
    mock_timer_->Fire();
    disconnect_from_endpoint_run_loop.Run();
  }

  void ExpectDisconnectFromEndpoint(
      base::OnceClosure on_disconnect_from_endpoint_closure) {
    on_disconnect_from_endpoint_closure_ =
        std::move(on_disconnect_from_endpoint_closure);
    EXPECT_CALL(mock_nearby_connections_, DisconnectFromEndpoint(_, _, _))
        .WillOnce(Invoke(
            [&](const std::string& service_id, const std::string& endpoint_id,
                NearbyConnectionsMojom::DisconnectFromEndpointCallback
                    callback) {
              disconnect_from_endpoint_callback_ = std::move(callback);
              std::move(on_disconnect_from_endpoint_closure_).Run();
            }));
  }

  bool IsTimerRunning() const { return mock_timer_->IsRunning(); }

  mojo::Receiver<mojom::FilePayloadListener>& file_payload_listener() {
    return file_payload_listener_;
  }

  const std::vector<mojom::FileTransferUpdatePtr>& file_transfer_updates() {
    return file_transfer_updates_;
  }

  NearbyConnectionsMojom::RequestConnectionCallback
      request_connection_callback_;
  NearbyConnectionsMojom::AcceptConnectionCallback accept_connection_callback_;
  NearbyConnectionsMojom::DisconnectFromEndpointCallback
      disconnect_from_endpoint_callback_;
  base::HistogramTester histogram_tester_;

 private:
  // mojom::NearbyMessageReceiver:
  void OnMessageReceived(const std::string& message) override {
    received_messages_.push_back(message);
    std::move(on_message_received_closure_).Run();
  }

  // mojom::NearbyConnectionStateListener:
  void OnNearbyConnectionStateChanged(
      mojom::NearbyConnectionStep nearby_connection_step,
      mojom::NearbyConnectionStepResult result) override {
    nearby_connection_step_ = nearby_connection_step;
    nearby_connection_step_result_ = result;
  }

  // mojom::FilePayloadListener:
  void OnFileTransferUpdate(mojom::FileTransferUpdatePtr update) override {
    file_transfer_updates_.push_back(std::move(update));
  }

  void OnConnected() { std::move(on_connected_closure_).Run(); }

  void OnDisconnected() { std::move(on_disconnected_closure_).Run(); }

  base::test::TaskEnvironment task_environment_;
  nearby::MockNearbyConnections mock_nearby_connections_;
  FakeNearbyEndpointFinder fake_endpoint_finder_;

  mojo::Remote<mojom::NearbyMessageSender> message_sender_;
  mojo::Remote<mojom::NearbyFilePayloadHandler> file_payload_handler_;
  mojo::Receiver<mojom::NearbyMessageReceiver> message_receiver_{this};
  mojo::Receiver<mojom::NearbyConnectionStateListener>
      nearby_connection_state_listener_{this};
  mojo::Receiver<mojom::FilePayloadListener> file_payload_listener_{this};

  std::unique_ptr<NearbyConnectionBroker> broker_;

  raw_ptr<base::MockOneShotTimer> mock_timer_ = nullptr;

  base::OnceClosure on_connected_closure_;
  base::OnceClosure on_disconnected_closure_;
  base::OnceClosure on_message_received_closure_;
  base::OnceClosure on_disconnect_from_endpoint_closure_;

  mojo::Remote<ConnectionLifecycleListener> connection_lifecycle_listener_;
  mojo::Remote<PayloadListener> payload_listener_;

  std::vector<std::string> received_messages_;
  std::vector<mojom::FileTransferUpdatePtr> file_transfer_updates_;
  mojom::NearbyConnectionStep nearby_connection_step_;
  mojom::NearbyConnectionStepResult nearby_connection_step_result_;
};

TEST_F(NearbyConnectionBrokerImplTest, SendAndReceive) {
  SetUpFullConnection();
  SendMessage("test1", /*success=*/true);
  SendMessage("test2", /*success=*/true);
  ReceiveMessage("test3");
  ReceiveMessage("test4");
  DisconnectMojoBindings(/*expected_to_disconnect=*/true);
  InvokeDisconnectedFromEndpointCallback(/*success=*/true);
  InvokeDisconnectedCallback();
}

TEST_F(NearbyConnectionBrokerImplTest, FailToDisconnect) {
  SetUpFullConnection();
  DisconnectMojoBindings(/*expected_to_disconnect=*/true);
  InvokeDisconnectedFromEndpointCallback(/*success=*/false);
}

TEST_F(NearbyConnectionBrokerImplTest, FailToDisconnect_Timeout) {
  SetUpFullConnection();
  DisconnectMojoBindings(/*expected_to_disconnect=*/true);
  SimulateTimeout(/*expected_to_disconnect=*/false);
}

TEST_F(NearbyConnectionBrokerImplTest, DisconnectsUnexpectedly) {
  SetUpFullConnection();
  InvokeDisconnectedCallback();
}

TEST_F(NearbyConnectionBrokerImplTest,
       DisconnectAfterReceivingFilePayloadWhenFeatureUnsupported) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(features::kPhoneHubCameraRoll);
  SetUpFullConnection();

  base::RunLoop disconnect_from_endpoint_run_loop;
  ExpectDisconnectFromEndpoint(disconnect_from_endpoint_run_loop.QuitClosure());
  base::FilePath path;
  base::CreateTemporaryFile(&path);
  ReceiveFilePayload(/*payload_id=*/1234, path);
  disconnect_from_endpoint_run_loop.Run();

  InvokeDisconnectedFromEndpointCallback(/*success=*/true);
  InvokeDisconnectedCallback();
}

TEST_F(NearbyConnectionBrokerImplTest,
       DisconnectAfterReceivingUnregisteredFilePayload) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(features::kPhoneHubCameraRoll);
  SetUpFullConnection();

  base::RunLoop disconnect_from_endpoint_run_loop;
  ExpectDisconnectFromEndpoint(disconnect_from_endpoint_run_loop.QuitClosure());
  base::FilePath path;
  base::CreateTemporaryFile(&path);
  RegisterPayloadFile(/*payload_id=*/1234, path, /*expect_success=*/false);
  ReceiveFilePayload(/*payload_id=*/1234, path);
  disconnect_from_endpoint_run_loop.Run();

  histogram_tester_.ExpectBucketCount(
      "MultiDevice.SecureChannel.Nearby.OperationResult.RegisterPayloadFiles",
      Status::kError,
      /*expected_count=*/1);
  histogram_tester_.ExpectBucketCount(
      "MultiDevice.SecureChannel.Nearby.FileAction",
      util::FileAction::kUnexpectedFileReceived,
      /*expected_count=*/1);

  InvokeDisconnectedFromEndpointCallback(/*success=*/true);
  InvokeDisconnectedCallback();
}

TEST_F(NearbyConnectionBrokerImplTest, FileTransferUpdateForRegisteredPayload) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(features::kPhoneHubCameraRoll);
  SetUpFullConnection();

  int64_t payload_id = 1234;
  base::FilePath path;
  base::CreateTemporaryFile(&path);
  RegisterPayloadFile(payload_id, path, /*expect_success=*/true);
  ReceiveFilePayload(payload_id, path);
  ReceiveFileTransferUpdate(
      PayloadTransferUpdate::New(payload_id, PayloadStatus::kInProgress,
                                 /*total_bytes=*/1000,
                                 /*bytes_transferred=*/100));
  ReceiveFileTransferUpdate(
      PayloadTransferUpdate::New(payload_id, PayloadStatus::kSuccess,
                                 /*total_bytes=*/1000,
                                 /*bytes_transferred=*/1000));
  file_payload_listener().FlushForTesting();

  EXPECT_EQ(2u, file_transfer_updates().size());
  EXPECT_EQ(file_transfer_updates().at(0),
            mojom::FileTransferUpdate::New(
                payload_id, mojom::FileTransferStatus::kInProgress,
                /*total_bytes=*/1000,
                /*bytes_transferred=*/100));
  EXPECT_EQ(file_transfer_updates().at(1),
            mojom::FileTransferUpdate::New(payload_id,
                                           mojom::FileTransferStatus::kSuccess,
                                           /*total_bytes=*/1000,
                                           /*bytes_transferred=*/1000));
  histogram_tester_.ExpectBucketCount(
      "MultiDevice.SecureChannel.Nearby.OperationResult.RegisterPayloadFiles",
      Status::kSuccess,
      /*expected_count=*/1);
  histogram_tester_.ExpectBucketCount(
      "MultiDevice.SecureChannel.Nearby.FileAction",
      util::FileAction::kRegisteredFileReceived,
      /*expected_count=*/1);
  histogram_tester_.ExpectBucketCount(
      "MultiDevice.SecureChannel.Nearby.FileTransferResult",
      util::FileTransferResult::kFileTransferSuccess,
      /*expected_count=*/1);

  DisconnectMojoBindings(/*expected_to_disconnect=*/true);
  InvokeDisconnectedFromEndpointCallback(/*success=*/true);
  InvokeDisconnectedCallback();
}

TEST_F(NearbyConnectionBrokerImplTest, FileTransferUpdateForCompletedPayload) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(features::kPhoneHubCameraRoll);
  SetUpFullConnection();

  int64_t payload_id = 1234;
  base::FilePath path;
  base::CreateTemporaryFile(&path);
  RegisterPayloadFile(payload_id, path, /*expect_success=*/true);
  ReceiveFilePayload(payload_id, path);
  ReceiveFileTransferUpdate(
      PayloadTransferUpdate::New(payload_id, PayloadStatus::kFailure,
                                 /*total_bytes=*/1000,
                                 /*bytes_transferred=*/100));
  // This is not supposed to trigger a FileTransferUpdate callback as this
  // payload has already been completed and is now untracked.
  ReceiveFileTransferUpdate(
      PayloadTransferUpdate::New(payload_id, PayloadStatus::kInProgress,
                                 /*total_bytes=*/1000,
                                 /*bytes_transferred=*/200));
  file_payload_listener().FlushForTesting();

  EXPECT_EQ(1u, file_transfer_updates().size());
  EXPECT_EQ(file_transfer_updates().at(0),
            mojom::FileTransferUpdate::New(payload_id,
                                           mojom::FileTransferStatus::kFailure,
                                           /*total_bytes=*/1000,
                                           /*bytes_transferred=*/100));
  histogram_tester_.ExpectBucketCount(
      "MultiDevice.SecureChannel.Nearby.FileTransferResult",
      util::FileTransferResult::kFileTransferFailure,
      /*expected_count=*/1);

  DisconnectMojoBindings(/*expected_to_disconnect=*/true);
  InvokeDisconnectedFromEndpointCallback(/*success=*/true);
  InvokeDisconnectedCallback();
}

TEST_F(NearbyConnectionBrokerImplTest,
       FileTransferUpdateForUnregisteredPayload) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(features::kPhoneHubCameraRoll);
  SetUpFullConnection();

  base::FilePath path;
  base::CreateTemporaryFile(&path);
  RegisterPayloadFile(/*payload_id=*/1234, path, /*expect_success=*/true);
  ReceiveFileTransferUpdate(PayloadTransferUpdate::New(
      /*payload_id=*/5678, PayloadStatus::kInProgress,
      /*total_bytes=*/1000,
      /*bytes_transferred=*/100));
  file_payload_listener().FlushForTesting();

  EXPECT_TRUE(file_transfer_updates().empty());

  DisconnectMojoBindings(/*expected_to_disconnect=*/true);
  InvokeDisconnectedFromEndpointCallback(/*success=*/true);
  InvokeDisconnectedCallback();
}

TEST_F(NearbyConnectionBrokerImplTest, FileTransferCanceledOnDisconnect) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(features::kPhoneHubCameraRoll);
  SetUpFullConnection();

  int64_t payload_id = 1234;
  base::FilePath path;
  base::CreateTemporaryFile(&path);
  RegisterPayloadFile(payload_id, path, /*expect_success=*/true);
  ReceiveFilePayload(payload_id, path);
  ReceiveFileTransferUpdate(
      PayloadTransferUpdate::New(payload_id, PayloadStatus::kInProgress,
                                 /*total_bytes=*/1000,
                                 /*bytes_transferred=*/100));
  // Disconnect before the transfer is complete.
  InvokeDisconnectedCallback();
  file_payload_listener().FlushForTesting();

  EXPECT_EQ(2u, file_transfer_updates().size());
  EXPECT_EQ(file_transfer_updates().at(0),
            mojom::FileTransferUpdate::New(
                payload_id, mojom::FileTransferStatus::kInProgress,
                /*total_bytes=*/1000,
                /*bytes_transferred=*/100));
  EXPECT_EQ(file_transfer_updates().at(1),
            mojom::FileTransferUpdate::New(payload_id,
                                           mojom::FileTransferStatus::kCanceled,
                                           /*total_bytes=*/0,
                                           /*bytes_transferred=*/0));
  histogram_tester_.ExpectBucketCount(
      "MultiDevice.SecureChannel.Nearby.FileTransferResult",
      util::FileTransferResult::kFileTransferCanceled,
      /*expected_count=*/1);
}

TEST_F(NearbyConnectionBrokerImplTest, FileTransferCanceledOnMojoDisconnect) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(features::kPhoneHubCameraRoll);
  SetUpFullConnection();

  int64_t payload_id = 1234;
  base::FilePath path;
  base::CreateTemporaryFile(&path);
  RegisterPayloadFile(payload_id, path, /*expect_success=*/true);
  ReceiveFilePayload(payload_id, path);
  ReceiveFileTransferUpdate(
      PayloadTransferUpdate::New(payload_id, PayloadStatus::kInProgress,
                                 /*total_bytes=*/1000,
                                 /*bytes_transferred=*/100));
  // Disconnect before the transfer is complete.
  DisconnectMojoBindings(/*expected_to_disconnect=*/true);
  InvokeDisconnectedFromEndpointCallback(/*success=*/true);
  InvokeDisconnectedCallback();
  file_payload_listener().FlushForTesting();

  EXPECT_EQ(2u, file_transfer_updates().size());
  EXPECT_EQ(file_transfer_updates().at(0),
            mojom::FileTransferUpdate::New(
                payload_id, mojom::FileTransferStatus::kInProgress,
                /*total_bytes=*/1000,
                /*bytes_transferred=*/100));
  EXPECT_EQ(file_transfer_updates().at(1),
            mojom::FileTransferUpdate::New(payload_id,
                                           mojom::FileTransferStatus::kCanceled,
                                           /*total_bytes=*/0,
                                           /*bytes_transferred=*/0));
  histogram_tester_.ExpectBucketCount(
      "MultiDevice.SecureChannel.Nearby.FileTransferResult",
      util::FileTransferResult::kFileTransferCanceled,
      /*expected_count=*/1);
}

TEST_F(NearbyConnectionBrokerImplTest, FailToSend) {
  SetUpFullConnection();
  SendMessage("test", /*success=*/false);
  InvokeDisconnectedFromEndpointCallback(/*success=*/true);
  InvokeDisconnectedCallback();
}

TEST_F(NearbyConnectionBrokerImplTest, FailDiscovery) {
  FailDiscovery();
}

TEST_F(NearbyConnectionBrokerImplTest, FailDiscovery_Timeout) {
  SimulateTimeout(/*expected_to_disconnect=*/false);
}

TEST_F(NearbyConnectionBrokerImplTest, MojoDisconnectionBeforeDiscovery) {
  DisconnectMojoBindings(/*expected_to_disconnect=*/false);
}

TEST_F(NearbyConnectionBrokerImplTest, MojoDisconnectionAfterDiscovery) {
  DiscoverEndpoint();
  DisconnectMojoBindings(/*expected_to_disconnect=*/true);

  // Run callback to prevent DCHECK() crash that ensures all Mojo callbacks are
  // invoked.
  std::move(request_connection_callback_).Run(Status::kError);
}

TEST_F(NearbyConnectionBrokerImplTest,
       FailRequestingConnection_AlreadyConnectedToEndpoint) {
  DiscoverEndpoint();
  InvokeRequestConnectionCallback(Status::kAlreadyConnectedToEndpoint);
}

TEST_F(NearbyConnectionBrokerImplTest,
       FailRequestingConnection_PayloadUnknown) {
  DiscoverEndpoint();
  InvokeRequestConnectionCallback(Status::kPayloadUnknown);
}

TEST_F(NearbyConnectionBrokerImplTest,
       FailRequestingConnection_NotConnectedToEndpoint) {
  DiscoverEndpoint();
  InvokeRequestConnectionCallback(Status::kNotConnectedToEndpoint);
}

TEST_F(NearbyConnectionBrokerImplTest,
       FailRequestingConnection_AlreadyAdvertising) {
  DiscoverEndpoint();
  InvokeRequestConnectionCallback(Status::kAlreadyAdvertising);
}

TEST_F(NearbyConnectionBrokerImplTest,
       FailRequestingConnection_AlreadyHaveActiveStrategy) {
  DiscoverEndpoint();
  InvokeRequestConnectionCallback(Status::kAlreadyHaveActiveStrategy);
}

TEST_F(NearbyConnectionBrokerImplTest,
       FailRequestingConnection_AlreadyListening) {
  DiscoverEndpoint();
  InvokeRequestConnectionCallback(Status::kAlreadyListening);
}

TEST_F(NearbyConnectionBrokerImplTest, FailRequestingConnection_Unknown) {
  DiscoverEndpoint();
  InvokeRequestConnectionCallback(Status::kUnknown);
}

TEST_F(NearbyConnectionBrokerImplTest,
       FailRequestingConnection_EndpointIOError) {
  DiscoverEndpoint();
  InvokeRequestConnectionCallback(Status::kEndpointIOError);
}

TEST_F(NearbyConnectionBrokerImplTest,
       FailRequestingConnection_EndpointUnknown) {
  DiscoverEndpoint();
  InvokeRequestConnectionCallback(Status::kEndpointUnknown);
}

TEST_F(NearbyConnectionBrokerImplTest, FailRequestingConnection_BleError) {
  DiscoverEndpoint();
  InvokeRequestConnectionCallback(Status::kBleError);
}

TEST_F(NearbyConnectionBrokerImplTest,
       FailRequestingConnection_BluetoothError) {
  DiscoverEndpoint();
  InvokeRequestConnectionCallback(Status::kBluetoothError);
}

TEST_F(NearbyConnectionBrokerImplTest,
       FailRequestingConnection_OutOfOrderApiCall) {
  DiscoverEndpoint();
  InvokeRequestConnectionCallback(Status::kOutOfOrderApiCall);
}

TEST_F(NearbyConnectionBrokerImplTest, FailRequestingConnection_WifiLanError) {
  DiscoverEndpoint();
  InvokeRequestConnectionCallback(Status::kWifiLanError);
}

TEST_F(NearbyConnectionBrokerImplTest, FailRequestingConnection_Reset) {
  DiscoverEndpoint();
  InvokeRequestConnectionCallback(Status::kReset);
}

TEST_F(NearbyConnectionBrokerImplTest,
       FailRequestingConnection_NearbyConnectionTimeout) {
  DiscoverEndpoint();
  InvokeRequestConnectionCallback(Status::kTimeout);
}

TEST_F(NearbyConnectionBrokerImplTest, FailRequestingConnection_Timeout) {
  DiscoverEndpoint();
  SimulateTimeout(/*expected_to_disconnect=*/true);
  InvokeDisconnectedFromEndpointCallback(/*success=*/true);
  InvokeDisconnectedCallback();
}

TEST_F(NearbyConnectionBrokerImplTest,
       MojoDisconnectionAfterRequestConnection) {
  DiscoverEndpoint();
  InvokeRequestConnectionCallback(Status::kSuccess);
  NotifyConnectionInitiated();
  DisconnectMojoBindings(/*expected_to_disconnect=*/true);
  InvokeDisconnectedFromEndpointCallback(/*success=*/true);
  InvokeDisconnectedCallback();

  // Run callback to prevent DCHECK() crash that ensures all Mojo callbacks are
  // invoked.
  std::move(accept_connection_callback_).Run(Status::kError);
}

TEST_F(NearbyConnectionBrokerImplTest, FailAcceptingConnection) {
  DiscoverEndpoint();
  InvokeRequestConnectionCallback(Status::kSuccess);
  NotifyConnectionInitiated();
  InvokeAcceptConnectionCallback(Status::kConnectionRejected);
  InvokeDisconnectedFromEndpointCallback(/*success=*/true);
  InvokeDisconnectedCallback();
}

// Regression test for https://crbug.com/1175489.
TEST_F(NearbyConnectionBrokerImplTest, OnAcceptedBeforeAcceptCallback) {
  DiscoverEndpoint();
  InvokeRequestConnectionCallback(Status::kSuccess);
  NotifyConnectionInitiated();

  // Invoke OnConnectionAccepted() callback before the AcceptConnection()
  // call returns a value. Generally we expect AcceptConnection() to return
  // before OnConnectionAccepted(), but we've seen in practice that this is
  // sometimes not the case.
  NotifyConnectionAccepted();
  InvokeAcceptConnectionCallback(Status::kSuccess);

  // The connection is now considered complete, so there should be no further
  // timeout.
  EXPECT_FALSE(IsTimerRunning());
}

TEST_F(NearbyConnectionBrokerImplTest, FailAcceptingConnection_Timeout) {
  DiscoverEndpoint();
  InvokeRequestConnectionCallback(Status::kSuccess);
  NotifyConnectionInitiated();
  SimulateTimeout(/*expected_to_disconnect=*/true);
  InvokeDisconnectedFromEndpointCallback(/*success=*/true);
  InvokeDisconnectedCallback();
}

}  // namespace secure_channel
}  // namespace ash