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

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

#include <memory>
#include <optional>
#include <vector>

#include "base/containers/flat_map.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "chrome/browser/ash/secure_channel/fake_nearby_connection_broker.h"
#include "chrome/browser/ash/secure_channel/fake_nearby_endpoint_finder.h"
#include "chrome/browser/ash/secure_channel/nearby_connection_broker_impl.h"
#include "chrome/browser/ash/secure_channel/nearby_endpoint_finder_impl.h"
#include "chromeos/ash/services/nearby/public/cpp/fake_nearby_process_manager.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 "mojo/public/cpp/bindings/remote.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {
namespace secure_channel {
namespace {

int g_next_message_receiver_id = 0;

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

std::vector<uint8_t> GetBluetoothAddress(uint8_t repeated_address_value) {
  return std::vector<uint8_t>(6u, repeated_address_value);
}

class FakeEndpointFinderFactory : public NearbyEndpointFinderImpl::Factory {
 public:
  FakeEndpointFinderFactory() = default;
  ~FakeEndpointFinderFactory() override = default;

 private:
  // NearbyEndpointFinderImpl::Factory:
  std::unique_ptr<NearbyEndpointFinder> CreateInstance(
      const mojo::SharedRemote<::nearby::connections::mojom::NearbyConnections>&
          nearby_connections) override {
    return std::make_unique<FakeNearbyEndpointFinder>();
  }
};

class FakeConnectionBrokerFactory : public NearbyConnectionBrokerImpl::Factory {
 public:
  FakeConnectionBrokerFactory() = default;
  ~FakeConnectionBrokerFactory() override = default;

  FakeNearbyConnectionBroker* last_created() { return last_created_; }

 private:
  // NearbyConnectionBrokerImpl::Factory:
  std::unique_ptr<NearbyConnectionBroker> CreateInstance(
      const std::vector<uint8_t>& bluetooth_public_address,
      NearbyEndpointFinder* endpoint_finder,
      mojo::PendingReceiver<mojom::NearbyMessageSender> message_sender_receiver,
      mojo::PendingReceiver<mojom::NearbyFilePayloadHandler>
          file_payload_handler_receiver,
      mojo::PendingRemote<mojom::NearbyMessageReceiver> message_receiver_remote,
      mojo::PendingRemote<mojom::NearbyConnectionStateListener>
          nearby_connection_state_listener,
      const mojo::SharedRemote<::nearby::connections::mojom::NearbyConnections>&
          nearby_connections,
      base::OnceClosure on_connected_callback,
      base::OnceClosure on_disconnected_callback,
      std::unique_ptr<base::OneShotTimer> timer) override {
    auto instance = std::make_unique<FakeNearbyConnectionBroker>(
        bluetooth_public_address, std::move(message_sender_receiver),
        std::move(file_payload_handler_receiver),
        std::move(message_receiver_remote),
        std::move(nearby_connection_state_listener),
        std::move(on_connected_callback), std::move(on_disconnected_callback));
    last_created_ = instance.get();
    return instance;
  }

  raw_ptr<FakeNearbyConnectionBroker, DanglingUntriaged> last_created_ =
      nullptr;
};

class FakeMessageReceiver : public mojom::NearbyMessageReceiver {
 public:
  FakeMessageReceiver() = default;
  ~FakeMessageReceiver() override = default;

  int id() const { return id_; }
  const std::vector<std::string>& received_messages() const {
    return received_messages_;
  }

  mojo::PendingRemote<mojom::NearbyMessageReceiver> GeneratePendingRemote() {
    return receiver_.BindNewPipeAndPassRemote();
  }

  void set_on_message_received(base::OnceClosure on_message_received) {
    on_message_received_ = std::move(on_message_received);
  }

  void SetMojoDisconnectHandler(base::OnceClosure on_disconnected) {
    receiver_.set_disconnect_handler(std::move(on_disconnected));
  }

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

  int id_ = g_next_message_receiver_id++;
  mojo::Receiver<mojom::NearbyMessageReceiver> receiver_{this};
  base::OnceClosure on_message_received_;
  std::vector<std::string> received_messages_;
};

class FakeNearbyConnectionStateListener
    : public mojom::NearbyConnectionStateListener {
 public:
  FakeNearbyConnectionStateListener() = default;
  ~FakeNearbyConnectionStateListener() override = default;

  mojo::PendingRemote<mojom::NearbyConnectionStateListener>
  GeneratePendingRemote() {
    return receiver_.BindNewPipeAndPassRemote();
  }

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

  mojo::Receiver<mojom::NearbyConnectionStateListener> receiver_{this};
  mojom::NearbyConnectionStep nearby_connection_step_;
  mojom::NearbyConnectionStepResult nearby_connection_step_result_;
};

}  // namespace

class NearbyConnectorImplTest : public testing::Test {
 protected:
  NearbyConnectorImplTest() = default;
  ~NearbyConnectorImplTest() override = default;

  // testing::Test:
  void SetUp() override {
    NearbyConnectionBrokerImpl::Factory::SetFactoryForTesting(
        &fake_connection_broker_factory_);
    NearbyEndpointFinderImpl::Factory::SetFactoryForTesting(
        &fake_endpoint_finder_factory_);

    connector_ =
        std::make_unique<NearbyConnectorImpl>(&fake_nearby_process_manager_);
  }

  void TearDown() override {
    NearbyConnectionBrokerImpl::Factory::SetFactoryForTesting(nullptr);
    NearbyEndpointFinderImpl::Factory::SetFactoryForTesting(nullptr);
  }

  void Connect(
      FakeMessageReceiver* fake_message_receiver,
      FakeNearbyConnectionStateListener* fake_nearby_connection_state_listener,
      const std::vector<uint8_t>& address) {
    connector_->Connect(
        address, GetEid(), fake_message_receiver->GeneratePendingRemote(),
        fake_nearby_connection_state_listener->GeneratePendingRemote(),
        base::BindOnce(&NearbyConnectorImplTest::OnConnectResult,
                       base::Unretained(this), fake_message_receiver->id()));
  }

  // Invoked when OnConnectResult() is called.
  base::OnceClosure on_connect_;

  // Keyed by FakeMessageReceiver::id().
  base::flat_map<int, mojo::Remote<mojom::NearbyMessageSender>>
      id_to_remote_map_;

  FakeConnectionBrokerFactory fake_connection_broker_factory_;
  nearby::FakeNearbyProcessManager fake_nearby_process_manager_;

 private:
  void OnConnectResult(int id,
                       mojo::PendingRemote<mojom::NearbyMessageSender>
                           message_sender_pending_remote,
                       mojo::PendingRemote<mojom::NearbyFilePayloadHandler>
                           file_payload_handler_remote) {
    if (!message_sender_pending_remote)
      id_to_remote_map_[id] = mojo::Remote<mojom::NearbyMessageSender>();
    else
      id_to_remote_map_[id].Bind(std::move(message_sender_pending_remote));

    std::move(on_connect_).Run();
  }

  base::test::TaskEnvironment task_environment_;

  FakeEndpointFinderFactory fake_endpoint_finder_factory_;

  std::unique_ptr<NearbyConnector> connector_;
};

TEST_F(NearbyConnectorImplTest, ConnectAndTransferMessages) {
  // Attempt connection.
  FakeMessageReceiver receiver;
  FakeNearbyConnectionStateListener nearby_connection_state_listener;
  Connect(&receiver, &nearby_connection_state_listener,
          GetBluetoothAddress(/*repeated_address_value=*/1u));
  FakeNearbyConnectionBroker* broker =
      fake_connection_broker_factory_.last_created();
  EXPECT_EQ(1u, fake_nearby_process_manager_.GetNumActiveReferences());

  // Complete connection.
  base::RunLoop connect_run_loop;
  on_connect_ = connect_run_loop.QuitClosure();
  broker->NotifyConnected();
  connect_run_loop.Run();

  // Send a message.
  base::RunLoop send_run_loop;
  id_to_remote_map_[receiver.id()]->SendMessage(
      "hi", base::BindLambdaForTesting([&](bool success) {
        EXPECT_TRUE(success);
        send_run_loop.Quit();
      }));
  send_run_loop.Run();
  EXPECT_EQ("hi", broker->sent_messages()[0]);

  // Receive a message.
  base::RunLoop receive_run_loop;
  receiver.set_on_message_received(receive_run_loop.QuitClosure());
  broker->NotifyMessageReceived("bye");
  receive_run_loop.Run();
  EXPECT_EQ("bye", receiver.received_messages()[0]);

  // Disconnect.
  base::RunLoop disconnect_run_loop;
  receiver.SetMojoDisconnectHandler(disconnect_run_loop.QuitClosure());
  broker->InvokeDisconnectedCallback();
  disconnect_run_loop.Run();
  EXPECT_EQ(0u, fake_nearby_process_manager_.GetNumActiveReferences());
}

TEST_F(NearbyConnectorImplTest, TwoConnections) {
  // Attempt connection 1.
  FakeMessageReceiver receiver1;
  FakeNearbyConnectionStateListener nearby_connection_state_listener1;
  Connect(&receiver1, &nearby_connection_state_listener1,
          GetBluetoothAddress(/*repeated_address_value=*/1u));
  FakeNearbyConnectionBroker* broker1 =
      fake_connection_broker_factory_.last_created();
  EXPECT_EQ(1u, fake_nearby_process_manager_.GetNumActiveReferences());

  // Attempt connection 2 before connection 1 has completed. No new broker
  // should have been created since they are queued.
  FakeMessageReceiver receiver2;
  FakeNearbyConnectionStateListener nearby_connection_state_listener2;
  Connect(&receiver2, &nearby_connection_state_listener2,
          GetBluetoothAddress(/*repeated_address_value=*/2u));
  EXPECT_EQ(broker1, fake_connection_broker_factory_.last_created());
  EXPECT_EQ(1u, fake_nearby_process_manager_.GetNumActiveReferences());

  // Complete connection 1.
  base::RunLoop connect_run_loop1;
  on_connect_ = connect_run_loop1.QuitClosure();
  broker1->NotifyConnected();
  connect_run_loop1.Run();

  // A new broker should have been created for connection 2 since connection 1
  // has completed.
  FakeNearbyConnectionBroker* broker2 =
      fake_connection_broker_factory_.last_created();
  EXPECT_NE(broker1, broker2);

  // Complete connection 2.
  base::RunLoop connect_run_loop2;
  on_connect_ = connect_run_loop2.QuitClosure();
  broker2->NotifyConnected();
  connect_run_loop2.Run();

  // Disconnect connection 1. The process reference should still be active since
  // there is still an active connection.
  base::RunLoop disconnect_run_loop1;
  receiver1.SetMojoDisconnectHandler(disconnect_run_loop1.QuitClosure());
  broker1->InvokeDisconnectedCallback();
  disconnect_run_loop1.Run();
  EXPECT_EQ(1u, fake_nearby_process_manager_.GetNumActiveReferences());

  // Disconnect connection 2. The process reference should have been released.
  base::RunLoop disconnect_run_loop2;
  receiver2.SetMojoDisconnectHandler(disconnect_run_loop2.QuitClosure());
  broker2->InvokeDisconnectedCallback();
  disconnect_run_loop2.Run();
  EXPECT_EQ(0u, fake_nearby_process_manager_.GetNumActiveReferences());
}

// Regression test for https://crbug.com/1156162.
TEST_F(NearbyConnectorImplTest, TwoConnections_FirstFails) {
  // Attempt connection 1.
  FakeMessageReceiver receiver1;
  FakeNearbyConnectionStateListener nearby_connection_state_listener1;
  Connect(&receiver1, &nearby_connection_state_listener1,
          GetBluetoothAddress(/*repeated_address_value=*/1u));
  FakeNearbyConnectionBroker* broker1 =
      fake_connection_broker_factory_.last_created();
  EXPECT_EQ(1u, fake_nearby_process_manager_.GetNumActiveReferences());

  // Attempt connection 2 before connection 1 has completed. No new broker
  // should have been created since they are queued.
  FakeMessageReceiver receiver2;
  FakeNearbyConnectionStateListener nearby_connection_state_listener2;
  Connect(&receiver2, &nearby_connection_state_listener2,
          GetBluetoothAddress(/*repeated_address_value=*/2u));
  EXPECT_EQ(broker1, fake_connection_broker_factory_.last_created());
  EXPECT_EQ(1u, fake_nearby_process_manager_.GetNumActiveReferences());

  // Fail connection 1 by calling Disconnect() before completing the connection.
  base::RunLoop connect_run_loop;
  on_connect_ = connect_run_loop.QuitClosure();
  base::RunLoop disconnect_run_loop;
  receiver1.SetMojoDisconnectHandler(disconnect_run_loop.QuitClosure());
  broker1->InvokeDisconnectedCallback();
  connect_run_loop.Run();
  disconnect_run_loop.Run();

  // A new broker should have been created for connection 2 since connection 1
  // has completed.
  FakeNearbyConnectionBroker* broker2 =
      fake_connection_broker_factory_.last_created();
  EXPECT_EQ(1u, fake_nearby_process_manager_.GetNumActiveReferences());

  // Complete connection 2.
  base::RunLoop connect_run_loop2;
  on_connect_ = connect_run_loop2.QuitClosure();
  broker2->NotifyConnected();
  connect_run_loop2.Run();

  // Disconnect connection 2. The process reference should have been released.
  base::RunLoop disconnect_run_loop2;
  receiver2.SetMojoDisconnectHandler(disconnect_run_loop2.QuitClosure());
  broker2->InvokeDisconnectedCallback();
  disconnect_run_loop2.Run();
  EXPECT_EQ(0u, fake_nearby_process_manager_.GetNumActiveReferences());
}

TEST_F(NearbyConnectorImplTest, FailToConnect) {
  // Attempt connection.
  FakeMessageReceiver receiver;
  FakeNearbyConnectionStateListener nearby_connection_state_listener;
  Connect(&receiver, &nearby_connection_state_listener,
          GetBluetoothAddress(/*repeated_address_value=*/1u));
  FakeNearbyConnectionBroker* broker =
      fake_connection_broker_factory_.last_created();
  EXPECT_EQ(1u, fake_nearby_process_manager_.GetNumActiveReferences());

  // Fail connection by calling Disconnect() before completing the connection.
  base::RunLoop connect_run_loop;
  on_connect_ = connect_run_loop.QuitClosure();
  base::RunLoop disconnect_run_loop;
  receiver.SetMojoDisconnectHandler(disconnect_run_loop.QuitClosure());
  broker->InvokeDisconnectedCallback();
  connect_run_loop.Run();
  disconnect_run_loop.Run();

  // The mojo::Remote<mojom::NearbyMessageSender> should be null.
  EXPECT_EQ(0u, fake_nearby_process_manager_.GetNumActiveReferences());
  EXPECT_FALSE(id_to_remote_map_[receiver.id()]);
}

TEST_F(NearbyConnectorImplTest, NearbyProcessStops) {
  // Attempt connection.
  FakeMessageReceiver receiver;
  FakeNearbyConnectionStateListener nearby_connection_state_listener;
  Connect(&receiver, &nearby_connection_state_listener,
          GetBluetoothAddress(/*repeated_address_value=*/1u));
  FakeNearbyConnectionBroker* broker =
      fake_connection_broker_factory_.last_created();
  EXPECT_EQ(1u, fake_nearby_process_manager_.GetNumActiveReferences());

  // Complete connection.
  base::RunLoop connect_run_loop;
  on_connect_ = connect_run_loop.QuitClosure();
  broker->NotifyConnected();
  connect_run_loop.Run();

  // Stop the Nearby process; this should result in a disconnection.
  base::RunLoop disconnect_run_loop;
  receiver.SetMojoDisconnectHandler(disconnect_run_loop.QuitClosure());
  fake_nearby_process_manager_.SimulateProcessStopped(
      nearby::NearbyProcessManager::NearbyProcessShutdownReason::kNormal);
  disconnect_run_loop.Run();
  EXPECT_EQ(0u, fake_nearby_process_manager_.GetNumActiveReferences());
}

TEST_F(NearbyConnectorImplTest, NearbyProcessStopsDuringConnectionAttempt) {
  // Attempt connection.
  FakeMessageReceiver receiver;
  FakeNearbyConnectionStateListener nearby_connection_state_listener;
  Connect(&receiver, &nearby_connection_state_listener,
          GetBluetoothAddress(/*repeated_address_value=*/1u));
  EXPECT_EQ(1u, fake_nearby_process_manager_.GetNumActiveReferences());

  // Stop the Nearby process; this should result in a disconnection.
  base::RunLoop connect_run_loop;
  on_connect_ = connect_run_loop.QuitClosure();
  base::RunLoop disconnect_run_loop;
  receiver.SetMojoDisconnectHandler(disconnect_run_loop.QuitClosure());
  fake_nearby_process_manager_.SimulateProcessStopped(
      nearby::NearbyProcessManager::NearbyProcessShutdownReason::kNormal);
  connect_run_loop.Run();
  disconnect_run_loop.Run();
  EXPECT_EQ(0u, fake_nearby_process_manager_.GetNumActiveReferences());
}

}  // namespace secure_channel
}  // namespace ash