//*****************************************************************************
//
//! amotas_main.c
//! @file
//!
//! @brief This file provides the main application for the AMOTA service.
//!
//
//*****************************************************************************

//*****************************************************************************
//
// Copyright (c) 2024, Ambiq Micro, Inc.
// 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 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 the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
//
// Third party software included in this distribution is subject to the
// additional license terms as defined in the /docs/licenses directory.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 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.
//
// This is part of revision ambiqvos_r4.5-fdfa8cf6a4 of the AmbiqSuite Development Package.
//
//*****************************************************************************
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include "wsf_types.h"
#include "wsf_assert.h"
#include "wsf_trace.h"
#include "bstream.h"
#include "att_api.h"
#include "svc_ch.h"
#include "svc_amotas.h"
#include "app_api.h"
#include "app_hw.h"
#include "amotas_api.h"
#include "am_util_debug.h"
#include "crc32.h"

#include "am_mcu_apollo.h"
#include "am_bsp.h"
#include "am_util.h"
#include "am_util_bootloader.h"

#include "amota_profile_config.h"
#include "am_util_multi_boot.h"

#if defined(AM_PART_APOLLO4B) || defined(AM_PART_APOLLO4P) || defined(AM_PART_APOLLO4L)
#include "am_hal_security.h"
#endif

#undef  APP_TRACE_INFO0
#undef  APP_TRACE_INFO1
#undef  APP_TRACE_INFO2
#undef  APP_TRACE_INFO3

#define APP_TRACE_INFO0(msg)
#define APP_TRACE_INFO1(msg, var1)
#define APP_TRACE_INFO2(msg, var1, var2)
#define APP_TRACE_INFO3(msg, var1, var2, var3)

static am_util_multiboot_flash_info_t *g_pFlash = &g_intFlash;

#if defined(AM_PART_APOLLO3) || defined(AM_PART_APOLLO3P)
#if (AMOTAS_SUPPORT_EXT_FLASH == 1)
// OTA using external flash requires secondary bootloader
// Customer specific Magic#'s can be used to communicate information from
// AMOTA to the secondary bootloader, in a vendor specific way
#error "External flash based OTA not supported in Apollo3"
#endif
#endif

// Need temporary buf equal to one flash page size (larger of int or ext, if ext flash is supported)
// We accumulate data in this buffer and perform Writes only on page boundaries in
// multiple of page lentghs
#if (AMOTAS_SUPPORT_EXT_FLASH == 1) && (AM_DEVICES_SPIFLASH_PAGE_SIZE > AM_HAL_FLASH_PAGE_SIZE)
#define AMOTAS_TEMP_BUFSIZE     AM_DEVICES_SPIFLASH_PAGE_SIZE
#else
#define AMOTAS_TEMP_BUFSIZE     AM_HAL_FLASH_PAGE_SIZE
#endif


// Protection against NULL pointer
#define FLASH_OPERATE(pFlash, func) ((pFlash)->func ? (pFlash)->func() : 0)


//
// Data structure for flash operation
//
typedef struct
{
    uint8_t     writeBuffer[AMOTAS_TEMP_BUFSIZE]   __attribute__((aligned(4)));   // needs to be 32-bit word aligned.
    uint16_t    bufferIndex;
}amotasFlashOp_t;

amotasFlashOp_t amotasFlash = {
    .bufferIndex = 0,
};

// Temporary scratch buffer used to read from flash
uint32_t amotasTmpBuf[AMOTA_PACKET_SIZE / 4];
#if defined(AM_PART_APOLLO4B) || defined(AM_PART_APOLLO4P) || defined(AM_PART_APOLLO4L)
static uint32_t sblOtaStorageAddr = AMOTA_INVALID_SBL_STOR_ADDR;
#endif
static uint32_t ui32ImageCalCRC = 0;

//*****************************************************************************
//
// Macro definitions
//
//*****************************************************************************

//
// amota states
//
typedef enum
{
    AMOTA_STATE_INIT,
    AMOTA_STATE_GETTING_FW,
    AMOTA_STATE_MAX
}eAmotaState;

//
// amota commands
//
typedef enum
{
    AMOTA_CMD_UNKNOWN,
    AMOTA_CMD_FW_HEADER,
    AMOTA_CMD_FW_DATA,
    AMOTA_CMD_FW_VERIFY,
    AMOTA_CMD_FW_RESET,
    AMOTA_CMD_MAX
}eAmotaCommand;

//
// amota status
//
typedef enum
{
    AMOTA_STATUS_SUCCESS,
    AMOTA_STATUS_CRC_ERROR,
    AMOTA_STATUS_INVALID_HEADER_INFO,
    AMOTA_STATUS_INVALID_PKT_LENGTH,
    AMOTA_STATUS_INSUFFICIENT_BUFFER,
    AMOTA_STATUS_INSUFFICIENT_FLASH,
    AMOTA_STATUS_UNKNOWN_ERROR,
    AMOTA_STATUS_FLASH_WRITE_ERROR,
    AMOTA_STATUS_MAX
}eAmotaStatus;


//
// FW header information
//
typedef struct
{
    uint32_t    encrypted;
    uint32_t    fwStartAddr;            // Address to install the image
    uint32_t    fwLength;
    uint32_t    fwCrc;
    uint32_t    secInfoLen;
    uint32_t    resvd1;
    uint32_t    resvd2;
    uint32_t    resvd3;
    uint32_t    version;
    uint32_t    fwDataType;             //binary type
    uint32_t    storageType;
#if defined(AM_PART_APOLLO4B) || defined(AM_PART_APOLLO4P) || defined(AM_PART_APOLLO4L)
    uint32_t    imageId;                //use to identify the ota image is for application, SBL or others
#else
    uint32_t    resvd4;
#endif
}
amotaHeaderInfo_t;

//
// 32-byte Metadata added ahead of the actual image and behind of 48-byte AMOTA header
//
#if defined(AM_PART_APOLLO3) || defined(AM_PART_APOLLO3P)
typedef struct
{
    //Word 0
    uint32_t blobSize:24;
    uint32_t magicNum:8;

    //Word 1-7
    uint32_t resvd[7];
}
amotaMetadataInfo_t;
#elif defined(AM_PART_APOLLO4B) || defined(AM_PART_APOLLO4P) || defined(AM_PART_APOLLO4L)
typedef struct
{
    //Word 0
    uint32_t blobSize:24;
    uint32_t resvd1:2;
    uint32_t crcCheck:1;
    uint32_t enc:1;
    uint32_t authCheck:1;
    uint32_t ccIncluded:1;
    uint32_t ambiq:1;
    uint32_t resvd2:1;

    //Word 1
    uint32_t crc;

    //Word 2
    uint32_t authKeyIdx:8;
    uint32_t encKeyIdx:8;
    uint32_t authAlgo:4;
    uint32_t encAlgo:4;
    uint32_t resvd3:8;

    //Word 3
    uint32_t resvd4;

    //Word 4
    uint32_t magicNum:8;
    uint32_t resvd5:24;

    //Word5-7
    uint32_t resvd6;
    uint32_t resvd7;
    uint32_t resvd8;
}
amotaMetadataInfo_t;
#endif

//
// FW packet information
//
typedef struct
{
    uint16_t    offset;
    uint16_t    len;                        // data plus checksum
    eAmotaCommand type;
    uint8_t     data[AMOTA_PACKET_SIZE]  __attribute__((aligned(4)));   // needs to be 32-bit word aligned.
}
amotaPacket_t;

//
// Connection control block
//
typedef struct
{
    dmConnId_t    connId;               // Connection ID
    bool_t        amotaToSend;          // AMOTA notify ready to be sent on this channel
}
amotasConn_t;

//
// Firmware Address
//
typedef struct
{
    uint32_t    addr;
    uint32_t    offset;
}
amotasNewFwFlashInfo_t;


/* Control block */
static struct
{
    amotasConn_t            conn[DM_CONN_MAX];    // connection control block
    bool_t                  txReady;              // TRUE if ready to send notifications
    wsfHandlerId_t          appHandlerId;
    AmotasCfg_t             cfg;                  // configurable parameters
    eAmotaState             state;
    amotaHeaderInfo_t       fwHeader;
    amotaPacket_t           pkt;
    amotasNewFwFlashInfo_t  newFwFlashInfo;
    wsfTimer_t              resetTimer;           // reset timer after OTA update done
    wsfTimer_t              disconnectTimer;      // Disconnect timer after OTA update done
#if defined(AM_PART_APOLLO3) || defined(AM_PART_APOLLO3P) || defined(AM_PART_APOLLO4B) || defined(AM_PART_APOLLO4P) || defined(AM_PART_APOLLO4L)
    amotaMetadataInfo_t     metaData;
#endif
}
amotasCb;


// Erases the flash based on ui32Addr & ui32NumBytes
void
erase_flash(uint32_t ui32Addr, uint32_t ui32NumBytes)
{
    // Erase the image
    while ( ui32NumBytes )
    {
        g_pFlash->flash_erase_sector(ui32Addr);
        if ( ui32NumBytes > g_pFlash->flashSectorSize )
        {
            ui32NumBytes -= g_pFlash->flashSectorSize;
            ui32Addr += g_pFlash->flashSectorSize;
        }
        else
        {
            break;
        }
    }
}

//*****************************************************************************
//
// Connection Open event
//
//*****************************************************************************
static void
amotas_conn_open(dmEvt_t *pMsg)
{
    hciLeConnCmplEvt_t *evt = (hciLeConnCmplEvt_t*) pMsg;
    amotasCb.txReady = TRUE;

    (void)evt;

    APP_TRACE_INFO0("connection opened");
    APP_TRACE_INFO1("handle = 0x%x", evt->handle);
    APP_TRACE_INFO1("role = 0x%x", evt->role);
    APP_TRACE_INFO3("addrMSB = %02x%02x%02x%02x%02x%02x", evt->peerAddr[0], evt->peerAddr[1], evt->peerAddr[2]);
    APP_TRACE_INFO3("addrLSB = %02x%02x%02x%02x%02x%02x", evt->peerAddr[3], evt->peerAddr[4], evt->peerAddr[5]);
    APP_TRACE_INFO1("connInterval = 0x%x", evt->connInterval);
    APP_TRACE_INFO1("connLatency = 0x%x", evt->connLatency);
    APP_TRACE_INFO1("supTimeout = 0x%x", evt->supTimeout);
}

//*****************************************************************************
//
// Connection Update event
//
//*****************************************************************************
static void
amotas_conn_update(dmEvt_t *pMsg)
{
    hciLeConnUpdateCmplEvt_t *evt = (hciLeConnUpdateCmplEvt_t*) pMsg;

    (void)evt;

    APP_TRACE_INFO1("connection update status = 0x%x", evt->status);

    if (evt->status == 0)
    {
        APP_TRACE_INFO1("handle = 0x%x", evt->handle);
        APP_TRACE_INFO1("connInterval = 0x%x", evt->connInterval);
        APP_TRACE_INFO1("connLatency = 0x%x", evt->connLatency);
        APP_TRACE_INFO1("supTimeout = 0x%x", evt->supTimeout);
    }
}

//*****************************************************************************
//
// Find Next Connection to Send on
//
//*****************************************************************************
static amotasConn_t*
amotas_find_next2send(void)
{
    amotasConn_t *pConn = amotasCb.conn;
    uint8_t i;

    for (i = 0; i < DM_CONN_MAX; i++, pConn++)
    {
        if (pConn->connId != DM_CONN_ID_NONE && pConn->amotaToSend)
        {
            //if (AttsCccEnabled(pConn->connId, cccIdx))
            return pConn;
        }
    }
    return NULL;
}


//*****************************************************************************
//
// Timer Expiration handler
//
//*****************************************************************************

void
amotas_disconnect_timer_expired(wsfMsgHdr_t *pMsg)
{
    amotasConn_t *pConn = amotas_find_next2send();
    if ( pConn )
    {
        AppConnClose(pConn->connId);
    }

    APP_TRACE_INFO0("disconnect BLE connection");

    //
    // Delay here to let disconnect req go through
    // the RF before we reboot.
    //
    WsfTimerStartMs(&amotasCb.resetTimer, 200);
}

void
amotas_reset_timer_expired(wsfMsgHdr_t *pMsg)
{
    APP_TRACE_INFO0("amotas_reset_board");
    am_util_delay_ms(10);
#if (defined(AM_PART_APOLLO3) || defined(AM_PART_APOLLO3P) || defined(AM_PART_APOLLO4) || defined(AM_PART_APOLLO4B) || defined(AM_PART_APOLLO4L) || defined(AM_PART_APOLLO4P))
    am_hal_reset_control(AM_HAL_RESET_CONTROL_SWPOR, 0);
#else
    am_hal_reset_poi();
#endif
}

//*****************************************************************************
//
// Send Notification to Client
//
//*****************************************************************************
static void
amotas_send_data(uint8_t *buf, uint16_t len)
{
    amotasConn_t *pConn = amotas_find_next2send();
    /* send notification */
    if ( pConn )
    {
        APP_TRACE_INFO1("Send to connId = %d", pConn->connId);
        AttsHandleValueNtf(pConn->connId, AMOTAS_TX_HDL, len, buf);
    }
    else
    {
        APP_TRACE_INFO0("Invalid Conn");
    }
}

//*****************************************************************************
//
// Send Reply to Client
//
//*****************************************************************************
static void
amotas_reply_to_client(eAmotaCommand cmd, eAmotaStatus status,
                       uint8_t *data, uint16_t len)
{
    uint8_t buf[ATT_DEFAULT_PAYLOAD_LEN] = {0};
    buf[0] = (len + 1) & 0xff;
    buf[1] = ((len + 1) >> 8) & 0xff;
    buf[2] = cmd;
    buf[3] = status;
    if ( len > 0 )
    {
        memcpy(buf + 4, data, len);
    }
    amotas_send_data(buf, len + 4);
}

//*****************************************************************************
//
// Set Firmware Address
//
//return true if success, otherwise false
//*****************************************************************************
static bool
amotas_set_fw_addr(void)
{
    bool bResult = false;

    amotasCb.newFwFlashInfo.addr = 0;
    amotasCb.newFwFlashInfo.offset = 0;

    //
    // Check storage type
    //
    if ( amotasCb.fwHeader.storageType == AMOTA_FW_STORAGE_INTERNAL )
    {
        // storage in internal flash
        uint32_t storeAddr = (AMOTA_INT_FLASH_OTA_ADDRESS + AM_HAL_FLASH_PAGE_SIZE - 1) & ~(AM_HAL_FLASH_PAGE_SIZE - 1);
        uint32_t maxSize = AMOTA_INT_FLASH_OTA_MAX_SIZE & ~(AM_HAL_FLASH_PAGE_SIZE - 1);

#if !defined(AM_PART_APOLLO3) && !defined(AM_PART_APOLLO3P) && !defined(AM_PART_APOLLO4B) && !defined(AM_PART_APOLLO4P) && !defined(AM_PART_APOLLO4L)// There is no easy way to get the information about the main image in Apollo3
        uint32_t ui32CurLinkAddr;
        uint32_t ui32CurLen;
        // Get information about current main image
        if (am_util_multiboot_get_main_image_info(&ui32CurLinkAddr, &ui32CurLen) == false)
        {
            return false;
        }
        // Length in flash page size multiples
        ui32CurLen = (ui32CurLen + (AM_HAL_FLASH_PAGE_SIZE - 1)) & ~(AM_HAL_FLASH_PAGE_SIZE - 1) ;
        // Make sure the OTA area does not overwrite the main image
        if (!((storeAddr + maxSize) <= ui32CurLinkAddr) &&
            !(storeAddr >= (ui32CurLinkAddr + ui32CurLen)))
        {
            APP_TRACE_INFO0("OTA memory overlaps with main firmware");
            return false;
        }
#endif
        // Check to make sure the incoming image will fit in the space allocated for OTA
        if (amotasCb.fwHeader.fwLength > maxSize)
        {
            APP_TRACE_INFO2("not enough OTA space allocated = %d bytes, Desired = %d bytes",
                maxSize, amotasCb.fwHeader.fwLength);
            return false;
        }

        g_pFlash = &g_intFlash;
        amotasCb.newFwFlashInfo.addr = storeAddr;
        bResult = true;
    }
    else if ( amotasCb.fwHeader.storageType == AMOTA_FW_STORAGE_EXTERNAL )
    {
        //storage in external flash

#if (AMOTAS_SUPPORT_EXT_FLASH == 1)
        //
        // update target address information
        //
        g_pFlash = &g_extFlash;

        if (g_pFlash->flash_read_page &&
            g_pFlash->flash_write_page &&
            g_pFlash->flash_erase_sector &&
            (amotasCb.fwHeader.fwLength <= AMOTA_EXT_FLASH_OTA_MAX_SIZE))
        {
            amotasCb.newFwFlashInfo.addr = AMOTA_EXT_FLASH_OTA_ADDRESS;
            bResult = true;
        }
        else
#endif
        {
            bResult = false;
        }
    }
    else
    {
        // reserved state
        bResult = false;
    }
    if (bResult == true)
    {
        //
        // Initialize the flash device.
        //
        if (FLASH_OPERATE(g_pFlash, flash_init) == 0)
        {
            if (FLASH_OPERATE(g_pFlash, flash_enable) != 0)
            {
                FLASH_OPERATE(g_pFlash, flash_deinit);
                bResult = false;
            }
            //
            // Erase necessary sectors in the flash according to length of the image.
            //
            erase_flash(amotasCb.newFwFlashInfo.addr, amotasCb.fwHeader.fwLength);

            FLASH_OPERATE(g_pFlash, flash_disable);
        }
        else
        {
            bResult = false;
        }
    }
    return bResult;
}

static int
verify_flash_content(uint32_t flashAddr, uint32_t *pSram, uint32_t len, am_util_multiboot_flash_info_t *pFlash)
{
    // read back and check
    uint32_t  offset = 0;
    uint32_t  remaining = len;
    int       ret = 0;
    while (remaining)
    {
        uint32_t tmpSize =
            (remaining > AMOTA_PACKET_SIZE) ? AMOTA_PACKET_SIZE : remaining;
        pFlash->flash_read_page((uint32_t)amotasTmpBuf, (uint32_t *)(flashAddr + offset), tmpSize);

        ret = memcmp(amotasTmpBuf, (uint8_t*)((uint32_t)pSram + offset), tmpSize);

        if ( ret != 0 )
        {
            // there is write failure happened.
            APP_TRACE_INFO2("flash write verify failed. address 0x%x. length %d", flashAddr, len);
            break;
        }
        offset += tmpSize;
        remaining -= tmpSize;
    }
    return ret;
}

//*****************************************************************************
//
// Write to Flash
//
//return true if success, otherwise false
//*****************************************************************************
#if 1
static bool amotas_write2flash(uint16_t len, uint8_t *buf, uint32_t addr, bool lastPktFlag)
{
    uint16_t ui16BytesRemaining = len;
    uint32_t ui32TargetAddress = 0;
    uint8_t ui8PageCount = 0;
    bool bResult = true;
    uint16_t i;

    addr -= amotasFlash.bufferIndex;
    //
    // Check the target flash address to ensure we do not operation the wrong address
    // make sure to write to page boundary
    //
#if defined(AM_PART_APOLLO4B) || defined(AM_PART_APOLLO4P) || defined(AM_PART_APOLLO4L)
    if (((!((amotasCb.fwHeader.imageId == AMOTA_IMAGE_ID_SBL) &&
        (amotasCb.newFwFlashInfo.offset < AMOTA_ENCRYPTED_SBL_SIZE))) &&
        ((uint32_t)amotasCb.newFwFlashInfo.addr > addr)) ||
        (addr & (g_pFlash->flashPageSize - 1)))
    {
        //
        // application is trying to write to wrong address
        //
        return false;
    }
#else
    if (((uint32_t)amotasCb.newFwFlashInfo.addr > addr) ||
        (addr & (g_pFlash->flashPageSize - 1)))
    {
        //
        // application is trying to write to wrong address
        //
        return false;
    }
#endif

    FLASH_OPERATE(g_pFlash, flash_enable);
    while (ui16BytesRemaining)
    {
        uint16_t ui16Bytes2write = g_pFlash->flashPageSize - amotasFlash.bufferIndex;
        if (ui16Bytes2write > ui16BytesRemaining)
        {
            ui16Bytes2write = ui16BytesRemaining;
        }
        // move data into buffer
        for ( i = 0; i < ui16Bytes2write; i++ )
        {
            // avoid using memcpy
            amotasFlash.writeBuffer[amotasFlash.bufferIndex++] = buf[i];
        }
        ui16BytesRemaining -= ui16Bytes2write;
        buf += ui16Bytes2write;

        //
        // Write to flash when there is data more than 1 page size
        // For last fragment write even if it is less than one page
        //
        if (lastPktFlag || (amotasFlash.bufferIndex == g_pFlash->flashPageSize))
        {
            ui32TargetAddress = (addr + ui8PageCount*g_pFlash->flashPageSize);
            // Always write whole pages
            if ((g_pFlash->flash_write_page(ui32TargetAddress, (uint32_t *)amotasFlash.writeBuffer, g_pFlash->flashPageSize) != 0)
                || (verify_flash_content(ui32TargetAddress, (uint32_t *)amotasFlash.writeBuffer, amotasFlash.bufferIndex, g_pFlash) != 0))
            {
                bResult = false;
                break;
            }
            APP_TRACE_INFO2("Flash write succeeded to address 0x%x. length %d", ui32TargetAddress, amotasFlash.bufferIndex);

            ui8PageCount++;
            amotasFlash.bufferIndex = 0;
            bResult = true;
        }
    }
    FLASH_OPERATE(g_pFlash, flash_disable);

    //
    // If we get here, operations are done correctly
    //
    return bResult;
}
#else
static bool amotas_write2flash(uint16_t len, uint8_t *buf, uint32_t addr, bool lastPktFlag)
{
    //
    // Program the flash page with the data.
    // Start a critical section.
    //
    if (am_hal_flash_program_main(AM_HAL_FLASH_PROGRAM_KEY, (uint32_t *)buf, (uint32_t *)addr, len / 4) != 0)
    {
        // flash helpers return non-zero for false, zero for success
        return false;
    }
    else
    {
        return true;
    }
}
#endif


#if 0 // Remove to avoid compiler warning
//*****************************************************************************
//
// Verify Firmware Image CRC
//
//return true if success, otherwise false
//*****************************************************************************
static bool_t
amotas_verify_firmware_crc(void)
{
    uint32_t i;
    uint32_t ui32Remainder;

    // read back the whole firmware image from flash and calculate CRC
    uint32_t ui32CRC = 0;

    //
    // Check crc in external flash
    //
    FLASH_OPERATE(g_pFlash, flash_enable);

    // read from spi flash and calculate CRC32
#if defined(AM_PART_APOLLO4B) || defined(AM_PART_APOLLO4P) || defined(AM_PART_APOLLO4L)
    if (amotasCb.fwHeader.imageId == AMOTA_IMAGE_ID_SBL)
    {
        for ( i = 0; i < (AMOTA_ENCRYPTED_SBL_SIZE / AMOTA_PACKET_SIZE); i++ )
        {
            g_pFlash->flash_read_page((uint32_t)amotasTmpBuf,
                (uint32_t *)(sblOtaStorageAddr + i*AMOTA_PACKET_SIZE),
                AMOTA_PACKET_SIZE);

            am_util_bootloader_partial_crc32(amotasTmpBuf, AMOTA_PACKET_SIZE, &ui32CRC);
        }

        ui32Remainder = AMOTA_ENCRYPTED_SBL_SIZE % AMOTA_PACKET_SIZE;
        if ( ui32Remainder )
        {
            g_pFlash->flash_read_page((uint32_t)amotasTmpBuf,
                (uint32_t *)(sblOtaStorageAddr + AMOTA_ENCRYPTED_SBL_SIZE - ui32Remainder),
                ui32Remainder);

            am_util_bootloader_partial_crc32(amotasTmpBuf, ui32Remainder, &ui32CRC);
        }

        for ( i = 0; i < ((amotasCb.fwHeader.fwLength - AMOTA_ENCRYPTED_SBL_SIZE) / AMOTA_PACKET_SIZE); i++ )
        {
            g_pFlash->flash_read_page((uint32_t)amotasTmpBuf,
                (uint32_t *)(amotasCb.newFwFlashInfo.addr + i*AMOTA_PACKET_SIZE),
                AMOTA_PACKET_SIZE);

            am_util_bootloader_partial_crc32(amotasTmpBuf, AMOTA_PACKET_SIZE, &ui32CRC);
        }
        ui32Remainder = (amotasCb.fwHeader.fwLength - AMOTA_ENCRYPTED_SBL_SIZE) % AMOTA_PACKET_SIZE;
        if ( ui32Remainder )
        {
            g_pFlash->flash_read_page((uint32_t)amotasTmpBuf,
                (uint32_t *)(amotasCb.newFwFlashInfo.addr + amotasCb.fwHeader.fwLength - ui32Remainder),
                ui32Remainder);

            am_util_bootloader_partial_crc32(amotasTmpBuf, ui32Remainder, &ui32CRC);
        }

    }
    else
#endif //#if defined(AM_PART_APOLLO4B) || defined(AM_PART_APOLLO4P) || defined(AM_PART_APOLLO4L)
    {
        for ( i = 0; i < (amotasCb.fwHeader.fwLength / AMOTA_PACKET_SIZE); i++ )
        {
            g_pFlash->flash_read_page((uint32_t)amotasTmpBuf,
                (uint32_t *)(amotasCb.newFwFlashInfo.addr + i*AMOTA_PACKET_SIZE),
                AMOTA_PACKET_SIZE);

            am_util_bootloader_partial_crc32(amotasTmpBuf, AMOTA_PACKET_SIZE, &ui32CRC);
        }

        ui32Remainder = amotasCb.fwHeader.fwLength % AMOTA_PACKET_SIZE;
        if ( ui32Remainder )
        {
            g_pFlash->flash_read_page((uint32_t)amotasTmpBuf,
                (uint32_t *)(amotasCb.newFwFlashInfo.addr + amotasCb.fwHeader.fwLength - ui32Remainder),
                ui32Remainder);

            am_util_bootloader_partial_crc32(amotasTmpBuf, ui32Remainder, &ui32CRC);
        }
    }

    FLASH_OPERATE(g_pFlash, flash_disable);

    return (ui32CRC == amotasCb.fwHeader.fwCrc);
}
#endif

//*****************************************************************************
//
// Update OTA information with Firmware Information.
//
//*****************************************************************************
#if defined(AM_PART_APOLLO3) || defined(AM_PART_APOLLO3P)
static void
amotas_update_ota(void)
{
    uint8_t magic = amotasCb.metaData.magicNum;

    // Set OTAPOINTER
    am_hal_ota_add(AM_HAL_FLASH_PROGRAM_KEY, magic, (uint32_t *)amotasCb.newFwFlashInfo.addr);
}

static void
amotas_init_ota(void)
{
    uint32_t *pOtaDesc = (uint32_t *)(OTA_POINTER_LOCATION & ~(AM_HAL_FLASH_PAGE_SIZE - 1));
    // Initialize OTA descriptor - This should ideally be initiated through a separate command
    // to facilitate multiple image upgrade in a single reboot
    // Will need change in the AMOTA app to do so
    am_hal_ota_init(AM_HAL_FLASH_PROGRAM_KEY, pOtaDesc);
}
#elif defined(AM_PART_APOLLO4B) || defined(AM_PART_APOLLO4P) || defined(AM_PART_APOLLO4L)
static void
amotas_update_ota(void)
{
    uint8_t magic;
    if (amotasCb.fwHeader.imageId == AMOTA_IMAGE_ID_SBL)
    {
        magic = AM_IMAGE_MAGIC_SBL;
    }
    else
    {
        magic = amotasCb.metaData.magicNum;
    }

    // Set OTAPOINTER
    am_hal_ota_add(AM_HAL_MRAM_PROGRAM_KEY, magic, (uint32_t *)amotasCb.newFwFlashInfo.addr);
}

static void
amotas_init_ota(void)
{
    am_hal_otadesc_t *pOtaDesc = (am_hal_otadesc_t *)(OTA_POINTER_LOCATION & ~(AM_HAL_FLASH_PAGE_SIZE - 1));
    // Initialize OTA descriptor - This should ideally be initiated through a separate command
    // to facilitate multiple image upgrade in a single reboot
    // Will need change in the AMOTA app to do so
    am_hal_ota_init(AM_HAL_MRAM_PROGRAM_KEY, pOtaDesc);
}
#else
static void
amotas_update_ota(void)
{
    am_util_multiboot_ota_t otaInfo;
    am_util_multiboot_ota_t *pOtaInfo = &otaInfo;
    uint32_t otaDescAddr;

    // We do not want to disturb the content of the flash page where OTA descriptor
    // resides, other than the OTA descriptor itself
    // This is to avoid needing a separate flash page just for this purpose
    // It allows the flash page to be shared with some other information if needed
    uint32_t otaPtrPageAddr = (OTA_POINTER_LOCATION & ~(AM_HAL_FLASH_PAGE_SIZE - 1));
    uint32_t otaPtrOffset = OTA_POINTER_LOCATION - otaPtrPageAddr;

    // Use the temporary accumulation buffer as scrach space
    // Take a backup of image info
    memcpy(amotasFlash.writeBuffer, (uint8_t *)otaPtrPageAddr, AM_HAL_FLASH_PAGE_SIZE);

    pOtaInfo->pui32LinkAddress = (uint32_t*)amotasCb.fwHeader.fwStartAddr;
    // When security info is present, it is prepended to the image in the blob
    // Actual image starts at an offset in the blob, and the actual image size
    // needs to be adjusted accordingly
    pOtaInfo->secInfoLen = amotasCb.fwHeader.secInfoLen;
    pOtaInfo->ui32NumBytes = amotasCb.fwHeader.fwLength - amotasCb.fwHeader.secInfoLen;

    pOtaInfo->ui32ImageCrc = amotasCb.fwHeader.fwCrc;

    pOtaInfo->pui32ImageAddr = (uint32_t*)(amotasCb.newFwFlashInfo.addr + amotasCb.fwHeader.secInfoLen);

    pOtaInfo->magicNum = OTA_INFO_MAGIC_NUM;

#if (AMOTAS_SUPPORT_EXT_FLASH == 1)
    if (amotasCb.fwHeader.storageType == AMOTA_FW_STORAGE_EXTERNAL)
    {
        pOtaInfo->ui32Options = OTA_INFO_OPTIONS_EXT_FLASH;
        g_pFlash->flash_read_page((uint32_t)amotasTmpBuf,
            (uint32_t *)amotasCb.newFwFlashInfo.addr, pOtaInfo->secInfoLen);

        // Store secInfo immediately following the OTA descriptor
        pOtaInfo->pui32SecInfoPtr = (uint32_t*)(OTA_POINTER_LOCATION + 4 + sizeof(otaInfo));

        // Copy the secInfo
        am_util_bootloader_write_flash_within_page((uint32_t)pOtaInfo->pui32SecInfoPtr,
                                               amotasTmpBuf, pOtaInfo->secInfoLen / 4);
    }
    else
#endif
    {
        pOtaInfo->ui32Options = 0;
        pOtaInfo->pui32SecInfoPtr = (uint32_t*)(amotasCb.newFwFlashInfo.addr);
    }
    if (amotasCb.fwHeader.fwDataType != 0)
    {
        pOtaInfo->ui32Options |= OTA_INFO_OPTIONS_DATA;
    }

    // Compute CRC of the OTA Descriptor
    pOtaInfo->ui32Crc = 0;
    am_util_bootloader_partial_crc32((uint32_t *)pOtaInfo, sizeof(*pOtaInfo) - 4, &pOtaInfo->ui32Crc);

    otaDescAddr = OTA_POINTER_LOCATION + 4;
    // Copy the OTA Pointer
    memcpy(&amotasFlash.writeBuffer[otaPtrOffset], (uint8_t *)&otaDescAddr, 4);
    // Copy the OTA descriptor
    memcpy(&amotasFlash.writeBuffer[otaPtrOffset + 4], (uint8_t *)pOtaInfo, sizeof(otaInfo));

    // Write the flash Page
    am_util_bootloader_program_flash_page(otaPtrPageAddr, (uint32_t *)amotasFlash.writeBuffer, AM_HAL_FLASH_PAGE_SIZE);
}
static void
amotas_init_ota(void)
{
}
#endif

//*****************************************************************************
//
// Handle the various packet types from the Client
//
//*****************************************************************************
void
amotas_packet_handler(eAmotaCommand cmd, uint16_t len, uint8_t *buf)
{
    eAmotaStatus status = AMOTA_STATUS_SUCCESS;
    uint8_t data[4] = {0};
    bool bResult = false;
    uint32_t ver, fwCrc;
    ver = fwCrc = 0;
    bool_t resumeTransfer = FALSE;

    APP_TRACE_INFO2("received packet cmd = 0x%x, len = 0x%x", cmd, len);

    switch(cmd)
    {
        case AMOTA_CMD_FW_HEADER:
            if (len < AMOTA_FW_HEADER_SIZE)
            {
                status = AMOTA_STATUS_INVALID_HEADER_INFO;
                amotas_reply_to_client(cmd, status, NULL, 0);
                break;
            }

            if (amotasCb.state == AMOTA_STATE_GETTING_FW)
            {
                BYTES_TO_UINT32(ver, buf + 32);
                BYTES_TO_UINT32(fwCrc, buf + 12);

                if ( ver == amotasCb.fwHeader.version && fwCrc == amotasCb.fwHeader.fwCrc )
                {
                    resumeTransfer = TRUE;
                }
            }

            ui32ImageCalCRC = 0;

            BYTES_TO_UINT32(amotasCb.fwHeader.encrypted, buf);
            BYTES_TO_UINT32(amotasCb.fwHeader.fwStartAddr, buf + 4);
            BYTES_TO_UINT32(amotasCb.fwHeader.fwLength, buf + 8);
            BYTES_TO_UINT32(amotasCb.fwHeader.fwCrc, buf + 12);
            BYTES_TO_UINT32(amotasCb.fwHeader.secInfoLen, buf + 16);
            BYTES_TO_UINT32(amotasCb.fwHeader.version, buf + 32);
            BYTES_TO_UINT32(amotasCb.fwHeader.fwDataType, buf + 36);
            BYTES_TO_UINT32(amotasCb.fwHeader.storageType, buf + 40);
#if defined(AM_PART_APOLLO4B) || defined(AM_PART_APOLLO4P) || defined(AM_PART_APOLLO4L)
            BYTES_TO_UINT32(amotasCb.fwHeader.imageId, buf + 44);

            //get the SBL OTA storage address if the image is for SBL
            //the address can be 0x8000 or 0x10000 based on current SBL running address
            if (amotasCb.fwHeader.imageId == AMOTA_IMAGE_ID_SBL)
            {
                am_hal_security_info_t pSecInfo;
                am_hal_security_get_info(&pSecInfo);
                sblOtaStorageAddr = pSecInfo.sblStagingAddr;
                APP_TRACE_INFO1("get sblOtaStorageAddr: 0x%x", sblOtaStorageAddr);
            }
#endif

            if (resumeTransfer)
            {
                APP_TRACE_INFO1("OTA process start from offset = 0x%x", amotasCb.newFwFlashInfo.offset);
                APP_TRACE_INFO1("beginning of flash addr = 0x%x", amotasCb.newFwFlashInfo.addr);
            }
            else
            {
                APP_TRACE_INFO0("OTA process start from beginning");
                amotasFlash.bufferIndex = 0;
                bResult = amotas_set_fw_addr();

                if ( bResult == false )
                {
                    amotas_reply_to_client(cmd, AMOTA_STATUS_INSUFFICIENT_FLASH, NULL, 0);
                    amotasCb.state = AMOTA_STATE_INIT;
                    return;
                }

                amotasCb.state = AMOTA_STATE_GETTING_FW;
            }
#ifdef AMOTA_DEBUG_ON
            APP_TRACE_INFO0("============= fw header start ===============");
            APP_TRACE_INFO1("encrypted = 0x%x", amotasCb.fwHeader.encrypted);
            APP_TRACE_INFO1("version = 0x%x", amotasCb.fwHeader.version);
            APP_TRACE_INFO1("fwLength = 0x%x", amotasCb.fwHeader.fwLength);
            APP_TRACE_INFO1("fwCrc = 0x%x", amotasCb.fwHeader.fwCrc);
            APP_TRACE_INFO1("fwStartAddr = 0x%x", amotasCb.fwHeader.fwStartAddr);
            APP_TRACE_INFO1("fwDataType = 0x%x", amotasCb.fwHeader.fwDataType);
            APP_TRACE_INFO1("storageType = 0x%x", amotasCb.fwHeader.storageType);
#if defined(AM_PART_APOLLO4B) || defined(AM_PART_APOLLO4P) || defined(AM_PART_APOLLO4L)
            APP_TRACE_INFO1("imageId = 0x%x", amotasCb.fwHeader.imageId);
#endif
            APP_TRACE_INFO0("============= fw header end ===============");
#endif // AMOTA_DEBUG_ON
            data[0] = ((amotasCb.newFwFlashInfo.offset) & 0xff);
            data[1] = ((amotasCb.newFwFlashInfo.offset >> 8) & 0xff);
            data[2] = ((amotasCb.newFwFlashInfo.offset >> 16) & 0xff);
            data[3] = ((amotasCb.newFwFlashInfo.offset >> 24) & 0xff);
            amotas_reply_to_client(cmd, AMOTA_STATUS_SUCCESS, data, sizeof(data));
        break;

        case AMOTA_CMD_FW_DATA:
        {
#if defined(AM_PART_APOLLO3) || defined(AM_PART_APOLLO3P) || defined(AM_PART_APOLLO4B) || defined(AM_PART_APOLLO4P) || defined(AM_PART_APOLLO4L)
            if ( amotasCb.newFwFlashInfo.offset == 0 )
            {
                memcpy(&amotasCb.metaData, buf, sizeof(amotaMetadataInfo_t));
            }
#endif

#if defined(AM_PART_APOLLO4B) || defined(AM_PART_APOLLO4P) || defined(AM_PART_APOLLO4L)
            if (amotasCb.fwHeader.imageId == AMOTA_IMAGE_ID_SBL)
            {
                if ( amotasCb.newFwFlashInfo.offset < AMOTA_ENCRYPTED_SBL_SIZE )
                {
                    bResult = amotas_write2flash(len, buf, sblOtaStorageAddr + amotasCb.newFwFlashInfo.offset,
                        ((amotasCb.newFwFlashInfo.offset + len) == AMOTA_ENCRYPTED_SBL_SIZE));
                }
                else
                {
                    bResult = amotas_write2flash(len, buf, amotasCb.newFwFlashInfo.addr + amotasCb.newFwFlashInfo.offset - AMOTA_ENCRYPTED_SBL_SIZE,
                        ((amotasCb.newFwFlashInfo.offset + len) == amotasCb.fwHeader.fwLength));
                }
            }
            else
#endif
            {
                bResult = amotas_write2flash(len, buf, amotasCb.newFwFlashInfo.addr + amotasCb.newFwFlashInfo.offset,
                    ((amotasCb.newFwFlashInfo.offset + len) == amotasCb.fwHeader.fwLength));
            }

            if ( bResult == false )
            {
                data[0] = ((amotasCb.newFwFlashInfo.offset) & 0xff);
                data[1] = ((amotasCb.newFwFlashInfo.offset >> 8) & 0xff);
                data[2] = ((amotasCb.newFwFlashInfo.offset >> 16) & 0xff);
                data[3] = ((amotasCb.newFwFlashInfo.offset >> 24) & 0xff);
                amotas_reply_to_client(cmd, AMOTA_STATUS_FLASH_WRITE_ERROR, data, sizeof(data));
            }
            else
            {
                am_util_bootloader_partial_crc32(buf, len, &ui32ImageCalCRC);

                amotasCb.newFwFlashInfo.offset += len;

                data[0] = ((amotasCb.newFwFlashInfo.offset) & 0xff);
                data[1] = ((amotasCb.newFwFlashInfo.offset >> 8) & 0xff);
                data[2] = ((amotasCb.newFwFlashInfo.offset >> 16) & 0xff);
                data[3] = ((amotasCb.newFwFlashInfo.offset >> 24) & 0xff);
                amotas_reply_to_client(cmd, AMOTA_STATUS_SUCCESS, data, sizeof(data));
            }
        }
        break;

        case AMOTA_CMD_FW_VERIFY:
            //amotas_verify_firmware_crc() reads data from flash and calculates the CRC, but may not stable enough
            //at this moment calculate the CRC in the firmware downloading process
            //since the data will be read out to verify in writing process
            if ( ui32ImageCalCRC == amotasCb.fwHeader.fwCrc )
            {
                APP_TRACE_INFO0("crc verify success");

                amotas_reply_to_client(cmd, AMOTA_STATUS_SUCCESS, NULL, 0);

                //
                // Update flash flag page here
                //
                amotas_update_ota();
            }
            else
            {
                APP_TRACE_INFO0("crc verify failed");
                amotas_reply_to_client(cmd, AMOTA_STATUS_CRC_ERROR, NULL, 0);
            }
            FLASH_OPERATE(g_pFlash, flash_deinit);
            amotasCb.state = AMOTA_STATE_INIT;
            ui32ImageCalCRC = 0;
            g_pFlash = &g_intFlash;

#if defined(AM_PART_APOLLO4B) || defined(AM_PART_APOLLO4P) || defined(AM_PART_APOLLO4L)
            //set SBL OTA storage address to invalid value once data verify finish
            if (amotasCb.fwHeader.imageId == AMOTA_IMAGE_ID_SBL)
            {
                sblOtaStorageAddr = AMOTA_INVALID_SBL_STOR_ADDR;
            }
#endif
        break;

        case AMOTA_CMD_FW_RESET:
            APP_TRACE_INFO0("Apollo will disconnect BLE link in 500ms.");
            amotas_reply_to_client(cmd, AMOTA_STATUS_SUCCESS, NULL, 0);

            //
            // Delay here to let packet go through the RF before we disconnect
            //
            WsfTimerStartMs(&amotasCb.disconnectTimer, 500);
        break;

        default:
        break;
    }
}


//*****************************************************************************
//
//! @brief initialize amota service
//!
//! @param handlerId - connection handle
//! @param pCfg - configuration parameters
//!
//! @return None
//
//*****************************************************************************
void
amotas_init(wsfHandlerId_t handlerId, AmotasCfg_t *pCfg)
{
    memset(&amotasCb, 0, sizeof(amotasCb));
    amotasCb.appHandlerId = handlerId;
    amotasCb.txReady = FALSE;
    amotasCb.state = AMOTA_STATE_INIT;
    amotasCb.resetTimer.handlerId = handlerId;
    amotasCb.disconnectTimer.handlerId = handlerId;
    for (int i = 0; i < DM_CONN_MAX; i++)
    {
        amotasCb.conn[i].connId = DM_CONN_ID_NONE;
    }
    amotas_init_ota();
}

void
amotas_conn_close(dmConnId_t connId)
{
    /* clear connection */
    amotasCb.conn[connId - 1].connId = DM_CONN_ID_NONE;
    amotasCb.conn[connId - 1].amotaToSend = FALSE;

    amotasCb.pkt.offset = 0;
    amotasCb.pkt.len = 0;
    amotasCb.pkt.type = AMOTA_CMD_UNKNOWN;
}

uint8_t
amotas_write_cback(dmConnId_t connId, uint16_t handle, uint8_t operation,
                   uint16_t offset, uint16_t len, uint8_t *pValue, attsAttr_t *pAttr)
{
    uint8_t dataIdx = 0;
    uint32_t calDataCrc = 0;
#if 0
    uint16_t i = 0;
    APP_TRACE_INFO0("============= data arrived start ===============");
    for (i = 0; i < len; i++)
    {
        APP_TRACE_INFO1("%x\t", pValue[i]);
    }
    APP_TRACE_INFO0("");
    APP_TRACE_INFO0("============= data arrived end ===============");
#endif
    if (amotasCb.pkt.offset == 0 && len < AMOTA_HEADER_SIZE_IN_PKT)
    {
        APP_TRACE_INFO0("Invalid packet!!!");
        amotas_reply_to_client(AMOTA_CMD_FW_HEADER, AMOTA_STATUS_INVALID_PKT_LENGTH, NULL, 0);
        return ATT_SUCCESS;
    }

    // new packet
    if (amotasCb.pkt.offset == 0)
    {
        BYTES_TO_UINT16(amotasCb.pkt.len, pValue);
        amotasCb.pkt.type = (eAmotaCommand) pValue[2];
        dataIdx = 3;
#ifdef AMOTA_DEBUG_ON
        APP_TRACE_INFO1("pkt.len = 0x%x", amotasCb.pkt.len);
        APP_TRACE_INFO1("pkt.type = 0x%x", amotasCb.pkt.type);
#endif
        if (dataIdx > amotasCb.pkt.len)
        {
            APP_TRACE_INFO0("packet length is wrong since it's smaller than 3!");
            return ATT_SUCCESS;
        }
    }

    // make sure we have enough space for new data
    if (amotasCb.pkt.offset + len - dataIdx > AMOTA_PACKET_SIZE)
    {
        APP_TRACE_INFO0("not enough buffer size!!!");
        amotas_reply_to_client(amotasCb.pkt.type, AMOTA_STATUS_INSUFFICIENT_BUFFER, NULL, 0);
        return ATT_SUCCESS;
    }

    // copy new data into buffer and also save crc into it if it's the last frame in a packet
    // 4 bytes crc is included in pkt length
    memcpy(amotasCb.pkt.data + amotasCb.pkt.offset, pValue + dataIdx, len - dataIdx);
    amotasCb.pkt.offset += (len - dataIdx);

    // whole packet received
    if (amotasCb.pkt.offset >= amotasCb.pkt.len)
    {
        uint32_t peerCrc = 0;
        // check CRC
        BYTES_TO_UINT32(peerCrc, amotasCb.pkt.data + amotasCb.pkt.len - AMOTA_CRC_SIZE_IN_PKT);
        calDataCrc = CalcCrc32(0xFFFFFFFFU, amotasCb.pkt.len - AMOTA_CRC_SIZE_IN_PKT, amotasCb.pkt.data);
#ifdef AMOTA_DEBUG_ON
        APP_TRACE_INFO1("calDataCrc = 0x%x", calDataCrc);
        APP_TRACE_INFO1("peerCrc = 0x%x", peerCrc);
#endif

        if (peerCrc != calDataCrc)
        {
            amotas_reply_to_client(amotasCb.pkt.type, AMOTA_STATUS_CRC_ERROR, NULL, 0);

            // clear pkt
            amotasCb.pkt.offset = 0;
            amotasCb.pkt.type = AMOTA_CMD_UNKNOWN;
            amotasCb.pkt.len = 0;

            return ATT_SUCCESS;
        }

        amotas_packet_handler(amotasCb.pkt.type, amotasCb.pkt.len - AMOTA_CRC_SIZE_IN_PKT, amotasCb.pkt.data);
        // clear pkt after handled
        amotasCb.pkt.offset = 0;
        amotasCb.pkt.type = AMOTA_CMD_UNKNOWN;
        amotasCb.pkt.len = 0;
    }

    return ATT_SUCCESS;
}

void
amotas_start(dmConnId_t connId, uint8_t resetTimerEvt,
             uint8_t disconnectTimerEvt, uint8_t amotaCccIdx)
{
    //
    // set conn id
    //
    amotasCb.conn[connId - 1].connId = connId;
    amotasCb.conn[connId - 1].amotaToSend = TRUE;

    amotasCb.resetTimer.msg.event = resetTimerEvt;
    amotasCb.disconnectTimer.msg.event = disconnectTimerEvt;
}

void
amotas_stop(dmConnId_t connId)
{
    //
    // clear connection
    //
    amotasCb.conn[connId - 1].connId = DM_CONN_ID_NONE;
    amotasCb.conn[connId - 1].amotaToSend = FALSE;
}


//*****************************************************************************
//
//! @brief initialize amota service
//!
//! @param pMsg - WSF message
//!
//! @return None
//
//*****************************************************************************
void amotas_proc_msg(wsfMsgHdr_t *pMsg)
{
    if (pMsg->event == DM_CONN_OPEN_IND)
    {
        amotas_conn_open((dmEvt_t *) pMsg);
    }
    else if (pMsg->event == DM_CONN_UPDATE_IND)
    {
        amotas_conn_update((dmEvt_t *) pMsg);
    }
    else if (pMsg->event == amotasCb.resetTimer.msg.event)
    {
        amotas_reset_timer_expired(pMsg);
    }
    else if (pMsg->event == amotasCb.disconnectTimer.msg.event)
    {
        amotas_disconnect_timer_expired(pMsg);
    }
}
