//
// Copyright (C) 2015 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#include <string>

#include <brillo/bind_lambda.h>
#include <brillo/dbus/dbus_object_test_helpers.h>
#include <dbus/mock_bus.h>
#include <dbus/mock_exported_object.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include "tpm_manager/common/mock_tpm_nvram_interface.h"
#include "tpm_manager/common/mock_tpm_ownership_interface.h"
#include "tpm_manager/common/tpm_manager_constants.h"
#include "tpm_manager/common/tpm_nvram_dbus_interface.h"
#include "tpm_manager/common/tpm_ownership_dbus_interface.h"
#include "tpm_manager/server/dbus_service.h"

using testing::_;
using testing::Invoke;
using testing::NiceMock;
using testing::Return;
using testing::StrictMock;
using testing::WithArgs;

namespace tpm_manager {

class DBusServiceTest : public testing::Test {
 public:
  ~DBusServiceTest() override = default;
  void SetUp() override {
    dbus::Bus::Options options;
    mock_bus_ = new NiceMock<dbus::MockBus>(options);
    dbus::ObjectPath path(kTpmManagerServicePath);
    mock_exported_object_ = new NiceMock<dbus::MockExportedObject>(
        mock_bus_.get(), path);
    ON_CALL(*mock_bus_, GetExportedObject(path))
        .WillByDefault(Return(mock_exported_object_.get()));
    dbus_service_.reset(new DBusService(mock_bus_,
                                        &mock_nvram_service_,
                                        &mock_ownership_service_));
    dbus_service_->Register(brillo::dbus_utils::AsyncEventSequencer::
                                GetDefaultCompletionAction());
  }

  template<typename RequestProtobufType, typename ReplyProtobufType>
  void ExecuteMethod(const std::string& method_name,
                     const RequestProtobufType& request,
                     ReplyProtobufType* reply,
                     const std::string& interface) {
    std::unique_ptr<dbus::MethodCall> call = CreateMethodCall(method_name,
                                                              interface);
    dbus::MessageWriter writer(call.get());
    writer.AppendProtoAsArrayOfBytes(request);
    auto response = brillo::dbus_utils::testing::CallMethod(
        dbus_service_->dbus_object_, call.get());
    dbus::MessageReader reader(response.get());
    EXPECT_TRUE(reader.PopArrayOfBytesAsProto(reply));
  }

 protected:
  std::unique_ptr<dbus::MethodCall> CreateMethodCall(
      const std::string& method_name, const std::string& interface) {
    std::unique_ptr<dbus::MethodCall> call(new dbus::MethodCall(
        interface, method_name));
    call->SetSerial(1);
    return call;
  }

  scoped_refptr<dbus::MockBus> mock_bus_;
  scoped_refptr<dbus::MockExportedObject> mock_exported_object_;
  StrictMock<MockTpmNvramInterface> mock_nvram_service_;
  StrictMock<MockTpmOwnershipInterface> mock_ownership_service_;
  std::unique_ptr<DBusService> dbus_service_;
};

TEST_F(DBusServiceTest, CopyableCallback) {
  EXPECT_CALL(mock_ownership_service_, GetTpmStatus(_, _))
      .WillOnce(WithArgs<1>(Invoke([](
          const TpmOwnershipInterface::GetTpmStatusCallback& callback) {
            // Copy the callback, then call the original.
            GetTpmStatusReply reply;
            base::Closure copy = base::Bind(callback, reply);
            callback.Run(reply);
          })));
  GetTpmStatusRequest request;
  GetTpmStatusReply reply;
  ExecuteMethod(kGetTpmStatus, request, &reply, kTpmOwnershipInterface);
}

TEST_F(DBusServiceTest, GetTpmStatus) {
  GetTpmStatusRequest request;
  EXPECT_CALL(mock_ownership_service_, GetTpmStatus(_, _))
      .WillOnce(Invoke([](
          const GetTpmStatusRequest& request,
          const TpmOwnershipInterface::GetTpmStatusCallback& callback) {
        GetTpmStatusReply reply;
        reply.set_status(STATUS_SUCCESS);
        reply.set_enabled(true);
        reply.set_owned(true);
        reply.set_dictionary_attack_counter(3);
        reply.set_dictionary_attack_threshold(4);
        reply.set_dictionary_attack_lockout_in_effect(true);
        reply.set_dictionary_attack_lockout_seconds_remaining(5);
        callback.Run(reply);
      }));
  GetTpmStatusReply reply;
  ExecuteMethod(kGetTpmStatus, request, &reply, kTpmOwnershipInterface);
  EXPECT_EQ(STATUS_SUCCESS, reply.status());
  EXPECT_TRUE(reply.enabled());
  EXPECT_TRUE(reply.owned());
  EXPECT_EQ(3, reply.dictionary_attack_counter());
  EXPECT_EQ(4, reply.dictionary_attack_threshold());
  EXPECT_TRUE(reply.dictionary_attack_lockout_in_effect());
  EXPECT_EQ(5, reply.dictionary_attack_lockout_seconds_remaining());
}

TEST_F(DBusServiceTest, TakeOwnership) {
  EXPECT_CALL(mock_ownership_service_, TakeOwnership(_, _))
      .WillOnce(Invoke([](
          const TakeOwnershipRequest& request,
          const TpmOwnershipInterface::TakeOwnershipCallback& callback) {
        TakeOwnershipReply reply;
        reply.set_status(STATUS_SUCCESS);
        callback.Run(reply);
      }));
  TakeOwnershipRequest request;
  TakeOwnershipReply reply;
  ExecuteMethod(kTakeOwnership, request, &reply, kTpmOwnershipInterface);
  EXPECT_EQ(STATUS_SUCCESS, reply.status());
}

TEST_F(DBusServiceTest, RemoveOwnerDependency) {
  std::string owner_dependency("owner_dependency");
  RemoveOwnerDependencyRequest request;
  request.set_owner_dependency(owner_dependency);
  EXPECT_CALL(mock_ownership_service_, RemoveOwnerDependency(_, _))
      .WillOnce(Invoke([&owner_dependency](
          const RemoveOwnerDependencyRequest& request,
          const TpmOwnershipInterface::RemoveOwnerDependencyCallback& callback)
      {
        EXPECT_TRUE(request.has_owner_dependency());
        EXPECT_EQ(owner_dependency, request.owner_dependency());
        RemoveOwnerDependencyReply reply;
        reply.set_status(STATUS_SUCCESS);
        callback.Run(reply);
      }));
  RemoveOwnerDependencyReply reply;
  ExecuteMethod(kRemoveOwnerDependency,
                request,
                &reply,
                kTpmOwnershipInterface);
  EXPECT_EQ(STATUS_SUCCESS, reply.status());
}

TEST_F(DBusServiceTest, DefineNvram) {
  uint32_t nvram_index = 5;
  size_t nvram_length = 32;
  DefineNvramRequest request;
  request.set_index(nvram_index);
  request.set_length(nvram_length);
  EXPECT_CALL(mock_nvram_service_, DefineNvram(_, _))
      .WillOnce(Invoke([nvram_index, nvram_length](
          const DefineNvramRequest& request,
          const TpmNvramInterface::DefineNvramCallback& callback) {
        EXPECT_TRUE(request.has_index());
        EXPECT_EQ(nvram_index, request.index());
        EXPECT_TRUE(request.has_length());
        EXPECT_EQ(nvram_length, request.length());
        DefineNvramReply reply;
        reply.set_status(STATUS_SUCCESS);
        callback.Run(reply);
      }));
  DefineNvramReply reply;
  ExecuteMethod(kDefineNvram, request, &reply, kTpmNvramInterface);
  EXPECT_EQ(STATUS_SUCCESS, reply.status());
}

TEST_F(DBusServiceTest, DestroyNvram) {
  uint32_t nvram_index = 5;
  DestroyNvramRequest request;
  request.set_index(nvram_index);
  EXPECT_CALL(mock_nvram_service_, DestroyNvram(_, _))
      .WillOnce(Invoke([nvram_index](
          const DestroyNvramRequest& request,
          const TpmNvramInterface::DestroyNvramCallback& callback) {
        EXPECT_TRUE(request.has_index());
        EXPECT_EQ(nvram_index, request.index());
        DestroyNvramReply reply;
        reply.set_status(STATUS_SUCCESS);
        callback.Run(reply);
      }));
  DestroyNvramReply reply;
  ExecuteMethod(kDestroyNvram, request, &reply, kTpmNvramInterface);
  EXPECT_EQ(STATUS_SUCCESS, reply.status());
}

TEST_F(DBusServiceTest, WriteNvram) {
  uint32_t nvram_index = 5;
  std::string nvram_data("nvram_data");
  WriteNvramRequest request;
  request.set_index(nvram_index);
  request.set_data(nvram_data);
  EXPECT_CALL(mock_nvram_service_, WriteNvram(_, _))
      .WillOnce(Invoke([nvram_index, nvram_data](
          const WriteNvramRequest& request,
          const TpmNvramInterface::WriteNvramCallback& callback) {
        EXPECT_TRUE(request.has_index());
        EXPECT_EQ(nvram_index, request.index());
        EXPECT_TRUE(request.has_data());
        EXPECT_EQ(nvram_data, request.data());
        WriteNvramReply reply;
        reply.set_status(STATUS_SUCCESS);
        callback.Run(reply);
      }));
  WriteNvramReply reply;
  ExecuteMethod(kWriteNvram, request, &reply, kTpmNvramInterface);
  EXPECT_EQ(STATUS_SUCCESS, reply.status());
}

TEST_F(DBusServiceTest, ReadNvram) {
  uint32_t nvram_index = 5;
  std::string nvram_data("nvram_data");
  ReadNvramRequest request;
  request.set_index(nvram_index);
  EXPECT_CALL(mock_nvram_service_, ReadNvram(_, _))
      .WillOnce(Invoke([nvram_index, nvram_data](
          const ReadNvramRequest& request,
          const TpmNvramInterface::ReadNvramCallback& callback) {
        EXPECT_TRUE(request.has_index());
        EXPECT_EQ(nvram_index, request.index());
        ReadNvramReply reply;
        reply.set_status(STATUS_SUCCESS);
        reply.set_data(nvram_data);
        callback.Run(reply);
      }));
  ReadNvramReply reply;
  ExecuteMethod(kReadNvram, request, &reply, kTpmNvramInterface);
  EXPECT_EQ(STATUS_SUCCESS, reply.status());
  EXPECT_TRUE(reply.has_data());
  EXPECT_EQ(nvram_data, reply.data());
}

TEST_F(DBusServiceTest, IsNvramDefined) {
  uint32_t nvram_index = 5;
  bool nvram_defined = true;
  IsNvramDefinedRequest request;
  request.set_index(nvram_index);
  EXPECT_CALL(mock_nvram_service_, IsNvramDefined(_, _))
      .WillOnce(Invoke([nvram_index, nvram_defined](
          const IsNvramDefinedRequest& request,
          const TpmNvramInterface::IsNvramDefinedCallback& callback) {
        EXPECT_TRUE(request.has_index());
        EXPECT_EQ(nvram_index, request.index());
        IsNvramDefinedReply reply;
        reply.set_status(STATUS_SUCCESS);
        reply.set_is_defined(nvram_defined);
        callback.Run(reply);
      }));
  IsNvramDefinedReply reply;
  ExecuteMethod(kIsNvramDefined, request, &reply, kTpmNvramInterface);
  EXPECT_EQ(STATUS_SUCCESS, reply.status());
  EXPECT_TRUE(reply.has_is_defined());
  EXPECT_EQ(nvram_defined, reply.is_defined());
}

TEST_F(DBusServiceTest, IsNvramLocked) {
  uint32_t nvram_index = 5;
  bool nvram_locked = true;
  IsNvramLockedRequest request;
  request.set_index(nvram_index);
  EXPECT_CALL(mock_nvram_service_, IsNvramLocked(_, _))
      .WillOnce(Invoke([nvram_index, nvram_locked](
          const IsNvramLockedRequest& request,
          const TpmNvramInterface::IsNvramLockedCallback& callback) {
        EXPECT_TRUE(request.has_index());
        EXPECT_EQ(nvram_index, request.index());
        IsNvramLockedReply reply;
        reply.set_status(STATUS_SUCCESS);
        reply.set_is_locked(nvram_locked);
        callback.Run(reply);
      }));
  IsNvramLockedReply reply;
  ExecuteMethod(kIsNvramLocked, request, &reply, kTpmNvramInterface);
  EXPECT_EQ(STATUS_SUCCESS, reply.status());
  EXPECT_TRUE(reply.has_is_locked());
  EXPECT_EQ(nvram_locked, reply.is_locked());
}

TEST_F(DBusServiceTest, GetNvramSize) {
  uint32_t nvram_index = 5;
  size_t nvram_size = 32;
  GetNvramSizeRequest request;
  request.set_index(nvram_index);
  EXPECT_CALL(mock_nvram_service_, GetNvramSize(_, _))
      .WillOnce(Invoke([nvram_index, nvram_size](
          const GetNvramSizeRequest& request,
          const TpmNvramInterface::GetNvramSizeCallback& callback) {
        EXPECT_TRUE(request.has_index());
        EXPECT_EQ(nvram_index, request.index());
        GetNvramSizeReply reply;
        reply.set_status(STATUS_SUCCESS);
        reply.set_size(nvram_size);
        callback.Run(reply);
      }));
  GetNvramSizeReply reply;
  ExecuteMethod(kGetNvramSize, request, &reply, kTpmNvramInterface);
  EXPECT_EQ(STATUS_SUCCESS, reply.status());
  EXPECT_TRUE(reply.has_size());
  EXPECT_EQ(nvram_size, reply.size());
}

}  // namespace tpm_manager