// Copyright 2014 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 <stddef.h>
#include <stdint.h>

#include <memory>
#include <string>

#include "base/bind.h"
#include "base/run_loop.h"
#include "chromeos/dbus/shill_client_unittest_base.h"
#include "chromeos/dbus/shill_third_party_vpn_driver_client.h"
#include "chromeos/dbus/shill_third_party_vpn_observer.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

using testing::_;

namespace chromeos {

namespace {

const char kExampleIPConfigPath[] = "/foo/bar";

class MockShillThirdPartyVpnObserver : public ShillThirdPartyVpnObserver {
 public:
  MockShillThirdPartyVpnObserver() = default;
  ~MockShillThirdPartyVpnObserver() override = default;
  MOCK_METHOD1(OnPacketReceived, void(const std::vector<char>& data));
  MOCK_METHOD1(OnPlatformMessage, void(uint32_t message));
};

}  // namespace

class ShillThirdPartyVpnDriverClientTest : public ShillClientUnittestBase {
 public:
  ShillThirdPartyVpnDriverClientTest()
      : ShillClientUnittestBase(shill::kFlimflamThirdPartyVpnInterface,
                                dbus::ObjectPath(kExampleIPConfigPath)) {}

  void SetUp() override {
    ShillClientUnittestBase::SetUp();

    // Create a client with the mock bus.
    client_.reset(ShillThirdPartyVpnDriverClient::Create());
    client_->Init(mock_bus_.get());
    // Run the message loop to run the signal connection result callback.
    base::RunLoop().RunUntilIdle();
  }

  void TearDown() override { ShillClientUnittestBase::TearDown(); }

  MOCK_METHOD0(MockSuccess, void());
  MOCK_METHOD1(MockSuccessWithWarning, void(const std::string& warning));
  static void Failure(const std::string& error_name,
                      const std::string& error_message) {
    ADD_FAILURE() << error_name << ": " << error_message;
  }

 protected:
  std::unique_ptr<ShillThirdPartyVpnDriverClient> client_;
};

TEST_F(ShillThirdPartyVpnDriverClientTest, PlatformSignal) {
  uint32_t connected_state = 123456;
  const size_t kPacketSize = 5;
  std::vector<char> data_packet(kPacketSize, 1);
  dbus::Signal pmessage_signal(shill::kFlimflamThirdPartyVpnInterface,
                               shill::kOnPlatformMessageFunction);
  {
    dbus::MessageWriter writer(&pmessage_signal);
    writer.AppendUint32(connected_state);
  }

  dbus::Signal preceived_signal(shill::kFlimflamThirdPartyVpnInterface,
                                shill::kOnPacketReceivedFunction);
  {
    dbus::MessageWriter writer(&preceived_signal);
    writer.AppendArrayOfBytes(
        reinterpret_cast<const uint8_t*>(data_packet.data()),
        data_packet.size());
  }

  // Expect each signal to be triggered once.
  MockShillThirdPartyVpnObserver observer;
  EXPECT_CALL(observer, OnPlatformMessage(connected_state)).Times(1);
  EXPECT_CALL(observer, OnPacketReceived(data_packet)).Times(1);

  client_->AddShillThirdPartyVpnObserver(kExampleIPConfigPath, &observer);

  // Run the signal callback.
  SendPlatformMessageSignal(&pmessage_signal);
  SendPacketReceievedSignal(&preceived_signal);

  testing::Mock::VerifyAndClearExpectations(&observer);

  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());
  uint32_t connection_state = 2;

  PrepareForMethodCall(shill::kUpdateConnectionStateFunction,
                       base::Bind(&ExpectUint32Argument, connection_state),
                       response.get());

  EXPECT_CALL(*this, MockSuccess()).Times(0);
  client_->UpdateConnectionState(
      kExampleIPConfigPath, connection_state,
      base::Bind(&ShillThirdPartyVpnDriverClientTest::MockSuccess,
                 base::Unretained(this)),
      base::Bind(&Failure));

  client_->RemoveShillThirdPartyVpnObserver(kExampleIPConfigPath);
  testing::Mock::VerifyAndClearExpectations(this);

  EXPECT_CALL(*this, MockSuccess()).Times(1);

  // Check after removing the observer that there is no further signals.
  EXPECT_CALL(observer, OnPlatformMessage(connected_state)).Times(0);
  EXPECT_CALL(observer, OnPacketReceived(data_packet)).Times(0);

  // Run the signal callback.
  SendPlatformMessageSignal(&pmessage_signal);
  SendPacketReceievedSignal(&preceived_signal);

  testing::Mock::VerifyAndClearExpectations(&observer);

  base::RunLoop().RunUntilIdle();
}

TEST_F(ShillThirdPartyVpnDriverClientTest, SetParameters) {
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());
  dbus::MessageWriter writer(response.get());
  writer.AppendString(std::string("deadbeef"));

  base::DictionaryValue parameters;
  const std::string kAddress("1.1.1.1");
  parameters.SetKey(shill::kAddressParameterThirdPartyVpn,
                    base::Value(kAddress));

  EXPECT_CALL(*this, MockSuccessWithWarning(std::string("deadbeef"))).Times(1);

  PrepareForMethodCall(
      shill::kSetParametersFunction,
      base::Bind(&ExpectDictionaryValueArgument, &parameters, true),
      response.get());

  client_->SetParameters(
      kExampleIPConfigPath, parameters,
      base::Bind(&ShillThirdPartyVpnDriverClientTest::MockSuccessWithWarning,
                 base::Unretained(this)),
      base::Bind(&Failure));

  base::RunLoop().RunUntilIdle();
}

TEST_F(ShillThirdPartyVpnDriverClientTest, UpdateConnectionState) {
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());
  uint32_t connection_state = 2;

  EXPECT_CALL(*this, MockSuccess()).Times(1);

  PrepareForMethodCall(shill::kUpdateConnectionStateFunction,
                       base::Bind(&ExpectUint32Argument, connection_state),
                       response.get());

  client_->UpdateConnectionState(
      kExampleIPConfigPath, connection_state,
      base::Bind(&ShillThirdPartyVpnDriverClientTest::MockSuccess,
                 base::Unretained(this)),
      base::Bind(&Failure));

  base::RunLoop().RunUntilIdle();
}

TEST_F(ShillThirdPartyVpnDriverClientTest, SendPacket) {
  std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());

  const size_t kPacketSize = 5;
  const std::vector<char> data(kPacketSize, 0);

  EXPECT_CALL(*this, MockSuccess()).Times(1);

  PrepareForMethodCall(shill::kSendPacketFunction,
                       base::Bind(&ExpectArrayOfBytesArgument,
                                  std::string(data.begin(), data.end())),
                       response.get());

  client_->SendPacket(
      kExampleIPConfigPath, data,
      base::Bind(&ShillThirdPartyVpnDriverClientTest::MockSuccess,
                 base::Unretained(this)),
      base::Bind(&Failure));

  base::RunLoop().RunUntilIdle();
}

}  // namespace chromeos
