32e597a136
Signed-off-by: anjiahao <anjiahao@xiaomi.com>
627 lines
15 KiB
C
627 lines
15 KiB
C
/****************************************************************************
|
|
* apps/system/ymodem/ymodem.c
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership. The
|
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <termios.h>
|
|
|
|
#include <nuttx/crc16.h>
|
|
|
|
#include "ymodem.h"
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_SYSTEM_YMODEM_DEBUG_FILEPATH
|
|
# define ymodem_debug(...) \
|
|
do \
|
|
{ \
|
|
dprintf(ctx->debug_fd, ##__VA_ARGS__); \
|
|
fsync(ctx->debug_fd); \
|
|
} \
|
|
while(0)
|
|
|
|
#else
|
|
# define ymodem_debug(...)
|
|
#endif
|
|
|
|
#define SOH 0x01 /* Start of 128-byte data packet */
|
|
#define STX 0x02 /* Start of 1024-byte data packet */
|
|
#define EOT 0x04 /* End of transmission */
|
|
#define ACK 0x06 /* Acknowledge */
|
|
#define NAK 0x15 /* Negative acknowledge */
|
|
#define CAN 0x18 /* Two of these in succession aborts transfer */
|
|
#define CRC 0x43 /* 'C' == 0x43, request 16-bit CRC */
|
|
|
|
#define MAX_RETRIES 100
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
static int ymodem_recv_buffer(FAR struct ymodem_ctx_s *ctx, FAR uint8_t *buf,
|
|
size_t size)
|
|
{
|
|
size_t i = 0;
|
|
|
|
ymodem_debug("recv buffer data, read size is %zu\n", size);
|
|
while (i < size)
|
|
{
|
|
ssize_t ret = read(ctx->recvfd, buf + i, size - i);
|
|
if (ret >= 0)
|
|
{
|
|
ymodem_debug("recv buffer data, size %zd\n", ret);
|
|
i += ret;
|
|
}
|
|
else
|
|
{
|
|
ymodem_debug("recv buffer error, ret %d\n", -errno);
|
|
return -errno;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ymodem_send_buffer(FAR struct ymodem_ctx_s *ctx,
|
|
FAR const uint8_t *buf, size_t size)
|
|
{
|
|
size_t i = 0;
|
|
|
|
ymodem_debug("send buffer data, write size is %zu\n", size);
|
|
while (i < size)
|
|
{
|
|
ssize_t ret = write(ctx->sendfd, buf, size);
|
|
if (ret >= 0)
|
|
{
|
|
ymodem_debug("send buffer data, size %zd\n", ret);
|
|
i += ret;
|
|
}
|
|
else
|
|
{
|
|
ymodem_debug("send buffer error, ret %d\n", -errno);
|
|
return -errno;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ymodem_recv_packet(FAR struct ymodem_ctx_s *ctx)
|
|
{
|
|
uint16_t recv_crc;
|
|
uint16_t cal_crc;
|
|
int ret;
|
|
|
|
ret = ymodem_recv_buffer(ctx, ctx->header, 1);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
switch (ctx->header[0])
|
|
{
|
|
case SOH:
|
|
ctx->packet_size = YMODEM_PACKET_SIZE;
|
|
break;
|
|
case STX:
|
|
ctx->packet_size = YMODEM_PACKET_1K_SIZE;
|
|
break;
|
|
case EOT:
|
|
return -EAGAIN;
|
|
case CAN:
|
|
ret = ymodem_recv_buffer(ctx, ctx->header, 1);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
else if (ctx->header[0] == CAN)
|
|
{
|
|
return -ECANCELED;
|
|
}
|
|
|
|
default:
|
|
ymodem_debug("recv_packet: EBADMSG: header[0]=0x%x\n",
|
|
ctx->header[0]);
|
|
return -EBADMSG;
|
|
}
|
|
|
|
ret = ymodem_recv_buffer(ctx, &ctx->header[1],
|
|
2 + ctx->packet_size + 2);
|
|
if (ret < 0)
|
|
{
|
|
ymodem_debug("recv_packet: err=%d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
if ((ctx->header[1] + ctx->header[2]) != 0xff)
|
|
{
|
|
ymodem_debug("recv_packet: EILSEQ seq[]=%d %d\n",
|
|
ctx->header[1], ctx->header[2]);
|
|
return -EILSEQ;
|
|
}
|
|
|
|
recv_crc = (ctx->data[ctx->packet_size] << 8) +
|
|
ctx->data[ctx->packet_size + 1];
|
|
cal_crc = crc16(ctx->data, ctx->packet_size);
|
|
if (cal_crc != recv_crc)
|
|
{
|
|
ymodem_debug("recv_packet: EBADMSG rcev:cal=0x%x 0x%x\n",
|
|
recv_crc, cal_crc);
|
|
return -EBADMSG;
|
|
}
|
|
|
|
ymodem_debug("recv_packet:OK: size=%d, seq=%d\n",
|
|
ctx->packet_size, ctx->header[1]);
|
|
return 0;
|
|
}
|
|
|
|
static int ymodem_recv_file(FAR struct ymodem_ctx_s *ctx)
|
|
{
|
|
FAR char *str = NULL;
|
|
uint32_t total_seq = 0;
|
|
int retries = 0;
|
|
int ret;
|
|
|
|
ctx->header[0] = CRC;
|
|
recv_packet:
|
|
ymodem_send_buffer(ctx, ctx->header, 1);
|
|
ret = ymodem_recv_packet(ctx);
|
|
if (ret == -ECANCELED)
|
|
{
|
|
ymodem_debug("recv_file: canceled by sender\n");
|
|
goto cancel;
|
|
}
|
|
else if (ret == -EAGAIN)
|
|
{
|
|
ctx->header[0] = ACK;
|
|
ymodem_send_buffer(ctx, ctx->header, 1);
|
|
ymodem_debug("recv_file: finished one file transfer\n");
|
|
ctx->header[0] = CRC;
|
|
total_seq = 0;
|
|
goto recv_packet;
|
|
}
|
|
else if (ret < 0)
|
|
{
|
|
/* other errors, like ETIMEDOUT, EILSEQ, EBADMSG... */
|
|
|
|
if (++retries > MAX_RETRIES)
|
|
{
|
|
ymodem_debug("recv_file: too many errors, cancel!!\n");
|
|
goto cancel;
|
|
}
|
|
|
|
/* Use str to mask transfer start */
|
|
|
|
ctx->header[0] = str ? NAK : CRC;
|
|
goto recv_packet;
|
|
}
|
|
|
|
if ((total_seq & 0xff) - 1 == ctx->header[1])
|
|
{
|
|
ymodem_debug("recv_file: Received the previous packet that has"
|
|
"been received, continue %lu %u\n", total_seq,
|
|
ctx->header[1]);
|
|
|
|
ctx->header[0] = ACK;
|
|
goto recv_packet;
|
|
}
|
|
else if ((total_seq & 0xff) != ctx->header[1])
|
|
{
|
|
ymodem_debug("recv_file: total seq error:%lu %u\n", total_seq,
|
|
ctx->header[1]);
|
|
ctx->header[0] = CRC;
|
|
goto recv_packet;
|
|
}
|
|
|
|
/* File name packet */
|
|
|
|
if (total_seq == 0)
|
|
{
|
|
/* Filename packet is empty, end session */
|
|
|
|
if (ctx->data[0] == '\0')
|
|
{
|
|
/* Last file done, so the session also finished */
|
|
|
|
ymodem_debug("recv_file: session finished\n");
|
|
ctx->header[0] = ACK;
|
|
ymodem_send_buffer(ctx, ctx->header, 1);
|
|
return 0;
|
|
}
|
|
|
|
str = (FAR char *)ctx->data;
|
|
ctx->packet_type = YMODEM_FILENAME_PACKET;
|
|
strlcpy(ctx->file_name, str, PATH_MAX);
|
|
str += strlen(str) + 1;
|
|
ctx->file_length = atoi(str);
|
|
ymodem_debug("recv_file: new file %s(%zu) start\n", ctx->file_name,
|
|
ctx->file_length);
|
|
ret = ctx->packet_handler(ctx);
|
|
if (ret < 0)
|
|
{
|
|
ymodem_debug("recv_file: handler err for file name packet:"
|
|
" ret=%d\n", ret);
|
|
goto cancel;
|
|
}
|
|
|
|
ctx->header[0] = ACK;
|
|
ymodem_send_buffer(ctx, ctx->header, 1);
|
|
ctx->header[0] = CRC;
|
|
total_seq++;
|
|
goto recv_packet;
|
|
}
|
|
|
|
/* data packet */
|
|
|
|
ctx->packet_type = YMODEM_DATA_PACKET;
|
|
ret = ctx->packet_handler(ctx);
|
|
if (ret < 0)
|
|
{
|
|
ymodem_debug("recv_file: handler err for data packet: ret=%d\n", ret);
|
|
goto cancel;
|
|
}
|
|
|
|
ctx->header[0] = ACK;
|
|
total_seq++;
|
|
ymodem_debug("recv_file: recv data success\n");
|
|
goto recv_packet;
|
|
|
|
cancel:
|
|
ctx->header[0] = CAN;
|
|
ymodem_send_buffer(ctx, ctx->header, 1);
|
|
ymodem_send_buffer(ctx, ctx->header, 1);
|
|
ymodem_debug("recv_file: cancel command sent to sender\n");
|
|
return ret;
|
|
}
|
|
|
|
static int ymodem_recv_cmd(FAR struct ymodem_ctx_s *ctx, uint8_t cmd)
|
|
{
|
|
int ret;
|
|
|
|
ret = ymodem_recv_buffer(ctx, ctx->header, 1);
|
|
if (ret < 0)
|
|
{
|
|
ymodem_debug("recv cmd error\n");
|
|
return ret;
|
|
}
|
|
|
|
if (ctx->header[0] == NAK)
|
|
{
|
|
return -EAGAIN;
|
|
}
|
|
|
|
if (ctx->header[0] != cmd)
|
|
{
|
|
ymodem_debug("recv cmd error, must 0x%x, but receive 0x%x\n",
|
|
cmd, ctx->header[0]);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ymodem_send_file(FAR struct ymodem_ctx_s *ctx)
|
|
{
|
|
uint16_t crc;
|
|
int retries;
|
|
int ret;
|
|
|
|
ymodem_debug("waiting handshake\n");
|
|
for (retries = 0; retries < MAX_RETRIES; retries++)
|
|
{
|
|
ret = ymodem_recv_cmd(ctx, CRC);
|
|
if (ret >= 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (retries >= MAX_RETRIES)
|
|
{
|
|
ymodem_debug("waiting handshake error\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
ymodem_debug("ymodem send file start\n");
|
|
send_start:
|
|
ctx->packet_type = YMODEM_FILENAME_PACKET;
|
|
ret = ctx->packet_handler(ctx);
|
|
if (ret < 0)
|
|
{
|
|
goto send_last;
|
|
}
|
|
|
|
ymodem_debug("sendfile filename:%s filelength:%zu\n",
|
|
ctx->file_name, ctx->file_length);
|
|
sprintf((FAR char *)ctx->data, "%s%c%zu", ctx->file_name,
|
|
'\0', ctx->file_length);
|
|
ctx->header[0] = SOH;
|
|
ctx->header[1] = 0x00;
|
|
ctx->header[2] = 0xff;
|
|
ctx->packet_size = YMODEM_PACKET_SIZE;
|
|
crc = crc16(ctx->data, ctx->packet_size);
|
|
ctx->data[ctx->packet_size] = crc >> 8;
|
|
ctx->data[ctx->packet_size + 1] = crc;
|
|
|
|
send_name:
|
|
ret = ymodem_send_buffer(ctx, ctx->header, 3 + ctx->packet_size + 2);
|
|
if (ret < 0)
|
|
{
|
|
ymodem_debug("send name packet error\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = ymodem_recv_cmd(ctx, ACK);
|
|
if (ret == -EAGAIN)
|
|
{
|
|
ymodem_debug("send name packet recv NAK, need send again\n");
|
|
goto send_name;
|
|
}
|
|
|
|
if (ret < 0)
|
|
{
|
|
ymodem_debug("send name packet, recv error cmd\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = ymodem_recv_cmd(ctx, CRC);
|
|
if (ret == -EAGAIN)
|
|
{
|
|
ymodem_debug("send name packet recv NAK, need send again\n");
|
|
goto send_name;
|
|
}
|
|
|
|
if (ret < 0)
|
|
{
|
|
ymodem_debug("send name packet, recv error cmd\n");
|
|
return ret;
|
|
}
|
|
|
|
ctx->packet_type = YMODEM_DATA_PACKET;
|
|
send_packet:
|
|
if (ctx->file_length <= YMODEM_PACKET_SIZE)
|
|
{
|
|
ctx->header[0] = SOH;
|
|
ctx->packet_size = YMODEM_PACKET_SIZE;
|
|
}
|
|
else
|
|
{
|
|
ctx->header[0] = STX;
|
|
ctx->packet_size = YMODEM_PACKET_1K_SIZE;
|
|
}
|
|
|
|
ymodem_debug("packet_size is %zu\n", ctx->packet_size);
|
|
ctx->header[1]++;
|
|
ctx->header[2]--;
|
|
ret = ctx->packet_handler(ctx);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
crc = crc16(ctx->data, ctx->packet_size);
|
|
ctx->data[ctx->packet_size] = crc >> 8;
|
|
ctx->data[ctx->packet_size + 1] = crc;
|
|
send_packet_again:
|
|
ret = ymodem_send_buffer(ctx, ctx->header, 3 + ctx->packet_size + 2);
|
|
if (ret < 0)
|
|
{
|
|
ymodem_debug("send data packet error\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = ymodem_recv_cmd(ctx, ACK);
|
|
if (ret == -EAGAIN)
|
|
{
|
|
ymodem_debug("send data packet recv NAK, need send again\n");
|
|
goto send_packet_again;
|
|
}
|
|
|
|
if (ret < 0)
|
|
{
|
|
ymodem_debug("send data packet, recv error\n");
|
|
return ret;
|
|
}
|
|
|
|
if (ctx->file_length != 0)
|
|
{
|
|
ymodem_debug("The remain bytes sent are %zu\n", ctx->file_length);
|
|
goto send_packet;
|
|
}
|
|
|
|
send_eot:
|
|
ctx->header[0] = EOT;
|
|
ret = ymodem_send_buffer(ctx, ctx->header, 1);
|
|
if (ret < 0)
|
|
{
|
|
ymodem_debug("send EOT error\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = ymodem_recv_cmd(ctx, ACK);
|
|
if (ret == -EAGAIN)
|
|
{
|
|
ymodem_debug("send EOT recv NAK, need send again\n");
|
|
goto send_eot;
|
|
}
|
|
|
|
if (ret < 0)
|
|
{
|
|
ymodem_debug("send EOT, recv ACK error\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = ymodem_recv_cmd(ctx, CRC);
|
|
if (ret == -EAGAIN)
|
|
{
|
|
ymodem_debug("send EOT recv NAK, need send again\n");
|
|
goto send_eot;
|
|
}
|
|
|
|
if (ret < 0)
|
|
{
|
|
ymodem_debug("send EOT, recv CRC error\n");
|
|
return ret;
|
|
}
|
|
|
|
goto send_start;
|
|
|
|
send_last:
|
|
ctx->header[0] = SOH;
|
|
ctx->header[1] = 0x00;
|
|
ctx->header[2] = 0xff;
|
|
ctx->packet_type = YMODEM_DATA_PACKET;
|
|
ctx->packet_size = YMODEM_PACKET_SIZE;
|
|
memset(ctx->data, 0, YMODEM_PACKET_SIZE);
|
|
crc = crc16(ctx->data, ctx->packet_size);
|
|
ctx->data[ctx->packet_size] = crc >> 8;
|
|
ctx->data[ctx->packet_size + 1] = crc;
|
|
send_last_again:
|
|
ret = ymodem_send_buffer(ctx, ctx->header, 3 + ctx->packet_size + 2);
|
|
if (ret < 0)
|
|
{
|
|
ymodem_debug("send last packet error\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = ymodem_recv_cmd(ctx, ACK);
|
|
if (ret == -EAGAIN)
|
|
{
|
|
ymodem_debug("send last packet, need send again\n");
|
|
goto send_last_again;
|
|
}
|
|
|
|
if (ret < 0)
|
|
{
|
|
ymodem_debug("send last packet, recv error\n");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
int ymodem_recv(FAR struct ymodem_ctx_s *ctx)
|
|
{
|
|
struct termios saveterm;
|
|
struct termios term;
|
|
int ret;
|
|
|
|
if (ctx == NULL || ctx->packet_handler == NULL)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
ctx->header = calloc(1, 3 + YMODEM_PACKET_1K_SIZE + 2);
|
|
if (ctx->header == NULL)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ctx->data = ctx->header + 3;
|
|
#ifdef CONFIG_SYSTEM_YMODEM_DEBUG_FILEPATH
|
|
ctx->debug_fd = open(CONFIG_SYSTEM_YMODEM_DEBUG_FILEPATH,
|
|
O_CREAT | O_TRUNC | O_WRONLY, 0666);
|
|
if (ctx->debug_fd < 0)
|
|
{
|
|
free(ctx->header);
|
|
return -errno;
|
|
}
|
|
#endif
|
|
|
|
tcgetattr(ctx->recvfd, &term);
|
|
memcpy(&saveterm, &term, sizeof(struct termios));
|
|
cfmakeraw(&term);
|
|
term.c_cc[VTIME] = 30;
|
|
term.c_cc[VMIN] = 255;
|
|
tcsetattr(ctx->recvfd, TCSANOW, &term);
|
|
|
|
ret = ymodem_recv_file(ctx);
|
|
|
|
tcsetattr(ctx->recvfd, TCSANOW, &saveterm);
|
|
#ifdef CONFIG_SYSTEM_YMODEM_DEBUG_FILEPATH
|
|
close(ctx->debug_fd);
|
|
#endif
|
|
|
|
free(ctx->header);
|
|
return ret;
|
|
}
|
|
|
|
int ymodem_send(FAR struct ymodem_ctx_s *ctx)
|
|
{
|
|
struct termios saveterm;
|
|
struct termios term;
|
|
int ret;
|
|
|
|
if (ctx == NULL || ctx->packet_handler == NULL)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
ctx->header = calloc(1, 3 + YMODEM_PACKET_1K_SIZE + 2);
|
|
if (ctx->header == NULL)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ctx->data = ctx->header + 3;
|
|
#ifdef CONFIG_SYSTEM_YMODEM_DEBUG_FILEPATH
|
|
ctx->debug_fd = open(CONFIG_SYSTEM_YMODEM_DEBUG_FILEPATH,
|
|
O_CREAT | O_TRUNC | O_WRONLY, 0666);
|
|
if (ctx->debug_fd < 0)
|
|
{
|
|
free(ctx->header);
|
|
return -errno;
|
|
}
|
|
#endif
|
|
|
|
tcgetattr(ctx->recvfd, &term);
|
|
memcpy(&saveterm, &term, sizeof(struct termios));
|
|
cfmakeraw(&term);
|
|
tcsetattr(ctx->recvfd, TCSANOW, &term);
|
|
|
|
ret = ymodem_send_file(ctx);
|
|
if (ret < 0)
|
|
{
|
|
ymodem_debug("ymodem send file error, ret:%d\n", ret);
|
|
}
|
|
|
|
tcsetattr(ctx->recvfd, TCSANOW, &saveterm);
|
|
#ifdef CONFIG_SYSTEM_YMODEM_DEBUG_FILEPATH
|
|
close(ctx->debug_fd);
|
|
#endif
|
|
|
|
free(ctx->header);
|
|
return ret;
|
|
}
|