// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
/*
 * Copyright (c) 2023 MediaTek Inc.
 */

#include <common.h>
#include <command.h>
#include <time.h>
#include <vsprintf.h>
#include <iniutility.h>
#include <iniparser.h>
#include <utility.h>
#include <debug_impl.h>
#include <mtk_dtbo.h>
#include <mtk_dataindex.h>
#include <romtblo_impl.h>
#ifdef CONFIG_LIBUFDT_OVERLAY
#include <ufdt_overlay.h>
#else
#include <fdt_support.h>
#endif
#ifndef CONFIG_DISABLE_DTBO_FACTORY_CHECK
#include <stdio.h>
#include <stdlib.h>
//verify separation dtbo files
#include "secure_boot.h"
#include "secure/crypto_auth.h"
#include "secure/secure_common.h"
#endif

#ifdef CONFIG_ANDROID_CN_PLATFORM
#define CUSDATA_DEFAULT_PROJECT_PARTITION_1     "project_id"
#define CUSDATA_DEFAULT_PROJECT_PARTITION_2     "config"
#elif CONFIG_LINUX_REF_PLATFORM
#define CUSDATA_DEFAULT_PROJECT_PARTITION_1     "project_id"
#define CUSDATA_DEFAULT_PROJECT_PARTITION_2     "config"
#else
#define CUSDATA_DEFAULT_PROJECT_PARTITION_1     "config"
#define CUSDATA_DEFAULT_PROJECT_PARTITION_2     "project_id"
#endif

#define DEVICE_INTERFACE                        "mmc"
#define CUSTOM_DB_READ_SIZE                     "0x200"
#define CUSTOM_DB_READ_OFFSET                   "0x0"
#define CUSTOM_DB_MAGIC_OFFSET                  0
#define CUSTOM_DB_PJID_OFFSET                   4
#define CMD_SIZE                                128
#define BUF_SIZE                                512
#define MAGIC_NUMBER                            0x504a4944
#define PJID_MAX                                0xffff

#define DEFAULT_DATAINDEX_LABEL                 "default"
#ifndef CONFIG_DISABLE_DTBO_FACTORY_CHECK
#define FACTORY_CHECKNODE_FILEPATH              "/mnt/vendor/bootdata/dtb/factory_dtbo_sep"
#define DTBO_TARGET_OFFSET                      2
#define KEY_WORDLIST_SIZE                       256
#endif

struct dataindex_partition {
    char partition[INI_INFO_SIZE];
    char mount[FILE_PATH_SIZE];
};

static struct project_id_info _project_id_info;
static iniparser_handle_t _dataindex_handle = NULL;
static struct dataindex_partition *_dataindex_partition = NULL;
static int _dataindex_partition_size;
static char _dataindex_datadir[FILE_PATH_SIZE];
static char _dataindex_id[INI_INFO_SIZE];
static char _dataindex_region_id[INI_INFO_SIZE];
static char _dataindex_region_file[INI_INFO_SIZE];

static const char *_get_default_dataIndex_cfg(const char *default_id)
{
    static char id_str[FILE_PATH_SIZE] = { 0 };
    u32 chipid = romtbl_get_chip_id_info_dec();
    u32 revision = romtbl_get_chip_revision_info();
    int len;

    UBOOT_TRACE("IN\n");
    if (chipid == 0 || revision == 0) {
        UBOOT_ERROR("Wrong chip id/revision = %d/%d\n",
        chipid, revision);
        return default_id;
    }

    len = snprintf(id_str, sizeof(id_str), "mt%d_1", chipid);

    if (len >= sizeof(id_str)-1) {
        UBOOT_ERROR("The size of default_dataIndex_cfg buffer is too small!\n");
        return default_id;
    }

    UBOOT_INFO("auto select project_id:%s because dataindex_cfg_name=default\n", id_str);
    UBOOT_TRACE("OUT\n");
    return (const char *)id_str;
}

static int _get_custom_db_project_id(struct project_id_info *info)
{
    int ret = ERR_DATAINDEX_OK;
    disk_partition_t d_info;
    struct blk_desc *blk_dev_desc = NULL;
    char cmd[CMD_SIZE];
    char *buf = NULL;
    int device_number = 0, snprintf_len;
    static char id_str[FILE_PATH_SIZE] = { 0 };
    u32 u32Magic, u32Pjid;

    UBOOT_TRACE("IN\n");

    blk_dev_desc = blk_get_dev(DEVICE_INTERFACE, device_number);
    if (!blk_dev_desc)
    {
        UBOOT_DEBUG("[ERROR] %s dev %u NOT available\n", DEVICE_INTERFACE, device_number);
        goto GET_CUSTOMDB_PROJECT_ID_ERR;
    }
    // Check custom_db is exist or not
    if (part_info_search(blk_dev_desc, CUSTOM_DB_PARTITION, &d_info) <= 0)
    {
        UBOOT_DEBUG("Search partition name:%s fail\n", CUSTOM_DB_PARTITION);
        goto GET_CUSTOMDB_PROJECT_ID_ERR;
    }

    memset(cmd, 0, CMD_SIZE);
    buf = (char *)malloc(BUF_SIZE);
    if (!buf)
    {
        UBOOT_ERROR("[ERROR] malloc failed\n");
        goto GET_CUSTOMDB_PROJECT_ID_ERR;
    }
    memset(buf, 0, BUF_SIZE);

    snprintf_len = snprintf(cmd, sizeof(cmd), "partition read %s %d %s %p %s %s",
                            DEVICE_INTERFACE, 0, CUSTOM_DB_PARTITION, buf,
                            CUSTOM_DB_READ_SIZE, CUSTOM_DB_READ_OFFSET);
    if (snprintf_len >= sizeof(cmd))
    {
        UBOOT_ERROR("The array size is too small(%d), snprintf fail '%s'\n",
                    (int)(sizeof(cmd)), cmd);
        goto GET_CUSTOMDB_PROJECT_ID_ERR;
    }
    UBOOT_DEBUG("cmd=%s\n", cmd);
    if (run_command(cmd, 0) != 0)
    {
        UBOOT_ERROR("[ERROR] cmd run error\n");
        goto GET_CUSTOMDB_PROJECT_ID_ERR;
    }
    else
    {
        u32Magic = be32_to_cpu(*(u32*)(buf+CUSTOM_DB_MAGIC_OFFSET));
        UBOOT_DEBUG("magic:0x%x\n", u32Magic);
        if (u32Magic == MAGIC_NUMBER)
        {
            u32Pjid = be16_to_cpu(*(u16*)(buf+CUSTOM_DB_PJID_OFFSET));
            if ((u32Pjid > 0) && (u32Pjid < PJID_MAX))
            {
                if (snprintf(id_str, sizeof(id_str), "%d", u32Pjid) < 0)
                {
                    UBOOT_ERROR("Convert pjid to string fail. id_str=%s, u32Pjid=%d\n",
                                id_str, u32Pjid);
                    goto GET_CUSTOMDB_PROJECT_ID_ERR;
                }
                strlcpy(info->project_id, id_str, sizeof(info->project_id));
                UBOOT_DEBUG("project_id=%s\n", info->project_id);
                goto GET_CUSTOMDB_PROJECT_ID_EXIT;
            }
            else
            {
                goto GET_CUSTOMDB_PROJECT_ID_ERR;
            }
        }
        else
        {
            UBOOT_DEBUG("Magic number not match!!\n");
            goto GET_CUSTOMDB_PROJECT_ID_ERR;
        }
    }

GET_CUSTOMDB_PROJECT_ID_ERR:
    ret = ERR_DATAINDEX_FAIL;
GET_CUSTOMDB_PROJECT_ID_EXIT:
    free(buf);
    UBOOT_TRACE("OUT\n");
    return ret;
}

static int _get_region_id(iniparser_handle_t ini_handle, struct project_id_info *info)
{
    struct section *section_entry;
    char region_id[INI_INFO_SIZE];
    unsigned char *ini_file_buf;
    loff_t size;
    int ret;

    UBOOT_TRACE("IN\n");

    strlcpy(region_id, "N/A", sizeof(region_id));
    ret = iniparser_get_section(ini_handle, "Region", &section_entry);
    if (ret < 0)
    {
        UBOOT_ERROR("Error: get ini section[%s] failure\n", "Region");
        return ERR_DATAINDEX_INI_PARSE;
    }

    ret = iniparser_getstring(section_entry, "region", "0", region_id, INI_INFO_SIZE);
    if (ret != 1)
    {
       UBOOT_ERROR("Error: get ini setting %s failure\n", "Region:region");
       return ERR_DATAINDEX_INI_PARSE;
    }

    UBOOT_DEBUG("Get Region=%s\n", region_id);

    // If region_id not equal N/A thus add it after project_id
    if (strncmp(region_id, "N/A", sizeof(region_id)) != 0)
    {
        if (snprintf(_dataindex_region_id, sizeof(_dataindex_region_id), "%s_%s", _dataindex_id, region_id) < 0)
        {
            UBOOT_ERROR("Cannot generate new id. id=%s , region_id=%s\n", _dataindex_id, region_id);
            return ERR_DATAINDEX_FAIL;
        }
        else
        {
            _dataindex_region_id[sizeof(_dataindex_region_id)-1] = 0;
        }
    }
    else
    {
        UBOOT_DEBUG("Get Region id=N/A\n");
        return ERR_DATAINDEX_OK;
    }

    // Update info->data_path
    if (snprintf(_dataindex_region_file, sizeof(_dataindex_region_file),
        "dataIndex_%s.ini",  _dataindex_region_id) < 0)
    {
        UBOOT_ERROR("Cannot generate dataIndex file path. data_path=%s , project+id=%s\n",
                    info->data_path, _dataindex_region_id);
        return ERR_DATAINDEX_FAIL;
    }

    // Check the dataindex file is existed or not
    ini_file_buf = read_storage_file_to_memory(DATAINDEX_PARTITION, _dataindex_region_file, &size);
    if (ini_file_buf != NULL)
    {// Can find corresponding data index file. Update the info->project_id.

        // Update info->project_id
        memcpy(info->project_id, _dataindex_region_id, sizeof(info->project_id));
        info->project_id[sizeof(info->project_id)-1] = 0;

        // Update info->data_path
        if (snprintf(info->data_path, sizeof(info->data_path),
            "%s/dataIndex_%s.ini", _dataindex_datadir, _dataindex_region_id) < 0)
        {
            UBOOT_ERROR("Cannot generate dataIndex file path. data_path=%s , project+id=%s\n",
                        info->data_path, _dataindex_region_id);
            return ERR_DATAINDEX_FAIL;
        }

        UBOOT_INFO("%s: can find the region_dataindex file form:'%s'\n",
                __func__, info->data_path);
    }
    else
    {// Can't find corresponding data index file. Thus we use (not assign) the original project_id.
        UBOOT_INFO("%s: The region has already been set, but there is no corresponding data index file. %s'\n",
                   __func__, info->data_path);
    }

    UBOOT_TRACE("OUT\n");
    return ERR_DATAINDEX_OK;
}

int set_project_id(const char *partition, const char *file,
                const char *project_id)
{
    iniparser_handle_t ini_handle = NULL;
    struct section *section_entry;
    unsigned char *ini_file_buf;
    const char *envstr;
    loff_t size;
    int ret;
    int sprintf_len = 0;
    char *full_file_path = NULL;
    int size_full_path = 0;

    UBOOT_TRACE("IN \n");
    ini_file_buf = read_storage_file_to_memory(partition, file, &size);
    if (ini_file_buf == NULL)
    {
        UBOOT_DEBUG("Error: Read ini file to DRAM failure\n");
        ret = ERR_DATAINDEX_NOT_FOUND;
        goto SET_PROJECT_ID_EXIT;
    }

    ret = iniparser_create((uchar*)ini_file_buf, size, &ini_handle);
    free(ini_file_buf);
    ini_file_buf = NULL;
    if (ret < 0)
    {
        UBOOT_ERROR("Error: parse ini [%s] failure\n", file);
        ret = ERR_DATAINDEX_INI_PARSE;
        goto SET_PROJECT_ID_EXIT;
    }

    envstr = env_get(ENV_CUS_PROJECTID);
    if (envstr)
    {
        UBOOT_INFO("%s: use env '%s=%s' for gProjectID\n",
                __func__, ENV_CUS_PROJECTID, envstr);

        env_set(ENV_CUS_PROJECTID, project_id);
    }
    else
    {
        ret = iniparser_get_section(ini_handle, "model", &section_entry);
        if (ret < 0)
        {
            UBOOT_ERROR("Error: get ini section[%s] failure\n", "model");
            ret = ERR_DATAINDEX_SECTION;
            goto SET_PROJECT_ID_EXIT;
        }
        ret = iniparser_setstring(section_entry, "gProjectID", project_id);
        if (ret != 0)
        {
            UBOOT_ERROR("Error: set ini setting %s = %s failure\n", "model:gProjectID", project_id);
            ret = ERR_DATAINDEX_KEY;
            goto SET_PROJECT_ID_EXIT;
        }
        ret = iniparser_create_dumpdata(ini_handle, &ini_file_buf, (unsigned int*)&size);
        if (ret != 0)
        {
            UBOOT_ERROR("Error: Dump ini setting to buffer failure\n");
            ret = ERR_DATAINDEX_KEY;
            goto SET_PROJECT_ID_EXIT;
        }

        size_full_path = strlen(file) + 2;
        full_file_path = malloc(size_full_path);
        if (full_file_path == NULL)
        {
            UBOOT_ERROR("Error: Allocate memory failure\n");
            ret = ERR_DATAINDEX_MEMORY;
            goto SET_PROJECT_ID_EXIT;
        }
        memset(full_file_path, 0, size_full_path);
        sprintf_len = snprintf(full_file_path, size_full_path, "/%s", file);
        if (sprintf_len < 0 || sprintf_len >= size_full_path)
        {
            UBOOT_ERROR("Error: snprintf failure. size_full_path:%d file:%s\n", size_full_path, file);
            ret = ERR_DATAINDEX_KEY;
            goto SET_PROJECT_ID_EXIT;
        }
        if (write_storage_file(partition, full_file_path, ini_file_buf, &size) ==NULL)
        {
            UBOOT_ERROR("Error: Write project_id.ini to storage failure\n");
            ret = ERR_DATAINDEX_KEY;
            goto SET_PROJECT_ID_EXIT;
        }
    }

    UBOOT_TRACE("OK \n");
    ret = ERR_DATAINDEX_OK;

SET_PROJECT_ID_EXIT:
    if (ini_handle != NULL)
    {
        iniparser_destroy(ini_handle);
        ini_handle = NULL;
    }
    if (ini_file_buf != NULL)
    {
        free(ini_file_buf);
        ini_file_buf = NULL;
    }
    if (full_file_path != NULL)
    {
        free(full_file_path);
        full_file_path = NULL;
    }

    if (ret != ERR_DATAINDEX_OK)
    {
        UBOOT_ERROR("ERR ret=%d\n", ret);
    }
    return ret;
}

int get_data_separation_info(const char *partition, const char *file,
                struct project_id_info *info)
{
    iniparser_handle_t ini_handle = NULL;
    struct section *section_entry;
    unsigned char *ini_file_buf;
    const char *envstr;
    loff_t size;
    int ret;

    UBOOT_TRACE("IN \n");

    ini_file_buf = read_storage_file_to_memory(partition, file, &size);
    if(ini_file_buf == NULL)
    {
        UBOOT_DEBUG("Error: Read ini file to DRAM failure\n");
        return ERR_DATAINDEX_NOT_FOUND;
    }

    ret = iniparser_create((uchar*)ini_file_buf, size, &ini_handle);
    free(ini_file_buf);
    if (ret < 0) {
        UBOOT_ERROR("Error: parse ini [%s] failure\n", file);
        return ERR_DATAINDEX_INI_PARSE;
    }

    if (strncmp(file, REGION_FILENAME, sizeof(REGION_FILENAME)) == 0)
    {// Get Region info from persist partition
        if (_get_region_id(ini_handle, info) == 0)
        {
            UBOOT_DEBUG("_get_region_id: ok\n");
        }
        else
        {
            iniparser_destroy(ini_handle);
            UBOOT_ERROR("Error: _get_region_id[%s] failure\n", "REGION_FILENAME");
            return ERR_DATAINDEX_FAIL;
        }
    }
    else
    {// Get project_id info from project_id.ini
        // get dataindex_cfg_name for auto select dataindex_chip.ini
        envstr = env_get(ENV_DATAINDEX_CFG_NAME);
        if (envstr && !strncmp(envstr, DEFAULT_DATAINDEX_LABEL, sizeof(DEFAULT_DATAINDEX_LABEL)))
        {
            envstr = _get_default_dataIndex_cfg(envstr);
            strlcpy(info->project_id, envstr, sizeof(info->project_id));
        }
        else
        { // else use project_id original flow
            envstr = env_get(ENV_CUS_PROJECTID);
            if (_get_custom_db_project_id(info) == ERR_DATAINDEX_OK)
            {
                // Get value from partition "custom_db" success.
            }
            else
            {
                if (envstr)
                {
                    UBOOT_INFO("%s: use env '%s=%s' for gProjectID\n",
                            __func__, ENV_CUS_PROJECTID, envstr);
                    strlcpy(info->project_id, envstr, sizeof(info->project_id));
                }
                else
                {
                    ret = iniparser_get_section(ini_handle, "model", &section_entry);
                    if (ret < 0)
                    {
                        iniparser_destroy(ini_handle);
                        UBOOT_ERROR("Error: get ini section[%s] failure\n", "model");
                        return ERR_DATAINDEX_SECTION;
                    }

                    ret = iniparser_getstring(section_entry, "gProjectID", "0",
                                     info->project_id, INI_INFO_SIZE);
                    if (ret != 1)
                    {
                        iniparser_destroy(ini_handle);
                        UBOOT_ERROR("Error: get ini setting %s failure\n", "model:gProjectID");
                        return ERR_DATAINDEX_KEY;
                    }
                }
            }
        }

        envstr = env_get(ENV_CUS_DATAINDEX_DIR);
        if (envstr)
        {
            UBOOT_INFO("%s: use env '%s=%s' for DataIndexDir\n",
                    __func__, ENV_CUS_DATAINDEX_DIR, envstr);
            strlcpy(_dataindex_datadir, envstr, sizeof(_dataindex_datadir));
        }
        else
        {
            ret = iniparser_get_section(ini_handle, "dataIndex", &section_entry);
            if (ret != 1)
            {
                iniparser_destroy(ini_handle);
                UBOOT_ERROR("Error: get ini section[dataIndex] failure\n");
                return ERR_DATAINDEX_SECTION;
            }
            ret = iniparser_getstring(section_entry, "DataIndexDir", "",
                    _dataindex_datadir, FILE_PATH_SIZE);
            if (ret != 1)
            {
                iniparser_destroy(ini_handle);
                UBOOT_ERROR("INFO: get ini setting dataIndex:DataIndexDir failure\n");
                return ERR_DATAINDEX_KEY;
            }
        }

        if (iniparser_get_section(ini_handle, PROJECT_ID_PARTITION, &section_entry) == 1)
        {
            struct keyval_pair *keyval_entry;
            int i = 0;

            UBOOT_DEBUG("load partition information\n");
            if (_dataindex_partition)
            {
                free(_dataindex_partition);
            }

            _dataindex_partition_size = 0;
            iniparser_for_each_keyval(section_entry, keyval_entry)
            {
                _dataindex_partition_size++;
            }
            _dataindex_partition =
                malloc(sizeof(_dataindex_partition[0])*_dataindex_partition_size);
            if (_dataindex_partition == NULL)
            {
                iniparser_destroy(ini_handle);
                UBOOT_ERROR("Error: cannot allocate enough memory\n");
                return ERR_DATAINDEX_MEMORY;
            }

            iniparser_for_each_keyval(section_entry, keyval_entry)
            {
                strlcpy(_dataindex_partition[i].partition,
                        keyval_entry->key,
                        sizeof(_dataindex_partition[i].partition));
                iniparser_unescape_string(
                        _dataindex_partition[i].mount,
                        keyval_entry->value,
                        sizeof(_dataindex_partition[i].mount));
                i++;
            }
            UBOOT_DEBUG("parse %d partition mount point(s)\n", i);
        }

        // converity fix: the object pointed to by argument "info->project_id"
        // may overlap with the object pointed to by argument "info->data_path"
        // Use another tmp buffer "id" to fix it.
        memcpy(_dataindex_id, info->project_id, sizeof(_dataindex_id));
        _dataindex_id[sizeof(_dataindex_id)-1] = 0;

        if (snprintf(info->data_path, sizeof(info->data_path),
                    "%s/dataIndex_%s.ini", _dataindex_datadir, _dataindex_id) < 0)
        {
            iniparser_destroy(ini_handle);
            UBOOT_ERROR("Cannot generate dataIndex file path. data_path=%s , project+id=%s\n",
                    info->data_path, _dataindex_id);
            return ERR_DATAINDEX_FAIL;
        }
    }

    UBOOT_INFO("Get project_id=%s\n", info->project_id);
    UBOOT_DEBUG("dataIndex file=%s\n", info->data_path);

    iniparser_destroy(ini_handle);
    UBOOT_TRACE("OK \n");
    return ERR_DATAINDEX_OK;
}

/**
 * dataindex_set_project_id - set project ID
 *
 * @project[in]: Pointer of pointer to store the iuput project ID
 *
 * Returns 0 if successful,
 *         negative if an error occurs
 */
int dataindex_set_project_id(const char *project_id)
{
    UBOOT_TRACE("IN \n");

    // search order of project_id partition:
    //   1. uboot env: ${ENV_CUS_PROJECTID_PART}
    //   2. ${CUSDATA_DEFAULT_PROJECT_PARTITION_1}
    //   3. ${CUSDATA_DEFAULT_PROJECT_PARTITION_2}
    static char *_part_project_id[] = {
        NULL,
        CUSDATA_DEFAULT_PROJECT_PARTITION_1,
        CUSDATA_DEFAULT_PROJECT_PARTITION_2
    };

    int ret = ERR_DATAINDEX_NOT_FOUND;
    int i;

    _part_project_id[0] = env_get(ENV_CUS_PROJECTID_PART);
    for (i = 0; i < ARRAY_SIZE(_part_project_id); i++)
    {
        char *part = _part_project_id[i];
        if (part && set_project_id(part,
                    PROJECT_ID_FILENAME, project_id) == 0)
        {
            ret = ERR_DATAINDEX_OK;
            break;
        }
    }
    if (ret != ERR_DATAINDEX_OK)
    {
        UBOOT_ERROR("cannot find " PROJECT_ID_FILENAME "\n");
        return ret;
    }

    UBOOT_TRACE("OK \n");
    return ERR_DATAINDEX_OK;
}

/**
 * dataindex_get_info - get dataindex information
 *
 * @info[out]: Pointer of pointer to store the output dataindex info
 *
 * Returns 0 if successful,
 *         negative if an error occurs
 */
int dataindex_get_info(struct project_id_info **info)
{
    UBOOT_TRACE("IN \n");
    if (_project_id_info.project_id[0] == 0)
    {
        // search order of project_id partition:
        //   1. uboot env: ${ENV_CUS_PROJECTID_PART}
        //   2. ${CUSDATA_DEFAULT_PROJECT_PARTITION_1}
        //   3. ${CUSDATA_DEFAULT_PROJECT_PARTITION_2}
        static char *_part_project_id[] = {
            NULL,
            CUSDATA_DEFAULT_PROJECT_PARTITION_1,
            CUSDATA_DEFAULT_PROJECT_PARTITION_2
        };

        int ret = ERR_DATAINDEX_NOT_FOUND;
        int i;

        _part_project_id[0] = env_get(ENV_CUS_PROJECTID_PART);
        for (i = 0; i < ARRAY_SIZE(_part_project_id); i++)
        {
            char *part = _part_project_id[i];
            if (part && get_data_separation_info(part,
                        PROJECT_ID_FILENAME, &_project_id_info) == 0)
            {
                ret = ERR_DATAINDEX_OK;
                break;
            }
        }

        if (ret != ERR_DATAINDEX_OK)
        {
            UBOOT_ERROR("cannot find " PROJECT_ID_FILENAME "\n");
            return ret;
        }

        // Try to get region_id info
        if (get_data_separation_info(REGION_PARTITION,
            REGION_FILENAME, &_project_id_info) != 0)
        {

            ret = ERR_DATAINDEX_NOT_FOUND;
        }

        if (ret != ERR_DATAINDEX_OK)
        {
            UBOOT_ERROR("cannot find " REGION_FILENAME "\n");
            // Region ID get fail, just print log and use original dataindex.ini
            //return ret;
        }
    }
    *info = &_project_id_info;
    UBOOT_TRACE("OK \n");
    return ERR_DATAINDEX_OK;
}

/**
 * dataindex_get_handle - get ini handle of dataindex file
 *
 * @phandle[out]: Pointer to store the dataindex ini parser handle
 *
 * Returns 0 if successful,
 *         negative if an error occurs
 */
int dataindex_get_handle(iniparser_handle_t *phandle)
{
    char partition[INI_INFO_SIZE];
    struct project_id_info *info;
    unsigned char *ini_file_buf;
    const char *relpath;
    loff_t size;
    int ret;

    UBOOT_TRACE("IN \n");
    if (_dataindex_handle == 0)
    {
        if (dataindex_get_info(&info) != 0)
        {
            UBOOT_ERROR("cannot get dataindex information\n");
            return ERR_DATAINDEX_NOT_FOUND;
        }
        if (dataindex_resolve_path(partition, sizeof(partition),
                    &relpath, info->data_path) != ERR_DATAINDEX_OK)
        {
            UBOOT_ERROR("dataIndex directory resolves fail: '%s'\n",
                    info->data_path);
            return ERR_DATAINDEX_FAIL;
        }

        ini_file_buf = read_storage_file_to_memory(partition, relpath, &size);
        if(ini_file_buf == NULL)
        {
            UBOOT_DEBUG("Error: Read partition %s : %s file to DRAM failure\n",
                    partition, relpath);
            return ERR_DATAINDEX_LOAD_FILE;
        }
        ret = iniparser_create((uchar *)ini_file_buf, size, &_dataindex_handle);
        if (ret < 0) {
            free(ini_file_buf);
            UBOOT_ERROR("Error: parse ini [%s] failure\n", relpath);
            return ERR_DATAINDEX_INI_PARSE;
        }
        free(ini_file_buf);
    }

    *phandle = _dataindex_handle;
    UBOOT_TRACE("OK \n");
    return ERR_DATAINDEX_OK;
}

/**
 * dataindex_get_section - get section handle of dataindex file
 *
 * @pentry[out]: Pointer to store the dataindex section handle
 * @section[in]: the section string
 *
 * Returns 0 if successful,
 *         negative if an error occurs
 */
int dataindex_get_section(struct section **pentry, const char *section)
{
    iniparser_handle_t ini_handle;
    struct section *section_entry;
    int ret;

    UBOOT_TRACE("IN \n");
    ret = dataindex_get_handle(&ini_handle);
    if (ret != ERR_DATAINDEX_OK)
    {
        return ret;
    }
    ret = iniparser_get_section(ini_handle, section, &section_entry);
    if (ret != 1) {
        UBOOT_INFO("Error: get section[%s] in dataindex file failure\n", section);
        return ERR_DATAINDEX_SECTION;
    }
    *pentry = section_entry;

    UBOOT_TRACE("OK \n");
    return ERR_DATAINDEX_OK;
}

/**
 * dataindex_get_keys - get the value of key from dataindex file
 *
 * @dest[out]: the output value buffer
 * @size[in]: the size of output value buffer
 * @section[in]: the section string
 * @key[in]: the key string
 * @default_str[in]: the default string if key is missing
 *
 * Returns 0 if successful,
 *         negative if an error occurs
 *     Notice: default_str is copied if default_str is not NULL and
 *     the the key is missing
 */
int dataindex_get_key(char *dest, int size,
       const char *section, const char *key, const char* default_str)
{
    struct section *section_entry;
    int ret;

    UBOOT_TRACE("IN \n");
    ret = dataindex_get_section(&section_entry, section);
    if (ret == ERR_DATAINDEX_OK)
    {
        if (iniparser_getstring(section_entry, key, "", dest, size) != 1)
        {
            UBOOT_ERROR("Error: get %s:%s in dataindex file failure\n",
                    section, key);
            ret = ERR_DATAINDEX_KEY;
        }
    }

    if (ret != ERR_DATAINDEX_OK && default_str)
    {
        strlcpy(dest, default_str, size);
    }

    UBOOT_TRACE("OK \n");
    return ret;
}

/**
 * dataindex_resolve_path - break down "path" to "partition" and "relative path"
 *                          that can be used in uboot.
 *
 * @partition[out]: the output partition buffer
 * @size[in]: the size of output value buffer
 * @relpath[out]: the pointer to output relative path
 * @path[in]: the path string to be resolved
 *
 * Returns 0 if successful,
 *         negative if an error occurs
 */
int dataindex_resolve_path(char *partition, int size,
       const char **relpath, const char *path)
{
    struct project_id_info *info;
    UBOOT_TRACE("IN \n");

    if (dataindex_get_info(&info) != ERR_DATAINDEX_OK)
    {
        UBOOT_ERROR("project_id partition infomation is not initialized\n");
        return ERR_DATAINDEX_FAIL;
    }
    int path_len = strlen(path);
    int i;

    for (i = 0; i < _dataindex_partition_size; i++)
    {
        const char *part = _dataindex_partition[i].partition;
        const char *mount = _dataindex_partition[i].mount;
        int len = strlen(mount);
        if (mount[len-1] == '/')
            len--;
        UBOOT_DEBUG("keyval: len=%d, %s=%s , path=%s\n", len, part, mount, path);
        if (len <= path_len
            && !strncmp(mount, path, len)
            && (path[len] == '/' || path[len] == 0))
        {
            if (partition)
                strlcpy(partition, part, size);
            if (relpath)
                *relpath = &path[len];
            UBOOT_TRACE("OK \n");
            return ERR_DATAINDEX_OK;
        }
    }

    return ERR_DATAINDEX_FAIL;
}

#ifndef CONFIG_DISABLE_DTBO_FACTORY_CHECK
/**
 * dataindex_dtbo_checknode - check the node name of target dtbo file in dataindex multi_dtbo folder
 *
 * @dtbo[in]: the pointer of dtbo file
 * @checklistanme[in]: the full path filename of checklist file
 *
 * Returns 0 if successful,
 *         negative if an error occurs
 *     Notice: default_str is copied if default_str is not NULL and
 *     the the key is missing
 */
static int dataindex_dtbo_checknode(unsigned char *dtbo, unsigned int dtbo_size, const char *category_name)
{
    int fixups_offset = fdt_path_offset(dtbo, "/__fixups__");
    int nextproperty_offset = 0;
    const struct fdt_property *property;
    const char *name;
    loff_t size = 0;
    int ret = 0;

    char partition[INI_INFO_SIZE] = {0};
    iniparser_handle_t ini_handle = NULL;
    struct section *section_entry = NULL;;
    unsigned char *ini_file_buf = NULL;
    char keyword[KEY_WORDLIST_SIZE] = {0};
    const char *relpath = NULL;

    UBOOT_TRACE("IN \n");
    ret = dataindex_resolve_path(partition, PART_NAME_SIZE, &relpath, FACTORY_CHECKNODE_FILEPATH);
    if (ret)
    {
        UBOOT_ERROR("resolve path=%s fail!\n", relpath);
        return ret;
    }

    ini_file_buf = read_storage_file_to_memory(partition, (char *)relpath, &size);
    if (ini_file_buf == NULL)
    {
        UBOOT_ERROR("Error: Read ini file to DRAM failure, relpath=%s\n", relpath);
        ret = ERR_DATAINDEX_NOT_FOUND;
        return ret;
    }

    ret = iniparser_create((uchar*)ini_file_buf, size, &ini_handle);
    free(ini_file_buf);
    if (ret < 0)
    {
        UBOOT_ERROR("Error: parse file [%s] failure\n", FACTORY_CHECKNODE_FILEPATH);
        ret = ERR_DATAINDEX_INI_PARSE;
        return ret;
    }

    ret = iniparser_get_section(ini_handle, "factory", &section_entry);
    if (ret < 0)
    {
        UBOOT_ERROR("Error: get ini section[%s] failure\n", "factory");
        ret = ERR_DATAINDEX_SECTION;
        return ret;
    }

    ret = iniparser_getstring(section_entry, category_name, "", keyword, KEY_WORDLIST_SIZE);
    if (ret != 1)
    {
        iniparser_destroy(ini_handle);
        UBOOT_ERROR("Error: get ini setting %s failure\n", category_name);
        return ERR_DATAINDEX_KEY;
    }

    if (fdt_check_header(dtbo) != 0)
    {
        UBOOT_ERROR("Error: Bad device tree blob header\n");
        return ERR_DATAINDEX_KEY;
    }

    if (fixups_offset < 0)
    {
        UBOOT_ERROR("Could not find /__fixups__ in the DTBO\n");
        return ERR_DATAINDEX_NOT_FOUND;
    }

    fdt_for_each_property_offset(nextproperty_offset, dtbo, fixups_offset)
    {
        property = fdt_get_property_by_offset(dtbo, nextproperty_offset, NULL);
        if (!property)
        {
            UBOOT_ERROR("Error getting property\n");
            continue;
        }
        name = fdt_string(dtbo, fdt32_to_cpu(property->nameoff));
        UBOOT_INFO("nextproperty_offset: [%d] / Node name: [%s]\n", nextproperty_offset, name);
        UBOOT_INFO("Checking Node name in keyword list: %s\n", keyword);
        if (!strstr(keyword, name))
        {
            UBOOT_ERROR("Node name %s  is invalid\n", name);
            return ERR_DATAINDEX_KEY;
        }
        else
        {
            UBOOT_INFO("Node name %s is allowed\n", name);
        }
    }

    UBOOT_INFO("Check file successfully\n");
    UBOOT_TRACE("OK \n");
    return 0;
}

/**
 * dataindex_dtbo_verify_firmware - check the signature of dtbo file in dataindex multi_dtbo folder
 *
 * @fw[in]: the pointer of auth data
 * @auth_size[in]: the size of auth data portion
 * @sign_data[in]: the pointer of sign data in memory
 * @sign_size[in]: the size of sign data portion
 *
 * Returns 0 if successful,
 *         negative if an error occurs
 *     Notice: default_str is copied if default_str is not NULL and
 *     the the key is missing
 */
static int dataindex_dtbo_verify_firmware(unsigned char *fw, unsigned int auth_size, unsigned char *sign_data, unsigned int sign_size)
{
    int ret = 0;
    st_secure_key key = {0};
    /* As follow reference signature flow in build time. */
    unsigned char *auth_data = fw;
    //unsigned char *sign_data = fw + auth_size;
    //unsigned int sign_size = SIGNATURE_LEN;

    UBOOT_TRACE("IN \n");
    UBOOT_INFO("Enter dataindex_dtbo_verify_firmware\n");
    UBOOT_INFO("auth_size = %d, sign_size = %d\n", auth_size, sign_size);
    /* Get public key. */
    if (secure_get_key(KEY_TYPE_CKB_A_RI_KEY, &key) == -1)
    {
        UBOOT_INFO("secure_get_key() fail.\n");
        return -ENOSYS;
    }

    UBOOT_INFO("Get public key successfully\n");
    if ( (ret = secure_do_authentication(key.data, key.data + RSA_PUBLIC_KEY_N_LEN,
                                                              sign_data,
                                                              auth_data,
                                                              auth_size) ) != 0)
    {
        UBOOT_INFO("secure_do_authentication() fail.\n");
        UBOOT_INFO("RSA_PUBLIC_KEY_N_LEN:\n");
        UBOOT_DUMP((void *)key.data, RSA_PUBLIC_KEY_N_LEN);
        UBOOT_INFO("RSA_PUBLIC_KEY_E_LEN:\n");
        UBOOT_DUMP((void *)key.data + RSA_PUBLIC_KEY_N_LEN, RSA_PUBLIC_KEY_E_LEN);
        UBOOT_INFO("signature:\n");
        UBOOT_DUMP((void *)sign_data, sign_size);
        UBOOT_INFO("firmware:\n");
        UBOOT_DUMP((void *)auth_data, auth_size);
        return -EPERM;
    }

    UBOOT_INFO("Verify file successfully\n");
    UBOOT_TRACE("OK \n");
    return 0;
}

static int __load_and_overlay_dtbo(char *ft_addr, const char *filepath, const char *category)
#else
static int __load_and_overlay_dtbo(char *ft_addr, const char *filepath)
#endif
{
    struct fdt_header *source = (struct fdt_header *)ft_addr;
    const char *relpath;
    loff_t blob_len;
    char part[PART_NAME_SIZE];
    unsigned char *blob;
    int ret;
    const char *envstr;

#ifndef CONFIG_DISABLE_DTBO_FACTORY_CHECK
    loff_t blob1_len = 0;
    char sign_filepath[FILE_PATH_SIZE] = {0};
    unsigned char *blob1 = NULL;
    //unsigned int start = 0, end = 0;

    UBOOT_TRACE("IN \n");
#endif
    ret = dataindex_resolve_path(part, PART_NAME_SIZE, &relpath, filepath);
    if (ret)
    {
        UBOOT_DEBUG("resolve path=%s fail!\n", filepath);
        return ret;
    }

    envstr = env_get(ENV_DTBO_RUNTIME_SECURE_CHECK_DISABLE);
    if (envstr)
    {
        UBOOT_DEBUG("current env setting include dtbo_secure_check_disable\n");
    }
#ifndef CONFIG_DISABLE_DTBO_FACTORY_CHECK
    /*
     *  need to add flow to verify sign file before __load_and_overlay_dtbo
     */
    if (envstr && !strcmp(envstr, "true"))
    {
        UBOOT_INFO("%s: use env '%s' for runtime disable DTBO secure check\n",
            __func__, ENV_DTBO_RUNTIME_SECURE_CHECK_DISABLE);
    }
    else
    {
        memset(sign_filepath, 0, FILE_PATH_SIZE);
        ret = snprintf(sign_filepath, FILE_PATH_SIZE, "%s_sign", relpath);
        if (ret < 0)
        {
            UBOOT_ERROR("sign file search flow is failed\n");
            return ret;
        }
    }
#endif

    UBOOT_DEBUG("try to load part:relpath=%s:%s\n", part, relpath);
    blob = read_storage_file_to_memory(part, (char *)relpath, &blob_len);
    if (blob == NULL)
    {
        UBOOT_ERROR("read %s:%s file fail!\n", part, relpath);
        return -ERR_DATAINDEX_LOAD_FILE;
    }

#ifndef CONFIG_DISABLE_DTBO_FACTORY_CHECK
    //start = get_timer(0);

    if (envstr && !strcmp(envstr, "true"))
    {
        UBOOT_INFO("Disable Runtime DTBO Secure Check\n");
    }
    else
    {
        UBOOT_INFO("DTBO Secure Check Executing\n");
        UBOOT_DEBUG("try to load part:relpath=%s:%s\n", part, sign_filepath);
        blob1 = read_storage_file_to_memory(part, (char *)sign_filepath, &blob1_len);
        if (blob1 == NULL)
        {
            UBOOT_ERROR("read %s:%s file fail!\n", part, sign_filepath);
            //start = 0;
            free(blob);
            return -ERR_DATAINDEX_LOAD_FILE;
        }

        ret = dataindex_dtbo_verify_firmware(blob, blob_len, blob1, blob1_len);
        if (ret)
        {
            //due to failure then free sign data + auth data
            UBOOT_ERROR("verify %s file fail!\n", sign_filepath);
            //start = 0;
            free(blob);
            free(blob1);
            return ret;
        }
        else
        {
            //due to success then free sign data only
            UBOOT_DEBUG("verify %s file successfully\n", sign_filepath);
            free(blob1);
        }

        // check the dtbo file with keyword list
        if (dataindex_dtbo_checknode(blob, blob_len, category))
        {
            UBOOT_ERROR("Failed on checking node of file!\n");
            free(blob);
            return -ERR_DATAINDEX_FAIL;
        }
    }
    /*
     * verify sign file end
     */
    //end = get_timer(0);
    //UBOOT_BOOTTIME("[boot_dtbo_verify][dtbo][ret=%d][start:%u][end:%u][total time:%u].\n",ret, start, end, end - start);
#endif

#ifdef CONFIG_LIBUFDT_OVERLAY
    source = ufdt_apply_overlay(source, fdt32_to_cpu(source->totalsize),
            (void*)blob, (size_t)blob_len);
    free(blob);
    if (source == NULL)
    {
        UBOOT_ERROR("ufdt_apply_overlay execute failure.\n");
        return -ERR_DATAINDEX_FAIL;
    }
    else
    {
        ret = fdt_open_into((void *)source, (void *)ft_addr, fdt32_to_cpu(source->totalsize));
        UBOOT_DEBUG("overlay %s:%s pass! , copy to kernel dtb[%d]\n",
                part, relpath, ret);
        free(source);
        return 0;
    }
#else
    ret = fdt_overlay_apply_verbose((void *)ft_addr, blob);
    free(blob);
    if (ret < 0)
    {
        UBOOT_ERROR("fdt_overlay_apply_verbose execute failure.\n");
        return -ERR_DATAINDEX_FAIL;
    }
#endif
#ifndef CONFIG_DISABLE_DTBO_FACTORY_CHECK
    UBOOT_TRACE("OK \n");
#endif
    return -ERR_DATAINDEX_FAIL;
}

/**
 * dataindex_dtbo_overlay - overlay dtbo read from dataindex file
 *
 * @ft_addr[in]: the kernel dtb buffer
 * @mode[in]: the overlay mode. 'u' for uboot and 'k' for kernel
 *
 * Returns 0 if successful,
 *         negative if an error occurs
 */
int dataindex_dtbo_overlay(char *ft_addr, char mode)
{
    struct keyval_pair *keyval_entry;
    struct section *section_entry;
    int ret = -ERR_DATAINDEX_OK;
    char filepath[FILE_PATH_SIZE];
    char *dtbo_str;

    UBOOT_TRACE("IN \n");
    dtbo_str = env_get(ENV_DATAINDEX_DTBO);
    if (dtbo_str && (dtbo_str[0] == '0' || dtbo_str[0] == 'n'))
    {
        printf("detect env %s=%s, skip dataindex dtbo overlay\n",
                ENV_DATAINDEX_DTBO, dtbo_str);
        return ret;
    }
    if (dataindex_get_section(&section_entry, "dtbo") == 0)
    {
        /* caller send ft_addr==NULL when in uboot dtbo mode */
        if (ft_addr == NULL)
        {
            unsigned long addr;
            get_uboot_dtb_addr(&addr);
            ft_addr = (char*)addr;
        }
        iniparser_for_each_keyval(section_entry, keyval_entry)
        {
            const char *key = keyval_entry->key;
            UBOOT_INFO("%s: get dtbo info: %s=%s\n", __func__, key, keyval_entry->value);
            if (key[1] != ',')
            {
                UBOOT_ERROR("dataindex dtbo entry format error: get %s=%s\n", key, keyval_entry->value);
                UBOOT_ERROR("Key format should be 'mode,name' which mode is 'u'/'k'/'*'\n");
                continue;
            }
            if (key[0] != mode && key[0] != '*')
                continue;
            iniparser_unescape_string(filepath, keyval_entry->value, sizeof(filepath));
            UBOOT_INFO("%s: overlay %s\n", __func__, filepath);
#ifndef CONFIG_DISABLE_DTBO_FACTORY_CHECK
            if (__load_and_overlay_dtbo(ft_addr, filepath, &key[DTBO_TARGET_OFFSET]))
#else
            if (__load_and_overlay_dtbo(ft_addr, filepath))
#endif
            {
                UBOOT_ERROR("Cannot overlay dtbo file: %s\n", filepath);
                ret = -ERR_DATAINDEX_FAIL;
            }
        }
    }

    UBOOT_TRACE("OK \n");
    return ret;
}
