/*!
 * @file        ymodem.c
 *
 * @brief       Include Ymodem operation to HyperTerminal
 *
 * @version     V1.0.0
 *
 * @date        2026-02-26
 *
 * @attention
 *
 *  Copyright (C) 2026 Geehy Semiconductor
 *
 *  You may not use this file except in compliance with the
 *  GEEHY COPYRIGHT NOTICE (Geehy Semiconductor Software License Agreement).
 *
 *  The program is only for reference, which is distributed in the hope
 *  that it will be useful and instructional for customers to develop
 *  their software. Unless required by applicable law or agreed to in
 *  writing, the program is distributed on an "AS IS" BASIS, WITHOUT
 *  ANY WARRANTY OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the Geehy Semiconductor Software License Agreement for the governing permissions
 *  and limitations under the License.
 */

/* Includes */
#include "ymodem.h"
#include <string.h>

/** @addtogroup Examples
  @{
  */

/** @addtogroup IAP_Examples
  @{
*/

/** @addtogroup IAP_BootLoader
  @{
  */

/** @defgroup IAP_BootLoader_Functions Functions
  @{
*/

/*!
 * @brief       Receive byte(s) from sender
 *
 * @param       CharacterPtr: input character
 *
 * @param       RevTimeout: receive timeout
 *
 * @retval      SUCCESS : Byte be received
 *              ERROR: receive timeout
 * @note
 */
static uint32_t Receive_Byte(uint8_t* CharacterPtr, uint32_t RevTimeout)
{
    uint32_t retVal = ERROR;
    volatile uint32_t revTimeoutTemp = RevTimeout;

    while (revTimeoutTemp > 0U)
    {
        if (Iap_GetKeyPressed(CharacterPtr) == (uint32_t)SUCCESS)
        {
            retVal = (uint32_t)SUCCESS;
            break;
        }
        else
        {
            /* do nothing */
        }

        revTimeoutTemp -= 1U;
    }

    return retVal;
}

/*!
 * @brief       Send a byte to receiver
 *
 * @param       Character: character to be sent
 *
 * @retval      reture value
 *              @arg 0: Byte sent
 *
 * @note
 */
static uint32_t Send_Byte(uint8_t Character)
{
    Iap_SendChar(Character);

    return 0U;
}

/*!
 * @brief       Update CRC16 for input byte
 *
 * @param       Value: CRC input value
 *
 * @param       InputByte: input byte
 *
 * @retval      returnTemp: Updated CRC value
 *
 * @note
 */
uint16_t Iap_Crc16Update(uint16_t Value, uint8_t InputByte)
{
    uint32_t value_crc = (uint32_t)Value;
    uint32_t value_int = InputByte | (uint32_t)0x100U;
    uint16_t returnTemp = 0U;

    do
    {
        value_crc <<= 1U;
        value_int <<= 1U;

        if ((value_int & (uint32_t)0x100U) != 0U)
        {
            ++ value_crc;
        }
        else
        {
            /* do nothing */
        }

        if ((value_crc & (uint32_t)0x10000U) != 0U)
        {
            value_crc ^= (uint32_t)0x1021U;
        }
        else
        {
            /* do nothing */
        }
    }
    while ((value_int & (uint32_t)0x10000U) == 0U);

    returnTemp = (uint16_t)(value_crc & (uint32_t)0xffffU);

    return returnTemp;
}

/*!
 * @brief       Cal CRC16
 *
 * @param       DataPtr:InPut data pointer
 *
 * @param       Len:Input data length
 *
 * @retval      CRC16 sum
 *
 * @note
 */
uint16_t Iap_Crc16Sum(const uint8_t* DataPtr, uint32_t Len)
{
    uint16_t crc_sum = 0U;
    uint32_t index = 0U;
    const uint8_t *dataTempPtr = DataPtr;

    do
    {
        crc_sum = Iap_Crc16Update(crc_sum, dataTempPtr[index]);
        index += 1U;
    }
    while (index < Len);

    crc_sum = Iap_Crc16Update(Iap_Crc16Update((uint16_t)crc_sum, 0U), 0U);

    crc_sum &= (uint16_t)0xffff;

    return crc_sum;
}

/*!
 * @brief       Receive a Ymodem packet from sender
 *
 * @param       DataPtr: Data pointer
 *
 * @param       LenPtr: Data length
 *
 * @param       Timeout
 *              @arg 0 : end
 *              @arg -1: abort
 *              @arg >0: pack length
 *
 * @retval      receive status
 *              @arg 0 : normally return
 *              @arg -1: timeout or pack error
 *              @arg 1 : abort
 *
 * @note
 */
static int32_t Receive_YmodePacket(uint8_t* DataPtr, int32_t* LenPtr, uint32_t Timeout)
{
    uint16_t pack_crc;
    uint32_t i, pack_len;
    uint8_t character;
    int32_t retVal = 2;
    *LenPtr = 0;

    if (Receive_Byte(&character, Timeout) != (uint32_t)SUCCESS)
    {
        retVal = -1;
    }
    else
    {
        if (character == (uint8_t)FRAME_SOH)
        {
            pack_len = PACKET_SIZE;
        }
        else if (character == (uint8_t)FRAME_STX)
        {
            pack_len = PACKET_1K_SIZE;
        }
        else if (character == (uint8_t)FRAME_EOT)
        {
            retVal = 0;
        }
        else if (character == (uint8_t)FRAME_CAN)
        {
            if ((Receive_Byte(&character, Timeout) == (uint32_t)SUCCESS) && (character == (uint8_t)FRAME_CAN))
            {
                *LenPtr = -1;
                retVal = 0;
            }
            else
            {
                retVal = -1;
            }
        }
        else if ((character == (uint8_t)ABORT1) || (character == (uint8_t)ABORT2))
        {
            retVal = 1;
        }
        else
        {
            retVal = -1;
        }
        
        if(2 == retVal)
        {
            *DataPtr = character;
        
            for (i = 1; i < (pack_len + (uint8_t)PACK_FRAME_OVERHEAD); i ++)
            {
                if (Receive_Byte((uint8_t*)((uint32_t)DataPtr + i), Timeout) != (uint32_t)SUCCESS)
                {
                    retVal = -1;
                    break;
                }
                else
                {
                    /* do nothing */
                }
            }

            if(2 == retVal)
            {
                if (DataPtr[PACK_FRAMENO_INDEX] != ((DataPtr[PACK_FRAMENO_COMP_INDEX] ^ (uint8_t)0xff) & (uint8_t)0xff))
                {
                    retVal = -1;
                }
                else
                {
                    /* do nothing */
                }
                
                if(2 == retVal)
                {
                    /* Calculate the CRC16 */
                    pack_crc = Iap_Crc16Sum(&DataPtr[PACK_FRAME_HEADER], (uint32_t)pack_len);
                    
                    if (pack_crc != (((uint16_t)DataPtr[pack_len + 3U] << 8U) | (uint16_t)DataPtr[pack_len + 4U]))
                    {
                        /* CRC16 error */
                        retVal = -1;
                    }
                    else
                    {
                        /* do nothing */
                    }
                    
                    if(2 == retVal)
                    {
                        *LenPtr = (int32_t)pack_len;
                        retVal = 0;
                    }
                    else
                    {
                        /* do nothing */
                    }
                }
                else
                {
                    /* do nothing */
                }
            }
            else
            {
                /* do nothing */
            }
        }
        else
        {
            /* do nothing */
        }
    }

    return retVal;
}

/*!
 * @brief       Receive a file header from sender using the Ymodem
 *
 * @param       PackdataPtr: file data pointer
 *
 * @param       LenPtr : file length pointer
 * 
 * @param       FileFinishPtr : file finish status pointer
 * 
 * @param       SessionFinishPtr : file session finish status pointer
 * 
 * @param       Application : APP1 or APP2
 *
 * @retval      The Api operation status
 *
 * @note
 */
static uint8_t Iap_DealPackFrameHead(const uint8_t *PackdataPtr, int32_t *LenPtr, int32_t *FileFinishPtr, 
    int32_t *SessionFinishPtr, uint8_t Application)
{
    const uint8_t* file_p;
    uint8_t file_len[FILE_SIZE_MAX];
    uint8_t retVal = 0U;
    uint32_t i;
    uint32_t index = 0U;
    
    /* Ymodem Filename Pack */
    if (PackdataPtr[PACK_FRAME_HEADER] != 0U)
    {
        file_p = (uint8_t*)((uint32_t)PackdataPtr + (uint32_t)PACK_FRAME_HEADER);

        /* Filename pack has valid data */
        for (i = 0U; i < (uint32_t)FILE_NAME_MAX; i++)
        {
            if(file_p[index] == 0U)
            {
                break;
            }
            else
            {
                /* do nothing */
            }

            FileN[i] = file_p[index];
            index += 1U;
        }

        FileN[i] = (uint8_t)'\0';

        index += 1U;
        for (i = 0; (file_p[index] != (uint8_t)' ') && (i < ((uint32_t)FILE_SIZE_MAX - 1U)); i++)
        {
            file_len[i] = file_p[index];
            index += 1U;
        }

        file_len[i] = (uint8_t)'\0';

        Iap_StrConInt(file_len, LenPtr);

        /* File len is greater than Flash len */
        if (*LenPtr > ((int32_t)USER_FLASH_SIZE + 1))
        {
            /** End section */
            Send_Byte(FRAME_CAN);
            Send_Byte(FRAME_CAN);

            *LenPtr = -1;
            retVal = 1U;
        }
        else
        {
            /* do nothing */
        }
        
        if(0U == retVal)
        {
            /* erase user application area */
            Flash_IapErase((App_DefType)Application);
            Send_Byte(FRAME_ACK);
            Send_Byte(FRAME_CRC16);
        }
        else
        {
            /* do nothing */
        }

    }
    /* Filename pack is empty, end section */
    else
    {
        Send_Byte(FRAME_ACK);
        *FileFinishPtr = 1;
        *SessionFinishPtr = 1;
        retVal = 1U;
    }

    return retVal;
}

/*!
 * @brief       Receive a file data from sender using the Ymodem
 *
 * @param       PackdataPtr: file data pointer
 *
 * @param       BufferPtr: file buffer
 * 
 * @param       LenPtr : file length pointer
 * 
 * @param       PackLenPtr : pack data length pointer
 * 
 * @param       Application : APP1 or APP2
 * 
 * @param       FlashAddressPtr : The flash address
 *
 * @retval      The Api operation status
 *
 * @note
 */
static uint8_t Iap_DealPackFrameData(const uint8_t *PackdataPtr, uint8_t *BufferPtr, int32_t *LenPtr, const int32_t *PackLenPtr,
     uint8_t Application, uint32_t *FlashAddressPtr)
{
    uint32_t code;
    uint8_t retVal = 0U;
    uint8_t* buf_p = BufferPtr;
    
    memcpy(buf_p, (const uint8_t*)((uint32_t)PackdataPtr + (uint32_t)PACK_FRAME_HEADER), (uint32_t)*PackLenPtr);
    code = (uint32_t)BufferPtr;

    /* Write data in Flash */
    if (Flash_IapWrite(FlashAddressPtr, (uint32_t*) code, (uint16_t)*PackLenPtr, 
        (App_DefType)Application) == (uint32_t)SUCCESS)
    {
        Send_Byte(FRAME_ACK);
    }
    else
    {
        /* End section */
        Send_Byte(FRAME_CAN);
        Send_Byte(FRAME_CAN);

        *LenPtr = -2;
        retVal = 1U;
    }

    return retVal;
}

/*!
 * @brief       Receive a file data from sender using the Ymodem
 *
 * @param       PackdataPtr: file data pointer
 *
 * @param       BufferPtr: file buffer
 * 
 * @param       FileFinishPtr : file finish status pointer
 * 
 * @param       SessionFinishPtr : file session finish status pointer
 * 
 * @param       LenPtr : file length pointer
 * 
 * @param       PackLenPtr : pack data length pointer
 * 
 * @param       Application : APP1 or APP2
 * 
 * @param       FlashAddressPtr : The flash address
 *
 * @retval      The Api operation status
 *
 * @note
 */
static uint8_t Iap_DeadReceiveFrame(const uint8_t *PackdataPtr, int32_t *LenPtr, int32_t *FileFinishPtr, 
    int32_t *SessionFinishPtr, uint8_t *BufferPtr, const int32_t *PackLenPtr, uint32_t *FlashAddressPtr, 
    uint8_t Application, uint32_t PackRec)
{
    uint8_t retVal = 0U;
    
    if (0U == PackRec)
    {
        /* Ymodem Filename Pack */
        retVal = Iap_DealPackFrameHead(PackdataPtr, LenPtr, FileFinishPtr, 
            SessionFinishPtr, Application);
    }
    /* Ymodem Data Pack */
    else
    {
        retVal = Iap_DealPackFrameData(PackdataPtr, BufferPtr, LenPtr, PackLenPtr,
            Application, FlashAddressPtr);
    }
    
    return retVal;
}

/*!
 * @brief       Receive a file from sender using the Ymodem
 *
 * @param       BufferPtr: file buffer
 *
 * @param       Application : APP1 or APP2
 *
 * @retval      The lenght of the file
 *
 * @note
 */
int32_t Iap_ReceiveFile(uint8_t* BufferPtr, uint8_t Application)
{
    uint8_t pack_data[PACK_1K_FRAME_OVERHEAD_SIZE];
    uint8_t dataDealRet = 0U;
    int32_t pack_len;
    int32_t session_finish;
    int32_t file_finish;
    uint32_t packs_received;
    int32_t err;
    int32_t session_begin;
    int32_t pack_ref;
    int32_t len = -5;
    uint32_t flashaddress;
    uint8_t crcSendFlag = 0U;

    /* Initialize flashaddress variable */
    if (Application == (uint8_t)APP1)
    {
        flashaddress = USER_APP1_START_ADDRESS;
    }
    else
    {
        flashaddress = USER_APP2_START_ADDRESS;
    }

    session_finish = 0;
    err = 0;
    session_begin = 0;

    for (;;)
    {
        packs_received = 0; 
        file_finish = 0;
        crcSendFlag = 0U;

        for (;;)
        {
            pack_ref = Receive_YmodePacket(pack_data, &pack_len, NAK_TIMEOUT);

            if (pack_ref == 0x01)
            {
                Send_Byte(FRAME_CAN);
                Send_Byte(FRAME_CAN);

                len = -3;
            }
            else if (pack_ref == 0)
            {
                err = 0;

                if (pack_len == -1)
                {
                    Send_Byte(FRAME_ACK);
                    len = 0;
                }
                else if (pack_len == 0)
                {
                    Send_Byte(FRAME_ACK);
                    file_finish = 1;
                }
                else
                {
                    if (((uint32_t)pack_data[(uint32_t)PACK_FRAMENO_INDEX] & (uint32_t)0xffU) != 
                        (packs_received & (uint32_t)0xffU))
                    {
                        Send_Byte(FRAME_NACK);
                    }
                    else
                    {
                        dataDealRet = Iap_DeadReceiveFrame(pack_data, &len, &file_finish, 
                            &session_finish, BufferPtr, &pack_len, &flashaddress, 
                            Application, packs_received);
                        
                        if(1U != dataDealRet)
                        {
                            packs_received++;
                            session_begin = 1;
                        }
                        else
                        {
                            /* do nothing */
                        }
                    }
                }
            }
            else
            {
                if (session_begin > 0)
                {
                    err ++;
                }
                else
                {
                    /* do nothing */
                }

                if (err > ERRORS)
                {
                    Send_Byte(FRAME_CAN);
                    Send_Byte(FRAME_CAN);

                    len = 0;
                }
                else
                {
                    /* do nothing */
                }

                if(len != 0)
                {
                    Send_Byte(FRAME_CRC16);
                    crcSendFlag = 1U;
                }
                else
                {
                    /* do nothing */
                }
            }

            if ((file_finish != 0) || (-3 == len) || (0 == len) || 
                (1U == dataDealRet) || (1U == crcSendFlag))
            {
                break;
            }
            else
            {
                /* do nothing */
            }
        }

        if ((session_finish != 0) || (len == -3) || (len == 0) || (len == -1) || (len == -2))
        {
            break;
        }
        else
        {
            /* do nothing */
        }
    }

    return (int32_t)len;
}

/**@} end of group IAP_BootLoader_Functions */
/**@} end of group IAP_BootLoader */
/**@} end of group IAP_Examples */
/**@} end of group Examples */
