/**
 * Copyright (c) 2016 - 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 "nrf_bootloader_fw_activation.h"
#include "nrf_dfu_settings.h"
#include "nrf_dfu_mbr.h"
#include "nrf_bootloader_info.h"
#include "crc32.h"
#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_dfu_utils.h"
#include "nrf_bootloader_wdt.h"


static volatile bool m_flash_write_done;


/**
 * @brief Function for copying image. Image is copied in chunks. Frequency of storing progress
 *        in flash is configured by input parameter.
 *
 * @param[in] dst_addr             Destination address. Must be page aligned.
 * @param[in] src_addr             Source address. Must be higher value than dst_addr.
 * @param[in] size                 Image size.
 * @param[in] progress_update_step Number of copied pages that triggers saving progress to non-volatile memory.
 *                                 Note that step can be decreased if there is a risk of corruption caused by source
 *                                 and destination overlapping.
 *
 * @return NRF_SUCCESS or error code in case of failure.
 */
static uint32_t image_copy(uint32_t dst_addr,
                           uint32_t src_addr,
                           uint32_t size,
                           uint32_t progress_update_step)
{
    if (src_addr == dst_addr)
    {
        NRF_LOG_DEBUG("No copy needed");
        return NRF_SUCCESS;
    }

    ASSERT(src_addr >= dst_addr);
    ASSERT(progress_update_step > 0);
    if (size != 0)
    {
        ASSERT((dst_addr % CODE_PAGE_SIZE) == 0);
    }

    uint32_t max_safe_progress_upd_step = (src_addr - dst_addr)/CODE_PAGE_SIZE;
    ASSERT(max_safe_progress_upd_step > 0);

    uint32_t ret_val = NRF_SUCCESS;
    uint32_t pages_left = CEIL_DIV(size, CODE_PAGE_SIZE);

    //Firmware copying is time consuming operation thus watchdog handling is started
    nrf_bootloader_wdt_init();

    progress_update_step = MIN(progress_update_step, max_safe_progress_upd_step);

    while (size > 0)
    {
        uint32_t pages;
        uint32_t bytes;
        if (pages_left <= progress_update_step)
        {
            pages = pages_left;
            bytes = size;
        }
        else
        {
            pages = progress_update_step;
            bytes = progress_update_step * CODE_PAGE_SIZE;
        }
        // Erase the target pages
        ret_val = nrf_dfu_flash_erase(dst_addr, pages, NULL);
        if (ret_val != NRF_SUCCESS)
        {
            return ret_val;
        }

        // Flash one page
        NRF_LOG_DEBUG("Copying 0x%x to 0x%x, size: 0x%x", src_addr, dst_addr, bytes);
        ret_val = nrf_dfu_flash_store(dst_addr,
                                      (uint32_t *)src_addr,
                                      ALIGN_NUM(sizeof(uint32_t), bytes),
                                      NULL);
        if (ret_val != NRF_SUCCESS)
        {
            return ret_val;
        }

        pages_left  -= pages;
        size        -= bytes;
        dst_addr    += bytes;
        src_addr    += bytes;
        s_dfu_settings.write_offset += bytes;

        //store progress in flash on every successful chunk write
        ret_val = nrf_dfu_settings_write_and_backup(NULL);
        if (ret_val != NRF_SUCCESS)
        {
            NRF_LOG_ERROR("Failed to write image copying progress to settings page.");
            return ret_val;
        }
    }

    return ret_val;
}

/** @brief Function to continue application update.
 *
 * @details This function will be called after reset if there is a valid application in Bank1
 *          required to be copied down to Bank 0.
 *
 * @return NRF_SUCCESS if continuation was successful, NRF_ERROR_INTERNAL if new firmware does not
 *         contain softdevice or other error coming from modules used by this function.
 */
static uint32_t app_activate(void)
{
    // This function is only in use when new app is present in Bank 1
    uint32_t const image_size  = s_dfu_settings.bank_1.image_size;

    uint32_t src_addr    = s_dfu_settings.progress.update_start_address;
    uint32_t ret_val     = NRF_SUCCESS;
    uint32_t target_addr = nrf_dfu_bank0_start_addr() + s_dfu_settings.write_offset;
    uint32_t length_left = (image_size - s_dfu_settings.write_offset);
    uint32_t crc;

    NRF_LOG_DEBUG("Enter nrf_dfu_app_continue");

    src_addr += s_dfu_settings.write_offset;

    if (src_addr == target_addr)
    {
        length_left = 0;
    }

    ret_val = image_copy(target_addr, src_addr, length_left, NRF_BL_FW_COPY_PROGRESS_STORE_STEP);
    if (ret_val != NRF_SUCCESS)
    {
        NRF_LOG_ERROR("Failed to copy firmware.");
        return ret_val;
    }

    // Check the CRC of the copied data. Enable if so.
    crc = crc32_compute((uint8_t*)nrf_dfu_bank0_start_addr(), image_size, NULL);

    if (crc == s_dfu_settings.bank_1.image_crc)
    {
        NRF_LOG_DEBUG("Setting app as valid");
        s_dfu_settings.bank_0.bank_code = NRF_DFU_BANK_VALID_APP;
        s_dfu_settings.bank_0.image_crc = crc;
        s_dfu_settings.bank_0.image_size = image_size;
    }
    else
    {
        NRF_LOG_ERROR("CRC computation failed for copied app: "
                      "src crc: 0x%08x, res crc: 0x%08x",
                      s_dfu_settings.bank_1.image_crc,
                      crc);
    }

    return ret_val;
}


/** @brief Function to execute the continuation of a SoftDevice update.
 *
 * @return NRF_SUCCESS if continuation was successful, NRF_ERROR_INTERNAL if new firmware does not
 *         contain softdevice or other error coming from modules used by this function.
 */
static uint32_t sd_activate(void)
{
    uint32_t   ret_val      = NRF_SUCCESS;
    uint32_t   target_addr  = nrf_dfu_softdevice_start_address() + s_dfu_settings.write_offset;
    uint32_t   src_addr     = s_dfu_settings.progress.update_start_address;
    uint32_t   sd_size      = s_dfu_settings.sd_size;
    uint32_t   length_left  = ALIGN_TO_PAGE(sd_size - s_dfu_settings.write_offset);

    NRF_LOG_DEBUG("Enter nrf_bootloader_dfu_sd_continue");

    if (SD_MAGIC_NUMBER_GET(src_addr) != SD_MAGIC_NUMBER)
    {
        NRF_LOG_ERROR("Source address does not contain a valid SoftDevice.")
        return NRF_ERROR_INTERNAL;
    }

    // This can be a continuation due to a power failure
    src_addr += s_dfu_settings.write_offset;

    if (s_dfu_settings.write_offset == sd_size)
    {
        NRF_LOG_DEBUG("SD already copied");
        return NRF_SUCCESS;
    }

    if (s_dfu_settings.write_offset == 0)
    {
        NRF_LOG_DEBUG("Updating SD. Old SD ver: %d, New ver: %d",
            SD_VERSION_GET(MBR_SIZE) / 1000000, SD_VERSION_GET(src_addr) / 1000000);
    }

    ret_val = image_copy(target_addr, src_addr, length_left, NRF_BL_FW_COPY_PROGRESS_STORE_STEP);
    if (ret_val != NRF_SUCCESS)
    {
        NRF_LOG_ERROR("Failed to copy firmware.");
        return ret_val;
    }

    ret_val = nrf_dfu_settings_write_and_backup(NULL);

    return ret_val;
}


/** @brief Function to continue bootloader update.
 *
 * @details     This function will be called after reset if there is a valid bootloader in Bank 0 or Bank 1
 *              required to be relocated and activated through MBR commands.
 *
 * @return This function will not return if the bootloader is copied successfully.
 *         After the copy is verified, the device will reset and start the new bootloader.
 *
 * @retval NRF_SUCCESS Continuation was successful.
 * @retval NRF_ERROR_INVALID_LENGTH Invalid length of flash operation.
 * @retval NRF_ERROR_NO_MEM If no parameter page is provided (see sds for more info).
 * @retval NRF_ERROR_INVALID_PARAM If an invalid command is given.
 * @retval NRF_ERROR_INTERNAL Internal error that should not happen.
 * @retval NRF_ERROR_FORBIDDEN If NRF_UICR->BOOTADDR is not set.
 */
static uint32_t bl_activate(void)
{
    uint32_t         ret_val  = NRF_ERROR_INVALID_DATA;
    nrf_dfu_bank_t * p_bank   = &s_dfu_settings.bank_1;
    uint32_t         len      = p_bank->image_size;
    uint32_t         src_addr = s_dfu_settings.progress.update_start_address;

    if (p_bank->bank_code == NRF_DFU_BANK_VALID_SD_BL)
    {
        src_addr += s_dfu_settings.sd_size;
        len      -= s_dfu_settings.sd_size;
    }
    else if (src_addr == 0)
    {
        src_addr = nrf_dfu_bank1_start_addr();
    }

    NRF_LOG_DEBUG("Verifying BL: Addr: 0x%08x, Src: 0x%08x, Len: 0x%08x", BOOTLOADER_START_ADDR, src_addr, len);

    // This code is a configurable workaround for updating SD+BL from SDK 12.x.y - 14.1.0
    // SoftDevice size increase would lead to unaligned source address when comparing new BL in SD+BL updates.
    // This workaround is not required once BL is successfully installed with a version that is compiled SDK 14.1.0
#if defined(NRF52832_XXAA) && defined(BLE_STACK_SUPPORT_REQD)
    if ((p_bank->bank_code == NRF_DFU_BANK_VALID_SD_BL) &&
        (memcmp((void *)BOOTLOADER_START_ADDR, (void *)(src_addr - 0x4000), len) == 0))
    {
        ret_val = NRF_SUCCESS;
    }
#endif // defined(NRF52832_XXAA)

    // Check if the BL has already been copied.
    if ((ret_val != NRF_SUCCESS) &&
        (memcmp((void *)BOOTLOADER_START_ADDR, (void *)src_addr, len) == 0))
    {
        ret_val =  NRF_SUCCESS;
    }

    // If the bootloader is the same as the banked version, the copy is finished
    if (ret_val == NRF_SUCCESS)
    {
        NRF_LOG_DEBUG("No bootloader copy needed, bootloader update complete.");
    }
    else
    {
        NRF_LOG_DEBUG("Copying bootloader: Src: 0x%08x, Len: 0x%08x", src_addr, len);
        NRF_LOG_FLUSH();

        nrf_bootloader_wdt_feed();

        // Bootloader is different than the banked version. Continue copy
        // Note that if the SD and BL was combined, then the split point between them is in s_dfu_settings.sd_size
        // On success this function won't return.
        ret_val = nrf_dfu_mbr_copy_bl((uint32_t*)src_addr, len);
        if (ret_val != NRF_SUCCESS)
        {
            NRF_LOG_ERROR("Request to copy BL failed");
        }
    }

    return ret_val;
}


/** @brief Function to continue combined bootloader and SoftDevice update.
 *
 * @details     This function will be called after reset if there is a valid bootloader and SoftDevice in Bank 0 or Bank 1
 *              required to be relocated and activated through MBR commands.
 *
 * @retval NRF_SUCCESS Continuation was successful.
 * @retval NRF_ERROR_INVALID_LENGTH Invalid length.
 * @retval NRF_ERROR_NO_MEM If UICR.NRFFW[1] is not set (i.e. is 0xFFFFFFFF).
 * @retval NRF_ERROR_INVALID_PARAM If an invalid command is given.
 * @retval NRF_ERROR_INTERNAL Indicates that the contents of the memory blocks where not verified correctly after copying.
 * @retval NRF_ERROR_NULL If the content of the memory blocks differs after copying.
 * @retval NRF_ERROR_FORBIDDEN If NRF_UICR->BOOTADDR is not set.
 */
static uint32_t sd_bl_activate()
{
    uint32_t ret_val = NRF_SUCCESS;

    NRF_LOG_DEBUG("Enter nrf_dfu_sd_bl_continue");

    ret_val = sd_activate();
    if (ret_val != NRF_SUCCESS)
    {
        NRF_LOG_ERROR("SD+BL: SD copy failed");
        return ret_val;
    }

    ret_val = bl_activate();
    if (ret_val != NRF_SUCCESS)
    {
        NRF_LOG_ERROR("SD+BL: BL copy failed");
        return ret_val;
    }

    return ret_val;
}


static void flash_write_callback(void * p_context)
{
    UNUSED_PARAMETER(p_context);
    m_flash_write_done = true;
}


nrf_bootloader_fw_activation_result_t nrf_bootloader_fw_activate(void)
{
    nrf_bootloader_fw_activation_result_t result;
    uint32_t                              ret_val   = NRF_SUCCESS;
    nrf_dfu_bank_t                      * p_bank    = &s_dfu_settings.bank_1;
    bool                                  sd_update = false;

    NRF_LOG_DEBUG("Enter nrf_bootloader_fw_activate");

    switch (p_bank->bank_code)
    {
       case NRF_DFU_BANK_VALID_APP:
            NRF_LOG_DEBUG("Valid App");
            ret_val = app_activate();
            break;
       case NRF_DFU_BANK_VALID_SD:
            NRF_LOG_DEBUG("Valid SD");
            ret_val = sd_activate();
            sd_update = true;
            break;
        case NRF_DFU_BANK_VALID_BL:
            NRF_LOG_DEBUG("Valid BL");
            ret_val = bl_activate();
            break;
        case NRF_DFU_BANK_VALID_SD_BL:
            NRF_LOG_DEBUG("Valid SD + BL");
            ret_val = sd_bl_activate();
            sd_update = true;
            break;
        case NRF_DFU_BANK_INVALID:
        default:
            NRF_LOG_INFO("No firmware to activate.");
            return ACTIVATION_NONE;
    }

    if (ret_val != NRF_SUCCESS)
    {
        NRF_LOG_ERROR("Activation failed with error %d (bank code: 0x%x)", ret_val, p_bank->bank_code);
        result = ACTIVATION_ERROR;
    }

    // Invalidate bank, marking completion.
    nrf_dfu_bank_invalidate(p_bank);

    m_flash_write_done = false;
    ret_val = nrf_dfu_settings_write_and_backup(flash_write_callback);
    ASSERT(m_flash_write_done); /* At this point flash module is performing blocking operation. It is expected that operation is already performed. */

    if (ret_val == NRF_SUCCESS)
    {
        result = ACTIVATION_SUCCESS;
        if (sd_update && (s_dfu_settings.bank_0.bank_code == NRF_DFU_BANK_VALID_APP))
        {
            //If SD was updated and application is valid we want to stay in DFU to receive application.
            NRF_LOG_DEBUG("A SoftDevice has just been activated. It's likely that an application will come immediately");
            result = ACTIVATION_SUCCESS_EXPECT_ADDITIONAL_UPDATE;
        }
    }
    else
    {
        NRF_LOG_ERROR("Could not write settings.");
        result = ACTIVATION_ERROR;
    }

    return result;
}

