// 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 "mojo/edk/system/options_validation.h"

#include <stddef.h>
#include <stdint.h>

#include "mojo/public/c/system/macros.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace mojo {
namespace edk {
namespace {

// Declare a test options struct just as we do in actual public headers.

using TestOptionsFlags = uint32_t;

static_assert(MOJO_ALIGNOF(int64_t) == 8, "int64_t has weird alignment");
struct MOJO_ALIGNAS(8) TestOptions {
  uint32_t struct_size;
  TestOptionsFlags flags;
  uint32_t member1;
  uint32_t member2;
};
static_assert(sizeof(TestOptions) == 16, "TestOptions has wrong size");

const uint32_t kSizeOfTestOptions = static_cast<uint32_t>(sizeof(TestOptions));

TEST(OptionsValidationTest, Valid) {
  {
    const TestOptions kOptions = {kSizeOfTestOptions};
    UserOptionsReader<TestOptions> reader(&kOptions);
    EXPECT_TRUE(reader.is_valid());
    EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, flags, reader));
    EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member1, reader));
    EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member2, reader));
  }
  {
    const TestOptions kOptions = {static_cast<uint32_t>(
        offsetof(TestOptions, struct_size) + sizeof(uint32_t))};
    UserOptionsReader<TestOptions> reader(&kOptions);
    EXPECT_TRUE(reader.is_valid());
    EXPECT_FALSE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, flags, reader));
    EXPECT_FALSE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member1, reader));
    EXPECT_FALSE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member2, reader));
  }

  {
    const TestOptions kOptions = {
        static_cast<uint32_t>(offsetof(TestOptions, flags) + sizeof(uint32_t))};
    UserOptionsReader<TestOptions> reader(&kOptions);
    EXPECT_TRUE(reader.is_valid());
    EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, flags, reader));
    EXPECT_FALSE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member1, reader));
    EXPECT_FALSE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member2, reader));
  }
  {
    MOJO_ALIGNAS(8) char buf[sizeof(TestOptions) + 100] = {};
    TestOptions* options = reinterpret_cast<TestOptions*>(buf);
    options->struct_size = kSizeOfTestOptions + 1;
    UserOptionsReader<TestOptions> reader(options);
    EXPECT_TRUE(reader.is_valid());
    EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, flags, reader));
    EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member1, reader));
    EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member2, reader));
  }
  {
    MOJO_ALIGNAS(8) char buf[sizeof(TestOptions) + 100] = {};
    TestOptions* options = reinterpret_cast<TestOptions*>(buf);
    options->struct_size = kSizeOfTestOptions + 4;
    UserOptionsReader<TestOptions> reader(options);
    EXPECT_TRUE(reader.is_valid());
    EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, flags, reader));
    EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member1, reader));
    EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member2, reader));
  }
}

TEST(OptionsValidationTest, Invalid) {
  // Size too small:
  for (size_t i = 0; i < sizeof(uint32_t); i++) {
    TestOptions options = {static_cast<uint32_t>(i)};
    UserOptionsReader<TestOptions> reader(&options);
    EXPECT_FALSE(reader.is_valid()) << i;
  }
}

// These test invalid arguments that should cause death if we're being paranoid
// about checking arguments (which we would want to do if, e.g., we were in a
// true "kernel" situation, but we might not want to do otherwise for
// performance reasons). Probably blatant errors like passing in null pointers
// (for required pointer arguments) will still cause death, but perhaps not
// predictably.
TEST(OptionsValidationTest, InvalidDeath) {
#if defined(OFFICIAL_BUILD)
  const char kMemoryCheckFailedRegex[] = "";
#else
  const char kMemoryCheckFailedRegex[] = "Check failed";
#endif

  // Null:
  EXPECT_DEATH_IF_SUPPORTED(
      { UserOptionsReader<TestOptions> reader((nullptr)); },
      kMemoryCheckFailedRegex);

  // Unaligned:
  EXPECT_DEATH_IF_SUPPORTED(
      {
        UserOptionsReader<TestOptions> reader(
            reinterpret_cast<const TestOptions*>(1));
      },
      kMemoryCheckFailedRegex);
  // Note: The current implementation checks the size only after checking the
  // alignment versus that required for the |uint32_t| size, so it won't die in
  // the expected way if you pass, e.g., 4. So we have to manufacture a valid
  // pointer at an offset of alignment 4.
  EXPECT_DEATH_IF_SUPPORTED(
      {
        uint32_t buffer[100] = {};
        TestOptions* options = (reinterpret_cast<uintptr_t>(buffer) % 8 == 0)
                                   ? reinterpret_cast<TestOptions*>(&buffer[1])
                                   : reinterpret_cast<TestOptions*>(&buffer[0]);
        options->struct_size = static_cast<uint32_t>(sizeof(TestOptions));
        UserOptionsReader<TestOptions> reader(options);
      },
      kMemoryCheckFailedRegex);
}

}  // namespace
}  // namespace edk
}  // namespace mojo