1.03 XModem Protocol with CRC - JohnHau/mis GitHub Wiki
XMODEM Xmodemis one of the most widely used file transfer protocols. The original Xmodem protocol uses 128-byte packets and a simple "checksum" method of error detection. A later enhancement, Xmodem-CRC, uses a more secure Cyclic Redundancy Check (CRC) method for error detection. Xmodem protocol always attempts to use CRC first. If the sender does not acknowledge the requests for CRC, the receiver shifts to the checksum mode and continues its request for transmission.
Xmodem-1K
Xmodem 1K is essentially Xmodem CRC with 1K (1024 byte) packets. On some systems and bulletin boards it may also be referred to as Ymodem. Some communication software programs, most notably Procomm Plus 1.x, also list Xmodem-1K as Ymodem. Procomm Plus 2.0 no longer refers to Xmodem-1K as Ymodem.
Introduction The Xmodem protocol was created years ago as a simple means of having two computers talk to each other. With its half-duplex mode of operation, 128- byte packets, ACK/NACK responses and CRC data checking, the Xmodem protocol has found its way into many applications. In fact most communication packages found on the PC today have a Xmodem protocol available to the user.
Theory of Operation Xmodem is a half-duplex communication protocol. The receiver, after receiving a packet, will either acknowledge (ACK) or not acknowledge (NAK) the packet. The CRC extension to the original protocol uses a more robust 16-bit CRC to validate the data block and is used here. Xmodem can be considered to be receiver driven. That is, the receiver sends an initial character βCβ to the sender indicating that itβs ready to receive data in CRC mode. The sender then sends a 133-byte packet, the receiver validates it and responds with an ACK or a NAK at which time the sender will either send the next packet or re-send the last packet. This process is continued until an EOT is received at the receiver side and is properly ACKed to the sender. After the initial handshake the receiver controls the flow of data through ACKing and NAKing the sender.
Table 1. XmodemCRC Packet Format
Byte 1 of the XmodemCRC packet can only have a value of SOH, EOT, CAN or ETB anything else is an error. Bytes 2 and 3 form a packet number with checksum, add the two bytes together and they should always equal 0xff. Please note that the packet number starts out at 1 and rolls over to 0 if there are more than 255 packets to be received. Bytes 4 - 131 form the data packet and can be anything. Bytes 132 and 133 form the 16-bit CRC. The high byte of the CRC is located in byte 132. The CRC is calculated only on the data packet bytes (4 - 131) .
Synchronization The receiver starts by sending an ASCII βCβ (0x43) character to the sender indicating it wishes to use the CRC method of block validating. After sending the initial βCβ the receiver waits for either a 3 second time out or until a buffer full flag is set. If the receiver is timed out then another βCβ is sent to the sender and the 3 second time out starts again. This process continues until the receiver receives a complete 133-byte packet.
Receiver Considerations This protocol NAKs the following conditions: 1. Framing error on any byte 2. Overrun error on any byte 3. Duplicate packet 4. CRC error 5. Receiver timed out (didn't receive packet within 1 second) On any NAK, the sender will re-transmit the last packet. Items 1 and 2 should be considered serious hardware failures. Verify that sender and receiver are using the samebaud rate, start bits and stop bits. Item 3 is usually the sender getting an ACK garbled and re-transmitting the packet. Item 4 is found in noisy environments. And the last issue should be self-correcting after the receiver NAKs the sender.
Sample crc calculation code int calcrc(char *ptr, int count) { int crc; char i; crc = 0; while (--count >= 0) { crc = crc ^ (int) *ptr++ << 8; i = 8; do { if (crc & 0x8000) crc = crc << 1 ^ 0x1021; else crc = crc << 1; } while(--i); } return (crc); }
//////////////////////////////////////////////////////////////////// /*
- Minimalistic implementation of the XModem/YModem protocol suite, including
- a compact version of an CRC16 algorithm. The code is just enough to upload
- an image to an MCU that bootstraps itself over an UART.
- Copyright (c) 2014 Daniel Mack [email protected]
- License: MIT */
#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <fcntl.h> #include <termios.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/mman.h>
#define X_STX 0x02 #define X_ACK 0x06 #define X_NAK 0x15 #define X_EOF 0x04
#define min(a, b) ((a) < (b) ? (a) : (b))
struct xmodem_chunk { uint8_t start; uint8_t block; uint8_t block_neg; uint8_t payload[1024]; uint16_t crc; } attribute((packed));
#define CRC_POLY 0x1021
static uint16_t crc_update(uint16_t crc_in, int incr) { uint16_t xor = crc_in >> 15; uint16_t out = crc_in << 1;
if (incr)
out++;
if (xor)
out ^= CRC_POLY;
return out;
}
static uint16_t crc16(const uint8_t *data, uint16_t size) { uint16_t crc, i;
for (crc = 0; size > 0; size--, data++)
for (i = 0x80; i; i >>= 1)
crc = crc_update(crc, *data & i);
for (i = 0; i < 16; i++)
crc = crc_update(crc, 0);
return crc;
} static uint16_t swap16(uint16_t in) { return (in >> 8) | ((in & 0xff) << 8); }
enum { PROTOCOL_XMODEM, PROTOCOL_YMODEM, };
static int xymodem_send(int serial_fd, const char *filename, int protocol, int wait) { size_t len; int ret, fd; uint8_t answer; struct stat stat; const uint8_t *buf; uint8_t eof = X_EOF; struct xmodem_chunk chunk; int skip_payload = 0;
fd = open(filename, O_RDONLY);
if (fd < 0) {
perror("open");
return -errno;
}
fstat(fd, &stat);
len = stat.st_size;
buf = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
if (!buf) {
perror("mmap");
return -errno;
}
if (wait) {
printf("Waiting for receiver ping ...");
fflush(stdout);
do {
ret = read(serial_fd, &answer, sizeof(answer));
if (ret != sizeof(answer)) {
perror("read");
return -errno;
}
} while (answer != 'C');
printf("done.\n");
}
printf("Sending %s ", filename);
if (protocol == PROTOCOL_YMODEM) {
strncpy((char *) chunk.payload, filename, sizeof(chunk.payload));
chunk.block = 0;
skip_payload = 1;
} else {
chunk.block = 1;
}
chunk.start = X_STX;
while (len) {
size_t z = 0;
int next = 0;
char status;
if (!skip_payload) {
z = min(len, sizeof(chunk.payload));
memcpy(chunk.payload, buf, z);
memset(chunk.payload + z, 0xff, sizeof(chunk.payload) - z);
} else {
skip_payload = 0;
}
chunk.crc = swap16(crc16(chunk.payload, sizeof(chunk.payload)));
chunk.block_neg = 0xff - chunk.block;
ret = write(serial_fd, &chunk, sizeof(chunk));
if (ret != sizeof(chunk))
return -errno;
ret = read(serial_fd, &answer, sizeof(answer));
if (ret != sizeof(answer))
return -errno;
switch (answer) {
case X_NAK:
status = 'N';
break;
case X_ACK:
status = '.';
next = 1;
break;
default:
status = '?';
break;
}
printf("%c", status);
fflush(stdout);
if (next) {
chunk.block++;
len -= z;
buf += z;
}
}
ret = write(serial_fd, &eof, sizeof(eof));
if (ret != sizeof(eof))
return -errno;
/* send EOT again for YMODEM */
if (protocol == PROTOCOL_YMODEM) {
ret = write(serial_fd, &eof, sizeof(eof));
if (ret != sizeof(eof))
return -errno;
}
printf("done.\n");
return 0;
}
static int open_serial(const char *path, int baud) { int fd; struct termios tty;
fd = open(path, O_RDWR | O_SYNC);
if (fd < 0) {
perror("open");
return -errno;
}
memset(&tty, 0, sizeof(tty));
if (tcgetattr(fd, &tty) != 0) {
perror("tcgetattr");
return -errno;
}
cfsetospeed(&tty, baud);
cfsetispeed(&tty, baud);
tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; // 8-bit chars
tty.c_iflag &= ~IGNBRK; // disable break processing
tty.c_lflag = 0; // no signaling chars, no echo,
// no canonical processing
tty.c_oflag = 0; // no remapping, no delays
tty.c_cc[VMIN] = 1; // read doesn't block
tty.c_cc[VTIME] = 5; // 0.5 seconds read timeout
tty.c_iflag &= ~(IXON | IXOFF | IXANY); // shut off xon/xoff ctrl
tty.c_cflag |= (CLOCAL | CREAD); // ignore modem controls,
// enable reading
tty.c_cflag &= ~(PARENB | PARODD); // shut off parity
tty.c_cflag &= ~CSTOPB;
tty.c_cflag &= ~CRTSCTS;
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
perror("tcsetattr");
return -errno;
}
return fd;
}
static void dump_serial(int serial_fd) { char in;
for (;;) {
read(serial_fd, &in, sizeof(in));
printf("%c", in);
fflush(stdout);
}
}
int main(int argc, char **argv) { int a, ret, serial_fd;
serial_fd = open_serial("/dev/ttyUSB0", 115200);
if (serial_fd < 0)
return -errno;
ret = xymodem_send(serial_fd, argv[1], PROTOCOL_XMODEM, 1);
if (ret < 0)
return ret;
ret = xymodem_send(serial_fd, argv[2], PROTOCOL_YMODEM, 1);
if (ret < 0)
return ret;
sleep(4);
ret = xymodem_send(serial_fd, argv[1], PROTOCOL_YMODEM, 0);
if (ret < 0)
return ret;
sleep(3);
ret = xymodem_send(serial_fd, argv[2], PROTOCOL_YMODEM, 0);
if (ret < 0)
return ret;
dump_serial(serial_fd);
return 0;
}
//////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////// /* XMODEM support for GDB, the GNU debugger. Copyright 1995, 2000, 2001 Free Software Foundation, Inc.
This file is part of GDB.
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
#include "defs.h" #include "serial.h" #include "target.h" #include "xmodem.h"
/* These definitions are for xmodem protocol. */
#define SOH 0x01 #define STX 0x02 #define ACK 0x06 #define NAK 0x15 #define EOT 0x04 #define CANCEL 0x18
static int blknum; /* XMODEM block number / static int crcflag; / Sez we are using CRC's instead of cksums */
static int readchar (struct serial *desc, int timeout) { int c;
c = serial_readchar (desc, timeout);
if (remote_debug > 0) fputc_unfiltered (c, gdb_stdlog);
if (c >= 0) return c;
if (c == SERIAL_TIMEOUT) error ("Timeout reading from remote system.");
perror_with_name ("xmodem.c:readchar()"); }
#define CRC16 0x1021 /* Generator polynomial (X^16 + X^12 + X^5 + 1) */
static unsigned short *crctab;
/* Call this to init the fast CRC-16 calculation table. */
static void crcinit (void) { static int crctab_inited = 0; int val;
if (crctab_inited == 1) return;
crctab = xmalloc (256 * sizeof (short));
for (val = 0; val <= 255; val++) { int i; unsigned int crc;
crc = val << 8;
for (i = 0; i < 8; ++i)
{
crc <<= 1;
if (crc & 0x10000)
crc ^= CRC16;
}
crctab[val] = crc;
}
crctab_inited = 1; }
/* Calculate a CRC-16 for the LEN byte message pointed at by P. */
static unsigned short docrc (unsigned char *p, int len) { unsigned short crc = 0;
while (len-- > 0) crc = (crc << 8) ^ crctab[(crc >> 8) ^ *p++];
return crc; }
/* Start up the transmit process. Reset state variables. Wait for receiver to send NAK or CRC request. */
int xmodem_init_xfer (struct serial *desc) { int c; int i;
blknum = 1; crcflag = 0; crcinit ();
for (i = 1; i <= 10; i++) { c = readchar (desc, 6);
switch (c)
{
case 'C':
crcflag = 1;
case NAK:
return 0;
default:
fprintf_unfiltered (gdb_stderr, "xmodem_init_xfer: Got unexpected character %c (0%o)\n", c, c);
continue;
case CANCEL: /* target aborted load */
fprintf_unfiltered (gdb_stderr, "Got a CANCEL from the target.\n");
continue;
}
}
error ("xmodem_init_xfer: Too many unexpected characters."); }
/* Take 128 bytes of data and make a packet out of it.
-
Each packet looks like this:
-
+-----+-------+-------+------+-----+
-
| SOH | Seq1. | Seq2. | data | SUM |
-
+-----+-------+-------+------+-----+
-
SOH = 0x01
-
Seq1 = The sequence number.
-
Seq2 = The complement of the sequence number.
-
Data = A 128 bytes of data.
-
SUM = Add the contents of the 128 bytes and use the low-order
-
8 bits of the result.
- send_xmodem_packet fills in the XMODEM fields of PACKET and sends it to the
- remote system. PACKET must be XMODEM_PACKETSIZE bytes long. The data must
- start 3 bytes after the beginning of the packet to leave room for the
- XMODEM header. LEN is the length of the data portion of the packet (and
- must be <= 128 bytes). If it is < 128 bytes, ^Z padding will be added. */
void xmodem_send_packet (struct serial *desc, unsigned char *packet, int len, int hashmark) { int i; int retries; int pktlen; int datasize;
/* build the packet header */
packet[1] = blknum; packet[2] = ~blknum;
blknum++;
if (len <= XMODEM_DATASIZE) { packet[0] = SOH; datasize = XMODEM_DATASIZE; } else if (len <= XMODEM_1KDATASIZE) { packet[0] = STX; datasize = XMODEM_1KDATASIZE; } else internal_error (FILE, LINE, "failed internal consistency check"); /* Packet way too large */
/* Add ^Z padding if packet < 128 (or 1024) bytes */
memset (packet + 3 + len, '\026', datasize - len);
if (crcflag) { int crc;
crc = docrc (packet + 3, datasize);
packet[3 + datasize] = crc >> 8;
packet[3 + datasize + 1] = crc;
pktlen = datasize + 5;
}
else { int sum;
sum = 0;
for (i = 3; i < datasize + 3; i++)
sum += packet[i];
packet[3 + datasize] = sum; /* add the checksum */
pktlen = datasize + 4;
}
for (retries = 3; retries >= 0; retries--) { int c;
serial_write (desc, packet, pktlen);
c = readchar (desc, 3);
switch (c)
{
case ACK:
return;
case NAK:
if (!hashmark)
continue;
putchar_unfiltered ('-');
gdb_flush (gdb_stdout);
continue;
case CANCEL:
error ("xmodem_send_packet: Transfer aborted by receiver.");
default:
fprintf_unfiltered (gdb_stderr, "xmodem_send_packet: Got unexpected character %c (0%o)\n", c, c);
continue;
}
}
serial_write (desc, "\004", 1); /* Send an EOT */
error ("xmodem_send_packet: Excessive retries."); }
/* Finish off the transfer. Send out the EOT, and wait for an ACK. */
void xmodem_finish_xfer (struct serial *desc) { int retries;
for (retries = 10; retries >= 0; retries--) { int c;
serial_write (desc, "\004", 1); /* Send an EOT */
c = readchar (desc, 3);
switch (c)
{
case ACK:
return;
case NAK:
continue;
case CANCEL:
error ("xmodem_finish_xfer: Transfer aborted by receiver.");
default:
fprintf_unfiltered (gdb_stderr, "xmodem_send_packet: Got unexpected character %c (0%o)\n", c, c);
continue;
}
}
error ("xmodem_finish_xfer: Excessive retries."); }
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////