// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "device/bluetooth/floss/bluetooth_socket_floss.h"

#include <memory>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "device/bluetooth/bluetooth_adapter.h"
#include "device/bluetooth/bluetooth_device.h"
#include "device/bluetooth/bluetooth_socket.h"
#include "device/bluetooth/bluetooth_socket_thread.h"
#include "device/bluetooth/floss/bluetooth_adapter_floss.h"
#include "device/bluetooth/floss/fake_floss_adapter_client.h"
#include "device/bluetooth/floss/fake_floss_manager_client.h"
#include "device/bluetooth/floss/fake_floss_socket_manager.h"
#include "device/bluetooth/floss/floss_dbus_client.h"
#include "device/bluetooth/floss/floss_dbus_manager.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

using ::device::BluetoothAdapter;

}  // namespace

namespace floss {

class BluetoothSocketFlossTest : public testing::Test {
 public:
  void SetUp() override {
    std::unique_ptr<floss::FlossDBusManagerSetter> dbus_setter =
        floss::FlossDBusManager::GetSetterForTesting();

    auto fake_floss_manager_client = std::make_unique<FakeFlossManagerClient>();
    fake_floss_manager_client_ = fake_floss_manager_client.get();
    dbus_setter->SetFlossManagerClient(std::move(fake_floss_manager_client));

    InitializeAndEnableAdapter();
  }

  void TearDown() override {
    adapter_ = nullptr;
    device::BluetoothSocketThread::CleanupForTesting();
  }

  void InitializeAndEnableAdapter() {
    adapter_ = BluetoothAdapterFloss::CreateAdapter();

    fake_floss_manager_client_->SetAdapterPowered(/*adapter*/ 0,
                                                  /*powered*/ true);

    base::RunLoop run_loop;
    adapter_->Initialize(run_loop.QuitClosure());
    run_loop.Run();

    ASSERT_TRUE(adapter_);
    ASSERT_TRUE(adapter_->IsInitialized());
    ASSERT_TRUE(adapter_->IsPowered());

    ASSERT_TRUE(adapter_.get() != nullptr);

    fake_floss_manager_client_->NotifyObservers(
        base::BindLambdaForTesting([](FlossManagerClient::Observer* observer) {
          observer->AdapterEnabledChanged(/*adapter=*/0, /*enabled=*/true);
        }));
    base::RunLoop().RunUntilIdle();

    // Get the socket thread.
    device::BluetoothSocketThread::Get();
  }

  void AcceptSuccessCallback(base::OnceClosure exitloop,
                             const device::BluetoothDevice* device,
                             scoped_refptr<device::BluetoothSocket> socket) {
    success_callback_count_++;
    last_socket_ = std::move(socket);
    std::move(exitloop).Run();
  }

  void ConnectToServiceSuccessCallback(
      base::OnceClosure exitloop,
      scoped_refptr<device::BluetoothSocket> socket) {
    success_callback_count_++;
    last_socket_ = std::move(socket);
    std::move(exitloop).Run();
  }

  void CreateServiceSuccessCallback(
      base::OnceClosure exitloop,
      scoped_refptr<device::BluetoothSocket> socket) {
    success_callback_count_++;
    last_socket_ = std::move(socket);
    std::move(exitloop).Run();
  }

  void DisconnectSuccessCallback(base::OnceClosure exitloop) {
    std::move(exitloop).Run();
  }

  void ErrorCallback(base::OnceClosure exitloop, const std::string& message) {
    LOG(ERROR) << "ErrorCallback: " << message;
    error_callback_count_++;
    last_message_ = message;
    std::move(exitloop).Run();
  }

  void ImmediateSuccessCallback() { success_callback_count_++; }

  void SendSuccessCallback(base::OnceClosure exitloop, int bytes_sent) {
    ++success_callback_count_;
    last_bytes_sent_ = bytes_sent;
    std::move(exitloop).Run();
  }

  // Clear counters for test.
  void ClearCounters() {
    last_bytes_sent_ = 0;
    last_bytes_received_ = 0;
    success_callback_count_ = 0;
    error_callback_count_ = 0;
    last_socket_ = nullptr;
  }

  void DisconnectSocket(device::BluetoothSocket* socket) {
    base::RunLoop run_loop;
    socket->Disconnect(base::BindOnce(
        &BluetoothSocketFlossTest::DisconnectSuccessCallback,
        weak_ptr_factory_.GetWeakPtr(), run_loop.QuitWhenIdleClosure()));
    run_loop.Run();
  }

  raw_ptr<FakeFlossSocketManager> GetFakeFlossSocketManager() {
    return static_cast<FakeFlossSocketManager*>(
        FlossDBusManager::Get()->GetSocketManager());
  }

  base::test::TaskEnvironment task_environment_;
  scoped_refptr<BluetoothAdapter> adapter_;

  std::string last_message_;
  int last_bytes_sent_ = 0;
  int last_bytes_received_ = 0;
  int success_callback_count_ = 0;
  int error_callback_count_ = 0;
  scoped_refptr<device::BluetoothSocket> last_socket_;

  // Holds pointer to FakeFloss*Client's so that we can manipulate the fake
  // within tests.
  raw_ptr<FakeFlossManagerClient> fake_floss_manager_client_;

  base::WeakPtrFactory<BluetoothSocketFlossTest> weak_ptr_factory_{this};
};

TEST_F(BluetoothSocketFlossTest, Connect) {
  device::BluetoothDevice* device =
      adapter_->GetDevice(FakeFlossAdapterClient::kBondedAddress1);
  ASSERT_TRUE(device != nullptr);

  // First establish a connection.
  {
    base::RunLoop run_loop;
    device->ConnectToService(
        device::BluetoothUUID(FakeFlossSocketManager::kRfcommUuid),
        base::BindOnce(
            &BluetoothSocketFlossTest::ConnectToServiceSuccessCallback,
            weak_ptr_factory_.GetWeakPtr(), run_loop.QuitWhenIdleClosure()),
        base::BindOnce(&BluetoothSocketFlossTest::ErrorCallback,
                       weak_ptr_factory_.GetWeakPtr(),
                       run_loop.QuitWhenIdleClosure()));
    run_loop.Run();
  }

  EXPECT_EQ(1, success_callback_count_);
  EXPECT_EQ(0, error_callback_count_);
  EXPECT_TRUE(last_socket_.get() != nullptr);

  // Take ownership of socket.
  scoped_refptr<device::BluetoothSocket> socket = std::move(last_socket_);
  ClearCounters();

  auto write_buffer = base::MakeRefCounted<net::StringIOBuffer>("test");
  {
    base::RunLoop run_loop;
    socket->Send(write_buffer.get(), write_buffer->size(),
                 base::BindOnce(&BluetoothSocketFlossTest::SendSuccessCallback,
                                weak_ptr_factory_.GetWeakPtr(),
                                run_loop.QuitWhenIdleClosure()),
                 base::BindOnce(&BluetoothSocketFlossTest::ErrorCallback,
                                weak_ptr_factory_.GetWeakPtr(),
                                run_loop.QuitWhenIdleClosure()));
    run_loop.Run();
  }

  EXPECT_EQ(1, success_callback_count_);
  EXPECT_EQ(0, error_callback_count_);
  EXPECT_EQ(last_bytes_sent_, write_buffer->size());
  ClearCounters();

  // Clean up the socket
  DisconnectSocket(socket.get());
  socket = nullptr;
}

// TODO (crbug.com/1412530) Test is failing on ASan bots
TEST_F(BluetoothSocketFlossTest, Listen) {
  // Get socket id for next returned socket.
  FlossSocketManager::SocketId id = GetFakeFlossSocketManager()->GetNextId();

  // First create the service.
  {
    base::RunLoop run_loop;
    adapter_->CreateRfcommService(
        device::BluetoothUUID(FakeFlossSocketManager::kRfcommUuid),
        BluetoothAdapter::ServiceOptions(),
        base::BindOnce(&BluetoothSocketFlossTest::CreateServiceSuccessCallback,
                       weak_ptr_factory_.GetWeakPtr(),
                       run_loop.QuitWhenIdleClosure()),
        base::BindOnce(&BluetoothSocketFlossTest::ErrorCallback,
                       weak_ptr_factory_.GetWeakPtr(),
                       run_loop.QuitWhenIdleClosure()));
    run_loop.Run();
  }

  EXPECT_EQ(1, success_callback_count_);
  EXPECT_EQ(0, error_callback_count_);
  EXPECT_TRUE(last_socket_.get() != nullptr);

  // Take ownership of server socket.
  scoped_refptr<device::BluetoothSocket> server_socket =
      std::move(last_socket_);
  ClearCounters();

  // Mark the socket as ready. This should trigger an accept.
  GetFakeFlossSocketManager()->SendSocketReady(
      id, device::BluetoothUUID(FakeFlossSocketManager::kRfcommUuid),
      FlossDBusClient::BtifStatus::kSuccess);

  // Simulate incoming connection. This queues one up to be accepted later.
  FlossDeviceId device = {.address = FakeFlossAdapterClient::kBondedAddress1,
                          .name = "Foobar"};
  GetFakeFlossSocketManager()->SendIncomingConnection(
      id, device, device::BluetoothUUID(FakeFlossSocketManager::kRfcommUuid));

  // Accept a connection and verify there is something there.
  {
    base::RunLoop run_loop;
    server_socket->Accept(
        base::BindOnce(&BluetoothSocketFlossTest::AcceptSuccessCallback,
                       weak_ptr_factory_.GetWeakPtr(),
                       run_loop.QuitWhenIdleClosure()),
        base::BindOnce(&BluetoothSocketFlossTest::ErrorCallback,
                       weak_ptr_factory_.GetWeakPtr(),
                       run_loop.QuitWhenIdleClosure()));
    run_loop.Run();
  }
  EXPECT_EQ(1, success_callback_count_);
  EXPECT_EQ(0, error_callback_count_);
  EXPECT_TRUE(last_socket_.get() != nullptr);

  // Take ownership of the client socket and close it.
  scoped_refptr<device::BluetoothSocket> client_socket =
      std::move(last_socket_);
  ClearCounters();

  DisconnectSocket(client_socket.get());
  client_socket = nullptr;
  ClearCounters();

  // Accept a connection when there's nothing there and then send connection.
  {
    // First runloop will push the accept callbacks into socket.
    // Second runloop will actually trigger when incoming connection occurs.
    base::RunLoop outer_loop;
    base::RunLoop inner_loop;

    server_socket->Accept(
        base::BindOnce(&BluetoothSocketFlossTest::AcceptSuccessCallback,
                       weak_ptr_factory_.GetWeakPtr(),
                       inner_loop.QuitWhenIdleClosure()),
        base::BindOnce(&BluetoothSocketFlossTest::ErrorCallback,
                       weak_ptr_factory_.GetWeakPtr(),
                       inner_loop.QuitWhenIdleClosure()));
    outer_loop.RunUntilIdle();

    // No sockets found to accept.
    EXPECT_EQ(0, success_callback_count_);
    EXPECT_EQ(0, error_callback_count_);
    EXPECT_TRUE(last_socket_.get() == nullptr);

    GetFakeFlossSocketManager()->SendIncomingConnection(
        id, device, device::BluetoothUUID(FakeFlossSocketManager::kRfcommUuid));
    inner_loop.Run();

    EXPECT_EQ(0, error_callback_count_);
    EXPECT_EQ(1, success_callback_count_);
    EXPECT_TRUE(last_socket_.get() != nullptr);

    // Disconnect last connecting socket
    client_socket = std::move(last_socket_);
    DisconnectSocket(client_socket.get());
    client_socket = nullptr;
    last_socket_ = nullptr;
  }

  // Try to accept twice and make sure the second one fails.
  ClearCounters();
  {
    base::RunLoop run_loop;
    server_socket->Accept(
        base::BindOnce(&BluetoothSocketFlossTest::AcceptSuccessCallback,
                       weak_ptr_factory_.GetWeakPtr(),
                       run_loop.QuitWhenIdleClosure()),
        base::BindOnce(&BluetoothSocketFlossTest::ErrorCallback,
                       weak_ptr_factory_.GetWeakPtr(),
                       run_loop.QuitWhenIdleClosure()));
    run_loop.RunUntilIdle();

    // No change after first one
    EXPECT_EQ(0, error_callback_count_);

    server_socket->Accept(
        base::BindOnce(&BluetoothSocketFlossTest::AcceptSuccessCallback,
                       weak_ptr_factory_.GetWeakPtr(),
                       run_loop.QuitWhenIdleClosure()),
        base::BindOnce(&BluetoothSocketFlossTest::ErrorCallback,
                       weak_ptr_factory_.GetWeakPtr(),
                       run_loop.QuitWhenIdleClosure()));
    run_loop.RunUntilIdle();

    // Second one should fail
    EXPECT_EQ(1, error_callback_count_);
    ClearCounters();
  }

  // Clean up server socket at end.
  DisconnectSocket(server_socket.get());
}

}  // namespace floss