/**
 * Copyright (c) 2017 - 2020, Nordic Semiconductor ASA
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form, except as embedded into a Nordic
 *    Semiconductor ASA integrated circuit in a product or a software update for
 *    such product, must reproduce the above copyright notice, this list of
 *    conditions and the following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *
 * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * 4. This software, with or without modification, must only be used with a
 *    Nordic Semiconductor ASA integrated circuit.
 *
 * 5. Any software provided in binary form under this license must not be reverse
 *    engineered, decompiled, modified and/or disassembled.
 *
 * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
#include <stdbool.h>
#include "nrf_dfu_types.h"
#include "nrf_dfu_settings.h"
#include "nrf_dfu_utils.h"
#include "nrf_bootloader_info.h"
#include "nrf_crypto.h"
#include "nrf_assert.h"
#include "dfu-cc.pb.h"
#include "nrf_dfu_ver_validation.h"

#define NRF_LOG_MODULE_NAME nrf_dfu_ver_validation
#include "nrf_log.h"
NRF_LOG_MODULE_REGISTER();


/** @brief Macro for reading the Firmware ID of a SoftDevice at a given base address.
 */
#ifndef _SD_FWID_GET
#define _SD_FWID_GET(baseaddr)         SD_OFFSET_GET_UINT16(baseaddr, 0x0C)
#endif

#define EXT_ERR(err) (nrf_dfu_result_t)((uint32_t)NRF_DFU_RES_CODE_EXT_ERROR + (uint32_t)err)

static bool sd_req_check(uint32_t const * p_sd_req, uint8_t sd_req_cnt, bool accept_any)
{
    bool result = false;
    for (uint8_t i = 0; i < sd_req_cnt; i++)
    {
        if ((SD_PRESENT && (p_sd_req[i] == _SD_FWID_GET(MBR_SIZE))) ||
            (accept_any && (p_sd_req[i] == SD_REQ_ANY_VERSION))
           )
        {
            // Found a matching sd_req field. sd_req is ok.
            result = true;
            break;
        }
    }
    return result;
}


static bool sd_req_ok(dfu_init_command_t const * p_init)
{
    ASSERT(p_init != NULL);
    bool result;
#if defined(BLE_STACK_SUPPORT_REQD) || defined(ANT_STACK_SUPPORT_REQD)
    // The bootloader needs the SoftDevice, so disabling NRF_DFU_APP_DOWNGRADE_PREVENTION
    // should not be applied to SoftDevice updates.
    const bool prevent_downgrade = NRF_DFU_APP_DOWNGRADE_PREVENTION || (p_init->type == DFU_FW_TYPE_SOFTDEVICE);
#else
    const bool prevent_downgrade = NRF_DFU_APP_DOWNGRADE_PREVENTION;
#endif

    if (SD_PRESENT)
    {
        if (p_init->sd_req_count == 0)
        {
            result = false;
        }
        else if (p_init->sd_req[0] != SD_REQ_APP_OVERWRITES_SD)
        {
            result = sd_req_check(p_init->sd_req,
                                  p_init->sd_req_count,
                                  (p_init->type == DFU_FW_TYPE_EXTERNAL_APPLICATION));
        }
        else if (p_init->type == DFU_FW_TYPE_APPLICATION)
        {
            // The application wants to overwrite the SoftDevice.
            if (prevent_downgrade && (p_init->sd_req_count > 1) && (p_init->sd_req[0] == SD_REQ_APP_OVERWRITES_SD))
            {
                // The application can overwrite the SD if sd_req[0] == 0 and table has the FWID of the current SD.
                result = sd_req_check(p_init->sd_req, p_init->sd_req_count, false);

                // Prevent BLE/ANT bootloaders from allowing applications overwriting the SoftDevice.
#if defined(BLE_STACK_SUPPORT_REQD) || defined(ANT_STACK_SUPPORT_REQD)
                result = false;
#endif
            }
            else
            {
                result = true;
            }
        }
#if NRF_DFU_SUPPORTS_EXTERNAL_APP
        else if(p_init->type == DFU_FW_TYPE_EXTERNAL_APPLICATION)
        {
            // Won't accept FW upgrade using external application to
            // enforce replacing SoftDevice (SD_REQ_APP_OVERWRITES_SD)
            result = false;
        }
#endif // NRF_DFU_SUPPORTS_EXTERNAL_APP
        else
        {
            // Don't allow SoftDevice updates which assume no SD is present already.
            result = !prevent_downgrade || (p_init->type != DFU_FW_TYPE_SOFTDEVICE);
        }
    }

    else
    {
        if (p_init->sd_req_count && (p_init->sd_req[0] != SD_REQ_APP_OVERWRITES_SD))
        {
            // Fail if there is no SD and the update requires SD. The special "any" FWID is valid
            // for external apps only.
            result = false;
#if NRF_DFU_SUPPORTS_EXTERNAL_APP
            result = sd_req_check(p_init->sd_req,
                                  p_init->sd_req_count,
                                  (p_init->type == DFU_FW_TYPE_EXTERNAL_APPLICATION));
#endif // NRF_DFU_SUPPORTS_EXTERNAL_APP
        }
        else
        {
            // If there is no SD and update has SD it is accepted only if it has a fw_version.
            result = !prevent_downgrade || p_init->has_fw_version;
        }
    }
    return result;
}


static bool fw_hash_type_ok(dfu_init_command_t const * p_init)
{
    ASSERT(p_init != NULL);

    return (p_init->hash.hash_type == DFU_HASH_TYPE_SHA256);
}


static bool fw_version_required(dfu_fw_type_t new_fw_type)
{
    bool result = true;

    if (new_fw_type == DFU_FW_TYPE_SOFTDEVICE)
    {
        result = false; // fw_version is optional in SoftDevice updates. If present, it will be checked against the app version.
    }
    else if (new_fw_type == DFU_FW_TYPE_APPLICATION)
    {
        result = NRF_DFU_APP_DOWNGRADE_PREVENTION; // fw_version is configurable in app updates.
    }
#if NRF_DFU_SUPPORTS_EXTERNAL_APP
#if !NRF_DFU_EXTERNAL_APP_VERSIONING
    else if (new_fw_type == DFU_FW_TYPE_EXTERNAL_APPLICATION)
    {
        return false;
    }
#endif //!NRF_DFU_EXTERNAL_APP_VERSIONING
#endif // NRF_DFU_SUPPORTS_EXTERNAL_APP

    return result;
}


static bool fw_type_ok(dfu_init_command_t const * p_init)
{
    ASSERT(p_init != NULL);

    return ((p_init->has_type)
            && (  (p_init->type == DFU_FW_TYPE_APPLICATION)
               || (p_init->type == DFU_FW_TYPE_SOFTDEVICE)
               || (p_init->type == DFU_FW_TYPE_BOOTLOADER)
               || (p_init->type == DFU_FW_TYPE_SOFTDEVICE_BOOTLOADER)
#if NRF_DFU_SUPPORTS_EXTERNAL_APP
               || (p_init->type == DFU_FW_TYPE_EXTERNAL_APPLICATION)
#endif // NRF_DFU_SUPPORTS_EXTERNAL_APP
            ));

}


#ifndef NRF_DFU_APP_ACCEPT_SAME_VERSION
#define NRF_DFU_APP_ACCEPT_SAME_VERSION 1
#endif


// This function assumes p_init->has_fw_version.
static bool fw_version_ok(dfu_init_command_t const * p_init)
{
    ASSERT(p_init != NULL);
    ASSERT(p_init->has_fw_version);

    if ((p_init->type == DFU_FW_TYPE_APPLICATION) ||
        (p_init->type == DFU_FW_TYPE_SOFTDEVICE))
    {
        if (!NRF_DFU_APP_DOWNGRADE_PREVENTION)
        {
            return true;
        }
        else if ((p_init->fw_version > s_dfu_settings.app_version))
        {
            return true;
        }
        else if ((p_init->fw_version == s_dfu_settings.app_version))
        {
            return NRF_DFU_APP_ACCEPT_SAME_VERSION;
        }
        else
        {
            return false;
        }
    }
#if NRF_DFU_SUPPORTS_EXTERNAL_APP
#if NRF_DFU_EXTERNAL_APP_VERSIONING
    else if (p_init->type == DFU_FW_TYPE_EXTERNAL_APPLICATION)
    {
        return (p_init->fw_version >= s_dfu_settings.app_version);
    }
#else
    else if(p_init->type == DFU_FW_TYPE_EXTERNAL_APPLICATION)
    {
        return true;
    }
#endif // NRF_DFU_EXTERNAL_APP_VERSIONING
#endif // NRF_DFU_SUPPORTS_EXTERNAL_APP
    else
    {
        return  (p_init->fw_version > s_dfu_settings.bootloader_version);
    }
}


nrf_dfu_result_t nrf_dfu_ver_validation_check(dfu_init_command_t const * p_init)
{
    nrf_dfu_result_t ret_val = NRF_DFU_RES_CODE_SUCCESS;
    if (!fw_type_ok(p_init))
    {
        NRF_LOG_ERROR("Invalid firmware type.");
        ret_val = EXT_ERR(NRF_DFU_EXT_ERROR_INIT_COMMAND_INVALID);
    }
    else if (!fw_hash_type_ok(p_init))
    {
        NRF_LOG_ERROR("Invalid hash type.");
        ret_val = EXT_ERR(NRF_DFU_EXT_ERROR_WRONG_HASH_TYPE);
    }
    else if (!NRF_DFU_DEBUG ||
            (NRF_DFU_DEBUG && ((p_init->has_is_debug == false) || (p_init->is_debug == false))))
    {
        if (p_init->has_hw_version == false)
        {
            NRF_LOG_ERROR("No HW version.");
            ret_val = EXT_ERR(NRF_DFU_EXT_ERROR_INIT_COMMAND_INVALID);
        }
        else if (p_init->hw_version != NRF_DFU_HW_VERSION)
        {
            NRF_LOG_WARNING("Faulty HW version.");
            ret_val = EXT_ERR( NRF_DFU_EXT_ERROR_HW_VERSION_FAILURE);
        }

        else if (!sd_req_ok(p_init))
        {
            NRF_LOG_WARNING("SD req not met.");
            ret_val = EXT_ERR(NRF_DFU_EXT_ERROR_SD_VERSION_FAILURE);
        }
        else if (p_init->has_fw_version)
        {
            if (!fw_version_ok(p_init))
            {
                NRF_LOG_WARNING("FW version too low.");
                ret_val = EXT_ERR(NRF_DFU_EXT_ERROR_FW_VERSION_FAILURE);
            }
        }
        else
        {
            if (fw_version_required(p_init->type))
            {
                NRF_LOG_ERROR("FW version missing.");
                ret_val = EXT_ERR(NRF_DFU_EXT_ERROR_INIT_COMMAND_INVALID);
            }
        }
    }
    return ret_val;
}