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); }