/*
 * This file is part of the Trezor project, https://trezor.io/
 *
 * Copyright (c) SatoshiLabs
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifdef SECURE_MODE

#include <trezor_model.h>

#include <sec/monoctr.h>
#include <sec/secret.h>
#include <sys/flash.h>
#include <sys/mpu.h>

static int32_t get_offset(monoctr_type_t type) {
  switch (type) {
    case MONOCTR_BOOTLOADER_VERSION:
      return SECRET_MONOTONIC_COUNTER_0_OFFSET;
    case MONOCTR_FIRMWARE_VERSION:
      return SECRET_MONOTONIC_COUNTER_1_OFFSET;
#ifdef SECRET_MONOTONIC_COUNTER_2_OFFSET
    case MONOCTR_SECMON_VERSION:
      return SECRET_MONOTONIC_COUNTER_2_OFFSET;
#endif
    default:
      return -1;
  }
}

static size_t get_length(monoctr_type_t type) {
  switch (type) {
    case MONOCTR_BOOTLOADER_VERSION:
      return SECRET_MONOTONIC_COUNTER_0_LEN;
    case MONOCTR_FIRMWARE_VERSION:
      return SECRET_MONOTONIC_COUNTER_1_LEN;
#ifdef SECRET_MONOTONIC_COUNTER_2_LEN
    case MONOCTR_SECMON_VERSION:
      return SECRET_MONOTONIC_COUNTER_2_LEN;
#endif
    default:
      return 0;
  }
}

secbool monoctr_write(monoctr_type_t type, uint8_t value) {
  if (value > MONOCTR_MAX_VALUE) {
    return secfalse;
  }

  int32_t offset = get_offset(type);

  if (offset < 0) {
    return secfalse;
  }

  uint8_t current_value = 0;

  if (sectrue != monoctr_read(type, &current_value)) {
    return secfalse;
  }

  if (value < current_value) {
    return secfalse;
  }

  if (value == current_value) {
    return sectrue;
  }

  for (int i = 0; i < value; i++) {
    uint32_t data[4] = {0};
    if (sectrue != secret_write((uint8_t *)data, offset + i * 16, 16)) {
      return secfalse;
    }
  }

  return sectrue;
}

secbool monoctr_read(monoctr_type_t type, uint8_t *value) {
  int32_t offset = get_offset(type);
  size_t length = get_length(type);

  if (offset < 0) {
    return secfalse;
  }

  const uint8_t *counter_addr =
      flash_area_get_address(&SECRET_AREA, offset, length);

  if (counter_addr == NULL) {
    return secfalse;
  }

  mpu_mode_t mpu_mode = mpu_reconfig(MPU_MODE_SECRET);

  int counter = 0;

  int i = 0;

  for (i = 0; i < length / 16; i++) {
    secbool not_cleared = sectrue;
    for (int j = 0; j < 16; j++) {
      if (counter_addr[i * 16 + j] != 0xFF) {
        not_cleared = secfalse;
        break;
      }
    }

    if (not_cleared != sectrue) {
      counter++;
    } else {
      break;
    }
  }

  for (; i < length / 16; i++) {
    secbool not_cleared = sectrue;
    for (int j = 0; j < 16; j++) {
      if (counter_addr[i * 16 + j] != 0xFF) {
        not_cleared = secfalse;
        break;
      }
    }

    if (not_cleared != sectrue) {
      // monotonic counter is not valid
      mpu_restore(mpu_mode);
      return secfalse;
    }
  }

  mpu_restore(mpu_mode);

  if (value != NULL) {
    *value = counter;
  } else {
    return secfalse;
  }

  return sectrue;
}

#endif  // SECURE_MODE
