/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 *
 * Tests for VbTryLoadKernel()
 */

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

#include "gbb_header.h"
#include "load_kernel_fw.h"
#include "rollback_index.h"
#include "test_common.h"
#include "utility.h"
#include "vboot_api.h"


#define MAX_TEST_DISKS 10
#define DEFAULT_COUNT -1

typedef struct {
    uint64_t bytes_per_lba;
    uint64_t lba_count;
    uint32_t flags;
    const char *diskname;
} disk_desc_t;

typedef struct {
  char *name;

  /* inputs for test case */
  uint32_t want_flags;
  VbError_t diskgetinfo_return_val;
  disk_desc_t disks_to_provide[MAX_TEST_DISKS];
  int disk_count_to_return;
  VbError_t loadkernel_return_val[MAX_TEST_DISKS];

  /* outputs from test */
  uint32_t expected_recovery_request_val;
  const char *expected_to_find_disk;
  const char *expected_to_load_disk;
  uint32_t expected_return_val;

} test_case_t;

/****************************************************************************/
/* Test cases */

static const char pickme[] = "correct choice";
#define DONT_CARE ((const char *)42)

test_case_t test[] = {

  {
    .name = "first removable drive",
    .want_flags = VB_DISK_FLAG_REMOVABLE,
    .disks_to_provide = {
      {512,   10,  VB_DISK_FLAG_REMOVABLE, 0 }, /* too small */
      {2048, 100,  VB_DISK_FLAG_REMOVABLE, 0 }, /* wrong LBA */
      {512,  100,  VB_DISK_FLAG_FIXED, 0 },     /* wrong type */
      {512,  100,  0, 0 },                      /* wrong flags */
      {512,  100,  -1, 0 },                     /* still wrong flags */
      {512,  100,  VB_DISK_FLAG_REMOVABLE, pickme},
      {512,  100,  VB_DISK_FLAG_REMOVABLE, "holygrail"}, /* already got one */
    },
    .disk_count_to_return = DEFAULT_COUNT,
    .diskgetinfo_return_val = VBERROR_SUCCESS,
    .loadkernel_return_val = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, },

    .expected_recovery_request_val = VBNV_RECOVERY_NOT_REQUESTED,
    .expected_to_find_disk = pickme,
    .expected_to_load_disk = pickme,
    .expected_return_val = VBERROR_SUCCESS
  },

  {
    .name = "second removable drive",
    .want_flags = VB_DISK_FLAG_REMOVABLE,
    .disks_to_provide = {
      {512,  100,  0, 0 },                      /* wrong flags */
      {512,  100,  VB_DISK_FLAG_REMOVABLE, "not yet"},
      {512,  100,  VB_DISK_FLAG_REMOVABLE, pickme},
    },
    .disk_count_to_return = DEFAULT_COUNT,
    .diskgetinfo_return_val = VBERROR_SUCCESS,
    .loadkernel_return_val = { 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, },

    .expected_recovery_request_val = VBNV_RECOVERY_NOT_REQUESTED,
    .expected_to_find_disk = pickme,
    .expected_to_load_disk = pickme,
    .expected_return_val = VBERROR_SUCCESS
  },

  {
    .name = "first fixed drive",
    .want_flags = VB_DISK_FLAG_FIXED,
    .disks_to_provide = {
      {512,   10,  VB_DISK_FLAG_FIXED, 0 }, /* too small */
      {2048, 100,  VB_DISK_FLAG_FIXED, 0 }, /* wrong LBA */
      {512,  100,  VB_DISK_FLAG_REMOVABLE, 0 }, /* wrong type */
      {512,  100,  0, 0 },                      /* wrong flags */
      {512,  100,  -1, 0 },                     /* still wrong flags */
      {512,  100,  VB_DISK_FLAG_REMOVABLE|VB_DISK_FLAG_FIXED, 0 }, /* flags */
      {512,  100,  VB_DISK_FLAG_FIXED, pickme},
      {512,  100,  VB_DISK_FLAG_FIXED, "holygrail"}, /* already got one */
    },
    .disk_count_to_return = DEFAULT_COUNT,
    .diskgetinfo_return_val = VBERROR_SUCCESS,
    .loadkernel_return_val = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, },

    .expected_recovery_request_val = VBNV_RECOVERY_NOT_REQUESTED,
    .expected_to_find_disk = pickme,
    .expected_to_load_disk = pickme,
    .expected_return_val = VBERROR_SUCCESS
  },

  {
    .name = "no drives at all",
    .want_flags = VB_DISK_FLAG_FIXED,
    .disks_to_provide = {
    },
    .disk_count_to_return = DEFAULT_COUNT,
    .diskgetinfo_return_val = VBERROR_SUCCESS,
    .loadkernel_return_val = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, },

    .expected_recovery_request_val = VBNV_RECOVERY_RW_NO_DISK,
    .expected_to_find_disk = 0,
    .expected_to_load_disk = 0,
    .expected_return_val = VBERROR_NO_DISK_FOUND
  },

  {
    .name = "no valid drives",
    .want_flags = VB_DISK_FLAG_FIXED,
    .disks_to_provide = {
      {512,   10,  VB_DISK_FLAG_FIXED, 0 }, /* too small */
      {2048, 100,  VB_DISK_FLAG_FIXED, 0 }, /* wrong LBA */
      {512,  100,  VB_DISK_FLAG_REMOVABLE, 0 }, /* wrong type */
      {512,  100,  0, 0 },                      /* wrong flags */
      {512,  100,  -1, 0 },                     /* still wrong flags */
      {512,  100,  VB_DISK_FLAG_FIXED, "bad1"}, /* doesn't load */
      {512,  100,  VB_DISK_FLAG_FIXED, "bad2"}, /* doesn't load */
    },
    .disk_count_to_return = DEFAULT_COUNT,
    .diskgetinfo_return_val = VBERROR_SUCCESS,
    .loadkernel_return_val = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, },

    .expected_recovery_request_val = VBNV_RECOVERY_RW_NO_DISK,
    .expected_to_find_disk = DONT_CARE,
    .expected_to_load_disk = 0,
    .expected_return_val = 1
  },

};


/****************************************************************************/

/* Mock data */
static LoadKernelParams lkparams;
static VbDiskInfo mock_disks[MAX_TEST_DISKS];
static test_case_t *t;
static int load_kernel_calls;
static uint32_t got_recovery_request_val;
static const char *got_find_disk;
static const char *got_load_disk;
static uint32_t got_return_val;

/* Reset mock data (for use before each test) */
static void ResetMocks(int i) {
  Memset(&lkparams, 0, sizeof(lkparams));
  Memset(&mock_disks, 0, sizeof(mock_disks));
  load_kernel_calls = 0;

  got_recovery_request_val = VBNV_RECOVERY_NOT_REQUESTED;
  got_find_disk = 0;
  got_load_disk = 0;
  got_return_val = 0xdeadbeef;

  t = test+i;
}

int is_nonzero(const void *vptr, size_t count) {
  const char *p = (const char *)vptr;
  while (count--)
    if (*p++)
      return 1;

  return 0;
}

int CheckResults(void) {
  VBDEBUG(("%s()\n", __FUNCTION__));
  VBDEBUG(("  recovery_request: %x %x\n",
           t->expected_recovery_request_val, got_recovery_request_val));
  VBDEBUG(("  find_disk: (%s) (%s)\n",
           (t->expected_to_find_disk == DONT_CARE
            ? "DONT CARE"
            : t->expected_to_find_disk),
           got_find_disk));
  VBDEBUG(("  load_disk: (%s) (%s)\n",
           (t->expected_to_load_disk == DONT_CARE
            ? "DONT CARE"
            : t->expected_to_load_disk),
           got_load_disk));
  VBDEBUG(("  return_val: %x %x\n",
           t->expected_return_val, got_return_val));
  return (t->expected_recovery_request_val == got_recovery_request_val &&
          (t->expected_to_find_disk == DONT_CARE ||
           t->expected_to_find_disk == got_find_disk) &&
          (t->expected_to_load_disk == DONT_CARE ||
           t->expected_to_load_disk == got_load_disk) &&
          t->expected_return_val == got_return_val);
}

/****************************************************************************/
/* Mocked verification functions */

VbError_t VbExDiskGetInfo(VbDiskInfo** infos_ptr, uint32_t* count,
                          uint32_t disk_flags) {
  int i;
  int num_disks = 0;

  VBDEBUG(("My %s\n", __FUNCTION__));

  *infos_ptr = mock_disks;

  for(i=0; i<MAX_TEST_DISKS; i++) {
    if (is_nonzero(&t->disks_to_provide[i], sizeof(t->disks_to_provide[i]))) {
      mock_disks[num_disks].bytes_per_lba =
        t->disks_to_provide[i].bytes_per_lba;
      mock_disks[num_disks].lba_count = t->disks_to_provide[i].lba_count;
      mock_disks[num_disks].flags = t->disks_to_provide[i].flags;
      mock_disks[num_disks].handle =
        (VbExDiskHandle_t)t->disks_to_provide[i].diskname;
      VBDEBUG(("  mock_disk[%d] %lld %lld 0x%x %s\n", i,
               mock_disks[num_disks].bytes_per_lba,
               mock_disks[num_disks].lba_count,
               mock_disks[num_disks].flags,
               (mock_disks[num_disks].handle
                ? (char *)mock_disks[num_disks].handle
                : "0")));
      num_disks++;
    } else {
      mock_disks[num_disks].handle = (VbExDiskHandle_t)"INVALID";
    }
  }

  if (t->disk_count_to_return >= 0)
    *count = t->disk_count_to_return;
  else
    *count = num_disks;

  VBDEBUG(("  *count=%lld\n", *count));
  VBDEBUG(("  return 0x%x\n", t->diskgetinfo_return_val));

  return t->diskgetinfo_return_val;
}

VbError_t VbExDiskFreeInfo(VbDiskInfo* infos,
                           VbExDiskHandle_t preserve_handle) {
  got_load_disk = (const char *)preserve_handle;
  VBDEBUG(("%s(): got_load_disk = %s\n", __FUNCTION__,
           got_load_disk ? got_load_disk : "0"));
  return VBERROR_SUCCESS;
}

VbError_t LoadKernel(LoadKernelParams* params) {
  got_find_disk = (const char *)params->disk_handle;
  VBDEBUG(("%s(%d): got_find_disk = %s\n", __FUNCTION__,
           load_kernel_calls,
           got_find_disk ? got_find_disk : "0"));
  return t->loadkernel_return_val[load_kernel_calls++];
}

int VbNvSet(VbNvContext* context, VbNvParam param, uint32_t value) {
  VBDEBUG(("%s(): got_recovery_request_val = %d (0x%x)\n", __FUNCTION__,
           value, value));
  got_recovery_request_val = value;
  return 0;
}

/****************************************************************************/



/* This is not declared in any headers, so declare it here. */
uint32_t VbTryLoadKernel(VbCommonParams* cparams, LoadKernelParams* p,
                         uint32_t get_info_flags);


static void VbTryLoadKernelTest(void) {
  int i;
  int num_tests =  sizeof(test) / sizeof(test[0]);

  for (i=0; i<num_tests; i++) {
    VBDEBUG(("STARTING %s ...\n", test[i].name));
    ResetMocks(i);
    got_return_val = VbTryLoadKernel(0, &lkparams, test[i].want_flags);
    VBDEBUG(("VbTryLoadKernel(): got_return_val = 0x%x\n", got_return_val));
    TEST_TRUE(CheckResults(), test[i].name);
  }
}


/* disable MSVC warnings on unused arguments */
__pragma(warning (disable: 4100))

int main(int argc, char* argv[]) {
  int error_code = 0;

  VbTryLoadKernelTest();

  if (!gTestSuccess)
    error_code = 255;

  return error_code;
}
