// Copyright 2019 The Fuchsia 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 <zircon/syscalls/port.h>

#include <gtest/gtest.h>

#include "tools/fidlcat/interception_tests/interception_workflow_test.h"

namespace fidlcat {

// zx_vcpu_create tests.

std::unique_ptr<SystemCallTest> ZxVcpuCreate(int64_t result, std::string_view result_name,
                                             zx_handle_t guest, uint32_t options, zx_vaddr_t entry,
                                             zx_handle_t* out) {
  auto value = std::make_unique<SystemCallTest>("zx_vcpu_create", result, result_name);
  value->AddInput(guest);
  value->AddInput(options);
  value->AddInput(entry);
  value->AddInput(reinterpret_cast<uint64_t>(out));
  return value;
}

#define VCPU_CREATE_DISPLAY_TEST_CONTENT(result, expected) \
  zx_handle_t out = kHandleOut;                            \
  PerformDisplayTest("$plt(zx_vcpu_create)",               \
                     ZxVcpuCreate(result, #result, kHandle, 0, 0x123456, &out), expected);

#define VCPU_CREATE_DISPLAY_TEST(name, errno, expected)                                            \
  TEST_F(InterceptionWorkflowTestX64, name) { VCPU_CREATE_DISPLAY_TEST_CONTENT(errno, expected); } \
  TEST_F(InterceptionWorkflowTestArm, name) { VCPU_CREATE_DISPLAY_TEST_CONTENT(errno, expected); }

VCPU_CREATE_DISPLAY_TEST(
    ZxVcpuCreate, ZX_OK,
    "\n"
    "\x1B[32m0.000000\x1B[0m "
    "test_3141 \x1B[31m3141\x1B[0m:\x1B[31m8764\x1B[0m "
    "zx_vcpu_create("
    "guest: \x1B[32mhandle\x1B[0m = \x1B[31mcefa1db0\x1B[0m, "
    "options: \x1B[32muint32\x1B[0m = \x1B[34m0\x1B[0m, "
    "entry: \x1B[32mzx.vaddr\x1B[0m = \x1B[34m0000000000123456\x1B[0m)\n"
    "\x1B[32m0.000000\x1B[0m "
    "  -> \x1B[32mZX_OK\x1B[0m (out: \x1B[32mhandle\x1B[0m = \x1B[31mbde90caf\x1B[0m)\n");

// zx_vcpu_resume tests.

std::unique_ptr<SystemCallTest> ZxVcpuResume(int64_t result, std::string_view result_name,
                                             zx_handle_t handle, zx_port_packet_t* packet) {
  auto value = std::make_unique<SystemCallTest>("zx_vcpu_resume", result, result_name);
  value->AddInput(handle);
  value->AddInput(reinterpret_cast<uint64_t>(packet));
  return value;
}

#define VCPU_RESUME_DISPLAY_TEST_CONTENT(result, expected)                                    \
  zx_port_packet_t packet = {.key = kKey,                                                     \
                             .type = ZX_PKT_TYPE_GUEST_VCPU,                                  \
                             .status = ZX_OK,                                                 \
                             .guest_vcpu = {.startup.id = 1234,                               \
                                            .startup.entry = 0x123456,                        \
                                            .type = ZX_PKT_GUEST_VCPU_STARTUP,                \
                                            .reserved = 0}};                                  \
  PerformDisplayTest("$plt(zx_vcpu_resume)", ZxVcpuResume(result, #result, kHandle, &packet), \
                     expected);

#define VCPU_RESUME_DISPLAY_TEST(name, errno, expected)                                            \
  TEST_F(InterceptionWorkflowTestX64, name) { VCPU_RESUME_DISPLAY_TEST_CONTENT(errno, expected); } \
  TEST_F(InterceptionWorkflowTestArm, name) { VCPU_RESUME_DISPLAY_TEST_CONTENT(errno, expected); }

VCPU_RESUME_DISPLAY_TEST(
    ZxVcpuResume, ZX_OK,
    "\n"
    "\x1B[32m0.000000\x1B[0m "
    "test_3141 \x1B[31m3141\x1B[0m:\x1B[31m8764\x1B[0m "
    "zx_vcpu_resume(handle: \x1B[32mhandle\x1B[0m = \x1B[31mcefa1db0\x1B[0m)\n"
    "\x1B[32m0.000000\x1B[0m "
    "  -> \x1B[32mZX_OK\x1B[0m\n"
    "    packet: \x1B[32mzx_port_packet_t\x1B[0m = {\n"
    "      key: \x1B[32muint64\x1B[0m = \x1B[34m1234\x1B[0m\n"
    "      type: \x1B[32mzx.port_packet::type\x1B[0m = \x1B[34mZX_PKT_TYPE_GUEST_VCPU\x1B[0m\n"
    "      status: \x1B[32mzx.status\x1B[0m = \x1B[32mZX_OK\x1B[0m\n"
    "      guest_vcpu: \x1B[32mzx_packet_guest_vcpu_t\x1B[0m = {\n"
    "        type: \x1B[32mzx.packet_guest_vcpu::type\x1B[0m = "
    "\x1B[34mZX_PKT_GUEST_VCPU_STARTUP\x1B[0m\n"
    "        startup: \x1B[32mzx_packet_guest_vcpu_startup_t\x1B[0m = { "
    "id: \x1B[32muint64\x1B[0m = \x1B[34m1234\x1B[0m, "
    "entry: \x1B[32mzx.gpaddr\x1B[0m = \x1B[34m0000000000123456\x1B[0m"
    " }\n"
    "        reserved: \x1B[32muint64\x1B[0m = \x1B[34m0\x1B[0m\n"
    "      }\n"
    "    }\n");

// zx_vcpu_interrupt tests.

std::unique_ptr<SystemCallTest> ZxVcpuInterrupt(int64_t result, std::string_view result_name,
                                                zx_handle_t handle, uint32_t vector) {
  auto value = std::make_unique<SystemCallTest>("zx_vcpu_interrupt", result, result_name);
  value->AddInput(handle);
  value->AddInput(vector);
  return value;
}

#define VCPU_INTERRUPT_DISPLAY_TEST_CONTENT(result, expected)                                  \
  PerformDisplayTest("$plt(zx_vcpu_interrupt)", ZxVcpuInterrupt(result, #result, kHandle, 10), \
                     expected);

#define VCPU_INTERRUPT_DISPLAY_TEST(name, errno, expected) \
  TEST_F(InterceptionWorkflowTestX64, name) {              \
    VCPU_INTERRUPT_DISPLAY_TEST_CONTENT(errno, expected);  \
  }                                                        \
  TEST_F(InterceptionWorkflowTestArm, name) {              \
    VCPU_INTERRUPT_DISPLAY_TEST_CONTENT(errno, expected);  \
  }

VCPU_INTERRUPT_DISPLAY_TEST(ZxVcpuInterrupt, ZX_OK,
                            "\n"
                            "\x1B[32m0.000000\x1B[0m "
                            "test_3141 \x1B[31m3141\x1B[0m:\x1B[31m8764\x1B[0m "
                            "zx_vcpu_interrupt("
                            "handle: \x1B[32mhandle\x1B[0m = \x1B[31mcefa1db0\x1B[0m, "
                            "vector: \x1B[32muint32\x1B[0m = \x1B[34m10\x1B[0m)\n"
                            "\x1B[32m0.000000\x1B[0m "
                            "  -> \x1B[32mZX_OK\x1B[0m\n");

// zx_vcpu_read_state tests.

std::unique_ptr<SystemCallTest> ZxVcpuReadState(int64_t result, std::string_view result_name,
                                                zx_handle_t handle, uint32_t kind, void* buffer,
                                                size_t buffer_size) {
  auto value = std::make_unique<SystemCallTest>("zx_vcpu_read_state", result, result_name);
  value->AddInput(handle);
  value->AddInput(kind);
  value->AddInput(reinterpret_cast<uint64_t>(buffer));
  value->AddInput(buffer_size);
  return value;
}

#define VCPU_READ_STATE_DISPLAY_TEST_CONTENT(result, buffer, expected)                   \
  PerformDisplayTest(                                                                    \
      "$plt(zx_vcpu_read_state)",                                                        \
      ZxVcpuReadState(result, #result, kHandle, ZX_VCPU_STATE, &buffer, sizeof(buffer)), \
      expected);

TEST_F(InterceptionWorkflowTestArm, ZxVcpuReadStateAArch64) {
  zx_vcpu_state_aarch64_t buffer;
  for (int i = 0; i < 31; ++i) {
    buffer.x[i] = i;
  }
  buffer.sp = 0x1234576;
  buffer.cpsr = 0xe0000000;
  VCPU_READ_STATE_DISPLAY_TEST_CONTENT(
      ZX_OK, buffer,
      "\n"
      "\x1B[32m0.000000\x1B[0m "
      "test_3141 \x1B[31m3141\x1B[0m:\x1B[31m8764\x1B[0m "
      "zx_vcpu_read_state("
      "handle: \x1B[32mhandle\x1B[0m = \x1B[31mcefa1db0\x1B[0m, "
      "kind: \x1B[32mzx_vcpu_t\x1B[0m = \x1B[31mZX_VCPU_STATE\x1B[0m)\n"
      "\x1B[32m0.000000\x1B[0m "
      "  -> \x1B[32mZX_OK\x1B[0m\n"
      "    buffer: \x1B[32mzx_vcpu_state_aarch64_t\x1B[0m = {\n"
      "      x: vector<\x1B[32muint64\x1B[0m> = [ "
      "\x1B[34m0000000000000000\x1B[0m, \x1B[34m0000000000000001\x1B[0m, "
      "\x1B[34m0000000000000002\x1B[0m, \x1B[34m0000000000000003\x1B[0m, "
      "\x1B[34m0000000000000004\x1B[0m, \x1B[34m0000000000000005\x1B[0m, "
      "\x1B[34m0000000000000006\x1B[0m, \x1B[34m0000000000000007\x1B[0m, "
      "\x1B[34m0000000000000008\x1B[0m, \x1B[34m0000000000000009\x1B[0m, "
      "\x1B[34m000000000000000a\x1B[0m, \x1B[34m000000000000000b\x1B[0m, "
      "\x1B[34m000000000000000c\x1B[0m, \x1B[34m000000000000000d\x1B[0m, "
      "\x1B[34m000000000000000e\x1B[0m, \x1B[34m000000000000000f\x1B[0m, "
      "\x1B[34m0000000000000010\x1B[0m, \x1B[34m0000000000000011\x1B[0m, "
      "\x1B[34m0000000000000012\x1B[0m, \x1B[34m0000000000000013\x1B[0m, "
      "\x1B[34m0000000000000014\x1B[0m, \x1B[34m0000000000000015\x1B[0m, "
      "\x1B[34m0000000000000016\x1B[0m, \x1B[34m0000000000000017\x1B[0m, "
      "\x1B[34m0000000000000018\x1B[0m, \x1B[34m0000000000000019\x1B[0m, "
      "\x1B[34m000000000000001a\x1B[0m, \x1B[34m000000000000001b\x1B[0m, "
      "\x1B[34m000000000000001c\x1B[0m, \x1B[34m000000000000001d\x1B[0m, "
      "\x1B[34m000000000000001e\x1B[0m ]\n"
      "      sp: \x1B[32muint64\x1B[0m = \x1B[34m0000000001234576\x1B[0m\n"
      "      cpsr: \x1B[32muint32\x1B[0m = \x1B[34me0000000\x1B[0m\n"
      "    }\n");
}

TEST_F(InterceptionWorkflowTestX64, ZxVcpuReadStateX86) {
  zx_vcpu_state_x86_t buffer = {.rax = 1,
                                .rcx = 2,
                                .rdx = 3,
                                .rbx = 4,
                                .rsp = 5,
                                .rbp = 6,
                                .rsi = 7,
                                .rdi = 8,
                                .r8 = 9,
                                .r9 = 10,
                                .r10 = 11,
                                .r11 = 12,
                                .r12 = 13,
                                .r13 = 14,
                                .r14 = 15,
                                .r15 = 16,
                                .rflags = 0x1234};
  VCPU_READ_STATE_DISPLAY_TEST_CONTENT(
      ZX_OK, buffer,
      "\n"
      "\x1B[32m0.000000\x1B[0m "
      "test_3141 \x1B[31m3141\x1B[0m:\x1B[31m8764\x1B[0m "
      "zx_vcpu_read_state("
      "handle: \x1B[32mhandle\x1B[0m = \x1B[31mcefa1db0\x1B[0m, "
      "kind: \x1B[32mzx_vcpu_t\x1B[0m = \x1B[31mZX_VCPU_STATE\x1B[0m)\n"
      "\x1B[32m0.000000\x1B[0m "
      "  -> \x1B[32mZX_OK\x1B[0m\n"
      "    buffer: \x1B[32mzx_vcpu_state_x86_t\x1B[0m = {\n"
      "      rax: \x1B[32muint64\x1B[0m = \x1B[34m0000000000000001\x1B[0m\n"
      "      rcx: \x1B[32muint64\x1B[0m = \x1B[34m0000000000000002\x1B[0m\n"
      "      rdx: \x1B[32muint64\x1B[0m = \x1B[34m0000000000000003\x1B[0m\n"
      "      rbx: \x1B[32muint64\x1B[0m = \x1B[34m0000000000000004\x1B[0m\n"
      "      rsp: \x1B[32muint64\x1B[0m = \x1B[34m0000000000000005\x1B[0m\n"
      "      rbp: \x1B[32muint64\x1B[0m = \x1B[34m0000000000000006\x1B[0m\n"
      "      rsi: \x1B[32muint64\x1B[0m = \x1B[34m0000000000000007\x1B[0m\n"
      "      rdi: \x1B[32muint64\x1B[0m = \x1B[34m0000000000000008\x1B[0m\n"
      "      r8: \x1B[32muint64\x1B[0m = \x1B[34m0000000000000009\x1B[0m\n"
      "      r9: \x1B[32muint64\x1B[0m = \x1B[34m000000000000000a\x1B[0m\n"
      "      r10: \x1B[32muint64\x1B[0m = \x1B[34m000000000000000b\x1B[0m\n"
      "      r11: \x1B[32muint64\x1B[0m = \x1B[34m000000000000000c\x1B[0m\n"
      "      r12: \x1B[32muint64\x1B[0m = \x1B[34m000000000000000d\x1B[0m\n"
      "      r13: \x1B[32muint64\x1B[0m = \x1B[34m000000000000000e\x1B[0m\n"
      "      r14: \x1B[32muint64\x1B[0m = \x1B[34m000000000000000f\x1B[0m\n"
      "      r15: \x1B[32muint64\x1B[0m = \x1B[34m0000000000000010\x1B[0m\n"
      "      rflags: \x1B[32muint64\x1B[0m = \x1B[34m0000000000001234\x1B[0m\n"
      "    }\n");
}

// zx_vcpu_write_state tests.

std::unique_ptr<SystemCallTest> ZxVcpuWriteState(int64_t result, std::string_view result_name,
                                                 zx_handle_t handle, uint32_t kind,
                                                 const void* buffer, size_t buffer_size) {
  auto value = std::make_unique<SystemCallTest>("zx_vcpu_write_state", result, result_name);
  value->AddInput(handle);
  value->AddInput(kind);
  value->AddInput(reinterpret_cast<uint64_t>(buffer));
  value->AddInput(buffer_size);
  return value;
}

#define VCPU_WRITE_STATE_DISPLAY_TEST_CONTENT(result, buffer, expected)                  \
  PerformDisplayTest(                                                                    \
      "$plt(zx_vcpu_write_state)",                                                       \
      ZxVcpuReadState(result, #result, kHandle, ZX_VCPU_STATE, &buffer, sizeof(buffer)), \
      expected);

TEST_F(InterceptionWorkflowTestArm, ZxVcpuWriteStateAArch64) {
  zx_vcpu_state_aarch64_t buffer;
  for (int i = 0; i < 31; ++i) {
    buffer.x[i] = i;
  }
  buffer.sp = 0x1234576;
  buffer.cpsr = 0xe0000000;
  VCPU_WRITE_STATE_DISPLAY_TEST_CONTENT(
      ZX_OK, buffer,
      "\n"
      "\x1B[32m0.000000\x1B[0m "
      "test_3141 \x1B[31m3141\x1B[0m:\x1B[31m8764\x1B[0m "
      "zx_vcpu_write_state("
      "handle: \x1B[32mhandle\x1B[0m = \x1B[31mcefa1db0\x1B[0m, "
      "kind: \x1B[32mzx_vcpu_t\x1B[0m = \x1B[31mZX_VCPU_STATE\x1B[0m)\n"
      "  buffer: \x1B[32mzx_vcpu_state_aarch64_t\x1B[0m = {\n"
      "    x: vector<\x1B[32muint64\x1B[0m> = [ "
      "\x1B[34m0000000000000000\x1B[0m, \x1B[34m0000000000000001\x1B[0m, "
      "\x1B[34m0000000000000002\x1B[0m, \x1B[34m0000000000000003\x1B[0m, "
      "\x1B[34m0000000000000004\x1B[0m, \x1B[34m0000000000000005\x1B[0m, "
      "\x1B[34m0000000000000006\x1B[0m, \x1B[34m0000000000000007\x1B[0m, "
      "\x1B[34m0000000000000008\x1B[0m, \x1B[34m0000000000000009\x1B[0m, "
      "\x1B[34m000000000000000a\x1B[0m, \x1B[34m000000000000000b\x1B[0m, "
      "\x1B[34m000000000000000c\x1B[0m, \x1B[34m000000000000000d\x1B[0m, "
      "\x1B[34m000000000000000e\x1B[0m, \x1B[34m000000000000000f\x1B[0m, "
      "\x1B[34m0000000000000010\x1B[0m, \x1B[34m0000000000000011\x1B[0m, "
      "\x1B[34m0000000000000012\x1B[0m, \x1B[34m0000000000000013\x1B[0m, "
      "\x1B[34m0000000000000014\x1B[0m, \x1B[34m0000000000000015\x1B[0m, "
      "\x1B[34m0000000000000016\x1B[0m, \x1B[34m0000000000000017\x1B[0m, "
      "\x1B[34m0000000000000018\x1B[0m, \x1B[34m0000000000000019\x1B[0m, "
      "\x1B[34m000000000000001a\x1B[0m, \x1B[34m000000000000001b\x1B[0m, "
      "\x1B[34m000000000000001c\x1B[0m, \x1B[34m000000000000001d\x1B[0m, "
      "\x1B[34m000000000000001e\x1B[0m ]\n"
      "    sp: \x1B[32muint64\x1B[0m = \x1B[34m0000000001234576\x1B[0m\n"
      "    cpsr: \x1B[32muint32\x1B[0m = \x1B[34me0000000\x1B[0m\n"
      "  }\n"
      "\x1B[32m0.000000\x1B[0m "
      "  -> \x1B[32mZX_OK\x1B[0m\n");
}

TEST_F(InterceptionWorkflowTestX64, ZxVcpuWriteStateX86) {
  zx_vcpu_state_x86_t buffer = {.rax = 1,
                                .rcx = 2,
                                .rdx = 3,
                                .rbx = 4,
                                .rsp = 5,
                                .rbp = 6,
                                .rsi = 7,
                                .rdi = 8,
                                .r8 = 9,
                                .r9 = 10,
                                .r10 = 11,
                                .r11 = 12,
                                .r12 = 13,
                                .r13 = 14,
                                .r14 = 15,
                                .r15 = 16,
                                .rflags = 0x1234};
  VCPU_WRITE_STATE_DISPLAY_TEST_CONTENT(
      ZX_OK, buffer,
      "\n"
      "\x1B[32m0.000000\x1B[0m "
      "test_3141 \x1B[31m3141\x1B[0m:\x1B[31m8764\x1B[0m "
      "zx_vcpu_write_state("
      "handle: \x1B[32mhandle\x1B[0m = \x1B[31mcefa1db0\x1B[0m, "
      "kind: \x1B[32mzx_vcpu_t\x1B[0m = \x1B[31mZX_VCPU_STATE\x1B[0m)\n"
      "  buffer: \x1B[32mzx_vcpu_state_x86_t\x1B[0m = {\n"
      "    rax: \x1B[32muint64\x1B[0m = \x1B[34m0000000000000001\x1B[0m\n"
      "    rcx: \x1B[32muint64\x1B[0m = \x1B[34m0000000000000002\x1B[0m\n"
      "    rdx: \x1B[32muint64\x1B[0m = \x1B[34m0000000000000003\x1B[0m\n"
      "    rbx: \x1B[32muint64\x1B[0m = \x1B[34m0000000000000004\x1B[0m\n"
      "    rsp: \x1B[32muint64\x1B[0m = \x1B[34m0000000000000005\x1B[0m\n"
      "    rbp: \x1B[32muint64\x1B[0m = \x1B[34m0000000000000006\x1B[0m\n"
      "    rsi: \x1B[32muint64\x1B[0m = \x1B[34m0000000000000007\x1B[0m\n"
      "    rdi: \x1B[32muint64\x1B[0m = \x1B[34m0000000000000008\x1B[0m\n"
      "    r8: \x1B[32muint64\x1B[0m = \x1B[34m0000000000000009\x1B[0m\n"
      "    r9: \x1B[32muint64\x1B[0m = \x1B[34m000000000000000a\x1B[0m\n"
      "    r10: \x1B[32muint64\x1B[0m = \x1B[34m000000000000000b\x1B[0m\n"
      "    r11: \x1B[32muint64\x1B[0m = \x1B[34m000000000000000c\x1B[0m\n"
      "    r12: \x1B[32muint64\x1B[0m = \x1B[34m000000000000000d\x1B[0m\n"
      "    r13: \x1B[32muint64\x1B[0m = \x1B[34m000000000000000e\x1B[0m\n"
      "    r14: \x1B[32muint64\x1B[0m = \x1B[34m000000000000000f\x1B[0m\n"
      "    r15: \x1B[32muint64\x1B[0m = \x1B[34m0000000000000010\x1B[0m\n"
      "    rflags: \x1B[32muint64\x1B[0m = \x1B[34m0000000000001234\x1B[0m\n"
      "  }\n"
      "\x1B[32m0.000000\x1B[0m "
      "  -> \x1B[32mZX_OK\x1B[0m\n");
}

#define VCPU_WRITE_STATE_IO_DISPLAY_TEST_CONTENT(result, expected) \
  zx_vcpu_io_t buffer = {.access_size = 4, .u32 = 0x12345678};     \
  PerformDisplayTest(                                              \
      "$plt(zx_vcpu_write_state)",                                 \
      ZxVcpuReadState(result, #result, kHandle, ZX_VCPU_IO, &buffer, sizeof(buffer)), expected);

#define VCPU_WRITE_STATE_IO_DISPLAY_TEST(name, errno, expected) \
  TEST_F(InterceptionWorkflowTestX64, name) {                   \
    VCPU_WRITE_STATE_IO_DISPLAY_TEST_CONTENT(errno, expected);  \
  }                                                             \
  TEST_F(InterceptionWorkflowTestArm, name) {                   \
    VCPU_WRITE_STATE_IO_DISPLAY_TEST_CONTENT(errno, expected);  \
  }

VCPU_WRITE_STATE_IO_DISPLAY_TEST(
    ZxVcpuWriteStateIo, ZX_OK,
    "\n"
    "\x1B[32m0.000000\x1B[0m "
    "test_3141 \x1B[31m3141\x1B[0m:\x1B[31m8764\x1B[0m "
    "zx_vcpu_write_state("
    "handle: \x1B[32mhandle\x1B[0m = \x1B[31mcefa1db0\x1B[0m, "
    "kind: \x1B[32mzx_vcpu_t\x1B[0m = \x1B[31mZX_VCPU_IO\x1B[0m)\n"
    "  buffer: \x1B[32mzx_vcpu_io_t\x1B[0m = {\n"
    "    access_size: \x1B[32muint8\x1B[0m = \x1B[34m4\x1B[0m\n"
    "    u8: \x1B[32muint8\x1B[0m = \x1B[34m78\x1B[0m\n"
    "    u16: \x1B[32muint16\x1B[0m = \x1B[34m5678\x1B[0m\n"
    "    u32: \x1B[32muint32\x1B[0m = \x1B[34m12345678\x1B[0m\n"
    "    data: vector<\x1B[32muint8\x1B[0m> = [ "
    "\x1B[34m78\x1B[0m, \x1B[34m56\x1B[0m, \x1B[34m34\x1B[0m, \x1B[34m12\x1B[0m ]\n"
    "  }\n"
    "\x1B[32m0.000000\x1B[0m "
    "  -> \x1B[32mZX_OK\x1B[0m\n");

}  // namespace fidlcat
