www.pudn.com > xmodem.zip > XMDM2.C


/*--------------------------------------------------------------- 
                            XMODEM.C 
                    By Donald G. Krantz 4/84 
               (C) Copyright 1984 Donald G. Krantz 
                       All Rights Reserved 
 
            Christensen Protocol Routines for XMODEM.C 
This file contains a stand-alone Christensen Engine, which 
requires the following interface to another system: 
 
error() is called to terminate a transfer: the parameter is a  
string describing the error condition. 
VOID error( str ) 
   char *str; 
 
abort() is called at strategic places to determine if the  
local operator wants to quit sending/receiving. 
VOID abort() 
 
rx() picks up a character from the remote. 
char rx() 
 
rxstat() returns TRUE when a character is ready from the 
remote. 
int rxstat() 
 
tx() sends a character to the remote. 
VOID tx( ch ) 
   char ch; 
 
lcl_str() sends an informational message to the local 
console. 
lcl_str( str ) 
   char *str; 
 
---------------------------------------------------------------*/ 
 
#include "stdio.h"		/* ECO C */ 
#include "b:xmodem.h" 
 
/*--------------------------------------------------------------- 
txfile() does transmission of a file. 
Calling sequence: "fd" is a file pointer to the open file 
to be transmitted. 
 
References globals: buffer; 
Modifies globals: rec, crc; 
---------------------------------------------------------------*/ 
VOID txfile( fd ) 
   FILE *fd; 
{ 
   char ch;			/* scratch character		*/ 
   char *i;			/* buffer pointer (index)	*/ 
   int y;			/* scratch counter		*/ 
 
	rec = 1;		/* uses "natural" numbering	*/ 
	while( rxstat() ) 
		rx(); 
	while( TRUE )		/* handshake w/ receiver	*/ 
	{ 
		if( (ch = wait( 10, TRUE, TRUE )) == NAK ) 
		{ 
			crc = FALSE; 
			break; 
		} 
		if( ch == CRC ) 
		{ 
			crc = TRUE; 
			break; 
		} 
		abort(); 
	} 
	while( fillbuf( fd, buffer ) )	/* file not empty?	*/ 
	{ 
		txrec( buffer );	/* send record		*/ 
		abort(); 
	} 
	tx( EOT ); 
	while( (ch = wait( 1, TRUE, TRUE )) != ACK ) 
	{ 
		tx( EOT );		/* show end		*/ 
	} 
	fclose( fd );			/* dump file		*/ 
} 
 
/*--------------------------------------------------------------- 
txname() transmits a file name from the CP/M fcb format  
parameter 'name'. 
 
References globals: buffer, checksum, crc; 
Modifies globals: crc; 
---------------------------------------------------------------*/ 
VOID txname( name ) 
   char *name; 
{ 
   register int i;		/* scratch counter		*/ 
   char ch;			/* scratch char			*/ 
   char crcsav;			/* holds state of global 'crc'	*/ 
 
	crcsav = crc;		/* we always use checksum for	*/ 
	crc = FALSE;		/* name error detection		*/ 
	while( TRUE )		/* main loop for retries	*/ 
	{ 
		while( rxstat() ) 
			rx(); 
		i = 0;		/* retry count			*/ 
		while( TRUE ) 
		{ 
			if( wait( RETRY, TRUE, TRUE ) == NAK ) 
				break; 
			if( i++ > RETRY ) 
				error( "Can't send filename" ); 
		} 
		tx( ACK );	/* handshake name request	*/ 
		sleep();	/* decent interval		*/ 
		clrcrc();	/* clear checksum accumulator	*/ 
		for( i = 0 ; i < NAMESIZE ; i++ )/* name loop	*/ 
		{ 
			tx( name[ i ] ); 
			wait( 10, TRUE, TRUE );/* wait for ACK	*/ 
		} 
		tx( EoF );		/* name terminated w/EoF*/ 
		if(wait(10,FALSE,TRUE)==checksum) /* handshake	*/ 
		{ 
			tx( ACK );	/* handshake OK checksum*/ 
			crc = crcsav;	/* replacce 'crc'	*/ 
			return;		/* normal exit		*/ 
		} 
		tx( BADNAME );		/* handshake bad chksm	*/ 
	} 
} 
 
/*--------------------------------------------------------------- 
txrec() transmits a single record, with retransmit on error from 
receiver. Input is a pointer to the I/O buffer. 
 
References globals: rec, crcaccum, checsum, crc; 
Modifies globals: rec; 
---------------------------------------------------------------*/ 
VOID txrec( buf ) 
   char *buf; 
{ 
   register int i; 
   unsigned cr; 
 
	while( TRUE )			/* do it until right	*/ 
	{ 
		sprintf( msg, "\rTransmitting record %d   ", rec ); 
		lcl_str( msg ); 
		tx( SOH );		/* start of header	*/ 
		tx( rec );		/* rec #		*/ 
		tx( ~rec );		/* 1's comp		*/ 
		clrcrc();		/* clear CRC accum	*/ 
		for( i = 0 ; i < RECSIZE ; i++ ) 
			tx( buf[ i ] );	/* send record		*/  
		updcrc( 0 );		/* finish up CRC	*/ 
		updcrc( 0 );		/* again		*/ 
		cr = crcaccum; 		/* save crc lobyte	*/ 
		if( crc )		/* send hi byte first	*/ 
		{ 
			tx( crcaccum >> 8 ); 
			tx( cr ); 
		} 
		else 
			tx( checksum ); 
		if( wait(10, TRUE, TRUE)==ACK)/* quit if correct*/ 
			break;		 
	} 
	rec++;				/* bump record count	*/ 
} 
 
/*--------------------------------------------------------------- 
rxname() loads a CP/M style fcb with a filename from remote 
sender. On receipt of an EOT instead of a name character, exits 
---------------------------------------------------------------*/ 
VOID rxname( fcb ) 
   char *fcb;			/* points to CP/M style fcb	*/ 
{ 
   char *fcbptr;		/* index to fcb			*/ 
   register int i;		/* scratch counter		*/ 
   int ch;			/* scratch char	(must hold ERR)	*/ 
   char chksum;			/* checksum accumulator		*/ 
 
	while( TRUE ) 
	{ 
		fcbptr = fcb;		/* align index		*/ 
		i = RETRY * 5;		/* retry for name h.s.	*/ 
		while( TRUE )		/* Handshake NAK	*/ 
		{ 
			abort();	/* check operator abort	*/ 
			tx( NAK );	 
			if( wait( 1, FALSE, FALSE) == ACK ) 
				break;	 
			if( !( i-- ) ) 
				error( "Timed out waiting for name" ); 
		} 
		chksum = EoF;		/* init checksum	*/ 
		for( i = 0 ; i < 34 ; i++ )	/* accept noise	*/ 
		{ 
			if( (ch = wait(1,FALSE,FALSE)) == EOT ) 
			{ 
				tx( ACK ); 
				exit( 0 ); 
			} 
			if( ch == EoF )	/* End of name chars	*/ 
				break; 
			if( ch != ERROR )	/* not timeout	*/ 
			{ 
				*(fcbptr++) = ch & 0x7F; 
				chksum += ch & 0x7F; 
			} 
			abort();	/* operator abort	*/ 
			tx( ACK );	/* handshake name char	*/ 
		} 
		fcb[ NAMESIZE ] = 0;	/* terminate name field	*/ 
		do { 
			abort();	/* operator abort	*/ 
			tx( chksum );	/* handshake checksum	*/ 
		} while( (ch = wait(1,FALSE,FALSE)) == ERROR ); 
		if( ch == ACK )		/* ACK is good name	*/ 
			return; 
	} 
} 
	 
/*--------------------------------------------------------------- 
rxfile() receives a file. Input parameter is a file pointer  
to the local file receiving the transmitted file. 
 
References globals: buffer, rec, crcaccum, checksum, crc; 
Modifies globals: buffer, rec; 
---------------------------------------------------------------*/ 
VOID rxfile( fd ) 
   FILE *fd; 
{ 
   char ch;			/* scratch handshake var	*/ 
   char response;		/* ACK/NAK/CRC handshake	*/ 
   char crcrlo;			/* rec'd CRC low byte		*/ 
   char crcrhi;			/* rec'd CRC hi byte		*/ 
   char r1;			/* current record number	*/ 
   char r2;			/* 1's comp record number	*/ 
   unsigned int j;		/* wait loop timer		*/ 
   int i;			/* scratch counter		*/ 
   register char *bptr;		/* buffer index			*/ 
 
	while( rxstat() ) 
		rx(); 
	rec = 1;		/* uses natural numbering	*/ 
	if( crc )		/* set initial handshake to	*/ 
		response = CRC;	/* CRC or checksum		*/ 
	else 
		response = NAK; 
	while( TRUE )		/* record receive loop		*/ 
	{ 
		bptr = buffer;		/* align index		*/ 
		sprintf( msg, "\rWaiting for record %d    ", rec ); 
		lcl_str( msg ); 
		for( i=1 ; i <= RETRY * 5 ; i++ ) 
		{ 
			abort(); 
			tx( response );	/* send handshake	*/ 
			if( (ch = wait(1,TRUE,TRUE)) == SOH ) 
				break;	/* SOH indicatees rec	*/ 
			if( ch == EOT )	/* EOT indicates done	*/ 
			{ 
				fclose( fd ); 
				tx( ACK );	/* handshake 	*/ 
				return;		/* normal exit	*/ 
			} 
			if( ch == CAN )	/* Xmit request abort	*/ 
			{ 
				fclose( fd ); 
				error("\rReceived cancel request"); 
			} 
			if( i == RETRY * 5 )	/* timeout exit	*/ 
				error( "Can't sync to sender" ); 
		} 
		r1 = wait(1,FALSE,FALSE); /* record number	*/ 
		r2 = wait(1,FALSE,FALSE); /* 1's comp record #	*/ 
		while( bptr - buffer < RECSIZE ) /* test count	*/ 
		{ 
			*(bptr++)=wait(1,FALSE,FALSE);/*accept char*/ 
		} 
		if( crc )		/* get hibyte CRC	*/ 
			crcrhi = wait(1,FALSE,FALSE); 
		crcrlo = wait(1,FALSE,FALSE);/* lobyte CRC or chksm*/ 
		response = NAK;		/* init response	*/ 
		if( (~r1 & 0xFF) != (r2 & 0xFF) ) 
			continue; 
		clrcrc();		/* calc checksum/CRC	*/ 
		for( j = 0 ; j < RECSIZE ; j++ ) 
			updcrc( buffer[ j ] ); 
		updcrc( 0 );		/* required to finish	*/ 
		updcrc( 0 );		/* off CRC - why?	*/ 
		if( crc )		/* CRC test		*/ 
			if( (crcrlo + (crcrhi << 8)) != crcaccum ) 
				continue; 
		if( !crc )		/* checksum test	*/ 
			if( crcrlo != checksum ) 
				continue; 
		if( (r1 == (rec - 1) & 0xFF ) )	/* duplicate?	*/ 
		{ 
			response = ACK;	/* dup is OK - ACK was	*/ 
			continue;	/* trashed - ignore it	*/ 
		} 
		if( r1 != (rec & 0xFF) )/* fatal sequence error	*/ 
			error( "File record numbering error" ); 
		rec++;			/* bump record count	*/ 
		for( j = 0 ; j < RECSIZE ; j++ ) /* write data	*/	 
			putc( buffer[ j ], fd ); 
		response = ACK;		/* normal loop end	*/ 
	} 
} 
 
/*--------------------------------------------------------------- 
parse() expands  non - ambiguous filespecs to the 
standard CP/M fcb format, excluding drive byte.  
Inputs are "normal" filespec (inspec), and exanded fcb. 
---------------------------------------------------------------*/ 
char *parse( inspec, fcb ) 
   char *inspec, *fcb; 
{ 
   register int i;			/* fcb index		*/ 
   int inptr;				/* input spec index	*/ 
 
	for( i = 0 ; i < NAMESIZE ; i++ )/* blank fill name	*/ 
		fcb[ i ] = ' '; 
	if( inspec[ 1 ] == ':' )	/* check for drivspec	*/ 
		inptr = 2;		/* point past drivespec	*/ 
	else 
		inptr = 0;		/* index to start	*/ 
	i = 0;				/* pointer into fcb	*/ 
	while( TRUE ) 
		switch( inspec[ inptr++ ] ) 
		{ 
			case '\0':	/* end of input spec	*/ 
				fcb[ NAMESIZE ] = 0; 
				return( fcb ); 
			case '.':	/* extension spec'ed	*/ 
				i = NAMESIZE - 3; /* extension	*/ 
				break; 
			default: 
				if( i < NAMESIZE ) 
					fcb[ i++ ] = toupper(  
					inspec[ inptr - 1 ] ); 
		} 
} 
 
/*--------------------------------------------------------------- 
unparse() reassembles a filename from a CP/M fcb into "normal" or 
cmpressed form, so that our C functions can deal with them. 
Inputs are a pointer to a string to receive the name (name), and 
a pointer to a CP/M style fcb entry (buf). 
---------------------------------------------------------------*/ 
char *unparse( name, buf ) 
   char *name, *buf; 
{ 
   register int i;		/* 'name' index			*/ 
   int j;			/* 'buf' index			*/ 
 
	i = 0;			/* 'driveless' name	*/ 
	for( j = 0 ; j < NAMESIZE ; j++ )/* transfer chars	*/ 
	{ 
		if( buf[ j ] != ' ' )	/* (skip spaces)	*/ 
			name[ i++ ] = buf[ j ] & 0x7F; 
		if( j == NAMESIZE-4 )	/* don't forget dot	*/ 
			name[ i++ ] = '.'; 
	} 
	name[ i ] = '\0';		/* terminate string	*/ 
					/* eat terminal dot	*/ 
	if( *(index( name, '.' ) + 1) == '\0' ) 
		*(index( name, '.' )) = '\0';  
	return( name );			/* return pointer	*/ 
} 
 
/*--------------------------------------------------------------- 
fillbuf() loads the I/O buffer with a record from the input file. 
Returns TRUE if data was available, FALSE if no data left. 
---------------------------------------------------------------*/ 
int fillbuf( fd, buffer ) 
   FILE *fd; 
   char *buffer; 
{ 
   register int i;		/* scratch counter		*/ 
   int errorchk;		/* holds EOF in Eco C		*/ 
 
	for( i = 0 ; i < RECSIZE ; i++ ) 
	{ 
		if( (errorchk = getc( fd)) == EOF ) 
			break; 
		buffer[ i ] = errorchk; 
	} 
	if( i == 0 )			/* no data read		*/ 
		return( FALSE ); 
	for( ; i < RECSIZE ; i++ ) 
		buffer[ i ] = 0;	/* zero fill at EOF	*/ 
	return( TRUE ); 
} 
 
/*--------------------------------------------------------------- 
clrcrc() clears the crc accumulator. Not much to it, actually. 
 
References globals:  
Modifies globals: crcaccum, checksum; 
---------------------------------------------------------------*/ 
VOID clrcrc() 
{ 
	crcaccum =  
	checksum = 0; 
} 
 
/*--------------------------------------------------------------- 
updcrc() updates the crc accumulator, if 'crc' is TRUE, else 
updates the checksum.  
'x' is the byte to be added to CRC or checksum. 
CCITT polynomial. 
 
References globals: crc; 
Modifies globals: crcaccum, checksum; 
---------------------------------------------------------------*/ 
VOID updcrc( x ) 
   char x; 
{ 
   unsigned shifter, i, flag; 
 
	if( crc ) 
	{ 
		for( shifter = 0x80 ; shifter ; shifter >>= 1 ) 
		{ 
			flag = (crcaccum & 0x8000); 
			crcaccum <<= 1; 
			crcaccum |= ((shifter & x) ? 1 : 0); 
			if( flag ) 
				crcaccum ^= 0x1021; 
		} 
	} 
	else 
		checksum += x; 
} 
 
/*--------------------------------------------------------------- 
sleep() does a short delay to account for transmission line 
latency, etc. 
---------------------------------------------------------------*/ 
VOID sleep() 
{ 
   register unsigned int i; 
 
	for( i=0 ; i < MAGIC_NUMBER ; i++ ) 
		; 
} 
 
/*--------------------------------------------------------------- 
wait() waits for a character - timeout built in. Timeout  
condition causes error return to menu. 
  'time' controls duration of wait. 
  'is_cmd' TRUE indicates that CAN recvd will cause program exit 
  'error_out' TRUE indicates timeout causes program exit 
---------------------------------------------------------------*/ 
int wait( time, is_cmd, error_out ) 
   int time; 
{ 
   register unsigned int i;		/* loop timer		*/ 
   int j;				/* timeout count	*/ 
   int ch; 
 
	j =				/* timeout count	*/ 
	i = 0;				/* inner timeout count	*/ 
	while( !rxstat() ) 
		if( i++ > MAGIC_NUMBER )/* about 1.5 secs	*/ 
			if( !(time--) ) /* check retry count	*/ 
				if( error_out ) 
					error( "Receiver timed out" ); 
				else 
					return( ERROR ); 
			else		/* more tries available	*/ 
			{ 
				abort(); /* scan for ^X		*/ 
				if( !j ) /* \n first timeout	*/ 
				{ 
					sprintf( msg, "\n" ); 
					lcl_str( msg ); 
				} 
				sprintf( msg, "\rtimeout %d   ", ++j ); 
				lcl_str( msg ); 
				i = 0; 
			} 
	if( !is_cmd )	 
		return( rx() );		/* send back char	*/ 
	if( (ch = rx()) == CAN ) 
		error( "Received cancellation request" ); 
	return( ch ); 
} 
 
n( rx() );		/* send back char	*/ 
	if( (ch = rx()) == CAN ) 
		error