📄 zmodem.cpp
字号:
// ********************* START OF ZMODEM.CPP *********************
//
// This file contains all of the source code needed to support
// Zmodem file transfers. This code is directly derived from the
// public domain code released by Chuck Forsberg and Omen
// Technology. The Zmodem enhancements published by Omen
// Technology including variable headers and run length encoding
// are not supported here. The Omen Technology code is available
// with the source code for this book for the curious.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include "rs232.h"
#include "crc.h"
#include "ascii.h"
#include "zmodem.h"
#include "_zmodem.h"
// The two notification routines belong to the base class. There
// isn't any reason to override them in a derived class such as
// Zmodem, but a specific application may want to develop its own
// virtual functions to replace these.
void FileTransfer::error( char *fmt, ... )
{
va_list argptr;
va_start( argptr, fmt );
vprintf( fmt, argptr );
putc( '\n', stdout );
va_end( argptr );
}
void FileTransfer::status( char *fmt, ... )
{
va_list argptr;
va_start( argptr, fmt );
vprintf( fmt, argptr );
putc( '\n', stdout );
va_end( argptr );
}
// The Zmodem constructor only has to initialize a few variables.
Zmodem::Zmodem( RS232 *rs232_port )
{
port = rs232_port;
file_count = 0;
file_name[ 0 ] = '\0';
file_length = -1L;
file = 0;
receiver_buffer_length = 16384;
wake_up_sender_header_type = ZRINIT;
}
// The public Send functionsends a batch of files one at a time
// via the SendSingleFile function. When a normal completion
// occurs, it is flagged with a ZFIN frame.
int Zmodem::Send( char *files[] )
{
PackLongIntoHeader( 0L );
SendHexHeader(4, ZRQINIT, transmitted_header);
GetRinitHeader();
byte_count = -1;
while ( *files ) {
if ( SendSingleFile( *files ) == ERROR )
return ERROR;
files++;
}
SendZFIN();
return OK;
}
// This function is used *everywhere*, and benefits from being
// declared as inline.
inline int Zmodem::ReadChar( long timeout )
{
int c = port->Read( timeout );
return ( c < 0 ) ? TIMEOUT : c;
}
// This is the worker routine that transmits a single file.
int Zmodem::SendSingleFile( char *name )
{
int c;
unsigned long crc_value;
long lastcrcrq = -1;
int length;
file = fopen( name, "rb" );
if ( file == NULL) {
error( "Failed to open %s", name );
return OK;
}
file_at_eof = 0;
fseek( file, 0, SEEK_END );
file_length = ftell( file );
fseek( file, 0, SEEK_SET );
length = sprintf( buffer,
"%s%c%u 0 0 0 0 0",
name,
0,
file_length );
for ( ; ; ) {
PackLongIntoHeader( 0L );
SendBinaryHeader( 4, ZFILE, transmitted_header );
SendDataFrame( buffer, length, ZCRCW );
again:
c = ReadHeader( received_header );
switch ( c ) {
case ZRINIT:
while ( ( c = ReadChar( 5000L ) ) > 0 )
if ( c == ZPAD )
goto again;
/* **** FALL THRU TO **** */
default:
continue;
case ZCAN:
case TIMEOUT:
case ZABORT:
case ZFIN:
return ERROR;
case ZCRC:
if ( received_file_position != lastcrcrq ) {
Crc32 crc( 0xFFFFFFFFL );
lastcrcrq = received_file_position;
fseek( file, 0L, SEEK_SET );
while ( ( ( c = getc( file ) ) != EOF )
&& --lastcrcrq )
crc.update(c );
crc_value = ~crc.value();
fseek( file, 0L, SEEK_SET );
lastcrcrq = received_file_position;
}
PackLongIntoHeader( crc_value );
SendBinaryHeader( 4, ZCRC, transmitted_header );
goto again;
case ZSKIP:
fclose( file );
return OK;
case ZRPOS:
if (fseek(file,received_file_position,SEEK_SET))
return ERROR;
last_sync_position =
(byte_count=transmitted_file_position=
last_reported_position=
received_file_position) - 1;
return SendFileContents();
}
}
}
int Zmodem::SendFileContents( void )
{
int c;
int e;
int n;
int junkcount;
int newcnt;
junkcount = 0;
start_read:
newcnt = receiver_buffer_length;
PackLongIntoHeader( transmitted_file_position );
SendBinaryHeader( 4, ZDATA, transmitted_header );
do {
n = fread( buffer, 1, 1024, file );
if ( n < 1024 )
file_at_eof = 1;
if ( file_at_eof )
e = ZCRCE;
else if ( junkcount > 3 )
e = ZCRCW;
else if ( byte_count == last_sync_position )
e = ZCRCW;
else if ( receiver_buffer_length && ( newcnt -= n ) <= 0 )
e = ZCRCW;
else
e = ZCRCG;
SendDataFrame( buffer, n, e );
byte_count = transmitted_file_position += n;
if ( e == ZCRCW )
goto waitack;
while ( port->RXSpaceUsed() ) {
switch ( ReadChar( 100 ) ) {
case CAN:
case ZPAD:
c = SyncWithReceiver( 1 );
if ( c == ZACK )
break;
SendDataFrame( buffer, 0, ZCRCE );
goto gotack;
case XOFF:
case XOFF | 0x80 :
ReadChar( 10000L );
default:
junkcount++;
}
}
} while ( !file_at_eof );
for ( ; ; ) {
PackLongIntoHeader( transmitted_file_position );
SendBinaryHeader( 4, ZEOF, transmitted_header );
switch ( SyncWithReceiver( 0 ) ) {
case ZACK:
continue;
case ZRPOS:
goto start_read;
case ZRINIT:
return OK;
case ZSKIP:
fclose( file );
return c;
default:
fclose( file );
return ERROR;
}
}
//Backchannel processing
waitack:
junkcount = 0;
c = SyncWithReceiver( 0 );
gotack:
switch ( c ) {
default:
case ZCAN:
fclose( file );
return ERROR;
case ZSKIP:
fclose( file );
return c;
case ZACK:
case ZRPOS:
break;
case ZRINIT:
return OK;
}
while ( port->RXSpaceUsed() ) {
switch ( ReadChar( 100 ) ) {
case CAN:
case ZPAD:
c = SyncWithReceiver( 1 );
goto gotack;
case XOFF :
case XOFF | 0x80 :
ReadChar( 10000L );
}
}
goto start_read;
}
// If all goes well, the public receive function just calls
// WakeUpSender(), then ReceiveFiles(). If both of those
// do what they are supposed to do, a batch of files will
// have been properly transferred.
int Zmodem::Receive( char * )
{
static char CancelString[] = {
CAN, CAN, CAN, CAN, CAN, CAN, CAN, CAN, CAN, CAN,
BS, BS, BS, BS, BS, BS, BS, BS, BS, BS };
switch ( WakeUpSender() ) {
case 0 :
case ZCOMPL : return OK;
case ERROR : break;
default : if ( ReceiveFiles() == OK )
return OK;
}
port->Write( CancelString, sizeof CancelString, 30000L );
if ( file )
fclose( file );
return ERROR;
}
// This is the general purpose receiver function. It calls the
// ReceiveSingleFile function repeatedly as long as the wakeup
// function keeps receiving ZFILE frames.
int Zmodem::ReceiveFiles( void )
{
int return_status ;
for ( ; ; ) {
switch ( return_status = ReceiveSingleFile() ) {
case ZEOF:
case ZSKIP:
switch ( WakeUpSender() ) {
case ZCOMPL:
return OK;
default:
return ERROR;
case ZFILE:
break;
}
continue;
default:
return return_status;
case ERROR:
return ERROR;
}
}
}
// Some data used various places in the class
static char *Zendnames[] = { "ZCRCE", "ZCRCG", "ZCRCQ", "ZCRCW"};
static char *frametypes[] = {
"No Response to Error Correction Request",
"No Carrier Detect",
"TIMEOUT",
"ERROR",
"ZRQINIT", "ZRINIT", "ZSINIT", "ZACK", "ZFILE",
"ZSKIP", "ZNAK", "ZABORT", "ZFIN", "ZRPOS",
"ZDATA", "ZEOF", "ZFERR", "ZCRC", "ZCHALLENGE",
"ZCOMPL", "ZCAN", "ZFREECNT", "ZCOMMAND", "ZSTDERR"
};
// This function is used when receiving files. It sends out the
// initial frame and waits for a response from the sender. If
// things go properly it will get the file data subpacket and
// return ZFILE. If the sender has no more files it will send a
// ZFIN, which is handled here.
int Zmodem::WakeUpSender( void )
{
int c;
int n;
for ( n = 0 ; n < 16 ; n++ ) {
PackLongIntoHeader( 0L );
transmitted_header[ZF0] = CANFC32|CANFDX|CANOVIO|CANBRK;
SendHexHeader( 4,
wake_up_sender_header_type,
transmitted_header );
if ( wake_up_sender_header_type == ZSKIP )
wake_up_sender_header_type = ZRINIT;
for ( int try_again = 1 ; try_again ; ) {
switch ( ReadHeader( received_header ) ) {
case ZRQINIT :
case ZEOF :
case TIMEOUT :
default :
try_again = 0;
break;
case ZFILE :
wake_up_sender_header_type = ZRINIT;
c = ReadDataFrame( buffer, 1024 );
if ( c == GOTCRCW )
return ZFILE;
SendHexHeader( 4, ZNAK, transmitted_header );
break;
case ZSINIT :
if (ReadDataFrame(attention_string, ZATTNLEN)
== GOTCRCW ) {
PackLongIntoHeader( 1L );
SendHexHeader(4,ZACK,transmitted_header);
} else
SendHexHeader(4,ZNAK,transmitted_header);
break;
case ZCOMPL :
break;
case ZFIN :
AckZFIN();
return ZCOMPL;
case ZCAN :
return ERROR;
}
}
}
return 0;
}
// This is the workhorse routine that reads a single file from the
// sender. It reads in headers until it gets a ZDATA header, then
// it switches over to reading data subpackets until it gets one
// of the end of supbacket codes.
int Zmodem::ReceiveSingleFile( void )
{
int c;
int error_count;
long rxbytes;
if ( OpenInputFile( buffer ) == ERROR )
return wake_up_sender_header_type = ZSKIP;
error_count = 0;
rxbytes = 0L;
for ( ; ; ) {
PackLongIntoHeader( rxbytes );
SendHexHeader( 4, ZRPOS, transmitted_header );
nxthdr:
switch ( c = ReadHeader( received_header ) ) {
default:
error( "ReceiveSingleFile: ReadHeader returned %d",
c );
return ERROR;
case ZNAK:
case TIMEOUT:
if ( ++error_count >= 20 ) {
error("ReceiveSingleFile: ReadHeader returned %d",
c );
return ERROR;
}
case ZFILE:
ReadDataFrame( buffer, 1024 );
continue;
case ZEOF:
if (UnpackHeaderIntoLong(received_header)!=rxbytes)
goto nxthdr;
if ( fclose( file ) != 0 ) {
wake_up_sender_header_type = ZFERR;
error( "ReceiveSingleFile: fclose() "
"returned error" );
return ERROR;
}
return c;
case ERROR:
if ( ++error_count >= 20 ) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -