// Copyright 2014 The Crashpad Authors
//
// 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 "util/mach/composite_mach_message_server.h"

#include <sys/types.h>

#include <iterator>

#include "base/strings/stringprintf.h"
#include "gtest/gtest.h"
#include "test/gtest_death.h"
#include "util/mach/mach_message.h"

namespace crashpad {
namespace test {
namespace {

TEST(CompositeMachMessageServer, Empty) {
  CompositeMachMessageServer server;

  EXPECT_TRUE(server.MachMessageServerRequestIDs().empty());

  mach_msg_empty_rcv_t request = {};
  EXPECT_EQ(server.MachMessageServerRequestSize(), sizeof(request.header));

  mig_reply_error_t reply = {};
  EXPECT_EQ(server.MachMessageServerReplySize(), sizeof(reply));

  bool destroy_complex_request = false;
  EXPECT_FALSE(server.MachMessageServerFunction(
      &request.header, &reply.Head, &destroy_complex_request));
  EXPECT_EQ(reply.RetCode, MIG_BAD_ID);
}

class TestMachMessageHandler : public MachMessageServer::Interface {
 public:
  TestMachMessageHandler()
      : MachMessageServer::Interface(),
        request_ids_(),
        request_size_(0),
        reply_size_(0),
        return_code_(KERN_FAILURE),
        return_value_(false),
        destroy_complex_request_(false) {
  }

  TestMachMessageHandler(const TestMachMessageHandler&) = delete;
  TestMachMessageHandler& operator=(const TestMachMessageHandler&) = delete;

  ~TestMachMessageHandler() {
  }

  void SetReturnCodes(bool return_value,
                      kern_return_t return_code,
                      bool destroy_complex_request) {
    return_value_ = return_value;
    return_code_ = return_code;
    destroy_complex_request_ = destroy_complex_request;
  }

  void AddRequestID(mach_msg_id_t request_id) {
    request_ids_.insert(request_id);
  }

  void SetRequestSize(mach_msg_size_t request_size) {
    request_size_ = request_size;
  }

  void SetReplySize(mach_msg_size_t reply_size) {
    reply_size_ = reply_size;
  }

  // MachMessageServer::Interface:

  bool MachMessageServerFunction(const mach_msg_header_t* in,
                                 mach_msg_header_t* out,
                                 bool* destroy_complex_request) override {
    EXPECT_NE(request_ids_.find(in->msgh_id), request_ids_.end());

    *destroy_complex_request = destroy_complex_request_;
    PrepareMIGReplyFromRequest(in, out);
    SetMIGReplyError(out, return_code_);
    return return_value_;
  }

  std::set<mach_msg_id_t> MachMessageServerRequestIDs() override {
    return request_ids_;
  }

  mach_msg_size_t MachMessageServerRequestSize() override {
    return request_size_;
  }

  mach_msg_size_t MachMessageServerReplySize() override {
    return reply_size_;
  }

 private:
  std::set<mach_msg_id_t> request_ids_;
  mach_msg_size_t request_size_;
  mach_msg_size_t reply_size_;
  kern_return_t return_code_;
  bool return_value_;
  bool destroy_complex_request_;
};

TEST(CompositeMachMessageServer, HandlerDoesNotHandle) {
  TestMachMessageHandler handler;

  CompositeMachMessageServer server;
  server.AddHandler(&handler);

  EXPECT_TRUE(server.MachMessageServerRequestIDs().empty());

  mach_msg_empty_rcv_t request = {};
  EXPECT_EQ(server.MachMessageServerRequestSize(), sizeof(request.header));

  mig_reply_error_t reply = {};
  EXPECT_EQ(server.MachMessageServerReplySize(), sizeof(reply));

  bool destroy_complex_request = false;
  EXPECT_FALSE(server.MachMessageServerFunction(
      &request.header, &reply.Head, &destroy_complex_request));
  EXPECT_EQ(reply.RetCode, MIG_BAD_ID);
  EXPECT_FALSE(destroy_complex_request);
}

TEST(CompositeMachMessageServer, OneHandler) {
  constexpr mach_msg_id_t kRequestID = 100;
  constexpr mach_msg_size_t kRequestSize = 256;
  constexpr mach_msg_size_t kReplySize = 128;
  constexpr kern_return_t kReturnCode = KERN_SUCCESS;

  TestMachMessageHandler handler;
  handler.AddRequestID(kRequestID);
  handler.SetRequestSize(kRequestSize);
  handler.SetReplySize(kReplySize);
  handler.SetReturnCodes(true, kReturnCode, true);

  CompositeMachMessageServer server;

  // The chosen request and reply sizes must be larger than the defaults for
  // that portion fo the test to be valid.
  EXPECT_GT(kRequestSize, server.MachMessageServerRequestSize());
  EXPECT_GT(kReplySize, server.MachMessageServerReplySize());

  server.AddHandler(&handler);

  std::set<mach_msg_id_t> expect_request_ids;
  expect_request_ids.insert(kRequestID);
  EXPECT_EQ(server.MachMessageServerRequestIDs(), expect_request_ids);

  EXPECT_EQ(server.MachMessageServerRequestSize(), kRequestSize);
  EXPECT_EQ(server.MachMessageServerReplySize(), kReplySize);

  mach_msg_empty_rcv_t request = {};
  mig_reply_error_t reply = {};

  // Send a message with an unknown request ID.
  request.header.msgh_id = 0;
  bool destroy_complex_request = false;
  EXPECT_FALSE(server.MachMessageServerFunction(
      &request.header, &reply.Head, &destroy_complex_request));
  EXPECT_EQ(reply.RetCode, MIG_BAD_ID);
  EXPECT_FALSE(destroy_complex_request);

  // Send a message with a known request ID.
  request.header.msgh_id = kRequestID;
  EXPECT_TRUE(server.MachMessageServerFunction(
      &request.header, &reply.Head, &destroy_complex_request));
  EXPECT_EQ(reply.RetCode, kReturnCode);
  EXPECT_TRUE(destroy_complex_request);
}

TEST(CompositeMachMessageServer, ThreeHandlers) {
  static constexpr mach_msg_id_t kRequestIDs0[] = {5};
  constexpr kern_return_t kReturnCode0 = KERN_SUCCESS;

  static constexpr mach_msg_id_t kRequestIDs1[] = {4, 7};
  constexpr kern_return_t kReturnCode1 = KERN_PROTECTION_FAILURE;

  static constexpr mach_msg_id_t kRequestIDs2[] = {10, 0, 20};
  constexpr mach_msg_size_t kRequestSize2 = 6144;
  constexpr mach_msg_size_t kReplySize2 = 16384;
  constexpr kern_return_t kReturnCode2 = KERN_NOT_RECEIVER;

  TestMachMessageHandler handlers[3];
  std::set<mach_msg_id_t> expect_request_ids;

  for (size_t index = 0; index < std::size(kRequestIDs0); ++index) {
    const mach_msg_id_t request_id = kRequestIDs0[index];
    handlers[0].AddRequestID(request_id);
    expect_request_ids.insert(request_id);
  }
  handlers[0].SetRequestSize(sizeof(mach_msg_header_t));
  handlers[0].SetReplySize(sizeof(mig_reply_error_t));
  handlers[0].SetReturnCodes(true, kReturnCode0, false);

  for (size_t index = 0; index < std::size(kRequestIDs1); ++index) {
    const mach_msg_id_t request_id = kRequestIDs1[index];
    handlers[1].AddRequestID(request_id);
    expect_request_ids.insert(request_id);
  }
  handlers[1].SetRequestSize(100);
  handlers[1].SetReplySize(200);
  handlers[1].SetReturnCodes(false, kReturnCode1, true);

  for (size_t index = 0; index < std::size(kRequestIDs2); ++index) {
    const mach_msg_id_t request_id = kRequestIDs2[index];
    handlers[2].AddRequestID(request_id);
    expect_request_ids.insert(request_id);
  }
  handlers[2].SetRequestSize(kRequestSize2);
  handlers[2].SetReplySize(kReplySize2);
  handlers[2].SetReturnCodes(true, kReturnCode2, true);

  CompositeMachMessageServer server;

  // The chosen request and reply sizes must be larger than the defaults for
  // that portion fo the test to be valid.
  EXPECT_GT(kRequestSize2, server.MachMessageServerRequestSize());
  EXPECT_GT(kReplySize2, server.MachMessageServerReplySize());

  server.AddHandler(&handlers[0]);
  server.AddHandler(&handlers[1]);
  server.AddHandler(&handlers[2]);

  EXPECT_EQ(server.MachMessageServerRequestIDs(), expect_request_ids);

  EXPECT_EQ(server.MachMessageServerRequestSize(), kRequestSize2);
  EXPECT_EQ(server.MachMessageServerReplySize(), kReplySize2);

  mach_msg_empty_rcv_t request = {};
  mig_reply_error_t reply = {};

  // Send a message with an unknown request ID.
  request.header.msgh_id = 100;
  bool destroy_complex_request = false;
  EXPECT_FALSE(server.MachMessageServerFunction(
      &request.header, &reply.Head, &destroy_complex_request));
  EXPECT_EQ(reply.RetCode, MIG_BAD_ID);
  EXPECT_FALSE(destroy_complex_request);

  // Send messages with known request IDs.

  for (size_t index = 0; index < std::size(kRequestIDs0); ++index) {
    request.header.msgh_id = kRequestIDs0[index];
    SCOPED_TRACE(base::StringPrintf(
        "handler 0, index %zu, id %d", index, request.header.msgh_id));

    EXPECT_TRUE(server.MachMessageServerFunction(
        &request.header, &reply.Head, &destroy_complex_request));
    EXPECT_EQ(reply.RetCode, kReturnCode0);
    EXPECT_FALSE(destroy_complex_request);
  }

  for (size_t index = 0; index < std::size(kRequestIDs1); ++index) {
    request.header.msgh_id = kRequestIDs1[index];
    SCOPED_TRACE(base::StringPrintf(
        "handler 1, index %zu, id %d", index, request.header.msgh_id));

    EXPECT_FALSE(server.MachMessageServerFunction(
        &request.header, &reply.Head, &destroy_complex_request));
    EXPECT_EQ(reply.RetCode, kReturnCode1);
    EXPECT_TRUE(destroy_complex_request);
  }

  for (size_t index = 0; index < std::size(kRequestIDs2); ++index) {
    request.header.msgh_id = kRequestIDs2[index];
    SCOPED_TRACE(base::StringPrintf(
        "handler 2, index %zu, id %d", index, request.header.msgh_id));

    EXPECT_TRUE(server.MachMessageServerFunction(
        &request.header, &reply.Head, &destroy_complex_request));
    EXPECT_EQ(reply.RetCode, kReturnCode2);
    EXPECT_TRUE(destroy_complex_request);
  }
}

// CompositeMachMessageServer can’t deal with two handlers that want to handle
// the same request ID.
TEST(CompositeMachMessageServerDeathTest, DuplicateRequestID) {
  constexpr mach_msg_id_t kRequestID = 400;

  TestMachMessageHandler handlers[2];
  handlers[0].AddRequestID(kRequestID);
  handlers[1].AddRequestID(kRequestID);

  CompositeMachMessageServer server;

  server.AddHandler(&handlers[0]);
  EXPECT_DEATH_CHECK(server.AddHandler(&handlers[1]), "duplicate request ID");
}

}  // namespace
}  // namespace test
}  // namespace crashpad
