diff --git a/.clang-format b/.clang-format index 682ae09..d896a25 100644 --- a/.clang-format +++ b/.clang-format @@ -3,3 +3,7 @@ IndentWidth: 4 AllowShortFunctionsOnASingleLine: None KeepEmptyLinesAtTheStartOfBlocks: false BreakBeforeBraces: Allman +IndentCaseLabels: true +ColumnLimit: 144 +SortIncludes: false +SkipMacroDefinitionBody: true diff --git a/README.md b/README.md index e3ab066..0e33721 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ when used in combination with [tmux](https://tmux.github.io). * Useful for reconnecting when serial device has no serial device by ID * Support for non-standard baud rates * Support for mark and space parity - * X-modem (1K/CRC) and Y-modem file upload + * X-modem (1K/CRC/Checksum) and Y-modem file upload * Support for RS-485 mode * List available serial devices * By device @@ -431,7 +431,7 @@ Returns the `tio` table. Send file using x/y-modem protocol. -Protocol can be any of `XMODEM_1K`, `XMODEM_CRC`, `YMODEM`. +Protocol can be any of `XMODEM_1K`, `XMODEM_CRC`, `XMODEM_SUM`, `YMODEM`. #### `tio.ttysearch()` diff --git a/man/tio.1.in b/man/tio.1.in index eb0338d..8caeefb 100644 --- a/man/tio.1.in +++ b/man/tio.1.in @@ -205,8 +205,8 @@ Strip control characters and escape sequences from log. .TP .BR \-m ", " "\-\-map " \fI -Map (replace, translate) characters on input to the serial device or output -from the serial device. The following mapping flags are supported: +Map (replace, translate) characters on input from the serial device or output +to the serial device. The following mapping flags are supported: .RS .TP 12n @@ -426,7 +426,7 @@ Toggle line timestamp mode .IP "\fBctrl-t v" Show version .IP "\fBctrl-t x" -Send file using the XMODEM-1K or XMODEM-CRC protocol (prompts for file name and protocol) +Send file using the XMODEM-1K or XMODEM-CRC or XMODEM-SUM protocol (prompts for file name and protocol) .IP "\fBctrl-t y" Send file using the YMODEM protocol (prompts for file name) .IP "\fBctrl-t ctrl-t" @@ -468,7 +468,7 @@ Returns the tio table. .IP "\fBtio.send(file, protocol)" Send file using x/y-modem protocol. -Protocol can be any of XMODEM_1K, XMODEM_CRC, YMODEM. +Protocol can be any of XMODEM_1K, XMODEM_CRC, XMODEM_SUM, YMODEM. .IP "\fBtio.ttysearch()" Search for serial devices. diff --git a/man/tio.1.txt b/man/tio.1.txt index eeac82c..c1efbbc 100644 --- a/man/tio.1.txt +++ b/man/tio.1.txt @@ -158,7 +158,7 @@ OPTIONS -m, --map - Map (replace, translate) characters on input to the serial device or output from the serial device. The following mapping flags are supported: + Map (replace, translate) characters on input from the serial device or output to the serial device. The following mapping flags are supported: ICRNL Map CR to NL on input (unless IGNCR is set) @@ -342,7 +342,7 @@ KEY COMMANDS ctrl-t v Show version - ctrl-t x Send file using the XMODEM-1K or XMODEM-CRC protocol (prompts for file name and protocol) + ctrl-t x Send file using the XMODEM-1K or XMODEM-CRC or XMODEM-SUM protocol (prompts for file name and protocol) ctrl-t y Send file using the YMODEM protocol (prompts for file name) @@ -382,7 +382,7 @@ SCRIPT API send(file, protocol) Send file using x/y-modem protocol. - Protocol can be any of XMODEM_1K, XMODEM_CRC, YMODEM. + Protocol can be any of XMODEM_1K, XMODEM_CRC, XMODEM_SUM, YMODEM. tty_search() Search for serial devices. diff --git a/src/main.c b/src/main.c index 6676bb3..8434caa 100644 --- a/src/main.c +++ b/src/main.c @@ -68,7 +68,7 @@ int main(int argc, char *argv[]) /* Configure input terminal */ if (isatty(fileno(stdin))) { - stdin_configure(); + stdin_configure(); } else { @@ -110,7 +110,8 @@ int main(int argc, char *argv[]) if (interactive_mode) { tio_printf("Press ctrl-%c q to quit", option.prefix_key); - } else + } + else { tio_printf("Non-interactive mode enabled"); tio_printf("Press ctrl-c to quit"); diff --git a/src/misc.c b/src/misc.c index bd0a429..271d266 100644 --- a/src/misc.c +++ b/src/misc.c @@ -98,12 +98,17 @@ int read_poll(int fd, void *data, size_t len, int timeout) if (fds.revents & POLLIN) { // Read ready data + // return value should not be 0 return read(fd, data, len); } + else /* if (fds.revents & (POLLERR | POLLHUP | POLLNVAL)) */ + { + return -1; + } } /* Timeout */ - return ret; + return 0; } // Function to calculate djb2 hash of string @@ -170,6 +175,7 @@ bool match_patterns(const char *string, const char *patterns) pattern = strtok(patterns_copy, ","); while (pattern != NULL) { + // clang-format off // Check if the string matches the current pattern #ifdef FNM_EXTMATCH if (fnmatch(pattern, string, FNM_EXTMATCH) == 0) @@ -180,6 +186,7 @@ bool match_patterns(const char *string, const char *patterns) free(patterns_copy); return true; } + // clang-format on // Move to the next pattern pattern = strtok(NULL, ","); diff --git a/src/options.c b/src/options.c index acff7ce..beddd90 100644 --- a/src/options.c +++ b/src/options.c @@ -61,6 +61,7 @@ enum opt_t OPT_EXEC, }; +// clang-format off /* Default options */ struct option_t option = { @@ -125,6 +126,7 @@ struct option_t option = .map_i_msb2lsb = false, .map_o_ign_cr = false, }; +// clang-format on void option_print_help(char *argv[]) { @@ -833,12 +835,14 @@ void options_print() tio_printf(" Output line delay: %d", option.output_line_delay); tio_printf(" Automatic connect strategy: %s", option_auto_connect_state_to_string(option.auto_connect)); tio_printf(" Automatic reconnect: %s", option.no_reconnect ? "true" : "false"); + // clang-format off tio_printf(" Pulse duration: DTR=%d RTS=%d CTS=%d DSR=%d DCD=%d RI=%d", option.dtr_pulse_duration, option.rts_pulse_duration, option.cts_pulse_duration, option.dsr_pulse_duration, option.dcd_pulse_duration, option.ri_pulse_duration); + // clang-format on tio_printf(" Input mode: %s", option_input_mode_to_string(option.input_mode)); tio_printf(" Output mode: %s", option_output_mode_to_string(option.output_mode)); tio_printf(" Alert: %s", option_alert_state_to_string(option.alert)); @@ -887,8 +891,9 @@ void options_parse(int argc, char *argv[]) option.vt100 = true; } - while (1) + while (true) { + // clang-format off static struct option long_options[] = { {"baudrate", required_argument, 0, 'b' }, @@ -932,6 +937,7 @@ void options_parse(int argc, char *argv[]) {"complete-profiles", no_argument, 0, OPT_COMPLETE_PROFILES }, {0, 0, 0, 0 } }; + // clang-format on /* getopt_long stores the option index here */ int option_index = 0; @@ -1131,7 +1137,7 @@ void options_parse(int argc, char *argv[]) /* Assume first non-option is the target (tty device, profile, tid) */ if (strcmp(option.target, "")) { - optind++; + optind++; } else if (optind < argc) { @@ -1176,6 +1182,7 @@ void options_parse_final(int argc, char *argv[]) #ifdef __CYGWIN__ unsigned char portnum; char *tty_win; + // clang-format off if ( ((strncmp("COM", option.target, 3) == 0) || (strncmp("com", option.target, 3) == 0) ) && (sscanf(option.target + 3, "%hhu", &portnum) == 1) @@ -1184,5 +1191,6 @@ void options_parse_final(int argc, char *argv[]) asprintf(&tty_win, "/dev/ttyS%hhu", portnum - 1); option.target = tty_win; } + // clang-format on #endif } diff --git a/src/readline.c b/src/readline.c index 8176a0b..ecb382d 100644 --- a/src/readline.c +++ b/src/readline.c @@ -90,7 +90,7 @@ static void readline_input_cr(void) rl_history_count++; } else - { + { free(rl_history[0]); memmove(&rl_history[0], &rl_history[1], (RL_HISTORY_MAX - 1) * sizeof(char*)); rl_history[RL_HISTORY_MAX - 1] = strndup(rl_line, rl_line_length); @@ -139,6 +139,7 @@ static void readline_input_left_bracket(void) } else { + readline_input_char('['); rl_escape = 0; } } diff --git a/src/script.c b/src/script.c index b69d55b..cb104bd 100644 --- a/src/script.c +++ b/src/script.c @@ -45,6 +45,7 @@ static int device_fd; +// clang-format off static char script_init[] = "tio.set = function(arg)\n" " local dtr = arg.DTR or -1\n" @@ -71,6 +72,7 @@ static char script_init[] = "end\n" "tio.alwaysecho = true\n" "setmetatable(tio, tio)\n"; +// clang-format on static bool alwaysecho(lua_State *L) { @@ -100,7 +102,9 @@ static int api_echo(lua_State *L) log_printf("\n[%s] %s", pTimeStampNow, str); } } - } else { + } + else + { for (size_t i=0; i= '0' && c <= '9') + if (c >= '0' && c <= '9') { return c - '0'; } @@ -257,22 +259,23 @@ ssize_t tty_write(int fd, const void *buffer, size_t count) { ssize_t retval = 0, bytes_written = 0; size_t i; + unsigned char *cbuf = (unsigned char *)buffer; if (option.map_o_ltu) { // Convert lower case to upper case - for (i = 0; i 0; size--) { + for (crc = 0; size > 0; size--) + { s = *data++ ^ (crc >> 8); s ^= (s >> 4); crc = (crc << 8) ^ s ^ (s << 5) ^ (s << 12); @@ -71,291 +94,10 @@ static uint16_t crc16(const uint8_t *data, uint16_t size) return crc; } -static int xmodem_1k(int sio, const void *data, size_t len, int seq) -{ - struct xpacket_1k packet; - const uint8_t *buf = data; - char resp = 0; - int rc, crc; - - /* Drain pending characters from serial line. Insist on the - * last drained character being 'C'. - */ - while(1) { - if (key_hit) - return -1; - rc = read_poll(sio, &resp, 1, 50); - if (rc == 0) { - if (resp == 'C') break; - if (resp == CAN) return ERR; - continue; - } - else if (rc < 0) { - tio_error_print("Read sync from serial failed"); - return ERR; - } - } - - /* Always work with 1K packets */ - packet.seq = seq; - packet.type = STX; - - while (len) { - size_t sz, z = 0; - char *from, status; - - /* Build next packet, pad with 0 to full seq */ - z = min(len, sizeof(packet.data)); - memcpy(packet.data, buf, z); - memset(packet.data + z, 0, sizeof(packet.data) - z); - crc = crc16(packet.data, sizeof(packet.data)); - packet.crc_hi = crc >> 8; - packet.crc_lo = crc; - packet.nseq = 0xff - packet.seq; - - /* Send packet */ - from = (char *) &packet; - sz = sizeof(packet); - while (sz) { - if (key_hit) - return ERR; - if ((rc = write(sio, from, sz)) < 0 ) { - if (errno == EWOULDBLOCK) { - usleep(1000); - continue; - } - tio_error_print("Write packet to serial failed"); - return ERR; - } - from += rc; - sz -= rc; - } - - /* Clear response */ - resp = 0; - - /* 'lrzsz' does not ACK ymodem's fin packet */ - if (seq == 0 && packet.data[0] == 0) resp = ACK; - - /* Read receiver response, timeout 1 s */ - for(int n=0; n < 20; n++) { - if (key_hit) - return ERR; - rc = read_poll(sio, &resp, 1, 50); - if (rc < 0) { - tio_error_print("Read ack/nak from serial failed"); - return ERR; - } else if(rc > 0) { - break; - } - } - - /* Update "progress bar" */ - switch (resp) { - case NAK: status = 'N'; break; - case ACK: status = '.'; break; - case 'C': status = 'C'; break; - case CAN: status = '!'; return ERR; - default: status = '?'; - } - write(STDOUT_FILENO, &status, 1); - - /* Move to next block after ACK */ - if (resp == ACK) { - packet.seq++; - len -= z; - buf += z; - } - } - - /* Send EOT at 1 Hz until ACK or CAN received */ - while (seq) { - if (key_hit) - return ERR; - if (write(sio, EOT_STR, 1) < 0) { - tio_error_print("Write EOT to serial failed"); - return ERR; - } - write(STDOUT_FILENO, "|", 1); - /* 1s timeout */ - rc = read_poll(sio, &resp, 1, 1000); - if (rc < 0) { - tio_error_print("Read from serial failed"); - return ERR; - } else if(rc == 0) { - continue; - } - if (resp == ACK || resp == CAN) { - write(STDOUT_FILENO, "\r\n", 2); - return (resp == ACK) ? OK : ERR; - } - } - return 0; /* not reached */ -} - -static int xmodem(int sio, const void *data, size_t len) -{ - struct xpacket packet; - const uint8_t *buf = data; - char resp = 0; - int rc, crc; - - /* Drain pending characters from serial line. Insist on the - * last drained character being 'C'. - */ - while(1) { - if (key_hit) - return -1; - rc = read_poll(sio, &resp, 1, 50); - if (rc == 0) { - if (resp == 'C') break; - if (resp == CAN) return ERR; - continue; - } - else if (rc < 0) { - tio_error_print("Read sync from serial failed"); - return ERR; - } - } - - /* Always work with 128b packets */ - packet.seq = 1; - packet.type = SOH; - - while (len) { - size_t sz, z = 0; - char *from, status; - - /* Build next packet, pad with 0 to full seq */ - z = min(len, sizeof(packet.data)); - memcpy(packet.data, buf, z); - memset(packet.data + z, 0, sizeof(packet.data) - z); - crc = crc16(packet.data, sizeof(packet.data)); - packet.crc_hi = crc >> 8; - packet.crc_lo = crc; - packet.nseq = 0xff - packet.seq; - - /* Send packet */ - from = (char *) &packet; - sz = sizeof(packet); - while (sz) { - if (key_hit) - return ERR; - if ((rc = write(sio, from, sz)) < 0 ) { - if (errno == EWOULDBLOCK) { - usleep(1000); - continue; - } - tio_error_print("Write packet to serial failed"); - return ERR; - } - from += rc; - sz -= rc; - } - - /* Clear response */ - resp = 0; - - /* Read receiver response, timeout 1 s */ - for(int n=0; n < 20; n++) { - if (key_hit) - return ERR; - rc = read_poll(sio, &resp, 1, 50); - if (rc < 0) { - tio_error_print("Read ack/nak from serial failed"); - return ERR; - } else if(rc > 0) { - break; - } - } - - /* Update "progress bar" */ - switch (resp) { - case NAK: status = 'N'; break; - case ACK: status = '.'; break; - case 'C': status = 'C'; break; - case CAN: status = '!'; return ERR; - default: status = '?'; - } - write(STDOUT_FILENO, &status, 1); - - /* Move to next block after ACK */ - if (resp == ACK) { - packet.seq++; - len -= z; - buf += z; - } - } - - /* Send EOT at 1 Hz until ACK or CAN received */ - while (1) { - if (key_hit) - return ERR; - if (write(sio, EOT_STR, 1) < 0) { - tio_error_print("Write EOT to serial failed"); - return ERR; - } - write(STDOUT_FILENO, "|", 1); - /* 1s timeout */ - rc = read_poll(sio, &resp, 1, 1000); - if (rc < 0) { - tio_error_print("Read from serial failed"); - return ERR; - } else if(rc == 0) { - continue; - } - if (resp == ACK || resp == CAN) { - write(STDOUT_FILENO, "\r\n", 2); - return (resp == ACK) ? OK : ERR; - } - } - return 0; /* not reached */ -} - -int start_receive(int sio) -{ - int rc; - struct pollfd fds; - fds.events = POLLIN; - fds.fd = sio; - for (int n = 0; n < 20; n++) - { - /* Send the 'C' byte until the sender of the file responds with - something. The start character will be sent once a second for a number of - seconds. If nothing is received in that time then return false to indicate - that the transfer did not start. */ - rc = write(sio, "C", 1); - if (rc < 0) { - if (errno == EWOULDBLOCK) { - usleep(1000); - continue; - } - tio_error_print("Write packet to serial failed"); - return ERR; - } - /* Wait until data is available */ - rc = poll(&fds, 1, 3000); - if (rc < 0) - { - tio_error_print("%s", strerror(errno)); - return rc; - } - else if (rc > 0) - { - if (fds.revents & POLLIN) - { - return rc; - } - } - if (key_hit) - return USER_CAN; - } - return rc; -} - -uint16_t update_CRC(uint16_t crc, char data_char) +static uint16_t update_crc16(uint16_t crc, char data_char) { uint8_t data = data_char; + crc = crc ^ ((uint16_t)data << 8); for (int ix = 0; (ix < 8); ix++) { @@ -371,7 +113,459 @@ uint16_t update_CRC(uint16_t crc, char data_char) return crc; } -int receive_packet(int sio, struct xpacket packet, int fd) +static uint8_t calculate_cksum8(const uint8_t *data, uint16_t size) +{ + uint8_t sum; + for (sum = 0; size > 0; size--) { + sum += *data++; + } + return sum; +} + +/* + * Drain pending characters from serial line. Insist on the + * last drained character being initial character 'C':CRC,1K + */ +static int xmsend_initial_handshake(int sio, char init_ch) +{ + int rc; + char resp = 0; + + /* Wait for initial character */ + while (true) + { + if (key_hit) + return ERR_USER_CAN; + + rc = read_poll(sio, &resp, 1, 50); + if (rc == 0) + { + /* timeout 50 ms + resp has last received character beacuse read_poll() doesn't + destroy resp value in this case. */ + if (resp == init_ch) break; + if (resp == CAN) return ERR; + continue; + } + else if (rc < 0) + { + tio_error_print("Read sync from serial failed"); + return ERR; + } + } + return OK; +} + +/* + * Read receiver response, timeout 1 s + */ +static int xmsend_wait_response(int sio, char *resp, char tmo_resp) +{ + int rc; + + for (int n = 0; n < 20; n++) + { + if (key_hit) + return ERR_USER_CAN; + + rc = read_poll(sio, resp, 1, 50); + if (rc < 0) + { + tio_error_print("Read ack/nak from serial failed"); + return ERR; + } + else if (rc > 0) + { + /* response received */ + return OK; + } + } + /* no response time-out */ + *resp = tmo_resp; + return OK; +} + +/* + * Send EOT at 1 Hz until ACK or CAN received + */ +static int xmsend_repeat_eot_and_wait_response(int sio) +{ + int rc; + char resp; + + while (true) + { + if (key_hit) + return ERR_USER_CAN; + + if (write(sio, EOT_STR, 1) < 0) + { + tio_error_print("Write EOT to serial failed"); + return ERR; + } + write(STDOUT_FILENO, "|", 1); + /* 1s timeout */ + rc = read_poll(sio, &resp, 1, 1000); + if (rc < 0) + { + tio_error_print("Read from serial failed"); + return ERR; + } + else if (rc == 0) + { + continue; + } + if (resp == ACK || resp == CAN) + { + write(STDOUT_FILENO, "\r\n", 2); + return (resp == ACK) ? OK : ERR; + } + } + return OK; /* not reached */ +} + +static int xmodem_send_1k(int sio, const void *data, size_t len, int seq) +{ + struct xpacket_1k packet; + const uint8_t *buf = data; + char resp = 0, tmo_resp; + int rc, crc, err; + + /* Drain pending characters from serial line. + Insist on the last drained character being 'C' */ + err = xmsend_initial_handshake(sio, 'C'); + if (err != OK) + { + return err; + } + + /* Always work with 1K packets */ + packet.hdr.seq = seq; + packet.hdr.type = STX; + + while (len) + { + size_t sz, z = 0; + char *from, status; + + /* Build next packet, pad with 0 to full seq */ + z = min(len, sizeof(packet.data)); + memcpy(packet.data, buf, z); + memset(packet.data + z, 0, sizeof(packet.data) - z); + crc = calculate_crc16(packet.data, sizeof(packet.data)); + packet.ftr.crc_hi = crc >> 8; + packet.ftr.crc_lo = crc; + packet.hdr.nseq = 0xff - packet.hdr.seq; + + /* Send packet */ + from = (char *) &packet; + sz = sizeof(packet); + while (sz) + { + if (key_hit) + return ERR_USER_CAN; + + if ((rc = write(sio, from, sz)) < 0 ) + { + if (errno == EWOULDBLOCK) + { + usleep(1000); + continue; + } + tio_error_print("Write packet to serial failed"); + return ERR; + } + from += rc; + sz -= rc; + } + + /* Clear response */ + tmo_resp = 0; + + /* 'lrzsz' does not ACK ymodem's fin packet */ + if (seq == 0 && packet.data[0] == 0) tmo_resp = ACK; + + /* Read receiver response, timeout 1 s */ + err = xmsend_wait_response(sio, &resp, tmo_resp); + if (err != OK) + { + return err; + } + + /* Update "progress bar" */ + switch (resp) + { + case NAK: status = 'N'; break; + case ACK: status = '.'; break; + case 'C': status = 'C'; break; + case CAN: status = '!'; return ERR; + default: status = '?'; + } + write(STDOUT_FILENO, &status, 1); + + /* Move to next block after ACK */ + if (resp == ACK) + { + packet.hdr.seq++; + len -= z; + buf += z; + } + } + + if (seq != 0) + { + /* Send EOT at 1 Hz until ACK or CAN received */ + err = xmsend_repeat_eot_and_wait_response(sio); + if (err != OK) { + return err; + } + } + + return OK; +} + +static int xmodem_send_128b(int sio, const void *data, size_t len, bool use_crc) +{ + union + { + struct xpacket_128b_crc c; + struct xpacket_128b_sum s; + } packet; + const uint8_t *buf = data; + char resp = 0, tmo_resp; + int rc, crc, err; + + /* Drain pending characters from serial line. + Insist on the last drained character being 'C' */ + if (use_crc) + { + err = xmsend_initial_handshake(sio, 'C'); + } + else + { + err = xmsend_initial_handshake(sio, NAK); + } + if (err != OK) + return err; + + /* Always work with 128b packets */ + if (use_crc) + { + packet.c.hdr.seq = 1; + packet.c.hdr.type = SOH; + } + else + { + packet.s.hdr.seq = 1; + packet.s.hdr.type = SOH; + } + + while (len) + { + size_t sz, z = 0; + char *from, status; + + if (use_crc) + { + /* Build next packet, pad with 0 to full seq */ + z = min(len, sizeof(packet.c.data)); + memcpy(packet.c.data, buf, z); + memset(packet.c.data + z, 0, sizeof(packet.c.data) - z); + crc = calculate_crc16(packet.c.data, sizeof(packet.c.data)); + packet.c.ftr.crc_hi = crc >> 8; + packet.c.ftr.crc_lo = crc; + packet.c.hdr.nseq = 0xff - packet.c.hdr.seq; + + from = (char *) &packet.c; + sz = sizeof(packet.c); + } + else + { + /* Build next packet, pad with 0 to full seq */ + z = min(len, sizeof(packet.s.data)); + memcpy(packet.s.data, buf, z); + memset(packet.s.data + z, 0, sizeof(packet.s.data) - z); + packet.s.ftr.cksum = calculate_cksum8(packet.s.data, sizeof(packet.s.data)); + packet.s.hdr.nseq = 0xff - packet.s.hdr.seq; + + from = (char *) &packet.s; + sz = sizeof(packet.s); + } + + /* Send packet */ + while (sz) + { + if (key_hit) + return ERR_USER_CAN; + + if ((rc = write(sio, from, sz)) < 0 ) + { + if (errno == EWOULDBLOCK) + { + usleep(1000); + continue; + } + tio_error_print("Write packet to serial failed"); + return ERR; + } + from += rc; + sz -= rc; + } + + /* Clear response */ + tmo_resp = 0; + + /* Read receiver response, timeout 1 s */ + err = xmsend_wait_response(sio, &resp, tmo_resp); + if (err != OK) + { + return err; + } + + /* Update "progress bar" */ + switch (resp) + { + case NAK: status = 'N'; break; + case ACK: status = '.'; break; + case 'C': status = 'C'; break; + case CAN: status = '!'; return ERR; + default: status = '?'; + } + write(STDOUT_FILENO, &status, 1); + + /* Move to next block after ACK */ + if (resp == ACK) + { + if (use_crc) + packet.c.hdr.seq++; + else + packet.s.hdr.seq++; + + len -= z; + buf += z; + } + } + + /* Send EOT at 1 Hz until ACK or CAN received */ + err = xmsend_repeat_eot_and_wait_response(sio); + + return err; +} + +/* + * Ymodem: hdr + file + fin + */ +static int ymodem_send_1k(int sio, const void *data, size_t len, const char *filename, struct stat *stat) +{ + int err; + + while (1) + { + char hdr[1024], *p; + + err = ERR; + if (strlen(filename) > 977) break; /* hdr block overrun */ + p = stpncpy(hdr, filename, 1024) + 1; + p += sprintf(p, "%ld %lo %o", len, stat->st_mtime, stat->st_mode); + + if (xmodem_send_1k(sio, hdr, p - hdr, 0) < 0) break; /* hdr with metadata */ + if (xmodem_send_1k(sio, data, len, 1) < 0) break; /* xmodem file */ + if (xmodem_send_1k(sio, "", 1, 0) < 0) break; /* empty hdr = fin */ + err = OK; break; + } + return err; +} + +/* + * Drain pending characters from serial line. + */ +static int xmrecv_drain_pending_chars(int sio) +{ + int rc; + char resp; + + while (true) + { + if (key_hit) + return ERR_USER_CAN; + + rc = read_poll(sio, &resp, 1, 50); + if (rc == 0) + { + break; + } + else if (rc < 0) + { + tio_error_print("Read sync from serial failed"); + return ERR; + } + if (resp == CAN) + { + return ERR; + } + } + return OK; +} + +/* + * Start Receive + */ +static int xmrecv_start_receive(int sio, bool use_crc) +{ + int rc; + struct pollfd fds; + fds.events = POLLIN; + fds.fd = sio; + + for (int n = 0; n < 20; n++) + { + /* Send the 'C' byte until the sender of the file responds with + something. The start character will be sent once a second for a number of + seconds. If nothing is received in that time then return false to indicate + that the transfer did not start. */ + if (use_crc) + rc = write(sio, "C", 1); + else + rc = write(sio, NAK_STR, 1); + + if (rc < 0) + { + if (errno == EWOULDBLOCK) + { + usleep(1000); + continue; + } + tio_error_print("Write packet to serial failed"); + return ERR; + } + /* Wait until data is available */ + rc = poll(&fds, 1, 3000); + if (rc < 0) + { + tio_error_print("%s", strerror(errno)); + return ERR; + } + else if (rc > 0) + { + if (fds.revents & POLLIN) + { + return OK; + } + else /* if (fds.revents & (POLLERR | POLLHUP | POLLNVAL)) */ + { + return ERR; + } + } + if (key_hit) + return ERR_USER_CAN; + } + return ERR_TMO; +} + +/* + * Receive a packet + */ +static int xmrecv_receive_packet(int sio, struct xpacket_128b_crc *packet, int fd, bool use_crc) { char rxSeq1, rxSeq2 = 0; char resp = 0; @@ -385,91 +579,132 @@ int receive_packet(int sio, struct xpacket packet, int fd) /* Read seq bytes*/ rc = read_poll(sio, &rxSeq1, 1, 3000); - if (rc == 0) { + if (rc == 0) + { tio_error_print("Timeout waiting for first seq byte"); - return ERR; - } else if (rc < 0) { - tio_error_print("Error reading first seq byte") + return ERR_TMO; + } + else if (rc < 0) + { + tio_error_print("Error reading first seq byte"); return ERR_FATAL; } rc = read_poll(sio, &rxSeq2, 1, 3000); - if (rc == 0) { + if (rc == 0) + { tio_error_print("Timeout waiting for second seq byte"); - return ERR; - } else if (rc < 0) { - tio_error_print("Error reading second seq byte") + return ERR_TMO; + } + else if (rc < 0) + { + tio_error_print("Error reading second seq byte"); return ERR_FATAL; } if (key_hit) - return USER_CAN; + return ERR_USER_CAN; /* Read packet Data */ - for (unsigned ix = 0; (ix < sizeof(packet.data)); ix++) + for (unsigned ix = 0; (ix < sizeof(packet->data)); ix++) { rc = read_poll(sio, &resp, 1, 3000); - /* If the read times out or fails then fail this packet. */ + /* If the read times out or fails then fail this packet. */ if (rc == 0) { tio_error_print("Timeout waiting for next packet char"); rc = write(sio, CAN_STR, 1); - if (rc < 0) { + if (rc < 0) + { tio_error_print("Write cancel packet to serial failed"); return ERR_FATAL; } - return ERR; - } else if (rc < 0) { - tio_error_print("Error reading next packet char") + return ERR_TMO; + } + else if (rc < 0) + { + tio_error_print("Error reading next packet char"); rc = write(sio, CAN_STR, 1); - if (rc < 0) { + if (rc < 0) + { tio_error_print("Write cancel packet to serial failed"); } return ERR_FATAL; } - packet.data[ix] = (uint8_t) resp; - calcCrc = update_CRC(calcCrc, resp); + packet->data[ix] = (uint8_t) resp; + if (use_crc) + calcCrc = update_crc16(calcCrc, resp); + else + calcCrc = (calcCrc + (uint8_t)resp) & 0xff; + if (key_hit) - return USER_CAN; + return ERR_USER_CAN; } - /* Read CRC */ - rc = read_poll(sio, &resp, 1, 3000); - if (rc == 0) { - tio_error_print("Timeout waiting for first CRC byte"); - return ERR; - } else if (rc < 0) { - tio_error_print("Error reading first CRC byte") - return ERR_FATAL; - } + if (use_crc) + { + /* Read CRC */ + rc = read_poll(sio, &resp, 1, 3000); + if (rc == 0) + { + tio_error_print("Timeout waiting for first CRC byte"); + return ERR_TMO; + } + else if (rc < 0) + { + tio_error_print("Error reading first CRC byte"); + return ERR_FATAL; + } - uint8_t uresp = resp; - uint16_t uresp16 = uresp; - rxCrc = uresp16 << 8; + uint8_t uresp = resp; + uint16_t uresp16 = uresp; + rxCrc = uresp16 << 8; - rc = read_poll(sio, &resp, 1, 3000); - if (rc == 0) { - tio_error_print("Timeout waiting for second CRC byte"); - return ERR; - } else if (rc < 0) { - tio_error_print("Error reading second CRC byte") - return ERR_FATAL; + rc = read_poll(sio, &resp, 1, 3000); + if (rc == 0) + { + tio_error_print("Timeout waiting for second CRC byte"); + return ERR_TMO; + } + else if (rc < 0) + { + tio_error_print("Error reading second CRC byte"); + return ERR_FATAL; + } + + uresp = resp; + uresp16 = uresp; + rxCrc |= uresp16; + } + else + { + /* Read checksum */ + rc = read_poll(sio, &resp, 1, 3000); + if (rc == 0) + { + tio_error_print("Timeout waiting for checksum byte"); + return ERR_TMO; + } + else if (rc < 0) + { + tio_error_print("Error reading checksum byte"); + return ERR_FATAL; + } + + rxCrc = (uint8_t)resp; } - - uresp = resp; - uresp16 = uresp; - rxCrc |= uresp16; if (key_hit) - return USER_CAN; + return ERR_USER_CAN; /* At this point in the code, there should not be anything in the receive buffer - because the sender has just sent a complete packet and is waiting on a response. */ + because the sender has just sent a complete packet and is waiting on a response. */ rc = poll(&fds, 1, 10); if (rc < 0) { tio_error_print("%s", strerror(errno)); tio_error_print("Poll check error after packet finish"); rc = write(sio, CAN_STR, 1); - if (rc < 0) { + if (rc < 0) + { tio_error_print("Write cancel packet to serial failed"); } return ERR_FATAL; @@ -484,23 +719,28 @@ int receive_packet(int sio, struct xpacket packet, int fd) while (read_poll(sio, &dummy, 1, 100) > 0) {} return ERR; } + else /* if (fds.revents & (POLLERR | POLLHUP | POLLNVAL)) */ + { + return ERR; + } } uint8_t tester = 0xff; uint8_t seq1 = rxSeq1; uint8_t seq2 = rxSeq2; - if ((calcCrc == rxCrc) && (seq1 == packet.seq - 1) && ((seq1 ^ seq2) == tester)) + if ((calcCrc == rxCrc) && (seq1 == packet->hdr.seq - 1) && ((seq1 ^ seq2) == tester)) { /* Resend of previously processed packet. */ rc = write(sio, ACK_STR, 1); - if (rc < 0) { + if (rc < 0) + { tio_error_print("Write acknowlegdement packet to serial failed"); return ERR_FATAL; } return RX_IGNORE; } - else if ((calcCrc != rxCrc) || (seq1 != packet.seq) || ((seq1 ^ seq2) != tester)) + else if ((calcCrc != rxCrc) || (seq1 != packet->hdr.seq) || ((seq1 ^ seq2) != tester)) { /* Fail if the CRC or sequence number is not correct or if the two received sequence numbers are not the complement of one another. */ @@ -508,19 +748,20 @@ int receive_packet(int sio, struct xpacket packet, int fd) tio_debug_printf("CRC read: %u", rxCrc); tio_debug_printf("CRC calculated: %u", calcCrc); tio_debug_printf("Seq read: %hhu", rxSeq1); - tio_debug_printf("Seq should be: %hhu", packet.seq); + tio_debug_printf("Seq should be: %hhu", packet->hdr.seq); tio_debug_printf("inv seq: %hhu", rxSeq2); return ERR; } else { /* The data is good. Process the packet then ACK it to the sender. */ - rc = write(fd, packet.data, sizeof(packet.data)); + rc = write(fd, packet->data, sizeof(packet->data)); if (rc < 0) { tio_error_print("Problem writing to file"); rc = write(sio, CAN_STR, 1); - if (rc < 0) { + if (rc < 0) + { tio_error_print("Write cancel packet to serial failed"); } return ERR_FATAL; @@ -536,114 +777,122 @@ int receive_packet(int sio, struct xpacket packet, int fd) return OK; } -int xmodem_receive(int sio, int fd) +int xmodem_receive(int sio, int fd, bool use_crc) { - struct xpacket packet; + struct xpacket_128b_crc packet; char resp = 0; - int rc; + int rc, err; bool complete = false; char status; /* Drain pending characters from serial line.*/ - while(1) { - if (key_hit) - return -1; - rc = read_poll(sio, &resp, 1, 50); - if (rc == 0) { - if (resp == CAN) return ERR; - break; - } - else if (rc < 0) { - if (rc != USER_CAN) { - tio_error_print("Read sync from serial failed"); - } - return ERR; - } + err = xmrecv_drain_pending_chars(sio); + if (err != OK) + { + return err; } /* Always work with 128b packets */ - packet.seq = 1; - packet.type = SOH; + packet.hdr.seq = 1; + packet.hdr.type = SOH; /* Start Receive*/ - rc = start_receive(sio); - if (rc == 0) + err = xmrecv_start_receive(sio, use_crc); + if (err != OK) { - tio_error_print("Timeout waiting for transfer to start"); - return ERR; - } else if (rc < 0) { - tio_error_print("Error starting XMODEM receive"); - return ERR; + if (err == ERR_TMO) + { + tio_error_print("Timeout waiting for transfer to start"); + } + else + { + tio_error_print("Error starting XMODEM receive"); + } + return err; } - while (!complete) { + while (!complete) + { /* Poll for 1 new byte for 3 seconds */ rc = read_poll(sio, &resp, 1, 3000); - if (rc == 0) { + if (rc == 0) + { tio_error_print("Timeout waiting for start of next packet"); - return ERR; - } else if (rc < 0) { - tio_error_print("Error reading start of next packet") + return ERR_TMO; + } + else if (rc < 0) + { + tio_error_print("Error reading start of next packet"); return ERR; } if (key_hit) - return USER_CAN; + return ERR_USER_CAN; - switch(resp) + switch (resp) { case SOH: - /* Start of a packet */ - rc = receive_packet(sio, packet, fd); - if (rc == OK) { - packet.seq++; - status = '.'; - } else if (rc == ERR) { - rc = write(sio, NAK_STR, 1); - if (rc < 0) { - tio_error_print("Writing not acknowledge packet to serial failed"); + /* Start of a packet */ + err = xmrecv_receive_packet(sio, &packet, fd, use_crc); + if (err == OK) + { + packet.hdr.seq++; + status = '.'; + } + else if (err == ERR || err == ERR_TMO) + { + rc = write(sio, NAK_STR, 1); + if (rc < 0) + { + tio_error_print("Writing not acknowledge packet to serial failed"); + return ERR; + } + status = 'N'; + } + else if (err == ERR_FATAL) + { + tio_error_print("Receive cancelled due to fatal error"); return ERR; } - status = 'N'; - } else if (rc == ERR_FATAL) { - tio_error_print("Receive cancelled due to fatal error"); - return ERR; - } else if (rc == USER_CAN) { - rc = write(sio, CAN_STR, 1); - if (rc < 0) { - tio_error_print("Writing cancel to serial failed"); - return ERR; + else if (err == ERR_USER_CAN) + { + rc = write(sio, CAN_STR, 1); + if (rc < 0) + { + tio_error_print("Writing cancel to serial failed"); + return ERR; + } + return ERR_USER_CAN; } - return USER_CAN; - } else if (rc == RX_IGNORE) { - status = ':'; - } - break; + else if (err == RX_IGNORE) + { + status = ':'; + } + break; case EOT: - /* End of Transfer */ - rc = write(sio, ACK_STR, 1); - if (rc < 0) - { - tio_error_print("Write acknowlegdement packet to serial failed"); - return ERR; - } - complete = true; - status = '\0'; - write(STDOUT_FILENO, "|\r\n", 3); - break; + /* End of Transfer */ + rc = write(sio, ACK_STR, 1); + if (rc < 0) + { + tio_error_print("Write acknowlegdement packet to serial failed"); + return ERR; + } + complete = true; + status = '\0'; + write(STDOUT_FILENO, "|\r\n", 3); + break; case CAN: - /* Cancel from sender */ - tio_error_print("Transmission cancelled from sender"); - return ERR; - break; + /* Cancel from sender */ + tio_error_print("Transmission cancelled from sender"); + return ERR; + break; default: - tio_error_print("Unexpected character received waiting for next packet"); - return ERR; - break; + tio_error_print("Unexpected character received waiting for next packet"); + return ERR; + break; } - /* Update "progress bar" */ write(STDOUT_FILENO, &status, 1); @@ -654,20 +903,22 @@ int xmodem_receive(int sio, int fd) int xymodem_send(int sio, const char *filename, modem_mode_t mode) { size_t len; - int rc, fd; + int err, fd; struct stat stat; const uint8_t *buf; /* Open file, map into memory */ fd = open(filename, O_RDONLY); - if (fd < 0) { + if (fd < 0) + { tio_error_print("Could not open file"); return ERR; } fstat(fd, &stat); len = stat.st_size; buf = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0); - if (!buf) { + if (!buf) + { close(fd); tio_error_print("Could not mmap file"); return ERR; @@ -675,27 +926,21 @@ int xymodem_send(int sio, const char *filename, modem_mode_t mode) /* Do transfer */ key_hit = 0; - if (mode == XMODEM_1K) { - rc = xmodem_1k(sio, buf, len, 1); + if (mode == XMODEM_1K) + { + err = xmodem_send_1k(sio, buf, len, 1); } - else if (mode == XMODEM_CRC) { - rc = xmodem(sio, buf, len); + else if (mode == XMODEM_CRC) + { + err = xmodem_send_128b(sio, buf, len, /* use_crc= */ true); } - else { - /* Ymodem: hdr + file + fin */ - while(1) { - char hdr[1024], *p; - - rc = -1; - if (strlen(filename) > 977) break; /* hdr block overrun */ - p = stpncpy(hdr, filename, 1024) + 1; - p += sprintf(p, "%ld %lo %o", len, stat.st_mtime, stat.st_mode); - - if (xmodem_1k(sio, hdr, p - hdr, 0) < 0) break; /* hdr with metadata */ - if (xmodem_1k(sio, buf, len, 1) < 0) break; /* xmodem file */ - if (xmodem_1k(sio, "", 1, 0) < 0) break; /* empty hdr = fin */ - rc = 0; break; - } + else if (mode == XMODEM_SUM) + { + err = xmodem_send_128b(sio, buf, len, /* use_crc= */ false); + } + else /* if (mode == YMODEM) */ + { + err = ymodem_send_1k(sio, buf, len, filename, &stat); } key_hit = 0xff; @@ -703,37 +948,45 @@ int xymodem_send(int sio, const char *filename, modem_mode_t mode) tcflush(sio, TCIOFLUSH); munmap((void *)buf, len); close(fd); - return rc; + return err; } int xymodem_receive(int sio, const char *filename, modem_mode_t mode) { - int rc, fd; + int err, fd; /* Create new file */ fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0664); - if (fd < 0) { + if (fd < 0) + { tio_error_print("Could not open file"); return ERR; } /* Do transfer */ key_hit = 0; - if (mode == XMODEM_1K) { + if (mode == XMODEM_1K) + { tio_error_print("Not supported"); - rc = -1; + err = ERR; } - else if (mode == XMODEM_CRC) { - rc = xmodem_receive(sio, fd); + else if (mode == XMODEM_CRC) + { + err = xmodem_receive(sio, fd, /* use_crc= */ true); } - else { + else if (mode == XMODEM_SUM) + { + err = xmodem_receive(sio, fd, /* use_crc= */ false); + } + else + { tio_error_print("Not supported"); - rc = -1; + err = ERR; } key_hit = 0xff; /* Flush serial and release resources */ tcflush(sio, TCIOFLUSH); close(fd); - return rc; + return err; } diff --git a/src/xymodem.h b/src/xymodem.h index 1b46cd7..ed239be 100644 --- a/src/xymodem.h +++ b/src/xymodem.h @@ -21,14 +21,15 @@ #pragma once -typedef enum { +typedef enum +{ XMODEM_1K, XMODEM_CRC, + XMODEM_SUM, YMODEM, } modem_mode_t; extern char key_hit; int xymodem_send(int sio, const char *filename, modem_mode_t mode); - int xymodem_receive(int sio, const char *filename, modem_mode_t mode);