/**************************************************************************** * 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 #include #include #include #include #include #include #include #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 STC 0x03 /* Start of custom 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 STC: ctx->packet_size = ctx->custom_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... */ tcflush(ctx->recvfd, TCIOFLUSH); 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 %" PRIu32 " %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:%" PRIu32 " %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"); retries = 0; 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 if (ctx->custom_size != 0) { ctx->header[0] = STC; ctx->packet_size = ctx->custom_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; } if (ctx->custom_size != 0) { ctx->header = calloc(1, 3 + ctx->custom_size + 2); } else { 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] = 15; 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; } if (ctx->custom_size != 0) { ctx->header = calloc(1, 3 + ctx->custom_size + 2); } else { 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; }