| /**************************************************************************** |
| * 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 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; |
| } |