/*
 * Copyright 2019-2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * SPDX-License-Identifier:  GPL-2.0+
 */

#pragma once

#include <common.h>

#include "unlocker_impl.h"

///////////////////////////////////////////////////////////////////////////////
// Common header:

#ifdef __cplusplus
extern "C" {
#endif

typedef enum
{
    BOOTLOADER_OK,
    BOOTLOADER_VERIFY_SUCCESS,
    BOOTLOADER_VERIFY_FAILURE,
    BOOTLOADER_BOOT_COUNT_EXPIRED,
    BOOTLOADER_INVALID_SIGNATURE,
    BOOTLOADER_PARTIALLY_SECURED,
    BOOTLOADER_READ_ERROR,
    BOOTLOADER_WRITE_ERROR,
    BOOTLOADER_HASH_FAILURE,
    BOOTLOADER_NON_HEX_CHAR,
    BOOTLOADER_INVALID_IMAGE,
    BOOTLOADER_INVALID_PARTITION_NUMBER,
    BOOTLOADER_TIMEOUT,
    BOOTLOADER_INVALID_KEY_ID,
    BOOTLOADER_KEY_PARSE_FAILED,
    BOOTLOADER_INVALID_OFFSET,
    BOOTLOADER_INVALID_PARAMETER,
    BOOTLOADER_OTHER_ERROR

} bootloader_status_t;

#define UNLOCK_BLOCK_SET    0xC0C0A5E7
#define UNLOCK_BLOCK_IN_MEM 0xDA7ADA7A

typedef enum
{ /* These should be ordered most to least restricted */
    UNLOCK_STATE_INVALID = 0,
    /* Production locked device, no valid unlock code. Default state */
    UNLOCK_STATE_LOCKED,
    /* Production locked device. Valid vendor unlock code (boot count restricted) */
    UNLOCK_STATE_RESTRICTED_UNLOCK,
    /* Production locked device. Valid developer unlock code */
    UNLOCK_STATE_FULL_UNLOCK,
    /* Device not production locked */
    UNLOCK_STATE_NON_PROD
} unlock_state_t;

/* We've got some dodgy stuff going on with 64bit values.  Use an array */
typedef struct
{
    uint32_t    value[2];
} pseudo_uint64_t;

typedef struct
{
    pseudo_uint64_t  unlock_challenge_salt;
    uint32_t    relock_boot_count;
    uint32_t    challenge_str_len;
    signature_t unlock_signature;
    uint32_t    is_set;
} unlock_block_t;

typedef enum
{
    UNRESTRICTED_UNLOCK_PUBLIC_KEY,
    BOOT_COUNTED_UNLOCK_PUBLIC_KEY,
    PRODUCTION_CODE_SIGNATURE_PUBLIC_KEY,
    INSECURE_TEST_CODE_SIGNATURE_PUBLIC_KEY   // aka Dev signed
} key_id_t;

typedef struct
{
    signature_t  signature;           // signature of everything after this item i.e. rest of header + code image
    uint32_t     image_length;        // Length of header + code
    uint32_t     entry_point_offset;  // offset from start of this header
} signed_code_header_t;

typedef struct
{
    const char*          name;
    uint32_t             size;
} partition_desc_t;


void                boot(void);
void                user_select(void);
bootloader_status_t relock(bool reboot);
bootloader_status_t is_unlocked(unlock_state_t *unlocked_out);

/* Factory test mode functionality */
int enable_factory_test_mode(uint32_t factory_test_port);
int disable_factory_test_mode(void);
int get_factory_test_mode(bool *enabled, uint32_t *port);

/* Logging for debug */
#ifdef DEBUG_BOOT

#define boot_debug(...)   printf(__VA_ARGS__)

static inline void boot_debug_buf(const char* prefix, const void* buf, uint32_t len)
{
    boot_debug("%s",prefix);

    uint8_t *p = (uint8_t*)buf;
    while (len > 0)
    {
        boot_debug(" %02x", *p);
        p++;
        len--;
    }

    boot_debug("\n");
}

#else  // DEBUG

#define boot_debug(...)
static inline void boot_debug_buf(const char* prefix, const void* buf, uint32_t len) {}

#endif // DEBUG



///////////////////////////////////////////////////////////////////////////////
// These functions must be implemented by the platform

/*
 * Store the current unlock state.  This should _not_ be stored in non-volatile memory
 * as it should be recalculated per boot
 *
 * @return BOOTLOADER_OK if successfully stored otherwise BOOTLOADER_INVALID_PARAMETER
 *  if not a valid state, or other error as appropriate
 */
bootloader_status_t platform_set_unlock_state(unlock_state_t unlock_state);

/*
 * Retrieve the current unlock state.  This should _not_ be stored in non-volatile memory
 * as it should be recalculated per boot
 *
 * @return BOOTLOADER_OK if successfully retrieved otherwise BOOTLOADER_INVALID_PARAMETER
 *  if a NULL pointer
 */
bootloader_status_t platform_get_unlock_state(unlock_state_t *unlock_state);

/*
 * Secure boot enabled, JTAG disabled, OTP locked, Bootrom download disabled etc.
 * @return BOOTLOADER_OK if successfully retrieved otherwise BOOTLOADER_PARTIALLY_SECURED
 *  or BOOTLOADER_READ_ERROR
 */
bootloader_status_t platform_get_secured_status(bool * secured_status_out);

/*
 * Get unlock block from storage location
 * @return BOOTLOADER_OK if successfully retrieved otherwise BOOTLOADER_READ_ERROR
 */
bootloader_status_t platform_get_unlock_details(unlock_block_t *unlock_block_out);

/*
 * Write unlock block to storage location
 * @return BOOTLOADER_OK if successfully retrieved otherwise BOOTLOADER_WRITE_ERROR
 */
bootloader_status_t platform_write_unlock_details(const unlock_block_t *unlock_block);


bootloader_status_t platform_relock(void);

/*
 * Get unlock partition offset and size
 * @param offset_out: pointer to store the partition offset
 * @param size_out: pointer to store the partition size
 * @return 0 if successful
 */
int platform_get_unlock_partition_info(u32 *offset_out, u32 *size_out);

/*
 * Verify cryptographic signature
 * e.g. using RSASSA-PSS 2048bit or ECDSA 224bit
 * 
 * Use hardware acceleration if available
 * 
 * @return BOOTLOADER_VERIFY_SUCCESS if signature verifies correctly, otherwise BOOTLOADER_VERIFY_FAILURE
 */
bootloader_status_t platform_verify_signature ( const hash_t *hashed_challenge, const signature_t *signature, key_id_t public_key );

/*
 * Fetches the current restricted boot count
 * Boots are only counted whislt restricted.
 *
 * If not implemented - just set count=0 and return BOOTLOADER_OK
 *
 * @return BOOTLOADER_OK if count retrieved successfully, otherwise BOOTLOADER_READ_ERROR
 */
bootloader_status_t platform_get_current_boot_count(uint32_t *count);

/*
 * Increments the current restricted boot count in non-volatile memory
 * If not implemented - just do nothing and return BOOTLOADER_OK
 *
 * @return BOOTLOADER_OK if count incremented successfully, otherwise BOOTLOADER_WRITE_ERROR
 */
bootloader_status_t platform_increment_boot_count(void);

/*
 * Retrieves the device specific data
 *
 * This should include Device-Serial-Number, MAC address, flash memory unique ID, etc.
 *
 * It expected that the platform unique values are hashed together, although this
 * is not mandatory.  If the data is not hashed, the full length of the hash
 * must be initialised with constant data or the challenge will not match
 *
 * @return BOOTLOADER_OK if retrieved successfully, otherwise BOOTLOADER_READ_ERROR
 */
bootloader_status_t platform_get_device_unique_data( hash_t *unique_data_hash );


bootloader_status_t platform_get_device_unique_tag( unique_tag_t *unique_tag );

/*
 * Cryptographically hash data
 * e.g. SHA-256
 * Use hardware acceleration where possible
 *
 * @return BOOTLOADER_OK if retrieved successfully, otherwise BOOTLOADER_HASH_FAILURE
 */
bootloader_status_t platform_hash( hash_t *challenge_hash_out, const void* data, uint32_t data_size );

/*
 * Get the current image header to be booted
 * Whole image must be contiguous and code must be already
 * loaded immediately following header in memory
 *
 * @return BOOTLOADER_OK if retrieved successfully, otherwise BOOTLOADER_READ_ERROR
 */
bootloader_status_t platform_get_current_image( signed_code_header_t **header );

/*
 * Reboot system
 * Must not return
 */
void platform_reboot(void);

/*
 * Generate a random number
 * 
 * Use hardware RNG if available.
 * Otherwise, part of seed must be stored in non-volatile memory.
 */
bootloader_status_t platform_random(pseudo_uint64_t *rval);

bootloader_status_t platform_set_factory_test_mode(bool enabled, uint32_t factory_test_port);
bootloader_status_t platform_get_factory_test_mode(bool *enabled, uint32_t *factory_test_port);

int unlocker_get_challenge(char *unlock_code, int unlock_code_len);

#ifdef __cplusplus
}
#endif
