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

#include "chromeos/services/secure_channel/multiplexed_channel_impl.h"

#include <iterator>
#include <memory>
#include <unordered_map>
#include <utility>
#include <vector>

#include "base/stl_util.h"
#include "base/test/scoped_task_environment.h"
#include "chromeos/services/secure_channel/connection_details.h"
#include "chromeos/services/secure_channel/connection_medium.h"
#include "chromeos/services/secure_channel/fake_authenticated_channel.h"
#include "chromeos/services/secure_channel/fake_client_connection_parameters.h"
#include "chromeos/services/secure_channel/fake_connection_delegate.h"
#include "chromeos/services/secure_channel/fake_multiplexed_channel.h"
#include "chromeos/services/secure_channel/fake_single_client_message_proxy.h"
#include "chromeos/services/secure_channel/single_client_message_proxy_impl.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace chromeos {

namespace secure_channel {

namespace {

const char kTestDeviceId[] = "testDeviceId";
const char kTestFeature[] = "testFeature";

class FakeSingleClientMessageProxyImplFactory
    : public SingleClientMessageProxyImpl::Factory {
 public:
  FakeSingleClientMessageProxyImplFactory() = default;
  ~FakeSingleClientMessageProxyImplFactory() override = default;

  const SingleClientMessageProxy::Delegate* expected_delegate() {
    return expected_delegate_;
  }

  // Contains all created FakeSingleClientMessageProxy pointers which have not
  // yet been deleted.
  std::unordered_map<base::UnguessableToken,
                     FakeSingleClientMessageProxy*,
                     base::UnguessableTokenHash>&
  id_to_active_proxy_map() {
    return id_to_active_proxy_map_;
  }

 private:
  std::unique_ptr<SingleClientMessageProxy> BuildInstance(
      SingleClientMessageProxy::Delegate* delegate,
      std::unique_ptr<ClientConnectionParameters> client_connection_parameters)
      override {
    EXPECT_EQ(kTestFeature, client_connection_parameters->feature());
    EXPECT_TRUE(client_connection_parameters->IsClientWaitingForResponse());

    if (!expected_delegate_)
      expected_delegate_ = delegate;
    // Each call should have the same delegate.
    EXPECT_EQ(expected_delegate_, delegate);

    std::unique_ptr<SingleClientMessageProxy> proxy = std::make_unique<
        FakeSingleClientMessageProxy>(
        delegate,
        base::BindOnce(
            &FakeSingleClientMessageProxyImplFactory::OnCreatedInstanceDeleted,
            base::Unretained(this)));
    FakeSingleClientMessageProxy* proxy_raw =
        static_cast<FakeSingleClientMessageProxy*>(proxy.get());
    id_to_active_proxy_map_[proxy->GetProxyId()] = proxy_raw;

    return proxy;
  }

  void OnCreatedInstanceDeleted(
      const base::UnguessableToken& deleted_proxy_id) {
    size_t num_deleted = id_to_active_proxy_map_.erase(deleted_proxy_id);
    EXPECT_EQ(1u, num_deleted);
  }

  SingleClientMessageProxy::Delegate* expected_delegate_ = nullptr;
  std::unordered_map<base::UnguessableToken,
                     FakeSingleClientMessageProxy*,
                     base::UnguessableTokenHash>
      id_to_active_proxy_map_;

  DISALLOW_COPY_AND_ASSIGN(FakeSingleClientMessageProxyImplFactory);
};

}  // namespace

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

  void SetUp() override {
    fake_proxy_factory_ =
        std::make_unique<FakeSingleClientMessageProxyImplFactory>();
    SingleClientMessageProxyImpl::Factory::SetInstanceForTesting(
        fake_proxy_factory_.get());

    fake_delegate_ = std::make_unique<FakeMultiplexedChannelDelegate>();

    // The default list contains one client.
    initial_client_list_.push_back(
        std::make_unique<FakeClientConnectionParameters>(kTestFeature));
  }

  void TearDown() override {
    SingleClientMessageProxyImpl::Factory::SetInstanceForTesting(nullptr);
  }

  void CreateChannel() {
    auto fake_authenticated_channel =
        std::make_unique<FakeAuthenticatedChannel>();
    fake_authenticated_channel_ = fake_authenticated_channel.get();

    multiplexed_channel_ =
        MultiplexedChannelImpl::Factory::Get()->BuildInstance(
            std::move(fake_authenticated_channel), fake_delegate_.get(),
            ConnectionDetails(kTestDeviceId,
                              ConnectionMedium::kBluetoothLowEnergy),
            &initial_client_list_);

    // Once BuildInstance() has finished, |fake_proxy_factory_| is expected to
    // have already created one or more instances. Verify that the delegate
    // passed to the factory is actually |multiplexed_channel_|.
    EXPECT_EQ(static_cast<MultiplexedChannelImpl*>(multiplexed_channel_.get()),
              fake_proxy_factory_->expected_delegate());
  }

  // If |complete_sending| is true, the "on sent" callback is invoked.
  int SendMessageAndVerifyState(FakeSingleClientMessageProxy* sending_proxy,
                                const std::string& feature,
                                const std::string& payload,
                                bool complete_sending = true) {
    size_t num_sent_message_before_call = sent_messages().size();

    int message_counter = next_send_message_counter_++;
    sending_proxy->NotifySendMessageRequested(
        feature, payload,
        base::BindOnce(&SecureChannelMultiplexedChannelImplTest::OnMessageSent,
                       base::Unretained(this), message_counter));

    EXPECT_EQ(num_sent_message_before_call + 1u, sent_messages().size());
    EXPECT_EQ(feature, std::get<0>(sent_messages().back()));
    EXPECT_EQ(payload, std::get<1>(sent_messages().back()));

    if (complete_sending) {
      EXPECT_FALSE(HasMessageBeenSent(message_counter));
      std::move(std::get<2>(sent_messages().back())).Run();
      EXPECT_TRUE(HasMessageBeenSent(message_counter));
    }

    return message_counter;
  }

  void ReceiveMessageAndVerifyState(const std::string& feature,
                                    const std::string& payload) {
    std::unordered_map<base::UnguessableToken, size_t,
                       base::UnguessableTokenHash>
        proxy_id_to_num_processed_messages_before_call_map;
    for (auto& map_entry : id_to_active_proxy_map()) {
      proxy_id_to_num_processed_messages_before_call_map[map_entry.first] =
          map_entry.second->processed_messages().size();
    }

    fake_authenticated_channel_->NotifyMessageReceived(feature, payload);

    for (auto& map_entry : id_to_active_proxy_map()) {
      EXPECT_EQ(
          proxy_id_to_num_processed_messages_before_call_map[map_entry.first] +
              1u,
          map_entry.second->processed_messages().size());
      EXPECT_EQ(feature, map_entry.second->processed_messages().back().first);
      EXPECT_EQ(payload, map_entry.second->processed_messages().back().second);
    }
  }

  bool HasMessageBeenSent(int message_counter) {
    return base::ContainsKey(sent_message_counters_, message_counter);
  }

  void DisconnectClientAndVerifyState(
      FakeSingleClientMessageProxy* sending_proxy,
      bool expected_to_be_last_client) {
    // If this is the last client left, disconnecting it should result in the
    // underlying channel becoming disconnected.
    bool is_last_client = id_to_active_proxy_map().size() == 1u;
    EXPECT_EQ(is_last_client, expected_to_be_last_client);

    base::UnguessableToken proxy_id = sending_proxy->GetProxyId();

    // All relevant parties should still indicate that the connection is valid.
    EXPECT_TRUE(base::ContainsKey(id_to_active_proxy_map(), proxy_id));
    EXPECT_FALSE(
        fake_authenticated_channel_->has_disconnection_been_requested());
    EXPECT_FALSE(multiplexed_channel_->IsDisconnecting());
    EXPECT_FALSE(multiplexed_channel_->IsDisconnected());
    EXPECT_FALSE(fake_delegate_->disconnected_connection_details());

    // Disconnecting the client should result in the proxy being deleted.
    sending_proxy->NotifyClientDisconnected();
    EXPECT_FALSE(base::ContainsKey(id_to_active_proxy_map(), proxy_id));

    if (!is_last_client)
      return;

    // Since this was the last client, |multiplexed_channel_| should have
    // started the disconnection flow. Because disconnection is asynchronous,
    // it should have not yet completed yet.
    EXPECT_TRUE(multiplexed_channel_->IsDisconnecting());
    EXPECT_FALSE(multiplexed_channel_->IsDisconnected());
    EXPECT_TRUE(
        fake_authenticated_channel_->has_disconnection_been_requested());
    EXPECT_FALSE(fake_delegate_->disconnected_connection_details());

    // Complete asynchronous disconnection.
    fake_authenticated_channel_->NotifyDisconnected();

    // Verify that all relevant parties have been notified of the disconnection.
    EXPECT_EQ(multiplexed_channel_->connection_details(),
              fake_delegate_->disconnected_connection_details());
    EXPECT_FALSE(multiplexed_channel_->IsDisconnecting());
    EXPECT_TRUE(multiplexed_channel_->IsDisconnected());
  }

  void DisconnectRemoteDeviceAndVerifyState() {
    // Before the disconnection, all relevant parties should still indicate that
    // the connection is valid.
    EXPECT_FALSE(multiplexed_channel_->IsDisconnecting());
    EXPECT_FALSE(multiplexed_channel_->IsDisconnected());
    EXPECT_FALSE(fake_delegate_->disconnected_connection_details());
    for (auto& map_entry : id_to_active_proxy_map())
      EXPECT_FALSE(map_entry.second->was_remote_device_disconnection_handled());

    fake_authenticated_channel_->NotifyDisconnected();

    // Verify that the above preconditions have now been changed.
    EXPECT_FALSE(multiplexed_channel_->IsDisconnecting());
    EXPECT_TRUE(multiplexed_channel_->IsDisconnected());
    EXPECT_EQ(multiplexed_channel_->connection_details(),
              fake_delegate_->disconnected_connection_details());
    for (auto& map_entry : id_to_active_proxy_map())
      EXPECT_TRUE(map_entry.second->was_remote_device_disconnection_handled());
  }

  void AddClientToChannel(const std::string& feature) {
    bool success = multiplexed_channel_->AddClientToChannel(
        std::make_unique<FakeClientConnectionParameters>(feature));
    EXPECT_TRUE(success);
  }

  void CallGetConnectionMetadataFromDelegate(
      FakeSingleClientMessageProxy* proxy) {
    proxy->GetConnectionMetadataFromDelegate(base::BindOnce(
        &SecureChannelMultiplexedChannelImplTest::OnGetConnectionMetadata,
        base::Unretained(this)));
  }

  void OnGetConnectionMetadata(
      mojom::ConnectionMetadataPtr connection_metadata) {
    connection_metadata_ = std::move(connection_metadata);
  }

  std::vector<std::unique_ptr<ClientConnectionParameters>>&
  initial_client_list() {
    return initial_client_list_;
  }

  std::unordered_map<base::UnguessableToken,
                     FakeSingleClientMessageProxy*,
                     base::UnguessableTokenHash>&
  id_to_active_proxy_map() {
    return fake_proxy_factory_->id_to_active_proxy_map();
  }

  std::vector<std::tuple<std::string, std::string, base::OnceClosure>>&
  sent_messages() {
    return fake_authenticated_channel_->sent_messages();
  }

  FakeAuthenticatedChannel* fake_authenticated_channel() {
    return fake_authenticated_channel_;
  }

  mojom::ConnectionMetadataPtr connection_metadata_;

 private:
  void OnMessageSent(int message_counter) {
    sent_message_counters_.insert(message_counter);
  }

  const base::test::ScopedTaskEnvironment scoped_task_environment_;

  int next_send_message_counter_ = 0;
  std::unordered_set<int> sent_message_counters_;

  std::unique_ptr<FakeSingleClientMessageProxyImplFactory> fake_proxy_factory_;

  std::vector<std::unique_ptr<ClientConnectionParameters>> initial_client_list_;

  FakeAuthenticatedChannel* fake_authenticated_channel_ = nullptr;
  std::unique_ptr<FakeMultiplexedChannelDelegate> fake_delegate_;

  std::unique_ptr<MultiplexedChannel> multiplexed_channel_;

  DISALLOW_COPY_AND_ASSIGN(SecureChannelMultiplexedChannelImplTest);
};

TEST_F(SecureChannelMultiplexedChannelImplTest, ConnectionMetadata) {
  CreateChannel();
  EXPECT_EQ(1u, id_to_active_proxy_map().size());

  std::vector<mojom::ConnectionCreationDetail> creation_details{
      mojom::ConnectionCreationDetail::
          REMOTE_DEVICE_USED_BACKGROUND_BLE_ADVERTISING};

  // Set connection metadata on |fake_authenticated_channel_|.
  mojom::ConnectionMetadataPtr metadata = mojom::ConnectionMetadata::New(
      creation_details,
      mojom::BluetoothConnectionMetadata::New(-24 /* current_rssi */),
      "channel_binding_data_1");
  fake_authenticated_channel()->set_connection_metadata_for_next_call(
      std::move(metadata));

  // Retrieving the metadata through the proxy should cause
  // |fake_authenticated_channel_|'s metadata to be passed through
  // |multiplexed_channel_|.
  CallGetConnectionMetadataFromDelegate(
      id_to_active_proxy_map().begin()->second);
  EXPECT_EQ(creation_details, connection_metadata_->creation_details);
  EXPECT_EQ(-24,
            connection_metadata_->bluetooth_connection_metadata->current_rssi);
  EXPECT_EQ("channel_binding_data_1",
            connection_metadata_->channel_binding_data);

  // Now, change the values and set them on |fake_authenticated_channel_|.
  creation_details.clear();
  metadata = mojom::ConnectionMetadata::New(
      creation_details,
      mojom::BluetoothConnectionMetadata::New(-8 /* current_rssi */),
      "channel_binding_data_2");
  fake_authenticated_channel()->set_connection_metadata_for_next_call(
      std::move(metadata));

  // The new updates should be available.
  CallGetConnectionMetadataFromDelegate(
      id_to_active_proxy_map().begin()->second);
  EXPECT_EQ(creation_details, connection_metadata_->creation_details);
  EXPECT_EQ(-8,
            connection_metadata_->bluetooth_connection_metadata->current_rssi);
  EXPECT_EQ("channel_binding_data_2",
            connection_metadata_->channel_binding_data);

  DisconnectClientAndVerifyState(id_to_active_proxy_map().begin()->second,
                                 true /* expected_to_be_last_client */);
}

TEST_F(SecureChannelMultiplexedChannelImplTest,
       OneClient_SendReceive_DisconnectFromClient) {
  CreateChannel();
  EXPECT_EQ(1u, id_to_active_proxy_map().size());

  SendMessageAndVerifyState(
      id_to_active_proxy_map().begin()->second /* sending_proxy */, "feature1",
      "payload1");
  ReceiveMessageAndVerifyState("feature2", "payload2");

  DisconnectClientAndVerifyState(id_to_active_proxy_map().begin()->second,
                                 true /* expected_to_be_last_client */);
}

TEST_F(SecureChannelMultiplexedChannelImplTest,
       OneClient_SendAsync_DisconnectFromRemoteDevice) {
  CreateChannel();
  EXPECT_EQ(1u, id_to_active_proxy_map().size());

  // Start sending two messages, but do not complete the asynchronous sending.
  int counter1 = SendMessageAndVerifyState(
      id_to_active_proxy_map().begin()->second /* sending_proxy */, "feature1",
      "payload1", false /* complete_sending */);
  int counter2 = SendMessageAndVerifyState(
      id_to_active_proxy_map().begin()->second /* sending_proxy */, "feature2",
      "payload2", false /* complete_sending */);
  EXPECT_FALSE(HasMessageBeenSent(counter1));
  EXPECT_FALSE(HasMessageBeenSent(counter2));
  EXPECT_EQ(2u, sent_messages().size());

  // Finish sending the first message.
  std::move(std::get<2>(sent_messages()[0])).Run();
  EXPECT_TRUE(HasMessageBeenSent(counter1));
  EXPECT_FALSE(HasMessageBeenSent(counter2));

  // Before the second message is sent successfully, disconnect.
  DisconnectRemoteDeviceAndVerifyState();
}

TEST_F(SecureChannelMultiplexedChannelImplTest,
       TwoInitialClient_OneAdded_DisconnectFromClient) {
  // Add a second initial client.
  initial_client_list().push_back(
      std::make_unique<FakeClientConnectionParameters>(kTestFeature));

  CreateChannel();
  EXPECT_EQ(2u, id_to_active_proxy_map().size());

  // Send a message from the first client.
  SendMessageAndVerifyState(
      id_to_active_proxy_map().begin()->second /* sending_proxy */, "feature1",
      "payload1");

  // Receive a message (both clients should receive).
  ReceiveMessageAndVerifyState("feature2", "payload2");

  // Send a message from the second client.
  auto it = id_to_active_proxy_map().begin();
  std::advance(it, 1);
  SendMessageAndVerifyState(it->second /* sending_proxy */, "feature3",
                            "payload3");

  // Receive a message (both clients should receive).
  ReceiveMessageAndVerifyState("feature4", "payload4");

  // Create a third client and add it to the channel.
  AddClientToChannel(kTestFeature);
  EXPECT_EQ(3u, id_to_active_proxy_map().size());

  // Send a message from the third client.
  it = id_to_active_proxy_map().begin();
  std::advance(it, 2);
  SendMessageAndVerifyState(it->second, "feature5", "payload5");
  // Receive a message (both clients should receive).
  ReceiveMessageAndVerifyState("feature6", "payload6");

  // Disconnect the clients, one by one. Only the last client should actually
  // trigger |fake_authenticated_channel_| to disconnect.
  DisconnectClientAndVerifyState(id_to_active_proxy_map().begin()->second,
                                 false /* expected_to_be_last_client */);
  EXPECT_EQ(2u, id_to_active_proxy_map().size());
  DisconnectClientAndVerifyState(id_to_active_proxy_map().begin()->second,
                                 false /* expected_to_be_last_client */);
  EXPECT_EQ(1u, id_to_active_proxy_map().size());
  DisconnectClientAndVerifyState(id_to_active_proxy_map().begin()->second,
                                 true /* expected_to_be_last_client */);
}

}  // namespace secure_channel

}  // namespace chromeos
