CRC



September 01, 1990
URL:http://www.drdobbs.com/crc/184402238

September 1990/XMODEM, XMODEM-1K, And XMODEM/CRC/Figure 1

Figure 1 XMODEM File Transfer

SENDER                         RECEIVER
---------------------------------------
           Start Character (NAK or 'C')
Packet 1
              ACK (Packet Received OK)
Packet 2
                   NAK (Packet NOT OK)
Packet 2
                       ACK (Packet OK)
              
              
              
Packet n
                                   ACK
EOT
                                   ACK
--------------------------------------
September 1990/XMODEM, XMODEM-1K, And XMODEM/CRC/Listing 1

Listing 1

/*--------------------------------------------
XMODEM.H

Author    Date       Description
-------------------------------------
Jon Ward  22 Apr 90  Initial Revision.
--------------------------------------------*/

#ifndef XMODEM_H
#define XMODEM_H

/*--------------------------------------------
        Xmodem Transfer Errors
--------------------------------------------*/
enum xmodem_errors   
  {
  XERR_OK,  /* No errors */
  
  XERR_XMIT_FUNC,  /* NULL Transmit function pointer */
  XERR_RCVR_FUNC,  /* NULL Receive function pointer */
  
  XERR_RCVR_CANCEL,  /* Receiver cancelled the transfer */
  XERR_SEND_CANCEL,  /* Sender cancelled the transfer */
  XERR_USER CANCEL,  /* User cancelled the transfer */
  
  XERR_FILE_READ,   /* Error reading the file */
  XERR_FILE_WRITE,  /* Error writing the file */
  
  XERR_ACK_TIMEOUT,    /* Timed out waiting for data pack ACK */
  XERR_NAK_TIMEOUT,    /* Timed out waiting for initial NAK */
  XERR_SOH_TIMEOUT,    /* Timed out waiting for SOH */
  XERR_DATA_TIMEOUT,   /* Timed out waiting for data */
  XERR_LAST_ACK_TIMEOUT,/* Timed out waiting for final ACK */
  
  XERR_INVALID_SOH,    /* Invalid char waiting for SOH */
  XERR_INVALID_BLOCK_NUM,/* Block mismatch in packet header */
  XERR_INVALID_CRC,    /* CRC is incorrect */
  XERR_INVALID_CHECKSUM,/* Checksum is incorrect */
  XERR_BLOCK_SEQUENCE, /* Block out of sequence */
  
  XERR_CHAR_ERROR, /* Received character error */
  XERR_OFFLINE,        /* Modem not online */
  
  XERR_BLOCK_NAK,  /* NAK received */
  };
  
#ifdef XMODEM_LIB
/*------------------------------------------------
Number of attempts and timeout for each attempt
to transferr using CRC.
------------------------------------------------*/
#define CRC_RETRY_COUNT        4
#define CRC_TIMEOUT        (3L * MS_PER_SEC)

/*------------------------------------------------
Number of attempts and timeout for each attempt
to Negative Acknowledge a bad packet. This also
includes the initial NAK to get the thing started.
------------------------------------------------*/
#define NAK_RETRY_COUNT        10
#define NAK_TIMEOUT        (10L * MS_PER_SEC)

/*------------------------------------------------
Number of attempts and timeout for each attempt
to Acknowledge a good packet.
------------------------------------------------*/
#define ACK_RETRY_COUNT        10
#define ACK_TIMEOUT        (10L * MS_PER_SEC)

/*------------------------------------------------
Number of consecutive CANs and the timeout between
them.
------------------------------------------------*/
#define CAN_COUNT_ABORT        2

#define CAN_TIMEOUT        (2L * MS_PER_SEC)

/*------------------------------------------------
Timeout between packet data bytes.
------------------------------------------------*/
#define DATA_TIMEOUT       (1L * MS_PER_SEC)

/*------------------------------------------------
Number of false start characters that the sender
can receive before aborting the transmission.
------------------------------------------------*/
#define START_XMIT_RETRY_COUNT 10
#define START_XMIT_TIMEOUT (60L * MS_PER_SEC)

/*------------------------------------------------
Maximum number of attempts to send a single block.
------------------------------------------------*/
#define BLOCK_RETRY_COUNT      10

/*------------------------------------------------
Timeout for block responses.
------------------------------------------------*/
#define BLOCK_RESP_TIMEOUT (10L * MS_PER_SEC)

/*------------------------------------------------
Macro to purge all characters from the receive
buffer. This waits until there are no characters
received for a 1 second period.
------------------------------------------------*/
#define PURGE_RECEIVER(c) \
while ((*(c)) (MS_PER_SEC, NULL) != RECV_TIMEOUT)

#endif /* XMODEM_LIB */

/*------------------------------------------------
Number of milliseconds in a second. I know this
is silly, but it makes the meaning a little more
obvious.
------------------------------------------------*/
#define MS_PER_SEC     1000L

/*------------------------------------------------
Block sizes for normal and for 1K XMODEM
transfers.
------------------------------------------------*/
#define XMODEM_BLOCK_SIZE 128
#define XMODEM_1K_BLOCK_SIZE   1024

/*------------------------------------------------
Values returned by the receive character serial
interface function.
------------------------------------------------*/
#define RECV_TIMEOUT -1 /* receiver timed-out */

/*------------------------------------------------
Error bits stored in the receiv character error
flag.
------------------------------------------------*/
#define RE_OVERRUN_  0x01   /* receiver overrun error */
#define RE_PARITY_  0x02    /* receiver parity error */
#define RE_FRAMING_  0x04  /* receiver framing error */

/*------------------------------------------------
Values returned by the transmit character serial
interface function.
------------------------------------------------*/
#define XMIT_OK        0   /* character enqued for transmission */
#define XMIT_OFFLINE   -1  /* modem offline */

/*-----------------------------------------------
     ASCII values used by XMODEM
-----------------------------------------------*/
#define SOH        1
#define STX        2
#define EOT        4
#define ACK        6
#define BACKSPACE  8
#define NAK        21
#define CAN        24

#define CPMEOF     26

/*-----------------------------------------------
XMODEM block description structure. This is only
used internally by the send and receive routines
and should not be visible outside of these.
-----------------------------------------------*/
#ifdef XMODEM_LIB
struct xmodem_block_st
  {
  long total_block_count;  /* total blocks transferred */
  long total_byte_count;   /* total bytes transferred */
  
  unsigned char start_char;  /* block starting character */
  unsigned char block_num; /* transmission block number */
  unsigned char not_block_num; /* one's complement block number */
  char buffer [XMODEM_1K_BLOCK_SIZE + 1];  /* data buffer */
  int buflen;          /* buffer length (128 or 1024) */
  unsigned char checksum;  /* data checksum */
  unsigned int crc;        /* data CRC-16 */
  
  unsigned int crc_used: 1;    /* 0=Checksum 1=CRC-16 */
  };

typedef struct xmodem_block_st xblock;
#endif /* XMODEM_LIB */

/*-----------------------------------------------
XMODEM function pointer structure.
-----------------------------------------------*/
#ifdef XMODEM_LIB
struct xmodem_func_st
  {
  int (*transmit) (    /* xmit function */
    char);
  
  int (*receive) (  /* recv function */
    long,
    unsigned int *);
  
  void (*dispstat) (   /* display function */
    long,
    long,
    const char *);
  
  int (*check_abort) (void);  /* manual abort function */
  };

typedef struct xmodem_func_st xfunc;
#endif /* XMODEM_LIB */


/*-----------------------------------------------
         XMODEMR.C
-----------------------------------------------*/
int xmodem_recv (
  FILE *f,                 /* file to write to */
  int (*transmit) (char),          /* xmit function */
  int (*receive) (long, unsigned int *),   /* recv function */
  void (*dispstat) (long, long, const char *), /* display function */
  int (*check_abort) (void));          /* manual abort function */

#ifdef XMODEM_LIB
int xm_perror (
  int error,               /* error number */
  xfunc *xmf);             /* xmodem external functions */

void xm_no_disp_func (
  long a,
  long b,
  const char *buf);

int xm_no_abort_func (void);
#endif /* XMODEM_LIB */




/*-----------------------------------------------
         XMODEMS.C
-----------------------------------------------*/
int xmodem_send (
  int block_size,              /* maximum block size */
  FILE *f,                 /* file to write to */
  int (*transmit) (char),         /* xmit function */
  int (*receive) (long, unsigned int *),  /* recv function */
  void (*dispstat) (long, long, const char *), /* display function */
  int (*check_abort) (void));           /* manual abort function */

#ifdef XMODEM_LIB
unsigned int xm_update_CRC (
  unsigned int crc,       /* current CRC */
  unsigned int c);    /* character to add to CRC */

void xm_send_cancel (
  int (*transmit) (char)); /* transmit function */
#endif /* XMODEM_LIB */

#endif     /* XMODEM_H */
September 1990/XMODEM, XMODEM-1K, And XMODEM/CRC/Listing 2

Listing 2

/*-----------------------------------------------
XMODEMR.C

Author    Date        Description
----------------------------------------*/
Jon Ward  22  Apr 90  Initial Revision.
Jon Ward  23  Apr 90  Cleanup and modify for
            XMODEM-1K and XMODEM-CRC.
Jon Ward  26  Apr 90  Corrected implementation
            of XMODEM-CRC.
Jon Ward  7  Jun 90  Added more comments and a
            little cleanup.
-----------------------------------------------*/

#define XMODEM_LIB 1

#include <stdio.h>
#include "xmodem.h"


#define STATIC static  /* undef for debugging */

/*-----------------------------------------------
-----------------------------------------------*/
struct send_n_wait_st
  {
  char_char_to send;
  int retry_count;
  long ms_timeout;
  unsigned char *valid_responses;
  int num_valid_responses;
  };

STATIC unsigned char soh_stx_can [] =
  { SOH, STX, CAN };
STATIC unsigned char soh_stx_can_eot [] =
  { SOH, STX, CAN, EOT };

STATIC struct send_n_wait_st crc_req =
  {
  'C',
  CRC_RETRY_COUNT,
  CRC_TIMEOUT,
  soh_stx_can,
  sizeof (soh_stx_can)
  };

STATIC struct send_n_wait_st checksum_req =
  {
  NAK,
  NAK_RETRY_COUNT,
  NAK_TIMEOUT,
  soh_stx_can,
  sizeof (soh_stx_can)
  };

STATIC struct send_n_wait_st pack_nak =
  {
  NAK,
  NAK_RETRY_COUNT,
  NAK_TIMEOUT,
  soh_stx_can_eot,
  sizeof (soh_stx_can_eot)
  };

STATIC struct send_n_wait_st pack_ack =
  {
  ACK,
  ACK_RETRY_COUNT,
  ACK_TIMEOUT,
  soh_stx_can_eot,
  sizeof (soh_stx_can_eot)
  };

/*-----------------------------------------------
Error messages for error enums.
-----------------------------------------------*/
STATIC char *xmodem_errors [] =
  {
  "Transmission Successful",
  "NULL Transmit function pointer",
  "NULL Receive function pointer",
  "Receiver cancelled the transfer",
  "Sender cancelled the transfer",
  "User cancelled the transfer",
  "Error reading the flle",
  "Error writing the file",
  "Timed out waiting for data pack ACK",
  "Timed out waiting for initial NAK",
  "Timed out waiting for SOH",
  "Timed out waiting for data",
  "Timed out waiting for final ACK",
  "Invalid char waiting for SOH",
  "Block mismatch in packet header",
  "CRC is incorrect",
  "Checksum is incorrect",
  "Block out of sequence",
  "Received character error",
  "Modem is not online",
  };

/*-----------------------------------------------
      Local Function Prototypes
-----------------------------------------------*/
STATIC int xm_send_n_wait (
  const struct send_n_wait_st *req,    /* request structure */
  unsigned char *response,     /* response from sender */
  xfunc *xmf);             /* xmodem external functions */

STATIC int xm_block_start (
  xblock *xb,          /* xmodem block data */
  unsigned char block_start,   /* block start char from sender */
  xfunc *xmf);         /* xmodem external functions */

STATIC int xm_recv_block (
  xblock *xb,          /* xmodem block data */
  register xfunc *xmf);        /* xmodem external functions */

/*-----------------------------------------------
This function receives a file transferred via
XMODEM, XMODEM-1K or XMODEM/CRC. The f argument
represents the file to receive that has been
opened for writing.
-----------------------------------------------*/
int xmodem_recv (
  FILE *f,                 /* file to write to */
  int (*transmit) (char),          /* xmit function */
  int (*receive) (long, unsigned int *),   /* recv function */
  void (*dispstat) (long, long, const char *), /* display function */
  int (*check_abort) (void))           /* manual abort function */
{
register int error;        /* gen purpose error var */
unsigned char start_char;  /* first char of block */
unsigned char next_block;  /* next block we expect */
unsigned char last_block;  /* last successful block */
xblock xb;       /* xmodem block data */
xfunc xmfuncs;       /* xmodem external functions */

/*-----------------------------------------------
Initialize the function pointer structure.
-----------------------------------------------*/
if ((xmfuncs.dispstat = dispstat) == NULL)
  xmfuncs.dispstat = xm_no_disp_func;

if ((xmfuncs.check_abort = check_abort) == NULL)
  xmfuncs.check_abort = xm_no_abort_func;

if ((xmfuncs.transmit = transmit) == NULL)
  return (xm_perror (XERR_XMIT_FUNC, &xmfuncs));

if ((xmfuncs.receive = receive) == NULL)
  return (xm_perror (XERR_RCVR_FUNC, &xmfuncs));

/*-----------------------------------------------
Initialize data for the first block and purge
all data from the receive buffer. Init the
number of bytes and blocks received and display
some useful info.
-----------------------------------------------*/
next_block = last_block = 1;

xb.total_block_count = 0L;
xb.total_byte_count = 0L;

(*xmfuncs.dispstat) (0L, 0L, "");

PURGE_RECEIVER(receive);

/*-----------------------------------------------
Attempt to transfer using CRC-16 error detection.
This involves sending the CRC begin character:
'C'.
-----------------------------------------------*/
xb.crc_used = 1;
error = xm_send_n_wait (&crc_req,
         &start_char,
         &xmfuncs);

/*-----------------------------------------------
If the sender did not respond to the CRC-16
transfer request, then attempt to transfer using
checksum error detection.
-----------------------------------------------*/
if (error == XERR_SOH_TIMEOUT)
  {
  xb.crc_used = 0;
  error = xm_send_n_wait (&checksum_req,
           &start_char,
           &xmfuncs);
  }

/*------------------------------------------------
If begin transfer request failed, return error.
------------------------------------------------*/
if (error != XERR_OK)
  return (error);

/*------------------------------------------------
If the starting character of the next block is
an EOT, then we have completed transferring the
file and we exit this loop. Otherwise, we init
the xmodem packet structure based on the first
character of the packet.
------------------------------------------------*/
while (start_char != EOT)
  {
  register int good_block; /* NZ if packet was OK */
  
  error = xm_block_start (&xb,
           start_char,
           &xmfuncs);
  
  if (error != XERR_OK)
    return (error);
  
  good_block = -1;  /* assume packet will be OK */

/*------------------------------------------------
Receive the packet. If there was an error, then
NAK it. Otherwise, the packet was received OK.
------------------------------------------------*/
  if (xm_recv_block (&xb, &xmfuncs) != XERR_OK)
    {
    good_block = 0;        /* bad block */
    }

/*------------------------------------------------
If this is the next expected packet, then append
it to the file and update the last and next
packet vars.
------------------------------------------------*/
  else if (xb.block_num == next_block)
    {
    int bytes_written; /* bytes written for this block */
  last_block = next_block;
  next_block = (next_block + 1) % 256;
  
  bytes_written = fwrite (xb.buffer, 1, xb.buflen, f);
  
  xb.total_block_count++;
  xb.total_byte_count += bytes_written;
  
  (*xmfuncs.dispstat) (xb.total_block_count,
         xb.total_byte_count,
         NULL);
  
  if (bytes_written != xb.buflen)
    {
    xm_send_cancel (transmit);
    return (xm_error (XERR_FILE_WRITE, &xmfuncs));
    }
  }

/*------------------------------------------------
If this is the previous packet, then the sender
did not receive our ACK to that packet and
resent it. This is OK. Just ACK the packet.

If the block number for this packet is completely
out of sequence, cancel the transmission and
return an error.
------------------------------------------------*/
  else if (xb.block_num != last_block)
    {
    xm_send_cancel (transmit);
    return (xm_perror (XERR_BLOCK_SEQUENCE, &xmfuncs));
    }

/*------------------------------------------------
Here, good_block is non-zero if the block was
received and processed with no problems. If it
was a good block, then we send an ACK. A NAK is
sent for bad blocks.
------------------------------------------------*/
  if (good_block)
    {
    error = xm_send_n_wait (&pack_ack,
             &start_char,
             &xmfuncs);
    }
  else
    {
    PURGE_RECEIVER(receive);
    error = xm_send_n_wait (&pack_nak,
             &start_char,
             &xmfuncs);
    }
  
  if (error != XERR_OK)
    return (error);
  }

/*------------------------------------------------
The whole file has been received, so attempt to
send an ACK to the final EOT.
------------------------------------------------*/
if ((*transmit) (ACK) != XMIT_OK)
  return (xm_perror (XERR_OFFLINE, &xmfuncs));

return (xm_perror (XERR_OK, &xmfuncs));
}

/*------------------------------------------------
Dummy function used in case caller did not supply
a display function.
------------------------------------------------*/
void xm_no_disp_func (
  long a,
  long b,
  const char *buf)
{
a = a;
b = b;
buf = buf; /* avoid compiler warnings */
}

/*------------------------------------------------
Dummy function used in case caller did not supply
a used abort function.
------------------------------------------------*/
int xm_no_abort_func (void)
{
return (0);
}

/*------------------------------------------------
This function transmits a character and waits for
a response. The req argument points to a
structure containing info about the char to
transmit, retry count, timeout, etc. The
response argument points to a storage place for
the received character. The xmf argument points
to a structure of caller supplied functions.

Any errors encountered are returned.
------------------------------------------------*/
STATIC int xm_send_n_wait (
  const struct send_n_wait_st *req,    /*request structure */
  unsigned char *response,     /* response from sender */
  register xfunc *xmf)         /* xmodem external functions */
{
int j;
unsigned int rerr;

for (j = 0; j < req->retry_count; j++)
  {
  register int rcvd_char;
  register int i;

/*------------------------------------------------
Check to see if the user aborted the transfer.
------------------------------------------------*/
  if ((*xmf->check_abort) ( ) != 0)
    {
    xm_send_cancel (xmf->transmit);
    return (xm_perror (XERR_USER_CANCEL, xmf));
    }

/*------------------------------------------------
Transmit the block response (or block start)
character.
------------------------------------------------*/
  if ((*xmf->transmit) (req->char_to_send) != XMIT_OK)
    return (xm_perror (XERR_OFFLINE, xmf));

/*------------------------------------------------
Wait for a response. If there isn't one or if a
parity or similar error occurred, continue with
next iteration of the retry loop.
------------------------------------------------*/
  rcvd_char = (*xmf->receive) (req->ms_timeout, &rerr);
  if (rcvd_char == RECV_TIMEOUT)
    continue;
  
  if (rerr != 0)
    return (xm_perror (XERR_ CHAR_ERROR, xmf));

/*------------------------------------------------
Initialize the response and check to see if it is
valid.
------------------------------------------------*/
  if (response != NULL)
    *response = (unsigned char) rcvd_char;
  
  for (i = 0; i < req->num_valid_responses; i++)
    if (rcvd_char == req->valid_responses [i])
      return (XERR_OK);
  }

return (xm_perror (XERR_SOH_TIMEOUT, xmf));
}

/*------------------------------------------------
This function analyzes valid block start
characters to determine block size or in the case
of CAN whether to abort the transmission.

Any errors encountered are returned.
------------------------------------------------*/
STATIC int xm_block_start (
  register xblock *xb,    /* xmodem block data */
  unsigned char block_start,  /* block start char from sender */
  register xfunc *xmf)    /* xmodem external functions */
{
switch (block_start)
  {
  case SOH:               /* NORMAL 128-byte block */
    xb->buflen = XMODEM_BLOCK_SIZE;
    return (XERR_OK);
  
  case STX:               /* 1024-byte block */
    xb->buflen = XMODEM_1K_BLOCK_SIZE;
    return (XERR_OK);
  
  case CAN:               /* Abort signal */
    if ((*xmf->receive) (CAN_TIMEOUT, NULL) == CAN)
      {
      xm_send_cancel (xmf->transmit);
      return (xm_perror (XERR_SEND_CANCEL, xmf));
      }
    break;
  }

return (xm_perror (XERR_INVALID_SOH, xmf));
}

/*------------------------------------------------

This function receives the block numbers, block
data, and block checksum or CRC. The received
data is stored in the structure pointed to by the
xb argument. The block numbers are compared, and
the checksum or CRC is verified.

Any errors encountered are returned.
------------------------------------------------*/
STATIC int xm_recv_block (
  xblock *xb,         /* xmodem block data */
  register xfunc *xmf)    /* xmodem external functions */
{
register int i;
unsigned int rerr;    /* receive error */

/*------------------------------------------------
Attempt to receive the block number. If the
receiver timesout or if there is a receive error,
ignore the rest of the packet.
------------------------------------------------*/
if ((i = (*xmf->receive) (DATA_TIMEOUT, &rerr)) == RECV_TIMEOUT)
  return (xm_perror (XERR_DATA_TIMEOUT, xmf));

if (rerr != 0)
  return (xm_perror (XERR_CHAR_ERROR, xmf));

xb->block_num = (unsigned char) i;



/*------------------------------------------------
Attempt to receive the one's complement of the
block number.
------------------------------------------------*/
if ((i = (*xmf->receive) (DATA_TIMEOUT, &rerr)) == RECV_TIMEOUT)
  return (xm_perror (XERR_DATA_TIMEOUT, xmf));

if (rerr != 0)
  return (xm_perror (XERR_CHAR_ERROR, xmf));

xb->not_block_num = (unsigned char) i;

*/------------------------------------------------
Make sure that the block number and one's
complemented block number agree.
------------------------------------------------*/
if ((255 - xb->block_num) != xb->not_block_num)
  return (xm_perror (XERR_INVALID_BLOCK_NUM, xmf));

/*------------------------------------------------
Clear the CRC and checksum accumulators and
receive the data block.
------------------------------------------------*/
xb->crc = 0;
xb->checksum = 0;

for (i = 0; i < xb->buflen; i++)
  {
  int rcvd_char;
  
  if ((rcvd_char = (*xmf->receive) (DATA_TIMEOUT, &rerr)) ==
      RECV_TIMEOUT)
    return (xm_perror (XERR_DATA_TIMEOUT, xmf));
  
  if (rerr != 0)
    return (xm_perror (XERR_CHAR_ERROR, xmf));
  
  xb->buffer [i] = (unsigned char) rcvd_char;
  
  if (xb->crc_used != 0)
    xb->crc = xm_update_CRC (xb->crc, xb->buffer [i]);
  else
    xb->checksum += xb->buffer [i];


}

/*------------------------------------------------
Validate the CRC.
------------------------------------------------*/
if (xb->crc_used)
  {
  if ((i = (*xmf->receive) (DATA_TIMEOUT, &rerr)) == RECV_TIMEOUT)
    return (xm_perror (XERR_DATA_TIMEOUT, xmf));
  
  if (rerr != 0)
    return (xm_perror (XERR_CHAR_ERROR, xmf));
  
  if ((unsigned char) i != (unsigned char ) (xb->crc >> 8))
    return (xm_perror (XERR_INVALID_CRC, xmf));
  
  if ((i = (*xmf->receive) (DATA_TIMEOUT, &rerr)) == RECV_TIMEOUT)
    return (xm_perror (XERR_DATA_TIMEOUT, xmf));
  
  if (rerr != 0)
    return (xm_perror (XERR_CHAR_ERROR, xmf));
  
  if ((unsigned char) i != (unsigned char) (xb->crc & 0xFF))
    return (xm_perror (XERR_INVALID_CRC, xmf));
  }


/*------------------------------------------------
Validate the checksum.
------------------------------------------------*/
else
  {
  if ((i = (*xmf->receive) (DATA_TIMEOUT, &rerr)) == RECV_TIMEOUT)
    return (xm_perror (XERR_DATA_TIMEOUT, xmf));
  
  if (rerr != 0)
    return (xm_perror (XERR_CHAR_ERROR, xmf));
  
  if ((unsigned char) i != xb->checksum)
    return (xm_perror (XERR_INVALID_CHECKSUM, xmf));
  }

return (XERR_OK);
}

/*------------------------------------------------
This function prints an XMODEM status message
using the caller supplied display function. The
error argument is returned.
------------------------------------------------*/
int xm_perror (
  int error,      /* error number */
  xfunc *xmf)     /* xmodem external functions */
{
xmf->dispstat (-1L, -1L, xmodem_errors [error]);
return (error);
}
September 1990/XMODEM, XMODEM-1K, And XMODEM/CRC/Listing 3

Listing 3

/* ----------------------------------------------
XMODEMS.C

Author    Date       Description
-------------------------------------------
Jon Ward  22 Apr 90  Initial Revision.
Jon Ward  23 Apr 90  Cleanup and modify for
            XMODEM-1K and XMODEM-CRC.
Jon Ward  26 Apr 90 Corrected implementation of
            XMODEM-CRC.
-----------------------------------------------*/
#define XMODEM_LIB 1

#include <stdio.h>
#include "xmodem.h"

#define STATIC static /* undef for debugging */


/*------------------------------------------------
     Local Function Prototypes
------------------------------------------------*/
STATIC int xm_send file (
  FILE *f,    /* file pointer */
  xblock *xb,   /* pointer to block data */
  xfunc *xmf);  /* xmodem external functions */

STATIC int xm_send_block (
  xblock *xb,   /* pointer to block data */
  xfunc *xmf);  /* xmodem external functions */



/*------------------------------------------------
This function sends a file via XMODEM, XMODEM-1K
or XMODEM/CRC. The f argument represents the
file to send that has been opened for reading.
------------------------------------------------*/
int xmodem_send (
  int block_size,             /* maximum block size */
  FILE *f,                /* file to write to */
  int (*transmit) (char),          /* xmit function */
  int (*receive) (long, unsigned int *),   /* recv function */
  void (*dispstat) (long, long, const char *),  /* display function */
  int (*check_abort) (void))            /* manual abort function */
{
xblock xb          /* block data */
xfunc xmfuncs;        /* xmodem external functions */
register int error_count;  /* counter for errors */
register int can_count;        /* cancel counter */
int error;          /* gen error var */
unsigned int rerr;      /* received char error */



/*-------------------------------------------------
Initialize the function pointer structure.
-------------------------------------------------*/
if ((xmfuncs.dispstat = dispstat) == NULL)  
  xmfuncs.dispstat = xm_no_disp_func;
  
if ((xmfuncs.check_abort = check_abort) == NULL)
  xmfuncs.check_abort = xm_no_abort_func;
  
if ((xmfuncs.transmit = transmit) == NULL)
  return (xm_perror (XERR_XMIT_FUNC, &xmfuncs));
  
if ((xmfuncs.receive = receive) == NULL)
  return (xm_perror (XERR_RCVR_FUNC, &xmfuncs));


/*-------------------------------------------------
-------------------------------------------------*/
xb.total_block_count = 0L;
xb.total_byte_count = 0L;

(*xmfuncs.dispstat) (0L, 0L, "");

PURGE_RECEIVER(receive);

/*-------------------------------------------------
Purge all data from the receive buffer. Then we
wait for 1 full minute. If we receive no
characters during that time, then we return
indicating the file was not transferred. If we
receive a NAK, then we begin transmission. If
we receive an excessive number of garbage
characters, then we return indicating no file
transfer.
-------------------------------------------------*/
for (error_count : 0, can_count = 0; 1; )
  {
  int rcvd_char;
  
  if (error_count >= START_XMIT_RETRY_COUNT)
    return (xm_perror (XERR_NAK_TIMEOUT, &xmfuncs));
  
  /* try for 1 minute */
  rcvd_char = (*xmfuncs.receive) (START_XMIT_TIMEOUT, &rerr);
  
  if (rerr != 0)
    {
    xm_send_cancel (xmfuncs.transmit);
    return (xm_perror (XERR_CHAR_ERROR, &xmfuncs));
    }
  
  
  switch (rcvd_char)
    {
    case RECV_TIMEOUT:
      xm_send_cancel (xmfuncs.transmit);
      return (xm_perror (XERR_NAK_TIMEOUT, &xmfuncs));
    
    case 'C':
      xb.crc_used = 1;
      break;
    
    case NAK:
      xb.crc_used = 0;
      break;
    
    case CAN:
      if (++can_count >= CAN_COUNT_ABORT)
  {
  xm_send_cancel (xmfuncs.transmit);
  return (xm_perror (XERR_RCVR_CANCEL, &xmfuncs));
  }
      continue;
    
    default:
      can_count = 0;

      error_count++;
      continue;
    }
  
  break;
  }



/*------------------------------------------------
Setup the block size and the start of block
character and send the file.
------------------------------------------------*/
if (block_size == XMODEM_1K_BLOCK_SIZE)
  {
  xb.buflen = XMODEM_1K_BLOCK_SIZE;
  xb.start_char = STX;
  }
else
  {
  xb.buflen = XMODEM_BLOCK_SIZE;
  xb.start_char = SOH;
  }
  
if ((error = xm_send_file (f, &xb, &xmfuncs)) != XERR_OK)
  return (error);

/*-------------------------------------------------
Now, we send an EOT to the receiver and we
expect to get an ACK within 1 minute. If we
don't then we timeout and report an error. If
we get any character other than an ACK, we
retransmit the EOT. If we get an ACK, then we
exit with hopeful success.
-------------------------------------------------*/
while (1)
  {
  if ((*xmfuncs.transmit) (EOT) != XMIT_OK)
    return (xm_perror (XERR_OFFLINE, &xmfuncs));
  
  switch ((*receive) (START_XMIT_TIMEOUT, &rerr))
    {
    case RECV_TIMEOUT:
      return (xm_perror (XERR_LAST_ACK_TIMEOUT, &xmfuncs));
    
    case ACK:
      if (rerr != 0)
   continue;
   
      break;
    
    default:
      continue;
    }
  
  break;
  }
  
return (xm_perror (XERR_OK, &xmfuncs));
}


/*------------------------------------------------
This function transmits the file associated with
the file pointer f according to the requirements
of the XMODEM transfer protocol.
------------------------------------------------*/
STATIC int xm_send_file (
  FILE *f,        /* file pointer */
  register xblock *xb,    /* pointer to block data */
  register xfunc *xmf)    /* xmodem external functions */
{
int done;

/*------------------------------------------------
Repeat until we have sent the whole file.
------------------------------------------------*/
for (done = 0, xb->block_num = 1; !done; xb->block_num++)
  {
  size_t bytes_read;
  int error;



/*------------------------------------------------
Read a block of data. If it was not a full
block, then check for a file error. If it was a
file error, cancel the transfer and return an
error. Otherwise, pad the remainder of the
block with CPM EOFs.
------------------------------------------------*/
  bytes_read = fread (xb->buffer, 1, xb->buflen, f);
  
  if (bytes_read != xb->buflen)
    {
    
    register int i;
    
    if (ferror (f))
      {
      xm_send_cancel (xmf->transmit);
      return (xm_perror (XERR_FILE_READ, xmf));
      }
    
    for (i = bytes_read; i < xb->buflen; i++)
      xb->buffer [i] = CPMEOF;
    
    done = -1;
    }

/*-------------------------------------------------
Calculate the one's complement block number and
send the block.
-------------------------------------------------*/
  xb->not_block_num = (unsigned char) (255 - xb->block_num);
  
  if ((error = xm_send_block (xb, xmf)) != XERR_OK)
    return (error);
  }

return (XERR_OK);
}


/*-------------------------------------------------
This function transmits the block described by the
structure pointed to by the xb argument. The
transmit and receive arguments point to functions
that transmit and receive data to and from the
modem. A value of 0 is returned to indicate that
the block was successfully sent. A non-zero
return value indicates an offline condition.
-------------------------------------------------*/
STATIC int xm_send_block (
  register xblock *xb,  /* pointer to block data */
  register xfunc *xmf)  /* xmodem external functions */
{
int error_count;

error_count = 0;

while (1)
  {
  int i;
  int can_count;   /* number of CANS received */
  int block_resp;  /* response to block */
  unsigned rerr;   /* received char error */

/*------------------------------------------------
If there were too many errors, then exit with
error.
------------------------------------------------*/
  if (error_count > BLOCK_RETRY_COUNT)
    {
    xm_send_cancel (xmf->transmit);
    return (xm_perror (XERR_ACK_TIMEOUT, xmf));
    }

/*------------------------------------------------

Check for user abort and process it if
applicable.
------------------------------------------------*/
  if ((*xmf->check_abort) () != 0)
    {
    xm_send_cancel (xmf->transmit);
    return (xm_perror (XERR_USER_CANCEL, xmf));
    }


/*------------------------------------------------
Transmit the block start character (it's
different depending on whether we're sending
1024-BYTE or 128-BYTE packets.

Send the block number.

Send the one's complement of the block number.
------------------------------------------------*/
  if ((*xmf->transmit) (xb->start_char) != XMIT_OK)
    return (xm_perror (XERR_OFFLINE, xmf));
  
  if ((*xmf->transmit) (xb->block_num) != XMIT_OK)
    return (xm_perror (XERR_OFFLINE, xmf));
  
  if ((*xmf->transmit) (xb->not_block_num) != XMIT_OK)
    return (xm_perror (XERR_OFFLINE, xmf));

/*------------------------------------------------
Clear the CRC and checksum and send the data
block while building the CRC or checksum.
------------------------------------------------*/
  xb->crc = 0;
  xb->checksum = 0;
  
  for (i = 0; i < xb->buflen; i++)
    {
    if ((*xmf->transmit) (xb->buffer [i]) != XMIT_OK)
      return (xm_perror (XERR_OFFLINE, xmf));
    
    if (xb->crc_used != 0)
      xb->crc = xm_update_CRC (xb->crc, xb->buffer [i]);
    else
      xb->checksum += xb->buffer [i];
    }




/*------------------------------------------------
Send the CRC or checksum. If we send the CRC,
we must send the High BYTE first.
------------------------------------------------*/
  if (xb->crc_used == 0)
    {
    if ((*xmf->transmit) (xb->checksum) != XMIT_OK)
      return (xm_perror (XERR_OFFLINE, xmf));
    }
  else
    {
    if ((*xmf->transmit) ((unsigned char) (xb->crc >> 8)) != XMIT_OK)
      return (xm_perror (XERR_OFFLINE, xmf));
    
    if ((*xmf->transmit) ((unsigned char) (xb->crc & 0xFF)) != XMIT_OK)
      return (xm_perror (XERR_OFFLINE, xmf));
    }




/*------------------------------------------------
Wait for the receiver to respond with an ACK or
a NAK. If we timeout waiting, then return an
error code. If we get a NAK, retransmit the
block. If we gat an ACK, then return with
status OK. If we receive 2 consecutive CANs,
then we abort the transmission.
------------------------------------------------*/
  can_count = 0;

GET_BLOCK_RESPONSE:
  block_resp = (*xmf->receive) (BLOCK_RESP_TIMEOUT, &rerr);
  
  if (rerr != 0)
    {
    xm_perror (XERR_CHAR_ERROR, xmf);
    goto GET_BLOCK_RESPONSE;
    }
  switch (block_resp)
    {
    case ACK:
      error_count = 0;
      
      xb->total_block_count++;
      xb->total_byte_count += xb->buflen;
      
      (*xmf->dispstat) (xb->total_block_count,
          xb->total_byte_count,
          NULL);
      break;
    
    case CAN:
      if (++can_count >= CAN_COUNT_ABORT)
  {
  xm_send_cancel (xmf->transmit);
  return (xm_perror (XERR_RCVR_CANCEL, xmf));
  }
    goto GET_BLOCK_RESPONSE;

/*------------------------------------------------
I have seen 2 ways of handling this problem. One
is to return an error indicating that the sender
never received a packet ACK. Another method
retransmits the packet just sent in hopes that
the receiver will get its ACK together. The
second one is what I did.
------------------------------------------------*/
    case RECV_TIMEOUT:
      error_count++;
#if 0
      return (xm_perror (XERR_ACK_TIMEOUT, xmf));
#else
      xm_perror (XERR_ACK_TIMEOUT, xmf);
      continue;
#endif

    case NAK:
      xm_perror (XERR_BLOCK_NAK, xmf);
      error_count++;
      continue;
    
    default:
      goto GET_BLOCK_RESPONSE;
    }
  
  break;
  }

return (XERR_OK);
}



/*------------------------------------------------
This function updates a CRC accumulator for xmodem
CCITT CRC.
------------------------------------------------*/
unsigned int xm_update_CRC (
  register unsigned int crc,  /* current CRC */
  unsigned int c)  /* character to add to CRC */
{
static unsigned int crc_polynomial = 0x1021;
register int i;

c <<= 8;

for (i = 0; i < 8; i++)
  {
  if ((c ^ crc) & 0x8000)
    crc = (crc << 1) ^ crc_polynomial;
  else
    crc <<= 1;
    
    c <<= 1;
    }
  
  return (crc);
  }



/*------------------------------------------------
This function sends 8 CANs and 8 BSs as done by
YAM. This is used to cancel an X or Y Modem send
or receive.
------------------------------------------------*/
void xm_send_cancel (
  int (*transmit) (char))  /* transmit function */
{
register int i;

for (i = 0; i < 8; i++)
  (*transmit) (CAN);

for (i = 0; i < 8; i++)
  (*transmit) (BACKSPACE);
}

September 1990/XMODEM, XMODEM-1K, And XMODEM/CRC

XMODEM, XMODEM-1K, And XMODEM/CRC

Jonathan Ward


Jonathan Ward is a software engineer for PANDE, Inc. You may contact him at 14275 Midway Rd., Suite 220, Dallas, TX 75244 (214) 242-8628.

XMODEM, created in 1978 by Ward Christensen, is probably the most available file transfer protocol. XMODEM's simplicity and easy implementation have made it an immediate success. This article will discuss the original XMODEM protocol, as well as several of its many enhancements.

Original XMODEM

XMODEM is a half duplex, 8-bit protocol which transfers files by sending a portion of the file in a data structure called a packet and then waiting for a response to that packet. If the response is positive (indicating successful transmission) the next packet is sent. If the response is negative, the packet is resent.

The original XMODEM protocol transferred 128-byte data blocks with a 1-byte checksum for error detection. The layout of the XMODEM packet is:

SOH(0-01)
Packet Number
1's Complement of Packet Number
Data (128 bytes)
Arithmetic checksum

All fields except the data field are one byte. The data field contains 128 bytes of either ASCII or binary data that is usually obtained from the file being transferred.

Figure 1 shows a simplified progression of a typical transmission. The receiver initiates the transfer by asking the sender to transmit the first data packet. For the original XMODEM, this start character is a NAK (0x15). Once a packet has been sent, the sender waits for the receiver to respond to the packet. If the receiver responds with an ACK (0x06), indicating that the packet was received successfully, the sender transmits the next packet. A NAK indicates the receiver did not receive the packet successfully and sender resends the packet.

After transferring all the packets, the sender signals end-of-transmission with an EOT (0x04) and waits for an ACK. The transmission is complete once the sender receives an ACK.

Receiver Time-Outs

The sender and receiver do not wait indefinitely for responses. The protocol sets maximum time and retry figures for the different transmission states.

The receiver initiates the transfer by sending a start character. If no data is received within 10 seconds, the start character is sent again. This process is repeated 10 times before the receiver gives up.

While receiving the packet data, the receiver allows a one-second time-out for each character. There is no retry count. If more than one second elapses between received characters, the packet is NAKed.

After receiving a packet, the receiver sends the packet response. The receiver then waits for the beginning of a packet or for an EOT. If it doesn't receive characters within 10 seconds, the packet response is sent again. If the receiver receives nothing after transmitting the response code 10 times, it aborts the download.

There is no time-out or retry on the receiver for the final ACK.

Sender Time-Outs

Once started, the sender simply waits for a start character from the receiver. If the sender doesn't receive a character or if it counts more than 10 invalid start characters within 60 seconds, it aborts the transfer.

After transferring a packet, the sender waits for the response from the receiver. The time-out for the response is 10 seconds. If no response is received within that time, the packet is resent. After 10 retransmissions of the same packet, the transfer will be aborted.

The final EOT has a time-out of 60 seconds. If no ACK is received within that time, the sender aborts the transfer and returns an appropriate error.

Relaxed Time-Outs

Several implementations of XMODEM offer a feature called relaxed time-outs or relaxed timing. Relaxed timing allows extra time between characters and packets for large systems like CompuServe. I have excluded relaxed timing from my implementation.

Graceful Abort

Original XMODEM does not provide any method for aborting a download in progress. However, the XMODEM protocol now supports a graceful abort enhancement. When the sender is waiting for a start character, an ACK or NAK after packet transmission, or the final ACK, a CAN (0x18) character indicates that the receiver has aborted the download.

Likewise, when the receiver is waiting for the first character of the packet, reception of a CAN character indicates that the sender has aborted the download.

Some implementations of XMODEM abort the download after receiving a single CAN, while others abort after three consecutive CAN characters. My implementation requires two consecutive CANs to abort. Because line noise can some sometimes garble characters into a CAN, requiring multiple consecutive CAN characters avoids aborting transfers due to line noise.

If you are trapped in an XMODEM transfer and wish to abort, hit CTRL-X several times. CTRL-X transmits the CAN character and should abort the transfer.

If the receiver or sender encounters an unrecoverable error condition, XMODEM should abort the transfer on both ends. My implementation sends eight CAN characters followed by eight BACKSPACES (0x08) (so that the CANs are not displayed as received data).

XMODEM-1K

XMODEM-1K, an enhanced XMODEM, transmits 1,024 bytes of data per packet rather than the original 128 bytes. To indicate the larger packet size to the receiver, the first character of the packet sent is an STX (0x02). The layout of the XMODEM-1K packet is:

STX (0x02)
Packet Number
1's Complement of Packet Number
Data (1024 bytes)
Arithmetic checksum

XMODEM-1K may send data in either 128-byte packets or in the larger 1,024-byte packets. This flexibility necessitates the STX in the 1K packets. Once a packet has been sent as either 128-byte or 1,024-byte, it can't be resent in a different size packet.

Other than the larger packet size and the ability to mix large and small packets, XMODEM-1K does not differ from XMODEM.

XMODEM/CRC

XMODEM/CRC offers an increased error detection rate. XMODEM and XMODEM-1K use an arithmetic checksum to detect transfer errors. This 1-byte checksum detects 95 percent of all errors and is generated by summing all the bytes in the data field (excluding the SOH/STX, the packet number, and 1's complement packet number) modulo 256. However, XMODEM/CRC uses a 16-bit CRC (Cyclic Redundancy Check) instead of the checksum. This enhancement causes the error detection rate to exceed 99.997 percent.

The new packet layouts are:

128-byte packet
SOH (0x01)
Packet Number
1's Complement of Packet Number
Data (128 bytes)
16-bit CRC

1024-byte packet
STX (0x02)
Packet Number
1's Complement of Packet Number
Data (1024 bytes)
16-bit CRC

The CRC is calculated by using the binary representation of the data stream as the coefficients of a polynomial. The 128-byte packet would produce a polynomial of degree 1,023 (128 x 8 bits). This polynomial is then divided by the CRC polynomial using modulo-2 arithmetic, the remainder of which is the CRC. For XMODEM, the CRC polynomial is the CCITT polynomial:

X16 + x12 + x5 + 1
The high-byte of the 16-bit CRC is sent first followed by the low-byte.

The receiver determines whether packets are sent using CRC or checksum to detect errors. If the receiver wishes to transfer with CRC error detection, it sends the start character C (0x43) with a three-second time-out and a retry count of four. If the sender cannot transmit using CRC, it ignores the C and continues waiting for a NAK. If sender does not respond to the C start character, the receiver will send a NAK to attempt transfer using the checksum.

The receiver should always attempt to transfer using CRC and then back down to checksum if the CRC attempt fails.

My Implementation

I have implemented XMODEM, XMODEM/CRC, and XMODEM-1K transfer protocols. Rather than developing separate routines to handle each combination of these, I have written one routine for sending and one for receiving. The receive routine attempts to transmit using CRC block checking but will fall back to checksum if the CRC request fails. An argument passed to the send routine specifies which data block size to use (128 or 1,024). See Listing 1 for the header for the XMODEM routines.

Encapsulating XMODEM

I decided to make the transfer protocol oblivious to the serial I/O by providing medium-level serial functions for the transfer routines. Pointers to these functions are passed as arguments to the XMODEM transfer routines simplifying adaptation to different hardware configurations and avoiding the peculiarities of different UARTs, DUARTs, ACIAs, SIOs, etc.

To display transfer status information and manually abandon the transfer, pointers to functions for these purposes are also passed to the transfer routines.

Serial Receive Interface

I constructed two functions as an interface to the low-level serial routines. The first returns a single character received from the serial port. It is declared as:

int uart_receive (long ms_timeout,
     unsigned int *error_flag);
The uart_receive function returns the ASCII code for the received character or a value of RECEIVER_TIMEOUT if a character is not received within the time specified in ms_timeout. RECEIVER_TIMEOUT may be defined as anything that is not a valid ASCII character. I chose -1.

The ms_timeout argument contains the number of milliseconds to wait for a character (if none has already been received). The constant MS_PER_SEC is defined as 1000L to aid in conversions. If the time-out value is 0L and no characters have been received, the uart_receive function should immediately time-out.

The error_flag argument points to an unsigned integer that will be initialized to indicate hardware errors detected, i.e., parity error, overrun. Several constants are defined as different bits in the error flag. They are:

#define RE_OVERRUN_  0x01
#define RE_PARITY_   0x02
#define RE_FRAMING_  0x04
You test these error conditions by ANDing the constant with the variable containing the error flags. A TRUE test indicates that flag is set and the error condition occurred. If you don't have access to these error conditions, then the XMODEM routines need never know them. Simply set the error flag to 0.

Serial Transmit Interface

The second serial interface function transmits characters:

int uart_transmit (char xmit_char);
This function transmits the character value in the xmit_char argument. Two return values are possible: TRANSMITTER_OK indicates there are no errors with the transmitter, TRANSMITTER_OFFLINE indicates the receiving computer is no longer connected. The offline condition can be detected by loss of the carrier detect signal or some other hardware line. It may also be ignored entirely by always returning TRANSMITTER_OK.

You can use these functions with polled or interrupt-driven serial routines. Either way, the functions simplify the transfer protocol by keeping serial I/O out of the way.

Status Display Interface

A function to display the transfer status is declared as

void display_xfer_stats (
   long blocks_transferred,
   long bytes_transferred,
   const char *status_message);
The bytes_transferred argument contains the number of bytes that have been successfully transferred. If it is -1L, the number of bytes displayed should not be disturbed.

The blocks_transferred argument contains the total number of data blocks that have been transferred. If blocks_transferred is -1L, the total data blocks displayed should not be disturbed.

The status_message argument points to the status message to be displayed. It may also be NULL if no status message is to be displayed.

Abort Transfer Interface

A function for testing an abort condition may be provided to the send and receive routines. Its prototype is:

int check_abort (void);
The function returns a non-zero value if the user aborted the transfer manually by pressing the ESC or CTRL-C keys.

Receiving

Listing 2 shows the XMODEM receive function. Its prototype is:

int xmodem_recv (
   FILE *f,
   int   (*transmit) (char),
   int   (*receive) (long,
        unsigned int *),
   void  (*dispstat) (long, long,
        const char *),
   int   (*check_abort) (void));
The f argument points to a file that must already be opened for writing.

To transfer data, the receiver begins by requesting a packet from the sender. The receiver sends a C in an attempt to transfer using CRC instead of checksum for error detection. If it doesn't receive a response within three seconds, it resends the C. This process is repeated four times. If the attempt to use CRCs fails, the receiver reverts to checksum error detection and sends a NAK to request the first data packet. If there is no response within 10 seconds, it sends another NAK. This is repeated 10 times. If there is no response from the sender after 10 NAKs the transmission is automatically aborted.

The only valid characters for the beginning of a packet are: SOH, for a 128-byte packet, STX, for a 1K packet, or two consecutive CANs for an aborted transfer. All other characters are ignored.

SOH and STX signal the beginning of a packet. The next two bytes received are the block number (modulo 256 and starting with one) and the 1's complement of the block number respectively. These two bytes are compared, and if they don't represent the same block number, the packet will be NAKed.

Following the block numbers are the data block and the CRC or checksum. The data portion of the packet is collected and a checksum or CRC is generated. After receiving the data block and the block check, the receiver compares the generated checksum or CRC to the received checksum or CRC. If they are not the same, the packet is NAKed.

Once the receiver successfully receives a packet, it compares the received block number to the expected block and to the previously received block number. If the received block number doesn't match either of these other numbers, the packets have gotten out of sequence and the transmission must be aborted since XMODEM does not resynchronize block numbers that are out of sequence.

If the received block number matches the next expected block number, the block data is written to the file and an ACK is sent.

If the received block number matches the previously received block number, the packet is ACKed, but the data is not written since it was written when the block was successfully received the first time. The received block number might match the previously received block number for two reasons. First, if the ACK for the previous block was converted into a NAK because of line noise, the sender would resend the last packet. Second, some senders may assume that anything that is not an ACK must be a NAK, and would resend the last packet if the ACK was garbled.

After successfully receiving a packet, the receiver must wait for the start of the next packet or an EOT. For the sender to know that the file was successfully received, the EOT must be ACKed.

Sending

Listing 3 shows the XMODEM send function. The prototype for the XMODEM send function is:

int xmodem_send (
   FILE  *f,
   int    (*ransmit) (char),
   int    (*receive) (long,
         unsigned int *),
   void   (*dispstat) (long, long,
         const char *),
   int    (*check_abort) (void));
The f argument points to the file to be transferred. This file must already be open for reading.

The sender is much simpler than the receiver. It begins by waiting for a start character. Valid characters are: C, to indicate CRC block checking; NAK, to indicate checksum block checking; or two consecutive CANs, to indicate that the transfer has been aborted. If the sender receives no character within 60 seconds, or if it receives 10 invalid characters, the transfer is aborted.

After receiving a valid start character, the sender prepares and transmits the first packet, including the block start character (SOH or STX depending on the data size), the block number, the one's complement block number, the data block, and the checksum or CRC.

The sender waits for a response from the receiver. Valid responses are: ACK, indicating that the packet was successfully received; NAK, indicating the packet was not successfully received; or two consecutive CANs, to cancel the transfer. All other characters are invalid and are ignored. If the sender doesn't receive a character within 10 seconds (or receives a NAK), it retransmits the packet. If it receives an ACK, the sender prepares and sends the next packet, and the cycle is repeated. After the last packet is sent and ACKed, the sender sends an EOT to indicate end of transfer. The receiver must ACK to EOT for the sender to know that the transfer completed successfully.

XMODEM Problems

XMODEM presents several problems:

The YMODEM protocol makes several enhancements to XMODEM to solve these problems.

Does It Work?

I have successfully used my XMODEM routines to transfer files to and from local BBSs with great success.

Different implementations of XMODEM handle different events. The length of time-outs varies widely. Some implementations will not fall back to checksum error detection if the sender doesn't recognize the CRC start character. Error situations seem to be handled differently as well. I handled these events following whatever documentation I could find.

After implementation, I discovered a problem with interrupt-driven, buffered serial I/O, which involved the sender's packet response time-out. After transmitting a packet, the sender immediately begins waiting for the response. While this situation works fine with polled I/O, interrupt-driven serial routines buffer the data to send. When the sender starts waiting for the response, very little of the packet may have been sent. At 300 baud, it takes 35 seconds to transfer a 1K packet, and I'd bet that even a 4.77 MHz PC doesn't need 35 seconds to dump 1,000 bytes into a buffer. Even at 1,200 baud, the time-out is cut pretty close. It is left as an exercise for the reader to determine the best method of solving this problem. Here are some suggestions:

Documentation

The best documentation for XMODEM and its derivatives is online. I located a half dozen document files describing the XMODEM protocol. Some were just an overview while others were quite in-depth. Interestingly enough, the texts I own which describe XMODEM don't do nearly as well as the public domain document files.

Conclusion

XMODEM and its extensions are easy to implement. If you need to implement file transfer capability in an application, XMODEM is a good choice if the volume of data to transmit is small or if you are using a slow modem. If you are using a high-speed modem or are transferring large volumes of data, other transfer protocols like YMODEM-G and ZMODEM are more efficient, but a little more difficult to implement.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.