/**
 * @file ota_common.cpp
 * @author BES AI team
 * @version 0.1
 * @date 2020-04-17
 *
 * @copyright Copyright (c) 2015-2020 BES Technic.
 * All rights reserved. All unpublished rights reserved.
 *
 * No part of this work may be used or reproduced in any form or by any
 * means, or stored in a database or retrieval system, without prior written
 * permission of BES.
 *
 * Use of this work is governed by a license granted by BES.
 * This work contains confidential and proprietary information of
 * BES. which is protected by copyright, trade secret,
 * trademark and other intellectual property rights.
 */

/*****************************header include********************************/
#include "string.h"
#include "cmsis.h"
#include "hal_timer.h"
#include "norflash_drv.h"
#include "norflash_api.h"
#include "crc32.h"
#include "bt_drv_reg_op.h"
#include "app_bt.h"
#include "app.h"
#include "apps.h"
#include "nvrecord_ota.h"
#include "app_utils.h"
#include "ota_dbg.h"
#include "ota_common.h"

#ifdef IBRT
#include "app_tws_ibrt.h"
#include "app_tws_ctrl_thread.h"
#include "app_ibrt_ota_cmd.h"
#include "app_tws_if.h"
#include "app_ibrt_ui.h"
#include "app_ibrt_if.h"
#endif

/*********************external function declearation************************/

/************************private macro defination***************************/
#define OTA_BOOT_INFO_FLASH_OFFSET 0x1000
#define OTA_BREAKPOINT_STORE_GRANULARITY (256 * 1024) // must be 4KB aligned
#define LEN_OF_IMAGE_TAIL_TO_FIND_SANITY_CRC 512
#define INVALID_VERSION_STR "BESTECHNIC"

#define NORFLASH_API_MODE_ASYNC true
#define OTA_DEFAULT_NORFLASH_API_MODE NORFLASH_API_MODE_ASYNC

#define CASES(prefix, item) \
    case prefix##item:      \
        str = #item;        \
        break;

/************************private type defination****************************/

/**********************private function declearation************************/

/************************private variable defination************************/
__attribute__((unused)) static const char *imageSanityKeyWord = "CRC32_OF_IMAGE=0x";
__attribute__((unused)) static const char *oldImageSanityKeyWord = "CRC32_OF_IMAGE=";

#ifdef IBRT
static void _ota_tws_thread(const void *arg);
osThreadDef(_ota_tws_thread, osPriorityNormal, 1, 1024, "ota_tws");

static osEvent evt;

/// tws relay OTA command/data TX thread ID
osThreadId txThreadId = NULL;

/// tws relay OTA command/data RX thread ID
osThreadId rxThreadId;

osMutexDef(twsTxQueueMutex);
osMutexDef(twsRxQueueMutex);

/// tws relay data TX queue mutex
osMutexId txQueueMutexID = NULL;

/// tws relay data RX queue mutex
osMutexId rxQueueMutexID = NULL;

uint8_t relayBuf[TWS_RELAY_DATA_MAX_SIZE];
#endif

OTA_COMMON_ENV_T otaEnv;


/****************************function defination****************************/

/**
 * @brief Convert OTA command to string
 *
 * @param cmd           @see OTA_COMMAND_E
 * @return char*        string of OTA_COMMAND
 */
static char *_cmd2str(OTA_COMMAND_E cmd)
{
    const char *str = NULL;

    switch (cmd)
    {
        CASES(OTA_COMMAND_, BEGIN);
        CASES(OTA_COMMAND_, APPLY);
        CASES(OTA_COMMAND_, DATA);
        CASES(OTA_COMMAND_, ABORT);
#ifdef IBRT
        CASES(OTA_COMMAND_, RSP);
#endif
        // CASES(OTA_COMMAND_, );

    default:
        str = "INVALID";
        break;
    }

    return (char *)str;
}

/**
 * @brief Convert OTA user to string
 *
 * @param user          @see OTA_USER_E
 * @return char*        OTA_USER string
 */
static char *_user2str(OTA_USER_E user)
{
    const char *str = NULL;

    switch (user)
    {
        CASES(OTA_USER_, BES);
        CASES(OTA_USER_, COLORFUL);
        CASES(OTA_USER_, RED);
        CASES(OTA_USER_, ORANGE);
        CASES(OTA_USER_, GREEN);
        // CASES(OTA_USER_, );

    default:
        str = "INVALID";
        break;
    }

    return (char *)str;
}

/**
 * @brief Convert OTA path into string
 *
 * @param path          @see OTA_PATH_E
 * @return char*        OTA_PATH string
 */
static char *_path2str(OTA_PATH_E path)
{
    const char *str = NULL;

    switch (path)
    {
        CASES(OTA_PATH_, BT);
        CASES(OTA_PATH_, BLE);
        // CASES(OTA_PATH_, );

    default:
        str = "INVALID";
        break;
    }

    return (char *)str;
}

/**
 * @brief Convert OTA stage into string
 *
 * @param stage         @see OTA_STAGE_E
 * @return char*        OTA_STAGE string
 */
static char *_stage2str(OTA_STAGE_E stage)
{
    const char *str = NULL;

    switch (stage)
    {
        CASES(OTA_STAGE_, IDLE);
        CASES(OTA_STAGE_, ONGOING);
        CASES(OTA_STAGE_, DONE);
        // CASES(OTA_STAGE_, );

    default:
        str = "INVALID";
        break;
    }

    return (char *)str;
}

static char *_sts2str(OTA_STATUS_E sts)
{
    const char *str = NULL;

    switch (sts)
    {
        CASES(OTA_STATUS_, OK);
        CASES(OTA_STATUS_, ERROR);
        CASES(OTA_STATUS_, ERROR_RELAY_TIMEOUT);
        CASES(OTA_STATUS_, ERROR_CHECKSUM);
        CASES(OTA_STATUS_, ERROR_NOT_ALLOWED);
        // CASES(OTA_STATUS_, );

    default:
        str = "INVALID";
        break;
    }

    return (char *)str;
}

/**
 * @brief Find key word in upgrade data.
 *
 * This function is used to find out the key word at the end of upgrade data,
 * these key words are generated by python script by calculate the crc of whole
 * upgrade image and write the result at the end of the image in string format.
 * This is used to do sanity check.
 *
 * @param tgtArray      source array to search the key word
 * @param tgtArrayLen   source array length
 * @param keyArray      key word array
 * @param keyArrayLen   key word length
 * @return int32_t      index of the key word in dstArry
 */
__attribute__((unused)) static int32_t _find_key_word(uint8_t *tgtArray,
                              uint32_t tgtArrayLen,
                              uint8_t *keyArray,
                              uint32_t keyArrayLen)
{
    if ((keyArrayLen > 0) && (tgtArrayLen >= keyArrayLen))
    {
        uint32_t index = 0, targetIndex = 0;
        for (targetIndex = 0; targetIndex < tgtArrayLen; targetIndex++)
        {
            for (index = 0; index < keyArrayLen; index++)
            {
                if (tgtArray[targetIndex + index] != keyArray[index])
                {
                    break;
                }
            }

            if (index == keyArrayLen)
            {
                return targetIndex;
            }
        }

        return -1;
    }
    else
    {
        return -1;
    }
}

/**
 * @brief Convert ASCII code into hex format.
 *
 * @param asciiCode     ASCII code to convert
 * @return uint8_t      convert result
 */
__attribute__((unused)) static uint8_t _ascii2hex(uint8_t asciiCode)
{
    if ((asciiCode >= '0') && (asciiCode <= '9'))
    {
        return asciiCode - '0';
    }
    else if ((asciiCode >= 'a') && (asciiCode <= 'f'))
    {
        return asciiCode - 'a' + 10;
    }
    else if ((asciiCode >= 'A') && (asciiCode <= 'F'))
    {
        return asciiCode - 'A' + 10;
    }
    else
    {
        return 0xff;
    }
}

/**
 * @brief Update the stage of OTA progress.
 *
 * @param stage         OTA stage to update,
 *                      @see OTA_STAGE_E to get more details.
 */
static void _set_ota_stage(OTA_STAGE_E stage)
{
    LOG_D("stage update:%s->%s",
          _stage2str(otaEnv.currentStage),
          _stage2str(stage));

    otaEnv.currentStage = stage;
}

static void _update_ota_user(OTA_USER_E user)
{
    ASSERT((OTA_USER_NUM == otaEnv.currentUser) || (OTA_USER_NUM == user),
           "try to set a ota user while the current user is not null");

    LOG_I("ota user update:%s->%s",
          _user2str(otaEnv.currentUser),
          _user2str(user));

    otaEnv.currentUser = user;
}


/**
 * @brief Enter OTA state handler.
 *
 * This function is used to require the relative system resources to gurantee
 * the performance for both OTA progress and other functionalities
 *
 * @param path          Current OTA path,
 *                      @see OTA_PATH_E to get more details.
 */
static void _enter_ota_state(OTA_PATH_E path)
{
    LOG_I("%s", __func__);

    if (otaEnv.isInOtaState)
    {
        LOG_W("ALREADY in OTA state");
    }
    else
    {
        /// 1. guarantee performance->switch to the highest freq
        app_sysfreq_req(APP_SYSFREQ_USER_OTA, APP_SYSFREQ_104M);

#ifdef IBRT
        /// 2. guarantee performance->decrease the communication interval of TWS connection
        ibrt_ctrl_t *p_ibrt_ctrl = app_tws_ibrt_get_bt_ctrl_ctx();
        btdrv_reg_op_set_tws_link_duration(IBRT_TWS_LINK_LARGE_DURATION);
        btdrv_reg_op_set_private_tws_poll_interval(p_ibrt_ctrl->config.default_private_poll_interval,
                                                   p_ibrt_ctrl->config.default_private_poll_interval_in_sco);

        /// 3. guarantee performance->exit bt sniff mode
        app_ibrt_ui_judge_link_policy(OTA_START_TRIGGER, BTIF_BLP_DISABLE_ALL);

        if (app_tws_ibrt_tws_link_connected() && \
            (p_ibrt_ctrl->nv_role == IBRT_MASTER) && \
            p_ibrt_ctrl->p_tws_remote_dev)
        {
            btif_me_stop_sniff(p_ibrt_ctrl->p_tws_remote_dev);
        }

        app_ibrt_if_tws_sniff_block(15);
#else
        /// 3. guarantee performance->exit bt sniff mode
        app_bt_active_mode_set(ACTIVE_MODE_KEEPER_OTA, UPDATE_ACTIVE_MODE_FOR_ALL_LINKS);
#endif

        /// update the OTA path
        LOG_I("OTA path update:%s->%s", _path2str(otaEnv.currentPath), _path2str(path));
        otaEnv.currentPath = path;

        /// 4. guarantee performance->decrease the BLE connection interval
#ifdef BLE_ENABLE
        if (OTA_PATH_BLE == otaEnv.currentPath)
        {
            app_ble_update_conn_param_mode(BLE_CONN_PARAM_MODE_OTA, true);
        }
#endif
    }
}

/**
 * @brief Exit the OTA state handler.
 *
 * This function is used to release the system resources which are required
 * when enter the OTA state
 *
 */
__attribute__((unused)) static void _exit_ota_state(void)
{
    LOG_I("%s", __func__);

    if (otaEnv.isInOtaState)
    {
#ifdef IBRT
        app_ibrt_ui_judge_link_policy(OTA_STOP_TRIGGER, BTIF_BLP_SNIFF_MODE);
#else
        app_bt_active_mode_clear(ACTIVE_MODE_KEEPER_OTA, UPDATE_ACTIVE_MODE_FOR_ALL_LINKS);
#endif

        /// release the short TWS communication interval
#ifdef IBRT
        ibrt_ctrl_t *p_ibrt_ctrl = app_tws_ibrt_get_bt_ctrl_ctx();
        btdrv_reg_op_set_tws_link_duration(IBRT_TWS_LINK_DEFAULT_DURATION);
        btdrv_reg_op_set_private_tws_poll_interval(p_ibrt_ctrl->config.long_private_poll_interval,
                                                   p_ibrt_ctrl->config.default_private_poll_interval_in_sco);
#endif

        /// release the short BLE connection interval
#ifdef BLE_ENABLE
        app_ble_update_conn_param_mode(BLE_CONN_PARAM_MODE_OTA, false);
        app_ble_update_conn_param_mode(BLE_CONN_PARAM_MODE_OTA_SLOWER, false);
#endif

        /// update the OTA path to invalid
        otaEnv.currentPath = OTA_PATH_INVALID;

        /// release the system frequency
        app_sysfreq_req(APP_SYSFREQ_USER_OTA, APP_SYSFREQ_32K);

        otaEnv.isInOtaState = false;
    }
    else
    {
        LOG_W("NOT in OTA STATE");
    }
}

#ifdef IBRT

/**
 * @brief Do the sanity check for the upgrade file.
 *
 * Currently, this function uses the CRC that was inserted by the build process.
 * This CRC check does not verify the last x bytes of the image.  x varies
 * depending on order of the key/value pair order but is < 512 bytes.
 *
 * @return true         Sanity check success.
 * @return false        Sanity check failed.
 */
static bool _image_sanity_check(void)
{
    // find the location of the CRC key word string
    uint8_t *ptrOfTheLast4KImage = (uint8_t *)(OTA_FLASH_LOGIC_ADDR +
                                               NEW_IMAGE_FLASH_OFFSET +
                                               otaEnv.totalImageSize -
                                               LEN_OF_IMAGE_TAIL_TO_FIND_SANITY_CRC);

    uint32_t sanityCrc32;
    uint32_t crc32ImageOffset;
    int32_t sanity_crc_location = _find_key_word(ptrOfTheLast4KImage,
                                                 LEN_OF_IMAGE_TAIL_TO_FIND_SANITY_CRC,
                                                 (uint8_t *)imageSanityKeyWord,
                                                 strlen(imageSanityKeyWord));
    if (-1 == sanity_crc_location)
    {
        sanity_crc_location = _find_key_word(ptrOfTheLast4KImage,
                                             LEN_OF_IMAGE_TAIL_TO_FIND_SANITY_CRC,
                                             (uint8_t *)oldImageSanityKeyWord,
                                             strlen(oldImageSanityKeyWord));
        if (-1 == sanity_crc_location)
        {
            // if no sanity crc, fail the check
            return false;
        }
        else
        {
            crc32ImageOffset = sanity_crc_location +
                               otaEnv.totalImageSize -
                               LEN_OF_IMAGE_TAIL_TO_FIND_SANITY_CRC +
                               strlen(oldImageSanityKeyWord);
            sanityCrc32 = *(uint32_t *)(OTA_FLASH_LOGIC_ADDR + NEW_IMAGE_FLASH_OFFSET + crc32ImageOffset);
        }
    }
    else
    {
        crc32ImageOffset = sanity_crc_location +
                           otaEnv.totalImageSize -
                           LEN_OF_IMAGE_TAIL_TO_FIND_SANITY_CRC +
                           strlen(imageSanityKeyWord);

        sanityCrc32 = 0;
        uint8_t *crcString = (uint8_t *)(OTA_FLASH_LOGIC_ADDR + NEW_IMAGE_FLASH_OFFSET + crc32ImageOffset);

        for (uint8_t index = 0; index < 8; index++)
        {
            sanityCrc32 |= (_ascii2hex(crcString[index]) << (28 - 4 * index));
        }
    }

    LOG_D("Bytes to generate crc32 is %d", crc32ImageOffset);
    LOG_D("sanity_crc_location is %d", sanity_crc_location);

    LOG_D("sanityCrc32 is 0x%x", sanityCrc32);

    // generate the CRC from image data
    uint32_t calculatedCrc32 = 0;
    calculatedCrc32 = crc32(calculatedCrc32, (uint8_t *)(OTA_FLASH_LOGIC_ADDR + NEW_IMAGE_FLASH_OFFSET), crc32ImageOffset);

    LOG_D("calculatedCrc32 is 0x%x", calculatedCrc32);

    if (sanityCrc32 == calculatedCrc32)
    {
        return true;
    }

    return false;
}

static enum NORFLASH_API_MODULE_ID_T
_get_flash_module_from_ota_device(OTA_DEVICE_E device)
{
    enum NORFLASH_API_MODULE_ID_T mod = NORFLASH_API_MODULE_ID_COUNT;

    switch (device)
    {
    case OTA_DEVICE_APP:
        mod = NORFLASH_API_MODULE_ID_OTA;
        break;
    case OTA_DEVICE_HOTWORD:
        mod = NORFLASH_API_MODULE_ID_HOTWORD_MODEL;
        break;

    default:
        ASSERT(0, "invalid OTA device received in %s", __func__);
        break;
    }

    return mod;
}

static void _flush_data_to_flash(uint8_t *ptrSource,
                                 uint32_t lengthToBurn,
                                 uint32_t offsetInFlashToProgram,
                                 bool synWrite)
{

    LOG_D("flush %d bytes to flash offset 0x%x", lengthToBurn, offsetInFlashToProgram);
    enum NORFLASH_API_MODULE_ID_T mod = _get_flash_module_from_ota_device(otaEnv.deviceId);

    uint32_t preBytes = (FLASH_SECTOR_SIZE_IN_BYTES -
                         (offsetInFlashToProgram % FLASH_SECTOR_SIZE_IN_BYTES)) %
                        FLASH_SECTOR_SIZE_IN_BYTES;

    if (lengthToBurn < preBytes)
    {
        preBytes = lengthToBurn;
    }

    uint32_t middleBytes = 0;
    if (lengthToBurn > preBytes)
    {
        middleBytes = ((lengthToBurn - preBytes) / FLASH_SECTOR_SIZE_IN_BYTES * FLASH_SECTOR_SIZE_IN_BYTES);
    }

    uint32_t postBytes = 0;
    if (lengthToBurn > (preBytes + middleBytes))
    {
        postBytes = (offsetInFlashToProgram + lengthToBurn) % FLASH_SECTOR_SIZE_IN_BYTES;
    }

    LOG_D("Prebytes is %d middlebytes is %d postbytes is %d", preBytes, middleBytes, postBytes);

    if (preBytes > 0)
    {
        app_flash_page_program(mod, offsetInFlashToProgram, ptrSource, preBytes, synWrite);

        ptrSource += preBytes;
        offsetInFlashToProgram += preBytes;
    }

    uint32_t sectorIndexInFlash = offsetInFlashToProgram / FLASH_SECTOR_SIZE_IN_BYTES;

    if (middleBytes > 0)
    {
        uint32_t sectorCntToProgram = middleBytes / FLASH_SECTOR_SIZE_IN_BYTES;
        for (uint32_t sector = 0; sector < sectorCntToProgram; sector++)
        {
            app_flash_page_erase(mod, sectorIndexInFlash * FLASH_SECTOR_SIZE_IN_BYTES);
            app_flash_page_program(mod,
                                   sectorIndexInFlash * FLASH_SECTOR_SIZE_IN_BYTES,
                                   ptrSource + sector * FLASH_SECTOR_SIZE_IN_BYTES,
                                   FLASH_SECTOR_SIZE_IN_BYTES,
                                   synWrite);

            sectorIndexInFlash++;
        }

        ptrSource += middleBytes;
    }

    if (postBytes > 0)
    {
        app_flash_page_erase(mod, sectorIndexInFlash * FLASH_SECTOR_SIZE_IN_BYTES);
        app_flash_page_program(mod,
                               sectorIndexInFlash * FLASH_SECTOR_SIZE_IN_BYTES,
                               ptrSource,
                               postBytes,
                               synWrite);
    }

    app_flush_pending_flash_op(mod, NORFLASH_API_ALL);
}

/**
 * NOTE that this function is stil needed since the OTA bootloader uses the CRC
 * that is passed into it to re-verify the image and that CRC is for the entire
 * image.
 */
static bool _compute_whole_image_crc(void)
{
    uint32_t processedDataSize = 0;
    uint32_t crc32Value = 0;
    uint32_t bytes_to_use = 0;
    uint32_t lock;

    while (processedDataSize < otaEnv.totalImageSize)
    {
        if (otaEnv.totalImageSize - processedDataSize > OTA_DATA_CACHE_BUFFER_SIZE)
        {
            bytes_to_use = OTA_DATA_CACHE_BUFFER_SIZE;
        }
        else
        {
            bytes_to_use = otaEnv.totalImageSize - processedDataSize;
        }

        lock = int_lock_global();
        norflash_sync_read(NORFLASH_API_MODULE_ID_OTA,
                           (OTA_FLASH_LOGIC_ADDR + NEW_IMAGE_FLASH_OFFSET + processedDataSize),
                           otaEnv.dataCacheBuffer,
                           OTA_DATA_CACHE_BUFFER_SIZE);
        // memcpy(otaEnv.dataCacheBuffer, (uint8_t *)(OTA_FLASH_LOGIC_ADDR + NEW_IMAGE_FLASH_OFFSET + processedDataSize),
        //    OTA_DATA_CACHE_BUFFER_SIZE);
        int_unlock_global(lock);

        if (0 == processedDataSize)
        {
            if (*(uint32_t *)otaEnv.dataCacheBuffer != NORMAL_BOOT)
            {
                LOG_D("first 32bit value is not NORMAL_BOOT");
                return false;
            }
            else
            {
                *(uint32_t *)otaEnv.dataCacheBuffer = 0xFFFFFFFF;
            }
        }

        LOG_D("bytes to verify =%d.", bytes_to_use);

        crc32Value = crc32(crc32Value, (uint8_t *)otaEnv.dataCacheBuffer, bytes_to_use);

        processedDataSize += bytes_to_use;
    }

    LOG_D("Computed CRC32 is 0x%x.", crc32Value);

    /* This crc value will be passed to the ota app in GSoundOtaApply(). */
    otaEnv.crc32OfImage = crc32Value;
    return true;
}

static void _update_boot_info(OTA_BOOT_INFO_T *otaBootInfo)
{
    ASSERT(OTA_DEVICE_APP == otaEnv.deviceId, "illegal OTA device try to update boot info");
    hal_norflash_disable_protection(HAL_NORFLASH_ID_0);
    enum NORFLASH_API_MODULE_ID_T mod = _get_flash_module_from_ota_device(otaEnv.deviceId);
    app_flash_page_erase(mod, OTA_BOOT_INFO_FLASH_OFFSET);
    app_flash_page_program(mod,
                           OTA_BOOT_INFO_FLASH_OFFSET,
                           (uint8_t *)otaBootInfo,
                           sizeof(OTA_BOOT_INFO_T),
                           true);
}

static void _update_magic_number(uint32_t newMagicNumber)
{
    ASSERT(OTA_DEVICE_APP == otaEnv.deviceId, "illegal device %d to update magic number",
        otaEnv.deviceId);
    memcpy(otaEnv.dataCacheBuffer,
           (uint8_t *)(OTA_FLASH_LOGIC_ADDR + otaEnv.newImageFlashOffset),
           FLASH_SECTOR_SIZE_IN_BYTES);

    *(uint32_t *)otaEnv.dataCacheBuffer = newMagicNumber;
    enum NORFLASH_API_MODULE_ID_T mod = _get_flash_module_from_ota_device(otaEnv.deviceId);
    app_flash_page_erase(mod, otaEnv.newImageFlashOffset);

    app_flash_page_program(mod,
                           otaEnv.newImageFlashOffset,
                           otaEnv.dataCacheBuffer,
                           FLASH_SECTOR_SIZE_IN_BYTES,
                           true);

    app_flush_pending_flash_op(mod, NORFLASH_API_ALL);
}

static void _update_peer_result(OTA_STATUS_E ret)
{
    LOG_D("update peer result:%s->%s",
          _sts2str(otaEnv.peerResult),
          _sts2str(ret));

    otaEnv.peerResult = ret;
}

OTA_STATUS_E ota_common_get_peer_result(void)
{
    return otaEnv.peerResult;
}

static bool _relay_data_needed(void)
{
    bool ret = true;

    if (!app_tws_ibrt_tws_link_connected())
    {
        ret = false;
    }

    if (OTA_STAGE_IDLE == otaEnv.currentStage)
    {
        ret = false;
    }

    /// check the customized relay_data_needed permission
    if (ret && otaEnv.customRelayNeededHandler)
    {
        ret = otaEnv.customRelayNeededHandler();
    }

    return ret;
}

static void _ota_relay_data(OTA_COMMAND_E cmdType,
                            const uint8_t *dataPtr,
                            uint16_t length)
{
    uint8_t frameNum = 0;
    uint16_t frameLen = 0;
    OTA_TWS_DATA_T tCmd = {
        cmdType,
    };

    ASSERT(length <= ARRAY_SIZE(tCmd.data), "ILLEGAL relay packet length");

    /// split packet into servel frame to fulfill the max TWS data
    /// transmission length requirement
    if (length % OTA_TWS_PAYLOAD_MAX_LEN)
    {
        frameNum = length / OTA_TWS_PAYLOAD_MAX_LEN + 1;
    }
    else
    {
        frameNum = length / OTA_TWS_PAYLOAD_MAX_LEN;
    }

    LOG_D("packet len:%d, splited frame num:%d", length, frameNum);

    /// push all data into queue
    for (uint8_t i = 0; i < frameNum; i++)
    {
        if ((i + 1) == frameNum)
        {
            tCmd.magicCode = OTA_RELAY_PACKET_MAGIC_CODE_COMPLETE;
            tCmd.length = length % OTA_TWS_PAYLOAD_MAX_LEN;
        }
        else
        {
            tCmd.magicCode = OTA_RELAY_PACKET_MAGIC_CODE_INCOMPLETE;
            tCmd.length = OTA_TWS_PAYLOAD_MAX_LEN;
        }

        frameLen = OTA_TWS_HEAD_SIZE + tCmd.length;

        memcpy(tCmd.data, dataPtr + i * OTA_TWS_PAYLOAD_MAX_LEN, tCmd.length);

        osMutexWait(txQueueMutexID, osWaitForever);
        ASSERT(CQ_OK == EnCQueue(&otaEnv.txQueue, (CQItemType *)&frameLen, sizeof(frameLen)),
               "%s failed to push data to queue", __func__);
        ASSERT(CQ_OK == EnCQueue(&otaEnv.txQueue, (CQItemType *)&tCmd, frameLen),
               "%s failed to push data to queue", __func__);
        osMutexRelease(txQueueMutexID);
    }

    /// inform tx thread to handle the data to be transmitted
    osSignalSet(txThreadId, OTA_TWS_TX_SIGNAL);
}

/**
 * @brief Check the validity of received data frame.
 *
 * @param dataPtr       Pointer of received data
 * @param length        length of received data
 * @return true         Received data frame is valid
 * @return false        Received data frame is invalid
 */
static bool _tws_frame_validity_check(uint8_t *dataPtr, uint32_t length)
{
    bool isValid = true;

    OTA_TWS_DATA_T *otaTwsData = (OTA_TWS_DATA_T *)dataPtr;

    ASSERT(TWS_RELAY_DATA_MAX_SIZE >= length, "received overloaded data packet!!!");
    if (OTA_RELAY_PACKET_MAGIC_CODE_INCOMPLETE == otaEnv.currentMagicCode &&
        otaTwsData->cmdType != otaEnv.currentCmdType)
    {
        ASSERT(0, "bad frame is received!!!");
    }

    if (length != otaTwsData->length + OTA_TWS_HEAD_SIZE)
    {
        LOG_W("INVALID packet, dataLen:%d, expected dataLen:%d",
              length,
              otaTwsData->length + OTA_TWS_HEAD_SIZE);
        isValid = false;
    }

    if (OTA_RELAY_PACKET_MAGIC_CODE_COMPLETE != otaTwsData->magicCode &&
        OTA_RELAY_PACKET_MAGIC_CODE_INCOMPLETE != otaTwsData->magicCode)
    {
        LOG_W("INVALID magic code:0x%08x", otaTwsData->magicCode);
        isValid = false;
    }

    return isValid;
}

/**
 * @brief Response the master for the OTA_COMMAND received.
 *
 * This function will response the master after a whole packet receiving is
 * done.
 *
 * @param status        Status of receiving TWS relay data
 */
static void _tws_rsp(OTA_STATUS_E status)
{
    LOG_D("[%s] status:%s", __func__, _sts2str(status));

    OTA_STATUS_E rsp = status;
    _ota_relay_data(OTA_COMMAND_RSP, (const uint8_t *)&rsp, sizeof(rsp));
}
#endif

OTA_COMMON_ENV_T *ota_common_get_env(void)
{
    return &otaEnv;
}

/**
 * @brief Reset the OTA environment.
 *
 */
static void _reset_env(void)
{
    LOG_D("[%s]", __func__);
    memset(&otaEnv, 0, sizeof(otaEnv));

#ifdef __APP_USER_DATA_NV_FLASH_OFFSET__
    otaEnv.userDataNvFlashOffset = __APP_USER_DATA_NV_FLASH_OFFSET__;
#else
    otaEnv.userDataNvFlashOffset = hal_norflash_get_flash_total_size(HAL_NORFLASH_ID_0) - 2 * 4096;
#endif

    otaEnv.flashOffsetOfUserDataPool = otaEnv.userDataNvFlashOffset;
    _update_ota_user(OTA_USER_NUM);

#ifdef OTA_NVRAM
    // gOtaCtx.cfg.clearUserData = false;
    // gOtaCtx.flashOffsetOfFactoryDataPool =
    //     otaEnv.otaCommon->userDataNvFlashOffset + FLASH_SECTOR_SIZE_IN_BYTES;
#endif

#ifdef IBRT
    /// init tws TX queue
    osMutexWait(txQueueMutexID, osWaitForever);
    InitCQueue(&(otaEnv.txQueue), ARRAY_SIZE(otaEnv.txBuf), (CQItemType *)otaEnv.txBuf);
    osMutexRelease(txQueueMutexID);

    /// init tws RX queue
    osMutexWait(rxQueueMutexID, osWaitForever);
    InitCQueue(&(otaEnv.rxQueue), ARRAY_SIZE(otaEnv.rxBuf), (CQItemType *)otaEnv.rxBuf);
    osMutexRelease(rxQueueMutexID);
#endif
}

void ota_common_init_handler(void)
{
    LOG_D("[%s]", __func__);

    /// init OTA related nvrecord pointer
    nv_record_ota_init();

    /// init common used flash module
    ota_common_init_flash((uint8_t)NORFLASH_API_MODULE_ID_OTA,
                          0,
                          (OTA_FLASH_LOGIC_ADDR + (
#ifdef __APP_USER_DATA_NV_FLASH_OFFSET__
                                                      __APP_USER_DATA_NV_FLASH_OFFSET__
#else
                                                      (hal_norflash_get_flash_total_size(HAL_NORFLASH_ID_0) - 2 * 4096)
#endif
                                                      & 0xffffff)),
                          0);

#ifdef IBRT
    if (txThreadId == NULL)
    {
        txThreadId = osThreadCreate(osThread(_ota_tws_thread), NULL);
    }

    /// init tws TX queue mutex
    if (txQueueMutexID == NULL)
    {
        txQueueMutexID = osMutexCreate((osMutex(twsTxQueueMutex)));
    }

    /// init tws RX queue mutex
    if (rxQueueMutexID == NULL)
    {
        rxQueueMutexID = osMutexCreate((osMutex(twsRxQueueMutex)));
    }
#endif

    _reset_env();
}

void ota_common_enable_sanity_check(bool enable)
{
    LOG_I("sanity check enable state update:%d->%d",
          otaEnv.sanityCheckEnable, enable);
    otaEnv.sanityCheckEnable = enable;
}

void ota_common_init_flash(uint8_t module,
                           uint32_t baseAddr,
                           uint32_t len,
                           uint32_t imageHandler)
{
    enum NORFLASH_API_RET_T ret;
    uint32_t block_size = 0;
    uint32_t sector_size = 0;
    uint32_t page_size = 0;

    hal_norflash_get_size(HAL_NORFLASH_ID_0,
                          NULL,
                          &block_size,
                          &sector_size,
                          &page_size);

    LOG_I("%s module:%d, startAddr:0x%x, len:0x%x", __func__, module, baseAddr, len);
    ret = norflash_api_register((NORFLASH_API_MODULE_ID_T)module,
                                HAL_NORFLASH_ID_0,
                                baseAddr,
                                len,
                                block_size,
                                sector_size,
                                page_size,
                                OTA_NORFLASH_BUFFER_LEN,
                                (NORFLASH_API_OPERA_CB)imageHandler);

    ASSERT(ret == NORFLASH_API_OK,
           "ota_init_flash: norflash_api register failed,ret = %d.",
           ret);

#ifdef FLASH_SUSPEND
    norflash_suspend_check_irq(AUDMA_IRQn);
    norflash_suspend_check_irq(ISDATA_IRQn);
    norflash_suspend_check_irq(ISDATA1_IRQn);
#endif
}

bool ota_common_is_in_progress(void)
{
    bool ret = false;

    if (OTA_STAGE_IDLE != otaEnv.currentStage)
    {
        ret = true;
    }

    return ret;
}

void ota_common_registor_command_handler(OTA_COMMAND_E cmdType,
                                         void *cmdHandler)
{
    if (otaEnv.cmdHandler[cmdType])
    {
        LOG_W("handler for OTA command %d is not NULL", cmdType);
    }

    otaEnv.cmdHandler[cmdType] = (OTA_CMD_HANDLER_T)cmdHandler;
}

OTA_STATUS_E ota_common_command_received_handler(OTA_COMMAND_E cmdType,
                                                 void *cmdInfo,
                                                 uint16_t cmdLen)
{
    LOG_D("cmd received:%s", _cmd2str(cmdType));
    otaEnv.status = OTA_STATUS_OK;
    OTA_STATUS_E temp = OTA_STATUS_OK;

    /// init OTA progress when received begin command
    if (OTA_COMMAND_BEGIN == cmdType)
    {
        ASSERT(OTA_STAGE_IDLE == otaEnv.currentStage, "Received begin command while not in IDLE stage");
        OTA_BEGIN_PARAM_T *c = (OTA_BEGIN_PARAM_T *)cmdInfo;

        /// update the ota user to ota common layer
        _update_ota_user(c->user);

        /// update the new image size according to the image size param
        LOG_I("total image size update:%d->%d", otaEnv.totalImageSize, c->imageSize);
        otaEnv.totalImageSize = c->imageSize;

        /// update the new image offset according to the start offset param
        ASSERT(0 == (c->startOffset % FLASH_SECTOR_SIZE_IN_BYTES),
               "Resumed start offset is not 4KB aligned!");
        LOG_I("programOffset&receivedDataSize is update:%d->%d",
              otaEnv.newImageProgramOffset,
              c->startOffset);
        otaEnv.newImageProgramOffset = c->startOffset;
        otaEnv.receivedDataSize = c->startOffset;

        /// update the device index
        LOG_I("deviceId update:%d->%d", otaEnv.deviceId, c->device);
        otaEnv.deviceId = c->device;

        /// update the version info and version length
        memcpy(otaEnv.version, c->version, c->versionLen);
        otaEnv.versionLen = c->versionLen;

        /// update the flash offset of new image
        LOG_I("newImageFlashOffset update:0x%02x->0x%02x", otaEnv.newImageFlashOffset, c->flashOffset);
        otaEnv.newImageFlashOffset = c->flashOffset;

        /// set the OTA stage to ongoing
        _set_ota_stage(OTA_STAGE_ONGOING);

        /// system layer configurations to guarentee the performance
        /// of all functionalities
        _enter_ota_state(c->path);
    }

    /// execute the custom configuration
    if(OTA_COMMAND_NUM != cmdType)
    {
        if (otaEnv.cmdHandler[cmdType])
        {
            temp = otaEnv.cmdHandler[cmdType](cmdInfo, cmdLen);
            LOG_I("ota command %s handle done, status:%s", _cmd2str(cmdType), _sts2str(temp));

            if (OTA_STATUS_OK != temp)
            {
                otaEnv.status = temp;
            }
        }
        else
        {
            LOG_W("ota cmd %s is not handled by customor", _cmd2str(cmdType));
        }
    }
    else
    {
        LOG_E("INVALID cmd:%d", cmdType);
    }

    /// update the OTA progress
    if (OTA_STATUS_OK == otaEnv.status)
    {
        if (!nv_record_ota_update_info(otaEnv.currentUser,
                                        otaEnv.deviceId,
                                        otaEnv.currentStage,
                                        otaEnv.totalImageSize,
                                        otaEnv.version))
        {
            LOG_W("update info failed");
            otaEnv.status = OTA_STATUS_ERROR;
        }
    }

    /// special process for OTA_ABORT&&OTA_APPLY command
    if ((OTA_COMMAND_ABORT == cmdType) ||
        (OTA_COMMAND_APPLY == cmdType))
    {
        _reset_env();
    }

    LOG_I("%s cmd process result:%s", __func__, _sts2str(otaEnv.status));
    return otaEnv.status;
}

#ifdef OTA_NVRAM
static void ota_update_nv_data(void)
{
    if (otaEnv.configuration.isToClearUserData)
    {
        enum NORFLASH_API_MODULE_ID_T mod = _get_flash_module_from_ota_device(otaEnv.deviceId);
        app_flash_page_erase(mod, sectorIndexInFlash * FLASH_SECTOR_SIZE_IN_BYTES);
    }

    if (otaEnv.configuration.isToRenameBT ||
        otaEnv.configuration.isToRenameBLE ||
        otaEnv.configuration.isToUpdateBTAddr ||
        otaEnv.configuration.isToUpdateBLEAddr)
    {
        uint32_t *pOrgFactoryData, *pUpdatedFactoryData;
        pOrgFactoryData = (uint32_t *)(OTA_FLASH_LOGIC_ADDR + otaEnv.flashOffsetOfFactoryDataPool);
        memcpy(otaEnv.dataCacheBuffer, (uint8_t *)pOrgFactoryData, FLASH_SECTOR_SIZE_IN_BYTES);
        pUpdatedFactoryData = (uint32_t *)&(otaEnv.dataCacheBuffer);

        if (NVREC_DEV_VERSION_1 == nv_record_dev_rev)
        {
            if (otaEnv.configuration.isToRenameBT)
            {
                memset((uint8_t *)(pUpdatedFactoryData + dev_name),
                       0,
                       sizeof(uint32_t) * (dev_bt_addr - dev_name));

                memcpy((uint8_t *)(pUpdatedFactoryData + dev_name),
                       (uint8_t *)(otaEnv.configuration.newBTName),
                       NAME_LENGTH);
            }

            if (otaEnv.configuration.isToUpdateBTAddr)
            {
                memcpy((uint8_t *)(pUpdatedFactoryData + dev_bt_addr),
                       (uint8_t *)(otaEnv.configuration.newBTAddr),
                       BD_ADDR_LENGTH);
            }

            if (otaEnv.configuration.isToUpdateBLEAddr)
            {
                memcpy((uint8_t *)(pUpdatedFactoryData + dev_ble_addr),
                       (uint8_t *)(otaEnv.configuration.newBLEAddr),
                       BD_ADDR_LENGTH);
            }

            pUpdatedFactoryData[dev_crc] = crc32(0,
                                                 (uint8_t *)(&pUpdatedFactoryData[dev_reserv1]),
                                                 (dev_data_len - dev_reserv1) * sizeof(uint32_t));
        }
        else
        {
            if (otaEnv.configuration.isToRenameBT)
            {
                memset((uint8_t *)(pUpdatedFactoryData + rev2_dev_name),
                       0,
                       sizeof(uint32_t) * (rev2_dev_bt_addr - rev2_dev_name));

                memcpy((uint8_t *)(pUpdatedFactoryData + rev2_dev_name),
                       (uint8_t *)(otaEnv.configuration.newBTName),
                       NAME_LENGTH);
            }

            if (otaEnv.configuration.isToRenameBLE)
            {
                memset((uint8_t *)(pUpdatedFactoryData + rev2_dev_ble_name),
                       0,
                       sizeof(uint32_t) * (rev2_dev_section_end - rev2_dev_ble_name));

                memcpy((uint8_t *)(pUpdatedFactoryData + rev2_dev_ble_name),
                       (uint8_t *)(otaEnv.configuration.newBleName),
                       BLE_NAME_LEN_IN_NV);
            }

            if (otaEnv.configuration.isToUpdateBTAddr)
            {
                memcpy((uint8_t *)(pUpdatedFactoryData + rev2_dev_bt_addr),
                       (uint8_t *)(otaEnv.configuration.newBTAddr),
                       BD_ADDR_LENGTH);
            }

            if (otaEnv.configuration.isToUpdateBLEAddr)
            {
                memcpy((uint8_t *)(pUpdatedFactoryData + rev2_dev_ble_addr),
                       (uint8_t *)(otaEnv.configuration.newBLEAddr),
                       BD_ADDR_LENGTH);
            }

            pUpdatedFactoryData[dev_crc] = crc32(0,
                                                 (uint8_t *)(&pUpdatedFactoryData[rev2_dev_section_start_reserved]),
                                                 pUpdatedFactoryData[rev2_dev_data_len]);
        }

        enum NORFLASH_API_MODULE_ID_T mod = _get_flash_module_from_ota_device(otaEnv.deviceId);
        app_flash_page_erase(mod, sectorIndexInFlash * FLASH_SECTOR_SIZE_IN_BYTES);

        app_flash_page_program(mod,
                               otaEnv.flashOffsetOfFactoryDataPool,
                               (uint8_t *)pUpdatedFactoryData,
                               FLASH_SECTOR_SIZE_IN_BYTES);
    }
}
#endif

#ifdef IBRT
void ota_common_registor_relay_needed_handler(void *handler)
{
    if (otaEnv.customRelayNeededHandler)
    {
        LOG_W("handler for custom relay needed judge is not NULL");
    }

    otaEnv.customRelayNeededHandler = (CUSTOM_RELAY_NEEDED_FUNC_T)handler;
}

void ota_common_registor_peer_cmd_received_handler(void *handler)
{
    if (otaEnv.peerCmdReceivedHandler)
    {
        LOG_W("handler for peer cmd received is not NULL");
    }

    otaEnv.peerCmdReceivedHandler = (PEER_CMD_RECEIVED_HANDLER_T)handler;
}

OTA_STATUS_E ota_common_relay_data_to_peer(OTA_COMMAND_E cmdType, const uint8_t *data, uint16_t len)
{
    OTA_STATUS_E status = OTA_STATUS_OK;
    if (_relay_data_needed())
    {
        _ota_relay_data(cmdType, (const uint8_t *)data, len);
    }
    else
    {
        status = OTA_STATUS_ERROR;
    }

    return status;
}

OTA_STATUS_E ota_common_receive_peer_rsp(void)
{
    OTA_STATUS_E status = OTA_STATUS_OK;

    if (_relay_data_needed())
    {
        /// get current thread ID as tws rx thread
        rxThreadId = osThreadGetId();

        /// pending current thread to waitting for slave response
        evt = osSignalWait(OTA_TWS_RX_SIGNAL, OTA_TWS_RELAY_WAITTIME);

        if (evt.status == osEventTimeout)
        {
            status = OTA_STATUS_ERROR_RELAY_TIMEOUT;
            LOG_W("[%s]SignalWait TIMEOUT!", __func__);
        }
        else if (osEventSignal == evt.status)
        {
            status = ota_common_get_peer_result();
        }

        ///.clear the excute result of this time
        _update_peer_result(OTA_STATUS_OK);
    }
    else
    {
        status = OTA_STATUS_ERROR;
    }

    return status;
}

OTA_STATUS_E ota_common_fw_data_write(const uint8_t *data, uint16_t len)
{
    OTA_STATUS_E status = OTA_STATUS_OK;
    uint16_t leftDataSize = len;
    uint32_t offsetInReceivedRawData = 0;

    do
    {
        uint32_t bytesToCopy;
        // copy to data buffer
        if ((otaEnv.dataCacheBufferOffset + leftDataSize) >
            OTA_DATA_CACHE_BUFFER_SIZE)
        {
            bytesToCopy = OTA_DATA_CACHE_BUFFER_SIZE -
                          otaEnv.dataCacheBufferOffset;
        }
        else
        {
            bytesToCopy = leftDataSize;
        }

        leftDataSize -= bytesToCopy;

        memcpy(&otaEnv.dataCacheBuffer[otaEnv.dataCacheBufferOffset],
               &data[offsetInReceivedRawData],
               bytesToCopy);
        offsetInReceivedRawData += bytesToCopy;
        otaEnv.dataCacheBufferOffset += bytesToCopy;

        ASSERT(otaEnv.dataCacheBufferOffset <= OTA_DATA_CACHE_BUFFER_SIZE,
               "bad math in %s",
               __func__);
        if (OTA_DATA_CACHE_BUFFER_SIZE == otaEnv.dataCacheBufferOffset)
        {
            _flush_data_to_flash(otaEnv.dataCacheBuffer,
                                 OTA_DATA_CACHE_BUFFER_SIZE,
                                 (otaEnv.newImageProgramOffset +
                                  otaEnv.newImageFlashOffset),
                                 false);
            otaEnv.newImageProgramOffset += OTA_DATA_CACHE_BUFFER_SIZE;
            otaEnv.dataCacheBufferOffset = 0;
        }
    } while (offsetInReceivedRawData < len);

    otaEnv.receivedDataSize += len;

    // check whether all image data has been received
    if (otaEnv.receivedDataSize == otaEnv.totalImageSize)
    {
        LOG_D("The final image programming and crc32 check.");

        // flush any partial buffer to flash
        if (otaEnv.dataCacheBufferOffset != 0)
        {
            _flush_data_to_flash(otaEnv.dataCacheBuffer,
                                 otaEnv.dataCacheBufferOffset,
                                 (otaEnv.newImageProgramOffset +
                                  otaEnv.newImageFlashOffset),
                                 true);
        }

        if (OTA_DEVICE_APP == otaEnv.deviceId)
        {
            bool check = true;

            /// check the sanity if required
            if (otaEnv.sanityCheckEnable)
            {
                check = _image_sanity_check();
            }

            if (check)
            {
                /// update the magic code of the application image
                _update_magic_number(NORMAL_BOOT);

                /// check the crc32 of the received image data
                if (_compute_whole_image_crc())
                {
                    LOG_I("Whole image verification pass.");

                    /// update the OTA stage to OTA done
                    _set_ota_stage(OTA_STAGE_DONE);
                }
                else
                {
                    LOG_W("image verification failed @%d", __LINE__);

                    /// update the OTA stage to OTA idle
                    _set_ota_stage(OTA_STAGE_IDLE);
                }
            }
            else
            {
                /// sanity check failed
                LOG_W("image verification failed @%d", __LINE__);

                /// update the OTA stage to OTA idle
                _set_ota_stage(OTA_STAGE_IDLE);
            }
        }
        else
        {
            LOG_I("download finished, device:%d", otaEnv.deviceId);

            /// update the OTA stage to OTA idle
            _set_ota_stage(OTA_STAGE_DONE);
        }

        /// whole image verification failed somehow
        if (OTA_STAGE_IDLE == otaEnv.currentStage)
        {
            nv_record_ota_update_info(otaEnv.currentUser,
                                      otaEnv.deviceId,
                                      otaEnv.currentStage,
                                      0,
                                      INVALID_VERSION_STR);

            status = OTA_STATUS_ERROR_CHECKSUM;
        }
        else //!< whole image verification passed
        {
            nv_record_ota_update_info(otaEnv.currentUser,
                                      otaEnv.deviceId,
                                      otaEnv.currentStage,
                                      otaEnv.totalImageSize,
                                      otaEnv.version);
        }

        /// exit the OTA state
        _exit_ota_state();
    }
    else //!< whole image revceive not finished
    {
        LOG_D("Received image size:%d", otaEnv.receivedDataSize);

        /// update the break point if it is changed
        if ((otaEnv.receivedDataSize - otaEnv.breakPoint) >=
            OTA_BREAKPOINT_STORE_GRANULARITY)
        {
            otaEnv.breakPoint = (otaEnv.receivedDataSize /
                                 OTA_BREAKPOINT_STORE_GRANULARITY) *
                                OTA_BREAKPOINT_STORE_GRANULARITY;

            LOG_I("update record offset to %d", otaEnv.breakPoint);
            nv_record_ota_update_breakpoint(otaEnv.currentUser,
                                            otaEnv.deviceId,
                                            otaEnv.breakPoint);
        }
    }

    return status;
}

void ota_common_apply_current_fw(void)
{
    OTA_BOOT_INFO_T otaBootInfo = {COPY_NEW_IMAGE,
                                   otaEnv.totalImageSize,
                                   otaEnv.crc32OfImage};
    _update_boot_info(&otaBootInfo);

#ifdef OTA_NVRAM
    ota_update_nv_data();
#endif

    app_start_postponed_reset();
}

void ota_common_on_relay_data_received(uint8_t *ptrParam, uint32_t paramLen)
{
    ASSERT(ptrParam, "invalid data pointer received in %s", __func__);
    ASSERT(paramLen <= TWS_RELAY_DATA_MAX_SIZE, "illegal parameter length received in %s", __func__);

    OTA_TWS_DATA_T *otaTwsData = (OTA_TWS_DATA_T *)ptrParam;
    LOG_D("[%s] paramLen:%d, cmdType:%d|%s", __func__, paramLen, otaTwsData->cmdType, _cmd2str(otaTwsData->cmdType));

    OTA_STATUS_E status = OTA_STATUS_OK;
    OTA_BEGIN_PARAM_T *beginInfo = NULL;
    uint16_t packetLen = 0;

    if (_tws_frame_validity_check(ptrParam, paramLen))
    {
        /// update the magic code and command type
        otaEnv.currentMagicCode = otaTwsData->magicCode;
        otaEnv.currentCmdType = otaTwsData->cmdType;

        /// push received data into tws OTA data receive queue
        osMutexWait(rxQueueMutexID, osWaitForever);
        ASSERT(CQ_OK == EnCQueue(&otaEnv.rxQueue, (CQItemType *)otaTwsData->data, (paramLen - OTA_TWS_HEAD_SIZE)),
               "%s failed to push data to queue, avaiable:%d, push:%d",
               __func__,
               AvailableOfCQueue(&otaEnv.rxQueue),
               (paramLen - OTA_TWS_HEAD_SIZE));
        osMutexRelease(rxQueueMutexID);

        /// whole packet from APP received done
        if (OTA_RELAY_PACKET_MAGIC_CODE_COMPLETE == otaEnv.currentMagicCode)
        {
            osMutexWait(rxQueueMutexID, osWaitForever);
            packetLen = LengthOfCQueue(&otaEnv.rxQueue);
            LOG_D("length of rx queue:%d", packetLen);
            DeCQueue(&otaEnv.rxQueue, otaEnv.tempRxBuf, packetLen);
            osMutexRelease(rxQueueMutexID);

            switch (otaEnv.currentCmdType)
            {
            case OTA_COMMAND_RSP:
                /// retrieve and update the status of peer
                status = ((OTA_RESPONSE_PARAM_T *)otaEnv.tempRxBuf)->status;
                _update_peer_result(status);

                /// inform the receiving thread to proceed
                osSignalSet(rxThreadId, OTA_TWS_RX_SIGNAL);
                break;

            /// add other supported command here
            case OTA_COMMAND_BEGIN:
                beginInfo = (OTA_BEGIN_PARAM_T *)otaTwsData->data;
                if (beginInfo->initializer)
                {
                    beginInfo->initializer();
                }
                else
                {
                    LOG_E("initializer not registored");
                }
            case OTA_COMMAND_DATA:
            case OTA_COMMAND_APPLY:
            case OTA_COMMAND_ABORT:
                if (otaEnv.peerCmdReceivedHandler)
                {
                    status = otaEnv.peerCmdReceivedHandler(otaEnv.currentCmdType,
                                                           (const uint8_t*)otaEnv.tempRxBuf,
                                                           packetLen);
                }
                else
                {
                    LOG_W("peerCmdReceivedHandler is not registored");
                }

                break;

            default:
                ASSERT(0, "INVALID cmd received");
                status = OTA_STATUS_ERROR;
                break;
            }

            /// response to master if needed
            if (OTA_COMMAND_RSP != otaTwsData->cmdType)
            {
                _tws_rsp(status);
            }

            otaEnv.currentMagicCode = OTA_RELAY_PACKET_MAGIC_CODE_INVALID;
            otaEnv.currentCmdType = OTA_COMMAND_NUM;
        }
    }
    else
    {
        LOG_W("Received data frame is invalid");
        status = OTA_STATUS_ERROR;

        switch (otaTwsData->cmdType)
        {
        case OTA_COMMAND_RSP:
            _update_peer_result(status);
            osSignalSet(rxThreadId, OTA_TWS_RX_SIGNAL);
            break;

        case OTA_COMMAND_BEGIN:
        case OTA_COMMAND_DATA:
        case OTA_COMMAND_APPLY:
        case OTA_COMMAND_ABORT:
            _tws_rsp(status);
            break;

        default:
            ASSERT(0, "INVALID command received");
            break;
        }
    }
}

POSSIBLY_UNUSED static void _ota_tws_deinit(void)
{
    ResetCQueue(&otaEnv.txQueue);
    ResetCQueue(&otaEnv.rxQueue);
    memset(otaEnv.txBuf, 0, ARRAY_SIZE(otaEnv.txBuf));
    memset(otaEnv.rxBuf, 0, ARRAY_SIZE(otaEnv.rxBuf));
    memset(otaEnv.tempRxBuf, 0, ARRAY_SIZE(otaEnv.tempRxBuf));

    if (txThreadId)
    {
        osThreadTerminate(txThreadId);
        txThreadId = NULL;
    }

    if (txQueueMutexID != NULL)
    {
        osMutexDelete(txQueueMutexID);
        txQueueMutexID = NULL;
    }

    if (rxQueueMutexID != NULL)
    {
        osMutexDelete(rxQueueMutexID);
        rxQueueMutexID = NULL;
    }
}

static void _ota_tws_thread(const void *arg)
{
    volatile uint16_t qLen = 0;
    volatile uint16_t packetLen = 0;

    while (1)
    {
        osMutexWait(txQueueMutexID, osWaitForever);
        qLen = LengthOfCQueue(&otaEnv.txQueue);
        osMutexRelease(txQueueMutexID);
        LOG_D("queued data len:%d", qLen);

        while (qLen)
        {
            /// check the validity of queue length
            /// ASSERT(qLen <= OTA_MAX_MTU, "invalid OTA relay data len");

            osMutexWait(txQueueMutexID, osWaitForever);
            /// retrive and check the validity of packet length
            DeCQueue(&otaEnv.txQueue, (CQItemType *)&packetLen, 2);
            ASSERT(packetLen <= TWS_RELAY_DATA_MAX_SIZE, "invalid OTA relay data len:%d", packetLen);

            /// retrieve the data to transmit
            DeCQueue(&otaEnv.txQueue, (CQItemType *)relayBuf, packetLen);
            osMutexRelease(txQueueMutexID);

            LOG_D("send data len:%d", packetLen);

            /// send data to peer
            tws_ctrl_send_cmd(IBRT_COMMON_OTA, relayBuf, packetLen);
            memset(relayBuf, 0, TWS_RELAY_DATA_MAX_SIZE);

            osMutexWait(txQueueMutexID, osWaitForever);
            qLen = LengthOfCQueue(&otaEnv.txQueue);
            osMutexRelease(txQueueMutexID);
        }

        osSignalWait(OTA_TWS_TX_SIGNAL, osWaitForever);
    }
}

static void _sync_info_prepare_handler(uint8_t *buf, uint16_t *length)
{
    *length = OTA_DEVICE_CNT * sizeof(NV_OTA_INFO_T);

    void *otaInfo = NULL;
    nv_record_ota_get_ptr(&otaInfo);

    memcpy(buf, otaInfo, *length);
}

static void _sync_info_received_handler(uint8_t *buf, uint16_t length)
{
    // uodate gsound info
    // TODO:
}

void ota_common_tws_sync_init(void)
{
    TWS_SYNC_USER_T userOta = {
        _sync_info_prepare_handler,
        _sync_info_received_handler,
        _sync_info_prepare_handler,
        NULL,
        NULL,
    };

    app_tws_if_register_sync_user(TWS_SYNC_USER_OTA, &userOta);
}
#endif
