/* Copyright (c) 2011 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.
 *
 * Routines for verifying a firmware image's signature.
 */

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

#include "fmap.h"
#include "gbb_header.h"
#include "host_common.h"
#include "host_misc.h"
#include "load_firmware_fw.h"


typedef struct _CallerInternal {
  struct {
    uint8_t* fw;
    uint64_t size;
  } firmware[2];
} CallerInternal;

static char* progname = NULL;
static char* image_path = NULL;


/* wrapper of FmapAreaIndex; print error when not found */
int FmapAreaIndexOrError(const FmapHeader* fh, const FmapAreaHeader* ah,
    const char* name);

int GetFirmwareBody(LoadFirmwareParams* params, uint64_t firmware_index) {
  CallerInternal* ci = (CallerInternal*) params->caller_internal;

  if (firmware_index != 0 && firmware_index != 1)
    return 1;

#if 0
  VbUpdateFirmwareBodyHash(params,
                         ci->firmware[firmware_index].fw,
                         ci->firmware[firmware_index].size);
#endif

  return 0;
}

/* Get GBB
 *
 * Return pointer to GBB from firmware image, or NULL if not found.
 *
 * [base_of_rom] pointer to firmware image
 * [fmap] pointer to Flash Map of firmware image
 * [gbb_size] GBB size will be stored here if GBB is found
 */
void* GetFirmwareGBB(const void* base_of_rom, const void* fmap,
                     uint64_t* gbb_size) {
  const FmapHeader* fh = (const FmapHeader*) fmap;
  const FmapAreaHeader* ah = (const FmapAreaHeader*)
    (fmap + sizeof(FmapHeader));
  int i = FmapAreaIndexOrError(fh, ah, "GBB");

  if (i < 0)
    return NULL;

  *gbb_size = ah[i].area_size;
  return (void*)(base_of_rom + ah[i].area_offset);
}

/* Get verification block
 *
 * Return zero if succeed, or non-zero if failed
 *
 * [base_of_rom] pointer to firmware image
 * [fmap] pointer to Flash Map of firmware image
 * [index] index of verification block
 * [verification_block_ptr] pointer to storing the found verification block
 * [verification_size_ptr] pointer to store the found verification block size
 */
int GetVerificationBlock(const void* base_of_rom, const void* fmap, int index,
    void** verification_block_ptr, uint64_t* verification_size_ptr) {
  const char* key_area_name[2] = {
    "VBLOCK_A",
    "VBLOCK_B"
  };
  const FmapHeader* fh = (const FmapHeader*) fmap;
  const FmapAreaHeader* ah = (const FmapAreaHeader*)
    (fmap + sizeof(FmapHeader));
  int i = FmapAreaIndexOrError(fh, ah, key_area_name[index]);
  const void* kb;
  const VbKeyBlockHeader* kbh;
  const VbFirmwarePreambleHeader* fph;

  if (i < 0)
    return 1;

  kb = base_of_rom + ah[i].area_offset;
  *verification_block_ptr = (void*) kb;

  kbh = (const VbKeyBlockHeader*) kb;
  fph = (const VbFirmwarePreambleHeader*) (kb + kbh->key_block_size);

  *verification_size_ptr = kbh->key_block_size + fph->preamble_size;

  return 0;
}

/* Return non-zero if not found */
int GetFirmwareData(const void* base_of_rom, const void* fmap, int index,
    void *verification_block, uint8_t** body_ptr, uint64_t *size_ptr) {
  const char* data_area_name[2] = {
    "FW_MAIN_A",
    "FW_MAIN_B"
  };
  const FmapHeader* fh = (const FmapHeader*) fmap;
  const FmapAreaHeader* ah = (const FmapAreaHeader*)
    (fmap + sizeof(FmapHeader));
  const VbKeyBlockHeader* kbh = (const VbKeyBlockHeader*) verification_block;
  const VbFirmwarePreambleHeader* fph = (const VbFirmwarePreambleHeader*)
    (verification_block + kbh->key_block_size);
  int i = FmapAreaIndexOrError(fh, ah, data_area_name[index]);

  if (i < 0)
    return 1;

  *body_ptr = (uint8_t*) (base_of_rom + ah[i].area_offset);
  *size_ptr = (uint64_t) fph->body_signature.data_size;
  return 0;
}

/* Verify firmware image [base_of_rom] using [fmap] for looking up areas.
 * Return zero on success, non-zero on error
 *
 * [base_of_rom] pointer to start of firmware image
 * [fmap] pointer to start of Flash Map of firmware image
 */
int DriveLoadFirmware(const void* base_of_rom, const void* fmap,
    const uint64_t boot_flags) {
  LoadFirmwareParams lfp;
  CallerInternal ci;

  VbError_t status;
  int index;

  void** vblock_ptr[2] = {
    &lfp.verification_block_0, &lfp.verification_block_1
  };
  uint64_t* vsize_ptr[2] = {
    &lfp.verification_size_0, &lfp.verification_size_1
  };

  /* Initialize LoadFirmwareParams lfp */

  lfp.caller_internal = &ci;
  lfp.gbb_data = GetFirmwareGBB(base_of_rom, fmap, &lfp.gbb_size);
  if (!lfp.gbb_data) {
    printf("ERROR: cannot get firmware GBB\n");
    return 1;
  }

  printf("firmware GBB at 0x%08" PRIx64 "\n",
      (uint64_t) (lfp.gbb_data - base_of_rom));

  /* Loop to initialize firmware key and data A / B */
  for (index = 0; index < 2; ++index) {
    if (GetVerificationBlock(base_of_rom, fmap, index,
          vblock_ptr[index], vsize_ptr[index])) {
      printf("ERROR: cannot get key block %d\n", index);
      return 1;
    }

    printf("verification block %d at 0x%08" PRIx64 "\n", index,
        (uint64_t) (*vblock_ptr[index] - base_of_rom));
    printf("verification block %d size is 0x%08" PRIx64 "\n", index,
        *vsize_ptr[index]);

    if (GetFirmwareData(base_of_rom, fmap, index, *vblock_ptr[index],
          &(ci.firmware[index].fw), &(ci.firmware[index].size))) {
      printf("ERROR: cannot get firmware body %d\n", index);
      return 1;
    }

    printf("firmware %c at 0x%08" PRIx64 "\n", "AB"[index],
        (uint64_t) ((void*) ci.firmware[index].fw - base_of_rom));
    printf("firmware %c size is 0x%08" PRIx64 "\n", "AB"[index],
        ci.firmware[index].size);
  }

  lfp.shared_data_blob = malloc(VB_SHARED_DATA_MIN_SIZE);
  lfp.shared_data_size = VB_SHARED_DATA_MIN_SIZE;
  printf("shared data size 0x%08" PRIx32 "\n", lfp.shared_data_size);
  /* TODO: load_firmware_test was broken in the wrapper API rewrite.
   * Nothing uses it, so we haven't noticed yet.  Need to fix it.  See
   * crosbug.com/17564.  LoadFirmware() now assumes that VbInit() has
   * been called to set up the shared data structure, but that isn't
   * happening here. */

  status = LoadFirmware(&lfp);
  printf("LoadFirmware returned: 0x%x\n", (int)status);

  free(lfp.shared_data_blob);

  return 0;
}

/* wrap FmapAreaIndex; print error when not found */
int FmapAreaIndexOrError(const FmapHeader* fh, const FmapAreaHeader* ah,
    const char* name) {
  int i = FmapAreaIndex(fh, ah, name);
  if (i < 0)
    fprintf(stderr, "%s: can't find %s in firmware image\n", progname, name);
  return i;
}

int main(int argc, char* argv[]) {
  int i;
  int retval = 0;
  const void* base_of_rom;
  const void* fmap;
  uint64_t boot_flags = 0, rom_size;

  progname = argv[0];

  if (argc < 2) {
    fprintf(stderr, "usage: %s [-b NUM] <firmware_image>\n", progname);
    exit(1);
  }

  for (i = 1; i < argc; i++) {
    if (!strcmp(argv[i], "-b") && i < argc - 1)
      boot_flags = strtoull(argv[++i], NULL, 0);
    else
      image_path = argv[i];
  }

  base_of_rom = ReadFile(image_path, &rom_size);
  if (base_of_rom == NULL) {
    fprintf(stderr, "%s: can not open %s\n", progname, image_path);
    exit(1);
  }

  printf("opened %s\n", image_path);

  fmap = FmapFind((char*) base_of_rom, rom_size);

  retval = DriveLoadFirmware(base_of_rom, fmap, boot_flags);

  free((void*) base_of_rom);

  return retval;
}
