From eea46a20053d4df6cd8ddb476c46ea9e5fef8a9b Mon Sep 17 00:00:00 2001 From: Mengsk Date: Wed, 3 Apr 2024 15:25:57 +0200 Subject: [PATCH] Add Xmodem-CRC support. --- src/tty.c | 5 +- src/xymodem.c | 151 +++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 147 insertions(+), 9 deletions(-) diff --git a/src/tty.c b/src/tty.c index af36ae1..7281ebb 100644 --- a/src/tty.c +++ b/src/tty.c @@ -110,6 +110,7 @@ #define KEY_U 0x55 #define KEY_V 0x76 #define KEY_X 0x78 +#define KEY_SHIFT_X 0x58 #define KEY_Y 0x79 #define KEY_Z 0x7a @@ -720,6 +721,7 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward) tio_printf(" ctrl-%c U Toggle conversion to uppercase on output", option.prefix_key); tio_printf(" ctrl-%c v Show version", option.prefix_key); tio_printf(" ctrl-%c x Send file via Xmodem-1K", option.prefix_key); + tio_printf(" ctrl-%c X Send file via Xmodem-CRC", option.prefix_key); tio_printf(" ctrl-%c y Send file via Ymodem", option.prefix_key); tio_printf(" ctrl-%c ctrl-%c Send ctrl-%c character", option.prefix_key, option.prefix_key, option.prefix_key); break; @@ -887,7 +889,8 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward) case KEY_X: case KEY_Y: - tio_printf("Send file with %cMODEM", toupper(input_char)); + case KEY_SHIFT_X: + tio_printf("Send file with %s", input_char == KEY_X ? "XMODEM-1K" : (input_char == KEY_Y ? "YMODEM" : "XMODEM-CRC")); tio_printf_raw("Enter file name: "); if (tio_readln()) { tio_printf("Sending file '%s' ", line); diff --git a/src/xymodem.c b/src/xymodem.c index 53ec37d..42cd791 100644 --- a/src/xymodem.c +++ b/src/xymodem.c @@ -20,6 +20,7 @@ #include #include "misc.h" +#define SOH 0x01 #define STX 0x02 #define ACK 0x06 #define NAK 0x15 @@ -31,7 +32,7 @@ #define min(a, b) ((a) < (b) ? (a) : (b)) -struct xpacket { +struct xpacket_1k { uint8_t type; uint8_t seq; uint8_t nseq; @@ -40,6 +41,15 @@ struct xpacket { uint8_t crc_lo; } __attribute__((packed)); +struct xpacket { + uint8_t type; + uint8_t seq; + uint8_t nseq; + uint8_t data[128]; + uint8_t crc_hi; + uint8_t crc_lo; +} __attribute__((packed)); + /* See https://en.wikipedia.org/wiki/Computation_of_cyclic_redundancy_checks */ static uint16_t crc16(const uint8_t *data, uint16_t size) { @@ -53,9 +63,9 @@ static uint16_t crc16(const uint8_t *data, uint16_t size) return crc; } -static int xmodem(int sio, const void *data, size_t len, int seq) +static int xmodem_1k(int sio, const void *data, size_t len, int seq) { - struct xpacket packet; + struct xpacket_1k packet; const uint8_t *buf = data; char resp = 0; int rc, crc; @@ -80,7 +90,7 @@ static int xmodem(int sio, const void *data, size_t len, int seq) /* Always work with 1K packets */ packet.seq = seq; - packet.type = STX; + packet.type = STX; while (len) { size_t sz, z = 0; @@ -113,6 +123,9 @@ static int xmodem(int sio, const void *data, size_t len, int seq) sz -= rc; } + /* Clear response */ + resp = 0; + /* 'lrzsz' does not ACK ymodem's fin packet */ if (seq == 0 && packet.data[0] == 0) resp = ACK; @@ -172,6 +185,125 @@ static int xmodem(int sio, const void *data, size_t len, int seq) 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; + if (read(sio, &resp, 1) < 0) { + if (errno == EWOULDBLOCK) { + if (resp == 'C') break; + if (resp == CAN) return ERR; + usleep(50000); + continue; + } + perror("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; + } + perror("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; + if (read(sio, &resp, 1) < 0) { + if (errno == EWOULDBLOCK) { + usleep(50000); + continue; + } + perror("Read ack/nak from serial failed"); + return ERR; + } + 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, 1) < 0) { + perror("Write EOT to serial failed"); + return ERR; + } + write(STDOUT_FILENO, "|", 1); + usleep(1000000); /* 1 s timeout*/ + if (read(sio, &resp, 1) < 0) { + if (errno == EWOULDBLOCK) continue; + perror("Read from serial failed"); + return ERR; + } + if (resp == ACK || resp == CAN) { + write(STDOUT_FILENO, "\r\n", 2); + return (resp == ACK) ? OK : ERR; + } + } + return 0; /* not reached */ +} + int xymodem_send(int sio, const char *filename, char mode) { size_t len; @@ -197,7 +329,10 @@ int xymodem_send(int sio, const char *filename, char mode) /* Do transfer */ key_hit = 0; if (mode == 'x') { - rc = xmodem(sio, buf, len, 1); + rc = xmodem_1k(sio, buf, len, 1); + } + else if (mode == 'X') { + rc = xmodem(sio, buf, len); } else { /* Ymodem: hdr + file + fin */ @@ -209,9 +344,9 @@ int xymodem_send(int sio, const char *filename, char mode) p = stpcpy(hdr, filename) + 1; p += sprintf(p, "%ld %lo %o", len, stat.st_mtime, stat.st_mode); - if (xmodem(sio, hdr, p - hdr, 0) < 0) break; /* hdr with metadata */ - if (xmodem(sio, buf, len, 1) < 0) break; /* xmodem file */ - if (xmodem(sio, "", 1, 0) < 0) break; /* empty hdr = fin */ + 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; } }