motor_cs103/User/system/lib/bootload/ymodem.c

634 lines
24 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @file ymodem.c
* @author xxx
* @date 2024-02-18 19:32:40
* @brief
* 该模块实现了YMODEM协议的核心功能包括初始化、配置、数据接收、握手、数据传输和结束处理。
* 它使用了回调函数来处理不同阶段的事件并使用信号量来同步不同的流程。CRC校验是用来确保数据传输的完整性和准确性
* @copyright Copyright (c) 2024 by xxx, All Rights Reserved.
*/
#include "ymodem.h"
#include "sys.h"
#include "delay.h"
#include "flow.h"
sqqueue_ctrl_t rym_sqqueue; // 一个接收队列控制结构体,用于管理接收到的数据。
static uint32_t tm_sec = 0; // 握手超时时间,用于握手阶段的超时计时
static enum rym_stage stage = RYM_STAGE_NONE; // 当前的阶段
static int32_t rym_tm_sec = 0; // YMODEM超时计时器
static uint8_t aPacketData[_RYM_PKG_SZ]; // 数据包缓冲区
// 回调函数,用于处理不同阶段的事件
static rym_callback rym_on_begin = NULL;
static rym_callback rym_on_data = NULL;
static rym_callback rym_on_end = NULL;
static rym_callback rym_transmit = NULL;
static struct flow handshake_fw; // 握手流程
static struct flow trans_fw; // 传输流程
static struct flow finsh_fw; // 结束流程
static struct flow_sem msg_sem; // 消息信号量,用于同步
static const uint16_t ccitt_table[256] =
{
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0};
uint16_t CRC16(unsigned char *q, int len)
{
uint16_t crc = 0;
while (len-- > 0)
crc = (crc << 8) ^ ccitt_table[((crc >> 8) ^ *q++) & 0xff];
return crc;
}
/* Exported macro ------------------------------------------------------------*/
#define IS_CAP_LETTER(c) (((c) >= 'A') && ((c) <= 'F'))
#define IS_LC_LETTER(c) (((c) >= 'a') && ((c) <= 'f'))
#define IS_09(c) (((c) >= '0') && ((c) <= '9'))
#define ISVALIDHEX(c) (IS_CAP_LETTER(c) || IS_LC_LETTER(c) || IS_09(c))
#define ISVALIDDEC(c) IS_09(c)
#define CONVERTDEC(c) (c - '0')
#define CONVERTHEX_ALPHA(c) (IS_CAP_LETTER(c) ? ((c) - 'A' + 10) : ((c) - 'a' + 10))
#define CONVERTHEX(c) (IS_09(c) ? ((c) - '0') : CONVERTHEX_ALPHA(c))
/**
* @brief Convert a string to an integer
* @param p_inputstr: The string to be converted
* @param p_intnum: The integer value
* @retval 1: Correct
* 0: Error
*/
uint32_t Str2Int(uint8_t *p_inputstr, uint32_t *p_intnum)
{
uint32_t i = 0, res = 0;
uint32_t val = 0;
if ((p_inputstr[0] == '0') && ((p_inputstr[1] == 'x') || (p_inputstr[1] == 'X')))
{
i = 2;
while ((i < 11) && (p_inputstr[i] != '\0'))
{
if (ISVALIDHEX(p_inputstr[i]))
{
val = (val << 4) + CONVERTHEX(p_inputstr[i]);
}
else
{
/* Return 0, Invalid input */
res = 0;
break;
}
i++;
}
/* valid result */
if (p_inputstr[i] == '\0')
{
*p_intnum = val;
res = 1;
}
}
else /* max 10-digit decimal input */
{
while ((i < 11) && (res != 1))
{
if (p_inputstr[i] == '\0')
{
*p_intnum = val;
/* return 1 */
res = 1;
}
else if (((p_inputstr[i] == 'k') || (p_inputstr[i] == 'K')) && (i > 0))
{
val = val << 10;
*p_intnum = val;
res = 1;
}
else if (((p_inputstr[i] == 'm') || (p_inputstr[i] == 'M')) && (i > 0))
{
val = val << 20;
*p_intnum = val;
res = 1;
}
else if (ISVALIDDEC(p_inputstr[i]))
{
val = val * 10 + CONVERTDEC(p_inputstr[i]);
}
else
{
/* return 0, Invalid input */
res = 0;
break;
}
i++;
}
}
return res;
}
/**
* @brief Read data from the circular queue.
*
* This function reads the specified length of data from the circular queue and stores it in the given buffer.
* If the length of data in the queue is greater than or equal to the specified length, it directly reads the specified length of data.
* If the length of data in the queue is less than the specified length, it reads all the data in the queue.
*
* @param buffer The buffer to store the data.
* @param len The length of data to read.
*
* @return The actual length of data read.
*/
static uint16_t rym_sqqueue_read(void *buffer, uint16_t len)
{
uint16_t i = 0;
uint8_t *buf = buffer;
if (rym_sqqueue.get_len(&rym_sqqueue) >= len)
{
for (i = 0; i < len; i++)
{
buf[i] = *((uint8_t *)rym_sqqueue.del(&rym_sqqueue));
}
}
else
{
while ((rym_sqqueue.get_len(&rym_sqqueue) != 0) && (i < len))
{
buf[i] = *((uint8_t *)rym_sqqueue.del(&rym_sqqueue));
}
}
return i;
}
/**
* @brief Initializes the parameters for the YMODEM process.
*
* This function sets the stage variable to the specified stage and initializes the rym_tm_sec variable with the value of tm_sec.
* If the stage is RYM_STAGE_ESTABLISHING, it also clears the rym_sqqueue.
*
* @param st The stage of the YMODEM process.
*/
static void rym_process_params_init(enum rym_stage st)
{
stage = st;
rym_tm_sec = tm_sec;
if (st == RYM_STAGE_ESTABLISHING)
{
rym_sqqueue.clear_sqq(&rym_sqqueue);
}
}
/**
* @brief Performs the handshake process for the YMODEM protocol.
*
* This function reads packets from the input queue and performs the necessary checks and actions
* to establish a connection and initiate file transfer using the YMODEM protocol.
*
* @param fl The flow structure containing the necessary variables and synchronization mechanisms.
* @return The result of the handshake process.
* - 0: Handshake process completed successfully.
* - Non-zero: Handshake process failed.
*/
static uint8_t rym_do_handshake_process(struct flow *fl)
{
uint8_t index = 0;
FL_HEAD(fl);
static uint16_t rym_recv_len = 0;
static uint16_t recv_crc, cal_crc;
static uint8_t *file_ptr = NULL;
static uint8_t file_name[FILE_NAME_LENGTH];
static uint8_t file_size[FILE_SIZE_LENGTH];
static uint32_t filesize;
for (;;)
{
FL_LOCK_WAIT_SEM_OR_TIMEOUT(fl, &msg_sem, FL_CLOCK_SEC);
if (FL_SEM_IS_RELEASE(fl, &msg_sem))
{
rym_recv_len = rym_sqqueue_read(&aPacketData[PACKET_START_INDEX], 1);
if (rym_recv_len == 1)
{
if (aPacketData[PACKET_START_INDEX] != RYM_CODE_SOH && aPacketData[PACKET_START_INDEX] != RYM_CODE_STX)
continue;
/* SOH/STX + seq + payload + crc */
rym_recv_len = rym_sqqueue_read(&aPacketData[PACKET_NUMBER_INDEX],
_RYM_PKG_SZ - 1);
if (rym_recv_len != (_RYM_PKG_SZ - 1))
continue;
/* sanity check */
if ((aPacketData[PACKET_NUMBER_INDEX] != 0) || (aPacketData[PACKET_CNUMBER_INDEX] != 0xFF))
continue;
recv_crc = (uint16_t)(*(&aPacketData[PACKET_START_INDEX] + _RYM_PKG_SZ - 2) << 8) |
*(&aPacketData[PACKET_START_INDEX] + _RYM_PKG_SZ - 1);
cal_crc = CRC16(aPacketData + PACKET_DATA_INDEX, _RYM_PKG_SZ - 5);
if (recv_crc != cal_crc)
continue;
if (rym_on_begin != NULL)
{
file_ptr = aPacketData + PACKET_DATA_INDEX;
while ((*file_ptr != 0) && (index < FILE_NAME_LENGTH))
{
file_name[index++] = *file_ptr++;
}
file_name[index++] = '\0';
index = 0;
file_ptr++;
while ((*file_ptr != ' ') && (index < FILE_SIZE_LENGTH))
{
file_size[index++] = *file_ptr++;
}
file_size[index++] = '\0';
Str2Int(file_size, &filesize);
if (RYM_CODE_NONE != rym_on_begin(file_name, filesize))
{
for (uint8_t i = 0; i < RYM_END_SESSION_SEND_CAN_NUM; i++)
{
aPacketData[0] = RYM_CODE_CAN;
rym_transmit(aPacketData, 1);
}
}
else
{
aPacketData[0] = RYM_CODE_ACK;
rym_transmit(aPacketData, 1);
FL_LOCK_DELAY(fl, FL_CLOCK_100MSEC * 5);
aPacketData[0] = RYM_CODE_C;
rym_transmit(aPacketData, 1);
rym_process_params_init(RYM_STAGE_TRANSMITTING);
FL_LOCK_DELAY(fl, FL_CLOCK_SEC);
}
}
}
}
else
{
aPacketData[0] = RYM_CODE_C;
rym_transmit(aPacketData, 1);
rym_tm_sec--;
}
}
FL_TAIL(fl);
}
/**
* @brief Transfers data using the YMODEM protocol.
*
* This function is responsible for transferring data using the YMODEM protocol.
* It receives the size of the data to be transferred and a pointer to the code
* that will be returned. It performs various checks on the received data and
* calculates the CRC to ensure data integrity. If all checks pass, it sets the
* code to RYM_CODE_ACK and returns 0. Otherwise, it returns an error code.
*
* @param data_size The size of the data to be transferred.
* @param code Pointer to the code that will be returned.
* @return 0 if successful, otherwise an error code.
*/
static int8_t rym_tran_data(uint16_t data_size, uint8_t *code)
{
DBG_ASSERT(NULL != code __DBG_LINE);
uint16_t recv_len = 0;
uint16_t recv_crc, cal_crc;
const uint16_t tran_size = PACKET_HEADER_SIZE - 1 + data_size + PACKET_TRAILER_SIZE;
/* seq + data + crc */
recv_len = rym_sqqueue_read(&aPacketData[PACKET_NUMBER_INDEX],
tran_size);
if (recv_len != tran_size)
return -RYM_ERR_DSZ;
/* sanity check */
if ((aPacketData[PACKET_NUMBER_INDEX] + aPacketData[PACKET_CNUMBER_INDEX]) != 0xFF)
return -RYM_ERR_SEQ;
/* As we are sending C continuously, there is a chance that the
* sender(remote) receive an C after sending the first handshake package.
* So the sender will interpret it as NAK and re-send the package. So we
* just ignore it and proceed. */
if (stage == RYM_STAGE_ESTABLISHED && aPacketData[PACKET_NUMBER_INDEX] == RYM_CODE_NONE)
{
*code = RYM_CODE_NONE;
return 0;
}
stage = RYM_STAGE_TRANSMITTING;
recv_crc = (uint16_t)(*(&aPacketData[PACKET_START_INDEX] + tran_size - 1) << 8) |
*(&aPacketData[PACKET_START_INDEX] + tran_size);
cal_crc = CRC16(aPacketData + PACKET_DATA_INDEX, data_size);
if (recv_crc != cal_crc)
return -RYM_ERR_CRC;
*code = RYM_CODE_ACK;
return 0;
}
/**
* @brief Performs the YMODEM transmission process.
*
* This function is responsible for handling the YMODEM transmission process.
* It receives packets of data and performs the necessary operations based on the received data.
* It handles timeouts and retransmissions if necessary.
*
* @param fl The flow structure pointer.
* @return The status of the transmission process.
*/
static uint8_t rym_do_trans_process(struct flow *fl)
{
FL_HEAD(fl);
static uint16_t data_size, rym_recv_len;
static uint8_t rym_code;
static uint16_t tran_timeout = 0;
for (;;)
{
FL_LOCK_WAIT_SEM_OR_TIMEOUT(fl, &msg_sem, FL_CLOCK_SEC);
if (FALSE == FL_SEM_IS_RELEASE(fl, &msg_sem))
{
if (tran_timeout++ >= 5)
{
tran_timeout = 0;
rym_process_params_init(RYM_STAGE_ESTABLISHING);
FL_LOCK_DELAY(fl, FL_CLOCK_SEC);
}
}
else
{
tran_timeout = 0;
rym_recv_len = rym_sqqueue_read(&aPacketData[PACKET_START_INDEX], 1);
if (rym_recv_len == 1)
{
if (aPacketData[PACKET_START_INDEX] == RYM_CODE_SOH)
data_size = PACKET_SIZE;
else if (aPacketData[PACKET_START_INDEX] == RYM_CODE_STX)
data_size = PACKET_1K_SIZE;
else if (aPacketData[PACKET_START_INDEX] == RYM_CODE_EOT)
{
aPacketData[0] = RYM_CODE_NAK;
rym_transmit(aPacketData, 1);
rym_process_params_init(RYM_STAGE_FINISHING);
FL_LOCK_DELAY(fl, FL_CLOCK_SEC);
continue;
}
else
{
rym_process_params_init(RYM_STAGE_ESTABLISHING);
FL_LOCK_DELAY(fl, FL_CLOCK_SEC);
continue;
}
if (rym_tran_data(data_size, &rym_code) == 0)
{
if (rym_on_data != NULL)
rym_on_data(aPacketData + PACKET_DATA_INDEX, data_size);
if (rym_code == RYM_CODE_CAN)
{
for (uint8_t i = 0; i < RYM_END_SESSION_SEND_CAN_NUM; i++)
{
aPacketData[0] = RYM_CODE_CAN;
rym_transmit(aPacketData, 1);
}
}
else if (rym_code == RYM_CODE_ACK)
{
aPacketData[0] = RYM_CODE_ACK;
rym_transmit(aPacketData, 1);
}
}
}
}
}
FL_TAIL(fl);
}
/**
* @brief Performs the finishing process for the YMODEM protocol.
*
* This function is responsible for handling the final stage of the YMODEM protocol,
* where the receiver receives the end of transmission (EOT) signal from the sender.
* It verifies the received EOT signal, sends an acknowledgement (ACK) signal back to
* the sender, and waits for the start of header (SOH) signal to receive the final
* packet containing the payload and checksum. If the received packet is valid, it
* calculates the checksum and compares it with the received checksum. If they match,
* it sets the stage to RYM_STAGE_FINISHED and invokes the callback function if
* available. This function also handles timeout conditions and reinitializes the
* protocol parameters if necessary.
*
* @param fl The flow structure pointer.
* @return The result of the finishing process.
*/
static uint8_t rym_do_finish_process(struct flow *fl)
{
FL_HEAD(fl);
static uint16_t rym_recv_len;
static uint16_t recv_crc, cal_crc;
static uint16_t tran_timeout = 0;
for (;;)
{
FL_LOCK_WAIT_SEM_OR_TIMEOUT(fl, &msg_sem, FL_CLOCK_SEC);
if (FALSE == FL_SEM_IS_RELEASE(fl, &msg_sem))
{
if (tran_timeout++ >= 5)
{
tran_timeout = 0;
rym_process_params_init(RYM_STAGE_ESTABLISHING);
FL_LOCK_DELAY(fl, FL_CLOCK_SEC);
}
}
else
{
tran_timeout = 0;
/* read the length of the packet */
rym_recv_len = rym_sqqueue_read(&aPacketData[PACKET_START_INDEX], 1);
if (rym_recv_len == 1)
{
if (aPacketData[PACKET_START_INDEX] != RYM_CODE_EOT)
continue;
/* send an ACK */
aPacketData[0] = RYM_CODE_ACK;
rym_transmit(aPacketData, 1);
FL_LOCK_DELAY(fl, FL_CLOCK_100MSEC * 5);
/* send a C */
aPacketData[0] = RYM_CODE_C;
rym_transmit(aPacketData, 1);
FL_LOCK_WAIT_SEM_OR_TIMEOUT(fl, &msg_sem, FL_CLOCK_SEC);
if (FALSE == FL_SEM_IS_RELEASE(fl, &msg_sem))
continue;
/* read the length of the packet */
rym_recv_len = rym_sqqueue_read(&aPacketData[PACKET_START_INDEX], 1);
if (rym_recv_len == 1)
{
if (aPacketData[PACKET_START_INDEX] != RYM_CODE_SOH)
continue;
/* SOH/STX + seq + payload + crc */
rym_recv_len = rym_sqqueue_read(&aPacketData[PACKET_NUMBER_INDEX],
_RYM_SOH_PKG_SZ - 1);
if (rym_recv_len != (_RYM_SOH_PKG_SZ - 1))
continue;
/* sanity check */
if ((aPacketData[PACKET_NUMBER_INDEX] != 0) || (aPacketData[PACKET_CNUMBER_INDEX] != 0xFF))
continue;
recv_crc = (uint16_t)(*(&aPacketData[PACKET_START_INDEX] + _RYM_SOH_PKG_SZ - 2) << 8) |
*(&aPacketData[PACKET_START_INDEX] + _RYM_SOH_PKG_SZ - 1);
cal_crc = CRC16(aPacketData + PACKET_DATA_INDEX, _RYM_SOH_PKG_SZ - 5);
if (recv_crc != cal_crc)
continue;
/* we got a valid packet. invoke the callback if there is one. */
stage = RYM_STAGE_FINISHED;
aPacketData[0] = RYM_CODE_ACK;
rym_transmit(aPacketData, 1);
/* we already got one EOT in the caller. invoke the callback if there is
* one. */
if (rym_on_end)
rym_on_end(&aPacketData[PACKET_DATA_INDEX], PACKET_SIZE);
}
}
}
}
FL_TAIL(fl);
}
/**
* @brief Process the Ymodem protocol stages.
*
* This function is responsible for handling the different stages of the Ymodem protocol.
* It performs the necessary actions based on the current stage.
*
* @note The stages include establishing connection, transmitting data, finishing, and others.
*
* @param None
* @return None
*/
void rym_process(void)
{
switch (stage)
{
case RYM_STAGE_ESTABLISHING: // 建立连接 (Establishing connection)
rym_do_handshake_process(&handshake_fw);
break;
case RYM_STAGE_TRANSMITTING: // 传输中 (Transmitting)
rym_do_trans_process(&trans_fw);
break;
case RYM_STAGE_FINISHING: // 结束 (Finishing)
rym_do_finish_process(&finsh_fw);
break;
case RYM_STAGE_NONE:
rym_process_params_init(RYM_STAGE_ESTABLISHING);
break;
case RYM_STAGE_FINISHED:
rym_tm_sec = 0;
break;
default:
// rym_process_params_init(RYM_STAGE_ESTABLISHING);
break;
}
}
/**
* @brief Checks if the YMODEM receive timeout has occurred.
* @return TRUE if the timeout has occurred, FALSE otherwise.
*/
BOOL rym_timeout(void)
{
return rym_tm_sec == 0;
}
/**
* @brief Initializes the YMODEM protocol for receiving files.
*
* This function initializes the necessary data structures and variables
* for receiving files using the YMODEM protocol.
*
* @return TRUE if initialization is successful, FALSE otherwise.
*/
BOOL rym_init(void)
{
sqqueue_ctrl_init(&rym_sqqueue, sizeof(uint8_t), _RYM_PKG_SZ);
FL_INIT(&handshake_fw);
FL_INIT(&trans_fw);
FL_INIT(&finsh_fw);
return TRUE;
}
/**
* @brief Receive data using the Ymodem protocol.
*
* This function is used to receive data transmitted using the Ymodem protocol and store it in the specified buffer.
*
* @param p Pointer to the data storage buffer.
*/
uint16_t rym_receive(void *p, uint16_t size)
{
rym_sqqueue.string_enter(&rym_sqqueue, p, size);
FLOW_SEM_RELEASE(&msg_sem);
return 0;
}
/**
* @brief Configures the YMODEM protocol with the specified callbacks and handshake timeout.
*
* This function sets the callback functions for the YMODEM protocol, which will be called during different stages of the protocol.
* The `on_begin` callback is called when the YMODEM transfer begins.
* The `on_data` callback is called when a data packet is received during the transfer.
* The `on_end` callback is called when the YMODEM transfer ends.
* The `on_transmit` callback is called when a data packet needs to be transmitted during the transfer.
* The `handshake_timeout` parameter specifies the timeout duration for the handshake phase of the YMODEM protocol.
*
* @param on_begin The callback function to be called when the YMODEM transfer begins.
* @param on_data The callback function to be called when a data packet is received during the transfer.
* @param on_end The callback function to be called when the YMODEM transfer ends.
* @param on_transmit The callback function to be called when a data packet needs to be transmitted during the transfer.
* @param handshake_timeout The timeout duration for the handshake phase of the YMODEM protocol.
*
* @return TRUE if the configuration was successful, FALSE otherwise.
*/
BOOL rym_config(rym_callback on_begin, rym_callback on_data,
rym_callback on_end, rym_callback on_transmit,
int handshake_timeout)
{
rym_on_begin = on_begin;
rym_on_data = on_data;
rym_on_end = on_end;
rym_transmit = on_transmit;
tm_sec = handshake_timeout;
return TRUE;
}